TypeScript project conventions: derived types, type placement, acronym casing, imports, generics, factories, and runtime schema patterns. Use when editing `.ts` files, defining exported types, reviewing type names, or organizing type tests.
65
77%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/typescript/SKILL.mdProject-wide TypeScript conventions compose with narrower skills such as arktype, typebox, testing, and method-shorthand-jsdoc.
Base & Extra, never subtract-and-replace with Omit<Base, 'k'> & { k: U }. An Omit<...> & in a type you author is structural override; it is the tell that a smaller base wants a name. To narrow a surface, return that base rather than Omit-ing a member away. See project conventions.type, not interface.readonly only for arrays and maps, unless matching an upstream type exactly.parseUrl, defineKv, readJson, customerId..js extensions in relative imports. Do not use extensionless or .ts relative imports.export { ... } from ... for barrel files.React.FC/FunctionComponent; only type component values when you are storing them in a registry or passing them as data.T prefix, such as TSchema, TDefs, and TKey.create*, attach*, open*, or similar factory, derive it from the implementation with ReturnType<typeof createThing>. Put the exported output alias immediately after the factory. Keep input, config, data, protocol, and multi-implementation contract types above the factory.ReturnType<ReturnType<typeof createThing>>. For generic factories, instantiate typeof when needed, such as ReturnType<typeof openThing<TActions>>.Symbol brand when identity means a specific factory output, not a coincidental shape probe.as any. Use unknown, validation, brands, or narrower helpers instead.in checks or truthiness when checking optional properties.is, has, or can prefixes for booleans that answer a question.switch over if/else for repeated equality comparisons against the same value. Use default: value satisfies never for exhaustiveness when needed.Record lookup tables over nested ternaries for finite value mappings.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:
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-export satisfies or a direct export const fn = ... satisfies T['fn'].typeof Real annotation over satisfies: export const fn: typeof Real = unreachable hides the underlying value's identity from navigation. export const fn = unreachable satisfies typeof Real keeps the value as the source of truth.: T annotation over satisfies for a multi-impl port: the complement of the rule above. When an interface T has several impls (a #platform/* or browser/tauri split) and one impl is deliberately narrower than T (e.g. ignores a param the contract declares), export const x = {...} satisfies T leaks that narrow concrete type, so a caller's view of the method changes by platform. Annotate export const x: T = {...} to publish the wide contract, and the narrower impl still type-checks. Reference: whispering's ManualRecorderLive: RecorderService<...> (unary CPAL impl behind a binary contract).export { X } from './alias' outside index.ts costs an extra hop with nothing to show for it. Reserve export { ... } from ... for barrels; export at the declaration everywhere else.fromX translator or thin passthrough makes Go-to-Def land on the wrapper. Widen the underlying factory's return shape instead (see factory-function-composition "collapsed adapter" rule).export type Thing = ReturnType<typeof createThing>. This keeps navigation on the returned members and lets their JSDoc own the public documentation. See method-shorthand-jsdoc.satisfies generic lists: if a return object should prove it extends a generic contract but satisfies Contract<A, B, C> & Extras forces callers to restate inferred table, action, or runtime types, prefer a constrained identity helper owned by the contract module. Example: return defineWorkspace({ ...workspace, ...runtime }) where the helper accepts TWorkspace extends Workspace<...> and returns TWorkspace. This keeps the call site readable, preserves the exact inferred return type, and leaves Go-to-Def on the real object members.defineX: do not wrap a simple satisfies check just to give it a helper name. If the contract has no required type arguments, or its generics have defaults that make satisfies Contract readable, prefer satisfies. The helper only earns the extra name when it removes generic noise the reader would otherwise have to carry.For broader public-shape decisions that affect navigation across packages, see greenfield-clean-breaks.
types.ts location, co-location rules, inline-vs-extract hop test, options and ID naming.cb12bcc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.