@jskit-ai/http-runtime 0.1.243 → 0.1.244
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.json +6 -35
- package/src/client/index.js +2 -3
- package/src/shared/clientRuntime/client.js +1 -57
- package/src/shared/clientRuntime/errors.js +39 -2
- package/src/shared/clientRuntime/jsonApiResourceTransport.js +27 -39
- package/src/shared/index.js +2 -21
- package/src/shared/validators/errorResponses.js +1 -13
- package/src/shared/validators/jsonApiQueryTransport.js +1 -5
- package/src/shared/validators/jsonApiRouteTransport.js +43 -54
- package/src/shared/validators/jsonApiTransport.js +1 -5
- package/src/shared/validators/operationValidation.js +5 -92
- package/test/client.test.js +29 -0
- package/test/entrypoints.boundary.test.js +15 -14
- package/test/errorResponses.test.js +0 -11
- package/test/jsonApiRouteTransport.test.js +12 -7
- package/test/operationValidation.test.js +13 -61
- package/test/transientRetryHttpClient.test.js +11 -0
- package/test/validationErrors.test.js +20 -9
- package/src/client/providers/HttpClientRuntimeClientProvider.js +0 -20
- package/src/client/providers/HttpValidatorsClientProvider.js +0 -16
- package/src/server/providers/HttpClientProvider.js +0 -16
- package/src/server/providers/HttpValidatorsProvider.js +0 -16
- package/src/shared/clientRuntime/index.js +0 -4
- package/src/shared/validators/command.js +0 -24
- package/src/shared/validators/httpValidatorsApi.js +0 -129
- package/src/shared/validators/jsonApiResponses.js +0 -3
- package/src/shared/validators/operationMessages.js +0 -318
- package/src/shared/validators/paginationQuery.js +0 -27
- package/src/shared/validators/resource.js +0 -41
- package/test/command.test.js +0 -66
- package/test/operationMessages.test.js +0 -197
- package/test/paginationQuery.test.js +0 -28
- package/test/providerRuntime.httpClient.test.js +0 -22
- package/test/providerRuntime.validators.test.js +0 -22
- package/test/resource.test.js +0 -128
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.244",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
"./shared/validators/errorResponses": "./src/shared/validators/errorResponses.js",
|
|
12
12
|
"./shared/validators/jsonApiResult": "./src/shared/validators/jsonApiResult.js",
|
|
13
13
|
"./shared/validators/jsonApiRouteTransport": "./src/shared/validators/jsonApiRouteTransport.js",
|
|
14
|
-
"./shared/validators/paginationQuery": "./src/shared/validators/paginationQuery.js",
|
|
15
|
-
"./shared/validators/command": "./src/shared/validators/command.js",
|
|
16
|
-
"./shared/validators/resource": "./src/shared/validators/resource.js",
|
|
17
|
-
"./shared/validators/operationMessages": "./src/shared/validators/operationMessages.js",
|
|
18
14
|
"./shared/validators/operationValidation": "./src/shared/validators/operationValidation.js"
|
|
19
15
|
},
|
|
20
16
|
"dependencies": {
|
|
@@ -23,36 +19,15 @@
|
|
|
23
19
|
"jskit": {
|
|
24
20
|
"kind": "runtime",
|
|
25
21
|
"capabilities": {
|
|
26
|
-
"provides": [
|
|
27
|
-
"validators.http",
|
|
28
|
-
"runtime.http-client"
|
|
29
|
-
],
|
|
22
|
+
"provides": [],
|
|
30
23
|
"requires": []
|
|
31
24
|
},
|
|
32
25
|
"runtime": {
|
|
33
26
|
"server": {
|
|
34
|
-
"providers": [
|
|
35
|
-
{
|
|
36
|
-
"entrypoint": "src/server/providers/HttpValidatorsProvider.js",
|
|
37
|
-
"export": "HttpValidatorsProvider"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"entrypoint": "src/server/providers/HttpClientProvider.js",
|
|
41
|
-
"export": "HttpClientProvider"
|
|
42
|
-
}
|
|
43
|
-
]
|
|
27
|
+
"providers": []
|
|
44
28
|
},
|
|
45
29
|
"client": {
|
|
46
|
-
"providers": [
|
|
47
|
-
{
|
|
48
|
-
"entrypoint": "src/client/providers/HttpValidatorsClientProvider.js",
|
|
49
|
-
"export": "HttpValidatorsClientProvider"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"entrypoint": "src/client/providers/HttpClientRuntimeClientProvider.js",
|
|
53
|
-
"export": "HttpClientRuntimeClientProvider"
|
|
54
|
-
}
|
|
55
|
-
]
|
|
30
|
+
"providers": []
|
|
56
31
|
}
|
|
57
32
|
},
|
|
58
33
|
"metadata": {
|
|
@@ -60,11 +35,7 @@
|
|
|
60
35
|
"surfaces": [
|
|
61
36
|
{
|
|
62
37
|
"subpath": "./client",
|
|
63
|
-
"summary": "Exports HTTP client runtime APIs (createHttpClient and transport helpers)
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"subpath": "./server",
|
|
67
|
-
"summary": "Provides named HTTP validation and server-side HTTP client capabilities."
|
|
38
|
+
"summary": "Exports HTTP client runtime APIs (createHttpClient, createTransientRetryHttpClient and transport helpers)."
|
|
68
39
|
},
|
|
69
40
|
{
|
|
70
41
|
"subpath": "./shared",
|
|
@@ -78,6 +49,6 @@
|
|
|
78
49
|
}
|
|
79
50
|
},
|
|
80
51
|
"peerDependencies": {
|
|
81
|
-
"@jskit-ai/kernel": "0.1.
|
|
52
|
+
"@jskit-ai/kernel": "0.1.246"
|
|
82
53
|
}
|
|
83
54
|
}
|
package/src/client/index.js
CHANGED
|
@@ -7,9 +7,8 @@ export {
|
|
|
7
7
|
} from "./validationErrors.js";
|
|
8
8
|
export {
|
|
9
9
|
createHttpError,
|
|
10
|
-
createNetworkError
|
|
10
|
+
createNetworkError,
|
|
11
|
+
isGenericHttpErrorMessage
|
|
11
12
|
} from "../shared/clientRuntime/errors.js";
|
|
12
13
|
export { DEFAULT_RETRYABLE_CSRF_ERROR_CODES, shouldRetryForCsrfFailure } from "../shared/clientRuntime/retry.js";
|
|
13
14
|
export { normalizeHeaderName, hasHeader, setHeaderIfMissing } from "../shared/clientRuntime/headers.js";
|
|
14
|
-
export { HttpValidatorsClientProvider } from "./providers/HttpValidatorsClientProvider.js";
|
|
15
|
-
export { HttpClientRuntimeClientProvider } from "./providers/HttpClientRuntimeClientProvider.js";
|
|
@@ -762,67 +762,11 @@ function createHttpClient(options = {}) {
|
|
|
762
762
|
csrfTokenCache = "";
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
function resetForTests() {
|
|
766
|
-
csrfTokenCache = "";
|
|
767
|
-
csrfFetchPromise = null;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function get(url, requestOptions = {}) {
|
|
771
|
-
return request(url, {
|
|
772
|
-
...requestOptions,
|
|
773
|
-
method: "GET"
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function post(url, body, requestOptions = {}) {
|
|
778
|
-
return request(url, {
|
|
779
|
-
...requestOptions,
|
|
780
|
-
method: "POST",
|
|
781
|
-
body
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function put(url, body, requestOptions = {}) {
|
|
786
|
-
return request(url, {
|
|
787
|
-
...requestOptions,
|
|
788
|
-
method: "PUT",
|
|
789
|
-
body
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
function patch(url, body, requestOptions = {}) {
|
|
794
|
-
return request(url, {
|
|
795
|
-
...requestOptions,
|
|
796
|
-
method: "PATCH",
|
|
797
|
-
body
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function del(url, requestOptions = {}) {
|
|
802
|
-
return request(url, {
|
|
803
|
-
...requestOptions,
|
|
804
|
-
method: "DELETE"
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
|
|
808
765
|
return {
|
|
809
766
|
request,
|
|
810
767
|
requestStream,
|
|
811
768
|
ensureCsrfToken,
|
|
812
|
-
clearCsrfTokenCache
|
|
813
|
-
resetForTests,
|
|
814
|
-
get,
|
|
815
|
-
post,
|
|
816
|
-
put,
|
|
817
|
-
patch,
|
|
818
|
-
delete: del,
|
|
819
|
-
__testables: {
|
|
820
|
-
fetchSessionForCsrf,
|
|
821
|
-
updateCsrfTokenFromPayload,
|
|
822
|
-
createHttpError,
|
|
823
|
-
createNetworkError,
|
|
824
|
-
readNdjsonStream
|
|
825
|
-
}
|
|
769
|
+
clearCsrfTokenCache
|
|
826
770
|
};
|
|
827
771
|
}
|
|
828
772
|
|
|
@@ -9,6 +9,42 @@ function standardErrorMessage(payload = {}) {
|
|
|
9
9
|
return String(payload.error || payload.message || nestedMessage || "").trim();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function genericHttpErrorMessage(status) {
|
|
13
|
+
return `Request failed with status ${status}.`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveErrorStatus(error) {
|
|
17
|
+
if (error == null || typeof error !== "object") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const hasStatus = Object.prototype.hasOwnProperty.call(error, "status");
|
|
22
|
+
const hasStatusCode = Object.prototype.hasOwnProperty.call(error, "statusCode");
|
|
23
|
+
if (!hasStatus && !hasStatusCode) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const status = Number(hasStatus ? error.status : error.statusCode);
|
|
28
|
+
return Number.isInteger(status) ? status : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// True when the error carries only the transport's own wording (a network
|
|
32
|
+
// failure or the status-only message createHttpError falls back to), so a
|
|
33
|
+
// caller's specific fallback copy is more useful than the message.
|
|
34
|
+
function isGenericHttpErrorMessage(error) {
|
|
35
|
+
const normalizedMessage = String(error?.message || "").trim();
|
|
36
|
+
if (!normalizedMessage) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const status = resolveErrorStatus(error);
|
|
41
|
+
if (status === 0) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return status != null && status >= 400 && normalizedMessage === genericHttpErrorMessage(status);
|
|
46
|
+
}
|
|
47
|
+
|
|
12
48
|
function createHttpError(response, data = {}) {
|
|
13
49
|
const payload = isRecord(data) ? data : {};
|
|
14
50
|
const message = standardErrorMessage(payload);
|
|
@@ -17,7 +53,7 @@ function createHttpError(response, data = {}) {
|
|
|
17
53
|
return createHttpError(response, jsonApiPayload);
|
|
18
54
|
}
|
|
19
55
|
|
|
20
|
-
const error = new Error(message ||
|
|
56
|
+
const error = new Error(message || genericHttpErrorMessage(response.status));
|
|
21
57
|
const normalizedFieldErrors = resolveFieldErrors(payload);
|
|
22
58
|
error.status = Number(response?.status || 0);
|
|
23
59
|
error.code = String(payload.code || "").trim() || null;
|
|
@@ -50,5 +86,6 @@ function createNetworkError(cause) {
|
|
|
50
86
|
|
|
51
87
|
export {
|
|
52
88
|
createHttpError,
|
|
53
|
-
createNetworkError
|
|
89
|
+
createNetworkError,
|
|
90
|
+
isGenericHttpErrorMessage
|
|
54
91
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeArray, normalizeObject, normalizeText, normalizeTransactionOutcome } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { isRecord, normalizeArray, normalizeObject, normalizeText, normalizeTransactionOutcome } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import {
|
|
3
3
|
JSON_API_CONTENT_TYPE,
|
|
4
4
|
createJsonApiDocument,
|
|
@@ -12,10 +12,6 @@ import {
|
|
|
12
12
|
simplifyJsonApiResourceWithRelationshipIds
|
|
13
13
|
} from "../support/jsonApiSimplify.js";
|
|
14
14
|
|
|
15
|
-
function isRecord(value) {
|
|
16
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
function normalizeTransportKind(transport = null) {
|
|
20
16
|
return String(transport?.kind || "").trim().toLowerCase();
|
|
21
17
|
}
|
|
@@ -137,25 +133,26 @@ function simplifyRelationshipResource(linkage = {}, options = {}) {
|
|
|
137
133
|
|
|
138
134
|
function simplifyResourceObject(resource = {}, options = {}) {
|
|
139
135
|
const normalizedResource = isRecord(resource) ? normalizeObject(resource) : {};
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
const lookupContainerKey = normalizeText(options.lookupContainerKey, {
|
|
144
|
-
fallback: "lookups"
|
|
145
|
-
});
|
|
146
|
-
const lookupFieldMap = options.lookupFieldMap || null;
|
|
147
|
-
const resourceKey = `${normalizeText(normalizedResource.type)}:${normalizeText(normalizedResource.id)}`;
|
|
148
|
-
if (resourceKey && options.inFlightKeys?.has(resourceKey)) {
|
|
149
|
-
return simplified;
|
|
150
|
-
}
|
|
136
|
+
const type = normalizeText(normalizedResource.type);
|
|
137
|
+
const id = normalizeText(normalizedResource.id);
|
|
138
|
+
const resourceKey = type && id ? `${type}:${id}` : "";
|
|
151
139
|
if (resourceKey && options.resourceCache?.has(resourceKey)) {
|
|
152
140
|
return options.resourceCache.get(resourceKey);
|
|
153
141
|
}
|
|
154
142
|
|
|
143
|
+
const lookupFieldMap = options.lookupFieldMap || null;
|
|
144
|
+
const simplified = simplifyJsonApiResourceWithRelationshipIds(normalizedResource, { lookupFieldMap });
|
|
145
|
+
if (resourceKey && options.inFlightKeys?.has(resourceKey)) {
|
|
146
|
+
return simplified;
|
|
147
|
+
}
|
|
155
148
|
if (resourceKey) {
|
|
156
149
|
options.inFlightKeys?.add(resourceKey);
|
|
157
150
|
}
|
|
158
151
|
|
|
152
|
+
const lookupContainerKey = normalizeText(options.lookupContainerKey, {
|
|
153
|
+
fallback: "lookups"
|
|
154
|
+
});
|
|
155
|
+
|
|
159
156
|
const lookups = {};
|
|
160
157
|
for (const [relationshipName, relationshipValue] of Object.entries(normalizeObject(normalizedResource.relationships))) {
|
|
161
158
|
const relationshipData = relationshipValue?.data;
|
|
@@ -234,6 +231,18 @@ function decodeJsonApiResourceResponse(payload, transport = null) {
|
|
|
234
231
|
}
|
|
235
232
|
|
|
236
233
|
const document = normalizeJsonApiDocument(payload);
|
|
234
|
+
if (document.kind === "unknown") {
|
|
235
|
+
throw new Error("Expected JSON:API response document.");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (normalizedTransport.responseKind === "meta") {
|
|
239
|
+
if (document.kind !== "meta") {
|
|
240
|
+
throw new Error("Expected JSON:API meta document.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return isRecord(document.meta) ? document.meta : {};
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
const simplifyOptions = {
|
|
238
247
|
lookupContainerKey: normalizedTransport.lookupContainerKey,
|
|
239
248
|
lookupFieldMap: normalizedTransport.lookupFieldMap,
|
|
@@ -241,9 +250,6 @@ function decodeJsonApiResourceResponse(payload, transport = null) {
|
|
|
241
250
|
resourceCache: new Map(),
|
|
242
251
|
inFlightKeys: new Set()
|
|
243
252
|
};
|
|
244
|
-
if (document.kind === "unknown") {
|
|
245
|
-
throw new Error("Expected JSON:API response document.");
|
|
246
|
-
}
|
|
247
253
|
|
|
248
254
|
if (normalizedTransport.responseKind === "collection") {
|
|
249
255
|
if (document.kind !== "collection") {
|
|
@@ -260,26 +266,8 @@ function decodeJsonApiResourceResponse(payload, transport = null) {
|
|
|
260
266
|
};
|
|
261
267
|
}
|
|
262
268
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
throw new Error("Expected JSON:API meta document.");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return isRecord(document.meta) ? document.meta : {};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (normalizedTransport.responseKind === "nullable-record") {
|
|
272
|
-
if (document.kind !== "resource") {
|
|
273
|
-
throw new Error("Expected JSON:API resource document.");
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (document.data != null) {
|
|
277
|
-
assertPrimaryDataType(document.data, normalizedTransport.responseType);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return document.data == null ? null : simplifyResourceObject(document.data, simplifyOptions);
|
|
281
|
-
}
|
|
282
|
-
|
|
269
|
+
// "record" and "nullable-record" (and any other kind) decode the same resource document;
|
|
270
|
+
// the server success schema, not this decoder, is what forbids null data for "record".
|
|
283
271
|
if (document.kind !== "resource") {
|
|
284
272
|
throw new Error("Expected JSON:API resource document.");
|
|
285
273
|
}
|
package/src/shared/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { createPaginationQuerySchema } from "./validators/paginationQuery.js";
|
|
2
1
|
export {
|
|
3
2
|
fieldErrorsFieldDefinition,
|
|
4
3
|
apiErrorDetailsSchema,
|
|
@@ -10,27 +9,9 @@ export {
|
|
|
10
9
|
fastifyDefaultErrorTransportSchema,
|
|
11
10
|
STANDARD_ERROR_STATUS_CODES,
|
|
12
11
|
createTransportResponseSchema,
|
|
13
|
-
|
|
14
|
-
withStandardErrorResponses,
|
|
15
|
-
enumSchema
|
|
12
|
+
withStandardErrorResponses
|
|
16
13
|
} from "./validators/errorResponses.js";
|
|
17
|
-
export {
|
|
18
|
-
createCursorPagedListResponseSchema,
|
|
19
|
-
createResource
|
|
20
|
-
} from "./validators/resource.js";
|
|
21
|
-
export { createCommand } from "./validators/command.js";
|
|
22
|
-
export {
|
|
23
|
-
resolveSchemaMessages,
|
|
24
|
-
resolveFieldSchema,
|
|
25
|
-
resolveIssueField,
|
|
26
|
-
resolveMissingRequiredFields,
|
|
27
|
-
resolveIssueMessageFromSchema,
|
|
28
|
-
mapOperationIssues
|
|
29
|
-
} from "./validators/operationMessages.js";
|
|
30
|
-
export {
|
|
31
|
-
validateOperationSection,
|
|
32
|
-
validateOperationInput
|
|
33
|
-
} from "./validators/operationValidation.js";
|
|
14
|
+
export { validateOperationSection } from "./validators/operationValidation.js";
|
|
34
15
|
export {
|
|
35
16
|
JSON_API_CONTENT_TYPE,
|
|
36
17
|
createJsonApiDocument,
|
|
@@ -114,10 +114,6 @@ const sharedErrorTransportDefinitions = {
|
|
|
114
114
|
...embeddedApiErrorTransportSchema.definitions
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
-
function passthroughErrorResponses(successResponses) {
|
|
118
|
-
return successResponses;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
117
|
function withStandardErrorResponses(successResponses, { includeValidation400 = false } = {}) {
|
|
122
118
|
const responses = {
|
|
123
119
|
...successResponses
|
|
@@ -152,12 +148,6 @@ function withStandardErrorResponses(successResponses, { includeValidation400 = f
|
|
|
152
148
|
return responses;
|
|
153
149
|
}
|
|
154
150
|
|
|
155
|
-
function enumSchema(values) {
|
|
156
|
-
return {
|
|
157
|
-
anyOf: values.map((value) => ({ const: value }))
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
151
|
export {
|
|
162
152
|
fieldErrorsFieldDefinition,
|
|
163
153
|
apiErrorDetailsSchema,
|
|
@@ -169,7 +159,5 @@ export {
|
|
|
169
159
|
fastifyDefaultErrorTransportSchema,
|
|
170
160
|
STANDARD_ERROR_STATUS_CODES,
|
|
171
161
|
createTransportResponseSchema,
|
|
172
|
-
|
|
173
|
-
withStandardErrorResponses,
|
|
174
|
-
enumSchema
|
|
162
|
+
withStandardErrorResponses
|
|
175
163
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { isRecord, normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import {
|
|
3
3
|
normalizeJsonApiFieldList,
|
|
4
4
|
normalizeJsonApiFieldsets
|
|
@@ -12,10 +12,6 @@ const JSON_API_QUERY_SORT_KEY = "sort";
|
|
|
12
12
|
const JSON_API_FILTER_PREFIX = "filter[";
|
|
13
13
|
const JSON_API_FIELDS_PREFIX = "fields[";
|
|
14
14
|
|
|
15
|
-
function isRecord(value) {
|
|
16
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
function normalizeQueryKey(key = "") {
|
|
20
16
|
return String(key || "").trim();
|
|
21
17
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { normalizeArray } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { isRecord, normalizeArray, normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
2
|
import {
|
|
4
3
|
resolveSchemaTransportSchemaDefinition
|
|
5
4
|
} from "@jskit-ai/kernel/shared/validators";
|
|
@@ -134,10 +133,6 @@ const JSON_API_ERROR_DOCUMENT_SCHEMA = Object.freeze({
|
|
|
134
133
|
}
|
|
135
134
|
});
|
|
136
135
|
|
|
137
|
-
function isRecord(value) {
|
|
138
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
136
|
function createJsonApiTransportError(statusCode, message, code) {
|
|
142
137
|
const error = new Error(String(message || "JSON:API transport error."));
|
|
143
138
|
error.status = Number(statusCode) || 500;
|
|
@@ -492,10 +487,7 @@ function createJsonApiResourceSuccessTransportSchema({
|
|
|
492
487
|
);
|
|
493
488
|
|
|
494
489
|
const documentProperties = {};
|
|
495
|
-
const documentDefinitions = {
|
|
496
|
-
...resourceTransport.definitions,
|
|
497
|
-
...embeddedResource.definitions
|
|
498
|
-
};
|
|
490
|
+
const documentDefinitions = { ...embeddedResource.definitions };
|
|
499
491
|
|
|
500
492
|
if (normalizedKind === "collection") {
|
|
501
493
|
documentProperties.data = {
|
|
@@ -683,6 +675,35 @@ function createJsonApiResourceRouteTransport({
|
|
|
683
675
|
return createJsonApiResourceObject(resourceOptions);
|
|
684
676
|
}
|
|
685
677
|
|
|
678
|
+
// Collections always resolve meta (falling back to the nextCursor page meta); records add
|
|
679
|
+
// meta only when the caller supplies getDocumentMeta.
|
|
680
|
+
function buildDocumentOptions(sourcePayload, context, data, { resolveMeta = null } = {}) {
|
|
681
|
+
const documentOptions = { data };
|
|
682
|
+
|
|
683
|
+
if (typeof getIncluded === "function") {
|
|
684
|
+
const included = normalizeArray(getIncluded(sourcePayload, context));
|
|
685
|
+
if (included.length > 0) {
|
|
686
|
+
documentOptions.included = included;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (typeof getDocumentLinks === "function") {
|
|
691
|
+
const links = getDocumentLinks(sourcePayload, context);
|
|
692
|
+
if (links !== undefined) {
|
|
693
|
+
documentOptions.links = links;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (typeof resolveMeta === "function") {
|
|
698
|
+
const meta = resolveMeta(sourcePayload, context);
|
|
699
|
+
if (meta !== undefined) {
|
|
700
|
+
documentOptions.meta = meta;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return documentOptions;
|
|
705
|
+
}
|
|
706
|
+
|
|
686
707
|
return Object.freeze({
|
|
687
708
|
kind: "jsonapi-resource",
|
|
688
709
|
contentType: JSON_API_CONTENT_TYPE,
|
|
@@ -801,26 +822,12 @@ function createJsonApiResourceRouteTransport({
|
|
|
801
822
|
|
|
802
823
|
if (normalizedSuccessKind === "collection") {
|
|
803
824
|
const items = normalizeArray(resolveCollectionItems(sourcePayload, context));
|
|
804
|
-
const documentOptions =
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (included.length > 0) {
|
|
811
|
-
documentOptions.included = included;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const links = typeof getDocumentLinks === "function" ? getDocumentLinks(sourcePayload, context) : undefined;
|
|
816
|
-
if (links !== undefined) {
|
|
817
|
-
documentOptions.links = links;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const meta = resolveDocumentMeta(sourcePayload, context);
|
|
821
|
-
if (meta !== undefined) {
|
|
822
|
-
documentOptions.meta = meta;
|
|
823
|
-
}
|
|
825
|
+
const documentOptions = buildDocumentOptions(
|
|
826
|
+
sourcePayload,
|
|
827
|
+
context,
|
|
828
|
+
items.map((entry) => buildResourceObject(entry, context)),
|
|
829
|
+
{ resolveMeta: resolveDocumentMeta }
|
|
830
|
+
);
|
|
824
831
|
|
|
825
832
|
return createJsonApiDocument(documentOptions);
|
|
826
833
|
}
|
|
@@ -835,30 +842,12 @@ function createJsonApiResourceRouteTransport({
|
|
|
835
842
|
throw createJsonApiTransportError(500, "JSON:API resource response requires a record payload.", "jsonapi_record_missing");
|
|
836
843
|
}
|
|
837
844
|
|
|
838
|
-
const documentOptions =
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
if (included.length > 0) {
|
|
845
|
-
documentOptions.included = included;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (typeof getDocumentLinks === "function") {
|
|
850
|
-
const links = getDocumentLinks(sourcePayload, context);
|
|
851
|
-
if (links !== undefined) {
|
|
852
|
-
documentOptions.links = links;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
if (typeof getDocumentMeta === "function") {
|
|
857
|
-
const meta = getDocumentMeta(sourcePayload, context);
|
|
858
|
-
if (meta !== undefined) {
|
|
859
|
-
documentOptions.meta = meta;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
845
|
+
const documentOptions = buildDocumentOptions(
|
|
846
|
+
sourcePayload,
|
|
847
|
+
context,
|
|
848
|
+
buildResourceObject(sourcePayload, context),
|
|
849
|
+
{ resolveMeta: getDocumentMeta }
|
|
850
|
+
);
|
|
862
851
|
|
|
863
852
|
return createJsonApiDocument(documentOptions);
|
|
864
853
|
},
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { normalizeArray, normalizeObject, normalizeText, normalizeTransactionOutcome } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { isRecord, normalizeArray, normalizeObject, normalizeText, normalizeTransactionOutcome } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { simplifyJsonApiResourceWithRelationshipIds } from "../support/jsonApiSimplify.js";
|
|
3
3
|
|
|
4
4
|
const JSON_API_CONTENT_TYPE = "application/vnd.api+json";
|
|
5
5
|
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
function normalizeMediaType(contentType = "") {
|
|
11
7
|
return String(contentType || "")
|
|
12
8
|
.split(";")[0]
|
|
@@ -8,41 +8,18 @@ function resolveOperationSection(operation = {}, section = "body") {
|
|
|
8
8
|
return operation[section] == null ? null : operation[section];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function buildValidationSuccessResult(value) {
|
|
12
|
-
return {
|
|
13
|
-
ok: true,
|
|
14
|
-
value,
|
|
15
|
-
normalized: value,
|
|
16
|
-
fieldErrors: {},
|
|
17
|
-
globalErrors: [],
|
|
18
|
-
issues: []
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
11
|
function isFieldValidationError(error) {
|
|
23
12
|
return Boolean(error?.fieldErrors && typeof error.fieldErrors === "object");
|
|
24
13
|
}
|
|
25
14
|
|
|
26
|
-
function buildValidationFailureResult(error, normalized) {
|
|
27
|
-
return {
|
|
28
|
-
ok: false,
|
|
29
|
-
value: null,
|
|
30
|
-
normalized,
|
|
31
|
-
fieldErrors: error.fieldErrors,
|
|
32
|
-
globalErrors: [],
|
|
33
|
-
issues: []
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
15
|
function validateOperationSection({
|
|
38
16
|
operation = {},
|
|
39
17
|
section = "body",
|
|
40
|
-
value
|
|
41
|
-
context = {}
|
|
18
|
+
value
|
|
42
19
|
} = {}) {
|
|
43
20
|
const sectionDefinition = resolveOperationSection(operation, section);
|
|
44
21
|
if (!sectionDefinition) {
|
|
45
|
-
return
|
|
22
|
+
return { ok: true, value, fieldErrors: {} };
|
|
46
23
|
}
|
|
47
24
|
|
|
48
25
|
try {
|
|
@@ -51,78 +28,14 @@ function validateOperationSection({
|
|
|
51
28
|
context: `operation section "${section}"`
|
|
52
29
|
});
|
|
53
30
|
|
|
54
|
-
return
|
|
31
|
+
return { ok: true, value: normalized, fieldErrors: {} };
|
|
55
32
|
} catch (error) {
|
|
56
33
|
if (!isFieldValidationError(error)) {
|
|
57
34
|
throw error;
|
|
58
35
|
}
|
|
59
36
|
|
|
60
|
-
return
|
|
37
|
+
return { ok: false, value: null, fieldErrors: error.fieldErrors };
|
|
61
38
|
}
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
operation = {},
|
|
66
|
-
input = {},
|
|
67
|
-
context = {}
|
|
68
|
-
} = {}) {
|
|
69
|
-
const source = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
70
|
-
const sectionResults = {
|
|
71
|
-
params: validateOperationSection({
|
|
72
|
-
operation,
|
|
73
|
-
section: "params",
|
|
74
|
-
value: source.params,
|
|
75
|
-
context
|
|
76
|
-
}),
|
|
77
|
-
query: validateOperationSection({
|
|
78
|
-
operation,
|
|
79
|
-
section: "query",
|
|
80
|
-
value: source.query,
|
|
81
|
-
context
|
|
82
|
-
}),
|
|
83
|
-
body: validateOperationSection({
|
|
84
|
-
operation,
|
|
85
|
-
section: "body",
|
|
86
|
-
value: source.body,
|
|
87
|
-
context
|
|
88
|
-
})
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const fieldErrors = {
|
|
92
|
-
...sectionResults.params.fieldErrors,
|
|
93
|
-
...sectionResults.query.fieldErrors,
|
|
94
|
-
...sectionResults.body.fieldErrors
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const globalErrors = [
|
|
98
|
-
...sectionResults.params.globalErrors,
|
|
99
|
-
...sectionResults.query.globalErrors,
|
|
100
|
-
...sectionResults.body.globalErrors
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
ok: sectionResults.params.ok && sectionResults.query.ok && sectionResults.body.ok,
|
|
105
|
-
value: {
|
|
106
|
-
params: sectionResults.params.value,
|
|
107
|
-
query: sectionResults.query.value,
|
|
108
|
-
body: sectionResults.body.value
|
|
109
|
-
},
|
|
110
|
-
normalized: {
|
|
111
|
-
params: sectionResults.params.normalized,
|
|
112
|
-
query: sectionResults.query.normalized,
|
|
113
|
-
body: sectionResults.body.normalized
|
|
114
|
-
},
|
|
115
|
-
fieldErrors,
|
|
116
|
-
globalErrors,
|
|
117
|
-
issues: [
|
|
118
|
-
...sectionResults.params.issues,
|
|
119
|
-
...sectionResults.query.issues,
|
|
120
|
-
...sectionResults.body.issues
|
|
121
|
-
]
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export {
|
|
126
|
-
validateOperationSection,
|
|
127
|
-
validateOperationInput
|
|
128
|
-
};
|
|
41
|
+
export { validateOperationSection };
|