diff --git a/packages/metadata-protocol/src/protocol-meta-item-face.ts b/packages/metadata-protocol/src/protocol-meta-item-face.ts new file mode 100644 index 00000000000..4598ce1a91d --- /dev/null +++ b/packages/metadata-protocol/src/protocol-meta-item-face.ts @@ -0,0 +1,70 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#25467 cut 31] The by-name item read's disabled-package face, from + * `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: + * `readMetaItemFace`, the one body behind `getMetaItem` and + * `getMetaItemForExecution`. It refuses an object of a disabled package, + * withholds any other item of one, and otherwise answers what `readMetaItem` + * answers. + * + * A MOVE, not a redesign. The 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 body reaches 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 the method — the published `dist/*.d.ts` lists its name, + * docblock and declared signature — and its body is one literal of exactly + * the {@link MetaItemFaceHost} members it reads, plus a `.call`. The `engine` + * field is handed over as a getter, so the read finds it as it stands at call + * time; the call to `readMetaItem` is handed over as a closure, so it still + * reaches the class method. Every caller keeps calling the class method. + * + * The disabled-package helpers it reads are `protocol.ts`'s own, and are + * imported from there. Nothing re-exports this module, so the published + * surface does not move. + */ + +import type { ObjectStackProtocolImplementation } from './protocol.js'; +import { + isItemOfDisabledPackage, + isObjectOfDisabledPackage, + noPendingDraftError, + objectPackageDisabledError, +} from './protocol.js'; + +/** + * The `ObjectStackProtocolImplementation` members the function below reads — + * every one of them, and no other; the function's `this` is the `Pick` of + * them. 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. + */ +export interface MetaItemFaceHost { + readonly engine: ObjectStackProtocolImplementation['engine']; + readonly readMetaItem: ObjectStackProtocolImplementation['readMetaItem']; +} + +/** The body of `ObjectStackProtocolImplementation#readMetaItemFace`; the method's docblock there is its contract. */ +export async function readMetaItemFace( + this: Pick, + request: { + type: string, name: string, packageId?: string, state?: 'active' | 'draft', previewDrafts?: boolean, + member?: string, + }, + face: 'served' | 'execution', +) { + if (request.type === 'object' && isObjectOfDisabledPackage(this.engine?.registry, request.name)) { + throw objectPackageDisabledError(request.name); + } + const answer = await this.readMetaItem(request, { face }); + if (request.type === 'object' || request.type === 'package' + || !isItemOfDisabledPackage(this.engine?.registry, answer?.item)) { + return answer; + } + if (request.state === 'draft') throw noPendingDraftError(request.type, request.name); + const { item: _withheld, registration: _servedOnly, ...envelope } = answer as typeof answer & { registration?: unknown }; + return { ...envelope, item: undefined }; +} diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 33083b7e759..7270ca0864c 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -425,6 +425,8 @@ import { isDeclaredCodeDatasource, isHostCodeDatasource } from './protocol-code- import { legacyOrganizationRowsOf, withoutLegacyOrganizationRows } from './protocol-legacy-organization-rows.js'; // [#25467 cut 30] The object-extender fold onto a MetadataService object body. import { foldObjectExtendersFromRegistry } from './protocol-object-extender-fold.js'; +// [#25467 cut 31] The disabled-package face of the by-name item read. +import { readMetaItemFace } from './protocol-meta-item-face.js'; /** * Canonical Zod schema per metadata type lives in @@ -677,7 +679,7 @@ export function isItemOfDisabledPackage(registry: any, item: unknown): boolean { * finds no draft row and the read of a disabled package's draft, so the two * answers are byte-identical. */ -function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } { +export function noPendingDraftError(type: string, name: string): Error & { code: 'NO_DRAFT'; status: 404 } { const err = new Error(`No pending draft exists for ${type}/${name}.`) as Error & { code: 'NO_DRAFT'; status: 404 }; err.code = 'NO_DRAFT'; err.status = 404; @@ -10703,17 +10705,13 @@ export class ObjectStackProtocolImplementation implements type: string, name: string, packageId?: string, state?: 'active' | 'draft', previewDrafts?: boolean, member?: string, }, face: 'served' | 'execution') { - if (request.type === 'object' && isObjectOfDisabledPackage(this.engine?.registry, request.name)) { - throw objectPackageDisabledError(request.name); - } - const answer = await this.readMetaItem(request, { face }); - if (request.type === 'object' || request.type === 'package' - || !isItemOfDisabledPackage(this.engine?.registry, answer?.item)) { - return answer; - } - if (request.state === 'draft') throw noPendingDraftError(request.type, request.name); - const { item: _withheld, registration: _servedOnly, ...envelope } = answer as typeof answer & { registration?: unknown }; - return { ...envelope, item: undefined }; + // [#25467 cut 31] The body moved to `protocol-meta-item-face.ts` (see `MetaItemFaceHost`). + const readEngine = () => this.engine; + const host: ThisParameterType = { + get engine() { return readEngine(); }, + readMetaItem: (...a) => this.readMetaItem(...a), + }; + return readMetaItemFace.call(host, request, face); } /**