Skip to content

STRICT TYPING IMPLEMENTATION GUIDE

Overview

This guide provides a systematic approach to eliminate ALL any and unknown types from the TendSocial backend.

Goal: Zero any, zero unknown, 100% explicit types everywhere.


Phase 1: Setup (Day 1)

Step 1: Create Type Definitions Directory

bash
cd apps/backend/src/types
mkdir -p types/{platforms,ai,api}
mkdir -p utils/validation

Step 2: Add Core Type Files

Copy these files:

  • strict-types.tssrc/types/index.ts
  • Create individual category files

File structure:

src/types/
├── index.ts              # Re-export all types
├── errors.ts             # Error types and type guards
├── metrics.ts            # PerformanceMetrics, EditMetrics
├── generation.ts         # GenerationContext
├── collaboration.ts      # CollaborationProfile
├── ab-testing.ts         # A/B test types
├── platforms/
│   ├── index.ts
│   ├── linkedin.ts
│   ├── twitter.ts
│   ├── facebook.ts
│   ├── instagram.ts
│   ├── youtube.ts
│   ├── pinterest.ts
│   ├── tiktok.ts
│   ├── google-business.ts
│   ├── threads.ts
│   ├── bluesky.ts
│   ├── mastodon.ts
│   └── reddit.ts
├── ai/
│   ├── index.ts
│   ├── openai.ts
│   ├── anthropic.ts
│   ├── google.ts
│   └── social-post-generation.ts
└── api/
    ├── index.ts
    ├── genai.ts
    ├── posts.ts
    ├── campaigns.ts
    └── reports.ts

Step 3: Create Validation Utilities

Create src/utils/validation.ts:

typescript
import type { z } from 'zod';

/**
 * Validate Prisma JSON field with explicit type
 */
export function validateJsonField<T extends z.ZodType>(
  value: string | number | boolean | object | null | undefined,
  schema: T
): z.infer<T> | null {
  if (value === null || value === undefined) {
    return null;
  }
  
  const result = schema.safeParse(value);
  if (!result.success) {
    console.warn('JSON field validation failed:', result.error.format());
    return null;
  }
  
  return result.data;
}

/**
 * Parse JSON string with explicit type
 */
export function parseJSON<T extends z.ZodType>(
  json: string,
  schema: T
): z.infer<T> {
  const rawData: string | number | boolean | object | null = JSON.parse(json);
  return schema.parse(rawData);
}

/**
 * Safely parse with fallback
 */
export function safeParseJSON<T extends z.ZodType>(
  json: string,
  schema: T,
  fallback: z.infer<T>
): z.infer<T> {
  try {
    return parseJSON(json, schema);
  } catch {
    return fallback;
  }
}

Step 4: Update ESLint Configuration

Update apps/backend/.eslintrc.js:

javascript
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
  plugins: ['@typescript-eslint', 'local'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'plugin:@typescript-eslint/strict',
  ],
  rules: {
    // ZERO TOLERANCE
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/no-unsafe-assignment': 'error',
    '@typescript-eslint/no-unsafe-member-access': 'error',
    '@typescript-eslint/no-unsafe-call': 'error',
    '@typescript-eslint/no-unsafe-return': 'error',
    '@typescript-eslint/no-unsafe-argument': 'error',
    
    // REQUIRE EXPLICIT TYPES
    '@typescript-eslint/explicit-function-return-type': ['error', {
      allowExpressions: false,
      allowTypedFunctionExpressions: true,
      allowHigherOrderFunctions: false,
    }],
    
    '@typescript-eslint/explicit-module-boundary-types': 'error',
    
    // Other strict rules
    '@typescript-eslint/no-non-null-assertion': 'error',
    '@typescript-eslint/strict-boolean-expressions': 'error',
    
    // Keep existing
    'local/require-swagger-tags': 'warn',
  },
  ignorePatterns: ['dist', 'node_modules', '*.js', 'generated'],
};

Step 5: Update tsconfig.json

json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    // ... other settings
  }
}

Phase 2: Define All Types (Week 1)

Priority 1: Platform Types

For EACH platform, create complete type definitions:

typescript
// src/types/platforms.ts
import { z } from 'zod';

// Analytics Response
export interface LinkedInAnalyticsResponse {
  impressions: number;
  likes: number;
  comments: number;
  shares: number;
  engagement_rate: number;
  clicks?: number;
}

export const LinkedInAnalyticsResponseSchema = z.object({
  impressions: z.number().int().nonnegative(),
  likes: z.number().int().nonnegative(),
  comments: z.number().int().nonnegative(),
  shares: z.number().int().nonnegative(),
  engagement_rate: z.number().nonnegative(),
  clicks: z.number().int().nonnegative().optional(),
});

// Account Metadata
export interface LinkedInPageMetadata {
  organizationId: string;
  organizationName: string;
  organizationUrn?: string;
  authorUrn?: string;
  permissions?: string[];
}

