원클릭으로
typescript-strict
TypeScript strict mode patterns. Use when writing any TypeScript code.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
TypeScript strict mode patterns. Use when writing any TypeScript code.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when asked to find first customers, early adopters, design partners, beta users, or leads for a startup or product idea, to research who might buy, to define an ideal customer profile (ICP), or to validate early demand from public signals — given a product URL, repository, or description.
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
Feedback-loop-first diagnosis for hard bugs and performance regressions — build a tight red-capable repro loop before hypothesising.
Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.
Compact the current conversation into a handoff document for another agent to pick up.
Feedback-loop-first diagnosis for hard bugs and performance regressions — build a tight red-capable repro loop before hypothesising.
| name | typescript-strict |
| description | TypeScript strict mode patterns. Use when writing any TypeScript code. |
any - ever. Use unknown if type is truly unknownas Type) without justificationtype over interface for data structuresinterface for behavior contracts onlyCommon patterns:
src/schemas/ for shared schemasKey principle: Avoid duplicating the same validation logic across multiple files.
Common anti-pattern:
Defining the same schema in multiple places:
Why This Is Wrong:
Solution:
// ✅ CORRECT - Define schema once, import everywhere
// src/schemas/user-requests.ts
import { z } from "zod";
export const CreateUserRequestSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;
// Use in multiple places
import { CreateUserRequestSchema } from "../schemas/user-requests.js";
// Express endpoint
app.post("/users", (req, res) => {
const result = CreateUserRequestSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
// Use result.data (validated)
});
// GraphQL resolver
const createUser = (input: unknown) => {
const validated = CreateUserRequestSchema.parse(input);
return userService.create(validated);
};
Key Benefits:
Remember: If validation logic is duplicated, extract it into a shared schema.
The Rule:
new to create dependencies inside functionsWithout dependency injection:
With dependency injection:
❌ WRONG - Creating implementation internally
export const createOrderProcessor = ({
paymentGateway,
}: {
paymentGateway: PaymentGateway;
}): OrderProcessor => {
// ❌ Hardcoded implementation!
const orderRepository = new InMemoryOrderRepository();
return {
processOrder(order) {
const payment = paymentGateway.charge(order.total);
if (!payment.success) {
return { success: false, error: payment.error };
}
orderRepository.save(order); // Using hardcoded repository
return { success: true, data: order };
},
};
};
Why this is WRONG:
✅ CORRECT - Injecting all dependencies
export const createOrderProcessor = ({
paymentGateway, // ✅ Injected
orderRepository, // ✅ Injected
}: {
paymentGateway: PaymentGateway;
orderRepository: OrderRepository;
}): OrderProcessor => {
return {
processOrder(order) {
const payment = paymentGateway.charge(order.total);
if (!payment.success) {
return { success: false, error: payment.error };
}
orderRepository.save(order); // Delegate to injected dependency
return { success: true, data: order };
},
};
};
Why this is CORRECT:
The choice between type and interface is architectural, not stylistic.
interfaceWhen to use: Interfaces define contracts that must be implemented.
Examples: UserRepository, PaymentGateway, EmailService, CacheProvider
Why interface for behavior contracts?
Signals implementation contracts clearly
Better TypeScript errors when implementing
class X implements UserRepository gives clear errorsimplements keywordConventional for dependency injection
Class-friendly for implementations
Example:
// Behavior contract
export interface UserRepository {
findById(id: string): Promise<User | undefined>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
// Concrete implementation
export class PostgresUserRepository implements UserRepository {
async findById(id: string): Promise<User | undefined> {
// Implementation
}
// ... other methods
}
typeWhen to use: Types define immutable data structures.
Examples: User, Order, Config, ApiResponse
Why type for data?
Emphasizes immutability
readonly signal "don't mutate this"Better for unions, intersections, mapped types
type Result<T, E> = Success<T> | Failure<E>type Partial<T> = { [P in keyof T]?: T[P] }Prevents accidental mutations
readonly properties enforce immutability at type levelMore flexible composition
Example:
// Data structure
export type User = {
readonly id: string;
readonly email: string;
readonly name: string;
readonly roles: ReadonlyArray<string>;
};
export type Order = {
readonly id: string;
readonly userId: string;
readonly items: ReadonlyArray<OrderItem>;
readonly total: number;
};
This pattern supports clean architecture:
interface) = Boundaries between layerstype) = Data flowing through the systemreadonly){
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"allowUnusedLabels": false
}
}
Core strict flags:
strict: true - Enables all strict type checking optionsnoImplicitAny - Error on expressions/declarations with implied any typestrictNullChecks - null and undefined have their own types (not assignable to everything)noUnusedLocals - Error on unused local variablesnoUnusedParameters - Error on unused function parametersnoImplicitReturns - Error when not all code paths return a valuenoFallthroughCasesInSwitch - Error on fallthrough cases in switch statementsAdditional safety flags (CRITICAL):
noUncheckedIndexedAccess - Array/object access returns T | undefined (prevents runtime errors from assuming elements exist)exactOptionalPropertyTypes - Distinguishes property?: T from property: T | undefined (more precise types)noPropertyAccessFromIndexSignature - Requires bracket notation for index signature properties (forces awareness of dynamic access)forceConsistentCasingInFileNames - Prevents case sensitivity issues across operating systemsallowUnusedLabels - Error on unused labels (catches accidental labels that do nothing)@ts-ignore without explicit comments explaining whyThe noUnusedParameters rule can reveal architectural problems:
Example: A function with an unused parameter often indicates the parameter belongs in a different layer. Strict mode catches these design issues early.
readonly on All Data Structures// ✅ CORRECT - Immutable data structure
type ApiRequest = {
readonly method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
readonly url: string;
readonly headers?: {
readonly [key: string]: string;
};
readonly body?: unknown;
};
// ❌ WRONG - Mutable data structure
type ApiRequest = {
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
url: string;
headers?: {
[key: string]: string;
};
body?: unknown;
};
// ✅ CORRECT - Immutable array
type ShoppingCart = {
readonly id: string;
readonly items: ReadonlyArray<CartItem>;
};
// ❌ WRONG - Mutable array
type ShoppingCart = {
readonly id: string;
readonly items: CartItem[];
};
Prefer Result<T, E> types over exceptions for expected errors:
export type Result<T, E = Error> =
| { readonly success: true; readonly data: T }
| { readonly success: false; readonly error: E };
// Usage
export const findUser = (userId: string): Result<User> => {
const user = database.findById(userId);
if (!user) {
return { success: false, error: new Error("User not found") };
}
return { success: true, data: user };
};
Why result types?
// ✅ CORRECT - Factory function
export const createOrderService = (
orderRepository: OrderRepository,
paymentGateway: PaymentGateway,
): OrderService => {
return {
async createOrder(order) {
const validation = validateOrder(order);
if (!validation.success) {
return validation;
}
await orderRepository.save(order);
return { success: true, data: order };
},
async processPayment(orderId, paymentInfo) {
const order = await orderRepository.findById(orderId);
if (!order) {
return { success: false, error: new Error("Order not found") };
}
return paymentGateway.charge(order.total, paymentInfo);
},
};
};
// ❌ WRONG - Class-based creation
export class OrderService {
constructor(
private orderRepository: OrderRepository,
private paymentGateway: PaymentGateway,
) {}
async createOrder(order: Order) {
// Implementation with `this`
}
}
Why factory functions?
this context issuesnew keyword)These are common patterns, not strict rules. Adapt to your project's needs.
Interfaces (Behavior Contracts)
src/interfaces/, src/contracts/, src/ports/UserRepository, PaymentGateway, EmailServiceTypes (Data Structures)
src/types/, src/models/, co-located with featuresUser, Order, ConfigSchemas (Validation)
src/schemas/, src/validation/, co-located with featuresUserSchema, OrderSchema, ConfigSchemaBusiness Logic
src/services/, src/domain/, src/use-cases/createUserService, processOrder, validatePaymentImplementation Details
src/adapters/, src/infrastructure/, src/repositories/PostgresUserRepository, StripePaymentGateway, RedisCacheNote: These are suggestions based on common patterns. Your project may use different conventions. The key principles are:
// API responses, user input, external data
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
// Validate at boundary
const user = UserSchema.parse(apiResponse);
Partial<T>, Pick<T>, etc.)// ✅ CORRECT - No schema needed
type Result<T, E> = { success: true; data: T } | { success: false; error: E };
// ✅ CORRECT - Interface, no validation
interface UserService {
createUser(user: User): void;
}
These principles support immutability and type safety:
// ✅ CORRECT - Pure function
const addItem = (
items: ReadonlyArray<Item>,
newItem: Item,
): ReadonlyArray<Item> => {
return [...items, newItem]; // Returns new array
};
// ❌ WRONG - Impure function (mutates)
const addItem = (items: Item[], newItem: Item): void => {
items.push(newItem); // Mutates input!
};
readonly enforce this// ✅ CORRECT - Immutable update
const updateUser = (user: User, updates: Partial<User>): User => {
return { ...user, ...updates }; // New object
};
// ❌ WRONG - Mutation
const updateUser = (user: User, updates: Partial<User>): void => {
Object.assign(user, updates); // Mutates!
};
// ✅ CORRECT - Composed functions
const validate = (input: unknown) => UserSchema.parse(input);
const saveToDatabase = (user: User) => database.save(user);
const createUser = (input: unknown) => saveToDatabase(validate(input));
// ❌ WRONG - Complex monolithic function
const createUser = (input: unknown) => {
if (typeof input !== "object" || !input) throw new Error("Invalid");
if (!("email" in input)) throw new Error("Missing email");
// ... 50 more lines of validation and registration
};
map, filter, reduce for transformations// ✅ CORRECT - Functional array methods
const activeUsers = users.filter((u) => u.active);
const userEmails = users.map((u) => u.email);
// ❌ WRONG - Imperative loops
const activeUsers = [];
for (const u of users) {
if (u.active) {
activeUsers.push(u);
}
}
For type-safe primitives:
type UserId = string & { readonly brand: unique symbol };
type PaymentAmount = number & { readonly brand: unique symbol };
// Type-safe at compile time
const processPayment = (userId: UserId, amount: PaymentAmount) => {
// Implementation
};
// ❌ Can't pass raw string/number
processPayment("user-123", 100); // Error
// ✅ Must use branded type
const userId = "user-123" as UserId;
const amount = 100 as PaymentAmount;
processPayment(userId, amount); // OK
When writing TypeScript code, verify:
any types - using unknown where type is truly unknowntype for data structures with readonlyinterface for behavior contracts (ports)readonly on all data structure properties