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/validationStep 2: Add Core Type Files
Copy these files:
strict-types.ts→src/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.tsStep 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
- Import types:
typescript
import type { ABTestVariant, ABTestResults } from '@/types/ab-testing.js';
import { validateJsonField } from '@/types/index.js';
import { PerformanceMetricsSchema } from '@/types/metrics.js';- Replace line 51:
typescript
const variants: ABTestVariant[] = z.array(ABTestVariantSchema).parse(test.variants);- Replace line 107:
typescript
const metrics = validateJsonField(post.performanceMetrics, PerformanceMetricsSchema);
if (metrics !== null) {
results[variant].impressions += metrics.impressions ?? 0;
}- Replace line 120:
typescript
results: results as object, // Prisma Json accepts objectJob: exampleCuration.job.ts
- Import types
- Fix error handling (line 34)
- Fix metrics access (lines 62-70)
- 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):
- Import platform-specific types
- Replace metadata access
- Replace analytics parsing
- 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
- Define response types for each AI provider
- Replace all
anywith explicit types - 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:
- Define request types
- Define response types
- Validate inputs
- 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 anyWith:
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 -lFind all 'unknown' usage:
bash
grep -r ": unknown" . --include="*.ts" | wc -lRun 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:
| Metric | Target |
|---|---|
Total any count | 0 |
Total unknown count | 0 |
| ESLint errors | 0 |
| TypeScript errors | 0 |
| Test coverage | >80% |
Rollout Schedule
| Week | Focus | Deliverable |
|---|---|---|
| 1 | Setup + Type Definitions | All type files created |
| 2 | Job Files | All jobs strictly typed |
| 3 | Platform Adapters | All platforms strictly typed |
| 4 | AI Adapters | All AI calls strictly typed |
| 5 | Route Handlers | All routes strictly typed |
| 6 | Error Handling | All catches strictly typed |
Final Checklist
Before marking complete:
- [ ] Zero
anyin entire codebase - [ ] Zero
unknownin 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'