Loading...
Loading...
TypeScript/JavaScript guardrails, patterns, and best practices for AI-assisted development. Use when working with TypeScript (.ts, .tsx) or JavaScript (.js, .jsx) files, package.json, or tsconfig.json. Provides strict mode conventions, async patterns, testing standards, and module system guidelines.
npx skill4agent add ar4mirez/samuel typescript-guideApplies to: TypeScript 5+, Node.js 20+, ES2022+, React, Server-Side JS
constreadonlyas constanyunknownany"strict": truestrictNullChecksnoImplicitAnystrictFunctionTypes"noUncheckedIndexedAccess": trueT | undefined"noImplicitReturns": true"noFallthroughCasesInSwitch": true"target": "ES2022""module": "ESNext""moduleResolution": "bundler"@ts-ignore@ts-expect-errorconstletvar??||0""false?.const { id, name } = userindex.tsas constenum// Prefer this
const Status = {
Active: "active",
Inactive: "inactive",
} as const;
type Status = (typeof Status)[keyof typeof Status];
// Over this
enum Status {
Active = "active",
Inactive = "inactive",
}catchErrorcausenew AppError("msg", { cause: original })catchawaitvoidPromise.allawaitAbortControllerPromise.allSettled.then()await// Bad: sequential when order does not matter
const users = await fetchUsers();
const posts = await fetchPosts();
// Good: concurrent
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);
// Good: timeout with AbortController
async function fetchWithTimeout(url: string, ms: number): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}
}importexportrequire()import "./setup"index.tsimport/ordermyproject/
├── src/
│ ├── index.ts # Application entry point
│ ├── config/ # Environment, feature flags
│ │ └── env.ts # Validated env vars (Zod schema)
│ ├── domain/ # Business logic (no I/O)
│ │ ├── user.ts
│ │ └── order.ts
│ ├── services/ # Application services (orchestrate domain + I/O)
│ ├── repositories/ # Data access (database, external APIs)
│ ├── routes/ # HTTP handlers / controllers
│ ├── middleware/ # Express/Koa/Hono middleware
│ ├── utils/ # Pure utility functions
│ └── types/ # Shared type definitions
├── tests/
│ ├── unit/ # Mirror src/ structure
│ ├── integration/ # API and database tests
│ └── fixtures/ # Shared test data
├── tsconfig.json
├── package.json
├── .eslintrc.cjs
├── .prettierrc
└── vitest.config.tsdomain/types/class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 500,
options?: ErrorOptions
) {
super(message, options);
this.name = "AppError";
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`, "NOT_FOUND", 404);
this.name = "NotFoundError";
}
}type Result<T, E = AppError> =
| { ok: true; value: T }
| { ok: false; error: E };
function parseAge(input: string): Result<number> {
const age = Number(input);
if (Number.isNaN(age) || age < 0 || age > 150) {
return { ok: false, error: new AppError("Invalid age", "VALIDATION") };
}
return { ok: true, value: age };
}
// Usage: caller must check before accessing value
const result = parseAge(input);
if (!result.ok) {
return res.status(400).json({ error: result.error.message });
}
console.log(result.value); // Type-safe: numbertry {
await externalService.call();
} catch (error: unknown) {
if (error instanceof AppError) {
logger.warn(error.message, { code: error.code });
return res.status(error.statusCode).json({ error: error.message });
}
// Unknown error: wrap and re-throw
throw new AppError("Unexpected failure", "INTERNAL", 500, { cause: error });
}*.test.tstests/src/describe("ModuleName")it("should [expected behavior] when [condition]")beforeEachbeforeAllanyimport { describe, it, expect } from "vitest";
import { validateEmail } from "./validate";
describe("validateEmail", () => {
const cases = [
{ input: "user@example.com", expected: true, desc: "valid email" },
{ input: "no-at-sign", expected: false, desc: "missing @ symbol" },
{ input: "", expected: false, desc: "empty string" },
{ input: "a@b.c", expected: true, desc: "minimal valid email" },
] as const;
it.each(cases)("returns $expected for $desc", ({ input, expected }) => {
expect(validateEmail(input)).toBe(expected);
});
});import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserService } from "./user.service";
import type { UserRepository } from "./user.repository";
describe("UserService", () => {
let service: UserService;
let repo: UserRepository;
beforeEach(() => {
repo = {
findById: vi.fn(),
save: vi.fn(),
} as unknown as UserRepository;
service = new UserService(repo);
});
it("should throw NotFoundError when user does not exist", async () => {
vi.mocked(repo.findById).mockResolvedValue(null);
await expect(service.getUser("abc")).rejects.toThrow("not found");
expect(repo.findById).toHaveBeenCalledWith("abc");
});
});tsc --noEmit # Type check (no output)
eslint . --ext .ts,.tsx # Lint
prettier --check . # Format check
prettier --write . # Format fix
vitest # Run tests (watch mode)
vitest run # Run tests (CI mode)
vitest run --coverage # With coverage// eslint.config.mjs
import tseslint from "typescript-eslint";
import importPlugin from "eslint-plugin-import";
export default tseslint.config(
...tseslint.configs.strictTypeChecked,
{
rules: {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": ["warn", {
allowExpressions: true,
}],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/strict-boolean-expressions": "error",
"import/order": ["error", {
groups: ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "always",
}],
},
}
);{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"isolatedModules": true,
"verbatimModuleSyntax": true
}
}import { z } from "zod";
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
role: z.enum(["admin", "user", "viewer"]),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// At API boundary
function handleCreateUser(rawBody: unknown): CreateUserInput {
return CreateUserSchema.parse(rawBody); // throws ZodError on failure
}