0003 — Toolchain: TypeScript 7 for compilation, TypeScript 6 for the linter¶
Status: accepted Date: 2026-08-02
Context¶
The brief (§3) fixes the stack but does not name a linter, while Phase 0 requires
lint to run in CI. The vendored @clycites/schema@0.2.0 declares
typescript: ^7.0.2, so the whole workspace compiles under TypeScript 7.
typescript-eslint@8.65.0 refuses to load against TypeScript 7:
Support is tracked in typescript-eslint#10940. TypeScript 7 is documented as installable side-by-side with TypeScript 6.
Decision¶
- Each workspace package declares its own
typescript: ^7.0.2devDependency and compiles with it.pnpm --filter <pkg> run buildresolvestscfrom the package, so builds are unaffected. - The workspace root pins
typescript: 6.0.3. The root copy exists only to satisfytypescript-eslint's peer dependency; nothing is compiled with it. pnpm lintrunseslint .from the root rather than through Turborepo, because it is a single whole-workspace pass, not a per-package task.packages/schema/**is excluded from linting. It is vendored and read-only (brief §2); linting code we are not permitted to change produces noise, not signal.
Consequences¶
- Two TypeScript versions are installed. This is deliberate and confined to tooling; no source file is ever compiled by TypeScript 6.
- When typescript-eslint supports TypeScript 7, drop the root pin to
^7and delete this note. - CI runs on Node 22 (the version named in §3). Local development on Node 24
also works;
enginesrequires>=22.
Dependency injection must name its tokens¶
Tests run through tsx, which transpiles with esbuild, and esbuild does not
implement emitDecoratorMetadata. Nest's usual trick of reading constructor
parameter types from design:paramtypes therefore produces nothing under the
test runner: the container constructs the class with no arguments at all and
every dependency is undefined at the first call, with no error from Nest to
say so.
So every constructor parameter carries an explicit @Inject(Token), including
the ones whose type would have been enough under tsc. It costs a few
characters and removes an entire class of failure that only appears at runtime,
only in one of the two toolchains, and only once a request arrives.
Findings reported upstream¶
Linting the vendored schema (before it was excluded) surfaced three unused bindings. They are cosmetic and were not fixed, because the package is read-only here:
src/primitives.ts:28—brandparameter ofid()is unused; the brand is carried by the type parameterB.src/entities/index.ts:11—HarvestIdimported but unused.test/invariants.test.ts:3—zimported but unused.