|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * [#25467 cut 11] The registry materialize/strip pair of |
| 5 | + * `ObjectStackProtocolImplementation` — the read exits' registry-side |
| 6 | + * convergence of a served object document (`materializeFromRegistry`), the |
| 7 | + * whole of what a `/meta` read exit owes an object document |
| 8 | + * (`governServedObject`), the write path's inverse of the registry seam |
| 9 | + * (`stripMaterializedFromRegistry`) and the whole of what the write path owes |
| 10 | + * the read (`stripServedObjectColumns`) — moved out of `protocol.ts` whole. |
| 11 | + * |
| 12 | + * A MOVE, not a redesign. Each function below is the body of the |
| 13 | + * `ObjectStackProtocolImplementation` method of the same name as it stood, |
| 14 | + * re-indented one level and otherwise unedited: the same reads, in the same |
| 15 | + * order, with the same `try`/`catch` fallbacks for partial registry doubles. |
| 16 | + * |
| 17 | + * ## How the moved bodies reach the class |
| 18 | + * |
| 19 | + * The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts` |
| 20 | + * header carries the full reasoning), as `protocol-batch-data.ts` applies it. |
| 21 | + * The class keeps every method — the published `dist/*.d.ts` lists each one's |
| 22 | + * name, docblock and declared signature — and each body is one literal of |
| 23 | + * exactly the {@link RegistryMaterializeHost} members that method reads, plus |
| 24 | + * a `.call`. A method is handed over as a closure that calls it on the |
| 25 | + * protocol at call time, so a test double or spy set on the instance is still |
| 26 | + * the one the body reaches, and the moved bodies that call one another do so |
| 27 | + * through the protocol, exactly as before. Every other reader of these four |
| 28 | + * methods keeps calling the class method. |
| 29 | + * |
| 30 | + * The field the bodies read, `engine`, goes as a GETTER, so each read happens |
| 31 | + * when the body makes it: tests replace it on a constructed protocol. |
| 32 | + * |
| 33 | + * The module-level declarations of `protocol.ts` the bodies read stay there |
| 34 | + * and are imported from there: `canonicalMetaType`, which code that did not |
| 35 | + * move reads too, and `governServedItem` / `stripServedSystemColumns`, whose |
| 36 | + * docblocks the class's read and write paths cite by name. The module cycle is |
| 37 | + * evaluation-safe because every one of them is read only inside a function |
| 38 | + * body, never while the two modules are first evaluated. `index.ts` names what |
| 39 | + * it publishes from `protocol.ts` one by one, so the `export` the last two |
| 40 | + * gained for this keeps them off the published surface, and nothing |
| 41 | + * re-exports this module, so the published surface does not move. |
| 42 | + */ |
| 43 | + |
| 44 | +import type { ObjectStackProtocolImplementation } from './protocol.js'; |
| 45 | +import { canonicalMetaType, governServedItem, stripServedSystemColumns } from './protocol.js'; |
| 46 | + |
| 47 | +/** |
| 48 | + * The `ObjectStackProtocolImplementation` members the functions below read — |
| 49 | + * every one of them, and no other; each function's `this` is the `Pick` of the |
| 50 | + * members IT reads. Each is typed AS the class member (an indexed access on |
| 51 | + * the class), so a change to a member's signature on the class is a change |
| 52 | + * here too, and naming a member the class does not have is a compile error. |
| 53 | + * See the module header for which members are getters. |
| 54 | + */ |
| 55 | +export interface RegistryMaterializeHost { |
| 56 | + readonly engine: ObjectStackProtocolImplementation['engine']; |
| 57 | + materializeFromRegistry: ObjectStackProtocolImplementation['materializeFromRegistry']; |
| 58 | + stripMaterializedFromRegistry: ObjectStackProtocolImplementation['stripMaterializedFromRegistry']; |
| 59 | +} |
| 60 | + |
| 61 | +/** The body of `ObjectStackProtocolImplementation#materializeFromRegistry`; the method's docblock there is its contract. */ |
| 62 | +export function materializeFromRegistry<T>( |
| 63 | + this: Pick<RegistryMaterializeHost, 'engine'>, |
| 64 | + type: string, body: T): T { |
| 65 | + if (canonicalMetaType(type) !== 'object') return body; |
| 66 | + if (body === null || typeof body !== 'object') return body; |
| 67 | + const registry = (this.engine as any)?.registry; |
| 68 | + // Partial registry doubles in tests predate this method; a host that |
| 69 | + // cannot answer the gate answers exactly as it did before. The |
| 70 | + // `provisionSearchCompanionOnto` fallback keeps a double that was |
| 71 | + // written against #8038's narrower seam serving what it served then, |
| 72 | + // rather than silently losing the companion convergence to a rename. |
| 73 | + if (registry && typeof registry.materializeServedObjectOnto === 'function') { |
| 74 | + try { |
| 75 | + return registry.materializeServedObjectOnto(body) as T; |
| 76 | + } catch { |
| 77 | + // A read over an in-memory flag, the registry's own resolved |
| 78 | + // answer and the body's own fields; a failure here must not turn |
| 79 | + // a served schema into a 5xx. |
| 80 | + return body; |
| 81 | + } |
| 82 | + } |
| 83 | + if (registry && typeof registry.provisionSearchCompanionOnto === 'function') { |
| 84 | + try { |
| 85 | + return registry.provisionSearchCompanionOnto(body) as T; |
| 86 | + } catch { |
| 87 | + return body; |
| 88 | + } |
| 89 | + } |
| 90 | + return body; |
| 91 | +} |
| 92 | + |
| 93 | +/** The body of `ObjectStackProtocolImplementation#governServedObject`; the method's docblock there is its contract. */ |
| 94 | +export function governServedObject<T>( |
| 95 | + this: Pick<RegistryMaterializeHost, 'materializeFromRegistry'>, |
| 96 | + type: string, item: T): T { |
| 97 | + return this.materializeFromRegistry(type, governServedItem(type, item)); |
| 98 | +} |
| 99 | + |
| 100 | +/** The body of `ObjectStackProtocolImplementation#stripMaterializedFromRegistry`; the method's docblock there is its contract. */ |
| 101 | +export function stripMaterializedFromRegistry<T>( |
| 102 | + this: Pick<RegistryMaterializeHost, 'engine'>, |
| 103 | + type: string, item: T): T { |
| 104 | + if (canonicalMetaType(type) !== 'object') return item; |
| 105 | + if (item === null || typeof item !== 'object') return item; |
| 106 | + const registry = (this.engine as any)?.registry; |
| 107 | + // Partial registry doubles in tests predate this method; the narrower |
| 108 | + // #8038 spelling keeps such a host stripping what it stripped before |
| 109 | + // rather than silently losing the companion strip to a rename. |
| 110 | + if (registry && typeof registry.stripMaterializedStampsFrom === 'function') { |
| 111 | + try { |
| 112 | + return registry.stripMaterializedStampsFrom(item) as T; |
| 113 | + } catch { |
| 114 | + // A read over the body's own fields; a failure here must not |
| 115 | + // turn a save into a 5xx. |
| 116 | + return item; |
| 117 | + } |
| 118 | + } |
| 119 | + if (registry && typeof registry.stripProvisionedSearchCompanionFrom === 'function') { |
| 120 | + try { |
| 121 | + return registry.stripProvisionedSearchCompanionFrom(item) as T; |
| 122 | + } catch { |
| 123 | + return item; |
| 124 | + } |
| 125 | + } |
| 126 | + return item; |
| 127 | +} |
| 128 | + |
| 129 | +/** The body of `ObjectStackProtocolImplementation#stripServedObjectColumns`; the method's docblock there is its contract. */ |
| 130 | +export function stripServedObjectColumns<T>( |
| 131 | + this: Pick<RegistryMaterializeHost, 'stripMaterializedFromRegistry'>, |
| 132 | + type: string, item: T): T { |
| 133 | + return this.stripMaterializedFromRegistry(type, stripServedSystemColumns(type, item)); |
| 134 | +} |
0 commit comments