diff --git a/.changeset/driver-mongodb-test-tsc-program.md b/.changeset/driver-mongodb-test-tsc-program.md new file mode 100644 index 0000000000..79d24ac974 --- /dev/null +++ b/.changeset/driver-mongodb-test-tsc-program.md @@ -0,0 +1,57 @@ +--- +"@objectstack/driver-mongodb": patch +--- + +fix(driver-mongodb): put the test layer in front of tsc, so the package's own typecheck reports a PASS and not a NUMBER (#14917) + +`packages/drivers/driver-mongodb`'s `tsconfig.json` excluded `**/*.test.ts`, and +its `typecheck` script is `tsc --noEmit` against that very config. Measured at +`6ed4b811af` with the dependency closure built: that program admits **0** of the +package's 30 `src/**/*.test.ts` files while all **10** of its non-test `src/**` +files ARE there, so `pnpm --filter @objectstack/driver-mongodb typecheck` +exiting 0 was a true sentence carrying no information about any test file. + +The filing's headline — that a compile-time `Equals` / `IsAny` pin here is +"checked by nothing" — is **false**, and the correction on the card is right: a +second program does compile these files. `check-type-check-coverage.mjs`'s +`remeasureProject` drops only the test glob and compares the result against its +`TEST_DEBT` ledger. Confirmed here by ablation rather than argued: a +deliberately false `Equals` pin added to `mongodb-driver.test.ts` takes that +program from 10 errors to 11, above the ledger's recorded 10, which reddens it. +The pins were never phantoms. What was true is narrower, and is what this change +closes: the only program reading this layer was a **debt ratchet** — an +instrument that reports a number and fails when the number moves, not a gate +that reports a pass. + +Gives the package the #5286 sibling shape (`packages/rest`, `runtime`, +`objectql`, `core`): a `tsconfig.test.json` with module semantics only — +`esnext` / `bundler` / `lib: ES2022`, matching how vitest actually executes +these files — strictness inherited and untouched, named by the `typecheck` +script via `check:test-typecheck`. + +Measured: **10** errors under the ratchet's shape (matching its recorded number, +and its recorded composition `TS1309 x7, TS2550 x3`, class for class), and **0** +under the split. All 10 were config-tier in full — 7 `TS1309` (`await` at module +scope in a program NodeNext compiles as CJS, because this package has no `"type": +"module"`) and 3 `TS2550` (`Array.prototype.at` against a `lib` older than +es2022). Neither class says anything about a test, and nothing was exposed +behind them: there was no unresolved-import cascade here to collapse, so there +is no `+n` term. `noUnusedLocals` / `noUnusedParameters` are live for this +package (unlike `driver-turso`, which switches both off) and neither fires. + +The `TEST_DEBT` entry (10 errors) is **deleted**, not lowered — the graduation +this ratchet's invariant requires. No `test-typecheck-debt.json` is added: +residue is 0, so none is owed (#5286, maintainer-only to open). That leaves all +30 files unledgered, so any error any one of them gains is red on arrival. + +`check:type-source-resolution` went red from onboarding the new program (the +documented onboarding-limb case, #11490): a registry entry is added rather than +`paths`, with its numbers stated in place — 123 tsc programs / 309 pairs before, +124 / 310 after. The single new pair is `@objectstack/objectql`, a devDependency +that no non-test file in `src/` imports. + +No runtime code changes: not one test file and not one source file is edited, so +no shipped behaviour moves — the suite reports the same 552 passed / 147 skipped +across 30 files as before. The `patch` level reflects the published +`package.json` gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` +devDependency. diff --git a/packages/drivers/driver-mongodb/package.json b/packages/drivers/driver-mongodb/package.json index 8f0e375d18..ed800a419c 100644 --- a/packages/drivers/driver-mongodb/package.json +++ b/packages/drivers/driver-mongodb/package.json @@ -16,7 +16,8 @@ "build": "tsup --config ../../../tsup.config.ts && node ../../../scripts/check-dts-emitted.mjs", "dev": "tsc -w", "test": "vitest run", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit && pnpm check:test-typecheck", + "check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/drivers/driver-mongodb --project tsconfig.test.json" }, "dependencies": { "@objectstack/core": "workspace:*", @@ -29,6 +30,7 @@ "@objectstack/objectql": "workspace:*", "@types/node": "^26.2.0", "mongodb-memory-server": "^11.2.0", + "tsx": "^4.23.12", "typescript": "^6.0.3", "vitest": "^4.1.10" }, diff --git a/packages/drivers/driver-mongodb/tsconfig.test.json b/packages/drivers/driver-mongodb/tsconfig.test.json new file mode 100644 index 0000000000..4b3a1b9910 --- /dev/null +++ b/packages/drivers/driver-mongodb/tsconfig.test.json @@ -0,0 +1,91 @@ +// The TEST-layer type-check program (#14917), adopting the mechanism #5286 set +// for `packages/spec` and #5449 generalised — the route `packages/objectql` +// (#13676), `packages/runtime` (#14504) and `packages/core` (#14613) already +// run. `tsconfig.json` beside this file stays exactly as it is: it is the BUILD +// config, and its `**/*.test.ts` exclusion has a reason. This sibling puts the +// excluded layer back in front of tsc, and `package.json`'s `typecheck` script +// NAMES it (via `check:test-typecheck --project`), because a config no script +// invokes is exactly the phantom this whole mechanism is about. +// +// BEFORE THIS FILE, NO tsc PROGRAM COMPILED A SINGLE TEST FILE HERE, and that +// is measured rather than read off the config. At 6ed4b811af with the +// dependency closure built first, `tsc --noEmit --listFiles -p tsconfig.json` +// puts **0** of this package's 30 `src/**/*.test.ts` files in the program — +// while all **10** of its non-test `src/**` files ARE there, so the zero is the +// `exclude` line and not a probe that sees nothing. Under this file the count is +// **30 of 30**, with the same 10 non-test files beside them. +// `pnpm --filter @objectstack/driver-mongodb typecheck` exiting 0 was a true +// sentence carrying no information about any test file in this package. +// +// ⚠️ WHAT THE CARD SAID, AND WHAT WAS ACTUALLY WRONG. The filing's headline — +// that a compile-time `Equals` / `IsAny` pin here is "checked by nothing" — is +// FALSE, and the correction on the card is right: a second program does compile +// these files. `scripts/check-type-check-coverage.mjs`'s `remeasureProject` +// extends this package's tsconfig, drops only the test glob, and compares the +// result against its `TEST_DEBT` ledger. That is how CI caught PR #14914's +// three TS18047 errors, which this package's own `typecheck` could not see. So +// the pins were not phantoms. What was true is narrower and is what this file +// closes: the only program reading this layer was a DEBT RATCHET — an +// instrument that reports a NUMBER and fails when the number MOVES, not a gate +// that reports a pass. +// +// What differs from the build config, and what deliberately does NOT: +// - module semantics ONLY, plus `lib`. The tests are written and executed as +// ESM by vitest (esbuild/vite), while this package has no `"type": +// "module"`, so the build config's NodeNext compiles them as CJS. Measured +// cost of that mismatch here: ALL 10 of the raw diagnostics — TS1309 x7 +// ("cannot use `await` at the top level" in a CJS program, one per suite +// that awaits `startMongod()` at module scope) and TS2550 x3 (all three the +// same `Array.prototype.at` message in `mongodb-findone-options.test.ts`, +// against a `lib` older than es2022). Those 10 are about the CHECK, never +// about the code. Matching vitest is fidelity, not laxity. No `DOM` in +// `lib`: nothing in this layer touches a browser global. +// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, +// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch` +// are inherited from the root config, and `types: ["node"]` restates +// `tsconfig.json`'s — load-bearing for the same reason its comment gives, +// since the `.test.ts` files call the `setTimeout` / `console` users in +// `src/test-mongod.ts` and use those globals themselves. Nothing here may +// loosen a type rule; if a test does not compile, that is the finding. +// ⛔ Not one `any` and not one `@ts-expect-error` was added to any test file +// to open this gate — that shape is what turns a real gate into a phantom. +// - ⚠️ `noUnusedLocals` / `noUnusedParameters` are worth naming explicitly: +// they are `true` at the root and `false` in `driver-turso`'s own overrides +// but NOT in this package's, so this layer meets STRICTER settings than the +// sibling driver whose clean state might otherwise be read as a prediction. +// Measured: neither fires here, in either direction. Zero of the 10. +// - `rootDir` is INHERITED as `./src` and deliberately not widened. Like +// `packages/runtime` and unlike `objectql` / `spec`, this layer produces no +// TS6059: measured, every file this program admits is already under `src` +// (all 30 test files sit beside the sources they exercise). Widening it "to +// be safe" would admit files the build config does not and change what the +// gate judges, so it stays as the build config has it. +// +// ⭐ MEASURED at 6ed4b811af, dependency closure built first (an error count +// taken against an unbuilt closure is not a reading — unresolved-import +// cascades inflate it): this program reports **0 errors across 30 files**, from +// a raw 10 under the inherited NodeNext semantics. Every one of the 10 was +// config-tier and none survives; the residue is EMPTY. So unlike `objectql`, +// `runtime`, `spec` and `core`, this package needs **no** +// `test-typecheck-debt.json` at all, and carries none — which is the strongest +// form of this gate: all 30 files are unledgered, so any error any one of them +// ever gains is red on arrival, starting today. +// +// That empty residue is also why the `TEST_DEBT` entry in +// `scripts/check-type-check-coverage.mjs` GRADUATES in the same change rather +// than being paid down. Its recorded 10 and its recorded composition +// ("TS1309 x7, TS2550 x3") match this file's raw reading exactly, which is the +// cleanest possible confirmation that the ledger was measuring the CHECK's +// misconfiguration and never a defect in the tests. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffb4a8b104..7ac82ca212 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -377,7 +377,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.3.0))(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.3.0))(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/apps/setup: dependencies: @@ -956,6 +956,9 @@ importers: mongodb-memory-server: specifier: ^11.2.0 version: 11.2.0(socks@2.8.9) + tsx: + specifier: ^4.23.12 + version: 4.23.12 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -11652,6 +11655,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.10(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.14.6(@types/node@26.2.0)(typescript@6.0.3) + vite: 8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + '@vitest/mocker@4.1.10(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 @@ -15726,6 +15738,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.2.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.23.12 + yaml: 2.9.0 + vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 @@ -15741,6 +15768,37 @@ snapshots: tsx: 4.23.12 yaml: 2.9.0 + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.3.0))(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 26.2.0 + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) + happy-dom: 20.10.2 + jsdom: 30.0.1(@noble/hashes@2.3.0) + transitivePeerDependencies: + - msw + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.3.0))(msw@2.14.6(@types/node@26.2.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 45bdda702d..35d8315d8f 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -1026,6 +1026,48 @@ const TEST_DEBT = { // Measured: with `rootDir` inherited, the same program reports 116 additional // TS6059 — a config-tier pile that says nothing about any test. +// ── #14917: `@objectstack/driver-mongodb` GRADUATED — and this one really is +// at ZERO, which the six above are not ──────────────────────────────────────── +// +// `driver-mongodb` (10) left this ledger on 2026-09-04, by the same route as +// `runtime`, `cli`, `lint` and the three plugins: it now has a +// `tsconfig.test.json` its `typecheck` script NAMES, so `hidesTests` is false +// for it and this gate's per-PACKAGE approximation has nothing left to +// approximate. ⛔ This change repairs no test file and edits none. +// +// ⭐ WHAT IS DIFFERENT HERE, and it is the whole reading: those six graduated +// into a per-file `test-typecheck-debt.json` holding a residue. This one +// graduates into NO LEDGER AT ALL. Measured at 6ed4b811af with the dependency +// closure built, `tsc --noEmit -p tsconfig.test.json` reports **0 errors across +// all 30 test files**. The recorded 10 was config-tier IN FULL — the same +// dissolution measured for `lint`'s 16, but complete rather than partial. +// +// The attribution has no remainder in either direction. RECORDED here was +// `TS1309 x7, TS2550 x3`; re-measured on the way out at 6ed4b811af by dropping +// only the test glob (this gate's own `remeasureProject` shape), the raw +// program reports those same 10, class for class and file for file. All 7 +// TS1309 are "cannot use `await` at the top level" — this package has no +// `"type": "module"`, so NodeNext compiles as CJS the suites that await +// `startMongod()` at module scope, which vitest executes as ESM. All 3 TS2550 +// are the same `Array.prototype.at` message in `mongodb-findone-options.test.ts` +// against a `lib` older than es2022. Both classes are the CHECK being +// misconfigured; neither is a defect in a test. Under vitest-matching module +// semantics both dissolve and NOTHING is exposed behind them — there is no +// `+n` term here, because there was no unresolved-import cascade to collapse. +// +// ⚠️ So the shrink-only guarantee did not merely move to a finer instrument +// here, it STRENGTHENED twice over. All 30 files are unledgered, so any error +// any one of them gains is red on arrival. And what the package's own +// `typecheck` script reports about its test layer is now a PASS rather than a +// NUMBER — which was this card's actual finding, once its false headline (that +// the `Equals` / `IsAny` pins here were phantoms) had been corrected on the +// thread. The pins were always real; they were read by THIS ratchet, and a +// ratchet fails when a number moves, not when a type is wrong. +// +// ⚠️ PINS_CHECKED reported nothing for this package in either direction and +// still does not: measured on the way out, its test layer holds ZERO +// `@ts-expect-error` directives, so that half had no subject here either. + '@objectstack/mcp': { errors: 53, note: 'TS18046 x51 -- `json` is of type unknown, one `await res.json()` idiom repeated across four ' @@ -1056,21 +1098,6 @@ const TEST_DEBT = { + 're-confirmed at 53 at 62b2655d8, so the next new error in this package goes red on arrival -- ' + 're-establishing a margin deliberately remains a maintainer call (#5278 option A).', }, - '@objectstack/driver-mongodb': { - errors: 10, - note: 'TS1309 x7, TS2550 x3. Was 43 (TS2345 x33 + these 10), measured at 5ab08428 and still exactly ' - + '43 at d367f03d6^ -- the commit immediately before PR #6210. That PR (#6075) narrowed this ' - + "driver's six IDataDriver query methods to `DriverQuery`, which is what retired all 33 TS2345: " - + "they were this package's OWN test literals failing `Property 'object' is missing in type` " - + 'against a `QueryAST` that still required it. The ledger was never ratcheted down, so 33 errors ' - + 'of slack sat here. #6212 batch C lowers it to the measured 10 because that slack made the batch ' - + "OWN change unpinnable: this package's tsconfig excludes `*.test.ts`, so `pnpm typecheck` cannot " - + "see `aggregate`'s narrowing at all, and its only consumers are those excluded tests. Reverting " - + '`aggregate(object, query: DriverQuery)` back to `QueryAST` measures 12 here -- which the old ' - + '43-ceiling would have swallowed in silence. At 10 it goes red, which is the whole point of a ' - + 'ratchet. Re-measured 10 at 2bc187641, and the pristine tree at that commit reports the same 10, ' - + "so none of the -33 is this PR's doing.", - }, '@objectstack/formula': { errors: 17, note: 'TS2591 x6 (`process`), TS2345 x3, TS2352 x3, TS1470 x2, TS2339 x2, TS2739 x1. Re-measured 17 ' diff --git a/scripts/check-type-source-resolution.mjs b/scripts/check-type-source-resolution.mjs index 4613787e68..0a9be2e8ea 100644 --- a/scripts/check-type-source-resolution.mjs +++ b/scripts/check-type-source-resolution.mjs @@ -312,7 +312,31 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = { '@objectstack/types', '@objectstack/verify', ], '@objectstack/driver-memory': ['@objectstack/core', '@objectstack/spec', '@objectstack/types'], - '@objectstack/driver-mongodb': ['@objectstack/core', '@objectstack/spec', '@objectstack/types'], + // #14917: `packages/drivers/driver-mongodb` had NO tsc program compiling any + // of its 30 test files (the build config's `exclude` named `**/*.test.ts`), + // and its new `tsconfig.test.json` (the #5286 sibling route) is the first one + // that does — the same shape `@objectstack/rest` took below. Exactly ONE dep + // arrives from that program: `@objectstack/objectql`, a devDependency no + // non-test file in `src/` imports (measured: 0 non-test importers, and the + // gate's own provenance annotation reads `via tsconfig.test.json`). + // + // ⚠️ This is a program-set widening and its numbers are stated, per this + // registry's own rule: before, at 061d62e50 with the sibling config removed + // and unnamed, `--list` reported 61 of 78 packages / 123 tsc programs / 309 + // pairs / 17 clean; after, 61 of 78 packages / 124 programs / 310 pairs / 17 + // clean. +1 program, +0 entries (this package was already listed), +1 pair — + // that one pair, in this one package, reached only through the onboarded + // program. + // + // Why the entry and not `paths` rules, which is what this gate's failure text + // asks for: this is the onboarding case the doc-block above rules on, where + // `paths` is measured to be the WRONG tool (PR #12570). Redirecting objectql + // to source here would put its `src` tree in this package's test program and + // bill objectql's own diagnostics to a driver package that cannot pay them + // down. The test layer measures 0 errors as it stands. + '@objectstack/driver-mongodb': [ + '@objectstack/core', '@objectstack/objectql', '@objectstack/spec', '@objectstack/types', + ], '@objectstack/driver-sql': [ '@objectstack/core', '@objectstack/formula', '@objectstack/observability', '@objectstack/spec', '@objectstack/types',