@jskit-ai/kernel 0.1.244 → 0.1.246
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/client/appConfig.js +2 -1
- package/client/asyncModuleRecovery.js +2 -4
- package/client/index.d.ts +18 -76
- package/client/index.js +7 -2
- package/client/logging.js +8 -2
- package/client/moduleBootstrap.js +41 -33
- package/client/moduleBootstrap.test.js +35 -0
- package/client/shellBootstrap.js +38 -10
- package/client/shellBootstrap.test.js +85 -6
- package/client/shellRouting.js +16 -61
- package/client/shellRouting.test.js +30 -1
- package/client/vite/clientBootstrapPlugin.js +0 -6
- package/client/vite/clientBootstrapPlugin.test.js +2 -3
- package/internal/node/installedPackages.js +14 -58
- package/package.json +2 -4
- package/server/actions/actionCatalogue.js +13 -34
- package/{shared → server}/actions/actionContributorHelpers.js +0 -5
- package/{shared → server}/actions/actionDefinitions.js +24 -131
- package/{shared → server}/actions/actionDefinitions.test.js +75 -21
- package/server/actions/actionEvents.js +97 -2
- package/server/actions/actionEvents.test.js +79 -0
- package/{shared → server}/actions/executionContext.js +3 -13
- package/server/actions/idempotency.js +24 -0
- package/server/actions/pipeline.js +87 -0
- package/{shared → server}/actions/policies.js +20 -33
- package/{shared → server}/actions/policies.test.js +6 -6
- package/{shared → server}/actions/registry.js +3 -22
- package/{shared → server}/actions/registry.test.js +70 -32
- package/server/actions/requestMeta.js +17 -0
- package/server/http/capabilityHttpRuntime.js +1 -1
- package/server/http/capabilityHttpRuntime.test.js +56 -0
- package/server/http/errorHandling.js +291 -0
- package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
- package/server/http/lib/routeRegistration.js +36 -150
- package/server/http/lib/routeTransport.js +2 -0
- package/server/http/lib/routeValidator.js +5 -47
- package/server/http/lib/routeValidator.test.js +15 -17
- package/server/http/lib/router.js +10 -94
- package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
- package/server/platform/providerRuntime/helpers.js +1 -34
- package/server/runtime/integers.js +1 -5
- package/server/runtime/serviceAuthorization.js +4 -9
- package/server/support/appConfig.js +2 -1
- package/server/support/appConfigFiles.js +1 -38
- package/server/support/appConfigFiles.test.js +3 -39
- package/server/support/index.js +1 -20
- package/shared/capabilities/defineProvider.js +1 -1
- package/shared/capabilities/index.js +1 -1
- package/shared/index.js +1 -1
- package/shared/support/index.js +1 -4
- package/shared/support/mobileConfig.js +99 -0
- package/shared/support/mobileConfig.test.js +113 -0
- package/shared/support/normalize.js +0 -108
- package/shared/support/normalize.test.js +0 -134
- package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
- package/shared/support/returnToPath.js +16 -20
- package/shared/support/returnToPath.test.js +5 -33
- package/shared/support/shellLayoutTargets.js +2 -140
- package/shared/support/shellLayoutTargets.test.js +1 -110
- package/shared/support/sorting.js +1 -9
- package/shared/surface/apiPaths.js +1 -82
- package/shared/surface/paths.js +38 -171
- package/shared/surface/registry.js +2 -1
- package/shared/surface/runtime.js +18 -109
- package/shared/surface/runtime.test.js +57 -0
- package/shared/validators/index.js +1 -4
- package/shared/validators/jsonRestSchemaSupport.js +0 -9
- package/shared/validators/recordIdParamsValidator.js +0 -6
- package/shared/validators/schemaDefinitions.js +4 -30
- package/shared/validators/schemaPayloadValidation.js +2 -6
- package/test/barrelExposure.test.js +5 -8
- package/test/exportsContract.test.js +1 -3
- package/client/mobileLaunchRouting.js +0 -231
- package/client/mobileLaunchRouting.test.js +0 -230
- package/server/http/lib/controller.js +0 -183
- package/server/http/lib/controller.test.js +0 -143
- package/server/http/lib/index.js +0 -5
- package/server/runtime/canonicalJson.js +0 -74
- package/server/runtime/entityChangeEvents.js +0 -307
- package/server/runtime/entityChangeEvents.test.js +0 -274
- package/server/runtime/fastifyBootstrap.js +0 -553
- package/server/runtime/moduleConfig.js +0 -223
- package/server/runtime/moduleConfig.test.js +0 -165
- package/server/runtime/pagination.js +0 -13
- package/server/runtime/realtimeNormalization.js +0 -21
- package/server/runtime/routeUtils.js +0 -20
- package/server/runtime/securityAudit.js +0 -269
- package/server/runtime/securityAudit.test.js +0 -41
- package/server/support/pageTargets.js +0 -772
- package/server/support/pageTargets.test.js +0 -659
- package/server/support/shellOutlets.js +0 -576
- package/server/support/shellOutlets.test.js +0 -336
- package/server/surface/index.js +0 -18
- package/shared/actions/audit.js +0 -7
- package/shared/actions/idempotency.js +0 -62
- package/shared/actions/observability.js +0 -10
- package/shared/actions/pipeline.js +0 -309
- package/shared/actions/requestMeta.js +0 -36
- package/shared/actions/textNormalization.js +0 -3
- package/shared/actions/withActionDefaults.js +0 -34
- /package/{shared → server}/actions/executionContext.test.js +0 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
|
-
import { AppError, isAppError } from "
|
|
4
|
+
import { AppError, isAppError } from "../runtime/errors.js";
|
|
5
5
|
import {
|
|
6
6
|
normalizeBodylessContentTypeHeader,
|
|
7
7
|
registerApiErrorHandler,
|
|
8
8
|
registerBodylessContentTypeNormalizer,
|
|
9
|
-
registerJsonApiContentTypeParser
|
|
10
|
-
|
|
11
|
-
} from "./fastifyBootstrap.js";
|
|
9
|
+
registerJsonApiContentTypeParser
|
|
10
|
+
} from "./errorHandling.js";
|
|
12
11
|
|
|
13
12
|
function createFastifyStub() {
|
|
14
|
-
|
|
13
|
+
const fastify = {
|
|
15
14
|
errorHandler: null,
|
|
16
15
|
hooks: {},
|
|
17
16
|
contentTypeParsers: new Map(),
|
|
17
|
+
logged: [],
|
|
18
18
|
log: {
|
|
19
|
-
error() {
|
|
19
|
+
error(fields, message) {
|
|
20
|
+
this.owner.logged.push({ level: "error", fields, message });
|
|
21
|
+
},
|
|
22
|
+
info(fields, message) {
|
|
23
|
+
this.owner.logged.push({ level: "info", fields, message });
|
|
24
|
+
}
|
|
20
25
|
},
|
|
21
26
|
setErrorHandler(handler) {
|
|
22
27
|
this.errorHandler = handler;
|
|
@@ -24,6 +29,9 @@ function createFastifyStub() {
|
|
|
24
29
|
addHook(name, handler) {
|
|
25
30
|
this.hooks[name] = handler;
|
|
26
31
|
},
|
|
32
|
+
getDefaultJsonParser() {
|
|
33
|
+
return function parseJsonBody() {};
|
|
34
|
+
},
|
|
27
35
|
hasContentTypeParser(contentType) {
|
|
28
36
|
return this.contentTypeParsers.has(String(contentType || "").trim().toLowerCase());
|
|
29
37
|
},
|
|
@@ -34,6 +42,32 @@ function createFastifyStub() {
|
|
|
34
42
|
});
|
|
35
43
|
}
|
|
36
44
|
};
|
|
45
|
+
fastify.log.owner = fastify;
|
|
46
|
+
return fastify;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function transportRequest() {
|
|
50
|
+
return {
|
|
51
|
+
routeOptions: {
|
|
52
|
+
config: {
|
|
53
|
+
transport: {
|
|
54
|
+
kind: "jsonapi-resource",
|
|
55
|
+
contentType: "application/vnd.api+json",
|
|
56
|
+
error(currentError, { statusCode, code }) {
|
|
57
|
+
return {
|
|
58
|
+
errors: [
|
|
59
|
+
{
|
|
60
|
+
status: String(statusCode),
|
|
61
|
+
code,
|
|
62
|
+
title: currentError.message
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
function createReplyStub() {
|
|
@@ -227,22 +261,7 @@ test("registerApiErrorHandler uses route transport error serializer when present
|
|
|
227
261
|
|
|
228
262
|
fastify.errorHandler(
|
|
229
263
|
error,
|
|
230
|
-
|
|
231
|
-
routeTransport: {
|
|
232
|
-
contentType: "application/vnd.api+json",
|
|
233
|
-
error(currentError, { statusCode, code }) {
|
|
234
|
-
return {
|
|
235
|
-
errors: [
|
|
236
|
-
{
|
|
237
|
-
status: String(statusCode),
|
|
238
|
-
code,
|
|
239
|
-
title: currentError.message
|
|
240
|
-
}
|
|
241
|
-
]
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
},
|
|
264
|
+
transportRequest(),
|
|
246
265
|
reply
|
|
247
266
|
);
|
|
248
267
|
|
|
@@ -259,55 +278,46 @@ test("registerApiErrorHandler uses route transport error serializer when present
|
|
|
259
278
|
});
|
|
260
279
|
});
|
|
261
280
|
|
|
262
|
-
test("
|
|
281
|
+
test("transport routes log 5xx AppErrors and keep their headers", () => {
|
|
263
282
|
const fastify = createFastifyStub();
|
|
264
283
|
registerApiErrorHandler(fastify, { isAppError });
|
|
265
284
|
|
|
266
285
|
const reply = createReplyStub();
|
|
267
|
-
const error = new AppError(
|
|
268
|
-
code: "
|
|
286
|
+
const error = new AppError(503, "Try again shortly.", {
|
|
287
|
+
code: "service_unavailable",
|
|
288
|
+
headers: {
|
|
289
|
+
"Retry-After": "5"
|
|
290
|
+
}
|
|
269
291
|
});
|
|
270
292
|
|
|
271
|
-
fastify.errorHandler(
|
|
272
|
-
error,
|
|
273
|
-
{
|
|
274
|
-
routeOptions: {
|
|
275
|
-
config: {
|
|
276
|
-
transport: {
|
|
277
|
-
kind: "jsonapi-resource",
|
|
278
|
-
contentType: "application/vnd.api+json",
|
|
279
|
-
runtime: {
|
|
280
|
-
contentType: "application/vnd.api+json",
|
|
281
|
-
error(currentError, { statusCode, code }) {
|
|
282
|
-
return {
|
|
283
|
-
errors: [
|
|
284
|
-
{
|
|
285
|
-
status: String(statusCode),
|
|
286
|
-
code,
|
|
287
|
-
title: currentError.message
|
|
288
|
-
}
|
|
289
|
-
]
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
reply
|
|
298
|
-
);
|
|
293
|
+
fastify.errorHandler(error, transportRequest(), reply);
|
|
299
294
|
|
|
300
|
-
assert.equal(reply.statusCode,
|
|
295
|
+
assert.equal(reply.statusCode, 503);
|
|
296
|
+
assert.equal(reply.headers["Retry-After"], "5");
|
|
301
297
|
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
302
|
-
assert.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
298
|
+
assert.equal(reply.payload.errors[0].code, "service_unavailable");
|
|
299
|
+
assert.deepEqual(fastify.logged.map(({ level, message }) => [level, message]), [["error", "AppError 5xx"]]);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("Fastify client errors are logged below error level and server errors at error level", () => {
|
|
303
|
+
const fastify = createFastifyStub();
|
|
304
|
+
registerApiErrorHandler(fastify, { isAppError });
|
|
305
|
+
|
|
306
|
+
const tooLarge = Object.assign(new Error("Request body is too large"), {
|
|
307
|
+
statusCode: 413,
|
|
308
|
+
code: "FST_ERR_CTP_BODY_TOO_LARGE"
|
|
310
309
|
});
|
|
310
|
+
const tooLargeReply = createReplyStub();
|
|
311
|
+
fastify.errorHandler(tooLarge, transportRequest(), tooLargeReply);
|
|
312
|
+
assert.equal(tooLargeReply.statusCode, 413);
|
|
313
|
+
assert.equal(tooLargeReply.payload.errors[0].code, "FST_ERR_CTP_BODY_TOO_LARGE");
|
|
314
|
+
|
|
315
|
+
fastify.errorHandler(new Error("Unexpected DB failure"), {}, createReplyStub());
|
|
316
|
+
|
|
317
|
+
assert.deepEqual(fastify.logged.map(({ level, fields }) => [level, fields.statusCode]), [
|
|
318
|
+
["info", 413],
|
|
319
|
+
["error", undefined]
|
|
320
|
+
]);
|
|
311
321
|
});
|
|
312
322
|
|
|
313
323
|
test("registerJsonApiContentTypeParser installs the JSON:API media type parser once", () => {
|
|
@@ -358,61 +368,3 @@ test("registerBodylessContentTypeNormalizer installs the request hook once", asy
|
|
|
358
368
|
"content-length": "0"
|
|
359
369
|
});
|
|
360
370
|
});
|
|
361
|
-
|
|
362
|
-
test("registerRequestLoggingHooks uses configured default surface when getSurface is absent", async () => {
|
|
363
|
-
const fastify = createFastifyStub();
|
|
364
|
-
let loggedPayload = null;
|
|
365
|
-
|
|
366
|
-
registerRequestLoggingHooks(fastify, {
|
|
367
|
-
defaultSurfaceId: "home"
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
const request = {
|
|
371
|
-
id: "req-home",
|
|
372
|
-
method: "GET",
|
|
373
|
-
routeOptions: {
|
|
374
|
-
url: "/status"
|
|
375
|
-
},
|
|
376
|
-
log: {
|
|
377
|
-
info(payload) {
|
|
378
|
-
loggedPayload = payload;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
const reply = {
|
|
383
|
-
statusCode: 204
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
await fastify.hooks.onRequest(request);
|
|
387
|
-
await fastify.hooks.onResponse(request, reply);
|
|
388
|
-
|
|
389
|
-
assert.equal(loggedPayload.surface, "home");
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test("registerRequestLoggingHooks leaves surface empty when no surface default is configured", async () => {
|
|
393
|
-
const fastify = createFastifyStub();
|
|
394
|
-
let loggedPayload = null;
|
|
395
|
-
|
|
396
|
-
registerRequestLoggingHooks(fastify);
|
|
397
|
-
|
|
398
|
-
const request = {
|
|
399
|
-
id: "req-public",
|
|
400
|
-
method: "GET",
|
|
401
|
-
routeOptions: {
|
|
402
|
-
url: "/status"
|
|
403
|
-
},
|
|
404
|
-
log: {
|
|
405
|
-
info(payload) {
|
|
406
|
-
loggedPayload = payload;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
const reply = {
|
|
411
|
-
statusCode: 204
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
await fastify.hooks.onRequest(request);
|
|
415
|
-
await fastify.hooks.onResponse(request, reply);
|
|
416
|
-
|
|
417
|
-
assert.equal(loggedPayload.surface, "");
|
|
418
|
-
});
|
|
@@ -3,49 +3,30 @@ import { AppError } from "../../runtime/errors.js";
|
|
|
3
3
|
import { defaultApplyRoutePolicy } from "../../support/routePolicyConfig.js";
|
|
4
4
|
import { resolveDefaultSurfaceId } from "../../support/appConfig.js";
|
|
5
5
|
import { defaultMissingHandler } from "../../support/defaultMissingHandler.js";
|
|
6
|
-
import { registerJsonApiContentTypeParser } from "
|
|
6
|
+
import { registerJsonApiContentTypeParser } from "../errorHandling.js";
|
|
7
7
|
import { RouteRegistrationError } from "./errors.js";
|
|
8
8
|
import { executeMiddlewareStack, normalizeRuntimeMiddlewareConfig, resolveRouteMiddlewareHandlers } from "./middlewareRuntime.js";
|
|
9
9
|
import { attachDirectRequestActionExecutor } from "../directRequestActionExecutor.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveRouteLabel } from "./routeSupport.js";
|
|
11
|
+
import { JSON_API_CONTENT_TYPE } from "./routeTransport.js";
|
|
11
12
|
|
|
12
13
|
const { structuredClone: cloneRouteSchema } = globalThis;
|
|
13
14
|
const UNSAFE_BODY_METHODS = Object.freeze(["POST", "PUT", "PATCH"]);
|
|
14
|
-
const
|
|
15
|
+
const REQUEST_INPUT_KEYS = Object.freeze(["body", "query", "params"]);
|
|
15
16
|
|
|
16
17
|
function toFastifyRouteOptions(route) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (shouldStripFastifyBodySchema(sourceRoute, schema)) {
|
|
18
|
+
const schema = cloneRouteSchema(route.schema);
|
|
19
|
+
if (shouldStripFastifyBodySchema(route, schema)) {
|
|
20
20
|
delete schema.body;
|
|
21
21
|
}
|
|
22
|
-
const existingConfig = normalizeObject(sourceRoute.config);
|
|
23
|
-
const transportKind = normalizeText(sourceRoute?.transport?.kind).toLowerCase();
|
|
24
|
-
const existingTransportConfig =
|
|
25
|
-
existingConfig.transport && typeof existingConfig.transport === "object" && !Array.isArray(existingConfig.transport)
|
|
26
|
-
? normalizeObject(existingConfig.transport)
|
|
27
|
-
: {};
|
|
28
22
|
return {
|
|
29
|
-
method:
|
|
30
|
-
url:
|
|
23
|
+
method: route.method,
|
|
24
|
+
url: route.path,
|
|
31
25
|
...(schema ? { schema } : {}),
|
|
32
|
-
...(
|
|
26
|
+
...(route.bodyLimit ? { bodyLimit: route.bodyLimit } : {}),
|
|
33
27
|
config: {
|
|
34
|
-
...
|
|
35
|
-
...(
|
|
36
|
-
? {
|
|
37
|
-
transport: {
|
|
38
|
-
...existingTransportConfig,
|
|
39
|
-
kind: transportKind,
|
|
40
|
-
runtime: sourceRoute.transport,
|
|
41
|
-
...(normalizeText(sourceRoute?.transport?.contentType)
|
|
42
|
-
? {
|
|
43
|
-
contentType: normalizeText(sourceRoute.transport.contentType)
|
|
44
|
-
}
|
|
45
|
-
: {})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
: {})
|
|
28
|
+
...normalizeObject(route.config),
|
|
29
|
+
...(route.transport ? { transport: route.transport } : {})
|
|
49
30
|
}
|
|
50
31
|
};
|
|
51
32
|
}
|
|
@@ -112,19 +93,6 @@ function enforceRequestContentType({ request = null, route = null, transport = n
|
|
|
112
93
|
});
|
|
113
94
|
}
|
|
114
95
|
|
|
115
|
-
function attachRouteTransport(request, transport = null) {
|
|
116
|
-
if (!request || !transport) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
Object.defineProperty(request, "routeTransport", {
|
|
121
|
-
value: transport,
|
|
122
|
-
enumerable: false,
|
|
123
|
-
configurable: true,
|
|
124
|
-
writable: false
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
96
|
function replyHasHeader(reply, name = "") {
|
|
129
97
|
const normalizedName = String(name || "").trim().toLowerCase();
|
|
130
98
|
if (!normalizedName || !reply) {
|
|
@@ -146,75 +114,21 @@ function replyHasHeader(reply, name = "") {
|
|
|
146
114
|
return false;
|
|
147
115
|
}
|
|
148
116
|
|
|
149
|
-
function
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
throw new RouteRegistrationError(
|
|
157
|
-
`Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} input must be an object.`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const normalized = {};
|
|
162
|
-
for (const key of ["body", "query", "params"]) {
|
|
163
|
-
if (!Object.hasOwn(routeInput, key)) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const transform = routeInput[key];
|
|
168
|
-
if (transform == null) {
|
|
169
|
-
continue;
|
|
117
|
+
async function buildRequestInput(request, { inputTransforms = null, transportInputTransforms = null } = {}) {
|
|
118
|
+
const input = {};
|
|
119
|
+
for (const key of REQUEST_INPUT_KEYS) {
|
|
120
|
+
let value = request?.[key];
|
|
121
|
+
const transportTransform = transportInputTransforms?.[key];
|
|
122
|
+
if (typeof transportTransform === "function" && (key !== "body" || value !== undefined)) {
|
|
123
|
+
value = await transportTransform(value, request);
|
|
170
124
|
}
|
|
171
|
-
|
|
172
|
-
if (typeof
|
|
173
|
-
|
|
174
|
-
`Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} input.${key} must be a function.`
|
|
175
|
-
);
|
|
125
|
+
const inputTransform = inputTransforms?.[key];
|
|
126
|
+
if (typeof inputTransform === "function") {
|
|
127
|
+
value = await inputTransform(value, request);
|
|
176
128
|
}
|
|
177
|
-
|
|
178
|
-
normalized[key] = transform;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return Object.freeze(normalized);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function buildRequestInput({ request = null, inputTransforms = null, transportInputTransforms = null } = {}) {
|
|
185
|
-
const transforms = inputTransforms && typeof inputTransforms === "object" ? inputTransforms : {};
|
|
186
|
-
const transportTransforms =
|
|
187
|
-
transportInputTransforms && typeof transportInputTransforms === "object" ? transportInputTransforms : {};
|
|
188
|
-
|
|
189
|
-
let body = request?.body;
|
|
190
|
-
if (body !== undefined && typeof transportTransforms.body === "function") {
|
|
191
|
-
body = await transportTransforms.body(body, request);
|
|
192
|
-
}
|
|
193
|
-
if (typeof transforms.body === "function") {
|
|
194
|
-
body = await transforms.body(body, request);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
let query = request?.query;
|
|
198
|
-
if (typeof transportTransforms.query === "function") {
|
|
199
|
-
query = await transportTransforms.query(query, request);
|
|
200
|
-
}
|
|
201
|
-
if (typeof transforms.query === "function") {
|
|
202
|
-
query = await transforms.query(query, request);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
let params = request?.params;
|
|
206
|
-
if (typeof transportTransforms.params === "function") {
|
|
207
|
-
params = await transportTransforms.params(params, request);
|
|
208
|
-
}
|
|
209
|
-
if (typeof transforms.params === "function") {
|
|
210
|
-
params = await transforms.params(params, request);
|
|
129
|
+
input[key] = value;
|
|
211
130
|
}
|
|
212
|
-
|
|
213
|
-
return Object.freeze({
|
|
214
|
-
body,
|
|
215
|
-
query,
|
|
216
|
-
params
|
|
217
|
-
});
|
|
131
|
+
return Object.freeze(input);
|
|
218
132
|
}
|
|
219
133
|
|
|
220
134
|
function wrapReplySend({ reply = null, request = null, route = null, outputTransform = null, transport = null } = {}) {
|
|
@@ -248,9 +162,7 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
248
162
|
});
|
|
249
163
|
|
|
250
164
|
if (transportedPayload && typeof transportedPayload.then === "function") {
|
|
251
|
-
throw new RouteRegistrationError(
|
|
252
|
-
`Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} transport.response must return synchronously.`
|
|
253
|
-
);
|
|
165
|
+
throw new RouteRegistrationError(`Route ${resolveRouteLabel(route)} transport.response must return synchronously.`);
|
|
254
166
|
}
|
|
255
167
|
|
|
256
168
|
nextPayload = transportedPayload === undefined ? nextPayload : transportedPayload;
|
|
@@ -266,9 +178,7 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
266
178
|
});
|
|
267
179
|
|
|
268
180
|
if (transformedPayload && typeof transformedPayload.then === "function") {
|
|
269
|
-
throw new RouteRegistrationError(
|
|
270
|
-
`Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} output transform must return synchronously.`
|
|
271
|
-
);
|
|
181
|
+
throw new RouteRegistrationError(`Route ${resolveRouteLabel(route)} output transform must return synchronously.`);
|
|
272
182
|
}
|
|
273
183
|
|
|
274
184
|
nextPayload = transformedPayload === undefined ? nextPayload : transformedPayload;
|
|
@@ -317,39 +227,17 @@ function registerRoutes(
|
|
|
317
227
|
}
|
|
318
228
|
|
|
319
229
|
for (const route of publicRoutes) {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const routeOutputTransform = normalizeRouteOutputTransform(route?.output, {
|
|
325
|
-
context: `Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} output`,
|
|
326
|
-
ErrorType: RouteRegistrationError
|
|
327
|
-
});
|
|
328
|
-
const normalizedRoute = {
|
|
329
|
-
...route,
|
|
330
|
-
transport: routeTransport,
|
|
331
|
-
output: routeOutputTransform
|
|
332
|
-
};
|
|
333
|
-
const baseOptions = toFastifyRouteOptions(normalizedRoute);
|
|
334
|
-
const routeOptions = policyApplier(baseOptions, normalizedRoute);
|
|
335
|
-
const routeHandler = typeof normalizedRoute?.handler === "function" ? normalizedRoute.handler : fallbackHandler;
|
|
336
|
-
const resolvedMiddlewareHandlers = resolveRouteMiddlewareHandlers(normalizedRoute, runtimeMiddlewareConfig);
|
|
337
|
-
const routeInputTransforms = normalizeRouteInputTransforms(normalizedRoute);
|
|
230
|
+
const routeOptions = policyApplier(toFastifyRouteOptions(route), route);
|
|
231
|
+
const routeHandler = typeof route.handler === "function" ? route.handler : fallbackHandler;
|
|
232
|
+
const resolvedMiddlewareHandlers = resolveRouteMiddlewareHandlers(route, runtimeMiddlewareConfig);
|
|
233
|
+
const routeTransport = route.transport || null;
|
|
338
234
|
const routeActionDefaultSurface = resolveDefaultSurfaceId(null, {
|
|
339
|
-
defaultSurfaceId: route
|
|
235
|
+
defaultSurfaceId: route.surface || routeOptions?.config?.surface || requestActionDefaultSurface
|
|
340
236
|
});
|
|
341
237
|
|
|
342
238
|
fastify.route({
|
|
343
239
|
...routeOptions,
|
|
344
240
|
handler: async (request, reply) => {
|
|
345
|
-
if (!request.routeOptions || typeof request.routeOptions !== "object") {
|
|
346
|
-
request.routeOptions = {
|
|
347
|
-
config: normalizeObject(routeOptions?.config)
|
|
348
|
-
};
|
|
349
|
-
} else if (!request.routeOptions.config || typeof request.routeOptions.config !== "object") {
|
|
350
|
-
request.routeOptions.config = normalizeObject(routeOptions?.config);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
241
|
attachDirectRequestActionExecutor({
|
|
354
242
|
actions,
|
|
355
243
|
request,
|
|
@@ -358,18 +246,16 @@ function registerRoutes(
|
|
|
358
246
|
defaultSurfaceId: routeActionDefaultSurface
|
|
359
247
|
});
|
|
360
248
|
|
|
361
|
-
attachRouteTransport(request, routeTransport);
|
|
362
249
|
enforceRequestContentType({
|
|
363
250
|
request,
|
|
364
|
-
route
|
|
251
|
+
route,
|
|
365
252
|
transport: routeTransport
|
|
366
253
|
});
|
|
367
254
|
|
|
368
255
|
const transportInputTransforms = routeTransport?.request || null;
|
|
369
|
-
if (
|
|
370
|
-
request.input = await buildRequestInput({
|
|
371
|
-
|
|
372
|
-
inputTransforms: routeInputTransforms,
|
|
256
|
+
if (route.input || transportInputTransforms) {
|
|
257
|
+
request.input = await buildRequestInput(request, {
|
|
258
|
+
inputTransforms: route.input,
|
|
373
259
|
transportInputTransforms
|
|
374
260
|
});
|
|
375
261
|
}
|
|
@@ -377,8 +263,8 @@ function registerRoutes(
|
|
|
377
263
|
wrapReplySend({
|
|
378
264
|
reply,
|
|
379
265
|
request,
|
|
380
|
-
route
|
|
381
|
-
outputTransform:
|
|
266
|
+
route,
|
|
267
|
+
outputTransform: route.output,
|
|
382
268
|
transport: routeTransport
|
|
383
269
|
});
|
|
384
270
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
|
|
2
2
|
|
|
3
|
+
const JSON_API_CONTENT_TYPE = "application/vnd.api+json";
|
|
3
4
|
const ROUTE_TRANSPORT_KINDS = Object.freeze([
|
|
4
5
|
"command",
|
|
5
6
|
"jsonapi-resource"
|
|
@@ -120,6 +121,7 @@ function normalizeRouteTransport(value, { context = "route transport", ErrorType
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
export {
|
|
124
|
+
JSON_API_CONTENT_TYPE,
|
|
123
125
|
ROUTE_TRANSPORT_KINDS,
|
|
124
126
|
normalizeRouteOutputTransform,
|
|
125
127
|
normalizeRouteTransport
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
normalizeSingleSchemaDefinition,
|
|
4
4
|
resolveSchemaTransportSchemaDefinition,
|
|
5
5
|
validateSchemaPayload
|
|
6
6
|
} from "../../../shared/validators/index.js";
|
|
7
7
|
import { RouteDefinitionError } from "./errors.js";
|
|
8
8
|
import { resolveRouteLabel } from "./routeSupport.js";
|
|
9
9
|
|
|
10
|
-
const ROUTE_VALIDATOR_SYMBOL = "@jskit-ai/kernel/http/routeValidator";
|
|
11
10
|
const JSON_REST_TRANSPORT_EXTENSION_KEY = "x-json-rest-schema";
|
|
12
11
|
const VALIDATOR_OPTION_KEYS = Object.freeze([
|
|
13
12
|
"meta",
|
|
@@ -50,7 +49,7 @@ function stripJsonRestTransportExtensions(value) {
|
|
|
50
49
|
|
|
51
50
|
function normalizeRouteSchemaSection(value, { context = "route section", defaultMode = "patch" } = {}) {
|
|
52
51
|
try {
|
|
53
|
-
return
|
|
52
|
+
return normalizeSingleSchemaDefinition(value, {
|
|
54
53
|
context,
|
|
55
54
|
defaultMode
|
|
56
55
|
});
|
|
@@ -219,7 +218,8 @@ function normalizeRouteValidatorDefinition(sourceDefinition, { context = "route
|
|
|
219
218
|
return Object.freeze(normalized);
|
|
220
219
|
}
|
|
221
220
|
|
|
222
|
-
function
|
|
221
|
+
function compileRouteValidator(validator, { context = "route validator" } = {}) {
|
|
222
|
+
const normalizedValidator = normalizeRouteValidatorDefinition(validator, { context });
|
|
223
223
|
const schema = {};
|
|
224
224
|
const input = {};
|
|
225
225
|
|
|
@@ -306,48 +306,6 @@ function compileNormalizedRouteValidator(normalizedValidator) {
|
|
|
306
306
|
return Object.freeze(compiled);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
function normalizeRouteValidatorSource(validator, { context = "route validator" } = {}) {
|
|
310
|
-
if (validator && typeof validator === "object") {
|
|
311
|
-
const precompiled = validator[ROUTE_VALIDATOR_SYMBOL];
|
|
312
|
-
if (precompiled && typeof precompiled === "object") {
|
|
313
|
-
return precompiled;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return normalizeRouteValidatorDefinition(validator, {
|
|
318
|
-
context
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function compileRouteValidator(validator, { context = "route validator" } = {}) {
|
|
323
|
-
return compileNormalizedRouteValidator(
|
|
324
|
-
normalizeRouteValidatorSource(validator, {
|
|
325
|
-
context
|
|
326
|
-
})
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function defineRouteValidator(definition = {}) {
|
|
331
|
-
const normalized = normalizeRouteValidatorDefinition(definition, {
|
|
332
|
-
context: "defineRouteValidator()"
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const validator = {
|
|
336
|
-
toRouteOptions() {
|
|
337
|
-
return compileNormalizedRouteValidator(normalized);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
Object.defineProperty(validator, ROUTE_VALIDATOR_SYMBOL, {
|
|
342
|
-
value: normalized,
|
|
343
|
-
enumerable: false,
|
|
344
|
-
configurable: false,
|
|
345
|
-
writable: false
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
return Object.freeze(validator);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
309
|
function resolveRouteValidatorOptions({
|
|
352
310
|
method = "",
|
|
353
311
|
path = "",
|
|
@@ -401,4 +359,4 @@ function resolveRouteValidatorOptions({
|
|
|
401
359
|
};
|
|
402
360
|
}
|
|
403
361
|
|
|
404
|
-
export {
|
|
362
|
+
export { compileRouteValidator, resolveRouteValidatorOptions };
|