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

TL;DR — Numbers


Root Cause: What Makes This Codebase Slow

Running tsc --extendedDiagnostics on a cold build reveals:
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: 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:
Install as an optional devDependency:
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
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:
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:
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:
Run a codemod to identify candidates:
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:
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.
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

2. Add verbatimModuleSyntax to tsconfig.app.json

3. Fix the Turbo input glob for typecheck

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:

Summary Table


References