TypeScript Strict
Use this skill for TypeScript mechanics that are not specific to Effect.
Repo Facts
- Root
tsconfig.jsonis strict and no-emit. - Module mode:
Preserve; module resolution:bundler. - TS extension imports are allowed.
exactOptionalPropertyTypes,noUncheckedIndexedAccess, andnoPropertyAccessFromIndexSignatureare enabled.@effect/language-serviceis configured in roottsconfig.json.- Root typecheck runs package configs explicitly.
Type Rules
- Prefer
unknownoveranyat external boundaries. - Narrow
unknownbefore use. - Use explicit return types on exported functions when they define public API.
- Use
ReadonlyArray<T>and readonly object fields for inputs and data contracts. - Prefer literal unions or
as constobjects over numeric enums. - Use template literal types or branded types for structured strings that cross boundaries.
- Avoid non-null assertions; prove presence through narrowing.
- Avoid type assertions that erase useful errors or requirements.
Type-First Modeling
For new public APIs, protocols, or domain states, design the type shape before filling in implementation details.
- Define the data model: IDs, state variants, schemas, and external wire shape.
- Define function signatures: input, output, recoverable errors, and Effect requirements.
- Implement to satisfy the types; let compiler errors expose missing cases.
- Validate at boundaries where unknown data enters or leaves the system.
Rules:
- Make illegal states unrepresentable with discriminated unions instead of correlated optional fields or boolean flags.
- Use branded/schema-backed IDs for values that cross package, persistence, tool, or RPC boundaries.
- Prefer constructors/builders for exported protocol variants so defaults and
_tagliterals are centralized. - Avoid booleans by default for domain state. Use booleans only for truly binary values that are unlikely to grow into a state space.
- Prefer discriminated unions for simple states and an explicit state-machine module/service for complex state transitions.
- Prefer explicit required inputs over optional-parameter soup. If a value has a meaningful default, normalize it once at the boundary into a required field.
- Use explicit domain sentinels or variants when absence has semantics; do not rely on omission to mean a real state.
- Use exhaustive matching for closed internal protocols. Unknown internal variants are defects, not fallback cases.
- Avoid stringly typed protocols when the set is closed. Use literal unions or schemas.
- Use
satisfieswhen checking object shape without widening or erasing useful literal information. - Let TypeScript infer local obvious values, but write explicit return types for exported functions that define public API.
Module And Package Rules
- Keep package public exports intentional.
- Match
package.jsonexports with actual source entrypoints. - Use workspace path mappings from package
tsconfig.json; do not invent import paths. - Keep browser-safe packages free of Bun-only imports.
- Keep generated
dist/output derived from source.
Typecheck Workflow
- Read the relevant package
tsconfig.json. - Reproduce with the narrow package typecheck if available.
- Fix the type model, not just the local symptom.
- Run
bun run typecheckfor cross-package signatures, exports, schemas, or shared types.
Error-Handling Types
- Use typed result/error models for recoverable failures.
- In Effect code, preserve the
Echannel instead of collapsing errors tounknown. - In non-Effect code, prefer discriminated unions for recoverable results when exceptions are not the right boundary.
- Do not silently default malformed or incomplete input unless the default is a named domain rule. Prefer explicit validation and a typed failure.
- Recover from expected external uncertainty; fail loud on impossible internal states and violated invariants.
- When converting foreign errors, add stable context without leaking secrets or large payloads.
Anti-Patterns
- Do not use
as anyoras neverto escape a type error without explaining the invariant. - Do not widen closed sets to
stringunless external extensions can add values at runtime. - Do not use
T[]for input collections that should not be mutated. - Do not add default exports to packages that use named export style.
- Do not hide package-boundary type errors by changing root compiler options.
- Do not model mutually exclusive states as one object with many optional fields.
- Do not model lifecycle or protocol state as a matrix of booleans.
- Do not use broad index signatures when explicit keys or a typed map would preserve more information.
- Do not build object literals with conditional spread fragments like
...(x !== undefined ? { x } : {}). Normalize options first and pass an explicit required object. - Do not scatter nullish defaults through function bodies. Defaults should live in constructors, boundary normalizers, config layers, or clearly named constants.
- Do not let defaults creep inward through multiple call layers. Once normalized, pass the explicit normalized value.
- Do not add default branches that hide unsupported internal message types, protocol variants, or state transitions.
- Do not use
Partial<T>for domain update APIs. Prefer named commands such asrecordUsage,markCompleted, orsetIteration.Partial<T>is acceptable for tests, low-level persistence plumbing, or external patch surfaces after normalization.