Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* [#25467 cut 28] The code-datasource predicates, from
* `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: the
* two answers to "is this datasource name defined in code?" (#21899, #21944).
*
* - `isDeclaredCodeDatasource` — does an installed package declare it, or
* does the host register it from code? The `/meta` door's artifact-backed
* verdict and the stored-row lock read it.
* - `isHostCodeDatasource` — the host's code-datasource set alone, the
* first half of that answer and exactly what the datasource-admin boot
* restore reads.
*
* A MOVE, not a redesign. Each function below is the body of the
* `ObjectStackProtocolImplementation` method of the same name as it stood,
* re-indented one level and otherwise unedited.
*
* ## How the moved bodies reach the class
*
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
* The class keeps each method — the published `dist/*.d.ts` lists its name,
* docblock and declared signature — and its body is one literal of exactly
* the {@link CodeDatasourcePredicatesHost} members it reads, plus a `.call`.
* The `engine` and `getServicesRegistry` fields are handed over as getters,
* so each read finds them as they stand at call time;
* `isDeclaredCodeDatasource`'s call to `isHostCodeDatasource` is handed over
* as a closure, so it still reaches the class method. Every caller keeps
* calling the class methods.
*
* What the bodies read from elsewhere, they import from where `protocol.ts`
* imports it. Nothing re-exports this module, so the published surface does
* not move.
*/

import { PLURAL_TO_SINGULAR } from '@objectstack/spec/shared';
import type { ObjectStackProtocolImplementation } from './protocol.js';

/**
* The `ObjectStackProtocolImplementation` members the functions below read —
* every one of them, and no other; each function's `this` is the `Pick` of
* the members it reads. Each is typed AS the class member (an indexed access
* on the class), so a change to a member's signature on the class is a change
* here too, and naming a member the class does not have is a compile error.
*/
export interface CodeDatasourcePredicatesHost {
readonly engine: ObjectStackProtocolImplementation['engine'];
readonly getServicesRegistry: ObjectStackProtocolImplementation['getServicesRegistry'];
readonly isHostCodeDatasource: ObjectStackProtocolImplementation['isHostCodeDatasource'];
}

/** The body of `ObjectStackProtocolImplementation#isDeclaredCodeDatasource`; the method's docblock there is its contract. */
export function isDeclaredCodeDatasource(
this: Pick<CodeDatasourcePredicatesHost, 'engine' | 'isHostCodeDatasource'>,
type: string,
name: string,
): boolean {
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'datasource') return false;
if (this.isHostCodeDatasource(type, name)) return true;
const registry = (this.engine as any)?.registry;
if (typeof registry?.getAllPackages !== 'function') return false;
for (const record of registry.getAllPackages() as unknown[]) {
const declared = (record as { manifest?: { datasources?: unknown } } | null | undefined)
?.manifest?.datasources;
if (Array.isArray(declared) && declared.some((ds) => (ds as { name?: unknown } | null)?.name === name)) {
return true;
}
}
return false;
}

/** The body of `ObjectStackProtocolImplementation#isHostCodeDatasource`; the method's docblock there is its contract. */
export function isHostCodeDatasource(
this: Pick<CodeDatasourcePredicatesHost, 'getServicesRegistry'>,
type: string,
name: string,
): boolean {
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'datasource') return false;
const hostCode = this.getServicesRegistry?.().get('code-datasource-names') as { has?: unknown } | undefined;
return typeof hostCode?.has === 'function' && (hostCode as { has(n: string): boolean }).has(name);
}
30 changes: 15 additions & 15 deletions packages/metadata-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ import { emitMetaItemPublished, notifyMutationListenersLocal, publishMetadataMut
import { hierarchyResolverMounted, orgWallEnforced, rlsMembershipResolverKeys, tenancyPostureInForce } from './protocol-authoring-posture-probes.js';
// [#25467 cut 27] The automation service's flow reads: the stored-flow canonicalizer and the engine's registrations.
import { flowRegistrationsOf, resolveFlowCanonicalizer, resolveFlowRegistrationReader } from './protocol-automation-flow-reads.js';
// [#25467 cut 28] The code-datasource predicates: declared by an installed package, or registered by the host.
import { isDeclaredCodeDatasource, isHostCodeDatasource } from './protocol-code-datasource-predicates.js';

/**
* Canonical Zod schema per metadata type lives in
Expand Down Expand Up @@ -15673,18 +15675,13 @@ export class ObjectStackProtocolImplementation implements
* datasource stays creatable, editable and removable through this door.
*/
private isDeclaredCodeDatasource(type: string, name: string): boolean {
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'datasource') return false;
if (this.isHostCodeDatasource(type, name)) return true;
const registry = (this.engine as any)?.registry;
if (typeof registry?.getAllPackages !== 'function') return false;
for (const record of registry.getAllPackages() as unknown[]) {
const declared = (record as { manifest?: { datasources?: unknown } } | null | undefined)
?.manifest?.datasources;
if (Array.isArray(declared) && declared.some((ds) => (ds as { name?: unknown } | null)?.name === name)) {
return true;
}
}
return false;
// [#25467 cut 28] The body moved to `protocol-code-datasource-predicates.ts` (see `CodeDatasourcePredicatesHost`).
const readEngine = () => this.engine;
const host: ThisParameterType<typeof isDeclaredCodeDatasource> = {
get engine() { return readEngine(); },
isHostCodeDatasource: (...a) => this.isHostCodeDatasource(...a),
};
return isDeclaredCodeDatasource.call(host, type, name);
}

/**
Expand All @@ -15695,9 +15692,12 @@ export class ObjectStackProtocolImplementation implements
* those rows to that warning).
*/
private isHostCodeDatasource(type: string, name: string): boolean {
if ((PLURAL_TO_SINGULAR[type] ?? type) !== 'datasource') return false;
const hostCode = this.getServicesRegistry?.().get('code-datasource-names') as { has?: unknown } | undefined;
return typeof hostCode?.has === 'function' && (hostCode as { has(n: string): boolean }).has(name);
// [#25467 cut 28] The body moved to `protocol-code-datasource-predicates.ts` (see `CodeDatasourcePredicatesHost`).
const readGetServicesRegistry = () => this.getServicesRegistry;
const host: ThisParameterType<typeof isHostCodeDatasource> = {
get getServicesRegistry() { return readGetServicesRegistry(); },
};
return isHostCodeDatasource.call(host, type, name);
}

/**
Expand Down
Loading