@objectstack/runtime 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +54 -0
- package/dist/index.cjs +27 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/http-dispatcher.root.test.ts +15 -0
- package/src/http-dispatcher.test.ts +2 -1
- package/src/http-dispatcher.ts +46 -4
- package/src/seed-loader.ts +3 -3
- package/tsconfig.json +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "ObjectStack Core Runtime & Query Engine",
|
|
6
6
|
"type": "module",
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"zod": "^4.3.6",
|
|
18
|
-
"@objectstack/core": "
|
|
19
|
-
"@objectstack/rest": "
|
|
20
|
-
"@objectstack/spec": "
|
|
21
|
-
"@objectstack/types": "
|
|
18
|
+
"@objectstack/core": "4.0.0",
|
|
19
|
+
"@objectstack/rest": "4.0.0",
|
|
20
|
+
"@objectstack/spec": "4.0.0",
|
|
21
|
+
"@objectstack/types": "4.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"typescript": "^
|
|
25
|
-
"vitest": "^4.1.
|
|
24
|
+
"typescript": "^6.0.2",
|
|
25
|
+
"vitest": "^4.1.2"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsup --config ../../tsup.config.ts",
|
|
@@ -46,6 +46,21 @@ describe('HttpDispatcher Root Handling', () => {
|
|
|
46
46
|
expect(data.routes.metadata).toBe('/meta');
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('should handle GET /discovery (protocol-standard route)', async () => {
|
|
50
|
+
const context = { request: {} };
|
|
51
|
+
const result = await dispatcher.dispatch('GET', '/discovery', undefined, {}, context);
|
|
52
|
+
|
|
53
|
+
expect(result.handled).toBe(true);
|
|
54
|
+
expect(result.response).toBeDefined();
|
|
55
|
+
expect(result.response?.status).toBe(200);
|
|
56
|
+
|
|
57
|
+
const data = result.response?.body?.data;
|
|
58
|
+
expect(data).toBeDefined();
|
|
59
|
+
expect(data.name).toBe('ObjectOS');
|
|
60
|
+
expect(data.version).toBe('1.0.0');
|
|
61
|
+
expect(data.routes).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
49
64
|
it('should NOT handle POST request to root path ("")', async () => {
|
|
50
65
|
const context = { request: {} };
|
|
51
66
|
const method = 'POST';
|
|
@@ -645,9 +645,10 @@ describe('HttpDispatcher', () => {
|
|
|
645
645
|
);
|
|
646
646
|
|
|
647
647
|
expect(result.handled).toBe(true);
|
|
648
|
+
// top → limit and skip → offset are normalized by the dispatcher
|
|
648
649
|
expect(mockBroker.call).toHaveBeenCalledWith(
|
|
649
650
|
'data.query',
|
|
650
|
-
{ object: 'task', query },
|
|
651
|
+
{ object: 'task', query: { populate: 'assignee,project', limit: '10', offset: '0' } },
|
|
651
652
|
{ request: {} }
|
|
652
653
|
);
|
|
653
654
|
});
|
package/src/http-dispatcher.ts
CHANGED
|
@@ -585,8 +585,49 @@ export class HttpDispatcher {
|
|
|
585
585
|
} else {
|
|
586
586
|
// GET /data/:object (List)
|
|
587
587
|
if (m === 'GET') {
|
|
588
|
+
// ── Normalize HTTP transport params → Spec canonical (QueryAST) ──
|
|
589
|
+
// HTTP GET query params use transport-level names (filter, sort, top,
|
|
590
|
+
// skip, select, expand) which are normalized here to canonical
|
|
591
|
+
// QueryAST field names (where, orderBy, limit, offset, fields,
|
|
592
|
+
// expand) before forwarding to the broker layer.
|
|
593
|
+
// The protocol.ts findData() method performs a deeper normalization
|
|
594
|
+
// pass, but pre-normalizing here ensures the broker always receives
|
|
595
|
+
// Spec-canonical keys.
|
|
596
|
+
const normalized: Record<string, unknown> = { ...query };
|
|
597
|
+
|
|
598
|
+
// filter/filters → where
|
|
599
|
+
// Note: `filter` is the canonical HTTP *transport* parameter name
|
|
600
|
+
// (see HttpFindQueryParamsSchema). It is normalized here to the
|
|
601
|
+
// canonical *QueryAST* field name `where` before broker dispatch.
|
|
602
|
+
// `filters` (plural) is a deprecated alias for `filter`.
|
|
603
|
+
if (normalized.filter != null || normalized.filters != null) {
|
|
604
|
+
normalized.where = normalized.where ?? normalized.filter ?? normalized.filters;
|
|
605
|
+
delete normalized.filter;
|
|
606
|
+
delete normalized.filters;
|
|
607
|
+
}
|
|
608
|
+
// select → fields
|
|
609
|
+
if (normalized.select != null && normalized.fields == null) {
|
|
610
|
+
normalized.fields = normalized.select;
|
|
611
|
+
delete normalized.select;
|
|
612
|
+
}
|
|
613
|
+
// sort → orderBy
|
|
614
|
+
if (normalized.sort != null && normalized.orderBy == null) {
|
|
615
|
+
normalized.orderBy = normalized.sort;
|
|
616
|
+
delete normalized.sort;
|
|
617
|
+
}
|
|
618
|
+
// top → limit
|
|
619
|
+
if (normalized.top != null && normalized.limit == null) {
|
|
620
|
+
normalized.limit = normalized.top;
|
|
621
|
+
delete normalized.top;
|
|
622
|
+
}
|
|
623
|
+
// skip → offset
|
|
624
|
+
if (normalized.skip != null && normalized.offset == null) {
|
|
625
|
+
normalized.offset = normalized.skip;
|
|
626
|
+
delete normalized.skip;
|
|
627
|
+
}
|
|
628
|
+
|
|
588
629
|
// Spec: broker returns FindDataResponse = { object, records, total?, hasMore? }
|
|
589
|
-
const result = await broker.call('data.query', { object: objectName, query }, { request: context.request });
|
|
630
|
+
const result = await broker.call('data.query', { object: objectName, query: normalized }, { request: context.request });
|
|
590
631
|
return { handled: true, response: this.success(result) };
|
|
591
632
|
}
|
|
592
633
|
|
|
@@ -1154,9 +1195,10 @@ export class HttpDispatcher {
|
|
|
1154
1195
|
async dispatch(method: string, path: string, body: any, query: any, context: HttpProtocolContext): Promise<HttpDispatcherResult> {
|
|
1155
1196
|
const cleanPath = path.replace(/\/$/, ''); // Remove trailing slash if present, but strict on clean paths
|
|
1156
1197
|
|
|
1157
|
-
// 0.
|
|
1158
|
-
//
|
|
1159
|
-
|
|
1198
|
+
// 0. Discovery Endpoint (GET /discovery or GET /)
|
|
1199
|
+
// Standard route: /discovery (protocol-compliant)
|
|
1200
|
+
// Legacy route: / (empty path, for backward compatibility — MSW strips base URL)
|
|
1201
|
+
if ((cleanPath === '/discovery' || cleanPath === '') && method === 'GET') {
|
|
1160
1202
|
// We use '' as prefix since we are internal dispatcher
|
|
1161
1203
|
const info = await this.getDiscoveryInfo('');
|
|
1162
1204
|
return {
|
package/src/seed-loader.ts
CHANGED
|
@@ -324,8 +324,8 @@ export class SeedLoaderService implements ISeedLoaderService {
|
|
|
324
324
|
): Promise<string | null> {
|
|
325
325
|
try {
|
|
326
326
|
const records = await this.engine.find(targetObject, {
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
where: { [targetField]: value },
|
|
328
|
+
fields: ['id'],
|
|
329
329
|
limit: 1,
|
|
330
330
|
});
|
|
331
331
|
if (records && records.length > 0) {
|
|
@@ -612,7 +612,7 @@ export class SeedLoaderService implements ISeedLoaderService {
|
|
|
612
612
|
const map = new Map<string, any>();
|
|
613
613
|
try {
|
|
614
614
|
const records = await this.engine.find(objectName, {
|
|
615
|
-
|
|
615
|
+
fields: ['id', externalId],
|
|
616
616
|
});
|
|
617
617
|
for (const record of records || []) {
|
|
618
618
|
const key = String(record[externalId] ?? '');
|
package/tsconfig.json
CHANGED