Back to Articles
45 min read

Production-Grade TypeScript: Architecture, Tooling, and Ecosystem

The final frontier of TypeScript engineering. This guide moves from code to infrastructure: manipulating the AST with the Compiler API, optimizing compilation speeds, setting up enterprise-grade monorepos, and bridging the gap between compile-time checks and runtime reality with Zod and tRPC.

TypeScript Senior/Expert Level Topics

Advanced Generic Patterns

Advanced generics involve conditional types, mapped types, and recursive type definitions to create flexible, reusable type abstractions that adapt based on input types.

// Conditional + Mapped + Recursive type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; }; type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T; type Result = UnwrapPromise<Promise<Promise<string>>>; // string

Higher-Kinded Types Simulation

TypeScript lacks native HKTs, but we simulate them using interface merging and "type lambdas" through a registry pattern to abstract over type constructors.

// HKT simulation via URI registry interface URItoKind<A> { Array: Array<A>; Option: Option<A>; } type URIS = keyof URItoKind<any>; type Kind<F extends URIS, A> = URItoKind<A>[F]; interface Functor<F extends URIS> { map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>; }

Type-Level Programming

Using TypeScript's type system as a compile-time programming language to perform computations, validations, and transformations entirely at the type level.

// Type-level arithmetic type BuildTuple<N, T extends any[] = []> = T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>; type Add<A extends number, B extends number> = [...BuildTuple<A>, ...BuildTuple<B>]['length']; type Sum = Add<3, 4>; // 7 // Type-level string parsing type ParseInt<S> = S extends `${infer N extends number}` ? N : never; type Five = ParseInt<"5">; // 5

Phantom Types

Phantom types are type parameters that don't appear in runtime values but enforce compile-time constraints, useful for state machines and validation.

type Validated = { readonly _brand: 'validated' }; type Unvalidated = { readonly _brand: 'unvalidated' }; type Email<State> = string & { readonly _state: State }; function createEmail(input: string): Email<Unvalidated> { return input as Email<Unvalidated>; } function validate(email: Email<Unvalidated>): Email<Validated> | null { return email.includes('@') ? email as Email<Validated> : null; } function sendEmail(email: Email<Validated>) { /* only validated! */ }

Builder Pattern with Types

Combining the builder pattern with TypeScript's type system to track which properties have been set, ensuring compile-time completeness.

type BuilderState = { name: boolean; age: boolean }; class UserBuilder<S extends BuilderState = { name: false; age: false }> { private data: Partial<User> = {}; setName(name: string): UserBuilder<S & { name: true }> { this.data.name = name; return this as any; } setAge(age: number): UserBuilder<S & { age: true }> { this.data.age = age; return this as any; } build(this: UserBuilder<{ name: true; age: true }>): User { return this.data as User; } } new UserBuilder().setName("John").setAge(30).build(); // ✓ new UserBuilder().setName("John").build(); // ✗ Error!

Advanced Utility Type Creation

Creating sophisticated utility types that compose primitives like infer, conditional types, and template literals for complex transformations.

// Deep partial with array handling type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; // Get all nested paths as union type Paths<T, P extends string = ''> = T extends object ? { [K in keyof T & string]: P extends '' ? K | Paths<T[K], K> : `${P}.${K}` | Paths<T[K], `${P}.${K}`> }[keyof T & string] : never; type UserPaths = Paths<{ a: { b: { c: number } } }>; // "a" | "a.b" | "a.b.c"

Type-Safe Event Emitters

Creating event emitters where event names and their payload types are strictly enforced at compile time.

type EventMap = { login: { userId: string; timestamp: Date }; logout: { userId: string }; error: Error; }; class TypedEmitter<T extends Record<string, any>> { private listeners = new Map<keyof T, Set<Function>>(); on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void { if (!this.listeners.has(event)) this.listeners.set(event, new Set()); this.listeners.get(event)!.add(handler); } emit<K extends keyof T>(event: K, payload: T[K]): void { this.listeners.get(event)?.forEach(fn => fn(payload)); } } const emitter = new TypedEmitter<EventMap>(); emitter.on('login', (e) => console.log(e.userId)); // e is typed! emitter.emit('login', { userId: '123', timestamp: new Date() });

Type-Safe Dependency Injection

Implementing DI containers where dependencies are registered and resolved with full type inference and compile-time validation.

