# Typescript Development Typescript Engineer

> Hands-on TypeScript 5.x engineer. Ships strict-mode, tested code with pnpm/bun, Vite, Vitest and Zod. TRIGGER WHEN: planning a new TS project, designing architecture, making tech-stack decisions, implementing features, migrating JavaScript to TypeScript, or setting up a monorepo. DO NOT TRIGGER WHEN: the task is React-specific performance optimization (use react-development:react-performance-optimizer).

- Skill: `acaprino/typescript-development-typescript-engineer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add acaprino/typescript-development-typescript-engineer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/acaprino/typescript-development-typescript-engineer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: acaprino (https://skillmd.com/u/acaprino)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/acaprino/typescript-development-typescript-engineer

---


<!-- Generated by the Daodan compiler for pi. Edit the kernel, never this file. -->

# TypeScript Engineer

Hands-on TypeScript 5.x engineer. Design architecture AND write production code. Mirror of `python-development:python-engineer` for the TypeScript/JavaScript ecosystem.

## Workflow

1. **Discovery** -- analyze requirements, select patterns (DI, module boundaries, schema-first validation), define project structure
2. **Setup** -- scaffold with `package.json`, `tsconfig.json`, `eslint.config.mjs` (flat config), pick bundler (Vite for apps, tsup for libraries, Bun for CLIs)
3. **Implementation** -- write production code: API routes, services, data pipelines, CLI tools, shared libraries
4. **Validation** -- run `tsc --noEmit`, ESLint, test suite before shipping

## Capabilities

- **Language**: TS 5.10+ features (const type parameters, `using` / `await using`, decorators stage-3, `satisfies`, template-literal types, discriminated unions, conditional types, mapped types)
- **Tooling**: pnpm / bun, Vite / tsup / rollup, Vitest / Jest, ESLint 9 flat config, Biome, oxlint
- **Web**: Fastify / Hono / Nest 10+, React 19 integration (SSR + RSC), tRPC for end-to-end type safety
- **Data**: Zod / Valibot for runtime validation, Drizzle ORM / Prisma, SQLite / PostgreSQL, Redis
- **Monorepo**: Turborepo / Nx, pnpm workspaces, shared TS config via `tsconfig-base`
- **Infra**: Docker multi-stage (oven/bun or node:22-alpine), ESBuild-based production builds, container-ready outputs

## Conventions

- **Strict mode only**: `"strict": true`, `"noUncheckedIndexedAccess": true`, `"exactOptionalPropertyTypes": true` in `tsconfig.json`
- **No `any`**: use `unknown` + narrow, or `never` for exhaustive checks; `any` only at typed-module boundaries with an adjacent comment explaining why
- **Runtime validation at boundaries**: Zod or Valibot for every external input (HTTP, queue messages, file parsing). Internal function args trust TypeScript types
- **Prefer `satisfies` over annotation** for literal objects where type inference yields more precise types
- **Discriminated unions + exhaustive switches** for finite state (`case "idle" | "loading" | "success" | "error"`), with `never` assertions in the default branch
- **Module boundaries**: each package exports a named namespace + types; prefer named exports over default exports
- **ESM first**: `"type": "module"` in package.json; CommonJS only when a consumer explicitly needs it
- **One module, one responsibility**
- **No em dashes anywhere** (`--` or `-` only)

## Schema-first pattern (preferred)

```typescript
import { z } from "zod";

// Schema is the source of truth
export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  displayName: z.string().min(1).max(80),
  createdAt: z.coerce.date(),
}).strict(); // reject unknown keys -- adjust to .passthrough() at public API boundaries

export type User = z.infer<typeof UserSchema>;

// Runtime validation at the boundary
export function parseUser(raw: unknown): User {
  return UserSchema.parse(raw);
}

// Safe variant for public APIs
export function safeParseUser(raw: unknown) {
  const result = UserSchema.safeParse(raw);
  if (!result.success) {
    return { ok: false as const, issues: result.error.issues };
  }
  return { ok: true as const, data: result.data };
}
```

Valibot is a drop-in alternative with a smaller bundle (useful for browser code where every KB counts).

## Error handling pattern

```typescript
// Result type -- avoids throw/catch at module boundaries
export type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

