@jskit-ai/json-rest-api-core 0.1.3 → 0.1.5
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/package.descriptor.mjs +1 -1
- package/package.json +2 -2
- package/src/server/jsonRestApiHost.js +10 -126
- package/test/entrypoints.boundary.test.js +25 -43
package/package.descriptor.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/json-rest-api-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"hooked-api": "1.x.x",
|
|
13
13
|
"json-rest-api": "1.x.x",
|
|
14
|
-
"@jskit-ai/kernel": "0.1.
|
|
14
|
+
"@jskit-ai/kernel": "0.1.60"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -198,6 +198,15 @@ function buildJsonRestQueryParams(resourceType = "", query = {}, { include = und
|
|
|
198
198
|
return queryParams;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
function extractJsonRestCollectionRows(payload = null) {
|
|
202
|
+
if (Array.isArray(payload)) {
|
|
203
|
+
return payload;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const source = normalizeJsonRestObject(payload);
|
|
207
|
+
return Array.isArray(source.data) ? source.data : [];
|
|
208
|
+
}
|
|
209
|
+
|
|
201
210
|
function extractJsonApiInputRelationships(attributes = {}, resource = null, relationships = null) {
|
|
202
211
|
const normalizedAttributes = {
|
|
203
212
|
...normalizeJsonRestObject(attributes)
|
|
@@ -291,131 +300,6 @@ function createJsonRestResourceScopeOptions(resource = {}, { writeSerializers =
|
|
|
291
300
|
return scopeOptions;
|
|
292
301
|
}
|
|
293
302
|
|
|
294
|
-
function normalizeJsonApiResourceObject(resource = {}) {
|
|
295
|
-
const normalizedResource = normalizeJsonRestObject(resource);
|
|
296
|
-
return {
|
|
297
|
-
type: normalizeJsonRestText(normalizedResource.type),
|
|
298
|
-
id: normalizedResource.id == null ? null : String(normalizedResource.id),
|
|
299
|
-
attributes: normalizeJsonRestObject(normalizedResource.attributes),
|
|
300
|
-
relationships: normalizeJsonRestObject(normalizedResource.relationships)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function buildJsonApiIncludedIndex(payload = {}) {
|
|
305
|
-
const included = Array.isArray(payload?.included) ? payload.included : [];
|
|
306
|
-
const index = new Map();
|
|
307
|
-
|
|
308
|
-
for (const entry of included) {
|
|
309
|
-
const normalizedEntry = normalizeJsonApiResourceObject(entry);
|
|
310
|
-
if (!normalizedEntry.type || !normalizedEntry.id) {
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
index.set(`${normalizedEntry.type}:${normalizedEntry.id}`, normalizedEntry);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return index;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function simplifyJsonApiRelationshipData(data, { includedIndex = null, seen = null } = {}) {
|
|
321
|
-
if (Array.isArray(data)) {
|
|
322
|
-
return data
|
|
323
|
-
.map((entry) => simplifyJsonApiRelationshipData(entry, { includedIndex, seen }))
|
|
324
|
-
.filter((entry) => entry != null);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (data == null) {
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const normalizedReference = normalizeJsonApiResourceObject(data);
|
|
332
|
-
if (!normalizedReference.id) {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const referenceKey =
|
|
337
|
-
normalizedReference.type && normalizedReference.id
|
|
338
|
-
? `${normalizedReference.type}:${normalizedReference.id}`
|
|
339
|
-
: "";
|
|
340
|
-
const nextSeen = seen instanceof Set ? new Set(seen) : new Set();
|
|
341
|
-
|
|
342
|
-
if (referenceKey) {
|
|
343
|
-
if (nextSeen.has(referenceKey)) {
|
|
344
|
-
return {
|
|
345
|
-
id: normalizedReference.id,
|
|
346
|
-
...(normalizedReference.type ? { type: normalizedReference.type } : {})
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
nextSeen.add(referenceKey);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (referenceKey && includedIndex instanceof Map && includedIndex.has(referenceKey)) {
|
|
353
|
-
return simplifyJsonApiResourceObject(includedIndex.get(referenceKey), {
|
|
354
|
-
includedIndex,
|
|
355
|
-
seen: nextSeen
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
id: normalizedReference.id,
|
|
361
|
-
...(normalizedReference.type ? { type: normalizedReference.type } : {})
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function simplifyJsonApiResourceObject(resource = {}, { includedIndex = null, seen = null } = {}) {
|
|
366
|
-
const normalizedResource = normalizeJsonApiResourceObject(resource);
|
|
367
|
-
const resourceKey =
|
|
368
|
-
normalizedResource.type && normalizedResource.id
|
|
369
|
-
? `${normalizedResource.type}:${normalizedResource.id}`
|
|
370
|
-
: "";
|
|
371
|
-
const nextSeen = seen instanceof Set ? new Set(seen) : new Set();
|
|
372
|
-
|
|
373
|
-
if (resourceKey) {
|
|
374
|
-
nextSeen.add(resourceKey);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const simplified = {
|
|
378
|
-
...(normalizedResource.id == null ? {} : { id: normalizedResource.id }),
|
|
379
|
-
...normalizedResource.attributes
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
for (const [relationshipKey, relationshipValue] of Object.entries(normalizedResource.relationships)) {
|
|
383
|
-
if (!relationshipKey || !relationshipValue || !Object.hasOwn(relationshipValue, "data")) {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
simplified[relationshipKey] = simplifyJsonApiRelationshipData(relationshipValue.data, {
|
|
388
|
-
includedIndex,
|
|
389
|
-
seen: nextSeen
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return simplified;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function simplifyJsonApiDocument(payload = {}) {
|
|
397
|
-
const source = normalizeJsonRestObject(payload);
|
|
398
|
-
const includedIndex = buildJsonApiIncludedIndex(source);
|
|
399
|
-
|
|
400
|
-
if (Array.isArray(source.data)) {
|
|
401
|
-
return source.data.map((entry) => simplifyJsonApiResourceObject(entry, { includedIndex }));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (source.data && typeof source.data === "object") {
|
|
405
|
-
return simplifyJsonApiResourceObject(source.data, { includedIndex });
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (Object.hasOwn(source, "data") && source.data == null) {
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (source.meta && typeof source.meta === "object" && !Array.isArray(source.meta)) {
|
|
413
|
-
return source.meta;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return payload;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
303
|
function createJsonRestContext(context = null) {
|
|
420
304
|
if (!context || typeof context !== "object" || Array.isArray(context)) {
|
|
421
305
|
return {};
|
|
@@ -536,11 +420,11 @@ export {
|
|
|
536
420
|
createJsonApiRelationship,
|
|
537
421
|
createJsonRestResourceScopeOptions,
|
|
538
422
|
createJsonRestContext,
|
|
423
|
+
extractJsonRestCollectionRows,
|
|
539
424
|
isJsonRestResourceMissingError,
|
|
540
425
|
returnNullWhenJsonRestResourceMissing,
|
|
541
426
|
resolveWorkspaceScopeValue,
|
|
542
427
|
resolveUserScopeValue,
|
|
543
|
-
simplifyJsonApiDocument,
|
|
544
428
|
createJsonRestApiHost,
|
|
545
429
|
registerJsonRestApiHost
|
|
546
430
|
};
|
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
createJsonRestResourceScopeOptions,
|
|
13
13
|
createJsonRestContext,
|
|
14
14
|
createJsonRestApiHost,
|
|
15
|
+
extractJsonRestCollectionRows,
|
|
15
16
|
isJsonRestResourceMissingError,
|
|
16
17
|
registerJsonRestApiHost,
|
|
17
18
|
returnNullWhenJsonRestResourceMissing,
|
|
18
19
|
resolveWorkspaceScopeValue,
|
|
19
|
-
resolveUserScopeValue
|
|
20
|
-
simplifyJsonApiDocument
|
|
20
|
+
resolveUserScopeValue
|
|
21
21
|
} from "../src/server/jsonRestApiHost.js";
|
|
22
22
|
import { JsonRestApiCoreServiceProvider } from "../src/server/JsonRestApiCoreServiceProvider.js";
|
|
23
23
|
|
|
@@ -28,6 +28,11 @@ test("package exports include explicit server jsonRestApiHost entrypoint only",
|
|
|
28
28
|
assert.equal(exportsMap["./server"], undefined);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
test("server jsonRestApiHost entrypoint no longer exports host-side JSON:API simplification helpers", async () => {
|
|
32
|
+
const hostModule = await import("../src/server/jsonRestApiHost.js");
|
|
33
|
+
assert.equal(Object.hasOwn(hostModule, "simplifyJsonApiDocument"), false);
|
|
34
|
+
});
|
|
35
|
+
|
|
31
36
|
test("server entrypoint exports shared host helpers", () => {
|
|
32
37
|
assert.equal(INTERNAL_JSON_REST_API, "internal.json-rest-api");
|
|
33
38
|
assert.equal(typeof addResourceIfMissing, "function");
|
|
@@ -37,12 +42,12 @@ test("server entrypoint exports shared host helpers", () => {
|
|
|
37
42
|
assert.equal(typeof createJsonRestResourceScopeOptions, "function");
|
|
38
43
|
assert.equal(typeof createJsonRestContext, "function");
|
|
39
44
|
assert.equal(typeof createJsonRestApiHost, "function");
|
|
45
|
+
assert.equal(typeof extractJsonRestCollectionRows, "function");
|
|
40
46
|
assert.equal(typeof isJsonRestResourceMissingError, "function");
|
|
41
47
|
assert.equal(typeof registerJsonRestApiHost, "function");
|
|
42
48
|
assert.equal(typeof returnNullWhenJsonRestResourceMissing, "function");
|
|
43
49
|
assert.equal(typeof resolveWorkspaceScopeValue, "function");
|
|
44
50
|
assert.equal(typeof resolveUserScopeValue, "function");
|
|
45
|
-
assert.equal(typeof simplifyJsonApiDocument, "function");
|
|
46
51
|
assert.equal(typeof JsonRestApiCoreServiceProvider, "function");
|
|
47
52
|
});
|
|
48
53
|
|
|
@@ -84,6 +89,23 @@ test("createJsonRestContext returns an empty mutable object when source context
|
|
|
84
89
|
assert.equal(result.method, "query");
|
|
85
90
|
});
|
|
86
91
|
|
|
92
|
+
test("extractJsonRestCollectionRows understands the internal collection-document contract", () => {
|
|
93
|
+
const rows = [{ id: "1" }, { id: "2" }];
|
|
94
|
+
|
|
95
|
+
assert.deepEqual(extractJsonRestCollectionRows(rows), rows);
|
|
96
|
+
assert.deepEqual(
|
|
97
|
+
extractJsonRestCollectionRows({
|
|
98
|
+
data: rows,
|
|
99
|
+
links: {
|
|
100
|
+
self: "/contacts"
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
rows
|
|
104
|
+
);
|
|
105
|
+
assert.deepEqual(extractJsonRestCollectionRows({ data: null }), []);
|
|
106
|
+
assert.deepEqual(extractJsonRestCollectionRows(null), []);
|
|
107
|
+
});
|
|
108
|
+
|
|
87
109
|
test("createJsonRestApiHost installs normalizeRecordId as the default resource id normalizer", async () => {
|
|
88
110
|
const fakeKnex = Object.assign(() => {}, {
|
|
89
111
|
client: {
|
|
@@ -201,46 +223,6 @@ test("shared query/document helpers build json-rest-api request shapes", () => {
|
|
|
201
223
|
}
|
|
202
224
|
);
|
|
203
225
|
|
|
204
|
-
assert.deepEqual(
|
|
205
|
-
simplifyJsonApiDocument({
|
|
206
|
-
data: [
|
|
207
|
-
{
|
|
208
|
-
type: "workspace-memberships",
|
|
209
|
-
id: "11",
|
|
210
|
-
attributes: {
|
|
211
|
-
roleSid: "owner"
|
|
212
|
-
},
|
|
213
|
-
relationships: {
|
|
214
|
-
user: {
|
|
215
|
-
data: {
|
|
216
|
-
type: "user-profiles",
|
|
217
|
-
id: "9"
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
],
|
|
223
|
-
included: [
|
|
224
|
-
{
|
|
225
|
-
type: "user-profiles",
|
|
226
|
-
id: "9",
|
|
227
|
-
attributes: {
|
|
228
|
-
displayName: "Chiara"
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
]
|
|
232
|
-
}),
|
|
233
|
-
[
|
|
234
|
-
{
|
|
235
|
-
id: "11",
|
|
236
|
-
roleSid: "owner",
|
|
237
|
-
user: {
|
|
238
|
-
id: "9",
|
|
239
|
-
displayName: "Chiara"
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
]
|
|
243
|
-
);
|
|
244
226
|
});
|
|
245
227
|
|
|
246
228
|
test("createJsonRestResourceScopeOptions clones canonical resource metadata and resolves symbolic write serializers", () => {
|