agentsclimarketplace

Vp typescript best practices

Skill VdustR/skills/skills/vp-typescript-best-practices

Reusable Agent Skills by VdustR

Install
npx -y skills add VdustR/skills --skill vp-typescript-best-practices

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when writing, reviewing, or refactoring TypeScript in .ts, .tsx, or .test-d.ts files; fixing TS errors; designing types; validating external data; reducing unsafe assertions; or choosing between inference, annotations, satisfies, generics, unions, Zod schemas, and type-level tests.

SKILL.md

8.1 KB, as published. Nobody here has run it

TypeScript Best Practices

High safety per line of TypeScript. Follow the repository's documented style, tsconfig, lint rules, and generated-code workflow first; use these guidelines as portable defaults when the repo does not already decide.

Fallback Style Preferences

Use these only when the repository has no conflicting convention:

  • Prefer Array<T> / ReadonlyArray<T> over T[] / readonly T[] for visible aliases and public types.
  • Prefer interface for extensible object shapes; prefer type for unions, tuples, mapped types, conditional types, and aliases.
  • Prefer type-only namespaces to colocate a function's Options, Result, and overload contracts when the repo already permits namespaces.
  • Prefer descriptive, T-prefixed generic names for public or complex APIs, such as TConfig and TItem; reserve T, K, and V for tiny conventional helpers.
  • Treat acronyms as words in identifiers: userId, ApiClient, HttpError.

Key Items

  • Prefer inference until a boundary needs a contract. Let local variables and implementation details infer naturally; annotate exported APIs, callbacks, config objects, test fixtures, runtime boundaries, and values passed directly into another API.
  • Type entrypoints from the consumer contract. Use ComponentProps<typeof Button>["onClick"], Parameters<typeof fn>[0], ReturnType<typeof hook>, or a public Options type instead of retyping the same structure by hand.
  • Use satisfies as a review anchor. It validates object shape while keeping useful literal types. Use it where future schema, union, or config drift should fail at compile time.
  • Avoid as and ! by default. Prefer annotations, satisfies, control-flow narrowing, runtime validation, and explicit null checks. If an escape hatch is unavoidable, keep it local and document why it is safe.
  • Validate external data at runtime. Network responses, storage, environment variables, messages, user input, and bridge APIs enter as unknown; parse or normalize once, then derive TypeScript types from that runtime schema.
  • Keep transform boundaries single-sourced. If multiple callers must normalize the same wire shape, extract one shared transform or parser instead of copying conversion logic.
  • Preserve TypeScript narrowing. Keep discriminated objects intact when the relationship between flags and data matters. Promote nullable lookups once with a guard instead of scattering non-null assertions.
  • Guard indexed access. Treat array[index], dictionary lookups, and dynamic keys as possibly missing unless the type system proves otherwise. Prefer value-driven iteration when the index is not needed.
  • Make unions exhaustive. Branch on discriminants with switch or explicit guards, then use satisfies never so new variants break the build.
  • Extract, do not redefine. Reuse types from schemas, generated clients, library APIs, class constructors, and existing values. Manual copies drift.
  • Keep literal lists as runtime data. Prefer as const arrays or schemas plus derived union types over TypeScript enum when the repo uses strip-only or erasable syntax tooling.
  • Type-test complex types. Use colocated *.test-d.ts files for generics, schema compatibility, overloads, and intentional negative cases.
  • Respect generated and third-party boundaries. Regenerate generated code or wrap third-party gaps locally. Do not spread one local workaround across the application.

Quick Reference

GoalPreferred patternAvoid
Callback or prop typeComponentProps<typeof X>["onY"]Rewritten event types
Config objectconst value = {...} satisfies Optionsas Options
Runtime inputSchema parse, type guard, normalization functioninput as Model
Nullable lookupconst tryX = find(); if (!tryX) throw ...; const x = tryXfind()!
Array/dictionary lookupGuard undefined, use for...of when possibleitems[i]!
Union handlingDiscriminant switch + value satisfies neverSilent default branch
Error subclass argsConstructorParameters<typeof Error>Copying constructor overloads
Generated client typesParameters, ReturnType, indexed accessHandwritten copies
Type-only regression*.test-d.ts + satisfiesRuntime tests for pure types
Escape hatchLocal cast with WHY/SCOPE/SAFETY commentRepo-wide any plumbing
Public array aliasesArray<TItem> / ReadonlyArray<TItem>Mixed syntax without repo reason
Public generic namesTConfig, TItem, TResultCryptic names in large APIs

Core Patterns

Consumer Contract Typing

const onSelect: ComponentProps<typeof Select>["onChange"] = (value) => {
  updateSelection(value);
};

const payload: Parameters<typeof submitUser>[0] = {
  id: user.id,
  name: user.name,
};

Use this when the variable exists mainly to feed another API. The type error then appears where the value is created, not later at the callsite.

Runtime Boundary Validation

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
});

type User = z.infer<typeof UserSchema>;

function parseUser(input: unknown): User {
  return UserSchema.parse(input);
}

One runtime source of truth is safer than a handwritten User plus a separate validator. When transforms apply, use z.input and z.output only to make that boundary explicit.

Exhaustive Branching

function labelStatus(status: Status): string {
  switch (status.kind) {
    case "loading":
      return "Loading";
    case "success":
      return "Done";
    case "error":
      return status.message;
    default:
      status satisfies never;
      throw new Error(`Unhandled status: ${String(status)}`);
  }
}

satisfies never is compile-time only. Throw or return deliberately when runtime continuation would be unsafe.

Common Mistakes

MistakeBetter move
Adding types everywhere before reading the local styleFollow repo config, then annotate only useful boundaries
Using as const to compensate for missing shape validationAdd satisfies TargetType or contextual typing
Casting API data because "the backend returns this"Parse the data or derive the type from the generated/schema source
Destructuring discriminated query/result objects too earlyKeep the object intact until after narrowing
Silencing a third-party typing issue with a broad helperWrap the smallest bridge and document the safety argument
Adding enum in a strip-only toolchainUse a runtime list/schema and derive the union type
Duplicating a generated client's request or response typeExtract from the generated function, schema, or model

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.