export async function fetchUser(id: string): Promise<Result<User, FetchError>> {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) {
      return { ok: false, error: new FetchError(res.status, await res.text()) };
    }
    const raw = await res.json();
    return { ok: true, value: UserSchema.parse(raw) };
  } catch (e) {
    return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
  }
}
```

Top-level handlers (HTTP controllers, CLI entry points) convert `Result` failures into framework-appropriate errors.

## ESLint 9 flat config (modern default)

```javascript
// eslint.config.mjs
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default [
  js.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  ...tseslint.configs.stylisticTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: "./tsconfig.json",
      },
    },
    rules: {
      "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
      "@typescript-eslint/no-misused-promises": "error",
      "@typescript-eslint/no-floating-promises": "error",
    },
  },
];
```

Prefer `oxlint` or Biome as a pre-commit fast-path; keep `typescript-eslint` for type-aware rules that oxlint / Biome don't yet cover.

## Project scaffolding starter

When starting a new project, use this layout:

```
<project-name>/
  package.json
  tsconfig.json
  tsconfig.build.json
  eslint.config.mjs
  vitest.config.ts          # or vite.config.ts for apps
  src/
    index.ts                # public API
    schemas/
      user.ts               # Zod schemas -- export types via z.infer
    services/
      user-service.ts
    lib/
      result.ts             # Result<T, E> helper
    bin/
      cli.ts                # CLI entry if --type cli
  tests/
    unit/
    integration/
  dist/                     # build output, gitignored
```

`tsconfig.json` baseline:

```json
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "lib": ["ES2023"],
    "skipLibCheck": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["node"]
  },
  "include": ["src/**/*.ts", "tests/**/*.ts"]
}
```

For libraries, emit types separately:

```json
// tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noEmit": false
  },
  "include": ["src/**/*.ts"],
  "exclude": ["tests/**", "**/*.test.ts"]
}
```

## Common patterns

### `satisfies` vs annotation

```typescript
// Annotation loses literal narrowness
const routes: Record<string, string> = { home: "/", about: "/about" };
routes.home.length; // OK
routes.missing;     // string | undefined -- lost

// satisfies preserves narrowness
const routes2 = { home: "/", about: "/about" } satisfies Record<string, string>;
routes2.home.length;    // OK
// routes2.missing;     // compile error -- good
```

### Discriminated union + exhaustive switch

```typescript
type Action =
  | { type: "add"; value: number }
  | { type: "remove"; id: string }
  | { type: "clear" };

function handle(action: Action): void {
  switch (action.type) {
    case "add":    return store.push(action.value);
    case "remove": return store.deleteById(action.id);
    case "clear":  return store.clear();
    default: {
      const _exhaustive: never = action;
      throw new Error(`Unhandled action: ${_exhaustive satisfies never}`);
    }
  }
}
```

### `using` / `await using` (TS 5.2+, explicit resource management)

```typescript
class DbConnection {
  async [Symbol.asyncDispose]() { await this.close(); }
  async close() { /* ... */ }
}

async function query() {
  await using conn = await openConnection();
  // conn.[Symbol.asyncDispose]() runs automatically at scope exit
  return conn.execute("SELECT 1");
}
```

Cleaner than try/finally for scoped resources.

## Anti-patterns

- `any` escape hatch without a comment
- Top-level `try/catch` that swallows and returns `null` -- surface the error via `Result` or re-throw
- `JSON.parse` without Zod/Valibot validation downstream
- Export-default for libraries (breaks tree-shaking, complicates re-exports)
- Mixing ESM and CJS in the same package without `"exports"` map
- Using `ts-node` for production -- ship compiled output
- `// @ts-ignore` without adjacent comment explaining the ticket / incompatibility

## Integration

- React-specific performance optimization -> `react-development:react-performance-optimizer`
- Dead code / unused exports -> `typescript-development:knip` skill
- Enterprise-grade TypeScript deep dive -> `typescript-development:mastering-typescript` skill
- General writing / linting rules -> `typescript-development:typescript-write` skill
- Pre-commit code quality -> `/senior-review:code-review`
- Python equivalent for comparison -> `python-development:python-engineer`
- Schema-first validation parity -> `python-development:pydantic-v2` skill

## References

- [TypeScript Release Notes](https://devblogs.microsoft.com/typescript/)
- [typescript-eslint v8+ flat config](https://typescript-eslint.io/getting-started)
- [Zod docs](https://zod.dev/)
- [Valibot docs](https://valibot.dev/)
- [Biome](https://biomejs.dev/)
- [Oxlint](https://oxc.rs/)


