From 6e85f67ae4b471d77c99b96055b5d932510d5a34 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 08:20:28 +0000 Subject: [PATCH 1/2] wip: wire service-analytics typecheck, clear the 10 standing errors --- .../services/service-analytics/package.json | 1 + .../src/__tests__/analytics-service.test.ts | 1 - .../measure-source-field-gate.test.ts | 33 +++++++++++++++---- .../objectql-timedimension-projection.test.ts | 2 +- scripts/check-type-check-coverage.mjs | 10 ------ 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/services/service-analytics/package.json b/packages/services/service-analytics/package.json index 5760448f3c..b6bda74cd4 100644 --- a/packages/services/service-analytics/package.json +++ b/packages/services/service-analytics/package.json @@ -15,6 +15,7 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts && node ../../../scripts/check-dts-emitted.mjs", + "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { diff --git a/packages/services/service-analytics/src/__tests__/analytics-service.test.ts b/packages/services/service-analytics/src/__tests__/analytics-service.test.ts index cf7b0dd46a..b1e1af241b 100644 --- a/packages/services/service-analytics/src/__tests__/analytics-service.test.ts +++ b/packages/services/service-analytics/src/__tests__/analytics-service.test.ts @@ -7,7 +7,6 @@ import { AnalyticsService } from '../analytics-service.js'; import { CubeRegistry } from '../cube-registry.js'; import { NativeSQLStrategy } from '../strategies/native-sql-strategy.js'; import { ObjectQLStrategy } from '../strategies/objectql-strategy.js'; -import type { AnalyticsDriverCapabilities } from '../strategies/types.js'; // ───────────────────────────────────────────────────────────────── // Test fixtures diff --git a/packages/services/service-analytics/src/__tests__/measure-source-field-gate.test.ts b/packages/services/service-analytics/src/__tests__/measure-source-field-gate.test.ts index 6866d9cc4b..6cbc758624 100644 --- a/packages/services/service-analytics/src/__tests__/measure-source-field-gate.test.ts +++ b/packages/services/service-analytics/src/__tests__/measure-source-field-gate.test.ts @@ -36,6 +36,27 @@ import { describe, it, expect, vi } from 'vitest'; import type { Cube } from '@objectstack/spec/data'; import { AnalyticsService } from '../analytics-service.js'; +/** + * The refusal a query produced, typed as the Error it actually is. + * + * `promise.catch(fn)` does NOT drop the resolved branch from the type, so + * `service.query(...).catch((e) => e as Error)` is `AnalyticsResult | Error` + * and every `err.message` / `err.field` read below was a TS2339 against + * `AnalyticsResult` -- 7 of the 10 errors this package's unwired `typecheck` + * script hid. Narrowing once here rather than casting at each read also gives + * the resolved branch an honest failure: a query that is NOT refused now says + * so by name, instead of surfacing later as `expect(undefined).toMatch(...)`. + */ +type Refusal = Error & { code?: string; field?: string; member?: string; param?: string }; + +const refusalOf = (query: Promise): Promise => + query.then( + () => { + throw new Error('expected the query to be refused, but it resolved'); + }, + (e) => e as Refusal, + ); + const silentLogger = { info: vi.fn(), debug: vi.fn(), @@ -107,9 +128,9 @@ describe('#4437 — measure source-field gate', () => { // alternative guaranteed not to work. const { service } = makeService(); - const err = await service - .query({ cube: 'showcase_invoice', measures: ['ghost_sum'] } as any) - .catch((e) => e as Error); + const err = await refusalOf( + service.query({ cube: 'showcase_invoice', measures: ['ghost_sum'] } as any), + ); expect(err.message).toMatch(/Valid measures: count\./); expect(err.message).not.toMatch(/Valid measures:[^.]*ghost_sum/); @@ -268,9 +289,9 @@ describe('#4437 — measure source-field gate', () => { }; const { service } = makeService({ cubes: [joined] }); - const err = await service - .query({ cube: 'joined_cube', measures: ['remote_sum'] } as any) - .catch((e) => e as Error & { code?: string; field?: string; member?: string; param?: string }); + const err = await refusalOf( + service.query({ cube: 'joined_cube', measures: ['remote_sum'] } as any), + ); expect(err).toBeInstanceOf(Error); // It got as far as the strategy — i.e. past this gate — and was declined diff --git a/packages/services/service-analytics/src/__tests__/objectql-timedimension-projection.test.ts b/packages/services/service-analytics/src/__tests__/objectql-timedimension-projection.test.ts index 477c0bea0c..3cea3f15f6 100644 --- a/packages/services/service-analytics/src/__tests__/objectql-timedimension-projection.test.ts +++ b/packages/services/service-analytics/src/__tests__/objectql-timedimension-projection.test.ts @@ -41,7 +41,7 @@ const dataset = DatasetSchema.parse({ measures: [{ name: 'count', aggregate: 'count', field: 'id' }], }); -const TABLE = [ +const TABLE: Row[] = [ { id: 1, due_date: '2026-01-10', priority: 'high' }, { id: 2, due_date: '2026-01-20', priority: 'low' }, { id: 3, due_date: '2026-02-05', priority: 'high' }, diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 7b47f85065..5a6816359e 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -588,16 +588,6 @@ const DEBT = { errors: 11, note: 'all code-tier (TS2554 wrong arity x10, TS2552).', }, - '@objectstack/service-analytics': { - errors: 10, - note: 'code-tier 9 (TS2339 x7, TS7053 x2) + 1 noise (TS6133). Re-measured 10 at e8db1a230, up from 7 ' - + 'at 5ab08428 and 3 before that. All 7 TS2339 sit in __tests__/measure-source-field-gate.test.ts, ' - + 'the same file that carried 4 of them when this entry was last written; the +3 arrived with #5716 ' - + '/ PR #5963 rewriting that gate\'s refusals -- no new file, no new error class. This entry is the ' - + 'standing specimen for why the ERROR-COUNT layer needed a ratchet of its own: the PACKAGE layer ' - + 'of this gate has been closed to new debt the whole time, and the count still walked 3 -> 7 -> 10 ' - + 'unremarked (#5278).', - }, '@objectstack/service-automation': { errors: 3, note: 'code-tier 3 (TS2341 x3), all in src/nested-region-parity.test.ts at 95/151/180, where the ' From 6cd14ca62de5a6480e4042f2ba650850e3d2d4d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 08:42:49 +0000 Subject: [PATCH 2/2] changeset: service-analytics typecheck gate --- .../service-analytics-typecheck-gate.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .changeset/service-analytics-typecheck-gate.md diff --git a/.changeset/service-analytics-typecheck-gate.md b/.changeset/service-analytics-typecheck-gate.md new file mode 100644 index 0000000000..650574f848 --- /dev/null +++ b/.changeset/service-analytics-typecheck-gate.md @@ -0,0 +1,45 @@ +--- +"@objectstack/service-analytics": patch +--- + +fix(service-analytics): wire the `typecheck` script so turbo stops silently no-opping the gate, and clear the 10 type errors it was hiding (#12939) + +`packages/services/service-analytics/package.json` declared only `build` and +`test`. Root `typecheck` is `turbo run typecheck`, which **no-ops a package +that has no such script and reports success** — so no tsc read this package's +`src/` from the typecheck lane at all. `build` is tsup (esbuild; the DTS pass +processes declarations only) and `test` is vitest (esbuild transform), and +neither type-checks. The package was reached only by the `check:type-check-debt` +ratchet, which asserts the error count does not *grow* — never that it is zero. + +Adding the one-line script (mirroring its sibling `service-settings`, repaired +the same way in #7925) makes the task real. The tests are already inside the +program — the package `tsconfig.json` includes `src` and the tests live in +`src/__tests__/**` — so `tsc --noEmit --listFiles` lists **83 of the 83** +`*.test.ts` files on disk. The new gate reads the tests, not just the source. + +All 10 errors were stale tests, not source defects; no non-test source file +changed. Nothing was silenced: no `any` added, no `@ts-expect-error`, no +`@ts-nocheck`, `strict` untouched, and the tsconfig `include`/`exclude` are +byte-identical — excluding the tests would have converted a missing gate into +a lying one. + +- `__tests__/measure-source-field-gate.test.ts` (7 x TS2339). `promise.catch(fn)` + does not drop the resolved branch from the type, so + `service.query(...).catch((e) => e as Error)` was `AnalyticsResult | Error` + and every `err.message` / `err.field` / `err.member` / `err.param` read was a + property access on `AnalyticsResult`. A local `refusalOf()` helper narrows it + once via `then`; as a bonus the resolved branch now fails by + name instead of surfacing later as `expect(undefined).toMatch(...)`. +- `__tests__/objectql-timedimension-projection.test.ts` (2 x TS7053). The + `TABLE` fixture was inferred as `{ id: number; due_date: string; priority: + string }[]` and the aggregate stand-in indexes it by a computed `string` key. + Annotated as the `Row` (`Record`) the file already declares. +- `__tests__/analytics-service.test.ts` (1 x TS6133). An unused + `AnalyticsDriverCapabilities` type import. The capability literals in this + file are inline `ctx` objects checked contextually at each `canHandle` call + site, so the import added no coverage and is removed. + +`service-analytics` graduates out of the `check:type-check-coverage` DEBT +ledger: 65/78 -> 66/78 workspace packages type-checked, 382 -> 372 frozen raw +errors, 13 -> 12 ledger entries.