type TokenMap = {}; class Container { private deps = new Map<symbol, any>(); register<K extends string, T>( token: K, factory: () => T ): asserts this is this & { [P in K]: T } { const sym = Symbol.for(token); this.deps.set(sym, factory()); } resolve<K extends keyof this>(token: K): this[K] { return this.deps.get(Symbol.for(token as string)); } } // Usage with inference const container = new Container(); container.register('logger', () => new Logger()); container.register('db', () => new Database()); const logger = container.resolve('logger'); // Logger type inferred

Advanced Decorator Patterns

Using decorators with metadata reflection for AOP patterns like validation, caching, and method interception with full type preservation.

import 'reflect-metadata'; function Validate<T>(schema: (val: T) => boolean) { return function (target: any, key: string, desc: PropertyDescriptor) { const original = desc.value; desc.value = function (...args: T[]) { args.forEach((arg, i) => { if (!schema(arg)) throw new Error(`Validation failed for arg ${i}`); }); return original.apply(this, args); }; }; } // Metadata-based DI function Injectable(): ClassDecorator { return (target) => { const params = Reflect.getMetadata('design:paramtypes', target) || []; Reflect.defineMetadata('di:dependencies', params, target); }; }

Compiler API

TypeScript's Compiler API allows programmatic access to parsing, type-checking, and code generation for building custom tools.

import * as ts from 'typescript'; const code = `const x: number = 42;`; const sourceFile = ts.createSourceFile( 'temp.ts', code, ts.ScriptTarget.Latest, true ); // Create program for type checking const program = ts.createProgram(['file.ts'], {}); const checker = program.getTypeChecker(); // Get type information const symbol = checker.getSymbolAtLocation(node); const type = checker.getTypeOfSymbolAtLocation(symbol, node); console.log(checker.typeToString(type));

Custom Transformers

Transformers modify the AST during compilation, enabling compile-time code generation, macro-like behavior, and optimization.

