@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 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
@@ -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;
@@ -1,5 +1,70 @@
1
- import { http, HttpResponse, passthrough } from 'msw';
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
- return HttpResponse.json(await protocol.getMetaTypes({}));
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
- return HttpResponse.json(await protocol.getMetaItems({ type: params.type }));
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
- const queryParams = {};
273
- url.searchParams.forEach((value, key) => {
274
- queryParams[key] = value;
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.9.2",
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.9.2",
10
- "@objectstack/types": "0.9.2",
11
- "@objectstack/objectql": "0.9.2"
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.9.2"
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
- return HttpResponse.json(await protocol.getMetaTypes({}));
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
- return HttpResponse.json(await protocol.getMetaItems({ type: params.type as string }));
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
- const queryParams: Record<string, any> = {};
339
- url.searchParams.forEach((value, key) => {
340
- queryParams[key] = value;
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
  ];