export const LinkedInPageMetadataSchema = z.object({
  organizationId: z.string(),
  organizationName: z.string(),
  organizationUrn: z.string().optional(),
  authorUrn: z.string().optional(),
  permissions: z.array(z.string()).optional(),
});

// Publish Request
export interface LinkedInPublishRequest {
  content: string;
  imageUrls?: string[];
  videoUrl?: string;
  articleUrl?: string;
}

export const LinkedInPublishRequestSchema = z.object({
  content: z.string(),
  imageUrls: z.array(z.string().url()).optional(),
  videoUrl: z.string().url().optional(),
  articleUrl: z.string().url().optional(),
});

// Publish Response
export interface LinkedInPublishResponse {
  success: boolean;
  postId?: string;
  postUrl?: string;
  error?: string;
}

Repeat for ALL platforms: Twitter, Facebook, Instagram, YouTube, Pinterest, TikTok, Google Business, Threads, Bluesky, Mastodon, Reddit

Priority 2: AI Provider Types

typescript
// src/types/ai.ts
export interface OpenAICompletionResponse {
  id: string;
  object: string;
  created: number;
  model: string;
  choices: Array<{
    index: number;
    message: {
      role: 'system' | 'user' | 'assistant';
      content: string;
    };
    finish_reason: 'stop' | 'length' | 'content_filter';
  }>;
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
}

// Similar for Anthropic, Google, etc.

Priority 3: API Request/Response Types

typescript
// src/types/api.ts
export interface GenerateSocialPostsRequest {
  blogUrl: string;
  brandingUrl?: string;
  selectedProfiles: string[];
  includeCta?: boolean;
  hashtags?: string;
  hashtagCount?: number;
  imageCount?: number;
  imageStyle?: string;
  imageKeywords?: string;
  variationInstruction?: string;
}

export const GenerateSocialPostsRequestSchema = z.object({
  blogUrl: z.string().url(),
  brandingUrl: z.string().url().optional(),
  selectedProfiles: z.array(z.string()).min(1),
  includeCta: z.boolean().default(true),
  hashtags: z.string().optional(),
  hashtagCount: z.number().int().positive().default(3),
  imageCount: z.number().int().positive().default(1),
  imageStyle: z.string().default('Photorealistic'),
  imageKeywords: z.string().optional(),
  variationInstruction: z.string().optional(),
});

Phase 3: Fix Job Files (Week 2)

Job: abTestResults.job.ts

  1. Import types:
typescript
import type { ABTestVariant, ABTestResults } from '@/types/ab-testing.js';
import { validateJsonField } from '@/types/index.js';
import { PerformanceMetricsSchema } from '@/types/metrics.js';
  1. Replace line 51:
typescript
const variants: ABTestVariant[] = z.array(ABTestVariantSchema).parse(test.variants);
  1. Replace line 107:
typescript
const metrics = validateJsonField(post.performanceMetrics, PerformanceMetricsSchema);
if (metrics !== null) {
  results[variant].impressions += metrics.impressions ?? 0;
}
  1. Replace line 120:
typescript
results: results as object, // Prisma Json accepts object

Job: exampleCuration.job.ts

  1. Import types
  2. Fix error handling (line 34)
  3. Fix metrics access (lines 62-70)
  4. Fix context access (lines 86-87)

Repeat for ALL job files


Phase 4: Fix Platform Adapters (Week 3)

For each platform adapter (e.g., src/adapters/platforms/linkedin-pages.ts):

  1. Import platform-specific types
  2. Replace metadata access
  3. Replace analytics parsing
  4. Add proper error handling

Example:

typescript
import { 
  LinkedInPageMetadataSchema,
  LinkedInAnalyticsResponseSchema,
  type LinkedInPageMetadata,
  type LinkedInAnalyticsResponse 
} from '../../types/platforms/linkedin';

// In the file
const metadata: LinkedInPageMetadata = LinkedInPageMetadataSchema.parse(account.metadata);

// For API responses
const rawResponse: string | number | boolean | object | null = await response.json();
const analytics: LinkedInAnalyticsResponse = LinkedInAnalyticsResponseSchema.parse(rawResponse);

Phase 5: Fix AI Adapters (Week 4)

direct.adapter.ts, vercel-gateway.adapter.ts, vercel.adapter.ts

  1. Define response types for each AI provider
  2. Replace all any with explicit types
  3. Add validation for responses
typescript
// Example for OpenAI
import { OpenAICompletionResponseSchema, type OpenAICompletionResponse } from '../../types/ai/openai';

const rawData: string | number | boolean | object | null = await response.json();
const data: OpenAICompletionResponse = OpenAICompletionResponseSchema.parse(rawData);

return {
  text: data.choices[0].message.content,
  usage: {
    inputTokens: data.usage.prompt_tokens,
    outputTokens: data.usage.completion_tokens,
    totalTokens: data.usage.total_tokens,
  },
};

