@objectstack/plugin-msw 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/msw-plugin.ts CHANGED
@@ -4,11 +4,12 @@ import {
4
4
  Plugin,
5
5
  PluginContext,
6
6
  ObjectKernel,
7
- IDataEngine
7
+ HttpDispatcher,
8
+ HttpDispatcherResult
8
9
  } from '@objectstack/runtime';
9
10
  // import { ObjectStackProtocolImplementation } from '@objectstack/objectql';
10
11
  import { ObjectStackProtocol } from '@objectstack/spec/api';
11
- // import { IDataEngine } from '@objectstack/core';
12
+ import { IDataEngine } from '@objectstack/core';
12
13
 
13
14
  // Helper for parsing query parameters
14
15
  function parseQueryParams(url: URL): Record<string, any> {
@@ -54,33 +55,6 @@ function parseQueryParams(url: URL): Record<string, any> {
54
55
  return params;
55
56
  }
56
57
 
57
- // Helper to normalize flat parameters into 'where' clause
58
- function normalizeQuery(params: Record<string, any>): Record<string, any> {
59
- // If 'where' is already present, trust it
60
- if (params.where) return params;
61
-
62
- const reserved = ['select', 'order', 'orderBy', 'sort', 'limit', 'skip', 'offset', 'top', 'page', 'pageSize', 'count'];
63
- const where: Record<string, any> = {};
64
- let hasWhere = false;
65
-
66
- for (const key in params) {
67
- if (!reserved.includes(key)) {
68
- where[key] = params[key];
69
- hasWhere = true;
70
- }
71
- }
72
-
73
- if (hasWhere) {
74
- // Keep original params but add where.
75
- // This allows protocols that look at root properties to still work,
76
- // while providing 'where' for strict drivers.
77
- return { ...params, where };
78
- }
79
-
80
- return params;
81
- }
82
-
83
-
84
58
  export interface MSWPluginOptions {
85
59
  /**
86
60
  * Enable MSW in the browser environment
@@ -103,237 +77,9 @@ export interface MSWPluginOptions {
103
77
  logRequests?: boolean;
104
78
  }
105
79
 
106
- /**
107
- * ObjectStack Server Mock - Provides mock database functionality
108
- */
109
- export class ObjectStackServer {
110
- private static protocol: ObjectStackProtocol | null = null;
111
- private static logger: any | null = null;
112
-
113
- static init(protocol: ObjectStackProtocol, logger?: any) {
114
- this.protocol = protocol;
115
- this.logger = logger || {
116
- info: console.log,
117
- debug: console.debug,
118
- warn: console.warn,
119
- error: console.error
120
- };
121
- }
122
-
123
- static async findData(object: string, params?: any) {
124
- if (!this.protocol) {
125
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
126
- }
127
-
128
- this.logger?.debug?.('MSW: Finding records', { object, params });
129
- const result = await this.protocol.findData({ object, query: params || {} });
130
- this.logger?.debug?.('MSW: Find completed', { object, count: result?.records?.length ?? 0 });
131
- return {
132
- status: 200,
133
- data: result
134
- };
135
- }
136
-
137
- static async getData(object: string, id: string) {
138
- if (!this.protocol) {
139
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
140
- }
141
-
142
- this.logger?.debug?.('MSW: Getting record', { object, id });
143
- try {
144
- const result = await this.protocol.getData({ object, id });
145
- this.logger?.debug?.('MSW: Get completed', { object, id });
146
- return {
147
- status: 200,
148
- data: result
149
- };
150
- } catch (error) {
151
- this.logger?.warn?.('MSW: Get failed - not found', { object, id });
152
- const message = error instanceof Error ? error.message : 'Unknown error';
153
- return {
154
- status: 404,
155
- data: { error: message }
156
- };
157
- }
158
- }
159
-
160
- static async createData(object: string, data: any) {
161
- if (!this.protocol) {
162
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
163
- }
164
-
165
- this.logger?.debug?.('MSW: Creating record', { object });
166
- try {
167
- const result = await this.protocol.createData({ object, data });
168
- this.logger?.info?.('MSW: Record created', { object, id: result?.id });
169
- return {
170
- status: 201,
171
- data: result
172
- };
173
- } catch (error) {
174
- this.logger?.error?.('MSW: Create failed', error, { object });
175
- const message = error instanceof Error ? error.message : 'Unknown error';
176
- return {
177
- status: 400,
178
- data: { error: message }
179
- };
180
- }
181
- }
182
-
183
- static async updateData(object: string, id: string, data: any) {
184
- if (!this.protocol) {
185
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
186
- }
187
-
188
- this.logger?.debug?.('MSW: Updating record', { object, id });
189
- try {
190
- const result = await this.protocol.updateData({ object, id, data });
191
- this.logger?.info?.('MSW: Record updated', { object, id });
192
- return {
193
- status: 200,
194
- data: result
195
- };
196
- } catch (error) {
197
- this.logger?.error?.('MSW: Update failed', error, { object, id });
198
- const message = error instanceof Error ? error.message : 'Unknown error';
199
- return {
200
- status: 400,
201
- data: { error: message }
202
- };
203
- }
204
- }
205
-
206
- static async deleteData(object: string, id: string) {
207
- if (!this.protocol) {
208
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
209
- }
210
-
211
- this.logger?.debug?.('MSW: Deleting record', { object, id });
212
- try {
213
- const result = await this.protocol.deleteData({ object, id });
214
- this.logger?.info?.('MSW: Record deleted', { object, id, success: result?.success });
215
- return {
216
- status: 200,
217
- data: result
218
- };
219
- } catch (error) {
220
- this.logger?.error?.('MSW: Delete failed', error, { object, id });
221
- const message = error instanceof Error ? error.message : 'Unknown error';
222
- return {
223
- status: 400,
224
- data: { error: message }
225
- };
226
- }
227
- }
228
-
229
- static async analyticsQuery(request: any) {
230
- if (!this.protocol) {
231
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
232
- }
233
-
234
- this.logger?.debug?.('MSW: Executing analytics query', { request });
235
- try {
236
- const result = await this.protocol.analyticsQuery(request);
237
- this.logger?.debug?.('MSW: Analytics query completed', { result });
238
- return {
239
- status: 200,
240
- data: result
241
- };
242
- } catch (error) {
243
- this.logger?.error?.('MSW: Analytics query failed', error);
244
- const message = error instanceof Error ? error.message : 'Unknown error';
245
- return {
246
- status: 400,
247
- data: { error: message }
248
- };
249
- }
250
- }
251
-
252
- static async getAnalyticsMeta(request: any) {
253
- if (!this.protocol) {
254
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
255
- }
256
-
257
- this.logger?.debug?.('MSW: Getting analytics metadata', { request });
258
- try {
259
- const result = await this.protocol.getAnalyticsMeta(request);
260
- this.logger?.debug?.('MSW: Analytics metadata retrieved', { result });
261
- return {
262
- status: 200,
263
- data: result
264
- };
265
- } catch (error) {
266
- this.logger?.error?.('MSW: Analytics metadata failed', error);
267
- const message = error instanceof Error ? error.message : 'Unknown error';
268
- return {
269
- status: 400,
270
- data: { error: message }
271
- };
272
- }
273
- }
274
-
275
- static async triggerAutomation(request: any) {
276
- if (!this.protocol) {
277
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
278
- }
279
-
280
- this.logger?.debug?.('MSW: Triggering automation', { request });
281
- try {
282
- const result = await this.protocol.triggerAutomation(request);
283
- this.logger?.info?.('MSW: Automation triggered', { result });
284
- return {
285
- status: 200,
286
- data: result
287
- };
288
- } catch (error) {
289
- this.logger?.error?.('MSW: Automation trigger failed', error);
290
- const message = error instanceof Error ? error.message : 'Unknown error';
291
- return {
292
- status: 400,
293
- data: { error: message }
294
- };
295
- }
296
- }
297
-
298
- // Legacy method names for compatibility
299
- static async getUser(id: string) {
300
- return this.getData('user', id);
301
- }
302
-
303
- static async createUser(data: any) {
304
- return this.createData('user', data);
305
- }
306
- static async saveMetaItem(type: string, name: string, item: any) {
307
- if (!this.protocol) {
308
- throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
309
- }
310
-
311
- this.logger?.debug?.('MSW: Saving metadata', { type, name });
312
- try {
313
- // Check if protocol supports saveMetaItem (it might be added recently)
314
- if (!this.protocol.saveMetaItem) {
315
- throw new Error('Protocol does not support saveMetaItem');
316
- }
317
-
318
- const result = await this.protocol.saveMetaItem({ type, name, item });
319
- this.logger?.info?.('MSW: Metadata saved', { type, name });
320
- return {
321
- status: 200,
322
- data: result
323
- };
324
- } catch (error) {
325
- this.logger?.error?.('MSW: Save metadata failed', error, { type, name });
326
- const message = error instanceof Error ? error.message : 'Unknown error';
327
- return {
328
- status: 400,
329
- data: { error: message }
330
- };
331
- }
332
- }
333
- }
334
-
335
80
  /**
336
81
  * MSW Plugin for ObjectStack
82
+
337
83
  *
338
84
  * This plugin enables Mock Service Worker integration for testing and development.
339
85
  * It automatically mocks API endpoints using the ObjectStack runtime protocol.
@@ -358,6 +104,7 @@ export class MSWPlugin implements Plugin {
358
104
  private worker: any;
359
105
  private handlers: Array<any> = [];
360
106
  private protocol?: ObjectStackProtocol;
107
+ private dispatcher?: HttpDispatcher;
361
108
 
362
109
  constructor(options: MSWPluginOptions = {}) {
363
110
  this.options = {
@@ -438,315 +185,94 @@ export class MSWPlugin implements Plugin {
438
185
  * Setup MSW handlers
439
186
  */
440
187
  private setupHandlers(ctx: PluginContext) {
441
- if (!this.protocol) {
442
- ctx.logger.warn('[MSWPlugin] Protocol not initialized. Skipping default API handlers.');
443
- this.handlers = [
444
- ...(this.options.customHandlers || [])
445
- ];
446
- return;
188
+ // Initialize HttpDispatcher
189
+ try {
190
+ this.dispatcher = new HttpDispatcher(ctx.getKernel());
191
+ } catch (e) {
192
+ ctx.logger.warn('[MSWPlugin] Could not initialize HttpDispatcher via Kernel. Falling back to simple handlers.');
447
193
  }
448
194
 
449
- const protocol = this.protocol;
450
-
451
- // Initialize ObjectStackServer with structured logger
452
- ObjectStackServer.init(
453
- protocol,
454
- this.options.logRequests ? ctx.logger : undefined
455
- );
456
-
457
- ctx.logger.debug('Initialized ObjectStackServer', { logRequests: this.options.logRequests });
458
-
459
195
  const baseUrl = this.options.baseUrl || '/api/v1';
460
196
 
461
- // Define standard ObjectStack API handlers
197
+ // Custom handlers have priority
462
198
  this.handlers = [
463
- // Discovery endpoint
464
- http.get(`${baseUrl}`, async () => {
465
- const discovery = await protocol.getDiscovery({});
466
- return HttpResponse.json({
467
- ...discovery,
468
- routes: {
469
- data: `${baseUrl}/data`,
470
- metadata: `${baseUrl}/meta`,
471
- ui: `${baseUrl}/ui`,
472
- auth: `${baseUrl}/auth`
473
- }
474
- });
475
- }),
476
-
477
- // Meta endpoints
478
- http.get(`${baseUrl}/meta`, async ({ request }) => {
479
- const url = new URL(request.url);
480
- const query = parseQueryParams(url);
481
- return HttpResponse.json(await protocol.getMetaTypes({ query }));
482
- }),
199
+ ...(this.options.customHandlers || [])
200
+ ];
483
201
 
484
- http.get(`${baseUrl}/meta/:type`, async ({ params, request }) => {
202
+ if (this.dispatcher) {
203
+ const dispatcher = this.dispatcher;
204
+
205
+ // Catch-all handler for ObjectStack Runtime
206
+ // We use a wildcard to capture all methods and paths under baseUrl
207
+ const catchAll = async ({ request, params }: any) => {
485
208
  const url = new URL(request.url);
486
- const query = parseQueryParams(url);
487
- return HttpResponse.json(await protocol.getMetaItems({ type: params.type as string, query } as any));
488
- }),
489
-
490
- http.get(`${baseUrl}/meta/:type/:name`, async ({ params }) => {
491
- try {
492
- return HttpResponse.json(
493
- await protocol.getMetaItem({ type: params.type as string, name: params.name as string } as any)
494
- );
495
- } catch (error) {
496
- const message = error instanceof Error ? error.message : 'Unknown error';
497
- return HttpResponse.json({ error: message }, { status: 404 });
498
- }
499
- }),
500
-
501
- // Save Metadata
502
- http.put(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => {
503
- try {
504
- const body = await request.json();
505
- const result = await ObjectStackServer.saveMetaItem(
506
- params.type as string,
507
- params.name as string,
508
- body
509
- );
510
- return HttpResponse.json(result.data, { status: result.status });
511
- } catch (error) {
512
- const message = error instanceof Error ? error.message : 'Unknown error';
513
- return HttpResponse.json({ error: message }, { status: 400 });
514
- }
515
- }),
516
-
517
- // Data endpoints
518
- http.get(`${baseUrl}/data/:object`, async ({ params, request }) => {
519
- try {
520
- const url = new URL(request.url);
521
-
522
- // Use helper to parse properly (handle multiple values, JSON strings, numbers)
523
- const rawParams = parseQueryParams(url);
524
-
525
- // Normalize to standard query object (move flats to 'where')
526
- const queryParams = normalizeQuery(rawParams);
527
-
528
- const result = await ObjectStackServer.findData(
529
- params.object as string,
530
- queryParams
531
- );
532
- return HttpResponse.json(result.data, {
533
- status: result.status,
534
- headers: { 'Cache-Control': 'no-store' }
535
- });
536
- } catch (error) {
537
- const message = error instanceof Error ? error.message : 'Unknown error';
538
- return HttpResponse.json({ error: message }, { status: 404 });
539
- }
540
- }),
541
-
542
- http.get(`${baseUrl}/data/:object/:id`, async ({ params }) => {
543
- try {
544
- const result = await ObjectStackServer.getData(
545
- params.object as string,
546
- params.id as string
547
- );
548
- return HttpResponse.json(result.data, {
549
- status: result.status,
550
- headers: { 'Cache-Control': 'no-store' }
551
- });
552
- } catch (error) {
553
- const message = error instanceof Error ? error.message : 'Unknown error';
554
- return HttpResponse.json({ error: message }, { status: 404 });
555
- }
556
- }),
557
-
558
- http.post(`${baseUrl}/data/:object`, async ({ params, request }) => {
559
- try {
560
- const body = await request.json();
561
- const result = await ObjectStackServer.createData(
562
- params.object as string,
563
- body
564
- );
565
- return HttpResponse.json(result.data, { status: result.status });
566
- } catch (error) {
567
- const message = error instanceof Error ? error.message : 'Unknown error';
568
- return HttpResponse.json({ error: message }, { status: 400 });
569
- }
570
- }),
571
-
572
- http.patch(`${baseUrl}/data/:object/:id`, async ({ params, request }) => {
573
- try {
574
- const body = await request.json();
575
- const result = await ObjectStackServer.updateData(
576
- params.object as string,
577
- params.id as string,
578
- body
579
- );
580
- return HttpResponse.json(result.data, { status: result.status });
581
- } catch (error) {
582
- const message = error instanceof Error ? error.message : 'Unknown error';
583
- return HttpResponse.json({ error: message }, { status: 400 });
584
- }
585
- }),
586
-
587
- http.delete(`${baseUrl}/data/:object/:id`, async ({ params }) => {
588
- try {
589
- const result = await ObjectStackServer.deleteData(
590
- params.object as string,
591
- params.id as string
592
- );
593
- return HttpResponse.json(result.data, { status: result.status });
594
- } catch (error) {
595
- const message = error instanceof Error ? error.message : 'Unknown error';
596
- return HttpResponse.json({ error: message }, { status: 400 });
597
- }
598
- }),
599
-
600
- // Batch Operations
601
- http.post(`${baseUrl}/data/:object/batch`, async ({ params, request }) => {
602
- try {
603
- const body = await request.json();
604
- const result = await protocol.batchData({ object: params.object as string, request: body as any });
605
- return HttpResponse.json(result);
606
- } catch (error) {
607
- const message = error instanceof Error ? error.message : 'Unknown error';
608
- return HttpResponse.json({ error: message }, { status: 400 });
609
- }
610
- }),
611
-
612
- http.post(`${baseUrl}/data/:object/createMany`, async ({ params, request }) => {
613
- try {
614
- const body = await request.json();
615
- const records = Array.isArray(body) ? body : [];
616
- const result = await protocol.createManyData({ object: params.object as string, records });
617
- return HttpResponse.json(result, { status: 201 });
618
- } catch (error) {
619
- const message = error instanceof Error ? error.message : 'Unknown error';
620
- return HttpResponse.json({ error: message }, { status: 400 });
621
- }
622
- }),
623
-
624
- http.post(`${baseUrl}/data/:object/updateMany`, async ({ params, request }) => {
625
- try {
626
- const body = await request.json() as any;
627
- const result = await protocol.updateManyData({
628
- object: params.object as string,
629
- records: body?.records || [],
630
- options: body?.options
631
- });
632
- return HttpResponse.json(result);
633
- } catch (error) {
634
- const message = error instanceof Error ? error.message : 'Unknown error';
635
- return HttpResponse.json({ error: message }, { status: 400 });
209
+ // Calculate path relative to API prefix
210
+ // e.g. /api/v1/data/contacts -> /data/contacts
211
+ let path = url.pathname;
212
+ if (path.startsWith(baseUrl)) {
213
+ path = path.slice(baseUrl.length);
636
214
  }
637
- }),
638
-
639
- http.post(`${baseUrl}/data/:object/deleteMany`, async ({ params, request }) => {
640
- try {
641
- const body = await request.json() as any;
642
- const result = await protocol.deleteManyData({
643
- object: params.object as string,
644
- ids: body?.ids || [],
645
- options: body?.options
646
- });
647
- return HttpResponse.json(result);
648
- } catch (error) {
649
- const message = error instanceof Error ? error.message : 'Unknown error';
650
- return HttpResponse.json({ error: message }, { status: 400 });
215
+
216
+ // Parse Body if present
217
+ let body: any = undefined;
218
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
219
+ try {
220
+ body = await request.clone().json();
221
+ } catch (e) {
222
+ try {
223
+ // Try form data if json fails?
224
+ // Dispatcher expects objects usually.
225
+ // For file upload, body might be FormData logic needed?
226
+ // For now assume JSON or text
227
+ } catch (e2) {}
228
+ }
651
229
  }
652
- }),
653
230
 
654
- // Enhanced Metadata with Cache Support
655
- http.get(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => {
656
- try {
657
- const cacheRequest = {
658
- ifNoneMatch: request.headers.get('if-none-match') || undefined,
659
- ifModifiedSince: request.headers.get('if-modified-since') || undefined,
660
- };
661
-
662
- const result = await protocol.getMetaItemCached({
663
- type: params.type as string,
664
- name: params.name as string,
665
- cacheRequest
666
- } as any);
667
-
668
- if (result.notModified) {
669
- return new HttpResponse(null, { status: 304 });
670
- }
671
-
672
- // Build response headers
673
- const headers: Record<string, string> = {};
674
- if (result.etag) {
675
- const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
676
- headers['ETag'] = etagValue;
677
- }
678
- if (result.lastModified) {
679
- headers['Last-Modified'] = new Date(result.lastModified).toUTCString();
231
+ // Parse Query
232
+ const query = parseQueryParams(url);
233
+
234
+ // Dispatch
235
+ const result = await dispatcher.dispatch(
236
+ request.method,
237
+ path,
238
+ body,
239
+ query,
240
+ { request }
241
+ );
242
+
243
+ if (result.handled) {
244
+ if (result.response) {
245
+ return HttpResponse.json(result.response.body, {
246
+ status: result.response.status,
247
+ headers: result.response.headers as any
248
+ });
680
249
  }
681
- if (result.cacheControl) {
682
- const directives = result.cacheControl.directives.join(', ');
683
- const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : '';
684
- headers['Cache-Control'] = directives + maxAge;
250
+ if (result.result) {
251
+ // Handle special results (streams/redirects - unlikely in MSW but possible)
252
+ if (result.result.type === 'redirect') {
253
+ return HttpResponse.redirect(result.result.url);
254
+ }
255
+ // Fallback for others
256
+ return HttpResponse.json(result.result);
685
257
  }
686
-
687
- return HttpResponse.json(result.data, { headers });
688
- } catch (error) {
689
- const message = error instanceof Error ? error.message : 'Unknown error';
690
- return HttpResponse.json({ error: message }, { status: 404 });
691
- }
692
- }),
693
-
694
- // UI Protocol endpoint
695
- http.get(`${baseUrl}/ui/view/:object`, async ({ params, request }) => {
696
- try {
697
- const url = new URL(request.url);
698
- const viewType = url.searchParams.get('type') || 'list';
699
- const view = await protocol.getUiView({ object: params.object as string, type: viewType as 'list' | 'form' });
700
- return HttpResponse.json(view);
701
- } catch (error) {
702
- const message = error instanceof Error ? error.message : 'Unknown error';
703
- return HttpResponse.json({ error: message }, { status: 404 });
704
- }
705
- }),
706
-
707
- // Analytics Operations
708
- http.post(`${baseUrl}/analytics/query`, async ({ request }) => {
709
- try {
710
- const body = await request.json();
711
- const result = await ObjectStackServer.analyticsQuery(body);
712
- return HttpResponse.json(result.data, { status: result.status });
713
- } catch (error) {
714
- const message = error instanceof Error ? error.message : 'Unknown error';
715
- return HttpResponse.json({ error: message }, { status: 400 });
716
258
  }
717
- }),
718
-
719
- http.get(`${baseUrl}/analytics/meta`, async ({ request }) => {
720
- try {
721
- const url = new URL(request.url);
722
- const query = parseQueryParams(url);
723
- const result = await ObjectStackServer.getAnalyticsMeta(query);
724
- return HttpResponse.json(result.data, { status: result.status });
725
- } catch (error) {
726
- const message = error instanceof Error ? error.message : 'Unknown error';
727
- return HttpResponse.json({ error: message }, { status: 400 });
728
- }
729
- }),
730
-
731
- // Automation Operations
732
- http.post(`${baseUrl}/automation/trigger`, async ({ request }) => {
733
- try {
734
- const body = await request.json();
735
- const result = await ObjectStackServer.triggerAutomation(body);
736
- return HttpResponse.json(result.data, { status: result.status });
737
- } catch (error) {
738
- const message = error instanceof Error ? error.message : 'Unknown error';
739
- return HttpResponse.json({ error: message }, { status: 400 });
740
- }
741
- }),
742
-
743
- // Add custom handlers
744
- ...(this.options.customHandlers || [])
745
- ];
259
+
260
+ // Not handled by dispatcher (404 for this route subset)
261
+ return undefined; // Let MSW pass through or handle next
262
+ };
746
263
 
747
- ctx.logger.info('MSW request handlers installed', { count: this.handlers.length, baseUrl });
264
+ this.handlers.push(
265
+ http.all(`${baseUrl}/*`, catchAll),
266
+ http.all(`${baseUrl}`, catchAll) // Handle root if needed
267
+ );
268
+
269
+ ctx.logger.info('MSW handlers set up using HttpDispatcher', { baseUrl });
270
+ } else {
271
+ ctx.logger.warn('[MSWPlugin] No dispatcher available. No API routes registered.');
272
+ }
748
273
  }
749
274
 
275
+
750
276
  /**
751
277
  * Start the MSW worker
752
278
  */
@@ -788,3 +314,46 @@ export class MSWPlugin implements Plugin {
788
314
  return this.handlers;
789
315
  }
790
316
  }
317
+
318
+ /**
319
+ * Static helper for interacting with ObjectStack protocol in MSW handlers
320
+ */
321
+ export class ObjectStackServer {
322
+ private static protocol: ObjectStackProtocol;
323
+
324
+ static init(protocol: ObjectStackProtocol) {
325
+ this.protocol = protocol;
326
+ }
327
+
328
+ private static getProtocol(): ObjectStackProtocol {
329
+ if (!this.protocol) {
330
+ throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init(protocol) first.');
331
+ }
332
+ return this.protocol;
333
+ }
334
+
335
+ static async findData(objectName: string, query?: any) {
336
+ const body = await this.getProtocol().findData({ object: objectName, query });
337
+ return { data: body, status: 200 };
338
+ }
339
+
340
+ static async getData(objectName: string, id: string) {
341
+ const body = await this.getProtocol().getData({ object: objectName, id });
342
+ return { data: body, status: 200 };
343
+ }
344
+
345
+ static async createData(objectName: string, data: any) {
346
+ const body = await this.getProtocol().createData({ object: objectName, data });
347
+ return { data: body, status: 201 };
348
+ }
349
+
350
+ static async updateData(objectName: string, id: string, data: any) {
351
+ const body = await this.getProtocol().updateData({ object: objectName, id, data });
352
+ return { data: body, status: 200 };
353
+ }
354
+
355
+ static async deleteData(objectName: string, id: string) {
356
+ const body = await this.getProtocol().deleteData({ object: objectName, id });
357
+ return { data: body, status: 200 };
358
+ }
359
+ }