From global-plugin
Use when authoring or reviewing TypeScript types, generics, DTOs, or boundary parsing. Do NOT use for runtime/logic review without a type concern. Covers strict compiler options, discriminated unions, branded types, exhaustiveness, zod boundaries, error types.
npx claudepluginhub lgerard314/global-marketplace --plugin global-pluginThis skill is limited to using the following tools:
Make invalid states unrepresentable. Boundary parsing, branded IDs, discriminated unions, typed errors.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Make invalid states unrepresentable. Boundary parsing, branded IDs, discriminated unions, typed errors.
type UserId = string & { __brand: 'UserId' }). — Why: prevents passing an OrderId where a UserId is expected; the compiler rejects the swap.Partial<Everything> sprawl and leaky abstractions that expose implementation details.Result<T, E> or a typed exception hierarchy); catch (e: unknown) always narrows before use. — Why: untyped errors silently swallow diagnostic information and allow wrong-path code to keep running.Record<string, unknown> at boundaries — define the shape or use z.unknown() then parse. — Why: unstructured shapes reach domain logic and cause silent data corruption or runtime errors.<T> that silently accepts any. — Why: an unbounded <T> is just a delayed any; constraints make the generic honest.| Thought | Reality |
|---|---|
| "I'll cast this for now" | Casts lie to the compiler and propagate bugs downstream. |
| "Just add an optional field" | Optional + flag combos hide invariants; use a union instead. |
| "I validated it earlier" | Parsing once is cheap; re-checking at call sites is an invitation to drift. |
"any is fine here, it's internal" | any punches a hole through the type system; prefer unknown and narrow. |
Bad:
interface UIState {
isLoading: boolean;
data?: User[];
error?: string;
}
// nothing stops { isLoading: true, data: [], error: "oops" }
Good:
type UIState =
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; message: string };
// exhaustive handling — compiler catches missing branches
function render(state: UIState) {
switch (state.status) {
case 'loading': return <Spinner />;
case 'success': return <List items={state.data} />;
case 'error': return <Alert msg={state.message} />;
}
}
Bad:
function assignOrder(userId: string, orderId: string) { /* ... */ }
const uid = 'user-1';
const oid = 'order-99';
assignOrder(oid, uid); // silently swapped — no compile error
Good:
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };
function assignOrder(userId: UserId, orderId: OrderId) { /* ... */ }
// assignOrder(oid as OrderId, uid as UserId) — now a type error at the call site
Bad:
app.post('/users', (req, res) => {
const input = req.body as CreateUserInput; // lie to the compiler
createUser(input); // unvalidated data enters domain
});
Good:
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
app.post('/users', (req, res) => {
const input = CreateUserSchema.parse(req.body); // throws ZodError if invalid
createUser(input); // fully typed, trusted data
});
Recommended tsconfig.json options for projects that want stricter TS than the language's defaults:
"noPropertyAccessFromIndexSignature": true — forces obj['key'] syntax for index-signature access, making accidental property reads visible."noImplicitReturns": true — every code path in a function must return a value; prevents silently returning undefined."allowUnreachableCode": false — flags dead code after return/throw as an error rather than a warning."verbatimModuleSyntax": true — requires import type for type-only imports, keeping runtime module graph clean and bundler-friendly.Define the schema once; infer the TypeScript type from it — never the other way around.
import { z } from 'zod';
const QueueMessageSchema = z.object({
eventType: z.enum(['ORDER_PLACED', 'ORDER_CANCELLED']),
orderId: z.string().uuid(),
timestamp: z.coerce.date(),
});
type QueueMessage = z.infer<typeof QueueMessageSchema>;
// In the queue consumer:
async function handleMessage(raw: unknown): Promise<void> {
const msg = QueueMessageSchema.parse(raw); // typed QueueMessage or throws
await processOrder(msg.orderId, msg.eventType);
}
Result<T, E> for expected, recoverable failures (business rules, validation, not-found):
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type FindUserError = { code: 'NOT_FOUND' } | { code: 'DB_UNAVAILABLE' };
async function findUser(id: UserId): Promise<Result<User, FindUserError>> {
const row = await db.users.findUnique({ where: { id } });
if (!row) return { ok: false, error: { code: 'NOT_FOUND' } };
return { ok: true, value: row };
}
Typed exception hierarchy for bugs and unrecoverable failures (programming errors, infrastructure down):
class AppError extends Error {
constructor(message: string, readonly code: string) { super(message); }
}
class ValidationError extends AppError {
constructor(message: string) { super(message, 'VALIDATION_ERROR'); }
}
Use Result when callers must handle every failure path. Use typed exceptions when failure indicates a bug or an outage the caller cannot meaningfully recover from.
as SomeType casts with Zod schemas and let z.infer drive the type.any to unknown. A mechanical change — swap any for unknown throughout; the compiler then surfaces every unsafe access, one by one.unknown that surfaces, write a narrowing function or schema. Prefer Zod schemas at I/O boundaries; prefer type guard functions (isFoo(x): x is Foo) for internal domain checks.tsconfig one option at a time. Add one of the options from the Compiler options section, fix resulting errors, commit. Repeat. Mixing option additions with business changes makes errors harder to attribute.@ts-ignore; existing ones get a ticket-linked TODO.prisma-data-access-guard for Prisma-generated types; nestjs-service-boundary-guard for DTO validation placement; integration-contract-safety for cross-service type contracts.Produce a markdown report with these sections:
any, @ts-ignore, and untyped boundary with its exact file:line location.req.body as X casts with Zod schemas at the controller layer").catch narrows before accessRecord<string, unknown> at boundaries