Phase 6: Fix Route Handlers (Week 5)

For each route file:

  1. Define request types
  2. Define response types
  3. Validate inputs
  4. Type outputs
typescript
// Example pattern
import { z } from 'zod';
import type { ErrorResponse } from '@/types/errors.js';

const RequestSchema = z.object({
  field1: z.string(),
  field2: z.number(),
});

type Request = z.infer<typeof RequestSchema>;

app.post('/api/endpoint', async (req, res) => {
  try {
    const input: Request = RequestSchema.parse(req.body);
    const result = await service.process(input);
    res.json(result);
  } catch (error: Error | ErrorResponse | z.ZodError) {
    // Handle appropriately
  }
});

Phase 7: Fix Error Handling Everywhere (Week 6)

Replace ALL instances of:

typescript
catch (error: any) { ... }
catch (error: unknown) { ... }
catch (error) { ... } // implicit any

With:

typescript
import type { ErrorResponse } from '@/types/errors.js';
import { isError, getErrorMessage } from '@/types/errors.js';

catch (error: Error | ErrorResponse) {
  if (isError(error)) {
    logger.error(error.message, { stack: error.stack });
  } else {
    logger.error(getErrorMessage(error));
  }
}

Automated Detection

Find all 'any' usage:

bash
cd apps/backend/src
grep -r ": any" . --include="*.ts" | wc -l

Find all 'unknown' usage:

bash
grep -r ": unknown" . --include="*.ts" | wc -l

Run lint to find violations:

bash
pnpm run lint:backend 2>&1 | grep "no-explicit-any"

Testing Strategy

Unit Tests

typescript
describe('validateJsonField', () => {
  it('should validate correct data', () => {
    const data = { impressions: 100, likes: 50 };
    const result = validateJsonField(data, PerformanceMetricsSchema);
    expect(result).not.toBeNull();
    expect(result?.impressions).toBe(100);
  });
  
  it('should return null for invalid data', () => {
    const data = { impressions: -100 }; // negative not allowed
    const result = validateJsonField(data, PerformanceMetricsSchema);
    expect(result).toBeNull();
  });
});

Integration Tests

  • Test API endpoints with invalid data
  • Verify Zod validation rejects bad input
  • Test all platform adapters

AI Code Generation Templates

Template 1: New Route Handler

typescript
// COPY THIS TEMPLATE FOR NEW ROUTES
import { z } from 'zod';
import type { ErrorResponse } from '@/types/errors.js';
import { getErrorMessage } from '@/types/errors.js';

const RequestSchema = z.object({
  // Define all fields
});

type RequestType = z.infer<typeof RequestSchema>;

const ResponseSchema = z.object({
  // Define all fields
});

type ResponseType = z.infer<typeof ResponseSchema>;

export async function handler(req: Request, res: Response): Promise<void> {
  try {
    const input: RequestType = RequestSchema.parse(req.body);
    const result: ResponseType = await process(input);
    res.json(result);
  } catch (error: Error | ErrorResponse | z.ZodError) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ error: 'Validation failed', details: error.format() });
    } else {
      res.status(500).json({ error: getErrorMessage(error) });
    }
  }
}

Template 2: Platform API Call

typescript
// COPY THIS TEMPLATE FOR PLATFORM APIs
import { {Platform}ResponseSchema, type {Platform}Response } from '@/types/platforms.js';

export async function fetch{Platform}Data(
  accessToken: string,
  resourceId: string
): Promise<{Platform}Response> {
  const response = await fetch(`https://api.{platform}.com/{endpoint}`);
  
  if (!response.ok) {
    throw new Error(`{Platform} API error: ${response.statusText}`);
  }
  
  const rawData: string | number | boolean | object | null = await response.json();
  const data: {Platform}Response = {Platform}ResponseSchema.parse(rawData);
  
  return data;
}

Success Metrics

Track weekly:

MetricTarget
Total any count0
Total unknown count0
ESLint errors0
TypeScript errors0
Test coverage>80%

Rollout Schedule

WeekFocusDeliverable
1Setup + Type DefinitionsAll type files created
2Job FilesAll jobs strictly typed
3Platform AdaptersAll platforms strictly typed
4AI AdaptersAll AI calls strictly typed
5Route HandlersAll routes strictly typed
6Error HandlingAll catches strictly typed

Final Checklist

Before marking complete:

  • [ ] Zero any in entire codebase
  • [ ] Zero unknown in entire codebase
  • [ ] All Prisma JSON fields have types
  • [ ] All external APIs have response types
  • [ ] All routes validate input/output
  • [ ] All catches use Error | ErrorResponse
  • [ ] ESLint passes with strict rules
  • [ ] TypeScript compiles with no errors
  • [ ] Tests pass
  • [ ] Documentation updated

Implementation Guide v1.0Last Updated: January 2026Zero Tolerance: No 'any', No 'unknown'

TendSocial Documentation