From 652524219948a563030f8382745033b051fb9115 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 01:49:27 +0000 Subject: [PATCH] refactor(metadata-protocol): getMetaTypes() declares a named return type, pinned equal to the inferred one The method's inferred return type printed 793 lines inline in index.d.ts. It now declares Promise, composed from spec's MetadataTypeRegistryEntryParsed, Action and FormView, and a compile-time Equal check in the body holds the name equal to what the body builds. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01JQwxbWaMuBU5CquKAT7ZmH --- .changeset/29510-meta-types-named-return.md | 10 +++ .../src/protocol-meta-types-listing.ts | 68 +++++++++++++++++++ packages/metadata-protocol/src/protocol.ts | 11 ++- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 .changeset/29510-meta-types-named-return.md create mode 100644 packages/metadata-protocol/src/protocol-meta-types-listing.ts diff --git a/.changeset/29510-meta-types-named-return.md b/.changeset/29510-meta-types-named-return.md new file mode 100644 index 00000000000..a7d401ec459 --- /dev/null +++ b/.changeset/29510-meta-types-named-return.md @@ -0,0 +1,10 @@ +--- +'@objectstack/metadata-protocol': patch +--- + +refactor(metadata-protocol): `getMetaTypes()` declares a named return type, held equal to the type it returned before + +Clause-②: no +Metadata-protocol: none + +No behaviour change and no type change. `ObjectStackProtocolImplementation.getMetaTypes()` now declares `Promise`, composed from `@objectstack/spec`'s `MetadataTypeRegistryEntryParsed`, `Action` and `FormView`, in place of an inferred type that the published `index.d.ts` printed inline over 793 lines. A compile-time equality check in the method holds the name equal to what the body builds, so the two cannot drift. The `.d.ts` text is shorter; the type a consumer reads is the same one, checked equal against the declaration file `main` built before this change. `MetaTypesListing` is not exported from the package. Nothing to migrate. diff --git a/packages/metadata-protocol/src/protocol-meta-types-listing.ts b/packages/metadata-protocol/src/protocol-meta-types-listing.ts new file mode 100644 index 00000000000..81483bc18bc --- /dev/null +++ b/packages/metadata-protocol/src/protocol-meta-types-listing.ts @@ -0,0 +1,68 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// [#29510] The NAMED return type of `ObjectStackProtocolImplementation.getMetaTypes()` +// (`GET /meta/types`), composed from types `@objectstack/spec` already exports. +// +// Why a name: inferred, that return type rolled up into ~790 lines of inline +// `.d.ts`, and every `protocol.ts` cut of #25467 had to read that raw block by +// hand, because a byte shift in `protocol.ts` can surface a class comment +// inside an inferred type. Named, the `.d.ts` prints these aliases instead. +// +// What holds it to the implementation: `getMetaTypes()` builds its answer with +// an inferred type, and a `const` typed `Equal` +// is initialised with `true` there: it only compiles when the inferred type is +// EQUAL (not merely assignable) to the one declared here. A member added, +// dropped or retyped on either side fails `tsc` at that line. The type is the +// one `main` emitted before the name existed; only its `.d.ts` spelling changed. +// +// Not exported from the package index: no new public name. + +import type { MetadataTypeRegistryEntryParsed } from '@objectstack/spec/kernel'; +import type { Action, FormView } from '@objectstack/spec/ui'; + +/** Flattens an intersection into one object type, so it compares EQUAL to an inferred object literal. */ +type Flatten = { [K in keyof T]: T[K] }; + +/** + * One `entries` row: the registry entry for the type, with the per-type + * fields the listing adds or overrides. A type with no registry entry gets a + * synthesised descriptor that is a subtype of this row. + */ +export type MetaTypesListingEntry = Flatten< + Omit & { + /** Singular spelling, as `PLURAL_TO_SINGULAR` resolves it; plugin types are not in the registry enum. */ + type: string; + /** Same value as `type`; the API client reads this field. */ + schemaId: string; + /** The registry value, raised by the `OS_METADATA_WRITABLE` env hatch. */ + allowEnvironmentOverlay: boolean; + /** `'env'` when only the env hatch opened the overlay. */ + overrideSource: 'registry' | 'env'; + /** JSON Schema of the type, for the generic admin form. */ + schema: Record; + /** Form layout of the type's editor. */ + form: FormView; + /** The minimal create seed, when the spec declares one. */ + createSeed?: {} | null; + /** + * Declarative plus plugin-registered type actions (`Action[]`); absent + * when there are none. The registry entry's own parsed `actions` stay + * in the type because the row spreads the entry before overriding. + */ + actions?: Action[] | NonNullable; + } +>; + +/** What `getMetaTypes()` answers. */ +export type MetaTypesListing = { + /** Every live type name, spelled as its source stores it. */ + types: string[]; + /** One row per live type, sorted by `domain`, then `type`. */ + entries: MetaTypesListingEntry[]; +}; + +/** + * Strict type equality: no `any` leak, no one-sided assignability. `true` + * only when `A` and `B` are the same type. + */ +export type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false; diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index acae72bf506..19132e6b03c 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -466,6 +466,8 @@ import { datasourceCredentialsRefSaveRefusal } from './protocol-datasource-crede import { withRefusalAudit } from './protocol-refusal-audit.js'; // [#28665] The null-master rows a `controlled_by_parent` detail stops serving, counted into the finding that makes its master required. import { withNullMasterRowCounts } from './protocol-destructive-null-master.js'; +// [#29510] `getMetaTypes()`'s named return type and the equality its pin reads. +import type { Equal, MetaTypesListing } from './protocol-meta-types-listing.js'; // [#25467 E0] The module preamble that stood here moved, verbatim, into the themed // `protocol-*.ts` modules below. What this file exported from it, it still exports; // the module order below is the order the rolled-up `.d.ts` keeps their declarations in. @@ -3136,7 +3138,8 @@ export class ObjectStackProtocolImplementation implements return listLiveMetadataTypes.call(host); } - async getMetaTypes() { + // [#29510] Named return type; the `exact` pin below holds it EQUAL to what this body infers. + async getMetaTypes(): Promise { const allTypes = await this.listLiveMetadataTypes(); // Phase 3a-1: enrich response with per-type registry metadata so admin @@ -3289,7 +3292,11 @@ export class ObjectStackProtocolImplementation implements return a.type.localeCompare(b.type); }); - return { types: allTypes, entries }; + const listing = { types: allTypes, entries }; + // [#29510] Compile-time pin: `false` (a type error) the moment the body and the name drift apart. + const exact: Equal = true; + void exact; + return listing; } /**