@jskit-ai/kernel 0.1.245 → 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.
Files changed (101) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +13 -75
  4. package/client/index.js +1 -1
  5. package/client/logging.js +8 -2
  6. package/client/moduleBootstrap.js +41 -33
  7. package/client/moduleBootstrap.test.js +35 -0
  8. package/client/shellBootstrap.js +1 -8
  9. package/client/shellBootstrap.test.js +32 -5
  10. package/client/shellRouting.js +16 -61
  11. package/client/shellRouting.test.js +30 -1
  12. package/client/vite/clientBootstrapPlugin.js +0 -6
  13. package/client/vite/clientBootstrapPlugin.test.js +2 -3
  14. package/internal/node/installedPackages.js +14 -58
  15. package/package.json +2 -4
  16. package/server/actions/actionCatalogue.js +13 -34
  17. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  18. package/{shared → server}/actions/actionDefinitions.js +24 -131
  19. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  20. package/server/actions/actionEvents.js +97 -2
  21. package/server/actions/actionEvents.test.js +79 -0
  22. package/{shared → server}/actions/executionContext.js +3 -13
  23. package/server/actions/idempotency.js +24 -0
  24. package/server/actions/pipeline.js +87 -0
  25. package/{shared → server}/actions/policies.js +20 -33
  26. package/{shared → server}/actions/policies.test.js +6 -6
  27. package/{shared → server}/actions/registry.js +3 -22
  28. package/{shared → server}/actions/registry.test.js +70 -32
  29. package/server/actions/requestMeta.js +17 -0
  30. package/server/http/capabilityHttpRuntime.js +1 -1
  31. package/server/http/capabilityHttpRuntime.test.js +56 -0
  32. package/server/http/errorHandling.js +291 -0
  33. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  34. package/server/http/lib/routeRegistration.js +36 -150
  35. package/server/http/lib/routeTransport.js +2 -0
  36. package/server/http/lib/routeValidator.js +5 -47
  37. package/server/http/lib/routeValidator.test.js +15 -17
  38. package/server/http/lib/router.js +10 -94
  39. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  40. package/server/platform/providerRuntime/helpers.js +1 -34
  41. package/server/runtime/integers.js +1 -5
  42. package/server/runtime/serviceAuthorization.js +4 -9
  43. package/server/support/appConfig.js +2 -1
  44. package/server/support/appConfigFiles.js +1 -38
  45. package/server/support/appConfigFiles.test.js +3 -39
  46. package/server/support/index.js +1 -20
  47. package/shared/capabilities/defineProvider.js +1 -1
  48. package/shared/capabilities/index.js +1 -1
  49. package/shared/index.js +1 -1
  50. package/shared/support/index.js +1 -4
  51. package/shared/support/mobileConfig.js +99 -0
  52. package/shared/support/mobileConfig.test.js +113 -0
  53. package/shared/support/normalize.js +0 -108
  54. package/shared/support/normalize.test.js +0 -134
  55. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  56. package/shared/support/returnToPath.js +1 -20
  57. package/shared/support/returnToPath.test.js +1 -36
  58. package/shared/support/shellLayoutTargets.js +2 -140
  59. package/shared/support/shellLayoutTargets.test.js +1 -110
  60. package/shared/support/sorting.js +1 -9
  61. package/shared/surface/apiPaths.js +1 -82
  62. package/shared/surface/paths.js +38 -171
  63. package/shared/surface/registry.js +2 -1
  64. package/shared/surface/runtime.js +18 -109
  65. package/shared/surface/runtime.test.js +57 -0
  66. package/shared/validators/index.js +1 -4
  67. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  68. package/shared/validators/recordIdParamsValidator.js +0 -6
  69. package/shared/validators/schemaDefinitions.js +4 -30
  70. package/shared/validators/schemaPayloadValidation.js +2 -6
  71. package/test/barrelExposure.test.js +3 -7
  72. package/test/exportsContract.test.js +1 -3
  73. package/client/mobileLaunchRouting.js +0 -231
  74. package/client/mobileLaunchRouting.test.js +0 -230
  75. package/server/http/lib/controller.js +0 -183
  76. package/server/http/lib/controller.test.js +0 -143
  77. package/server/http/lib/index.js +0 -5
  78. package/server/runtime/canonicalJson.js +0 -74
  79. package/server/runtime/entityChangeEvents.js +0 -307
  80. package/server/runtime/entityChangeEvents.test.js +0 -274
  81. package/server/runtime/fastifyBootstrap.js +0 -553
  82. package/server/runtime/moduleConfig.js +0 -223
  83. package/server/runtime/moduleConfig.test.js +0 -165
  84. package/server/runtime/pagination.js +0 -13
  85. package/server/runtime/realtimeNormalization.js +0 -21
  86. package/server/runtime/routeUtils.js +0 -20
  87. package/server/runtime/securityAudit.js +0 -269
  88. package/server/runtime/securityAudit.test.js +0 -41
  89. package/server/support/pageTargets.js +0 -772
  90. package/server/support/pageTargets.test.js +0 -659
  91. package/server/support/shellOutlets.js +0 -576
  92. package/server/support/shellOutlets.test.js +0 -336
  93. package/server/surface/index.js +0 -18
  94. package/shared/actions/audit.js +0 -7
  95. package/shared/actions/idempotency.js +0 -62
  96. package/shared/actions/observability.js +0 -10
  97. package/shared/actions/pipeline.js +0 -309
  98. package/shared/actions/requestMeta.js +0 -36
  99. package/shared/actions/textNormalization.js +0 -3
  100. package/shared/actions/withActionDefaults.js +0 -34
  101. /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 "./errors.js";
