install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/ts-satisfies-operator" ~/.claude/skills/intense-visions-harness-engineering-ts-satisfies-operator-0dedf2 && rm -rf "$T"
manifest:
agents/skills/codex/ts-satisfies-operator/SKILL.mdsource content
TypeScript Satisfies Operator
Validate objects against a type without widening using the satisfies keyword
When to Use
- Validating configuration objects while preserving literal types
- Checking that a value conforms to a type without losing specific type information
- Ensuring exhaustive key coverage in record-like objects
- Replacing
+ type annotation patterns with a cleaner alternativeas const
Instructions
- Basic usage — validate without widening:
type Color = 'red' | 'green' | 'blue'; type Colors = Record<string, Color>; // With type annotation — loses specific key information const colors: Colors = { primary: 'red', secondary: 'blue' }; colors.tertiary; // No error — any string key is valid // With satisfies — preserves exact keys AND validates values const colors = { primary: 'red', secondary: 'blue', } satisfies Colors; colors.primary; // Type: 'red' (not just Color) colors.tertiary; // Error: property does not exist
- Configuration objects — validate shape while keeping literal types:
type Route = { path: string; method: 'GET' | 'POST' | 'PUT' | 'DELETE'; }; const routes = { getUser: { path: '/users/:id', method: 'GET' }, createUser: { path: '/users', method: 'POST' }, } satisfies Record<string, Route>; // routes.getUser.method is 'GET' (literal), not 'GET' | 'POST' | 'PUT' | 'DELETE'
- Combine with
for full immutability:as const
const config = { port: 3000, host: 'localhost', features: ['auth', 'logging'], } as const satisfies { port: number; host: string; features: readonly string[]; }; // config.port is 3000 (literal), config.features is readonly ['auth', 'logging']
- Exhaustive key checking on enum-keyed objects:
type Status = 'draft' | 'published' | 'archived'; const statusLabels = { draft: 'Draft', published: 'Published', archived: 'Archived', } satisfies Record<Status, string>; // Error if any status is missing
- Validate callback shapes without losing return type information:
type Handler = (req: Request) => Response | Promise<Response>; const handlers = { home: (req) => new Response('Hello'), api: async (req) => Response.json({ ok: true }), } satisfies Record<string, Handler>;
- Narrow union types in objects:
type Config = { mode: 'development' | 'production'; debug: boolean; }; const devConfig = { mode: 'development', debug: true, } satisfies Config; // devConfig.mode is 'development' (literal), not 'development' | 'production'
Details
The
satisfies operator (TypeScript 4.9+) validates that an expression matches a type without changing the inferred type of the expression. This solves a long-standing tension in TypeScript between type validation and type inference.
vs type annotation:satisfies
— validates AND widens the type toconst x: Type = value
. The specific value types are lost.Type
— validates againstconst x = value satisfies Type
but infers the type fromType
. Literal types, specific keys, and narrower unions are preserved.value
vs satisfies
:as const
makes everything readonly and literal but does not validate against a typeas const
validates against a type but does not make values readonlysatisfies
does both — readonly, literal, and validatedas const satisfies Type
When to use which:
- Use
when you want to enforce a type contract and do not need literals (function parameters, class properties): Type - Use
when you want validation AND literal preservation (config objects, route tables, enum maps)satisfies Type - Use
when you need immutability AND validationas const satisfies Type
Trade-offs:
is less familiar to many developers — use it consistently and explain in code reviewsatisfies- Error messages from
point to the mismatched property but can be verbose for complex typessatisfies
cannot be used with variable declarations that need explicit types for external consumption (exported API surfaces)satisfies
Source
https://typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.