TypeScript Strictness
The shared tsconfig.json presets -- strict mode flags, module resolution, target settings, and how each package extends the base config.
Trovella uses TypeScript 5.9 with a strict configuration that goes beyond the default strict: true flag. All type checking configuration is centralized in @repo/typescript-config, and every package extends one of its presets.
Preset Hierarchy
base.json <- strict flags, module settings, ES2022 target
library.json <- base + outDir/rootDir for library builds
nextjs.json <- base + JSX, DOM libs, Next.js plugin
Each package's tsconfig.json extends one preset and may add package-specific paths or include patterns. The presets themselves are never modified at the package level -- strictness is uniform across the monorepo.
Base Configuration
The base.json preset (packages/config-typescript/base.json) defines the full compiler options:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"module": "esnext",
"target": "es2022",
"lib": ["es2022"],
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true
},
"exclude": ["node_modules", "dist", ".next", "out", "build"]
}
Strict Mode Flags
strict: true enables all of these individual flags:
| Flag | What it prevents |
|---|---|
strictNullChecks | Accessing properties on possibly null or undefined values |
strictFunctionTypes | Unsound function argument contravariance |
strictBindCallApply | Incorrect bind, call, or apply usage |
strictPropertyInitialization | Class properties not assigned in the constructor |
noImplicitAny | Variables and parameters without explicit or inferred types |
noImplicitThis | this expressions with an implied any type |
alwaysStrict | Emits "use strict" in every file |
useUnknownInCatchVariables | Catch clause variables typed as unknown instead of any |
Beyond strict: true
Four additional flags are enabled on top of the standard strict set:
noUncheckedIndexedAccess -- adds | undefined to the return type of index signatures and array access. This forces explicit null checks when accessing dictionary or array values by index:
const map: Record<string, number> = { a: 1 };
const val = map["b"]; // Type is `number | undefined`, not `number`
if (val !== undefined) {
// Safe to use val as number
}
noImplicitOverride -- requires the override keyword when a subclass method overrides a base class method. This catches accidental overrides and makes intentional ones explicit.
noPropertyAccessFromIndexSignature -- forces bracket notation for index signature properties. If a type has [key: string]: unknown, you must write obj["key"] instead of obj.key. This makes it clear when you are accessing a known property versus an arbitrary key.
forceConsistentCasingInFileNames -- prevents importing ./MyFile when the file is actually ./myFile. Critical in a monorepo that builds on both macOS (case-insensitive FS) and Linux CI (case-sensitive FS).
Module and Emit Settings
| Setting | Value | Why |
|---|---|---|
moduleResolution | bundler | Matches the resolution behavior of Turbopack and Node.js with ESM |
module | esnext | Emit modern ESM syntax |
target | es2022 | Node.js 22+ and modern browsers support ES2022 natively |
isolatedModules | true | Required for Turbopack/SWC -- each file must be independently transformable |
verbatimModuleSyntax | true | Requires import type for type-only imports; aligns with ESLint's consistent-type-imports rule |
esModuleInterop | true | Allows import fs from "fs" for CJS modules without * as syntax |
resolveJsonModule | true | Enables importing .json files with type inference |
Output Settings
| Setting | Value | Why |
|---|---|---|
declaration | true | Generates .d.ts files for downstream package consumers |
declarationMap | true | Maps .d.ts declarations back to source for IDE navigation |
sourceMap | true | Enables source map debugging |
skipLibCheck | true | Skips type checking of .d.ts files from node_modules for build performance |
Library Preset
The library.json preset (packages/config-typescript/library.json) extends base.json and adds:
{
"extends": "./base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
}
}
All packages/* workspaces use this preset. The outDir/rootDir settings ensure compiled output lands in dist/ and source roots are consistent.
Next.js Preset
The nextjs.json preset (packages/config-typescript/nextjs.json) extends base.json with web-specific settings:
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "es2022"],
"plugins": [{ "name": "next" }],
"module": "esnext",
"moduleResolution": "bundler",
"allowJs": true
}
}
| Addition | Why |
|---|---|
jsx: "preserve" | Next.js handles JSX transformation via SWC; TypeScript should not transform it |
lib: ["dom", "dom.iterable"] | Browser API type definitions for client components |
plugins: [{ "name": "next" }] | Next.js compiler plugin for server/client component validation in the editor |
allowJs: true | Next.js projects may include JavaScript config files |
The apps/web/tsconfig.json extends this preset and adds path aliases:
{
"extends": "@repo/typescript-config/nextjs.json",
"compilerOptions": {
"declaration": false,
"declarationMap": false,
"paths": {
"@/*": ["./src/*"]
}
}
}
The web app disables declaration and declarationMap because Next.js does not need to produce .d.ts files -- it is a leaf application, not a consumed library.
How Type Checking Runs
Locally: Run pnpm typecheck to type-check all packages via Turborepo. Each package has a typecheck script in its package.json (typically tsc --noEmit).
In CI: The quality job runs pnpm turbo typecheck --affected, which only checks packages that have changed files. The --affected flag uses Turborepo's change detection against the base branch.
In the editor: ESLint's strictTypeChecked config uses parserOptions.projectService to find the nearest tsconfig.json automatically. The allowDefaultProject setting handles config files (*.config.ts, *.config.mjs) that are not included in any tsconfig.json.
Interaction with ESLint
TypeScript and ESLint work together on two fronts:
-
verbatimModuleSyntax(TypeScript) andconsistent-type-imports(ESLint) both enforceimport typesyntax. TypeScript emits an error at build time; ESLint catches it earlier during linting and auto-fixes it. -
strictTypeChecked(ESLint preset) uses the TypeScript compiler API for type-aware rules likeno-floating-promisesandno-misused-promises. These rules need a running type checker, which is why ESLint'sparserOptions.projectServicemust resolve to a validtsconfig.json.
ESLint Configuration
The shared ESLint flat config -- TypeScript strict rules, naming conventions, complexity limits, import sorting, code smell detection, and SDK boundary enforcement.
Prettier Formatting
Prettier configuration, file type coverage, the Tailwind CSS plugin, and the .prettierignore exclusions.