ワンクリックで
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