Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Typescript Patterns

ASecurity

TypeScript discipline for any JavaScript/TypeScript project (frontend + backend): strict mode, type design, generics, narrowing, error types, module resolution, tsconfig hygiene. Apply when the project has `tsconfig.json` and `typescript` in devDependencies. Stack-agnostic — referenced by every JS/TS framework plugin in the marketplace. Use this skill to: - Write types that catch bugs at compile time, not runtime. - Use generics, conditional types, and discriminated unions correctly. - Avoid...

35 stars
0 votes
0 copies
0 views
Added 9/22/2026
developmentjavascripttypescriptgojavareactvueangularnextjsnodeapi

Works with

api

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add AratKruglik/claude-sdlc --skill typescript-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Typescript Patterns?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Typescript Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aratkruglik-typescript-patterns/badge)](https://www.skillsdirectory.com/skills/aratkruglik-typescript-patterns)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: typescript-patterns
description: |
  TypeScript discipline for any JavaScript/TypeScript project (frontend + backend): strict mode, type design, generics, narrowing, error types, module resolution, tsconfig hygiene. Apply when the project has `tsconfig.json` and `typescript` in devDependencies. Stack-agnostic — referenced by every JS/TS framework plugin in the marketplace.

  Use this skill to:
  - Write types that catch bugs at compile time, not runtime.
  - Use generics, conditional types, and discriminated unions correctly.
  - Avoid `any`, `unknown`, and unsafe casts.
  - Match the project's tsconfig strictness level.
  - Type third-party libraries (with @types/* or declaration files).

  Do NOT use this skill for:
  - Plain JavaScript projects (no tsconfig.json).
  - Framework-specific type idioms (React component props, Vue defineProps, Angular signals — those live in framework plugins' own conventions skills).
  - tRPC/Zod runtime-validation specifics — handled by validation libs at the boundary.
user-invocable: false
paths: ["**/*.ts", "**/*.tsx", "tsconfig*.json"]
---

# TypeScript Patterns (stack-agnostic)

This skill encodes idioms that catch real bugs in any TypeScript codebase — backend or frontend, Node.js or browser. Apply alongside the active framework plugin's conventions skill (e.g., `react-conventions`, `nest-conventions`) and `npm-patterns`.

## Detection

Project is TypeScript when **all** hold:

- `tsconfig.json` exists in project root.
- `typescript` is in `devDependencies` (or `dependencies` — rare but valid).

Read `tsconfig.json` first to learn the strictness level. The agent's behavior depends on it.

## Tsconfig hygiene

A modern TypeScript project should have these flags. If the project's tsconfig is laxer, **do not silently tighten it** — match project conventions, but flag in DECISIONS that strictness could be improved.

```json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node16",
    "module": "node16",
    "target": "es2022",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist",
    "rootDir": "src"
  }
}
```

Frontend projects (Vite/Next.js/Angular) often use `"module": "ESNext"` + `"moduleResolution": "bundler"` instead — match the bundler's expectations.

Key flags and what they catch:

| Flag | Catches |
|---|---|
| `strict: true` | Master switch — enables noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, alwaysStrict. |
| `noUncheckedIndexedAccess` | `arr[i]` is `T \| undefined`, not `T`. Forces explicit handling of out-of-bounds. |
| `exactOptionalPropertyTypes` | Distinguishes `{ x?: number }` (key may be absent) from `{ x: number \| undefined }` (key present, may be undefined). |
| `isolatedModules` | Each file must be independently transpilable — required for SWC/esbuild/Babel/Vite. |

If you must add a new tsconfig flag for the feature, justify in DECISIONS and isolate it in a new tsconfig (`tsconfig.feature.json` extends base).

## Strictness rules

### Never use `any`

`any` opts out of type-checking entirely. If you genuinely don't know the type:

- **External input** (HTTP body, DB row, URL params): type as `unknown`, then narrow via runtime validator (`zod.parse`, `z.infer<T>`).
- **Library without types**: declare in `src/types/<lib>.d.ts` with the minimal shape you use.
- **Generic constraint**: use `unknown extends T ? ... : ...` patterns, not `any`.

If you must use `any` (real-world example: working with `eval`-like dynamic dispatch), `// eslint-disable-next-line @typescript-eslint/no-explicit-any` with a one-line reason comment.

### Prefer `unknown` over `any`

`unknown` is the safe top type. Forces explicit narrowing before use:

```ts
function parseJson(raw: string): unknown {
  return JSON.parse(raw);
}

const data = parseJson(input);
if (typeof data === 'object' && data !== null && 'id' in data) {
  // narrow further or validate
}
```

In production, prefer a runtime validator over hand-rolled narrowing.

### Type assertions are last resort

`x as Foo` is a lie to the compiler. Use only when:

- You know more than the compiler (e.g., after `instanceof` check that TS can't trace).
- You're bridging to typed code from `unknown` after explicit validation.

Never use `as any as Foo` to shut up the compiler. Find the real type.

### Non-null assertion `x!` — almost never

`x!` says "I promise this isn't null." TS can't verify. Use real narrowing:

```ts
// ❌ Don't
const user = users.find(u => u.id === id)!;

// ✅ Do
const user = users.find(u => u.id === id);
if (!user) throw new NotFoundError(`user ${id}`);
```

## Type design

### Discriminated unions for state

Don't use optional fields for state machines:

```ts
// ❌ Easy to misuse — what does { loading: true, data: foo } mean?
type Result = { loading?: boolean; data?: T; error?: Error };

// ✅ Discriminated — exhaustively checkable
type Result<T> =
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };
```

Use `switch (r.status)` and exhaustiveness check via `never`:

```ts
function handle<T>(r: Result<T>) {
  switch (r.status) {
    case 'loading': return spinner();
    case 'success': return render(r.data);
    case 'error': return showError(r.error);
    default: {
      const _exhaustive: never = r;
      throw new Error(`unhandled: ${_exhaustive}`);
    }
  }
}
```

### Branded types for IDs

Plain `string` IDs are interchangeable — UserId and OrderId mix. Brand them:

```ts
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

const asUserId = (s: string): UserId => s as UserId;  // controlled cast at boundary
```

Use for primary keys, tokens, secrets — anything that's a string but shouldn't mix with other strings.

### Readonly by default

Mutability is opt-in:

```ts
function summarize(items: ReadonlyArray<Item>): Summary { ... }
type Config = Readonly<{ port: number; host: string }>;
```

Mutable signatures are a permission slip — use them only when the function genuinely needs to mutate.

### Avoid enums; use `as const` unions

```ts
// ❌ Old-school — generates runtime code, weird semantics
enum Role { Admin = 'admin', User = 'user' }

// ✅ String literal union — zero runtime cost, standard behavior
const ROLES = ['admin', 'user'] as const;
type Role = typeof ROLES[number];
```

Exception: `const enum` for hot paths where inlining matters (rare).

## Generics

### Constrain, don't widen

```ts
// ❌ T can be anything — defeats the point
function first<T>(xs: T[]): T | undefined { return xs[0]; }

// ✅ T extends a sensible shape
function getId<T extends { id: string }>(item: T): string {
  return item.id;
}
```

### Infer return types from inputs

```ts
function pick<T, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K> {
  // ...
}
```

The caller gets a precisely typed result without explicit annotation.

### Conditional types — sparingly

Conditional types (`T extends U ? A : B`) are powerful but hard to read. Use for:

- Library API ergonomics (e.g., `ReturnType<F>`).
- Removing `null` from a generic: `NonNullable<T>`.

Avoid 3+ nested conditionals. If you reach for it, consider whether a simpler API works.

## Error handling

### Type errors as objects, not strings

```ts
class ValidationError extends Error {
  constructor(public readonly field: string, message: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

class NotFoundError extends Error {
  constructor(public readonly resource: string) {
    super(`${resource} not found`);
    this.name = 'NotFoundError';
  }
}
```

Catch with `instanceof`:

```ts
try {
  await something();
} catch (err) {
  if (err instanceof ValidationError) return res.status(400).json({ field: err.field });
  if (err instanceof NotFoundError) return res.status(404).end();
  throw err;
}
```

### `catch (err: unknown)`

In strict mode, caught errors are `unknown`. Narrow before access:

```ts
catch (err: unknown) {
  if (err instanceof Error) logger.error({ err: err.message, stack: err.stack });
  else logger.error({ err: String(err) });
}
```

## Module resolution

### ESM in modern projects

If `package.json` has `"type": "module"` (Node) or the project uses a bundler (Vite/Webpack/esbuild/Rollup):

- File extensions in imports per project convention:
  - Node ESM: required (`import { x } from './util.js'` — `.js` even from a `.ts` source).
  - Bundler-driven (Vite/Webpack/Next): no extension needed, bundler resolves.
- Use `module: "node16"` / `"nodenext"` / `"ESNext"` in tsconfig per environment.
- For Node ESM: `__dirname` / `__filename` don't exist; use `import.meta.url` + `fileURLToPath`.

### CJS on older Node or by choice

- Imports without extension: `import { x } from './util'`.
- Use `module: "commonjs"`.
- `__dirname` available natively.

### Path aliases — match the project

If tsconfig has `paths`, use them via the configured prefix (`@/util` etc.). Note: TS path aliases don't transpile away — if the project bundles, ensure the bundler resolves them; if it doesn't, use a runtime resolver (`tsconfig-paths`) or stick to relative imports.

## Validation at the boundary

Type the input as `unknown`, validate with a runtime schema, derive the type from the schema:

```ts
import { z } from 'zod';

const CreateUserSchema = z.object({
  email: z.string().email(),
  age: z.number().int().min(0),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;

// In a route handler / Server Action / form submit / event handler
const parsed = CreateUserSchema.safeParse(rawInput);
if (!parsed.success) return { errors: parsed.error.issues };
const input: CreateUserInput = parsed.data;  // fully typed and validated
```

Use whichever validator is in the project (`zod`, `joi`, `yup`, `valibot`, `ajv` + JSON Schema, `class-validator`). Don't introduce a new one without justification.

## Verification before commit

After implementing, ALWAYS run:

```sh
npx tsc --noEmit
```

For Vue projects: `npx vue-tsc --noEmit` (understands `.vue` SFC types).
For Angular projects: rely on `ng build` (Angular Compiler validates templates that plain `tsc` cannot).

If it errors, fix or report — never commit code that doesn't type-check. If the project has `npm run typecheck` or equivalent, prefer that.

For tests + types together:

```sh
npm test && npx tsc --noEmit
```

If `superpowers:verification-before-completion` is available, invoke it to systematically verify the change against the spec. Falls back to manual checklist (compile, run tests, re-read diff) if not.

## Anti-patterns

- ❌ `as any` to silence the compiler.
- ❌ `// @ts-ignore` / `// @ts-expect-error` without a comment explaining why.
- ❌ Disabling strict mode for one file (it lies to readers about safety guarantees).
- ❌ Casting `JSON.parse` result to a specific type without runtime validation.
- ❌ Using `Function` or `Object` types — too broad.
- ❌ Empty interface (`interface Foo {}`) — same as `{}`, matches almost anything.
- ❌ `Type | undefined` field on a `Class` without `?` (means "must assign undefined", not "may omit").
- ❌ Triple-slash directives in modern code (`/// <reference path="..." />`) — use imports.

Attribution

AratKruglikAratKruglik
View sourceMore from AratKruglik →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284722 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →