@objectstack/plugin-msw 0.9.2 → 1.0.1
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 +27 -0
- package/dist/msw-plugin.d.ts +123 -5
- package/dist/msw-plugin.js +218 -12
- package/package.json +9 -8
- package/src/msw-plugin.ts +235 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @objectstack/plugin-msw
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @objectstack/runtime@1.0.1
|
|
9
|
+
- @objectstack/spec@1.0.1
|
|
10
|
+
- @objectstack/types@1.0.1
|
|
11
|
+
- @objectstack/objectql@1.0.1
|
|
12
|
+
|
|
13
|
+
## 1.0.0
|
|
14
|
+
|
|
15
|
+
### Major Changes
|
|
16
|
+
|
|
17
|
+
- Major version release for ObjectStack Protocol v1.0.
|
|
18
|
+
- Stabilized Protocol Definitions
|
|
19
|
+
- Enhanced Runtime Plugin Support
|
|
20
|
+
- Fixed Type Compliance across Monorepo
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies
|
|
25
|
+
- @objectstack/spec@1.0.0
|
|
26
|
+
- @objectstack/runtime@1.0.0
|
|
27
|
+
- @objectstack/objectql@1.0.0
|
|
28
|
+
- @objectstack/types@1.0.0
|
|
29
|
+
|
|
3
30
|
## 0.9.2
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/msw-plugin.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export declare class ObjectStackServer {
|
|
|
38
38
|
status: number;
|
|
39
39
|
data: {
|
|
40
40
|
object: string;
|
|
41
|
-
id: string;
|
|
42
41
|
record: Record<string, any>;
|
|
42
|
+
id: string;
|
|
43
43
|
};
|
|
44
44
|
} | {
|
|
45
45
|
status: number;
|
|
@@ -51,8 +51,8 @@ export declare class ObjectStackServer {
|
|
|
51
51
|
status: number;
|
|
52
52
|
data: {
|
|
53
53
|
object: string;
|
|
54
|
-
id: string;
|
|
55
54
|
record: Record<string, any>;
|
|
55
|
+
id: string;
|
|
56
56
|
};
|
|
57
57
|
} | {
|
|
58
58
|
status: number;
|
|
@@ -64,8 +64,8 @@ export declare class ObjectStackServer {
|
|
|
64
64
|
status: number;
|
|
65
65
|
data: {
|
|
66
66
|
object: string;
|
|
67
|
-
id: string;
|
|
68
67
|
record: Record<string, any>;
|
|
68
|
+
id: string;
|
|
69
69
|
};
|
|
70
70
|
} | {
|
|
71
71
|
status: number;
|
|
@@ -86,12 +86,118 @@ export declare class ObjectStackServer {
|
|
|
86
86
|
error: string;
|
|
87
87
|
};
|
|
88
88
|
}>;
|
|
89
|
+
static analyticsQuery(request: any): Promise<{
|
|
90
|
+
status: number;
|
|
91
|
+
data: {
|
|
92
|
+
data: {
|
|
93
|
+
fields: {
|
|
94
|
+
type: string;
|
|
95
|
+
name: string;
|
|
96
|
+
}[];
|
|
97
|
+
rows: Record<string, any>[];
|
|
98
|
+
sql?: string | undefined;
|
|
99
|
+
};
|
|
100
|
+
success: boolean;
|
|
101
|
+
error?: {
|
|
102
|
+
message: string;
|
|
103
|
+
code: string;
|
|
104
|
+
category?: string | undefined;
|
|
105
|
+
requestId?: string | undefined;
|
|
106
|
+
details?: any;
|
|
107
|
+
} | undefined;
|
|
108
|
+
meta?: {
|
|
109
|
+
timestamp: string;
|
|
110
|
+
duration?: number | undefined;
|
|
111
|
+
requestId?: string | undefined;
|
|
112
|
+
traceId?: string | undefined;
|
|
113
|
+
} | undefined;
|
|
114
|
+
};
|
|
115
|
+
} | {
|
|
116
|
+
status: number;
|
|
117
|
+
data: {
|
|
118
|
+
error: string;
|
|
119
|
+
};
|
|
120
|
+
}>;
|
|
121
|
+
static getAnalyticsMeta(request: any): Promise<{
|
|
122
|
+
status: number;
|
|
123
|
+
data: {
|
|
124
|
+
data: {
|
|
125
|
+
cubes: {
|
|
126
|
+
dimensions: Record<string, {
|
|
127
|
+
type: "string" | "number" | "boolean" | "time" | "geo";
|
|
128
|
+
label: string;
|
|
129
|
+
name: string;
|
|
130
|
+
sql: string;
|
|
131
|
+
description?: string | undefined;
|
|
132
|
+
granularities?: ("second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year")[] | undefined;
|
|
133
|
+
}>;
|
|
134
|
+
name: string;
|
|
135
|
+
sql: string;
|
|
136
|
+
measures: Record<string, {
|
|
137
|
+
type: "string" | "number" | "boolean" | "count" | "sum" | "avg" | "min" | "max" | "count_distinct";
|
|
138
|
+
label: string;
|
|
139
|
+
name: string;
|
|
140
|
+
sql: string;
|
|
141
|
+
description?: string | undefined;
|
|
142
|
+
format?: string | undefined;
|
|
143
|
+
filters?: {
|
|
144
|
+
sql: string;
|
|
145
|
+
}[] | undefined;
|
|
146
|
+
}>;
|
|
147
|
+
public: boolean;
|
|
148
|
+
joins?: Record<string, {
|
|
149
|
+
name: string;
|
|
150
|
+
sql: string;
|
|
151
|
+
relationship: "one_to_one" | "one_to_many" | "many_to_one";
|
|
152
|
+
}> | undefined;
|
|
153
|
+
description?: string | undefined;
|
|
154
|
+
title?: string | undefined;
|
|
155
|
+
refreshKey?: {
|
|
156
|
+
every?: string | undefined;
|
|
157
|
+
sql?: string | undefined;
|
|
158
|
+
} | undefined;
|
|
159
|
+
}[];
|
|
160
|
+
};
|
|
161
|
+
success: boolean;
|
|
162
|
+
error?: {
|
|
163
|
+
message: string;
|
|
164
|
+
code: string;
|
|
165
|
+
category?: string | undefined;
|
|
166
|
+
requestId?: string | undefined;
|
|
167
|
+
details?: any;
|
|
168
|
+
} | undefined;
|
|
169
|
+
meta?: {
|
|
170
|
+
timestamp: string;
|
|
171
|
+
duration?: number | undefined;
|
|
172
|
+
requestId?: string | undefined;
|
|
173
|
+
traceId?: string | undefined;
|
|
174
|
+
} | undefined;
|
|
175
|
+
};
|
|
176
|
+
} | {
|
|
177
|
+
status: number;
|
|
178
|
+
data: {
|
|
179
|
+
error: string;
|
|
180
|
+
};
|
|
181
|
+
}>;
|
|
182
|
+
static triggerAutomation(request: any): Promise<{
|
|
183
|
+
status: number;
|
|
184
|
+
data: {
|
|
185
|
+
success: boolean;
|
|
186
|
+
result?: any;
|
|
187
|
+
jobId?: string | undefined;
|
|
188
|
+
};
|
|
189
|
+
} | {
|
|
190
|
+
status: number;
|
|
191
|
+
data: {
|
|
192
|
+
error: string;
|
|
193
|
+
};
|
|
194
|
+
}>;
|
|
89
195
|
static getUser(id: string): Promise<{
|
|
90
196
|
status: number;
|
|
91
197
|
data: {
|
|
92
198
|
object: string;
|
|
93
|
-
id: string;
|
|
94
199
|
record: Record<string, any>;
|
|
200
|
+
id: string;
|
|
95
201
|
};
|
|
96
202
|
} | {
|
|
97
203
|
status: number;
|
|
@@ -103,8 +209,20 @@ export declare class ObjectStackServer {
|
|
|
103
209
|
status: number;
|
|
104
210
|
data: {
|
|
105
211
|
object: string;
|
|
106
|
-
id: string;
|
|
107
212
|
record: Record<string, any>;
|
|
213
|
+
id: string;
|
|
214
|
+
};
|
|
215
|
+
} | {
|
|
216
|
+
status: number;
|
|
217
|
+
data: {
|
|
218
|
+
error: string;
|
|
219
|
+
};
|
|
220
|
+
}>;
|
|
221
|
+
static saveMetaItem(type: string, name: string, item: any): Promise<{
|
|
222
|
+
status: number;
|
|
223
|
+
data: {
|
|
224
|
+
success: boolean;
|
|
225
|
+
message?: string | undefined;
|
|
108
226
|
};
|
|
109
227
|
} | {
|
|
110
228
|
status: number;
|
package/dist/msw-plugin.js
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
|
-
import { http, HttpResponse
|
|
1
|
+
import { http, HttpResponse } from 'msw';
|
|
2
2
|
import { setupWorker } from 'msw/browser';
|
|
3
|
+
// import { IDataEngine } from '@objectstack/core';
|
|
4
|
+
// Helper for parsing query parameters
|
|
5
|
+
function parseQueryParams(url) {
|
|
6
|
+
const params = {};
|
|
7
|
+
const keys = Array.from(new Set(url.searchParams.keys()));
|
|
8
|
+
for (const key of keys) {
|
|
9
|
+
const values = url.searchParams.getAll(key);
|
|
10
|
+
// If single value, use it directly. If multiple, keep as array.
|
|
11
|
+
const rawValue = values.length === 1 ? values[0] : values;
|
|
12
|
+
// Helper to parse individual value
|
|
13
|
+
const parseValue = (val) => {
|
|
14
|
+
if (val === 'true')
|
|
15
|
+
return true;
|
|
16
|
+
if (val === 'false')
|
|
17
|
+
return false;
|
|
18
|
+
if (val === 'null')
|
|
19
|
+
return null;
|
|
20
|
+
if (val === 'undefined')
|
|
21
|
+
return undefined;
|
|
22
|
+
// Try number (integers only or floats)
|
|
23
|
+
// Safety check: Don't convert if it loses information (like leading zeros)
|
|
24
|
+
const num = Number(val);
|
|
25
|
+
if (!isNaN(num) && val.trim() !== '' && String(num) === val) {
|
|
26
|
+
return num;
|
|
27
|
+
}
|
|
28
|
+
// Try JSON
|
|
29
|
+
if ((val.startsWith('{') && val.endsWith('}')) || (val.startsWith('[') && val.endsWith(']'))) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(val);
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
};
|
|
37
|
+
if (Array.isArray(rawValue)) {
|
|
38
|
+
params[key] = rawValue.map(parseValue);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
params[key] = parseValue(rawValue);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return params;
|
|
45
|
+
}
|
|
46
|
+
// Helper to normalize flat parameters into 'where' clause
|
|
47
|
+
function normalizeQuery(params) {
|
|
48
|
+
// If 'where' is already present, trust it
|
|
49
|
+
if (params.where)
|
|
50
|
+
return params;
|
|
51
|
+
const reserved = ['select', 'order', 'orderBy', 'sort', 'limit', 'skip', 'offset', 'top', 'page', 'pageSize', 'count'];
|
|
52
|
+
const where = {};
|
|
53
|
+
let hasWhere = false;
|
|
54
|
+
for (const key in params) {
|
|
55
|
+
if (!reserved.includes(key)) {
|
|
56
|
+
where[key] = params[key];
|
|
57
|
+
hasWhere = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasWhere) {
|
|
61
|
+
// Keep original params but add where.
|
|
62
|
+
// This allows protocols that look at root properties to still work,
|
|
63
|
+
// while providing 'where' for strict drivers.
|
|
64
|
+
return { ...params, where };
|
|
65
|
+
}
|
|
66
|
+
return params;
|
|
67
|
+
}
|
|
3
68
|
/**
|
|
4
69
|
* ObjectStack Server Mock - Provides mock database functionality
|
|
5
70
|
*/
|
|
@@ -113,6 +178,72 @@ export class ObjectStackServer {
|
|
|
113
178
|
};
|
|
114
179
|
}
|
|
115
180
|
}
|
|
181
|
+
static async analyticsQuery(request) {
|
|
182
|
+
if (!this.protocol) {
|
|
183
|
+
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
184
|
+
}
|
|
185
|
+
this.logger?.debug?.('MSW: Executing analytics query', { request });
|
|
186
|
+
try {
|
|
187
|
+
const result = await this.protocol.analyticsQuery(request);
|
|
188
|
+
this.logger?.debug?.('MSW: Analytics query completed', { result });
|
|
189
|
+
return {
|
|
190
|
+
status: 200,
|
|
191
|
+
data: result
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
this.logger?.error?.('MSW: Analytics query failed', error);
|
|
196
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
197
|
+
return {
|
|
198
|
+
status: 400,
|
|
199
|
+
data: { error: message }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
static async getAnalyticsMeta(request) {
|
|
204
|
+
if (!this.protocol) {
|
|
205
|
+
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
206
|
+
}
|
|
207
|
+
this.logger?.debug?.('MSW: Getting analytics metadata', { request });
|
|
208
|
+
try {
|
|
209
|
+
const result = await this.protocol.getAnalyticsMeta(request);
|
|
210
|
+
this.logger?.debug?.('MSW: Analytics metadata retrieved', { result });
|
|
211
|
+
return {
|
|
212
|
+
status: 200,
|
|
213
|
+
data: result
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.logger?.error?.('MSW: Analytics metadata failed', error);
|
|
218
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
219
|
+
return {
|
|
220
|
+
status: 400,
|
|
221
|
+
data: { error: message }
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
static async triggerAutomation(request) {
|
|
226
|
+
if (!this.protocol) {
|
|
227
|
+
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
228
|
+
}
|
|
229
|
+
this.logger?.debug?.('MSW: Triggering automation', { request });
|
|
230
|
+
try {
|
|
231
|
+
const result = await this.protocol.triggerAutomation(request);
|
|
232
|
+
this.logger?.info?.('MSW: Automation triggered', { result });
|
|
233
|
+
return {
|
|
234
|
+
status: 200,
|
|
235
|
+
data: result
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
this.logger?.error?.('MSW: Automation trigger failed', error);
|
|
240
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
241
|
+
return {
|
|
242
|
+
status: 400,
|
|
243
|
+
data: { error: message }
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
116
247
|
// Legacy method names for compatibility
|
|
117
248
|
static async getUser(id) {
|
|
118
249
|
return this.getData('user', id);
|
|
@@ -120,6 +251,32 @@ export class ObjectStackServer {
|
|
|
120
251
|
static async createUser(data) {
|
|
121
252
|
return this.createData('user', data);
|
|
122
253
|
}
|
|
254
|
+
static async saveMetaItem(type, name, item) {
|
|
255
|
+
if (!this.protocol) {
|
|
256
|
+
throw new Error('ObjectStackServer not initialized. Call ObjectStackServer.init() first.');
|
|
257
|
+
}
|
|
258
|
+
this.logger?.debug?.('MSW: Saving metadata', { type, name });
|
|
259
|
+
try {
|
|
260
|
+
// Check if protocol supports saveMetaItem (it might be added recently)
|
|
261
|
+
if (!this.protocol.saveMetaItem) {
|
|
262
|
+
throw new Error('Protocol does not support saveMetaItem');
|
|
263
|
+
}
|
|
264
|
+
const result = await this.protocol.saveMetaItem({ type, name, item });
|
|
265
|
+
this.logger?.info?.('MSW: Metadata saved', { type, name });
|
|
266
|
+
return {
|
|
267
|
+
status: 200,
|
|
268
|
+
data: result
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
this.logger?.error?.('MSW: Save metadata failed', error, { type, name });
|
|
273
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
274
|
+
return {
|
|
275
|
+
status: 400,
|
|
276
|
+
data: { error: message }
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
123
280
|
}
|
|
124
281
|
ObjectStackServer.protocol = null;
|
|
125
282
|
ObjectStackServer.logger = null;
|
|
@@ -233,9 +390,6 @@ export class MSWPlugin {
|
|
|
233
390
|
const baseUrl = this.options.baseUrl || '/api/v1';
|
|
234
391
|
// Define standard ObjectStack API handlers
|
|
235
392
|
this.handlers = [
|
|
236
|
-
// Passthrough for external resources
|
|
237
|
-
http.get('https://fonts.googleapis.com/*', () => passthrough()),
|
|
238
|
-
http.get('https://fonts.gstatic.com/*', () => passthrough()),
|
|
239
393
|
// Discovery endpoint
|
|
240
394
|
http.get(`${baseUrl}`, async () => {
|
|
241
395
|
const discovery = await protocol.getDiscovery({});
|
|
@@ -250,11 +404,15 @@ export class MSWPlugin {
|
|
|
250
404
|
});
|
|
251
405
|
}),
|
|
252
406
|
// Meta endpoints
|
|
253
|
-
http.get(`${baseUrl}/meta`, async () => {
|
|
254
|
-
|
|
407
|
+
http.get(`${baseUrl}/meta`, async ({ request }) => {
|
|
408
|
+
const url = new URL(request.url);
|
|
409
|
+
const query = parseQueryParams(url);
|
|
410
|
+
return HttpResponse.json(await protocol.getMetaTypes({ query }));
|
|
255
411
|
}),
|
|
256
|
-
http.get(`${baseUrl}/meta/:type`, async ({ params }) => {
|
|
257
|
-
|
|
412
|
+
http.get(`${baseUrl}/meta/:type`, async ({ params, request }) => {
|
|
413
|
+
const url = new URL(request.url);
|
|
414
|
+
const query = parseQueryParams(url);
|
|
415
|
+
return HttpResponse.json(await protocol.getMetaItems({ type: params.type, query }));
|
|
258
416
|
}),
|
|
259
417
|
http.get(`${baseUrl}/meta/:type/:name`, async ({ params }) => {
|
|
260
418
|
try {
|
|
@@ -265,14 +423,26 @@ export class MSWPlugin {
|
|
|
265
423
|
return HttpResponse.json({ error: message }, { status: 404 });
|
|
266
424
|
}
|
|
267
425
|
}),
|
|
426
|
+
// Save Metadata
|
|
427
|
+
http.put(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => {
|
|
428
|
+
try {
|
|
429
|
+
const body = await request.json();
|
|
430
|
+
const result = await ObjectStackServer.saveMetaItem(params.type, params.name, body);
|
|
431
|
+
return HttpResponse.json(result.data, { status: result.status });
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
435
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
436
|
+
}
|
|
437
|
+
}),
|
|
268
438
|
// Data endpoints
|
|
269
439
|
http.get(`${baseUrl}/data/:object`, async ({ params, request }) => {
|
|
270
440
|
try {
|
|
271
441
|
const url = new URL(request.url);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
442
|
+
// Use helper to parse properly (handle multiple values, JSON strings, numbers)
|
|
443
|
+
const rawParams = parseQueryParams(url);
|
|
444
|
+
// Normalize to standard query object (move flats to 'where')
|
|
445
|
+
const queryParams = normalizeQuery(rawParams);
|
|
276
446
|
const result = await ObjectStackServer.findData(params.object, queryParams);
|
|
277
447
|
return HttpResponse.json(result.data, {
|
|
278
448
|
status: result.status,
|
|
@@ -432,6 +602,42 @@ export class MSWPlugin {
|
|
|
432
602
|
return HttpResponse.json({ error: message }, { status: 404 });
|
|
433
603
|
}
|
|
434
604
|
}),
|
|
605
|
+
// Analytics Operations
|
|
606
|
+
http.post(`${baseUrl}/analytics/query`, async ({ request }) => {
|
|
607
|
+
try {
|
|
608
|
+
const body = await request.json();
|
|
609
|
+
const result = await ObjectStackServer.analyticsQuery(body);
|
|
610
|
+
return HttpResponse.json(result.data, { status: result.status });
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
614
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
615
|
+
}
|
|
616
|
+
}),
|
|
617
|
+
http.get(`${baseUrl}/analytics/meta`, async ({ request }) => {
|
|
618
|
+
try {
|
|
619
|
+
const url = new URL(request.url);
|
|
620
|
+
const query = parseQueryParams(url);
|
|
621
|
+
const result = await ObjectStackServer.getAnalyticsMeta(query);
|
|
622
|
+
return HttpResponse.json(result.data, { status: result.status });
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
626
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
627
|
+
}
|
|
628
|
+
}),
|
|
629
|
+
// Automation Operations
|
|
630
|
+
http.post(`${baseUrl}/automation/trigger`, async ({ request }) => {
|
|
631
|
+
try {
|
|
632
|
+
const body = await request.json();
|
|
633
|
+
const result = await ObjectStackServer.triggerAutomation(body);
|
|
634
|
+
return HttpResponse.json(result.data, { status: result.status });
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
638
|
+
return HttpResponse.json({ error: message }, { status: 400 });
|
|
639
|
+
}
|
|
640
|
+
}),
|
|
435
641
|
// Add custom handlers
|
|
436
642
|
...(this.options.customHandlers || [])
|
|
437
643
|
];
|
package/package.json
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/plugin-msw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MSW (Mock Service Worker) Plugin for ObjectStack Runtime",
|
|
5
|
+
"license": "Apache-2.0",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@objectstack/runtime": "^1.0.1"
|
|
10
|
+
},
|
|
7
11
|
"dependencies": {
|
|
8
12
|
"msw": "^2.0.0",
|
|
9
|
-
"@objectstack/spec": "0.
|
|
10
|
-
"@objectstack/types": "0.
|
|
11
|
-
"@objectstack/objectql": "0.
|
|
13
|
+
"@objectstack/spec": "1.0.1",
|
|
14
|
+
"@objectstack/types": "1.0.1",
|
|
15
|
+
"@objectstack/objectql": "1.0.1"
|
|
12
16
|
},
|
|
13
17
|
"devDependencies": {
|
|
14
18
|
"@types/node": "^25.1.0",
|
|
15
19
|
"typescript": "^5.0.0",
|
|
16
20
|
"vitest": "^4.0.18",
|
|
17
|
-
"@objectstack/runtime": "0.
|
|
18
|
-
},
|
|
19
|
-
"peerDependencies": {
|
|
20
|
-
"@objectstack/runtime": "^0.9.2"
|
|
21
|
+
"@objectstack/runtime": "1.0.1"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsc",
|
package/src/msw-plugin.ts
CHANGED
|
@@ -10,6 +10,77 @@ import {
|
|
|
10
10
|
import { ObjectStackProtocol } from '@objectstack/spec/api';
|
|
11
11
|
// import { IDataEngine } from '@objectstack/core';
|
|
12
12
|
|
|
13
|
+
// Helper for parsing query parameters
|
|
14
|
+
function parseQueryParams(url: URL): Record<string, any> {
|
|
15
|
+
const params: Record<string, any> = {};
|
|
16
|
+
const keys = Array.from(new Set(url.searchParams.keys()));
|
|
17
|
+
|
|
18
|
+
for (const key of keys) {
|
|
19
|
+
const values = url.searchParams.getAll(key);
|
|
20
|
+
// If single value, use it directly. If multiple, keep as array.
|
|
21
|
+
const rawValue = values.length === 1 ? values[0] : values;
|
|
22
|
+
|
|
23
|
+
// Helper to parse individual value
|
|
24
|
+
const parseValue = (val: string) => {
|
|
25
|
+
if (val === 'true') return true;
|
|
26
|
+
if (val === 'false') return false;
|
|
27
|
+
if (val === 'null') return null;
|
|
28
|
+
if (val === 'undefined') return undefined;
|
|
29
|
+
|
|
30
|
+
// Try number (integers only or floats)
|
|
31
|
+
// Safety check: Don't convert if it loses information (like leading zeros)
|
|
32
|
+
const num = Number(val);
|
|
33
|
+
if (!isNaN(num) && val.trim() !== '' && String(num) === val) {
|
|
34
|
+
return num;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Try JSON
|
|
38
|
+
if ((val.startsWith('{') && val.endsWith('}')) || (val.startsWith('[') && val.endsWith(']'))) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(val);
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return val;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(rawValue)) {
|
|
48
|
+
params[key] = rawValue.map(parseValue);
|
|
49
|
+
} else {
|
|
50
|
+
params[key] = parseValue(rawValue as string);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return params;
|
|
55
|
+
}
|
|
56
|
+
|
|
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
|
+
|
|
13
84
|
export interface MSWPluginOptions {
|
|
14
85
|
/**
|
|
15
86
|
* Enable MSW in the browser environment
|
|
@@ -155,6 +226,75 @@ export class ObjectStackServer {
|
|
|
155
226
|
}
|
|
156
227
|
}
|
|
157
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
|
+
|
|
158
298
|
// Legacy method names for compatibility
|
|
159
299
|
static async getUser(id: string) {
|
|
160
300
|
return this.getData('user', id);
|
|
@@ -163,6 +303,33 @@ export class ObjectStackServer {
|
|
|
163
303
|
static async createUser(data: any) {
|
|
164
304
|
return this.createData('user', data);
|
|
165
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
|
+
}
|
|
166
333
|
}
|
|
167
334
|
|
|
168
335
|
/**
|
|
@@ -293,10 +460,6 @@ export class MSWPlugin implements Plugin {
|
|
|
293
460
|
|
|
294
461
|
// Define standard ObjectStack API handlers
|
|
295
462
|
this.handlers = [
|
|
296
|
-
// Passthrough for external resources
|
|
297
|
-
http.get('https://fonts.googleapis.com/*', () => passthrough()),
|
|
298
|
-
http.get('https://fonts.gstatic.com/*', () => passthrough()),
|
|
299
|
-
|
|
300
463
|
// Discovery endpoint
|
|
301
464
|
http.get(`${baseUrl}`, async () => {
|
|
302
465
|
const discovery = await protocol.getDiscovery({});
|
|
@@ -312,18 +475,22 @@ export class MSWPlugin implements Plugin {
|
|
|
312
475
|
}),
|
|
313
476
|
|
|
314
477
|
// Meta endpoints
|
|
315
|
-
http.get(`${baseUrl}/meta`, async () => {
|
|
316
|
-
|
|
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 }));
|
|
317
482
|
}),
|
|
318
483
|
|
|
319
|
-
http.get(`${baseUrl}/meta/:type`, async ({ params }) => {
|
|
320
|
-
|
|
484
|
+
http.get(`${baseUrl}/meta/:type`, async ({ params, request }) => {
|
|
485
|
+
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));
|
|
321
488
|
}),
|
|
322
489
|
|
|
323
490
|
http.get(`${baseUrl}/meta/:type/:name`, async ({ params }) => {
|
|
324
491
|
try {
|
|
325
492
|
return HttpResponse.json(
|
|
326
|
-
await protocol.getMetaItem({ type: params.type as string, name: params.name as string })
|
|
493
|
+
await protocol.getMetaItem({ type: params.type as string, name: params.name as string } as any)
|
|
327
494
|
);
|
|
328
495
|
} catch (error) {
|
|
329
496
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -331,14 +498,32 @@ export class MSWPlugin implements Plugin {
|
|
|
331
498
|
}
|
|
332
499
|
}),
|
|
333
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
|
+
|
|
334
517
|
// Data endpoints
|
|
335
518
|
http.get(`${baseUrl}/data/:object`, async ({ params, request }) => {
|
|
336
519
|
try {
|
|
337
520
|
const url = new URL(request.url);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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);
|
|
342
527
|
|
|
343
528
|
const result = await ObjectStackServer.findData(
|
|
344
529
|
params.object as string,
|
|
@@ -478,7 +663,7 @@ export class MSWPlugin implements Plugin {
|
|
|
478
663
|
type: params.type as string,
|
|
479
664
|
name: params.name as string,
|
|
480
665
|
cacheRequest
|
|
481
|
-
});
|
|
666
|
+
} as any);
|
|
482
667
|
|
|
483
668
|
if (result.notModified) {
|
|
484
669
|
return new HttpResponse(null, { status: 304 });
|
|
@@ -519,6 +704,42 @@ export class MSWPlugin implements Plugin {
|
|
|
519
704
|
}
|
|
520
705
|
}),
|
|
521
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
|
+
}
|
|
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
|
+
|
|
522
743
|
// Add custom handlers
|
|
523
744
|
...(this.options.customHandlers || [])
|
|
524
745
|
];
|