Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Last Updated: 2026-04-28
Applies to: Encore Health OS (v0.9.0-alpha, TypeScript 6.0.3, ~8,400 .ts/.tsx files)

TL;DR — Numbers

ScenarioTimeNotes
Cold npm run typecheck~8m 48sNo .tsbuildinfo cache
Warm npm run typecheck~12sIncremental cache hits
CI warm (Turbo cache hit)~230msFull skip via content-addressed cache
tsgo (TypeScript Go preview) cold~58s8.5× faster than tsc, no incremental needed
tsgo warm~3s

Root Cause: What Makes This Codebase Slow

Running tsc --extendedDiagnostics on a cold build reveals:
Lines of TypeScript:   1,368,584
Files:                    10,721
Symbols:               9,162,192
Types:                 3,518,177
Instantiations:       31,066,718   ← the smoking gun
Memory used:           7,220,011K  ← 7.2 GB
Check time:              485.84s   ← 98% of total runtime
Total time:              494.18s
Parse + Bind is only ~6 seconds. 98% of the time is spent in type checking. The 31 million type instantiations drive everything. Here is what causes them, ranked by impact:

#1: The 107k-line generated Supabase types.ts (primary bottleneck)

src/integrations/supabase/types.ts is 3.4 MB and defines a single Database type encoding 1,126 tables × 3 operation shapes (Row/Insert/Update) = 3,378 table-type variants, all nested inside deeply-parameterized generic helpers (TablesInsert<T>, TablesUpdate<T>, Enums<T>, etc.).
  • 672 source files directly import from it
  • 1,866 uses of Database['public']['Tables']['...']['Row'] indexing
  • 7,426 .from(tableName) calls — every call instantiates the full Database<> generic tree against the table-name union
Every time the TypeScript checker encounters .from('ce_activities') it must:
  1. Resolve tableName as "ce_activities" against the 1,039-member string union of all table names
  2. Distribute Database['public']['Tables'][typeof tableName] across all 1,126 branches
  3. Check assignability for every instantiation
This is the classic “large union × heavy generics = exponential work” problem.

#2: 317 files use type intersections (&) instead of interface extends

TypeScript’s wiki explicitly calls this out: intersections are checked structurally every time (O(N²) in members), while interface extends results are cached. Files like src/cores/gr/types/index.ts (1,140 lines, 198 exports) frequently define types as A & B & { ... }.

#3: 392 barrel index.ts files

Barrel files don’t cause check-time overhead directly, but they dramatically increase the module graph that TypeScript must resolve and bind before checking begins. The biggest ones (src/cores/gr/types/index.ts, src/cores/rh/types/index.ts) re-export hundreds of types that may already be inlined elsewhere, forcing the checker to track them across module boundaries repeatedly.

#4: Tests mixed into the Turbo input glob

turbo.json lists tests/**/*.ts and tests/**/*.tsx as inputs to the typecheck task, but tsconfig.app.json only includes src/. This means Turbo correctly invalidates its cache when test files change — even though tsc never actually type-checks them in the app project. A stray test file (tests/utils/supabase-test-client.ts) is included because it is transitively reachable from a src/ import via path resolution.

#5: No strict mode / inferred return types

With strict: false and noImplicitAny: false, TypeScript must infer return types for almost every function. Complex functions that return supabase.from(...) chains have inferred return types that cascade into many re-instantiations of the Database generics.

Recommendations — Prioritized

Priority 1 — Immediate (hours of effort, high impact)

1a. Migrate to tsgo (TypeScript 7 Go native preview) for local dev and pre-commit

The TypeScript team has ported the compiler to Go (@typescript/native-preview). Tested on this exact codebase:
Time
tsc cold8m 48s
tsgo cold58s
tsgo warm~3s
This is a pre-release CLI (7.0.0-dev.*) and should not replace tsc in the authoritative CI gate yet — tsgo currently has minor compatibility gaps. However, it is safe to offer it as an opt-in developer speedup:
// package.json — add alongside existing scripts
"typecheck:fast": "tsgo --noEmit --project tsconfig.app.json",
Install as an optional devDependency:
npm install --save-dev --save-optional @typescript/native-preview
Update the pre-commit hook to use typecheck:fast for the interactive developer workflow, keeping typecheck (plain tsc) in CI. This alone cuts pre-commit wait time from ~12s (warm tsc) to ~3s. Risk: tsgo may miss a small number of errors that tsc catches. CI must keep tsc as the authoritative gate. As tsgo reaches stability (expected TypeScript 7.0 stable), flip CI to tsgo as well.

