Agent skill
typescript
Esposter TypeScript conventions — banned patterns (any, Omit, !, forEach, parameter properties), error handling with InvalidOperationError, control flow guard clauses, and enum ref defaults. Apply when writing any TypeScript in this project.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/typescript-esposter-esposter
SKILL.md
TypeScript Conventions
Core Rules
- TypeScript compiler:
strictmode enabled. ESLint:tseslint.configs.strictTypeChecked.anyis BANNED. - Always use strict equality —
===and!==only. Never==or!=, including null checks: use=== null || === undefined(or optional chaining) instead of== null. Omitis BANNED — useExceptfromtype-fest(import type { Except } from "type-fest"). Note:Exceptis not re-exported from@esposter/shared— always import directly fromtype-fest.- No parameter properties — never use
constructor(private readonly foo: T). Always declare class properties explicitly and assign in the constructor body:private readonly foo: T; constructor(foo: T) { super(); this.foo = foo; }. - Non-null assertions (
!) are BANNED — use optional chaining or guard clauses. .forEach()is BANNED — usefor...of.typealiases for object shapes are BANNED — always useinterfacefor object type declarations.Array#sort()is BANNED — useArray#toSorted()instead. This returns a new array without mutating the original, so replace[...arr].sort(fn)witharr.toSorted(fn).- Always use named imports from libraries — only when not already auto-imported by Nuxt or Nuxt modules (e.g.
ref,computed,watchfrom Vue;storeToRefsfrom Pinia; VueUse composables are all auto-imported and must not be manually imported). - Explicitly type variables with proper types.
- No
current*variable caching of.value— don't assignconst currentX = x.valuejust to use it once. If TypeScript narrowing is needed after a guard, assign with a descriptive name (const selectedFile = file.value). Prefer plainconstovercomputed()when the source value is already non-reactive (e.g. areadonlyprop field). - Cloning objects — use
structuredClone(obj)for deep clones; useObject.assign(structuredClone(obj), { ...updates })to clone and override fields. Never use{ ...spread }to clone a class instance — spread creates a plain object losing the prototype. Exception:structuredClone(new ClassName(...))is intentional when a plain object is explicitly required (e.g. Vjsf does not accept class instances — must usestructuredCloneto strip the prototype). Always add a comment explaining why. - Boolean casting — never use
!!to cast to boolean. Always useBoolean(value).
Promise Style
- Always use
async/await— never use.then()or.catch()promise chains. Usetry/catchblocks for error handling. - When fire-and-forgetting an async operation, extract to a named
asyncfunction and call it withoutawait.
Error Handling
- Never use
new Error(...)— always thrownew InvalidOperationError(operation, name, message)from@esposter/shared. - Pick the appropriate
Operationenum value (Operation.Read,Operation.Create,Operation.Update,Operation.Delete, etc.). - Use the resource name (e.g.
file.name, entity ID) as thenameargument — fall back to the calling function's name (e.g.deserializeJson.name) if no better resource name is available. - For user-supplied JSON (file uploads, external input): use Zod
safeParseand throwInvalidOperationErroron failure — never use bareJSON.parsewith a type cast. - For validated endpoint data:
jsonDateParsefrom@esposter/sharedis acceptable.
Control Flow
- Guard clauses first: always use
if (!condition) returnto exit early instead of wrapping the main body in anifblock. Reduce nesting aggressively — if the body of anifis the rest of the function, invert the condition and return early instead. - Always use
if/else if/elsefrom the very first branch when a function has multiple conditional returns — no standaloneiffollowed byelse if.
Return Type Annotations
- Prefer inferred return types — don't annotate a function's return type when TypeScript can infer it correctly. Only add explicit return type annotations when: (a) the inferred type is too broad and you want to enforce a narrower contract (e.g.
ComputedRef<ValidationRule>instead ofComputedRef<(value: string) => string | true>), or (b) the function is part of a public API boundary. Never add redundant annotations just for documentation.
Helper Functions
- Don't extract helpers that add no value — if a helper function just wraps an inline object literal or a single expression without reuse or meaningful abstraction, return/use the value directly. Three lines of inline code is better than a named wrapper used once.
- Function naming prefixes — use
get*for functions that derive or compute a display value (e.g.getVisibilityTooltip,getRowTitle). Use CRUD prefixes (create*,update*,delete*) for heavier operations that interact with data or stores.
Enum Refs
- Never use
ref<EnumType | null>(null)— always default to a sensible first enum value:ref(DataSourceType.Csv),ref(ColumnType.String), etc. - Never write
ref<EnumType>(EnumValue)— TypeScript infers the enum type from the value. Writeref(ColumnType.String), notref<ColumnType>(ColumnType.String).
Stable Identifiers for Selections
- Track selections by stable ID, not by name or index — column names change, indices shift on delete/reorder. Always use
entity.id(UUID) as the key when storing which items are selected/active. A stale ID in a selection is harmless; a stale name or index is a bug.
Generic Definition Arrays — as const Without satisfies
When a definition array has entries typed as Definition<T> where the generic T controls a contravariant position (e.g. a callback parameter like format: (value: ColumnStats[T]) => string), using satisfies readonly Definition[] widens each entry to Definition<KeyUnion>, which fails due to contravariance.
Fix: drop satisfies and use as const alone. Each entry retains its specific Definition<"specificKey"> type, inferred by the define* helper.
// ColumnStatDefinition<T> has format: (value: ColumnStats[T]) => string — contravariant in T
// satisfies readonly ColumnStatDefinition[] FAILS (widens T → ColumnStatKey → function param too broad)
// as const alone PASSES — preserves ColumnStatDefinition<"nullCount">, etc.
export const ColumnStatDefinitions = [
defineColumnStat({ key: "nullCount", format: (value) => String(value), ... }),
...
] as const; // NOT "satisfies readonly ColumnStatDefinition[]"
At call sites where the entry is destructured from the array (losing key↔format correlation), cast the value with as never:
// key and format are destructured — TypeScript loses their correlation
format(item[key] as never); // safe: key and format always come from the same definition entry
Filter-Based Type Narrowing
- No redundant type guards after a filtering condition — if a
.filter()predicate already narrows the type (e.g.filter((v) => typeof v === "number")), the resulting array is already typednumber[]. Do NOT add a separate type guard (: v is number) or cast inside the callback — the filter itself is sufficient.tsException: when the predicate is a function reference (e.g.// WRONG — redundant guard values.filter((v): v is number => typeof v === "number"); // CORRECT — filter condition narrows the type values.filter((v) => typeof v === "number");filter(Boolean)) that TypeScript cannot narrow automatically, a type predicate is still needed.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?