From harness-claude
Validates TypeScript objects against types using 'satisfies' operator without widening inference, preserving literal types for configs, records, and exhaustive key checks.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Validate objects against a type without widening using the satisfies keyword
Guides TypeScript type system usage: generics, utility types (Partial, Pick, Omit), type narrowing, guards, discriminated unions, strict mode, and patterns with zod/valibot.
Guides TypeScript type system mastery with strict mode tsconfig options, essential compiler settings, tsc commands for type checking, and coverage of advanced types, generics, type guards.
Implements strict TypeScript practices: eliminates 'any' types with unknown guards, Zod validation, Result types, discriminated unions, branded types, and TS 5.5+ features.
Share bugs, ideas, or general feedback.
Validate objects against a type without widening using the satisfies keyword
as const + type annotation patterns with a cleaner alternativetype 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
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'
as const for full immutability: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']
type Status = 'draft' | 'published' | 'archived';
const statusLabels = {
draft: 'Draft',
published: 'Published',
archived: 'Archived',
} satisfies Record<Status, string>;
// Error if any status is missing
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>;
type Config = {
mode: 'development' | 'production';
debug: boolean;
};
const devConfig = {
mode: 'development',
debug: true,
} satisfies Config;
// devConfig.mode is 'development' (literal), not 'development' | 'production'
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.
satisfies vs type annotation:
const x: Type = value — validates AND widens the type to Type. The specific value types are lost.const x = value satisfies Type — validates against Type but infers the type from value. Literal types, specific keys, and narrower unions are preserved.satisfies vs as const:
as const makes everything readonly and literal but does not validate against a typesatisfies validates against a type but does not make values readonlyas const satisfies Type does both — readonly, literal, and validatedWhen to use which:
: Type when you want to enforce a type contract and do not need literals (function parameters, class properties)satisfies Type when you want validation AND literal preservation (config objects, route tables, enum maps)as const satisfies Type when you need immutability AND validationTrade-offs:
satisfies is less familiar to many developers — use it consistently and explain in code reviewsatisfies 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)https://typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html