1b. Add verbatimModuleSyntax: true to tsconfig.app.json

This flag makes all type-only imports require import type, which:
  • Eliminates the compiler’s need to determine at emit time whether an import is value or type
  • Reduces the number of files that must be “checked together” vs checked in isolation
  • Is already enforced by the Biome linter rule useImportType — this makes it a compiler-enforced invariant too
// tsconfig.app.json compilerOptions
"verbatimModuleSyntax": true,
Compatibility note: allowJs: true (set in root tsconfig.json) is incompatible with verbatimModuleSyntax in some edge cases. Since tsconfig.app.json does not enable allowJs, this is safe.

1c. Narrow the Turbo typecheck input glob to exclude test files

Currently turbo.json lists tests/**/*.ts and tests/**/*.tsx as inputs, which means any test file change invalidates Turbo’s typecheck cache even though tsconfig.app.json only checks src/. Change it:
"inputs": [
  "src/**/*.ts",
  "src/**/*.tsx",
  "tsconfig.json",
  "tsconfig.app.json",
  "tsconfig.node.json"
]
The tests/** globs that were previously listed have been removed. turbo.json does not support JSON comments, so this note lives here instead. This allows Turbo to skip the typecheck entirely on PRs that only touch test files, which is a common pattern.

Priority 2 — Medium (days of effort, very high long-term impact)

2a. Split src/integrations/supabase/types.ts into per-core type modules

The 107k-line monolithic types.ts is the single biggest driver of the 31M instantiations. Each Database['public']['Tables']['x'] access forces TypeScript to evaluate every table branch. Recommended approach:
  1. Generate per-core type slice files (e.g., src/integrations/supabase/types.ce.ts, types.hr.ts, etc.) containing only the tables owned by each core.
  2. Each core imports from its own slice file instead of the monolithic one.
  3. Reduce the Database type to a minimal “plumbing” type used only by the Supabase client itself.
A custom code-generation step can produce these from the Supabase schema:
# Example: generate per-core type slices
npx supabase gen types typescript --local > src/integrations/supabase/types.ts
npx tsx scripts/database/split-types-by-core.ts  # new script to be authored
Expected impact: Reduces union breadth from 1,126 tables to ~50–150 tables per core, cutting instantiation count by 7-15×.

2b. Replace type X = A & B patterns with interface X extends A, B

In files like src/cores/gr/types/index.ts, src/cores/rh/types/index.ts, and src/platform/permissions/constants.ts, convert intersection types to interface extension wherever possible:
// Before (slow — re-checked structurally every time)
type UserWithPermissions = User & { permissions: Permission[] };

// After (fast — result cached after first check)
interface UserWithPermissions extends User {
  permissions: Permission[];
}
Run a codemod to identify candidates:
npx ts-migrate-types --pattern "type * = * & *" src/
Focus first on the largest type definition files (by line count) as those have the highest payoff.

2c. Add explicit return type annotations to complex functions

Functions that call supabase.from(...) chains without return type annotations force TypeScript to re-infer the full generic return type on every call site. Adding explicit Promise<SomeRow[]> or PostgrestQueryBuilder<...> annotations breaks the inference chain:
// Before: inferred return type triggers 31M instantiations
async function getUsers(orgId: string) {
  return supabase.from('pf_users').select('*').eq('organization_id', orgId);
}

// After: explicit type — checker stops at the annotation
async function getUsers(orgId: string): Promise<{ data: PfUser[] | null; error: PostgrestError | null }> {
  return supabase.from('pf_users').select('*').eq('organization_id', orgId);
}
The docs:comments:audit:changed script already enforces TSDoc on exported APIs. This is the same principle applied to return types of functions that interact with the Supabase client.

Priority 3 — Architectural (weeks of effort, maximum long-term impact)

3a. TypeScript Project References with composite: true

Break the monolithic tsconfig.app.json into per-core projects that reference each other. This is the TypeScript team’s recommended approach for large codebases.
tsconfig.json (solution file — references all)
├── tsconfig.platform.json  (src/platform/**)
├── tsconfig.core-hr.json   (src/cores/hr/**)
├── tsconfig.core-fa.json   (src/cores/fa/**)
... (one per core)
└── tsconfig.routes.json    (src/routes/**)
With tsc -b (build mode):
  • Each sub-project produces a .d.ts declaration file (not the full source types)
  • Downstream projects check against the declaration, not the full source
  • Changed files only re-check their sub-project and any downstream projects that directly depend on them
Constraints for this codebase:
  • composite: true requires declaration: true, which is incompatible with noEmit: true — you would need to emit .d.ts files into a temp location (e.g., node_modules/.cache/tsc-declarations/)
  • The existing architecture enforcement (no core-to-core imports) maps cleanly to project reference boundaries
  • Estimated setup effort: 2–3 days to author the per-core configs and validate the dependency graph
  • Expected outcome: tsc -b --incremental cold time drops from ~9min to ~1–2min; warm stays ~10s
This is the highest-effort but most structurally sound long-term fix.

3b. Migrate to tsgo (TypeScript 7 Go) as the primary CI type-checker

Once TypeScript 7.0 stable is released (expected late 2026), migrate the typecheck CI step from tsc to tsgo. As demonstrated above, this is a near drop-in replacement that delivers ~8-10× speedup with no code changes. Track readiness at: https://github.com/microsoft/typescript-go

Quick-Win Configuration Changes (Apply Now)

These changes can be made immediately with zero risk:

1. Add typecheck:fast script using tsgo

// package.json
"typecheck:fast": "tsgo --noEmit --project tsconfig.app.json",

2. Add verbatimModuleSyntax to tsconfig.app.json

// tsconfig.app.json
"verbatimModuleSyntax": true,

3. Fix the Turbo input glob for typecheck

// turbo.json — "//#typecheck"
"inputs": [
  "src/**/*.ts",
  "src/**/*.tsx",
  "tsconfig.json",
  "tsconfig.app.json",
  "tsconfig.node.json"
]

