Skip to content

Commit 76dc5c0

Browse files
committed
feat(msw-plugin): enhance query parameter parsing and add analytics endpoints
1 parent 00c50e6 commit 76dc5c0

1 file changed

Lines changed: 155 additions & 12 deletions

File tree

packages/plugins/plugin-msw/src/msw-plugin.ts

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,77 @@ import {
1010
import { ObjectStackProtocol } from '@objectstack/spec/api';
1111
// import { IDataEngine } from '@objectstack/core';
1212

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+
1384
export interface MSWPluginOptions {
1485
/**
1586
* Enable MSW in the browser environment
@@ -155,6 +226,52 @@ export class ObjectStackServer {
155226
}
156227
}
157228

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+
158275
// Legacy method names for compatibility
159276
static async getUser(id: string) {
160277
return this.getData('user', id);
@@ -293,10 +410,6 @@ export class MSWPlugin implements Plugin {
293410

294411
// Define standard ObjectStack API handlers
295412
this.handlers = [
296-
// Passthrough for external resources
297-
http.get('https://fonts.googleapis.com/*', () => passthrough()),
298-
http.get('https://fonts.gstatic.com/*', () => passthrough()),
299-
300413
// Discovery endpoint
301414
http.get(`${baseUrl}`, async () => {
302415
const discovery = await protocol.getDiscovery({});
@@ -312,12 +425,16 @@ export class MSWPlugin implements Plugin {
312425
}),
313426

314427
// Meta endpoints
315-
http.get(`${baseUrl}/meta`, async () => {
316-
return HttpResponse.json(await protocol.getMetaTypes({}));
428+
http.get(`${baseUrl}/meta`, async ({ request }) => {
429+
const url = new URL(request.url);
430+
const query = parseQueryParams(url);
431+
return HttpResponse.json(await protocol.getMetaTypes({ query }));
317432
}),
318433

319-
http.get(`${baseUrl}/meta/:type`, async ({ params }) => {
320-
return HttpResponse.json(await protocol.getMetaItems({ type: params.type as string }));
434+
http.get(`${baseUrl}/meta/:type`, async ({ params, request }) => {
435+
const url = new URL(request.url);
436+
const query = parseQueryParams(url);
437+
return HttpResponse.json(await protocol.getMetaItems({ type: params.type as string, query }));
321438
}),
322439

323440
http.get(`${baseUrl}/meta/:type/:name`, async ({ params }) => {
@@ -335,10 +452,12 @@ export class MSWPlugin implements Plugin {
335452
http.get(`${baseUrl}/data/:object`, async ({ params, request }) => {
336453
try {
337454
const url = new URL(request.url);
338-
const queryParams: Record<string, any> = {};
339-
url.searchParams.forEach((value, key) => {
340-
queryParams[key] = value;
341-
});
455+
456+
// Use helper to parse properly (handle multiple values, JSON strings, numbers)
457+
const rawParams = parseQueryParams(url);
458+
459+
// Normalize to standard query object (move flats to 'where')
460+
const queryParams = normalizeQuery(rawParams);
342461

343462
const result = await ObjectStackServer.findData(
344463
params.object as string,
@@ -519,6 +638,30 @@ export class MSWPlugin implements Plugin {
519638
}
520639
}),
521640

641+
// Analytics Operations
642+
http.post(`${baseUrl}/analytics/query`, async ({ request }) => {
643+
try {
644+
const body = await request.json();
645+
const result = await ObjectStackServer.analyticsQuery(body);
646+
return HttpResponse.json(result.data, { status: result.status });
647+
} catch (error) {
648+
const message = error instanceof Error ? error.message : 'Unknown error';
649+
return HttpResponse.json({ error: message }, { status: 400 });
650+
}
651+
}),
652+
653+
http.get(`${baseUrl}/analytics/meta`, async ({ request }) => {
654+
try {
655+
const url = new URL(request.url);
656+
const query = parseQueryParams(url);
657+
const result = await ObjectStackServer.getAnalyticsMeta(query);
658+
return HttpResponse.json(result.data, { status: result.status });
659+
} catch (error) {
660+
const message = error instanceof Error ? error.message : 'Unknown error';
661+
return HttpResponse.json({ error: message }, { status: 400 });
662+
}
663+
}),
664+
522665
// Add custom handlers
523666
...(this.options.customHandlers || [])
524667
];

0 commit comments

Comments
 (0)