Skip to main content

loom-typescript

TypeScript language expertise for type-safe, production-quality code.

Quellinformationen

Repository
cosmix/loom
Letzte Quellaktivität
24. September 2026 um 17:08
Erkannte Sprache von SKILL.md
Englisch
Sterne
63
Forks
5

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
loom-typescript
description
TypeScript language expertise for type-safe, production-quality code.
triggers
["typescript","ts","tsx","interface","generic","union","intersection","discriminated union","type guard","type assertion","utility types","conditional types","mapped types","satisfies","zod","trpc","prisma","react","node","nodejs","deno","bun","oxc","oxlint","oxfmt","rolldown","tsdown","vite","eslint","prettier","npm","pnpm","[Truncated]"]
# TypeScript Language Expertise ## Overview Type-safe, production-quality TypeScript: the type system's sharp edges, strict-mode config that actually moves the needle, the type-vs-runtime boundary, and framework patterns (zod/tRPC/Prisma/React/Express). Assumes fluency with JS and basic TS — this is reference for the traps and idioms that bite experienced engineers. Toolchain assumed throughout: **Bun** (install/run/test), **Vite** (app dev server and bundler — Rolldown bundling, Oxc transforms and minifier, Lightning CSS), **tsdown** (library bundler on Rolldown), **oxlint** (lint), **oxfmt** (format), `tsc --noEmit` (type-check). ESLint, Prettier, esbuild, and Babel are the legacy equivalents — do not add them to a project that has this stack. See **Toolchain** below. ## Type System Essentials ### Generics ```typescript // Constraints + default type param; `this` return for fluent chaining interface HasId { id: string } class Repo<T extends HasId = HasId> { private items = new Map<string, T>(); save(item: T): this { this.items.set(item.id, item); return this; } find(id: string): T | undefined { return this.items.get(id); } } // Return `as const` to preserve a tuple/literal shape instead of widening function pair<A, B>(a: A, b: B) { return [a, b] as const; } // readonly [A, B] ``` ### Utility types | Utility | Result | | ----------------------------- | --------------------------------------------- | | `Partial<T>` / `Required<T>` | all props optional / required | | `Readonly<T>` | all props `readonly` (shallow) | | `Pick<T,K>` / `Omit<T,K>` | keep / drop keys `K` | | `Record<K,V>` | object with keys `K`, values `V` | | `Extract<U,V>` / `Exclude<U,V>` | keep / drop union members of `U` assignable to `V` | | `NonNullable<T>` | strip `null` / `undefined` | | `ReturnType<F>` / `Parameters<F>` | function return type / param tuple | | `Awaited<T>` | recursively unwrap `Promise` (prefer over a hand-rolled `Unwrap`) | ```typescript // Derive types from values so they can't drift: function createUser(name: string, email: string): User { /* … */ } type NewUser = ReturnType<typeof createUser>; // User type NewUserArgs = Parameters<typeof createUser>; // [string, string] ``` ### Conditional types & `infer` ```typescript type Elem<T> = T extends (infer U)[] ? U : never; type Ret<T> = T extends (...a: any[]) => infer R ? R : never; // Conditionals DISTRIBUTE over naked union type params: type ToArray<T> = T extends any ? T[] : never; type A = ToArray<string | number>; // string[] | number[] // Wrap both sides in a 1-tuple to DISABLE distribution: type ToArray1<T> = [T] extends [any] ? T[] : never; type B = ToArray1<string | number>; // (string | number)[] ``` ### Mapped types ```typescript type Mutable<T> = { -readonly [K in keyof T]: T[K] }; type Optional<T> = { [K in keyof T]+?: T[K] }; // Key remapping via `as` (TS 4.1+): rename or filter keys type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }; type PickByValue<T, V> = { [K in keyof T as T[K] extends V ? K : never]: T[K] }; // PickByValue<{a:string;b:number}, string> → { a: string } ``` ### Discriminated unions & exhaustiveness Model each valid state as a variant with a shared literal discriminant; `switch` on it narrows each arm, and an `assertNever` default turns "added a variant, forgot a case" into a compile error. ```typescript type State = | { status: "idle" } | { status: "loading" } | { status: "success"; data: User[] } | { status: "error"; error: Error }; function assertNever(x: never): never { throw new Error(`Unreachable: ${JSON.stringify(x)}`); } function render(s: State): string { switch (s.status) { case "idle": return "Click to load"; case "loading": return "Loading…"; case "success": return `Loaded ${s.data.length}`; // s narrowed → data exists case "error": return s.error.message; // s narrowed → error exists default: return assertNever(s); // ← new unhandled variant = type error } } ``` ⚠ Exhaustiveness relies on a *finite* discriminant. A **numeric enum** discriminant accepts any `number`, so `assertNever` won't catch a missing case — use string-literal unions or `as const` objects (see Anti-Patterns). ### Type guards, assertion functions, `unknown` vs `any` - `any` disables checking **transitively** — it silently poisons every expression it flows into. `unknown` is the safe top type: assignable *from* anything, assignable *to* nothing until you narrow. - Type external inputs (`JSON.parse`, `fetch().json()`, `catch` vars, `process.env` shapes) as `unknown` and narrow with `typeof` / `instanceof` / `in` / a validator before use. ```typescript function isCat(a: Cat | Dog): a is Cat { return "meow" in a; } // Assertion function: narrows for the rest of the scope, throws otherwise function assertNonNull<T>(v: T | null | undefined, msg?: string): asserts v is T { if (v == null) throw new Error(msg ?? "value is null/undefined"); } ``` ⚠ An explicit `x is T` predicate is **trusted, not verified** by the compiler — a wrong body is as unsafe as `as`. Prefer letting TS *infer* the predicate (TS 5.5+, see Gotchas); reserve explicit `is` for what inference can't express, and unit-test those. ## tsconfig & Strict Mode `module`/`moduleResolution` must match where the output runs — there is no universal template. Pick ONE of the two below; never copy a `nodenext` config into a bundler app or vice versa. ### Node app or published library — `module: nodenext` Requires explicit `.js` extensions on relative imports plus `"type": "module"` in `package.json` (or `.mts`/`.cts`). `nodenext` implies a matching `lib`/`target`, so an explicit `"lib"` is redundant here. ```json { "compilerOptions": { "module": "nodenext", "verbatimModuleSyntax": true, "outDir": "./dist", "rootDir": "./src", "declaration": true, "declarationMap": true, "sourceMap": true, "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ### Bundler app (Vite / Rolldown) Extensionless relative imports work; the bundler emits, so `tsc` only type-checks (`noEmit`). ⚠ Never use `moduleResolution: bundler` for a **published library** — it's "infectious": emitted `.d.ts` files carry extensionless relative imports that break Node.js ESM consumers. Vite transpiles TS with Oxc, which strips types without checking them — `tsc --noEmit` stays the type gate (`oxlint --type-aware` covers the lint half, not the compiler's). ```json { "compilerOptions": { "module": "esnext", "moduleResolution": "bundler", "verbatimModuleSyntax": true, "noEmit": true, "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true }, "include": ["src/**/*"] } ``` ### Flags beyond `strict` `strict: true` does NOT include these — opt in explicitly (TS 5.9 `tsc --init` now enables them for new projects): - **`noUncheckedIndexedAccess`** — adds `| undefined` to array subscripts and index-signature access (`arr[i]: T | undefined`). ⚠ NOT applied to named properties, NOT to `for...of` loop variables (by design, microsoft/TypeScript#42622), NOT to `Object.values()` — so it's no safety net when iterating. - **`exactOptionalPropertyTypes`** — `obj.x = undefined` becomes an error for `x?: "a" | "b"`; only *deleting* the key makes the property absent. Matters for `"x" in obj` checks and serialization round-trips. - **`useUnknownInCatchVariables`** (on via `strict` since 4.4) — `catch` vars are `unknown`; guard with `instanceof Error`. - **`verbatimModuleSyntax`** — modern module-safety baseline; supersedes the now-no-op `importsNotUsedAsValues`/`preserveValueImports` (see Modules). - **`isolatedModules`** — forbids constructs single-file transpilers can't handle (re-exporting a type without `type`, `const enum` inlining); required for single-file transpilers — Oxc (Vite, tsdown), Bun's transpiler, esbuild, swc, Babel. ### Checklist: tsconfig - [ ] Exactly one of `nodenext` / `bundler` chosen to match the runtime — not mixed - [ ] `strict: true` PLUS `noUncheckedIndexedAccess` + `exactOptionalPropertyTypes` (they aren't in `strict`) - [ ] `verbatimModuleSyntax: true`; type-only imports carry the `type` modifier - [ ] Library build: `declaration: true`, NOT `moduleResolution: bundler`, no `const enum` in public `.d.ts` - [ ] `isolatedModules: true` if any non-`tsc` transpiler is in the build ## Toolchain: Bun + Vite + Oxc One stack, no overlap: **Bun** installs, runs scripts, and runs `bun test`; **Vite** builds apps (Rolldown bundler, Oxc transform + minifier, Lightning CSS); **tsdown** builds libraries (Rolldown); **oxlint** lints; **oxfmt** formats; `tsc --noEmit` type-checks. Oxc replaces ESLint, Prettier, esbuild, and Babel — never add those to a project that has this stack, and never run two formatters over the same files; a second linter is acceptable only as a temporary migration bridge (see oxlint below). ```bash bun add -D typescript oxlint oxfmt bunx oxlint --init # writes .oxlintrc.json bunx oxfmt --init # writes .oxfmtrc.json (--migrate=prettier converts an existing Prettier config) bun add -D oxlint-tsgolint # needed by "typeAware": true in the config below; drop both to skip type-aware rules ``` `package.json` scripts: ```json { "scripts": { "typecheck": "tsc --noEmit", "lint": "oxlint --deny-warnings", "lint:fix": "oxlint --fix", "format": "oxfmt", "format:check": "oxfmt --check", "check": "bun run typecheck && bun run lint && bun run format:check" } } ``` ### oxlint - Config is `.oxlintrc.json`. Rules are grouped in categories; only `correctness` is on by default — opt into `suspicious`, `perf`, `pedantic` per project rather than `all`. - Setting `plugins` REPLACES the default set (`eslint`, `typescript`, `unicorn`, `oxc`) — list the defaults you keep alongside the ones you add (`import`, `promise`, `vitest`, `react`, `jsx-a11y`, ...). - `oxlint --fix` applies only safe fixes; `--fix-suggestions` and `--fix-dangerously` can change behavior — review the diff. - Type-aware rules (`typescript/no-floating-promises`, `typescript/no-unsafe-assignment`, ...) need `oxlint-tsgolint` plus `"options": { "typeAware": true }` (or `oxlint --type-aware`). They run on the native TypeScript compiler (tsgo), so the tsconfig must be tsgo-clean — no `baseUrl` — and `include` should stay tight or the pass gets slow. - Migrating from ESLint: `bunx -p @oxlint/migrate oxlint-migrate` converts a flat config to `.oxlintrc.json`. If a rule oxlint lacks must stay, keep ESLint for that rule only, add `eslint-plugin-oxlint` to turn off everything oxlint already covers, and run `oxlint && eslint` so the fast pass fails first. ```json { "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "promise"], "categories": { "correctness": "error", "suspicious": "warn" }, "rules": { "typescript/no-explicit-any": "error",
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen