TypeScript Guidelines
Use this skill for project-wide TypeScript conventions before loading narrower skills such as arktype, typebox, testing, or method-shorthand-jsdoc.
When To Apply This Skill
Use this skill when you need to:
- Write or refactor TypeScript with Epicenter naming and style conventions.
- Decide whether to derive, import, or declare a type.
- Review type ownership, copied shapes, factory return types, brands, casts, and generic names.
- Choose clear value-mapping and control-flow patterns for unions and discriminated values.
- Organize type tests, runtime schemas, or factory-focused refactors.
Core Rules
- Try to derive or import a type before declaring a new named type. New named types must earn their place as a real contract, protocol vocabulary, discriminated result union, capability port, or multi-implementation shape.
- Treat local shape copies as boundary smells. Prefer the owning runtime type, schema inference, factory return type, function signature, or a caller-owned capability function.
- Use
type, notinterface. - Use
readonlyonly for arrays and maps, unless matching an upstream type exactly. - Treat acronyms as normal words in camelCase:
parseUrl,defineKv,readJson,customerId. - Use
.jsextensions in relative imports. Do not use extensionless or.tsrelative imports. - Export symbols at their declarations. Reserve
export { ... } from ...for barrel files. - Prefer factory functions over classes. Let closure position communicate private vs public API.
- Use descriptive generic names with a
Tprefix, such asTSchema,TDefs, andTKey. - Destructure options in the function signature when the object is a configuration bag. Keep a named value only when it is the domain object being transformed or forwarded.
- Let TypeScript infer private and inner return types. Annotate exported APIs only when useful for clarity or to break circular inference.
- If an exported type is exactly the object returned by a
create*factory, derive it withReturnType<typeof createThing>. Put useful annotations on returned members instead of duplicating the object shape. - Use a
Symbolbrand when identity means a specific factory output, not a coincidental shape probe. - Avoid
as any. Useunknown, validation, brands, or narrower helpers instead. - Prefer optional chaining over
inchecks or truthiness when checking optional properties. - Use
is,has, orcanprefixes for booleans that answer a question. - Prefer
switchoverif/elsefor repeated equality comparisons against the same value. Usedefault: value satisfies neverfor exhaustiveness when needed. - Prefer
Recordlookup tables over nested ternaries for finite value mappings. - Compose typed errors bottom-up. Do not filter a broad upstream error union at the boundary.
- Question silent fallbacks that hide invalid state. Preserve round-trip invariants when parsing and serializing.
Go-to-Definition Awareness
When organizing types and exports, always consider Go-to-Definition. A developer pressing Go-to-Def from a call site should land as close as possible to the actual source of truth. If a design choice forces an extra navigation hop, the choice has to earn it elsewhere (e.g., a real validation boundary, a published contract, or a multi-implementation port).
Concrete regressions to watch for:
- Destructure-re-export of a module-level object:
const stub = { fn, gn } satisfies T; export const { fn, gn } = stub;lands Go-to-Def on the destructuring line, not the real definition. Prefer per-exportsatisfiesor a directexport const fn = ... satisfies T['fn']. typeof Realannotation oversatisfies:export const fn: typeof Real = unreachablehides the underlying value's identity from navigation.export const fn = unreachable satisfies typeof Realkeeps the value as the source of truth.- Re-export chains in non-barrel files:
export { X } from './alias'outsideindex.tscosts an extra hop with nothing to show for it. Reserveexport { ... } from ...for barrels; export at the declaration everywhere else. - Adapter / proxy / wrapper with no behavior change: a
fromXtranslator or thin passthrough makes Go-to-Def land on the wrapper. Widen the underlying factory's return shape instead (seefactory-function-composition"collapsed adapter" rule). - Manual return type annotation duplicating zone 4: annotating a factory with a hand-written interface diverts Go-to-Def to the alias. Use
ReturnType<typeof createThing>so navigation lands on the actual returned member (this is the same choice that drives JSDoc preservation, seemethod-shorthand-jsdoc).
For broader public-shape decisions that affect navigation across packages, see cohesive-clean-breaks.
Reference Map
- Project conventions: detailed examples for derived types, local shape copies, imports, barrels, factories, generics, destructuring, and factory return types.
- Type safety and control flow: identity brands, casts, optional properties, boolean naming, switches, record lookups, error composition, fallback smells, and round-trip invariants.
- Type organization:
types.tslocation, co-location rules, inline-vs-extract hop test, options and ID naming. - Factory patterns: factory-focused refactors, parameter destructuring, and coupled state extraction.
- Runtime schema patterns: arktype, branded IDs, optional property syntax, and workspace table IDs.
- Testing patterns: inline single-use setup and source-shadowing tests.
- Advanced TypeScript features: iterator helpers and const generic array inference.