@objectstack/plugin-msw 1.0.1 → 1.0.4
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/CHANGELOG.md +45 -0
- package/README.md +20 -6
- package/dist/msw-plugin.d.ts +51 -213
- package/dist/msw-plugin.js +97 -495
- package/package.json +7 -6
- package/src/msw-plugin.ts +120 -551
package/src/msw-plugin.ts
CHANGED
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
Plugin,
|
|
5
5
|
PluginContext,
|
|
6
6
|
ObjectKernel,
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
this.
|
|
444
|
-
|
|
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
|
-
//
|
|
197
|
+
// Custom handlers have priority
|
|
462
198
|
this.handlers = [
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
720
|
-
|
|
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
|
-
|
|
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
|
+
}
|