4
+ import { AppError, isAppError } from "../runtime/errors.js";
5
5
  import {
6
6
  normalizeBodylessContentTypeHeader,
7
7
  registerApiErrorHandler,
8
8
  registerBodylessContentTypeNormalizer,
9
- registerJsonApiContentTypeParser,
10
- registerRequestLoggingHooks
11
- } from "./fastifyBootstrap.js";
9
+ registerJsonApiContentTypeParser
10
+ } from "./errorHandling.js";
12
11
 
13
12
  function createFastifyStub() {
14
- return {
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("registerApiErrorHandler uses route config transport runtime serializer before handler attachment", () => {
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(401, "Authentication required.", {
268
- code: "AUTH_POLICY_ERROR"
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, 401);
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.deepEqual(reply.payload, {
303
- errors: [
304
- {
305
- status: "401",
306
- code: "AUTH_POLICY_ERROR",
307
- title: "Authentication required."
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 "../../runtime/fastifyBootstrap.js";
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 { normalizeRouteOutputTransform, normalizeRouteTransport } from "./routeTransport.js";
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 JSON_API_CONTENT_TYPE = "application/vnd.api+json";
15
+ const REQUEST_INPUT_KEYS = Object.freeze(["body", "query", "params"]);
15
16
 
16
17
  function toFastifyRouteOptions(route) {
17
- const sourceRoute = normalizeObject(route);
18
- const schema = cloneRouteSchema(sourceRoute.schema);
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: sourceRoute.method,
30
- url: sourceRoute.path,
23
+ method: route.method,
24
+ url: route.path,
31
25
  ...(schema ? { schema } : {}),
32
- ...(sourceRoute.bodyLimit ? { bodyLimit: sourceRoute.bodyLimit } : {}),
26
+ ...(route.bodyLimit ? { bodyLimit: route.bodyLimit } : {}),
33
27
  config: {
34
- ...existingConfig,
35
- ...(transportKind
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 normalizeRouteInputTransforms(route) {
150
- const routeInput = route?.input;
151
- if (routeInput == null) {
152
- return null;
153
- }
154
-
155
- if (!routeInput || typeof routeInput !== "object" || Array.isArray(routeInput)) {
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 transform !== "function") {
173
- throw new RouteRegistrationError(
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 routeTransport = normalizeRouteTransport(route?.transport, {
321
- context: `Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} transport`,
322
- ErrorType: RouteRegistrationError
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?.surface || routeOptions?.config?.surface || requestActionDefaultSurface
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: normalizedRoute,
251
+ route,
365
252
  transport: routeTransport
366
253
  });
367
254
 
368
255
  const transportInputTransforms = routeTransport?.request || null;
369
- if (routeInputTransforms || transportInputTransforms) {
370
- request.input = await buildRequestInput({
371
- request,
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: normalizedRoute,
381
- outputTransform: routeOutputTransform,
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
- normalizeSchemaDefinition,
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 normalizeSchemaDefinition(value, {
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 compileNormalizedRouteValidator(normalizedValidator) {
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 { defineRouteValidator, compileRouteValidator, resolveRouteValidatorOptions };
362
+ export { compileRouteValidator, resolveRouteValidatorOptions };