44 * Creates a fully bootstrapped ObjectStack kernel for use in both
55 * browser (MSW setupWorker) and test (MSW setupServer) environments.
66 *
7+ * Uses MSWPlugin from @objectstack/plugin-msw to expose the full
8+ * ObjectStack protocol via MSW. This ensures filter/sort/top/pagination
9+ * work identically to server mode.
10+ *
711 * Follows the same pattern as @objectstack/studio's createKernel —
812 * see https://github.com/objectstack-ai/spec
913 */
1014
1115import { ObjectKernel , DriverPlugin , AppPlugin } from '@objectstack/runtime' ;
1216import { ObjectQLPlugin } from '@objectstack/objectql' ;
1317import { InMemoryDriver } from '@objectstack/driver-memory' ;
18+ import { MSWPlugin } from '@objectstack/plugin-msw' ;
19+ import type { MSWPluginOptions } from '@objectstack/plugin-msw' ;
1420
1521export interface KernelOptions {
1622 /** Application configuration (defineStack output) */
1723 appConfig : any ;
1824 /** Whether to skip system validation (useful in browser) */
1925 skipSystemValidation ?: boolean ;
26+ /** MSWPlugin options; when provided, MSWPlugin is added to the kernel. */
27+ mswOptions ?: MSWPluginOptions ;
2028}
2129
2230export interface KernelResult {
2331 kernel : ObjectKernel ;
2432 driver : InMemoryDriver ;
33+ /** The MSWPlugin instance (if mswOptions was provided). */
34+ mswPlugin ?: MSWPlugin ;
35+ }
36+
37+ /**
38+ * Install a lightweight broker shim on the kernel so that
39+ * HttpDispatcher (used by MSWPlugin) can route data/metadata
40+ * calls through the ObjectStack protocol service.
41+ *
42+ * A full Moleculer-based broker is only available in server mode
43+ * (HonoServerPlugin). In MSW/browser mode we bridge the gap with
44+ * this thin adapter that delegates to the protocol service.
45+ */
46+ async function installBrokerShim ( kernel : ObjectKernel ) : Promise < void > {
47+ let protocol : any ;
48+ try {
49+ protocol = await kernel . getService ( 'protocol' ) ;
50+ } catch {
51+ return ;
52+ }
53+ if ( ! protocol ) return ;
54+
55+ ( kernel as any ) . broker = {
56+ async call ( action : string , params : any = { } ) {
57+ const [ service , method ] = action . split ( '.' ) ;
58+
59+ if ( service === 'data' ) {
60+ switch ( method ) {
61+ case 'query' :
62+ return protocol . findData ( { object : params . object , query : params . query ?? params } ) ;
63+ case 'get' :
64+ return protocol . getData ( { object : params . object , id : params . id } ) ;
65+ case 'create' :
66+ return protocol . createData ( { object : params . object , data : params . data } ) ;
67+ case 'update' :
68+ return protocol . updateData ( { object : params . object , id : params . id , data : params . data } ) ;
69+ case 'delete' :
70+ return protocol . deleteData ( { object : params . object , id : params . id } ) ;
71+ case 'batch' :
72+ return protocol . batchData ?.( { object : params . object , ...params } ) ?? { results : [ ] } ;
73+ }
74+ }
75+
76+ if ( service === 'metadata' ) {
77+ if ( method === 'types' ) return protocol . getMetaTypes ( { } ) ;
78+ if ( method === 'getObject' ) {
79+ return protocol . getMetaItem ( { type : 'object' , name : params . objectName } ) ;
80+ }
81+ if ( method === 'saveItem' ) {
82+ return protocol . saveMetaItem ?.( { type : params . type , name : params . name , item : params . item } ) ;
83+ }
84+ if ( method . startsWith ( 'get' ) ) {
85+ const type = method . replace ( 'get' , '' ) . toLowerCase ( ) ;
86+ return protocol . getMetaItem ( { type, name : params . name } ) ;
87+ }
88+ // list-style calls: metadata.objects, metadata.apps, etc.
89+ return protocol . getMetaItems ( { type : method , packageId : params . packageId } ) ;
90+ }
91+
92+ throw new Error ( `[BrokerShim] Unhandled action: ${ action } ` ) ;
93+ } ,
94+ } ;
2595}
2696
2797/**
@@ -31,7 +101,7 @@ export interface KernelResult {
31101 * so that kernel setup logic is not duplicated.
32102 */
33103export async function createKernel ( options : KernelOptions ) : Promise < KernelResult > {
34- const { appConfig, skipSystemValidation = true } = options ;
104+ const { appConfig, skipSystemValidation = true , mswOptions } = options ;
35105
36106 const driver = new InMemoryDriver ( ) ;
37107
@@ -43,7 +113,24 @@ export async function createKernel(options: KernelOptions): Promise<KernelResult
43113 await kernel . use ( new DriverPlugin ( driver , 'memory' ) ) ;
44114 await kernel . use ( new AppPlugin ( appConfig ) ) ;
45115
116+ let mswPlugin : MSWPlugin | undefined ;
117+ if ( mswOptions ) {
118+ // Install a protocol-based broker shim BEFORE MSWPlugin's start phase
119+ // so that HttpDispatcher (inside MSWPlugin) can resolve data/metadata
120+ // calls without requiring a full Moleculer broker.
121+ await installBrokerShim ( kernel ) ;
122+
123+ mswPlugin = new MSWPlugin ( mswOptions ) ;
124+ await kernel . use ( mswPlugin ) ;
125+ }
126+
46127 await kernel . bootstrap ( ) ;
47128
48- return { kernel, driver } ;
129+ // Re-install broker shim after bootstrap to ensure the protocol service
130+ // is fully initialised (some plugins register services during start phase).
131+ if ( mswOptions ) {
132+ await installBrokerShim ( kernel ) ;
133+ }
134+
135+ return { kernel, driver, mswPlugin } ;
49136}
0 commit comments