3232// already does that for the packaged case.
3333
3434import { describe , it , expect } from 'vitest' ;
35- import {
36- ActionActivationProjection ,
37- ObjectStoreActionActivationStore ,
38- assertEngineUpdateDispatch ,
39- } from '@objectstack/objectql' ;
35+ import { ActionActivationProjection , ObjectStoreActionActivationStore } from '@objectstack/objectql' ;
4036
4137import { HttpDispatcher } from '../http-dispatcher.js' ;
4238import type { HttpProtocolContext } from '../http-dispatcher.js' ;
@@ -80,60 +76,69 @@ function matches(row: Row, where: Row | undefined): boolean {
8076 } ) ;
8177}
8278
79+ /**
80+ * The in-memory `sys_metadata_activation` the durable store writes into: the
81+ * store's keyed `find` and its `insert`, and nothing else. At module scope, on
82+ * its own, so `pnpm check:objectql-double-limit` can lift and drive the `find`.
83+ *
84+ * `writeError` is thrown from every write — a store or record-validation
85+ * failure arriving through the door's own `catch`.
86+ *
87+ * ⛔ No `update`: every case here writes a FIRST row, which the store inserts.
88+ * The store's update leg for a package-less row is core's to pin
89+ * (`metadata-activation-store.test.ts`, "UPDATES its row with no package on the
90+ * way back on"); a second update double here would pin nothing that one does not.
91+ */
92+ function makeLedgerTable ( writeError ?: Error ) {
93+ const rows : Row [ ] = [ ] ;
94+ let nextId = 1 ;
95+ const engine = {
96+ async find ( object : string , options ?: any ) {
97+ const found = object === 'sys_metadata_activation' ? rows . filter ( ( r ) => matches ( r , options ?. where ) ) : [ ] ;
98+ // The caller's bound, AFTER the filter, by presence: the store's
99+ // table probe reads `limit: 1`.
100+ return typeof options ?. limit === 'number' ? found . slice ( 0 , options . limit ) : found ;
101+ } ,
102+ async insert ( object : string , data : Row ) {
103+ if ( writeError ) throw writeError ;
104+ const row = { id : `act_${ nextId ++ } ` , ...data } ;
105+ if ( object === LEDGER ) rows . push ( row ) ;
106+ return row ;
107+ } ,
108+ } ;
109+ return { rows, engine } ;
110+ }
111+
83112interface Harness {
84113 dispatcher : HttpDispatcher ;
85114 rows : Row [ ] ;
86- executeAction : ( ) => Promise < unknown > ;
87115 executed : ( ) => number ;
88116}
89117
90118/**
91119 * One deployment: the route object is a code artifact shipped by `crm`, the
92120 * engine carries the REAL action activation projection over the REAL durable
93- * store, and that store writes into an in-memory `sys_metadata_activation`.
94- *
95- * `writeError` is thrown from every ledger write — a store or record-validation
96- * failure arriving through the door's own `catch`.
121+ * store, and that store writes into {@link makeLedgerTable}.
97122 */
98123function boot ( opts : { writeError ?: Error } = { } ) : Harness {
99- const rows : Row [ ] = [ ] ;
124+ const { rows, engine : ledger } = makeLedgerTable ( opts . writeError ) ;
100125 let executed = 0 ;
101- let nextId = 1 ;
102126 const objectDef = {
103127 name : OBJECT , _packageId : PACKAGE , _provenance : 'package' , actions : [ PACKAGED , EMBEDDED_UNSTAMPED ] ,
104128 } ;
105129 const projection = new ActionActivationProjection ( ) ;
106- const executeAction = async ( ) => {
107- executed ++ ;
108- return { ran : 'script' } ;
109- } ;
110130 const ql : any = {
111- executeAction,
131+ ...ledger ,
132+ executeAction : async ( ) => {
133+ executed ++ ;
134+ return { ran : 'script' } ;
135+ } ,
112136 getSchema : ( name : string ) => ( name === OBJECT ? objectDef : undefined ) ,
113137 registry : {
114138 getObject : ( name : string ) => ( name === OBJECT ? objectDef : undefined ) ,
115139 getItem : ( type : string , name : string ) =>
116140 ( type === 'action' ? STANDALONE . find ( ( a ) => a . name === name ) : undefined ) ,
117141 } ,
118- async find ( object : string , options ?: any ) {
119- return object === LEDGER ? rows . filter ( ( r ) => matches ( r , options ?. where ) ) : [ ] ;
120- } ,
121- async insert ( object : string , data : Row ) {
122- if ( opts . writeError ) throw opts . writeError ;
123- const row = { id : `act_${ nextId ++ } ` , ...data } ;
124- if ( object === LEDGER ) rows . push ( row ) ;
125- return row ;
126- } ,
127- async update ( object : string , data : Row , options ?: any ) {
128- // The real engine's dispatch: the store updates by id, and nothing
129- // looser may pass here (`check:engine-double-contract`).
130- const dispatch = assertEngineUpdateDispatch ( data , options ) ;
131- if ( dispatch . kind !== 'by-id' ) throw new Error ( 'fake engine: only by-id updates are modelled' ) ;
132- if ( opts . writeError ) throw opts . writeError ;
133- const row = rows . find ( ( r ) => r . id === dispatch . id ) ;
134- if ( object === LEDGER && row ) Object . assign ( row , data ) ;
135- return row ;
136- } ,
137142 isActionEnabled : ( name : string ) => projection . isEnabled ( name ) ,
138143 describeDisabledAction : ( name : string ) => projection . describeDisabled ( name ) ,
139144 setActionActive : ( row : any ) => projection . setActive ( row ) ,
@@ -154,7 +159,7 @@ function boot(opts: { writeError?: Error } = {}): Harness {
154159 getServiceAsync : async ( name : string ) => resolve ( name ) ,
155160 context : { getService : resolve } ,
156161 } ;
157- return { dispatcher : new HttpDispatcher ( kernel as never ) , rows, executeAction , executed : ( ) => executed } ;
162+ return { dispatcher : new HttpDispatcher ( kernel as never ) , rows, executed : ( ) => executed } ;
158163}
159164
160165/** A single-tenant administrator holding `manage_metadata`: every authority gate passes. */
@@ -189,10 +194,9 @@ describe('#22817 — an action no package ships is switched, and its row names n
189194 ] ) ;
190195 } ) ;
191196
192- it ( '…and back on: the same row is UPDATED, still with no package ' , async ( ) => {
197+ it ( '…and switching it ON first writes the same package-less row, armed ' , async ( ) => {
193198 const h = boot ( ) ;
194199
195- await flip ( h , 'global' , ENV_GLOBAL . name , false ) ;
196200 const res = await flip ( h , 'global' , ENV_GLOBAL . name , true ) ;
197201
198202 expect ( statusOf ( res ) , bodyOf ( res ) ) . toBe ( 200 ) ;
0 commit comments