ts-language-patterns
Use when writing or reviewing modern TS syntax — const type parameters, using/await using, standard decorators, satisfies, or LLM anti-patterns (legacy enum/namespace). Not for tsconfig (ts-config).
SKILL.mdplugins/typescript-expert/skills/ts-language-patterns/SKILL.md
GitHub<objective>
This skill covers TypeScript 7.0 language features and idioms (7.0 keeps 6.0's type-checking
behavior): const type parameters (<const T>) for narrowest-literal inference without as const,
using/await using for deterministic Symbol.dispose/asyncDispose cleanup, standard ECMAScript
decorators (never experimentalDecorators), satisfies for narrow-type validation, the 6.0
inference change making method-syntax callbacks order-independent, and 7.0's Unicode-aware
template-literal inference.
It also covers fixing LLM-authored anti-patterns: legacy enum (un-erasable by Node's type stripper, replace with const objects as const), namespace wrapping runtime code, and missing import type under verbatimModuleSyntax.
Out of scope: tsconfig/compiler flags belong to ts-config; SOLID structure and file-size rules belong to solid-generic; framework-specific APIs are not covered. </objective>
TypeScript Language Patterns (TS 7.0)
Agent Workflow (MANDATORY)
Before writing non-trivial TypeScript, spawn 3 agents in parallel, one Agent call each with a name:
- fuse-ai-pilot:explore-codebase - Detect existing idioms,
verbatimModuleSyntax, tsconfig - fuse-ai-pilot:research-expert - Verify current syntax on typescriptlang.org
- mcp__context7__query-docs -
/microsoft/typescriptfor exact API shapes
After writing, run fuse-ai-pilot:sniper for validation.
Overview
| Feature | What it gives you |
|---|---|
const type parameters |
<const T> infers the narrowest literal type without as const at call sites |
using / await using |
Deterministic cleanup via Symbol.dispose / Symbol.asyncDispose |
| Standard decorators | ECMAScript decorators — NOT experimentalDecorators |
satisfies |
Validate a value against a type while keeping its narrow inferred type |
| 6.0 inference | Method-syntax callbacks are no longer contextually sensitive → order-independent inference |
| 7.0 template literals | Inference splits on Unicode code points, not UTF-16 surrogate halves |
Critical Rules
import typeis mandatory underverbatimModuleSyntax- type-only imports must saytype, or emit/stripping breaks.- No legacy
enum- preferconstobjectsas const;enumis un-erasable by Node's type stripper. - No
namespacewith runtime code - use ESM modules; type-onlynamespaceis acceptable. - Use standard decorators - never enable
experimentalDecoratorsfor new code. - Never rely on implicit
any-strictis the default since 6.0 (kept in 7.0); annotate or infer explicitly.
Reference Guide
Concepts
| Topic | Reference | When to Consult |
|---|---|---|
| Resource management | resource-management.md | Load when using using / await using / disposables |
| Generics & inference | generics-and-inference.md | Load when using const type params, satisfies, or debugging inference |
| Decorators | decorators.md | Load when adding decorators |
| LLM pitfalls | llm-pitfalls.md | Load when reviewing or migrating legacy/AI-authored TypeScript |
Templates
| Template | When to Use |
|---|---|
| modern-patterns.md | Copy-paste const/satisfies/decorator examples |
| resource-management.md | Copy-paste using / DisposableStack examples |
Quick Reference
const type parameter
function first<const T extends readonly unknown[]>(arr: T): T[0] {
return arr[0];
}
const x = first(["a", "b"]); // x: "a" (literal, no `as const` needed)
using for deterministic cleanup
function openFile(path: string) {
const handle = acquire(path);
return { handle, [Symbol.dispose]() { release(handle); } };
}
{
using file = openFile("./data"); // released automatically at block end
}
satisfies
const config = {
port: 3000,
host: "localhost",
} satisfies Record<string, string | number>;
// config.port stays `number`, not widened to `string | number`
Best Practices
DO
- Replace
enum Color { Red }withconst Color = { Red: "red" } as const - Reach for
usingover manualtry/finallycleanup when a resource has a disposer - Use
satisfiesinstead of a type annotation when you want to keep the narrow inferred type
DON'T
- Write
import { SomeType }for a type-only import — useimport type { SomeType } - Enable
experimentalDecoratorsoremitDecoratorMetadatafor new code - Use
namespaceto organize runtime code — that is what ESM modules are for