Skip to content

Commit 8b57bac

Browse files
Copilothotlong
andcommitted
fix: handle populate/joins mapping in engine.ts and HTTP query param normalization in protocol.ts
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent c65c0df commit 8b57bac

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

packages/objectql/src/engine.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,14 @@ export class ObjectQL implements IDataEngine {
416416

417417
if (options.skip !== undefined) ast.offset = options.skip;
418418

419-
// TODO: Handle populate/joins mapping if Driver supports it in QueryAST
419+
// Map populate (relationship field names) to QueryAST expand entries
420+
if (options.populate && options.populate.length > 0) {
421+
ast.expand = {};
422+
for (const rel of options.populate) {
423+
ast.expand[rel] = { object: rel };
424+
}
425+
}
426+
420427
return ast;
421428
}
422429

packages/objectql/src/protocol.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,51 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
186186
}
187187

188188
async findData(request: { object: string, query?: any }) {
189-
// TODO: Normalize query from HTTP Query params (string values) to DataEngineQueryOptions (typed)
190-
// For now, we assume query is partially compatible or simple enough.
191-
// We should parse 'top', 'skip', 'limit' to numbers if they are strings.
192189
const options: any = { ...request.query };
193-
if (options.top) options.top = Number(options.top);
194-
if (options.skip) options.skip = Number(options.skip);
195-
if (options.limit) options.limit = Number(options.limit);
190+
191+
// Numeric fields
192+
if (options.top != null) options.top = Number(options.top);
193+
if (options.skip != null) options.skip = Number(options.skip);
194+
if (options.limit != null) options.limit = Number(options.limit);
195+
196+
// Select: comma-separated string → array
197+
if (typeof options.select === 'string') {
198+
options.select = options.select.split(',').map((s: string) => s.trim()).filter(Boolean);
199+
}
200+
201+
// Sort/orderBy: string → sort array (e.g. "name asc,created_at desc" or "name,-created_at")
202+
const sortValue = options.orderBy ?? options.sort;
203+
if (typeof sortValue === 'string') {
204+
const parsed = sortValue.split(',').map((part: string) => {
205+
const trimmed = part.trim();
206+
if (trimmed.startsWith('-')) {
207+
return { field: trimmed.slice(1), order: 'desc' as const };
208+
}
209+
const [field, order] = trimmed.split(/\s+/);
210+
return { field, order: (order?.toLowerCase() === 'desc' ? 'desc' : 'asc') as 'asc' | 'desc' };
211+
}).filter((s: any) => s.field);
212+
options.sort = parsed;
213+
delete options.orderBy;
214+
}
215+
216+
// Filter: JSON string → object
217+
if (typeof options.filter === 'string') {
218+
try { options.filter = JSON.parse(options.filter); } catch { /* keep as-is */ }
219+
}
220+
if (typeof options.filters === 'string') {
221+
try { options.filter = JSON.parse(options.filters); delete options.filters; } catch { /* keep as-is */ }
222+
}
223+
224+
// Populate: comma-separated string → array
225+
if (typeof options.populate === 'string') {
226+
options.populate = options.populate.split(',').map((s: string) => s.trim()).filter(Boolean);
227+
}
228+
229+
// Boolean fields
230+
for (const key of ['distinct', 'count']) {
231+
if (options[key] === 'true') options[key] = true;
232+
else if (options[key] === 'false') options[key] = false;
233+
}
196234

197235
// Handle OData style $filter if present, or flat filters
198236
// This is a naive implementation, a real OData parser is needed for complex scenarios.

0 commit comments

Comments
 (0)