@@ -53,6 +53,8 @@ export class HttpDispatcher {
5353 const hasSearch = ! ! services [ 'search' ] ;
5454 const hasWebSockets = ! ! services [ 'realtime' ] ;
5555 const hasFiles = ! ! ( services [ 'file-storage' ] || services [ 'storage' ] ?. supportsFiles ) ;
56+ const hasAnalytics = ! ! services [ 'analytics' ] ;
57+ const hasHub = ! ! services [ 'hub' ] ;
5658
5759 return {
5860 name : 'ObjectOS' ,
@@ -64,13 +66,22 @@ export class HttpDispatcher {
6466 auth : `${ prefix } /auth` ,
6567 graphql : hasGraphQL ? `${ prefix } /graphql` : undefined ,
6668 storage : hasFiles ? `${ prefix } /storage` : undefined ,
69+ analytics : hasAnalytics ? `${ prefix } /analytics` : undefined ,
70+ hub : hasHub ? `${ prefix } /hub` : undefined ,
6771 } ,
6872 features : {
6973 graphql : hasGraphQL ,
7074 search : hasSearch ,
7175 websockets : hasWebSockets ,
7276 files : hasFiles ,
77+ analytics : hasAnalytics ,
78+ hub : hasHub ,
7379 } ,
80+ locale : {
81+ default : 'en' ,
82+ supported : [ 'en' , 'zh-CN' ] ,
83+ timezone : 'UTC'
84+ }
7485 } ;
7586 }
7687
@@ -116,21 +127,66 @@ export class HttpDispatcher {
116127
117128 /**
118129 * Handles Metadata requests
119- * path: sub-path after /metadata/ (e.g. "contacts" or empty)
130+ * Standard: /metadata/:type/:name
131+ * Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
120132 */
121133 async handleMetadata ( path : string , context : HttpProtocolContext ) : Promise < HttpDispatcherResult > {
122134 const broker = this . ensureBroker ( ) ;
123- const cleanPath = path . replace ( / ^ \/ + / , '' ) ;
135+ const parts = path . replace ( / ^ \/ + / , '' ) . split ( '/' ) . filter ( Boolean ) ;
136+
137+ // GET /metadata/types
138+ if ( parts [ 0 ] === 'types' ) {
139+ // This would normally come from a registry service
140+ // For now we mock the types supported by core
141+ return { handled : true , response : this . success ( { types : [ 'objects' , 'apps' , 'plugins' ] } ) } ;
142+ }
143+
144+ // GET /metadata/:type/:name
145+ if ( parts . length === 2 ) {
146+ const [ type , name ] = parts ;
147+ try {
148+ // Try specific calls based on type
149+ if ( type === 'objects' ) {
150+ const data = await broker . call ( 'metadata.getObject' , { objectName : name } , { request : context . request } ) ;
151+ return { handled : true , response : this . success ( data ) } ;
152+ }
153+ // Generic call for other types if supported
154+ const data = await broker . call ( `metadata.get${ this . capitalize ( type . slice ( 0 , - 1 ) ) } ` , { name } , { request : context . request } ) ;
155+ return { handled : true , response : this . success ( data ) } ;
156+ } catch ( e : any ) {
157+ // Fallback: treat first part as object name if only 1 part (handled below)
158+ // But here we are deep in 2 parts. Must be an error.
159+ return { handled : true , response : this . error ( e . message , 404 ) } ;
160+ }
161+ }
124162
125- // GET /metadata
126- if ( ! cleanPath ) {
163+ // GET /metadata/:type (List items of type) OR /metadata/:objectName (Legacy)
164+ if ( parts . length === 1 ) {
165+ const typeOrName = parts [ 0 ] ;
166+
167+ // Heuristic: if it maps to a known type, list it. Else treat as object name.
168+ if ( [ 'objects' , 'apps' , 'plugins' ] . includes ( typeOrName ) ) {
169+ if ( typeOrName === 'objects' ) {
170+ const data = await broker . call ( 'metadata.objects' , { } , { request : context . request } ) ;
171+ return { handled : true , response : this . success ( data ) } ;
172+ }
173+ // Try generic list
174+ const data = await broker . call ( `metadata.${ typeOrName } ` , { } , { request : context . request } ) ;
175+ return { handled : true , response : this . success ( data ) } ;
176+ }
177+
178+ // Legacy: /metadata/:objectName
179+ const data = await broker . call ( 'metadata.getObject' , { objectName : typeOrName } , { request : context . request } ) ;
180+ return { handled : true , response : this . success ( data ) } ;
181+ }
182+
183+ // GET /metadata (List Objects - Default)
184+ if ( parts . length === 0 ) {
127185 const data = await broker . call ( 'metadata.objects' , { } , { request : context . request } ) ;
128186 return { handled : true , response : this . success ( data ) } ;
129187 }
130188
131- // GET /metadata/:objectName
132- const data = await broker . call ( 'metadata.getObject' , { objectName : cleanPath } , { request : context . request } ) ;
133- return { handled : true , response : this . success ( data ) } ;
189+ return { handled : false } ;
134190 }
135191
136192 /**
@@ -160,7 +216,9 @@ export class HttpDispatcher {
160216
161217 // POST /data/:object/batch
162218 if ( action === 'batch' && m === 'POST' ) {
163- const result = await broker . call ( 'data.batch' , { object : objectName , operations : body . operations } , { request : context . request } ) ;
219+ // Spec complaint: forward the whole body { operation, records, options }
220+ // Implementation in Kernel should handle the 'operation' field
221+ const result = await broker . call ( 'data.batch' , { object : objectName , ...body } , { request : context . request } ) ;
164222 return { handled : true , response : this . success ( result ) } ;
165223 }
166224
@@ -204,6 +262,47 @@ export class HttpDispatcher {
204262 return { handled : false } ;
205263 }
206264
265+ /**
266+ * Handles Analytics requests
267+ * path: sub-path after /analytics/
268+ */
269+ async handleAnalytics ( path : string , method : string , body : any , context : HttpProtocolContext ) : Promise < HttpDispatcherResult > {
270+ const analyticsService = this . getService ( 'analytics' ) ;
271+ if ( ! analyticsService ) return { handled : false } ; // 404 handled by caller if unhandled
272+
273+ const m = method . toUpperCase ( ) ;
274+ const subPath = path . replace ( / ^ \/ + / , '' ) ;
275+
276+ // POST /analytics/query
277+ if ( subPath === 'query' && m === 'POST' ) {
278+ const result = await analyticsService . query ( body , { request : context . request } ) ;
279+ return { handled : true , response : this . success ( result ) } ;
280+ }
281+
282+ // GET /analytics/meta
283+ if ( subPath === 'meta' && m === 'GET' ) {
284+ const result = await analyticsService . getMetadata ( { request : context . request } ) ;
285+ return { handled : true , response : this . success ( result ) } ;
286+ }
287+
288+ return { handled : false } ;
289+ }
290+
291+ /**
292+ * Handles Hub requests
293+ * path: sub-path after /hub/
294+ */
295+ async handleHub ( path : string , method : string , body : any , query : any , context : HttpProtocolContext ) : Promise < HttpDispatcherResult > {
296+ const hubService = this . getService ( 'hub' ) ;
297+ if ( ! hubService ) return { handled : false } ;
298+
299+ const m = method . toUpperCase ( ) ;
300+ // Dispatch to hub service methods based on convention or explicit mapping
301+ // This is a placeholder for Hub Protocol implementation
302+
303+ return { handled : false } ;
304+ }
305+
207306 /**
208307 * Handles Storage requests
209308 * path: sub-path after /storage/
@@ -272,4 +371,8 @@ export class HttpDispatcher {
272371 const services = this . getServicesMap ( ) ;
273372 return services [ name ] ;
274373 }
374+
375+ private capitalize ( s : string ) {
376+ return s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) ;
377+ }
275378}
0 commit comments