@jskit-ai/http-runtime 0.1.59 → 0.1.61
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 +2 -2
- package/package.json +2 -2
- package/src/shared/clientRuntime/jsonApiResourceTransport.js +8 -17
- package/src/shared/support/jsonApiSimplify.js +39 -0
- package/src/shared/validators/jsonApiTransport.js +2 -5
- package/test/client.test.js +51 -0
- package/test/jsonApiTransport.test.js +34 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/http-runtime",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.61",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"dependsOn": [],
|
|
7
7
|
"capabilities": {
|
|
@@ -67,7 +67,7 @@ export default Object.freeze({
|
|
|
67
67
|
"mutations": {
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"runtime": {
|
|
70
|
-
"@jskit-ai/kernel": "0.1.
|
|
70
|
+
"@jskit-ai/kernel": "0.1.62"
|
|
71
71
|
},
|
|
72
72
|
"dev": {}
|
|
73
73
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/http-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"./shared/validators/operationValidation": "./src/shared/validators/operationValidation.js"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
21
|
+
"@jskit-ai/kernel": "0.1.62",
|
|
22
22
|
"json-rest-schema": "1.x.x"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
resolveJsonApiTransportTypes
|
|
8
8
|
} from "../validators/jsonApiTransport.js";
|
|
9
9
|
import { encodeJsonApiResourceQueryObject } from "../validators/jsonApiQueryTransport.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveRelationshipFieldKey,
|
|
12
|
+
simplifyJsonApiResourceWithRelationshipIds
|
|
13
|
+
} from "../support/jsonApiSimplify.js";
|
|
10
14
|
|
|
11
15
|
function isRecord(value) {
|
|
12
16
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -98,16 +102,6 @@ function encodeJsonApiResourceQuery(query, transport = null) {
|
|
|
98
102
|
});
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
function resolveRelationshipFieldKey(relationshipName = "", lookupFieldMap = null) {
|
|
102
|
-
const normalizedRelationshipName = normalizeText(relationshipName);
|
|
103
|
-
const explicitFieldKey = normalizeText(lookupFieldMap?.[normalizedRelationshipName]);
|
|
104
|
-
if (explicitFieldKey) {
|
|
105
|
-
return explicitFieldKey;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return normalizedRelationshipName ? `${normalizedRelationshipName}Id` : "";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
105
|
function createIncludedResourceIndex(document = {}) {
|
|
112
106
|
const index = new Map();
|
|
113
107
|
|
|
@@ -135,9 +129,7 @@ function simplifyRelationshipResource(linkage = {}, options = {}) {
|
|
|
135
129
|
const resourceKey = `${type}:${id}`;
|
|
136
130
|
const includedResource = options.includedResourceIndex?.get(resourceKey);
|
|
137
131
|
if (!includedResource) {
|
|
138
|
-
return
|
|
139
|
-
id
|
|
140
|
-
};
|
|
132
|
+
return null;
|
|
141
133
|
}
|
|
142
134
|
|
|
143
135
|
return simplifyResourceObject(includedResource, options);
|
|
@@ -145,10 +137,9 @@ function simplifyRelationshipResource(linkage = {}, options = {}) {
|
|
|
145
137
|
|
|
146
138
|
function simplifyResourceObject(resource = {}, options = {}) {
|
|
147
139
|
const normalizedResource = isRecord(resource) ? normalizeObject(resource) : {};
|
|
148
|
-
const simplified = {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
};
|
|
140
|
+
const simplified = simplifyJsonApiResourceWithRelationshipIds(normalizedResource, {
|
|
141
|
+
lookupFieldMap: options.lookupFieldMap || null
|
|
142
|
+
});
|
|
152
143
|
const lookupContainerKey = normalizeText(options.lookupContainerKey, {
|
|
153
144
|
fallback: "lookups"
|
|
154
145
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isRecord, normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
function resolveRelationshipFieldKey(relationshipName = "", lookupFieldMap = null) {
|
|
4
|
+
const normalizedRelationshipName = normalizeText(relationshipName);
|
|
5
|
+
const explicitFieldKey = normalizeText(lookupFieldMap?.[normalizedRelationshipName]);
|
|
6
|
+
if (explicitFieldKey) {
|
|
7
|
+
return explicitFieldKey;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return normalizedRelationshipName ? `${normalizedRelationshipName}Id` : "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function simplifyJsonApiResourceWithRelationshipIds(resource = {}, { lookupFieldMap = null } = {}) {
|
|
14
|
+
const normalizedResource = isRecord(resource) ? normalizeObject(resource) : {};
|
|
15
|
+
const simplified = {
|
|
16
|
+
id: normalizedResource.id == null ? "" : String(normalizedResource.id),
|
|
17
|
+
...(normalizeObject(normalizedResource.attributes))
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
for (const [relationshipName, relationshipValue] of Object.entries(normalizeObject(normalizedResource.relationships))) {
|
|
21
|
+
const relationshipData = relationshipValue?.data;
|
|
22
|
+
if (Array.isArray(relationshipData)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fieldKey = resolveRelationshipFieldKey(relationshipName, lookupFieldMap);
|
|
27
|
+
if (!fieldKey || Object.hasOwn(simplified, fieldKey)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
simplified[fieldKey] = relationshipData?.id == null ? null : String(relationshipData.id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return simplified;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
resolveRelationshipFieldKey,
|
|
38
|
+
simplifyJsonApiResourceWithRelationshipIds
|
|
39
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeArray, normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
import { simplifyJsonApiResourceWithRelationshipIds } from "../support/jsonApiSimplify.js";
|
|
2
3
|
|
|
3
4
|
const JSON_API_CONTENT_TYPE = "application/vnd.api+json";
|
|
4
5
|
|
|
@@ -441,11 +442,7 @@ function createJsonApiErrorDocumentFromFailure({
|
|
|
441
442
|
}
|
|
442
443
|
|
|
443
444
|
function simplifyJsonApiResourceObject(resource = {}) {
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
id: normalized.id == null ? "" : normalized.id,
|
|
447
|
-
...(normalized.attributes || {})
|
|
448
|
-
};
|
|
445
|
+
return simplifyJsonApiResourceWithRelationshipIds(normalizeJsonApiResourceObject(resource));
|
|
449
446
|
}
|
|
450
447
|
|
|
451
448
|
function simplifyJsonApiDocument(payload = {}) {
|
package/test/client.test.js
CHANGED
|
@@ -236,6 +236,57 @@ test("request decodes json:api relationship includes into JSKIT lookups and fore
|
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
test("request keeps relationship foreign-key fields even when related resources are not included", async () => {
|
|
240
|
+
const fetchImpl = async () =>
|
|
241
|
+
mockResponse({
|
|
242
|
+
contentType: "application/vnd.api+json",
|
|
243
|
+
data: {
|
|
244
|
+
data: {
|
|
245
|
+
type: "pets",
|
|
246
|
+
id: "729900",
|
|
247
|
+
attributes: {
|
|
248
|
+
name: "Daisy"
|
|
249
|
+
},
|
|
250
|
+
relationships: {
|
|
251
|
+
contact: {
|
|
252
|
+
data: {
|
|
253
|
+
type: "contacts",
|
|
254
|
+
id: "552252"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
breed: {
|
|
258
|
+
data: {
|
|
259
|
+
type: "breeds",
|
|
260
|
+
id: "2"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const client = createHttpClient({ fetchImpl });
|
|
269
|
+
const payload = await client.request("/api/pets/729900", {
|
|
270
|
+
method: "GET",
|
|
271
|
+
transport: {
|
|
272
|
+
kind: "jsonapi-resource",
|
|
273
|
+
responseType: "pets",
|
|
274
|
+
responseKind: "record",
|
|
275
|
+
lookupFieldMap: {
|
|
276
|
+
contact: "contactId",
|
|
277
|
+
breed: "breedId"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
assert.deepEqual(payload, {
|
|
283
|
+
id: "729900",
|
|
284
|
+
name: "Daisy",
|
|
285
|
+
contactId: "552252",
|
|
286
|
+
breedId: "2"
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
239
290
|
test("request recursively decodes nested included relationships for collection-style lookups", async () => {
|
|
240
291
|
const fetchImpl = async () =>
|
|
241
292
|
mockResponse({
|
|
@@ -173,6 +173,40 @@ test("simplifyJsonApiDocument keeps flat-record behavior for resource and collec
|
|
|
173
173
|
);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
+
test("simplifyJsonApiDocument preserves single-relationship foreign key ids without lookup hydration", () => {
|
|
177
|
+
assert.deepEqual(
|
|
178
|
+
simplifyJsonApiDocument({
|
|
179
|
+
data: {
|
|
180
|
+
type: "workspace-memberships",
|
|
181
|
+
id: "11",
|
|
182
|
+
attributes: {
|
|
183
|
+
status: "active"
|
|
184
|
+
},
|
|
185
|
+
relationships: {
|
|
186
|
+
workspace: {
|
|
187
|
+
data: {
|
|
188
|
+
type: "workspaces",
|
|
189
|
+
id: "7"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
user: {
|
|
193
|
+
data: {
|
|
194
|
+
type: "userProfiles",
|
|
195
|
+
id: "9"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}),
|
|
201
|
+
{
|
|
202
|
+
id: "11",
|
|
203
|
+
status: "active",
|
|
204
|
+
workspaceId: "7",
|
|
205
|
+
userId: "9"
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
176
210
|
test("createJsonApiErrorDocumentFromFailure maps field errors and validation issues to JSON:API errors", () => {
|
|
177
211
|
assert.deepEqual(
|
|
178
212
|
createJsonApiErrorDocumentFromFailure({
|