Owns the TypeScript 7 strict-mode configuration, the shared tsconfig bases in packages/tsconfig/, and the tsgo --noEmit gate stage. Pinned to @typescript/native-preview — the Go-based native compiler (microsoft/typescript-go). The binary is tsgo (replaces tsc); same tsconfig.json shape, same flags, same diagnostics on TypeScript files (parity per upstream's status table). Emit is handled by Bun/Vite — tsgo only checks.
isolatedDeclarations: true is the eventual target but is left off in the base today: the t3-env createEnv return type is generic-heavy and not yet annotated for fast .d.ts emit. Turn it on once the env wiring is annotated and surface the change in AGENTS.md.
When to invoke
- Authoring or editing a
tsconfig.json(root, base, or workspace). - Diagnosing a
tsgo --noEmitfailure during the gate. - Setting up project references between
apps/webandpackages/*. - A user asks for a hand-written interface — redirect to
z.infer<typeof Schema>.
Owns
TypeScript 7 strict config, tsconfig bases in packages/tsconfig, tsgo --noEmit gate behavior, and the "no hand-written type when a Zod schema exists" rule.
Defers to
zod(Wave 2, forward) — for how to author a schema. Once it exists, the type isz.infer<typeof Schema>. The "always usez.infer" rule is owned by zod; ts enforces it through "don't hand-write types that mirror an existing schema."biome— for lint/format of.ts/.tsx. ts owns type-checking; Biome owns syntactic concerns.turborepo— for whentsgo --noEmitruns in the gate sequence (after Stylelint, beforebun test).bun-test— for testing the runtime behavior of types' boundary parses.
Dean-stack rules
- Pillar 4 (CLI-gate-first) means:
tsgo --noEmitexits non-zero on any error; the gate has zero tolerance. - Pillar 2 (Zod-first types) means: every type with a corresponding Zod schema is
z.infer<typeof Schema>; ts does not own the schema authoring (zod does), but it does refuse hand-written duplicates. - Project-wide settings:
"strict": true,"noUncheckedIndexedAccess": true,"exactOptionalPropertyTypes": true,"verbatimModuleSyntax": true. (isolatedDeclarationsis the eventual target but currently OFF — see overview paragraph above for why.) - Use
import type { ... }for type-only imports — required byverbatimModuleSyntax.
Patterns
Shared base in packages/tsconfig/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true,
// "isolatedDeclarations": true, // eventual target — currently off (t3-env createEnv return type not yet annotated for fast .d.ts emit)
"module": "esnext",
"moduleResolution": "bundler",
"target": "es2024",
"lib": ["es2024", "dom", "dom.iterable"],
"jsx": "react-jsx",
"skipLibCheck": true,
"noEmit": true,
"allowImportingTsExtensions": true
}
}
Every workspace tsconfig.json extends this base — never duplicate compiler options across workspaces.
Workspace tsconfig.json
// apps/web/tsconfig.json
{
"extends": "@dean-stack/tsconfig/base.json",
"compilerOptions": {
"types": ["bun", "vite/client"],
"paths": { "~/*": ["./app/*"] }
},
"include": ["app/**/*", "*.config.ts"],
"references": [{ "path": "../../packages/schemas" }]
}
Every tsconfig extends the base; per-workspace overrides are minimal.
Boundary parsing replaces casts
import { z } from "zod";
// schema is the source of truth for the type
export const ScoreSchema = z.object({ value: z.number().int().min(0) });
export type Score = z.infer<typeof ScoreSchema>; // never hand-written
// at the boundary, parse — never cast
function loadScore(raw: unknown): Score {
return ScoreSchema.parse(raw);
}
as casts at module boundaries are forbidden by AGENTS.md. Parse with Zod instead — see zod for the schema authoring rules.
Type-only imports under verbatimModuleSyntax
import type { ReactNode } from "react";
import { z } from "zod";
import type { Score } from "./schema";
Without import type, type-only specifiers leak into runtime imports and verbatimModuleSyntax errors.
Gate invocation
tsgo --noEmit # third stage of `bun run check`
tsgo --noEmit --watch # local inner loop alongside `bun run dev`
tsgo only checks; emit is Bun's/Vite's job. If a script reaches for tsc directly, it's referencing a binary that no longer exists in this repo — swap to tsgo.
Anti-patterns
- Don't hand-write a type that mirrors a Zod schema — use
z.infer<typeof Schema>(seezod). - Don't use
asto cross a module boundary — parse with Zod and letsafeParsecarry the failure. - Don't use
any—unknownplus a Zod parse is the only correct way through an opaque value. - Don't disable
noUncheckedIndexedAccessorexactOptionalPropertyTypes— they catch IDB-shape bugs that the runtime would silently swallow. - Don't use
importfor a type-only specifier —verbatimModuleSyntaxerrors on it; useimport type. - Don't run
tsgo(ortsc) to emit — the build is Bun/Vite; the TypeScript compiler only type-checks here. - Don't depend on the
typescriptpackage by name — this repo ships@typescript/native-previewinstead. Tools that look uptypescriptinnode_modulesmay need a peer/fallback shim if they refuse to resolve through the native-preview package.
Triggers on
typescript 7, ts strict, tsconfig, tsgo --noEmit, ts compiler, isolatedDeclarations, ts project references