Skip to content

Commit 3dbbdfa

Browse files
committed
feat: Update metadata handling across components to align with new response specifications
1 parent 5730807 commit 3dbbdfa

8 files changed

Lines changed: 85 additions & 50 deletions

File tree

apps/console/src/components/MetadataExplorer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ export function MetadataExplorer({ client, selectedObject, onSelectObject }: Met
2222
// Use plural 'objects' to ensure HttpDispatcher treats it as a list request
2323
// Singular 'object' is interpreted as getObject('object')
2424
const result: any = await client.meta.getItems('objects');
25-
// Support Standard Envelope { success, data } or direct array
25+
// Spec: GetMetaItemsResponse = { type, items: any[] }
2626
let items = [];
27-
if (Array.isArray(result)) {
27+
if (result && Array.isArray(result.items)) {
28+
items = result.items;
29+
} else if (Array.isArray(result)) {
2830
items = result;
29-
} else if (result && result.success && Array.isArray(result.data)) {
30-
items = result.data;
3131
} else if (result && Array.isArray(result.value)) {
3232
items = result.value;
3333
}

apps/console/src/components/ObjectDataForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export function ObjectDataForm({ client, objectApiName, record, onSuccess, onCan
3232
try {
3333
const found: any = await client.meta.getItem('object', objectApiName);
3434
if (mounted && found) {
35-
const resolved = found.data || found;
35+
// Spec: GetMetaItemResponse = { type, name, item }
36+
const resolved = found.item || found;
3637
setDef(resolved);
3738
if (record) {
3839
setFormData({ ...record });

apps/console/src/components/ObjectDataTable.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export function ObjectDataTable({ client, objectApiName, onEdit }: ObjectDataTab
9494
try {
9595
const found: any = await client.meta.getItem('object', objectApiName);
9696
if (mounted && found) {
97-
const def = found.data || found;
97+
// Spec: GetMetaItemResponse = { type, name, item }
98+
const def = found.item || found;
9899
setDef(def);
99100
}
100101
} catch (err) {

apps/console/src/components/TaskForm.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ export function TaskForm({ client, editingTask, onSuccess, onCancel }: TaskFormP
2929
// 'todo_task' should match the object name in Schema
3030
const res = await client.meta.getObject('todo_task');
3131

32-
// In Protocol v1 (protocol.ts), getMetaItem returns { type: 'object', name: 'todo_task', item: { ...fields... } }
33-
// So we need res.item (the schema definition) or res (if it's direct)
34-
const schemaDef = res.data || res.item || res;
32+
// Protocol: getMetaItem returns { type, name, item }
33+
const schemaDef = res.item || res;
3534

3635
setSchema(schemaDef);
3736
} catch (err) {

apps/console/src/components/app-sidebar.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,13 @@ export function AppSidebar({ client, selectedObject, onSelectObject, apps, selec
110110
if (!client) return;
111111
setLoading(true);
112112
try {
113-
// 1. Discover all registered metadata types
114-
const typesRaw: any = await client.meta.getTypes();
115-
// Unwrap { success, data } envelope if present
116-
const typesResult = typesRaw?.data ?? typesRaw;
113+
// 1. Discover all registered metadata types (spec: GetMetaTypesResponse)
114+
const typesResult = await client.meta.getTypes();
117115
let types: string[] = [];
118116
if (typesResult && Array.isArray(typesResult.types)) {
119117
types = typesResult.types;
120118
} else if (Array.isArray(typesResult)) {
121-
types = typesResult;
119+
types = typesResult as any;
122120
}
123121
setMetaTypes(types);
124122

@@ -128,18 +126,15 @@ export function AppSidebar({ client, selectedObject, onSelectObject, apps, selec
128126
.filter(t => !HIDDEN_TYPES.has(t))
129127
.map(async (type) => {
130128
try {
131-
const raw: any = await client.meta.getItems(type);
132-
// Unwrap { success, data } envelope if present
133-
const result = raw?.data ?? raw;
129+
// Spec: GetMetaItemsResponse = { type, items: any[] }
130+
const result = await client.meta.getItems(type);
134131
let items: any[] = [];
135132
if (Array.isArray(result)) {
136-
items = result;
133+
items = result as any;
137134
} else if (result && Array.isArray(result.items)) {
138135
items = result.items;
139-
} else if (result && Array.isArray(result.data)) {
140-
items = result.data;
141-
} else if (result && Array.isArray(result.value)) {
142-
items = result.value;
136+
} else if (result && Array.isArray((result as any).value)) {
137+
items = (result as any).value;
143138
}
144139
return [type, items] as const;
145140
} catch {

apps/console/src/mocks/createKernel.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,22 @@ export async function createKernel(options: KernelOptions) {
5353
const ql = (kernel as any).context?.getService('objectql');
5454

5555
if (service === 'data') {
56+
// All data responses conform to protocol.zod.ts schemas:
57+
// CreateDataResponse = { object, id, record }
58+
// GetDataResponse = { object, id, record }
59+
// FindDataResponse = { object, records, total?, hasMore? }
60+
// UpdateDataResponse = { object, id, record }
61+
// DeleteDataResponse = { object, id, deleted }
5662
if (method === 'create') {
5763
const res = await ql.insert(params.object, params.data);
58-
return { ...params.data, ...res };
64+
const record = { ...params.data, ...res };
65+
return { object: params.object, id: record.id || record._id, record };
5966
}
6067
if (method === 'get') {
6168
let all = await ql.find(params.object);
6269
if (!all) all = [];
6370
const match = all.find((i: any) => i.id === params.id || i._id === params.id);
64-
return match || null;
71+
return match ? { object: params.object, id: params.id, record: match } : null;
6572
}
6673
if (method === 'update') {
6774
if (params.id) {
@@ -87,14 +94,15 @@ export async function createKernel(options: KernelOptions) {
8794
throw err;
8895
}
8996

90-
return { ...existing, ...params.data };
97+
return { object: params.object, id: params.id, record: { ...existing, ...params.data } };
9198
}
9299
return null;
93100
}
94101
if (method === 'delete') {
95102
try {
96103
// ql.delete(object, options) where options.filter is ID
97-
return await ql.delete(params.object, { filter: params.id });
104+
await ql.delete(params.object, { filter: params.id });
105+
return { object: params.object, id: params.id, deleted: true };
98106
} catch (err: any) {
99107
console.warn(`[BrokerShim] delete failed: ${err.message}`);
100108
throw err;
@@ -184,7 +192,8 @@ export async function createKernel(options: KernelOptions) {
184192
}
185193

186194
console.log(`[BrokerShim] find/query(${params.object}) -> count: ${all.length}`);
187-
return { data: all, count: all.length };
195+
// Spec: FindDataResponse = { object, records, total?, hasMore? }
196+
return { object: params.object, records: all, total: all.length };
188197
}
189198
}
190199

@@ -194,13 +203,13 @@ export async function createKernel(options: KernelOptions) {
194203
return { types: SchemaRegistry.getRegisteredTypes() };
195204
}
196205
if (method === 'objects') {
197-
// Try engine first if implemented
206+
// Return spec-compliant GetMetaItemsResponse
198207
let objs = (ql && typeof ql.getObjects === 'function') ? ql.getObjects() : [];
199208

200209
if (!objs || objs.length === 0) {
201210
objs = SchemaRegistry.getAllObjects();
202211
}
203-
return objs;
212+
return { type: 'object', items: objs };
204213
}
205214
if (method === 'getObject' || method === 'getItem') {
206215
// Hack: If no objectName provided, it might be a list request mapped incorrectly

packages/client/src/index.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,18 @@ export interface QueryOptions {
5656
}
5757

5858
export interface PaginatedResult<T = any> {
59-
value: T[];
60-
count: number;
59+
/** @deprecated Use `records` — aligned with FindDataResponseSchema */
60+
value?: T[];
61+
/** Spec-compliant: array of matching records */
62+
records: T[];
63+
/** @deprecated Use `total` — aligned with FindDataResponseSchema */
64+
count?: number;
65+
/** Total number of matching records (if requested) */
66+
total?: number;
67+
/** The object name */
68+
object?: string;
69+
/** Whether more records are available */
70+
hasMore?: boolean;
6171
}
6272

6373
export interface StandardError {
@@ -164,7 +174,7 @@ export class ObjectStackClient {
164174
getTypes: async (): Promise<GetMetaTypesResponse> => {
165175
const route = this.getRoute('metadata');
166176
const res = await this.fetch(`${this.baseUrl}${route}`);
167-
return res.json();
177+
return this.unwrapResponse<GetMetaTypesResponse>(res);
168178
},
169179

170180
/**
@@ -174,7 +184,7 @@ export class ObjectStackClient {
174184
getItems: async (type: string): Promise<GetMetaItemsResponse> => {
175185
const route = this.getRoute('metadata');
176186
const res = await this.fetch(`${this.baseUrl}${route}/${type}`);
177-
return res.json();
187+
return this.unwrapResponse<GetMetaItemsResponse>(res);
178188
},
179189

180190
/**
@@ -185,7 +195,7 @@ export class ObjectStackClient {
185195
getObject: async (name: string) => {
186196
const route = this.getRoute('metadata');
187197
const res = await this.fetch(`${this.baseUrl}${route}/object/${name}`);
188-
return res.json();
198+
return this.unwrapResponse(res);
189199
},
190200

191201
/**
@@ -196,7 +206,7 @@ export class ObjectStackClient {
196206
getItem: async (type: string, name: string) => {
197207
const route = this.getRoute('metadata');
198208
const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
199-
return res.json();
209+
return this.unwrapResponse(res);
200210
},
201211

202212
/**
@@ -211,7 +221,7 @@ export class ObjectStackClient {
211221
method: 'PUT',
212222
body: JSON.stringify(item)
213223
});
214-
return res.json();
224+
return this.unwrapResponse(res);
215225
},
216226

217227
/**
@@ -262,7 +272,7 @@ export class ObjectStackClient {
262272
getView: async (object: string, type: 'list' | 'form' = 'list') => {
263273
const route = this.getRoute('ui');
264274
const res = await this.fetch(`${this.baseUrl}${route}/view/${object}?type=${type}`);
265-
return res.json();
275+
return this.unwrapResponse(res);
266276
}
267277
};
268278

@@ -596,6 +606,23 @@ export class ObjectStackClient {
596606
return Array.isArray(filter);
597607
}
598608

609+
/**
610+
* Unwrap the standard REST API response envelope.
611+
* The HTTP layer wraps responses as `{ success: boolean, data: T, meta? }`
612+
* (see BaseResponseSchema in contract.zod.ts).
613+
* This method strips the envelope and returns the inner `data` payload
614+
* so callers receive the spec-level type (e.g. GetMetaTypesResponse).
615+
*/
616+
private async unwrapResponse<T>(res: Response): Promise<T> {
617+
const body = await res.json();
618+
// If the body has a `success` flag it's a BaseResponse envelope
619+
if (body && typeof body.success === 'boolean' && 'data' in body) {
620+
return body.data as T;
621+
}
622+
// Already unwrapped or non-standard
623+
return body as T;
624+
}
625+
599626
private async fetch(url: string, options: RequestInit = {}): Promise<Response> {
600627
this.logger.debug('HTTP request', {
601628
method: options.method || 'GET',

packages/runtime/src/http-dispatcher.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -301,50 +301,53 @@ export class HttpDispatcher {
301301

302302
// POST /data/:object/query
303303
if (action === 'query' && m === 'POST') {
304+
// Spec: broker returns FindDataResponse = { object, records, total?, hasMore? }
304305
const result = await broker.call('data.query', { object: objectName, ...body }, { request: context.request });
305-
return { handled: true, response: this.success(result.data, { count: result.count, limit: body.limit, skip: body.skip }) };
306+
return { handled: true, response: this.success(result) };
306307
}
307308

308309
// POST /data/:object/batch
309310
if (action === 'batch' && m === 'POST') {
310-
// Spec complaint: forward the whole body { operation, records, options }
311-
// Implementation in Kernel should handle the 'operation' field
312311
const result = await broker.call('data.batch', { object: objectName, ...body }, { request: context.request });
313312
return { handled: true, response: this.success(result) };
314313
}
315314

316315
// GET /data/:object/:id
317316
if (parts.length === 2 && m === 'GET') {
318317
const id = parts[1];
319-
const data = await broker.call('data.get', { object: objectName, id, ...query }, { request: context.request });
320-
return { handled: true, response: this.success(data) };
318+
// Spec: broker returns GetDataResponse = { object, id, record }
319+
const result = await broker.call('data.get', { object: objectName, id, ...query }, { request: context.request });
320+
return { handled: true, response: this.success(result) };
321321
}
322322

323323
// PATCH /data/:object/:id
324324
if (parts.length === 2 && m === 'PATCH') {
325325
const id = parts[1];
326-
const data = await broker.call('data.update', { object: objectName, id, data: body }, { request: context.request });
327-
return { handled: true, response: this.success(data) };
326+
// Spec: broker returns UpdateDataResponse = { object, id, record }
327+
const result = await broker.call('data.update', { object: objectName, id, data: body }, { request: context.request });
328+
return { handled: true, response: this.success(result) };
328329
}
329330

330331
// DELETE /data/:object/:id
331332
if (parts.length === 2 && m === 'DELETE') {
332333
const id = parts[1];
333-
await broker.call('data.delete', { object: objectName, id }, { request: context.request });
334-
return { handled: true, response: this.success({ id, deleted: true }) };
334+
// Spec: broker returns DeleteDataResponse = { object, id, deleted }
335+
const result = await broker.call('data.delete', { object: objectName, id }, { request: context.request });
336+
return { handled: true, response: this.success(result) };
335337
}
336338
} else {
337339
// GET /data/:object (List)
338340
if (m === 'GET') {
341+
// Spec: broker returns FindDataResponse = { object, records, total?, hasMore? }
339342
const result = await broker.call('data.query', { object: objectName, filters: query }, { request: context.request });
340-
return { handled: true, response: this.success(result.data, { count: result.count }) };
343+
return { handled: true, response: this.success(result) };
341344
}
342345

343346
// POST /data/:object (Create)
344347
if (m === 'POST') {
345-
const data = await broker.call('data.create', { object: objectName, data: body }, { request: context.request });
346-
// Note: ideally 201
347-
const res = this.success(data);
348+
// Spec: broker returns CreateDataResponse = { object, id, record }
349+
const result = await broker.call('data.create', { object: objectName, data: body }, { request: context.request });
350+
const res = this.success(result);
348351
res.status = 201;
349352
return { handled: true, response: res };
350353
}

0 commit comments

Comments
 (0)