Typescript
TypeScript software architecture baseline with VS Code Agent Skills for AI coding, review, security, testing, API design, and delivery standards.
npx -y skills add batur/ts-baseline-docs --skill typescriptAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Apply the project TypeScript baseline when creating, editing, or reviewing TypeScript code, tsconfig files, imports, module formats, strict typing, NodeNext backend projects, frontend bundler projects, build configs, and package-level TypeScript conventions.
The file declares its own license as Proprietary. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.7 KB, as published. Nobody here has run it
TypeScript Skill
When to use this skill
Use this skill when the task involves TypeScript source code, TypeScript configuration, build configuration, module imports, type design, runtime validation boundaries, Node.js backend TypeScript, frontend/bundler TypeScript, or review of TypeScript code quality.
Also use this skill when the user asks to create, edit, or review:
tsconfig.jsontsconfig.build.json- TypeScript imports/exports
- ESM/CommonJS module choices
- strict typing rules
- backend Node.js TypeScript projects
- frontend TypeScript projects
- package scripts that run
tsc - TypeScript coding standards for AI-generated code
Goal
Produce and review TypeScript that is strict, explicit, ESM-first, maintainable, framework-aware, and aligned with the project architecture baseline.
The TypeScript standard must reduce runtime surprises, preserve module boundaries, avoid implicit behavior, and keep application/domain logic independent from infrastructure details.
Core decisions
Follow these accepted baseline decisions:
- Runtime default is Node.js.
- Package manager default is
pnpm. - Module system is always ESM.
- Backend Node projects use
module: "NodeNext"andmoduleResolution: "NodeNext". - Frontend/bundler projects use
module: "ESNext"andmoduleResolution: "Bundler". - Default target is
ES2022. - Backend
libis["ES2022"]and does not include DOM. - Frontend
libis["ES2022", "DOM", "DOM.Iterable"]. - Strict TypeScript is mandatory.
- Default alias is only
@/* -> src/*. - Backend direct NodeNext imports use explicit
.jsextensions for relative runtime imports. - Frontend bundler imports may be extensionless.
- Use named exports by default.
- Default exports are allowed only for framework/tooling conventions.
- Use
import type/export typefor type-only imports and exports. - Do not use TypeScript types as a substitute for runtime validation at external boundaries.
Required strict compiler options
When creating or reviewing tsconfig.json, enforce these options unless a framework has a documented reason to override them:
{
"compilerOptions": {
"target": "ES2022",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"useUnknownInCatchVariables": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
Prefer skipLibCheck: true for practical build performance. Do not use it as an excuse to ignore application type errors.
Backend Node.js profile
Use this profile for backend services, Node.js APIs, CLI tools, workers, and packages intended to run directly on Node.js.
Required baseline:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"types": ["node"],
"noEmit": true
}
}
For backend relative imports, use runtime .js extensions in TypeScript source:
import { CREATE_USER_SCHEMA } from "./create-user.schema.js";
import type { UserRepository } from "./user.repository.js";
Do not import relative TypeScript files with .ts extensions in backend NodeNext source.
Frontend / bundler profile
Use this profile for Vite, React, browser-based frontend apps, and other bundler-managed TypeScript projects.
Required baseline:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"noEmit": true
}
}
Frontend/bundler projects may use extensionless relative imports because bundlers resolve them.
Do not use the backend NodeNext profile as the frontend browser profile.
Build configuration
Use tsconfig.build.json when the project emits JavaScript or declarations.
Backend app build baseline:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
"tsBuildInfoFile": "dist/.tsbuildinfo"
},
"exclude": [
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/*.spec.ts",
"src/**/*.spec.tsx",
"src/**/__tests__/**"
]
}
Library/package build may enable:
{
"declaration": true,
"declarationMap": true
}
Do not emit test files into production build output.
JavaScript files in TypeScript projects
Prefer TypeScript for application source files.
allowJs: true and checkJs: true are acceptable when the project intentionally typechecks JavaScript tooling files such as eslint.config.js, prettier.config.js, or other config files.
For pure application templates, prefer:
{
"allowJs": false,
"checkJs": false
}
Do not mix JS and TS application code casually.
Import and export rules
Follow these rules in all TypeScript source:
- Use ESM syntax only.
- Use named exports by default.
- Avoid default exports except where a framework or tool requires them.
- Use
import typefor type-only imports. - Use
export typefor type-only exports. - Use
node:prefix for Node.js built-ins. - Use
@/*only for imports fromsrc/*. - Avoid cross-component deep imports.
- Import another component through its public
index.tsAPI.
Preferred import order:
- Side-effect imports, only in entry/setup/instrumentation files
- Node built-ins
- External packages
- Internal alias imports
- Parent imports
- Sibling imports
- Type-only imports
Example:
import "dotenv/config";
import { randomUUID } from "node:crypto";
import { z } from "zod";
import { APP_ERROR_CODE } from "@/shared/errors/app-error-code.js";
import { CREATE_USER_SCHEMA } from "./create-user.schema.js";
import type { UserRepository } from "./user.repository.js";
import type { CreateUserInput } from "./create-user.schema.js";
Type design rules
Use TypeScript to make illegal states difficult to represent, but do not over-engineer simple code.
Prefer:
- explicit input/output types at component boundaries
unknownfor untrusted external data before validation- discriminated unions for state machines and branching flows
as constobjects plus inferred union types for enum-like values- small, composable types near the owning component
ReadonlyArray<T>or readonly object types when immutability is intended
Avoid:
- broad
any - unsafe type assertions
- global
types/dumping grounds - type-only architecture that hides missing runtime validation
- overly generic helper types without a concrete use case
Enum-like constant pattern:
export const APPLICATION_STATUS = {
DRAFT_CREATED: "draft_created",
APPLIED: "applied",
SKIPPED: "skipped",
} as const;
export type ApplicationStatus =
(typeof APPLICATION_STATUS)[keyof typeof APPLICATION_STATUS];
Error and unknown handling
Because useUnknownInCatchVariables is required, caught errors must be narrowed or normalized.
Do not do this:
try {
await operation();
} catch (error) {
console.error(error.message);
}
Prefer:
try {
await operation();
} catch (error: unknown) {
const normalizedError = normalizeError(error);
throw normalizedError;
}
Do not return raw unknown errors to API clients.
Runtime validation boundary
TypeScript types do not validate runtime data.
Treat these values as untrusted until validated:
- request body
- query params
- path params
- headers
- cookies
- environment variables
- webhooks
- third-party provider responses
- browser storage
- AI model outputs
Use Zod or the project validation standard at the boundary before passing data to application/use-case logic.
Do not pass raw unknown, raw request objects, raw provider payloads, or raw env values into application/domain logic.
Environment access
Do not read process.env in application, domain, use-case, repository, adapter, or UI code.
Allowed locations:
src/shared/config/**src/**/config/**- framework/tooling config files
- test setup files, when necessary
Application code consumes typed config objects.
Architecture boundary expectations
TypeScript code must respect component architecture:
- backend business components live under
src/modules/* - frontend feature components live under
src/features/* - technical shared code lives under
src/shared/* - bootstrap/composition root lives under
src/app/* - public component API is
index.ts - files not exported by
index.tsare internal by default
Domain/application code must not directly import provider SDKs or database clients such as Drizzle, Prisma, Supabase JS, Firebase SDK, MongoDB driver, OpenAI SDK, or Stripe SDK. Use adapters, repositories, or infrastructure implementations.
Coding workflow
When editing TypeScript:
- Identify whether the file belongs to backend Node, frontend bundler, shared package, or tooling.
- Apply the correct TypeScript profile.
- Keep imports ESM and profile-correct.
- Add or update explicit boundary types.
- Validate external input at runtime before use-case/domain logic.
- Keep domain/application code independent from DB, SDK, framework, env, and transport details.
- Run or request
pnpm typecheckand relevant tests. - If a compiler option must be relaxed, explain the reason and prefer the smallest scoped override.
Review checklist
Before approving TypeScript changes, check:
- Is the correct backend or frontend TypeScript profile used?
- Is strict mode preserved?
- Are all required strict compiler options present?
- Are backend NodeNext relative imports using
.jsruntime extensions? - Are frontend/bundler configs using
moduleResolution: "Bundler"? - Are type-only imports written with
import type? - Is
anyavoided or explicitly justified? - Are unsafe type assertions avoided?
- Is caught
unknownnormalized before use? - Is external input validated before entering application/use-case logic?
- Is
process.envlimited to config modules? - Are provider/database SDKs kept out of domain/application code?
- Are DB rows or provider payloads prevented from leaking into API DTOs?
- Are test files excluded from build output?
- Does the change require documentation, OpenAPI, config, or ADR updates?
Do not
Do not:
- switch the project to CommonJS
- disable
strict - remove
noUncheckedIndexedAccessorexactOptionalPropertyTypesto silence errors - use backend NodeNext config for browser frontend projects
- use frontend Bundler config for direct Node runtime projects
- import
.tsfiles in backend NodeNext runtime imports - add default exports without framework/tooling need
- use global
types/folders as dumping grounds - pass raw external data into use-cases
- use TypeScript-only types as a security boundary
- read
process.envoutside config modules - use
anyas a shortcut around design issues - hide business rules in type tricks
Good examples
Backend use-case input:
export type CreateUserInput = {
email: string;
displayName: string;
};
export async function createUser(input: CreateUserInput): Promise<User> {
return USER_REPOSITORY.create(input);
}
Validated controller boundary:
export async function createUserController(request: Request) {
const body: unknown = await request.json();
const input = CREATE_USER_SCHEMA.parse(body);
const user = await createUser(input);
return {
data: serializeUser(user),
};
}
Typed config consumption:
import { SERVER_ENV } from "@/shared/config/server-env.js";
export const HTTP_CONFIG = {
PORT: SERVER_ENV.PORT,
} as const;
Bad examples
Raw external data passed into use-case:
const body = await request.json();
await createUser(body);
Wrong backend NodeNext import:
import { createUser } from "./create-user";
Direct env access outside config:
const apiKey = process.env.OPENAI_API_KEY;
Unsafe type assertion:
const input = body as CreateUserInput;
Provider SDK in application logic:
import OpenAI from "openai";
export async function generateProposal() {
const client = new OpenAI();
}