Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/metadata/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/loaders/database-loader.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/metadata-history.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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');
}
});
Expand Down
14 changes: 7 additions & 7 deletions packages/metadata/src/metadata-manager-cluster.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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',
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/metadata/src/metadata-realtime-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down
4 changes: 2 additions & 2 deletions packages/metadata/src/metadata-service.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 8 additions & 3 deletions packages/metadata/src/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down Expand Up @@ -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()),
Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/plugin-hmr-reload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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]);

Expand Down
4 changes: 2 additions & 2 deletions packages/metadata/src/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
43 changes: 22 additions & 21 deletions packages/metadata/src/register-notifies-watchers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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' });

Expand All @@ -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' });

Expand Down Expand Up @@ -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 });

Expand All @@ -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' });

Expand All @@ -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' });

Expand All @@ -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();
Expand All @@ -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');

Expand All @@ -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 });

Expand All @@ -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' } },
Expand All @@ -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(
[
Expand All @@ -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' },
Expand All @@ -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');

Expand All @@ -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' });

Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/serializers/serializers.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/metadata/src/utils/lru-cache.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
36 changes: 21 additions & 15 deletions scripts/check-type-check-coverage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>` -- 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,
Expand All @@ -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).',
Expand Down
Loading