Skip to content

Commit a9f11ca

Browse files
committed
feat(http-dispatcher): add support for analytics and hub requests with enhanced metadata handling
1 parent ef78825 commit a9f11ca

1 file changed

Lines changed: 111 additions & 8 deletions

File tree

packages/runtime/src/http-dispatcher.ts

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)