@objectstack/plugin-msw 0.6.1 → 0.7.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/CHANGELOG.md +22 -0
- package/README.md +10 -0
- package/dist/msw-plugin.d.ts +68 -9
- package/dist/msw-plugin.js +270 -36
- package/objectstack.config.ts +273 -0
- package/package.json +6 -6
- package/src/msw-plugin.ts +274 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @objectstack/plugin-msw
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fb41cc0: Patch release: Updated documentation and JSON schemas
|
|
8
|
+
- Updated dependencies [fb41cc0]
|
|
9
|
+
- @objectstack/spec@0.7.2
|
|
10
|
+
- @objectstack/types@0.7.2
|
|
11
|
+
- @objectstack/objectql@0.7.2
|
|
12
|
+
- @objectstack/runtime@0.7.2
|
|
13
|
+
|
|
14
|
+
## 0.7.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Patch release for maintenance and stability improvements
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @objectstack/spec@0.7.1
|
|
21
|
+
- @objectstack/objectql@0.7.1
|
|
22
|
+
- @objectstack/runtime@0.7.1
|
|
23
|
+
- @objectstack/types@0.7.1
|
|
24
|
+
|
|
3
25
|
## 0.6.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
MSW (Mock Service Worker) Plugin for ObjectStack Runtime. This plugin enables seamless integration with [Mock Service Worker](https://mswjs.io/) for testing and development environments.
|
|
4
4
|
|
|
5
|
+
## Plugin Capabilities
|
|
6
|
+
|
|
7
|
+
This plugin implements the ObjectStack plugin capability protocol:
|
|
8
|
+
- **Protocol**: `com.objectstack.protocol.testing.mock.v1` (full conformance)
|
|
9
|
+
- **Protocol**: `com.objectstack.protocol.api.rest.v1` (full conformance)
|
|
10
|
+
- **Provides**: `ObjectStackServer` interface for mock API operations
|
|
11
|
+
- **Requires**: `com.objectstack.engine.objectql` for data operations
|
|
12
|
+
|
|
13
|
+
See [objectstack.config.ts](./objectstack.config.ts) for the complete capability manifest.
|
|
14
|
+
|
|
5
15
|
## Features
|
|
6
16
|
|
|
7
17
|
- 🎯 **Automatic API Mocking**: Automatically generates MSW handlers for all ObjectStack API endpoints
|
package/dist/msw-plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Plugin, PluginContext } from '@objectstack/runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { ObjectStackProtocol } from '@objectstack/spec/api';
|
|
3
3
|
export interface MSWPluginOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Enable MSW in the browser environment
|
|
@@ -24,34 +24,93 @@ export interface MSWPluginOptions {
|
|
|
24
24
|
export declare class ObjectStackServer {
|
|
25
25
|
private static protocol;
|
|
26
26
|
private static logger;
|
|
27
|
-
static init(protocol:
|
|
27
|
+
static init(protocol: ObjectStackProtocol, logger?: any): void;
|
|
28
28
|
static findData(object: string, params?: any): Promise<{
|
|
29
29
|
status: number;
|
|
30
|
-
data:
|
|
30
|
+
data: {
|
|
31
|
+
object: string;
|
|
32
|
+
records: Record<string, any>[];
|
|
33
|
+
total?: number | undefined;
|
|
34
|
+
hasMore?: boolean | undefined;
|
|
35
|
+
};
|
|
31
36
|
}>;
|
|
32
37
|
static getData(object: string, id: string): Promise<{
|
|
33
38
|
status: number;
|
|
34
|
-
data:
|
|
39
|
+
data: {
|
|
40
|
+
object: string;
|
|
41
|
+
record: Record<string, any>;
|
|
42
|
+
id: string;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
status: number;
|
|
46
|
+
data: {
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
35
49
|
}>;
|
|
36
50
|
static createData(object: string, data: any): Promise<{
|
|
37
51
|
status: number;
|
|
38
|
-
data:
|
|
52
|
+
data: {
|
|
53
|
+
object: string;
|
|
54
|
+
record: Record<string, any>;
|
|
55
|
+
id: string;
|
|
56
|
+
};
|
|
57
|
+
} | {
|
|
58
|
+
status: number;
|
|
59
|
+
data: {
|
|
60
|
+
error: string;
|
|
61
|
+
};
|
|
39
62
|
}>;
|
|
40
63
|
static updateData(object: string, id: string, data: any): Promise<{
|
|
41
64
|
status: number;
|
|
42
|
-
data:
|
|
65
|
+
data: {
|
|
66
|
+
object: string;
|
|
67
|
+
record: Record<string, any>;
|
|
68
|
+
id: string;
|
|
69
|
+
};
|
|
70
|
+
} | {
|
|
71
|
+
status: number;
|
|
72
|
+
data: {
|
|
73
|
+
error: string;
|
|
74
|
+
};
|
|
43
75
|
}>;
|
|
44
76
|
static deleteData(object: string, id: string): Promise<{
|
|
45
77
|
status: number;
|
|
46
|
-
data:
|
|
78
|
+
data: {
|
|
79
|
+
object: string;
|
|
80
|
+
id: string;
|
|
81
|
+
success: boolean;
|
|
82
|
+
};
|
|
83
|
+
} | {
|
|
84
|
+
status: number;
|
|
85
|
+
data: {
|
|
86
|
+
error: string;
|
|
87
|
+
};
|
|
47
88
|
}>;
|
|
48
89
|
static getUser(id: string): Promise<{
|
|
49
90
|
status: number;
|
|
50
|
-
data:
|
|
91
|
+
data: {
|
|
92
|
+
object: string;
|
|
93
|
+
record: Record<string, any>;
|
|
94
|
+
id: string;
|
|
95
|
+
};
|
|
96
|
+
} | {
|
|
97
|
+
status: number;
|
|
98
|
+
data: {
|
|
99
|
+
error: string;
|
|
100
|
+
};
|
|
51
101
|
}>;
|
|
52
102
|
static createUser(data: any): Promise<{
|
|
53
103
|
status: number;
|
|
54
|
-
data:
|
|
104
|
+
data: {
|
|
105
|
+
object: string;
|
|
106
|
+
record: Record<string, any>;
|
|
107
|
+
id: string;
|
|
108
|
+
};
|
|
109
|
+
} | {
|
|
110
|
+
status: number;
|
|
111
|
+
data: {
|
|
112
|
+
error: string;
|
|
113
|
+
};
|
|
55
114
|
}>;
|
|
56
115
|
}
|
|
57
116
|
/**
|
package/dist/msw-plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { http, HttpResponse } from 'msw';
|
|
1
|
+
import { http, HttpResponse, passthrough } from 'msw';
|
|
2
2
|
import { setupWorker } from 'msw/browser';
|
|
3
3
|
import { ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
4
4
|
/**
|
|
@@ -7,14 +7,20 @@ import { ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
|
7
7
|
export class ObjectStackServer {
|
|
8
8
|
static init(protocol, logger) {
|
|
9
9
|
this.protocol = protocol;
|
|
10
|
-
this.logger = logger ||
|
|
10
|
+
this.logger = logger || {
|
|
11
|
+
info: console.log,
|
|
12
|
+
debug: console.debug,
|
|
13
|
+
warn: console.warn,
|
|
14
|
+
error: console.error
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
static async findData(object, params) {
|
|
13
18
|
if (!this.protocol) {
|
|
14
19
|
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
15
20
|
}
|
|
16
|
-
this.logger?.(
|
|
17
|
-
const result = await this.protocol.findData(object, params || {});
|
|
21
|
+
this.logger?.debug?.('MSW: Finding records', { object, params });
|
|
22
|
+
const result = await this.protocol.findData({ object, query: params || {} });
|
|
23
|
+
this.logger?.debug?.('MSW: Find completed', { object, count: result?.records?.length ?? 0 });
|
|
18
24
|
return {
|
|
19
25
|
status: 200,
|
|
20
26
|
data: result
|
|
@@ -24,15 +30,17 @@ export class ObjectStackServer {
|
|
|
24
30
|
if (!this.protocol) {
|
|
25
31
|
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
26
32
|
}
|
|
27
|
-
this.logger?.(
|
|
33
|
+
this.logger?.debug?.('MSW: Getting record', { object, id });
|
|
28
34
|
try {
|
|
29
|
-
const result = await this.protocol.getData(object, id);
|
|
35
|
+
const result = await this.protocol.getData({ object, id });
|
|
36
|
+
this.logger?.debug?.('MSW: Get completed', { object, id });
|
|
30
37
|
return {
|
|
31
38
|
status: 200,
|
|
32
39
|
data: result
|
|
33
40
|
};
|
|
34
41
|
}
|
|
35
42
|
catch (error) {
|
|
43
|
+
this.logger?.warn?.('MSW: Get failed - not found', { object, id });
|
|
36
44
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
37
45
|
return {
|
|
38
46
|
status: 404,
|
|
@@ -44,15 +52,17 @@ export class ObjectStackServer {
|
|
|
44
52
|
if (!this.protocol) {
|
|
45
53
|
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
46
54
|
}
|
|
47
|
-
this.logger?.(
|
|
55
|
+
this.logger?.debug?.('MSW: Creating record', { object });
|
|
48
56
|
try {
|
|
49
|
-
const result = await this.protocol.createData(object, data);
|
|
57
|
+
const result = await this.protocol.createData({ object, data });
|
|
58
|
+
this.logger?.info?.('MSW: Record created', { object, id: result?.id });
|
|
50
59
|
return {
|
|
51
60
|
status: 201,
|
|
52
61
|
data: result
|
|
53
62
|
};
|
|
54
63
|
}
|
|
55
64
|
catch (error) {
|
|
65
|
+
this.logger?.error?.('MSW: Create failed', error, { object });
|
|
56
66
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
57
67
|
return {
|
|
58
68
|
status: 400,
|
|
@@ -64,15 +74,17 @@ export class ObjectStackServer {
|
|
|
64
74
|
if (!this.protocol) {
|
|
65
75
|
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
66
76
|
}
|
|
67
|
-
this.logger?.(
|
|
77
|
+
this.logger?.debug?.('MSW: Updating record', { object, id });
|
|
68
78
|
try {
|
|
69
|
-
const result = await this.protocol.updateData(object, id, data);
|
|
79
|
+
const result = await this.protocol.updateData({ object, id, data });
|
|
80
|
+
this.logger?.info?.('MSW: Record updated', { object, id });
|
|
70
81
|
return {
|
|
71
82
|
status: 200,
|
|
72
83
|
data: result
|
|
73
84
|
};
|
|
74
85
|
}
|
|
75
86
|
catch (error) {
|
|
87
|
+
this.logger?.error?.('MSW: Update failed', error, { object, id });
|
|
76
88
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
77
89
|
return {
|
|
78
90
|
status: 400,
|
|
@@ -84,15 +96,17 @@ export class ObjectStackServer {
|
|
|
84
96
|
if (!this.protocol) {
|
|
85
97
|
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
86
98
|
}
|
|
87
|
-
this.logger?.(
|
|
99
|
+
this.logger?.debug?.('MSW: Deleting record', { object, id });
|
|
88
100
|
try {
|
|
89
|
-
const result = await this.protocol.deleteData(object, id);
|
|
101
|
+
const result = await this.protocol.deleteData({ object, id });
|
|
102
|
+
this.logger?.info?.('MSW: Record deleted', { object, id, success: result?.success });
|
|
90
103
|
return {
|
|
91
104
|
status: 200,
|
|
92
105
|
data: result
|
|
93
106
|
};
|
|
94
107
|
}
|
|
95
108
|
catch (error) {
|
|
109
|
+
this.logger?.error?.('MSW: Delete failed', error, { object, id });
|
|
96
110
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
97
111
|
return {
|
|
98
112
|
status: 400,
|
|
@@ -144,23 +158,30 @@ export class MSWPlugin {
|
|
|
144
158
|
* Init phase
|
|
145
159
|
*/
|
|
146
160
|
async init(ctx) {
|
|
161
|
+
ctx.logger.debug('Initializing MSW plugin', {
|
|
162
|
+
enableBrowser: this.options.enableBrowser,
|
|
163
|
+
baseUrl: this.options.baseUrl,
|
|
164
|
+
logRequests: this.options.logRequests
|
|
165
|
+
});
|
|
147
166
|
// Protocol will be created in start phase
|
|
148
|
-
ctx.logger.
|
|
167
|
+
ctx.logger.info('MSW plugin initialized');
|
|
149
168
|
}
|
|
150
169
|
/**
|
|
151
170
|
* Start phase
|
|
152
171
|
*/
|
|
153
172
|
async start(ctx) {
|
|
173
|
+
ctx.logger.debug('Starting MSW plugin');
|
|
154
174
|
try {
|
|
155
175
|
const dataEngine = ctx.getService('objectql');
|
|
156
176
|
this.protocol = new ObjectStackProtocolImplementation(dataEngine);
|
|
177
|
+
ctx.logger.debug('Protocol implementation created');
|
|
157
178
|
}
|
|
158
179
|
catch (e) {
|
|
159
|
-
|
|
180
|
+
ctx.logger.error('Failed to initialize protocol', e);
|
|
160
181
|
throw new Error('[MSWPlugin] Failed to initialize protocol (missing objectql service?)');
|
|
161
182
|
}
|
|
162
|
-
this.setupHandlers();
|
|
163
|
-
await this.startWorker();
|
|
183
|
+
this.setupHandlers(ctx);
|
|
184
|
+
await this.startWorker(ctx);
|
|
164
185
|
}
|
|
165
186
|
/**
|
|
166
187
|
* Destroy phase
|
|
@@ -171,30 +192,43 @@ export class MSWPlugin {
|
|
|
171
192
|
/**
|
|
172
193
|
* Setup MSW handlers
|
|
173
194
|
*/
|
|
174
|
-
setupHandlers() {
|
|
195
|
+
setupHandlers(ctx) {
|
|
175
196
|
if (!this.protocol) {
|
|
176
197
|
throw new Error('[MSWPlugin] Protocol not initialized');
|
|
177
198
|
}
|
|
178
199
|
const protocol = this.protocol;
|
|
179
|
-
// Initialize ObjectStackServer
|
|
180
|
-
ObjectStackServer.init(protocol, this.options.logRequests ?
|
|
200
|
+
// Initialize ObjectStackServer with structured logger
|
|
201
|
+
ObjectStackServer.init(protocol, this.options.logRequests ? ctx.logger : undefined);
|
|
202
|
+
ctx.logger.debug('Initialized ObjectStackServer', { logRequests: this.options.logRequests });
|
|
181
203
|
const baseUrl = this.options.baseUrl || '/api/v1';
|
|
182
204
|
// Define standard ObjectStack API handlers
|
|
183
205
|
this.handlers = [
|
|
206
|
+
// Passthrough for external resources
|
|
207
|
+
http.get('https://fonts.googleapis.com/*', () => passthrough()),
|
|
208
|
+
http.get('https://fonts.gstatic.com/*', () => passthrough()),
|
|
184
209
|
// Discovery endpoint
|
|
185
|
-
http.get(`${baseUrl}`, () => {
|
|
186
|
-
|
|
210
|
+
http.get(`${baseUrl}`, async () => {
|
|
211
|
+
const discovery = await protocol.getDiscovery({});
|
|
212
|
+
return HttpResponse.json({
|
|
213
|
+
...discovery,
|
|
214
|
+
routes: {
|
|
215
|
+
data: `${baseUrl}/data`,
|
|
216
|
+
metadata: `${baseUrl}/meta`,
|
|
217
|
+
ui: `${baseUrl}/ui`,
|
|
218
|
+
auth: `${baseUrl}/auth`
|
|
219
|
+
}
|
|
220
|
+
});
|
|
187
221
|
}),
|
|
188
222
|
// Meta endpoints
|
|
189
|
-
http.get(`${baseUrl}/meta`, () => {
|
|
190
|
-
return HttpResponse.json(protocol.getMetaTypes());
|
|
223
|
+
http.get(`${baseUrl}/meta`, async () => {
|
|
224
|
+
return HttpResponse.json(await protocol.getMetaTypes({}));
|
|
191
225
|
}),
|
|
192
|
-
http.get(`${baseUrl}/meta/:type`, ({ params }) => {
|
|
193
|
-
return HttpResponse.json(protocol.getMetaItems(params.type));
|
|
226
|
+
http.get(`${baseUrl}/meta/:type`, async ({ params }) => {
|
|
227
|
+
return HttpResponse.json(await protocol.getMetaItems({ type: params.type }));
|
|
194
228
|
}),
|
|
195
|
-
http.get(`${baseUrl}/meta/:type/:name`, ({ params }) => {
|
|
229
|
+
http.get(`${baseUrl}/meta/:type/:name`, async ({ params }) => {
|
|
196
230
|
try {
|
|
197
|
-
return HttpResponse.json(protocol.getMetaItem(params.type, params.name));
|
|
231
|
+
return HttpResponse.json(await protocol.getMetaItem({ type: params.type, name: params.name }));
|
|
198
232
|
}
|
|
199
233
|
catch (error) {
|
|
200
234
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -210,7 +244,10 @@ export class MSWPlugin {
|
|
|
210
244
|
queryParams[key] = value;
|
|
211
245
|
});
|
|
212
246
|
const result = await ObjectStackServer.findData(params.object, queryParams);
|
|
213
|
-
return HttpResponse.json(result.data, {
|
|
247
|
+
return HttpResponse.json(result.data, {
|
|
248
|
+
status: result.status,
|
|
249
|
+
headers: { 'Cache-Control': 'no-store' }
|
|
250
|
+
});
|
|
214
251
|
}
|
|
215
252
|
catch (error) {
|
|
216
253
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -220,7 +257,10 @@ export class MSWPlugin {
|
|
|
220
257
|
http.get(`${baseUrl}/data/:object/:id`, async ({ params }) => {
|
|
221
258
|
try {
|
|
222
259
|
const result = await ObjectStackServer.getData(params.object, params.id);
|
|
223
|
-
return HttpResponse.json(result.data, {
|
|
260
|
+
return HttpResponse.json(result.data, {
|
|
261
|
+
status: result.status,
|
|
262
|
+
headers: { 'Cache-Control': 'no-store' }
|
|
263
|
+
});
|
|
224
264
|
}
|
|
225
265
|
catch (error) {
|
|
226
266
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -259,12 +299,102 @@ export class MSWPlugin {
|
|
|
259
299
|
return HttpResponse.json({ error: message }, { status: 400 });
|
|
260
300
|
}
|
|
261
301
|
}),
|
|
302
|
+
// Batch Operations
|
|
303
|
+
http.post(`${baseUrl}/data/:object/batch`, async ({ params, request }) => {
|
|
304
|
+
try {
|
|
305
|
+
const body = await request.json();
|
|
306
|
+
const result = await protocol.batchData({ object: params.object, request: body });
|
|
307
|
+
return HttpResponse.json(result);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
311
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
http.post(`${baseUrl}/data/:object/createMany`, async ({ params, request }) => {
|
|
315
|
+
try {
|
|
316
|
+
const body = await request.json();
|
|
317
|
+
const records = Array.isArray(body) ? body : [];
|
|
318
|
+
const result = await protocol.createManyData({ object: params.object, records });
|
|
319
|
+
return HttpResponse.json(result, { status: 201 });
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
323
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
324
|
+
}
|
|
325
|
+
}),
|
|
326
|
+
http.post(`${baseUrl}/data/:object/updateMany`, async ({ params, request }) => {
|
|
327
|
+
try {
|
|
328
|
+
const body = await request.json();
|
|
329
|
+
const result = await protocol.updateManyData({
|
|
330
|
+
object: params.object,
|
|
331
|
+
records: body?.records || [],
|
|
332
|
+
options: body?.options
|
|
333
|
+
});
|
|
334
|
+
return HttpResponse.json(result);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
338
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
339
|
+
}
|
|
340
|
+
}),
|
|
341
|
+
http.post(`${baseUrl}/data/:object/deleteMany`, async ({ params, request }) => {
|
|
342
|
+
try {
|
|
343
|
+
const body = await request.json();
|
|
344
|
+
const result = await protocol.deleteManyData({
|
|
345
|
+
object: params.object,
|
|
346
|
+
ids: body?.ids || [],
|
|
347
|
+
options: body?.options
|
|
348
|
+
});
|
|
349
|
+
return HttpResponse.json(result);
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
353
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
354
|
+
}
|
|
355
|
+
}),
|
|
356
|
+
// Enhanced Metadata with Cache Support
|
|
357
|
+
http.get(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => {
|
|
358
|
+
try {
|
|
359
|
+
const cacheRequest = {
|
|
360
|
+
ifNoneMatch: request.headers.get('if-none-match') || undefined,
|
|
361
|
+
ifModifiedSince: request.headers.get('if-modified-since') || undefined,
|
|
362
|
+
};
|
|
363
|
+
const result = await protocol.getMetaItemCached({
|
|
364
|
+
type: params.type,
|
|
365
|
+
name: params.name,
|
|
366
|
+
cacheRequest
|
|
367
|
+
});
|
|
368
|
+
if (result.notModified) {
|
|
369
|
+
return new HttpResponse(null, { status: 304 });
|
|
370
|
+
}
|
|
371
|
+
// Build response headers
|
|
372
|
+
const headers = {};
|
|
373
|
+
if (result.etag) {
|
|
374
|
+
const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
|
|
375
|
+
headers['ETag'] = etagValue;
|
|
376
|
+
}
|
|
377
|
+
if (result.lastModified) {
|
|
378
|
+
headers['Last-Modified'] = new Date(result.lastModified).toUTCString();
|
|
379
|
+
}
|
|
380
|
+
if (result.cacheControl) {
|
|
381
|
+
const directives = result.cacheControl.directives.join(', ');
|
|
382
|
+
const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : '';
|
|
383
|
+
headers['Cache-Control'] = directives + maxAge;
|
|
384
|
+
}
|
|
385
|
+
return HttpResponse.json(result.data, { headers });
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
389
|
+
return HttpResponse.json({ error: message }, { status: 404 });
|
|
390
|
+
}
|
|
391
|
+
}),
|
|
262
392
|
// UI Protocol endpoint
|
|
263
|
-
http.get(`${baseUrl}/ui/view/:object`, ({ params, request }) => {
|
|
393
|
+
http.get(`${baseUrl}/ui/view/:object`, async ({ params, request }) => {
|
|
264
394
|
try {
|
|
265
395
|
const url = new URL(request.url);
|
|
266
396
|
const viewType = url.searchParams.get('type') || 'list';
|
|
267
|
-
const view = protocol.getUiView(params.object, viewType);
|
|
397
|
+
const view = await protocol.getUiView({ object: params.object, type: viewType });
|
|
268
398
|
return HttpResponse.json(view);
|
|
269
399
|
}
|
|
270
400
|
catch (error) {
|
|
@@ -272,25 +402,129 @@ export class MSWPlugin {
|
|
|
272
402
|
return HttpResponse.json({ error: message }, { status: 404 });
|
|
273
403
|
}
|
|
274
404
|
}),
|
|
405
|
+
// View Storage Operations
|
|
406
|
+
http.post(`${baseUrl}/ui/views`, async ({ request }) => {
|
|
407
|
+
try {
|
|
408
|
+
const body = await request.json();
|
|
409
|
+
const result = await protocol.createView(body);
|
|
410
|
+
if (result.success) {
|
|
411
|
+
return HttpResponse.json(result, { status: 201 });
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
return HttpResponse.json(result, { status: 400 });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
419
|
+
return HttpResponse.json({ success: false, error: { code: 'internal_error', message } }, { status: 500 });
|
|
420
|
+
}
|
|
421
|
+
}),
|
|
422
|
+
http.get(`${baseUrl}/ui/views/:id`, async ({ params }) => {
|
|
423
|
+
try {
|
|
424
|
+
const result = await protocol.getView({ id: params.id });
|
|
425
|
+
if (result.success) {
|
|
426
|
+
return HttpResponse.json(result);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
return HttpResponse.json(result, { status: 404 });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
434
|
+
return HttpResponse.json({ success: false, error: { code: 'internal_error', message } }, { status: 500 });
|
|
435
|
+
}
|
|
436
|
+
}),
|
|
437
|
+
http.get(`${baseUrl}/ui/views`, async ({ request }) => {
|
|
438
|
+
try {
|
|
439
|
+
const url = new URL(request.url);
|
|
440
|
+
const queryRequest = {};
|
|
441
|
+
if (url.searchParams.get('object'))
|
|
442
|
+
queryRequest.object = url.searchParams.get('object');
|
|
443
|
+
if (url.searchParams.get('type'))
|
|
444
|
+
queryRequest.type = url.searchParams.get('type');
|
|
445
|
+
if (url.searchParams.get('visibility'))
|
|
446
|
+
queryRequest.visibility = url.searchParams.get('visibility');
|
|
447
|
+
if (url.searchParams.get('createdBy'))
|
|
448
|
+
queryRequest.createdBy = url.searchParams.get('createdBy');
|
|
449
|
+
if (url.searchParams.get('isDefault'))
|
|
450
|
+
queryRequest.isDefault = url.searchParams.get('isDefault') === 'true';
|
|
451
|
+
// Parse numeric parameters with validation
|
|
452
|
+
const limitParam = url.searchParams.get('limit');
|
|
453
|
+
const offsetParam = url.searchParams.get('offset');
|
|
454
|
+
if (limitParam) {
|
|
455
|
+
const limit = parseInt(limitParam, 10);
|
|
456
|
+
if (!isNaN(limit) && limit > 0)
|
|
457
|
+
queryRequest.limit = limit;
|
|
458
|
+
}
|
|
459
|
+
if (offsetParam) {
|
|
460
|
+
const offset = parseInt(offsetParam, 10);
|
|
461
|
+
if (!isNaN(offset) && offset >= 0)
|
|
462
|
+
queryRequest.offset = offset;
|
|
463
|
+
}
|
|
464
|
+
const result = await protocol.listViews(queryRequest);
|
|
465
|
+
return HttpResponse.json(result);
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
469
|
+
return HttpResponse.json({ success: false, error: { code: 'internal_error', message } }, { status: 500 });
|
|
470
|
+
}
|
|
471
|
+
}),
|
|
472
|
+
http.patch(`${baseUrl}/ui/views/:id`, async ({ params, request }) => {
|
|
473
|
+
try {
|
|
474
|
+
const body = await request.json();
|
|
475
|
+
// Merge body with id parameter, ensuring body is an object
|
|
476
|
+
const updateData = (typeof body === 'object' && body !== null)
|
|
477
|
+
? { ...body, id: params.id }
|
|
478
|
+
: { id: params.id };
|
|
479
|
+
const result = await protocol.updateView(updateData);
|
|
480
|
+
if (result.success) {
|
|
481
|
+
return HttpResponse.json(result);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const statusCode = result.error?.code === 'resource_not_found' ? 404 : 400;
|
|
485
|
+
return HttpResponse.json(result, { status: statusCode });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
490
|
+
return HttpResponse.json({ success: false, error: { code: 'internal_error', message } }, { status: 500 });
|
|
491
|
+
}
|
|
492
|
+
}),
|
|
493
|
+
http.delete(`${baseUrl}/ui/views/:id`, async ({ params }) => {
|
|
494
|
+
try {
|
|
495
|
+
const result = await protocol.deleteView({ id: params.id });
|
|
496
|
+
if (result.success) {
|
|
497
|
+
return HttpResponse.json(result);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
return HttpResponse.json(result, { status: 404 });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
505
|
+
return HttpResponse.json({ success: false, error: { code: 'internal_error', message } }, { status: 500 });
|
|
506
|
+
}
|
|
507
|
+
}),
|
|
275
508
|
// Add custom handlers
|
|
276
509
|
...(this.options.customHandlers || [])
|
|
277
510
|
];
|
|
278
|
-
|
|
511
|
+
ctx.logger.info('MSW request handlers installed', { count: this.handlers.length, baseUrl });
|
|
279
512
|
}
|
|
280
513
|
/**
|
|
281
514
|
* Start the MSW worker
|
|
282
515
|
*/
|
|
283
|
-
async startWorker() {
|
|
516
|
+
async startWorker(ctx) {
|
|
284
517
|
if (this.options.enableBrowser && typeof window !== 'undefined') {
|
|
285
518
|
// Browser environment
|
|
519
|
+
ctx.logger.debug('Starting MSW in browser mode');
|
|
286
520
|
this.worker = setupWorker(...this.handlers);
|
|
287
521
|
await this.worker.start({
|
|
288
522
|
onUnhandledRequest: 'bypass',
|
|
289
523
|
});
|
|
290
|
-
|
|
524
|
+
ctx.logger.info('MSW started in browser mode');
|
|
291
525
|
}
|
|
292
526
|
else {
|
|
293
|
-
|
|
527
|
+
ctx.logger.debug('MSW browser mode disabled or not in browser environment');
|
|
294
528
|
}
|
|
295
529
|
}
|
|
296
530
|
/**
|
|
@@ -299,7 +533,7 @@ export class MSWPlugin {
|
|
|
299
533
|
async stopWorker() {
|
|
300
534
|
if (this.worker) {
|
|
301
535
|
this.worker.stop();
|
|
302
|
-
console.log(
|
|
536
|
+
console.log('[MSWPlugin] Stopped MSW worker');
|
|
303
537
|
}
|
|
304
538
|
}
|
|
305
539
|
/**
|