import * as ts from 'typescript'; const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => { return (sourceFile) => { const visitor = (node: ts.Node): ts.Node => { // Replace all string literals with uppercase if (ts.isStringLiteral(node)) { return ts.factory.createStringLiteral(node.text.toUpperCase()); } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor) as ts.SourceFile; }; }; // Usage in tsconfig or programmatically // "customTransformers": { "before": [transformer] }

AST Manipulation

Understanding and manipulating TypeScript's Abstract Syntax Tree for code analysis, refactoring, and generation tools.

                    SourceFile
                        │
          ┌─────────────┼─────────────┐
          │             │             │
    ImportDecl    FunctionDecl   ExportDecl
                        │
              ┌─────────┼─────────┐
              │         │         │
          Identifier  Params     Block
              │                    │
           "greet"          ReturnStatement
                                   │
                            BinaryExpr
                               │
                    ┌──────────┼──────────┐
                    │          │          │
                 String       "+"    Identifier
function analyzeAST(sourceFile: ts.SourceFile) { ts.forEachChild(sourceFile, function visit(node) { console.log(`${ts.SyntaxKind[node.kind]} at ${node.pos}-${node.end}`); ts.forEachChild(node, visit); }); }

Writing Custom Lint Rules

Creating ESLint rules with TypeScript's type information via @typescript-eslint/utils for semantic linting beyond syntax.

import { ESLintUtils } from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator(name => `https://example.com/${name}`); export const noFloatingPromises = createRule({ name: 'no-floating-promises', meta: { type: 'problem', messages: { floating: 'Promises must be awaited or returned' }, schema: [], }, defaultOptions: [], create(context) { const services = ESLintUtils.getParserServices(context); const checker = services.program.getTypeChecker(); return { ExpressionStatement(node) { const type = checker.getTypeAtLocation( services.esTreeNodeToTSNodeMap.get(node.expression) ); if (type.symbol?.name === 'Promise') { context.report({ node, messageId: 'floating' }); } }, }; }, });

Performance Optimization

Key strategies for reducing TypeScript compilation times and type-checking overhead in large codebases.

┌─────────────────────────────────────────────────────────────┐
│                 PERFORMANCE CHECKLIST                       │
├─────────────────────────────────────────────────────────────┤
│ ✓ Use `interface` over `type` for object shapes (faster)    │
│ ✓ Avoid deep conditional types (max 3-4 levels)            │
│ ✓ Use `skipLibCheck: true` in tsconfig                     │
│ ✓ Enable `incremental: true`                               │
│ ✓ Prefer `unknown` over complex union exhaustion           │
│ ✓ Avoid `export * from` (slows resolution)                 │
│ ✓ Use project references for large codebases              │
│ ✓ Profile with `--generateTrace` and analyze               │
└─────────────────────────────────────────────────────────────┘
tsc --generateTrace ./trace npx @typescript/analyze-trace ./trace

Project References

Project references enable modular compilation, allowing independent builds of sub-projects with proper dependency ordering.

monorepo/
├── tsconfig.json          # Root config with references
├── packages/
│   ├── core/
│   │   ├── tsconfig.json  # "composite": true
│   │   └── src/
│   ├── api/
│   │   ├── tsconfig.json  # references: [{ path: "../core" }]
│   │   └── src/
// packages/api/tsconfig.json { "compilerOptions": { "composite": true, "declaration": true, "declarationMap": true, "outDir": "./dist" }, "references": [ { "path": "../core" } ] }
tsc --build # Builds in dependency order tsc --build --watch # Incremental watch across projects

Incremental Compilation

Incremental mode caches compilation info to .tsbuildinfo files, dramatically speeding up subsequent builds.

// tsconfig.json { "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./dist/.tsbuildinfo" } }
First build:    ████████████████████ 15.2s
Second build:   ███                   2.1s  (86% faster)

.tsbuildinfo contains:
├── File versions (hashes)
├── Dependency graph
├── Emitted file signatures
└── Semantic diagnostics cache

Watch Mode Optimization

Optimizing --watch for fast feedback loops using OS file watchers and reduced scope checking.

{ "compilerOptions": { "incremental": true }, "watchOptions": { "watchFile": "useFsEvents", // macOS/Linux native "watchDirectory": "useFsEvents", "fallbackPolling": "dynamicPriority", "synchronousWatchDirectory": true, "excludeDirectories": ["node_modules", "dist"] } }
# Ultra-fast watch with SWC npx ts-node --swc --watch src/index.ts # Or with transpileOnly (skips type-checking) tsc --watch --noEmit & nodemon dist/index.js

Monorepo TypeScript Setup

Configuring TypeScript for monorepos using workspaces, project references, and shared configurations.

monorepo/
├── package.json              # workspaces: ["packages/*"]
├── tsconfig.base.json        # Shared compiler options
├── tsconfig.json             # Root with all references
└── packages/
    ├── shared/
    │   ├── package.json      # "name": "@mono/shared"
    │   └── tsconfig.json
    ├── web/
    │   └── tsconfig.json     # extends + references
    └── api/
        └── tsconfig.json
// tsconfig.base.json { "compilerOptions": { "strict": true, "composite": true, "declaration": true, "declarationMap": true, "moduleResolution": "bundler" } } // packages/api/tsconfig.json { "extends": "../../tsconfig.base.json", "references": [{ "path": "../shared" }], "compilerOptions": { "rootDir": "./src", "outDir": "./dist" } }

Path Mapping and Module Resolution

Configuring path aliases and understanding TypeScript's module resolution for clean imports and proper bundler integration.

// tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils": ["src/utils/index.ts"], "#server/*": ["src/server/*"] // Node subpath pattern }, "moduleResolution": "bundler", // Modern bundlers // or "nodenext" for pure Node.js "moduleSuffixes": [".ios", ""] // React Native platform ext } }
Resolution order for "import { foo } from '@utils'"
──────────────────────────────────────────────────────
1. tsconfig paths → src/utils/index.ts
2. node_modules/@utils
3. Error: Cannot find module

⚠️  Runtime also needs configuration!
   - Vite: vite-tsconfig-paths
   - Node: tsconfig-paths or tsx
   - Jest: moduleNameMapper

Composite Projects

Composite projects enable large TypeScript codebases to be split into smaller, independently compilable projects using references in tsconfig.json, enabling incremental builds and better IDE performance through project boundaries.

┌─────────────────────────────────────────────────┐
│  monorepo/                                      │
│  ├── packages/                                  │
│  │   ├── core/          ←──────┐               │
│  │   │   └── tsconfig.json     │ references    │
│  │   ├── api/           ───────┤               │
│  │   │   └── tsconfig.json     │               │
│  │   └── web/           ───────┘               │
│  │       └── tsconfig.json                     │
│  └── tsconfig.base.json (shared settings)      │
└─────────────────────────────────────────────────┘
// packages/api/tsconfig.json { "compilerOptions": { "composite": true, "outDir": "./dist" }, "references": [{ "path": "../core" }] }
tsc --build --watch # Incremental builds across all projects

Type-safe API Design Patterns

Design APIs that leverage TypeScript's type system to catch errors at compile time, using branded types, discriminated unions, and builder patterns to make invalid states unrepresentable.

// Branded types for type-safe IDs type UserId = string & { readonly __brand: unique symbol }; type OrderId = string & { readonly __brand: unique symbol }; const createUserId = (id: string): UserId => id as UserId; // Discriminated unions for state machines type RequestState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }; // Builder pattern with type accumulation class QueryBuilder<T extends Record<string, unknown> = {}> { select<K extends string>(field: K): QueryBuilder<T & Record<K, unknown>> { return this as any; } }

Type-safe ORMs and Query Builders

Modern TypeScript ORMs like Prisma, Drizzle, and Kysely provide end-to-end type safety by inferring types from your schema, ensuring queries are validated at compile time and results are fully typed.

// Prisma: Schema-first, generated types const user = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true } // Auto-typed relation }); // user.posts is Post[] - fully typed! // Drizzle: TypeScript-first schema import { pgTable, serial, text } from 'drizzle-orm/pg-core'; const users = pgTable('users', { id: serial('id').primaryKey(), name: text('name').notNull(), }); const result = await db.select().from(users).where(eq(users.id, 1)); // result: { id: number; name: string }[] // Kysely: Query builder with inference const result = await db .selectFrom('users') .select(['id', 'name']) .execute(); // Typed from DB schema

Integration with Build Tools

Each bundler has different TypeScript handling strategies: webpack uses ts-loader or babel-loader, esbuild/swc strip types without type-checking for speed, and rollup uses plugins—all requiring separate tsc for type validation.

┌──────────────────────────────────────────────────────────┐
│                   Build Pipeline                          │
├──────────────┬──────────────┬──────────────┬─────────────┤
│   webpack    │    esbuild   │     swc      │   rollup    │
├──────────────┼──────────────┼──────────────┼─────────────┤
│ ts-loader    │ Native TS    │ @swc/core    │ @rollup/    │
│ fork-ts-     │ (strips      │ (strips      │ plugin-     │
│ checker     │ types only)  │ types only)  │ typescript  │
├──────────────┴──────────────┴──────────────┴─────────────┤
│  ⚠️  esbuild/swc: Run `tsc --noEmit` separately!        │
└──────────────────────────────────────────────────────────┘
// vite.config.ts (uses esbuild) export default defineConfig({ plugins: [react()], esbuild: { target: 'esnext' } }); // package.json - parallel type checking { "scripts": { "build": "tsc --noEmit && vite build" } }

TypeScript with Node.js (ESM vs CJS)

Node.js TypeScript projects must choose between CommonJS ("module": "commonjs") and ES Modules ("module": "nodenext"), with ESM requiring explicit .js extensions in imports and "type": "module" in package.json.

┌─────────────────────────────────────────────────────────┐
│           CommonJS vs ES Modules in Node.js             │
├────────────────────────┬────────────────────────────────┤
│       CommonJS         │          ES Modules            │
├────────────────────────┼────────────────────────────────┤
│ require() / exports    │ import / export                │
│ Synchronous loading    │ Async, static analysis         │
│ __dirname available    │ import.meta.url                │
│ .js or .cjs extension  │ .js or .mjs extension          │
│ No file ext in imports │ MUST include .js extension     │
└────────────────────────┴────────────────────────────────┘
// tsconfig.json for ESM { "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", "outDir": "./dist" } }
// ESM requires .js extension (even for .ts files!) import { helper } from './utils.js'; // ✅ Correct import { helper } from './utils'; // ❌ Fails at runtime

TypeScript with React/Vue/Angular Advanced Patterns

Each framework leverages TypeScript differently: React uses generics for component props and hooks, Vue 3 uses defineComponent with Composition API, and Angular uses decorators with strict template type-checking.

// React: Generic components & typed hooks interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; } function List<T>({ items, renderItem }: ListProps<T>) { return <>{items.map(renderItem)}</>; } // Vue 3: Typed props with defineComponent defineComponent({ props: { userId: { type: Number, required: true } }, setup(props) { const user = ref<User | null>(null); // props.userId is number } }); // Angular: Strict templates @Component({ template: `<div>{{ user.name }}</div>` // Error if user is possibly null }) export class UserComponent { @Input() user!: User; }

Testing TypeScript Applications

Testing TypeScript requires configuring your test runner (Jest/Vitest) to handle TS files, with Vitest offering native support, while Jest needs ts-jest or SWC; use type-safe mocking libraries like jest-mock-extended.

// vitest.config.ts - native TS support import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node' } }); // Type-safe mocking with jest-mock-extended import { mock, MockProxy } from 'jest-mock-extended'; interface UserService { getUser(id: number): Promise<User>; } describe('UserController', () => { let mockService: MockProxy<UserService>; beforeEach(() => { mockService = mock<UserService>(); mockService.getUser.calledWith(1).mockResolvedValue({ id: 1, name: 'Test' }); }); });

Migration Strategies from JavaScript

Migrate incrementally by enabling allowJs and checkJs, renaming files from .js to .ts one at a time, starting with leaf modules, using // @ts-ignore temporarily, and progressively tightening strict mode options.

┌─────────────────────────────────────────────────────────┐
│              Migration Strategy Phases                   │
├─────────────────────────────────────────────────────────┤
│  Phase 1: Setup                                         │
│  ├── Add tsconfig.json with allowJs: true               │
│  ├── Set strict: false initially                        │
│  └── Install @types/* packages                          │
├─────────────────────────────────────────────────────────┤
│  Phase 2: Gradual conversion (leaf → root)              │
│  ├── utils.js → utils.ts                                │
│  ├── services/*.js → services/*.ts                      │
│  └── app.js → app.ts                                    │
├─────────────────────────────────────────────────────────┤
│  Phase 3: Enable strict incrementally                   │
│  ├── noImplicitAny: true                                │
│  ├── strictNullChecks: true                             │
│  └── strict: true (final goal)                          │
└─────────────────────────────────────────────────────────┘
// Initial permissive config { "compilerOptions": { "allowJs": true, "checkJs": true, "strict": false } }

Contribution to DefinitelyTyped

Contributing to DefinitelyTyped involves forking the repo, creating types in types/<package>/index.d.ts, adding tests, and submitting a PR—following strict naming conventions and ensuring compatibility with the package's API.

# Clone and setup git clone https://github.com/DefinitelyTyped/DefinitelyTyped.git cd DefinitelyTyped npm install # Create new type definition mkdir types/my-library
// types/my-library/index.d.ts declare module 'my-library' { export function doSomething(input: string): number; export interface Config { timeout?: number; } } // types/my-library/my-library-tests.ts import { doSomething } from 'my-library'; doSomething('test'); // $ExpectType number
// types/my-library/package.json (required metadata) { "name": "@types/my-library", "version": "1.0.0", "types": "index.d.ts" }

Writing Publishable Type Definitions

Publishable type definitions should include proper exports, JSDoc comments, support for multiple module systems, and be tested with dtslint or tsd—either bundled with your package or published separately to @types.

// src/index.ts - Source with embedded types /** * Formats a date to ISO string * @param date - The date to format * @returns ISO formatted string */ export function formatDate(date: Date): string { return date.toISOString(); } // package.json { "name": "my-package", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" } } } // tsconfig.json { "compilerOptions": { "declaration": true, "declarationMap": true } }

TypeScript ESLint Advanced Configuration

TypeScript ESLint requires @typescript-eslint/parser and rules that leverage type information via parserOptions.project, enabling powerful rules like no-floating-promises and strict-boolean-expressions.

// eslint.config.js (flat config - ESLint 9+) import tseslint from 'typescript-eslint'; export default tseslint.config( ...tseslint.configs.strictTypeChecked, { languageOptions: { parserOptions: { project: './tsconfig.json', tsconfigRootDir: import.meta.dirname, }, }, rules: { '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/strict-boolean-expressions': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', }, } );

Runtime Type Validation (zod, io-ts, typia)

Runtime validators bridge the compile-time/runtime gap: Zod provides fluent schema building, io-ts offers functional programming style with Either types, and Typia generates validators at compile-time for zero runtime overhead.

// Zod: Most popular, fluent API import { z } from 'zod'; const UserSchema = z.object({ id: z.number(), email: z.string().email(), role: z.enum(['admin', 'user']) }); type User = z.infer<typeof UserSchema>; // Infer TS type const result = UserSchema.safeParse(input); // Runtime validation // io-ts: Functional style import * as t from 'io-ts'; const User = t.type({ id: t.number, name: t.string }); const decoded = User.decode(input); // Either<Errors, User> // Typia: Compile-time generation (fastest) import typia from 'typia'; interface User { id: number; email: string; } const validate = typia.createValidate<User>(); // Zero overhead

End-to-end Type Safety (tRPC, GraphQL Codegen)

End-to-end type safety ensures types flow from backend to frontend automatically: tRPC shares types via TypeScript inference, while GraphQL codegen generates types from your schema and operations.

┌─────────────────────────────────────────────────────────┐
│              End-to-End Type Flow                        │
│                                                          │
│   Backend                           Frontend             │
│  ┌─────────┐    Type Inference    ┌─────────┐          │
│  │  tRPC   │ ←──────────────────→ │ Client  │          │
│  │ Router  │   (no codegen!)      │         │          │
│  └─────────┘                      └─────────┘          │
│                                                          │
│  ┌─────────┐    Code Generation   ┌─────────┐          │
│  │ GraphQL │ ────────────────────→│ Types + │          │
│  │ Schema  │    (graphql-codegen) │ Hooks   │          │
│  └─────────┘                      └─────────┘          │
└─────────────────────────────────────────────────────────┘
// tRPC: Shared types via inference // server.ts const appRouter = router({ user: { get: publicProcedure.input(z.number()).query(({ input }) => db.user.find(input)) } }); export type AppRouter = typeof appRouter; // client.ts const user = await trpc.user.get.query(1); // Fully typed!

WebAssembly with TypeScript

TypeScript integrates with WebAssembly through AssemblyScript (a TypeScript subset compiling to Wasm) or by consuming Wasm modules with typed bindings, enabling high-performance computation in browsers and Node.js.

// AssemblyScript: TypeScript-like syntax → Wasm // assembly/index.ts export function fibonacci(n: i32): i32 { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } // Consuming Wasm in TypeScript // types/wasm.d.ts declare module '*.wasm' { const module: { fibonacci: (n: number) => number; }; export default module; } // app.ts import wasmModule from './optimized.wasm'; const result = wasmModule.fibonacci(40); // Type-safe!
# Compile AssemblyScript npx asc assembly/index.ts -o build/optimized.wasm --optimize

TypeScript in Serverless Environments

Serverless TypeScript requires fast cold starts (favor esbuild/swc bundling), minimal bundle sizes, and typed handler interfaces—with platforms like AWS Lambda, Vercel, and Cloudflare Workers each having specific type packages.

// AWS Lambda with typed handlers import { APIGatewayProxyHandler } from 'aws-lambda'; export const handler: APIGatewayProxyHandler = async (event) => { const body = JSON.parse(event.body || '{}'); return { statusCode: 200, body: JSON.stringify({ message: 'Success' }) }; }; // Vercel Serverless Function import type { VercelRequest, VercelResponse } from '@vercel/node'; export default function handler(req: VercelRequest, res: VercelResponse) { res.json({ timestamp: Date.now() }); }
# serverless.yml with esbuild plugin (fast builds) plugins: - serverless-esbuild custom: esbuild: bundle: true minify: true

Edge Runtime Considerations

Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) have limited APIs compared to Node.js—no fs, restricted crypto, and different global objects—requiring careful type configuration and edge-compatible libraries.

┌─────────────────────────────────────────────────────────┐ │ Edge vs Node.js Runtime APIs │ ├─────────────────────┬───────────────────────────────────┤ │ Available │ Not Available │ ├─────────────────────┼───────────────────────────────────┤ │ fetch, Request │ fs, path, child_process │ │ Response, Headers │ Node.js native modules │ │ crypto.subtle │ Full crypto module │ │ TextEncoder/Decoder │ Buffer (use Uint8Array) │ │ WebSocket │ net, http modules │ └─────────────────────┴───────────────────────────────────┘
// Cloudflare Workers typed handler export interface Env { MY_KV: KVNamespace; DB: D1Database; } export default { async fetch(request: Request, env: Env): Promise<Response> { const data = await env.MY_KV.get('key'); return new Response(data); } }; // tsconfig.json for edge { "compilerOptions": { "types": ["@cloudflare/workers-types"], "lib": ["ES2022"] } }