4. Pre-commit hook: use typecheck:fast for developer iteration

In .husky/pre-commit or the lint-staged config, replace npm run typecheck with npm run typecheck:fast for the interactive pre-commit check. Keep npm run typecheck in CI.

Measurement Commands

Use these to track progress:
# Cold typecheck baseline (delete cache first)
rm -rf node_modules/.cache/tsc
time npm run typecheck

# Cold tsgo baseline
TSGO=/path/to/tsgo
time $TSGO --noEmit --project tsconfig.app.json

# Extended diagnostics (shows instantiation count)
NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit -p tsconfig.app.json --extendedDiagnostics

# Performance trace (open in Perfetto: https://ui.perfetto.dev)
NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit -p tsconfig.app.json --generateTrace /tmp/tsc-trace
npx @typescript/analyze-trace /tmp/tsc-trace

# Warm incremental check
time npm run typecheck

# File count in compilation
npx tsc --listFilesOnly -p tsconfig.app.json | wc -l

Summary Table

RecommendationEffortImpactRisk
Add typecheck:fast with tsgo1 hour★★★★★ — 8.5× cold speedupLow — CI keeps tsc
Fix Turbo input glob30 min★★★ — skip on test-only PRsNone
Add verbatimModuleSyntax1 hour★★ — reduces redundant checkingLow
Split types.ts by core2–3 days★★★★★ — eliminates 31M instantiationsMedium — requires gen tooling
Convert & to extends1–2 days★★★ — caches intersection resultsLow
Explicit return types on Supabase fns2–3 days★★★★ — breaks inference cascadeLow
Full project references1 week★★★★★ — structural + incrementalHigh — large config change
Upgrade to tsgo/TS7 in CIFuture★★★★★ — 10× with no code changesWait for stable

References