diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 4ac2213264..5b591ba4ab 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -57,6 +57,7 @@ "build": "tsup && node ../../scripts/check-dts-emitted.mjs", "dev": "tsc --watch", "clean": "rm -rf dist", + "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage" diff --git a/packages/metadata/src/loaders/database-loader.test.ts b/packages/metadata/src/loaders/database-loader.test.ts index 832042b655..e6263ea0eb 100644 --- a/packages/metadata/src/loaders/database-loader.test.ts +++ b/packages/metadata/src/loaders/database-loader.test.ts @@ -1,10 +1,10 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { DatabaseLoader, type DatabaseLoaderOptions } from './database-loader'; +import { DatabaseLoader } from './database-loader.js'; import type { IDataDriver } from '@objectstack/spec/contracts'; -import { MetadataManager } from '../metadata-manager'; -import { MemoryLoader } from './memory-loader'; +import { MetadataManager } from '../metadata-manager.js'; +import { MemoryLoader } from './memory-loader.js'; // Suppress logger output during tests. Stable object (not a fresh one per // `createLogger()` call) so the #5108 block can assert on what `list()` says diff --git a/packages/metadata/src/loaders/overlay-index-single-producer.test.ts b/packages/metadata/src/loaders/overlay-index-single-producer.test.ts index 8f6f2597cc..0efa059293 100644 --- a/packages/metadata/src/loaders/overlay-index-single-producer.test.ts +++ b/packages/metadata/src/loaders/overlay-index-single-producer.test.ts @@ -43,7 +43,7 @@ */ import { describe, it, expect } from 'vitest'; -import { DatabaseLoader } from './database-loader'; +import { DatabaseLoader } from './database-loader.js'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; const INDEX = 'idx_sys_metadata_overlay_active'; diff --git a/packages/metadata/src/metadata-history.test.ts b/packages/metadata/src/metadata-history.test.ts index 193ef648d8..404a3a499a 100644 --- a/packages/metadata/src/metadata-history.test.ts +++ b/packages/metadata/src/metadata-history.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, beforeEach } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { DatabaseLoader } from './loaders/database-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { DatabaseLoader } from './loaders/database-loader.js'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; describe('Metadata History', () => { @@ -147,7 +147,7 @@ describe('Metadata History', () => { const diffResult = await manager.diff('object', 'test_object', 1, 2); expect(diffResult.identical).toBe(false); - expect(diffResult.patch.length).toBeGreaterThan(0); + expect(diffResult.patch!.length).toBeGreaterThan(0); expect(diffResult.summary).toContain('modified'); } }); diff --git a/packages/metadata/src/metadata-manager-cluster.test.ts b/packages/metadata/src/metadata-manager-cluster.test.ts index d4812e5f55..1b702e6c0c 100644 --- a/packages/metadata/src/metadata-manager-cluster.test.ts +++ b/packages/metadata/src/metadata-manager-cluster.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import type { MetadataLoader } from './loaders/loader-interface.js'; import type { MetadataLoaderContract, MetadataLoadResult, MetadataSaveResult, MetadataStats } from '@objectstack/spec/system'; import type { IPubSub, PubSubMessage } from '@objectstack/spec/contracts'; @@ -172,8 +172,8 @@ describe('MetadataManager — cluster pub/sub bridge', () => { const a: unknown[] = []; const b: unknown[] = []; - mgrA.subscribe('object', (e) => a.push(e)); - mgrB.subscribe('object', (e) => b.push(e)); + mgrA.subscribe('object', (e) => { a.push(e); }); + mgrB.subscribe('object', (e) => { b.push(e); }); // Simulate manager A emitting a watch event by going through the // public publish surface directly (we don't have a repository @@ -197,7 +197,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { mgr.attachClusterPubSub(bus, 'node-B'); const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); mgr.detachClusterPubSub(); mgr.detachClusterPubSub(); // idempotent @@ -220,7 +220,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { const off2 = mgr.attachClusterPubSub(bus, 'node-B'); // should not double-subscribe const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); await bus.publish('metadata.changed', { originNode: 'node-A', @@ -239,7 +239,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { mgr.attachClusterPubSub(bus, 'node-B'); const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); await bus.publish('metadata.changed', { originNode: 'node-A' }); // missing type/event await bus.publish('metadata.changed', null); diff --git a/packages/metadata/src/metadata-realtime-events.test.ts b/packages/metadata/src/metadata-realtime-events.test.ts index c61e4cc9ca..17380e100e 100644 --- a/packages/metadata/src/metadata-realtime-events.test.ts +++ b/packages/metadata/src/metadata-realtime-events.test.ts @@ -24,8 +24,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MetadataEventSchema } from '@objectstack/spec/api'; import type { IRealtimeService, RealtimeEventPayload } from '@objectstack/spec/contracts'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; vi.mock('@objectstack/core', async (orig) => ({ diff --git a/packages/metadata/src/metadata-service.test.ts b/packages/metadata/src/metadata-service.test.ts index d0f441cf56..e1116c6ac4 100644 --- a/packages/metadata/src/metadata-service.test.ts +++ b/packages/metadata/src/metadata-service.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; // Suppress logger output during tests diff --git a/packages/metadata/src/metadata.test.ts b/packages/metadata/src/metadata.test.ts index 30cc3e85ed..14d48f664a 100644 --- a/packages/metadata/src/metadata.test.ts +++ b/packages/metadata/src/metadata.test.ts @@ -4,9 +4,9 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; -import type { MetadataLoader } from './loaders/loader-interface'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; +import type { MetadataLoader } from './loaders/loader-interface.js'; // Suppress logger output during tests vi.mock('@objectstack/core', async (orig) => ({ @@ -953,6 +953,11 @@ function createMockLoaderMany(name: string, items: any[], shouldFail = false): M function createMockPluginContext() { return { registerService: vi.fn(), + // Required members of `PluginContext` (@objectstack/core) that this suite + // never drives — inert stubs so the literal satisfies the parameter type + // rather than 30 call sites each asserting past it. + registerServiceFactory: vi.fn(), + getServiceScoped: vi.fn(), replaceService: vi.fn(), getService: vi.fn().mockReturnValue(null), getServices: vi.fn().mockReturnValue(new Map()), diff --git a/packages/metadata/src/plugin-hmr-reload.test.ts b/packages/metadata/src/plugin-hmr-reload.test.ts index 0927d96916..be07f88bec 100644 --- a/packages/metadata/src/plugin-hmr-reload.test.ts +++ b/packages/metadata/src/plugin-hmr-reload.test.ts @@ -12,8 +12,8 @@ import { describe, it, expect, vi } from 'vitest'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { MetadataPlugin } from './plugin'; -import type { NodeMetadataManager } from './node-metadata-manager'; +import { MetadataPlugin } from './plugin.js'; +import type { NodeMetadataManager } from './node-metadata-manager.js'; function fakeCtx() { return { @@ -111,7 +111,7 @@ describe('MetadataPlugin._reloadAndAnnounce — fires metadata:reloaded after re const file = writeArtifact('sweep3'); const perItemEvents: unknown[] = []; - mgr.subscribe('flow', (evt) => perItemEvents.push(evt)); + mgr.subscribe('flow', (evt) => { perItemEvents.push(evt); }); await (plugin as any)._reloadAndAnnounce(ctx, { path: file, fetchTimeoutMs: undefined }, [file]); diff --git a/packages/metadata/src/plugin.test.ts b/packages/metadata/src/plugin.test.ts index c0268b8a6d..8b795b4f83 100644 --- a/packages/metadata/src/plugin.test.ts +++ b/packages/metadata/src/plugin.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi } from 'vitest'; -import { MetadataPlugin } from './plugin'; -import { NodeMetadataManager } from './node-metadata-manager'; +import { MetadataPlugin } from './plugin.js'; +import { NodeMetadataManager } from './node-metadata-manager.js'; vi.mock('@objectstack/core', async (orig) => { const real = (await orig()) as any; diff --git a/packages/metadata/src/register-notifies-watchers.test.ts b/packages/metadata/src/register-notifies-watchers.test.ts index b549b979a9..1a07faff82 100644 --- a/packages/metadata/src/register-notifies-watchers.test.ts +++ b/packages/metadata/src/register-notifies-watchers.test.ts @@ -50,15 +50,16 @@ import type { MetadataStats, MetadataWatchEvent, } from '@objectstack/spec/system'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; -// `.js` deliberately, unlike the three extensionless imports above it: under -// `moduleResolution: nodenext` an extensionless relative import does not +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; +// `.js` on this relative import, as on every relative import in this file: +// under `moduleResolution: nodenext` an extensionless relative import does not // resolve, and every symbol it names silently becomes `any` (AGENTS.md, the -// TS7006 cascade). Spelling this one correctly is what makes `implements +// TS7006 cascade). Spelling it correctly is what makes `implements // MetadataLoader` on the fixture below an actual check rather than decoration. -// The three above are this package's pre-existing type-check debt (#4311) and -// are left for whoever pays that ledger down. +// That is no longer a convention this file keeps on its own: the package +// declares a `typecheck` script, so `turbo run typecheck` compiles this file +// and an extensionless relative import here is a red gate, not a silent `any`. import type { MetadataLoader } from './loaders/loader-interface.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; @@ -205,7 +206,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('register()', () => { it('announces a first registration as "added"', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account', label: 'Account' }); @@ -222,7 +223,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account', label: 'V1' }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account', label: 'V2' }); @@ -290,7 +291,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('is silent when the caller opts out with { notify: false }', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account' }, { notify: false }); @@ -302,8 +303,8 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('only notifies watchers of the written type', async () => { const objects: any[] = []; const views: any[] = []; - manager.subscribe('object', (evt) => objects.push(evt)); - manager.subscribe('view', (evt) => views.push(evt)); + manager.subscribe('object', (evt) => { objects.push(evt); }); + manager.subscribe('view', (evt) => { views.push(evt); }); await manager.register('object', 'account', { name: 'account' }); @@ -320,7 +321,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => readOnly.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY); const seen: any[] = []; - readOnly.subscribe('object', (evt) => seen.push(evt)); + readOnly.subscribe('object', (evt) => { seen.push(evt); }); await readOnly.register('object', 'account', { name: 'account' }); @@ -340,7 +341,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('stops notifying after unsubscribe', async () => { const seen: any[] = []; - const off = manager.subscribe('object', (evt) => seen.push(evt)); + const off = manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'a', { name: 'a' }); off(); @@ -356,7 +357,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account' }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregister('object', 'account'); @@ -372,7 +373,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account' }, { notify: false }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregister('object', 'account', { notify: false }); @@ -384,7 +385,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('bulk forms', () => { it('bulkRegister announces one event per item by default', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkRegister([ { type: 'object', name: 'a', data: { name: 'a' } }, @@ -396,7 +397,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('bulkRegister forwards { notify: false } to every item', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkRegister( [ @@ -420,7 +421,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => ); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkUnregister([ { type: 'object', name: 'a' }, @@ -442,7 +443,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'other', { name: 'other', packageId: 'com.other' }, { notify: false }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregisterPackage('com.acme.crm'); @@ -456,7 +457,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('registerInMemory()', () => { it('stays silent by design (GitOps-owned artefacts, documented on the method)', async () => { const seen: any[] = []; - manager.subscribe('datasource', (evt) => seen.push(evt)); + manager.subscribe('datasource', (evt) => { seen.push(evt); }); manager.registerInMemory('datasource', 'crm_db', { name: 'crm_db', origin: 'code' }); diff --git a/packages/metadata/src/serializers/serializers.test.ts b/packages/metadata/src/serializers/serializers.test.ts index 9533a416a1..3943092f54 100644 --- a/packages/metadata/src/serializers/serializers.test.ts +++ b/packages/metadata/src/serializers/serializers.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; -import { JSONSerializer } from '../serializers/json-serializer'; -import { YAMLSerializer } from '../serializers/yaml-serializer'; -import { TypeScriptSerializer } from '../serializers/typescript-serializer'; +import { JSONSerializer } from '../serializers/json-serializer.js'; +import { YAMLSerializer } from '../serializers/yaml-serializer.js'; +import { TypeScriptSerializer } from '../serializers/typescript-serializer.js'; describe('Serializers', () => { describe('JSONSerializer', () => { diff --git a/packages/metadata/src/utils/lru-cache.test.ts b/packages/metadata/src/utils/lru-cache.test.ts index cef0663218..53c521a78e 100644 --- a/packages/metadata/src/utils/lru-cache.test.ts +++ b/packages/metadata/src/utils/lru-cache.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { LRUCache } from './lru-cache'; +import { LRUCache } from './lru-cache.js'; describe('LRUCache', () => { describe('basic set / get', () => { diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 12fdaecc18..56433caaab 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -642,6 +642,27 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts'; // at all, and simultaneously EXPOSED 12 real defects in `service-settings` // that the unresolved imports had been masking. A config-tier count is an // upper bound on nothing: fix the config first, then read the residue. +// +// `@objectstack/metadata` GRADUATED from this ledger (#14342; entry: 89 raw, +// repaired to 0, the route #13978 took for `metadata-protocol` and ruled by +// precedent for this one). It is worth a line because it is the sharpest +// measurement of the paragraph directly above, and because the composition +// this entry recorded was wrong in a way no re-measure could show. Its opening +// itemisation read, verbatim, 'code-tier 30 (TS2345 x30); config-tier 25 +// (TS2835 x25); noise 34 (TS7006 x33, TS6133)'. Repairing the 25 TS2835 alone +// took the pile to 52 -- ALL 33 TS7006 dissolved (they were the +// cascade, not noise the package owed), while 20 TS2322 and 1 TS18048 appeared +// that the 89 had never contained. So the true code tier was 51, not 30: 30 +// TS2345 (the one shared mock `PluginContext` literal, closed by adding the two +// members it lacked to the single factory), 20 TS2322 (`(evt) => arr.push(evt)` +// in a watcher slot typed `void | Promise` -- a concise arrow body whose +// `number` only becomes visible once the callback parameter has a real type, +// and which the void-return assignability rule does NOT forgive because the +// target is a union), and 1 TS18048. This is the `rest` lesson (TS18048 x13 +// exposed the same way) measured a second time, on a package whose note had +// already itemised its own tiers with confidence: a tier split read off an +// unrepaired config is a guess about what is UNDER it, and the only honest way +// to size the code tier is to fix the config and look. const DEBT = { '@objectstack/cloud-connection': { errors: 13, @@ -658,21 +679,6 @@ const DEBT = { errors: 3, note: 'all code-tier (TS2769/TS18046).', }, - '@objectstack/metadata': { - errors: 89, - note: 'code-tier 30 (TS2345 x30); config-tier 25 (TS2835 x25); noise 34 (TS7006 x33, TS6133). ' - + 'Re-measured 89 at 4b84834a32, DOWN from 92 at 5ab08428 -- itself up from 87, so this entry has ' - + 'now drifted both ways. Against the composition recorded here at 92 the delta is attributable ' - + 'tier by tier: code-tier lost the 4 TS2322 (-4), config-tier gained one TS2835 (+1), noise did ' - + 'not move. TS2353 then TS2322 have each passed through the code tier and left; TS2345 x30 is its ' - + 'only lasting resident. Read the 89 as three mechanical repairs, not 89 problems: all 30 TS2345 ' - + 'are one defect thirty times over, in metadata.test.ts between 608 and 945, every one the same ' - + 'mock PluginContext literal missing registerServiceFactory and getServiceScoped, so one shared ' - + 'fixture closes the code tier outright; the 25 TS2835 are the widest spread (12 files) and are ' - + 'one codemod, a relative import wanting an explicit .js extension under node16 resolution. ' - + 'metadata.test.ts (34) and register-notifies-watchers.test.ts (16) do still hold 50 of the 89, ' - + 'but that is over HALF -- the "two thirds" claimed here was true at neither 92 nor 89.', - }, '@objectstack/observability': { errors: 11, note: 'all code-tier (TS2554 wrong arity x10, TS2552).',