Trovella Wiki

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:

FlagWhat it prevents
strictNullChecksAccessing properties on possibly null or undefined values
strictFunctionTypesUnsound function argument contravariance
strictBindCallApplyIncorrect bind, call, or apply usage
strictPropertyInitializationClass properties not assigned in the constructor
noImplicitAnyVariables and parameters without explicit or inferred types
noImplicitThisthis expressions with an implied any type
alwaysStrictEmits "use strict" in every file
useUnknownInCatchVariablesCatch 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

SettingValueWhy
moduleResolutionbundlerMatches the resolution behavior of Turbopack and Node.js with ESM
moduleesnextEmit modern ESM syntax
targetes2022Node.js 22+ and modern browsers support ES2022 natively
isolatedModulestrueRequired for Turbopack/SWC -- each file must be independently transformable
verbatimModuleSyntaxtrueRequires import type for type-only imports; aligns with ESLint's consistent-type-imports rule
esModuleInteroptrueAllows import fs from "fs" for CJS modules without * as syntax
resolveJsonModuletrueEnables importing .json files with type inference

Output Settings

SettingValueWhy
declarationtrueGenerates .d.ts files for downstream package consumers
declarationMaptrueMaps .d.ts declarations back to source for IDE navigation
sourceMaptrueEnables source map debugging
skipLibChecktrueSkips 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
  }
}
AdditionWhy
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: trueNext.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:

  1. verbatimModuleSyntax (TypeScript) and consistent-type-imports (ESLint) both enforce import type syntax. TypeScript emits an error at build time; ESLint catches it earlier during linting and auto-fixes it.

  2. strictTypeChecked (ESLint preset) uses the TypeScript compiler API for type-aware rules like no-floating-promises and no-misused-promises. These rules need a running type checker, which is why ESLint's parserOptions.projectService must resolve to a valid tsconfig.json.

On this page