@@ -10,6 +10,77 @@ import {
1010import { 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+
1384export 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