11/**
22 * MSW Browser Worker Setup via ObjectStack Runtime
33 *
4- * Bootstraps a full ObjectStack kernel (ObjectQL + InMemoryDriver + AppPlugin)
5- * in the browser, then creates MSW handlers manually so responses match the
6- * format the ObjectStackClient expects (same as HonoServerPlugin / RestServer).
4+ * Uses the shared createKernel() factory to bootstrap the ObjectStack kernel,
5+ * then creates MSW handlers via the shared handler factory.
76 *
8- * This intentionally does NOT use @objectstack/plugin-msw because its
9- * HttpDispatcher wraps every response in a { success, data } envelope that
10- * the client does not expect.
7+ * This pattern follows @objectstack/studio — see https://github.com/objectstack-ai/spec
118 */
129
13- import { ObjectKernel , DriverPlugin , AppPlugin } from '@objectstack/runtime' ;
14- import { ObjectQLPlugin } from '@objectstack/objectql' ;
10+ import { ObjectKernel } from '@objectstack/runtime' ;
1511import { InMemoryDriver } from '@objectstack/driver-memory' ;
1612import { setupWorker } from 'msw/browser' ;
17- import { http , HttpResponse } from 'msw' ;
1813import appConfig from '../../objectstack.shared' ;
14+ import { createKernel } from './createKernel' ;
15+ import { createHandlers } from './handlers' ;
1916
2017let kernel : ObjectKernel | null = null ;
2118let driver : InMemoryDriver | null = null ;
@@ -38,18 +35,9 @@ export async function startMockServer() {
3835
3936 if ( import . meta. env . DEV ) console . log ( '[MSW] Starting ObjectStack Runtime (Browser Mode)...' ) ;
4037
41- driver = new InMemoryDriver ( ) ;
42-
43- kernel = new ObjectKernel ( {
44- skipSystemValidation : true
45- } ) ;
46-
47- await kernel . use ( new ObjectQLPlugin ( ) ) ;
48- await kernel . use ( new DriverPlugin ( driver , 'memory' ) ) ;
49- await kernel . use ( new AppPlugin ( appConfig ) ) ;
50-
51- // Bootstrap kernel WITHOUT MSW plugin — we create handlers manually below
52- await kernel . bootstrap ( ) ;
38+ const result = await createKernel ( { appConfig } ) ;
39+ kernel = result . kernel ;
40+ driver = result . driver ;
5341
5442 // Create MSW handlers that match the response format of HonoServerPlugin
5543 // Include both /api/v1 and legacy /api paths so the ObjectStackClient can
@@ -82,190 +70,3 @@ export function getKernel(): ObjectKernel | null {
8270export function getDriver ( ) : InMemoryDriver | null {
8371 return driver ;
8472}
85-
86- /**
87- * Create MSW request handlers for ObjectStack API.
88- *
89- * Response shapes intentionally match HonoServerPlugin / RestServer so that
90- * ObjectStackClient works identically in both MSW and server mode.
91- */
92- function createHandlers ( baseUrl : string , kernel : ObjectKernel , driver : InMemoryDriver ) {
93- const protocol = kernel . getService ( 'protocol' ) as any ;
94-
95- return [
96- // ── Discovery ────────────────────────────────────────────────────────
97- http . get ( '*/.well-known/objectstack' , async ( ) => {
98- const response = await protocol . getDiscovery ( ) ;
99- return HttpResponse . json ( response , { status : 200 } ) ;
100- } ) ,
101-
102- http . get ( `*${ baseUrl } ` , async ( ) => {
103- const response = await protocol . getDiscovery ( ) ;
104- return HttpResponse . json ( response , { status : 200 } ) ;
105- } ) ,
106- http . get ( `*${ baseUrl } /` , async ( ) => {
107- const response = await protocol . getDiscovery ( ) ;
108- return HttpResponse . json ( response , { status : 200 } ) ;
109- } ) ,
110-
111- // ── Metadata: list objects ───────────────────────────────────────────
112- http . get ( `*${ baseUrl } /meta/objects` , async ( ) => {
113- const response = await protocol . getMetaItems ( { type : 'object' } ) ;
114- return HttpResponse . json ( response , { status : 200 } ) ;
115- } ) ,
116- http . get ( `*${ baseUrl } /metadata/objects` , async ( ) => {
117- const response = await protocol . getMetaItems ( { type : 'object' } ) ;
118- return HttpResponse . json ( response , { status : 200 } ) ;
119- } ) ,
120-
121- // ── Metadata: single object (legacy /meta/objects/:name) ─────────────
122- http . get ( `*${ baseUrl } /meta/objects/:objectName` , async ( { params } ) => {
123- if ( import . meta. env . DEV ) console . log ( '[MSW] meta/objects/' , params . objectName ) ;
124- try {
125- const response = await protocol . getMetaItem ( {
126- type : 'object' ,
127- name : params . objectName as string
128- } ) ;
129- return HttpResponse . json ( response || { error : 'Not found' } , { status : response ? 200 : 404 } ) ;
130- } catch ( e ) {
131- return HttpResponse . json ( { error : String ( e ) } , { status : 500 } ) ;
132- }
133- } ) ,
134-
135- // ── Metadata: single object (/meta/object/:name & /metadata/object/:name)
136- http . get ( `*${ baseUrl } /meta/object/:objectName` , async ( { params } ) => {
137- if ( import . meta. env . DEV ) console . log ( '[MSW] meta/object/' , params . objectName ) ;
138- try {
139- const response = await protocol . getMetaItem ( {
140- type : 'object' ,
141- name : params . objectName as string
142- } ) ;
143- const payload = ( response && response . item ) ? response . item : response ;
144- return HttpResponse . json ( payload || { error : 'Not found' } , { status : payload ? 200 : 404 } ) ;
145- } catch ( e ) {
146- console . error ( '[MSW] error getting meta item' , e ) ;
147- return HttpResponse . json ( { error : String ( e ) } , { status : 500 } ) ;
148- }
149- } ) ,
150-
151- http . get ( `*${ baseUrl } /metadata/object/:objectName` , async ( { params } ) => {
152- if ( import . meta. env . DEV ) console . log ( '[MSW] metadata/object/' , params . objectName ) ;
153- try {
154- const response = await protocol . getMetaItem ( {
155- type : 'object' ,
156- name : params . objectName as string
157- } ) ;
158- const payload = ( response && response . item ) ? response . item : response ;
159- return HttpResponse . json ( payload || { error : 'Not found' } , { status : payload ? 200 : 404 } ) ;
160- } catch ( e ) {
161- console . error ( '[MSW] error getting meta item' , e ) ;
162- return HttpResponse . json ( { error : String ( e ) } , { status : 500 } ) ;
163- }
164- } ) ,
165-
166- // ── Metadata: apps ──────────────────────────────────────────────────
167- http . get ( `*${ baseUrl } /meta/apps` , async ( ) => {
168- const response = await protocol . getMetaItems ( { type : 'app' } ) ;
169- return HttpResponse . json ( response , { status : 200 } ) ;
170- } ) ,
171- http . get ( `*${ baseUrl } /metadata/apps` , async ( ) => {
172- const response = await protocol . getMetaItems ( { type : 'app' } ) ;
173- return HttpResponse . json ( response , { status : 200 } ) ;
174- } ) ,
175-
176- // ── Metadata: dashboards ────────────────────────────────────────────
177- http . get ( `*${ baseUrl } /meta/dashboards` , async ( ) => {
178- const response = await protocol . getMetaItems ( { type : 'dashboard' } ) ;
179- return HttpResponse . json ( response , { status : 200 } ) ;
180- } ) ,
181- http . get ( `*${ baseUrl } /metadata/dashboards` , async ( ) => {
182- const response = await protocol . getMetaItems ( { type : 'dashboard' } ) ;
183- return HttpResponse . json ( response , { status : 200 } ) ;
184- } ) ,
185-
186- // ── Metadata: reports ───────────────────────────────────────────────
187- http . get ( `*${ baseUrl } /meta/reports` , async ( ) => {
188- const response = await protocol . getMetaItems ( { type : 'report' } ) ;
189- return HttpResponse . json ( response , { status : 200 } ) ;
190- } ) ,
191- http . get ( `*${ baseUrl } /metadata/reports` , async ( ) => {
192- const response = await protocol . getMetaItems ( { type : 'report' } ) ;
193- return HttpResponse . json ( response , { status : 200 } ) ;
194- } ) ,
195-
196- // ── Metadata: pages ─────────────────────────────────────────────────
197- http . get ( `*${ baseUrl } /meta/pages` , async ( ) => {
198- const response = await protocol . getMetaItems ( { type : 'page' } ) ;
199- return HttpResponse . json ( response , { status : 200 } ) ;
200- } ) ,
201- http . get ( `*${ baseUrl } /metadata/pages` , async ( ) => {
202- const response = await protocol . getMetaItems ( { type : 'page' } ) ;
203- return HttpResponse . json ( response , { status : 200 } ) ;
204- } ) ,
205-
206- // ── Data: find all ──────────────────────────────────────────────────
207- http . get ( `*${ baseUrl } /data/:objectName` , async ( { params, request } ) => {
208- const url = new URL ( request . url ) ;
209- const query : any = { } ;
210-
211- url . searchParams . forEach ( ( value , key ) => {
212- try {
213- query [ key ] = JSON . parse ( value ) ;
214- } catch {
215- query [ key ] = value ;
216- }
217- } ) ;
218-
219- if ( import . meta. env . DEV ) console . log ( '[MSW] find' , params . objectName , query ) ;
220- const response = await driver . find ( params . objectName as string , query ) ;
221- return HttpResponse . json ( { value : response } , { status : 200 } ) ;
222- } ) ,
223-
224- // ── Data: find by ID ────────────────────────────────────────────────
225- http . get ( `*${ baseUrl } /data/:objectName/:id` , async ( { params } ) => {
226- try {
227- if ( import . meta. env . DEV ) console . log ( '[MSW] getData' , params . objectName , params . id ) ;
228-
229- const allRecords = await driver . find ( params . objectName as string , {
230- object : params . objectName as string
231- } ) ;
232- const record = allRecords
233- ? allRecords . find ( ( r : any ) =>
234- String ( r . id ) === String ( params . id ) ||
235- String ( r . _id ) === String ( params . id )
236- )
237- : null ;
238-
239- if ( import . meta. env . DEV ) console . log ( '[MSW] getData result' , JSON . stringify ( record ) ) ;
240- return HttpResponse . json ( { record } , { status : record ? 200 : 404 } ) ;
241- } catch ( e ) {
242- console . error ( '[MSW] getData error' , e ) ;
243- return HttpResponse . json ( { error : String ( e ) } , { status : 500 } ) ;
244- }
245- } ) ,
246-
247- // ── Data: create ────────────────────────────────────────────────────
248- http . post ( `*${ baseUrl } /data/:objectName` , async ( { params, request } ) => {
249- const body = await request . json ( ) ;
250- if ( import . meta. env . DEV ) console . log ( '[MSW] create' , params . objectName , JSON . stringify ( body ) ) ;
251- const response = await driver . create ( params . objectName as string , body as any ) ;
252- if ( import . meta. env . DEV ) console . log ( '[MSW] create result' , JSON . stringify ( response ) ) ;
253- return HttpResponse . json ( { record : response } , { status : 201 } ) ;
254- } ) ,
255-
256- // ── Data: update ────────────────────────────────────────────────────
257- http . patch ( `*${ baseUrl } /data/:objectName/:id` , async ( { params, request } ) => {
258- const body = await request . json ( ) ;
259- if ( import . meta. env . DEV ) console . log ( '[MSW] update' , params . objectName , params . id , JSON . stringify ( body ) ) ;
260- const response = await driver . update ( params . objectName as string , params . id as string , body as any ) ;
261- if ( import . meta. env . DEV ) console . log ( '[MSW] update result' , JSON . stringify ( response ) ) ;
262- return HttpResponse . json ( { record : response } , { status : 200 } ) ;
263- } ) ,
264-
265- // ── Data: delete ────────────────────────────────────────────────────
266- http . delete ( `*${ baseUrl } /data/:objectName/:id` , async ( { params } ) => {
267- const response = await driver . delete ( params . objectName as string , params . id as string ) ;
268- return HttpResponse . json ( response , { status : 200 } ) ;
269- } ) ,
270- ] ;
271- }
0 commit comments