@jskit-ai/kernel 0.1.4

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 (185) hide show
  1. package/README.md +24 -0
  2. package/_testable/index.js +4 -0
  3. package/client/appConfig.js +33 -0
  4. package/client/componentInteraction.js +51 -0
  5. package/client/componentInteraction.test.js +111 -0
  6. package/client/descriptorSections.js +75 -0
  7. package/client/index.d.ts +70 -0
  8. package/client/index.js +3 -0
  9. package/client/logging.js +38 -0
  10. package/client/moduleBootstrap.js +670 -0
  11. package/client/moduleBootstrap.test.js +403 -0
  12. package/client/shellBootstrap.js +233 -0
  13. package/client/shellBootstrap.test.js +185 -0
  14. package/client/shellRouting.js +321 -0
  15. package/client/shellRouting.test.js +113 -0
  16. package/client/vite/clientBootstrapPlugin.js +259 -0
  17. package/client/vite/clientBootstrapPlugin.test.js +563 -0
  18. package/client/vite/index.js +3 -0
  19. package/internal/node/fileSystem.js +21 -0
  20. package/internal/node/installedPackageDescriptor.js +104 -0
  21. package/package.json +43 -0
  22. package/server/actions/ActionRuntimeServiceProvider.js +309 -0
  23. package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
  24. package/server/actions/index.js +8 -0
  25. package/server/container/ContainerCoreServiceProvider.js +27 -0
  26. package/server/container/index.js +10 -0
  27. package/server/exportPolicy.test.js +68 -0
  28. package/server/http/HttpFastifyServiceProvider.js +25 -0
  29. package/server/http/_testable/index.js +2 -0
  30. package/server/http/index.js +1 -0
  31. package/server/http/lib/controller.js +183 -0
  32. package/server/http/lib/controller.test.js +143 -0
  33. package/server/http/lib/errors.js +12 -0
  34. package/server/http/lib/httpRuntime.js +82 -0
  35. package/server/http/lib/index.js +18 -0
  36. package/server/http/lib/kernel.js +15 -0
  37. package/server/http/lib/kernel.test.js +880 -0
  38. package/server/http/lib/middlewareRuntime.js +149 -0
  39. package/server/http/lib/requestActionExecutor.js +258 -0
  40. package/server/http/lib/requestScope.js +59 -0
  41. package/server/http/lib/routeRegistration.js +165 -0
  42. package/server/http/lib/routeSupport.js +45 -0
  43. package/server/http/lib/routeValidator.js +469 -0
  44. package/server/http/lib/routeValidator.test.js +474 -0
  45. package/server/http/lib/router.js +206 -0
  46. package/server/kernel/KernelCoreServiceProvider.js +27 -0
  47. package/server/kernel/index.js +10 -0
  48. package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
  49. package/server/platform/index.js +5 -0
  50. package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
  51. package/server/platform/providerRuntime/helpers.js +45 -0
  52. package/server/platform/providerRuntime/lockfile.js +27 -0
  53. package/server/platform/providerRuntime/providerLoader.js +283 -0
  54. package/server/platform/providerRuntime.js +142 -0
  55. package/server/platform/providerRuntime.test.js +217 -0
  56. package/server/platform/runtime.js +40 -0
  57. package/server/platform/surfaceRuntime.js +150 -0
  58. package/server/platform/surfaceRuntime.test.js +136 -0
  59. package/server/registries/actionSurfaceSourceRegistry.js +150 -0
  60. package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
  61. package/server/registries/domainEventListenerRegistry.js +61 -0
  62. package/server/registries/index.js +36 -0
  63. package/server/registries/primitives.js +63 -0
  64. package/server/registries/routeVisibilityResolverRegistry.js +87 -0
  65. package/server/registries/serviceRegistrationRegistry.js +431 -0
  66. package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
  67. package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
  68. package/server/runtime/apiRoutePolicyParity.test.js +109 -0
  69. package/server/runtime/apiRouteRegistration.js +65 -0
  70. package/server/runtime/bootBootstrapRoutes.js +46 -0
  71. package/server/runtime/bootBootstrapRoutes.test.js +79 -0
  72. package/server/runtime/bootstrapContributors.test.js +114 -0
  73. package/server/runtime/canonicalJson.js +74 -0
  74. package/server/runtime/composition.js +142 -0
  75. package/server/runtime/domainEvents.test.js +114 -0
  76. package/server/runtime/domainRules.js +50 -0
  77. package/server/runtime/domainRules.test.js +87 -0
  78. package/server/runtime/entityChangeEvents.js +182 -0
  79. package/server/runtime/entityChangeEvents.test.js +211 -0
  80. package/server/runtime/errors.js +68 -0
  81. package/server/runtime/errors.test.js +73 -0
  82. package/server/runtime/fastifyBootstrap.js +372 -0
  83. package/server/runtime/fastifyBootstrap.test.js +194 -0
  84. package/server/runtime/index.js +6 -0
  85. package/server/runtime/integers.js +13 -0
  86. package/server/runtime/moduleConfig.js +269 -0
  87. package/server/runtime/moduleConfig.test.js +141 -0
  88. package/server/runtime/pagination.js +13 -0
  89. package/server/runtime/realtimeNormalization.js +21 -0
  90. package/server/runtime/requestUrl.js +38 -0
  91. package/server/runtime/routeUtils.js +20 -0
  92. package/server/runtime/runtimeAssembly.js +113 -0
  93. package/server/runtime/runtimeKernel.js +55 -0
  94. package/server/runtime/securityAudit.js +269 -0
  95. package/server/runtime/securityAudit.test.js +41 -0
  96. package/server/runtime/serviceAuthorization.js +113 -0
  97. package/server/runtime/serviceAuthorization.test.js +100 -0
  98. package/server/runtime/serviceRegistration.test.js +197 -0
  99. package/server/support/SupportCoreServiceProvider.js +25 -0
  100. package/server/support/appConfig.js +37 -0
  101. package/server/support/appConfig.test.js +94 -0
  102. package/server/support/defaultMissingHandler.js +7 -0
  103. package/server/support/index.js +2 -0
  104. package/server/support/routePolicyConfig.js +51 -0
  105. package/server/support/symlinkSafeRequire.js +78 -0
  106. package/server/support/symlinkSafeRequire.test.js +27 -0
  107. package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
  108. package/server/surface/index.js +19 -0
  109. package/shared/actions/actionContributorHelpers.js +34 -0
  110. package/shared/actions/actionContributorHelpers.test.js +16 -0
  111. package/shared/actions/actionDefinitions.js +488 -0
  112. package/shared/actions/actionDefinitions.test.js +212 -0
  113. package/shared/actions/audit.js +7 -0
  114. package/shared/actions/executionContext.js +97 -0
  115. package/shared/actions/executionContext.test.js +66 -0
  116. package/shared/actions/idempotency.js +62 -0
  117. package/shared/actions/index.js +2 -0
  118. package/shared/actions/observability.js +10 -0
  119. package/shared/actions/pipeline.js +287 -0
  120. package/shared/actions/policies.js +342 -0
  121. package/shared/actions/policies.test.js +233 -0
  122. package/shared/actions/registry.js +187 -0
  123. package/shared/actions/registry.test.js +381 -0
  124. package/shared/actions/requestMeta.js +36 -0
  125. package/shared/actions/textNormalization.js +3 -0
  126. package/shared/actions/withActionDefaults.js +34 -0
  127. package/shared/index.js +2 -0
  128. package/shared/runtime/application.js +323 -0
  129. package/shared/runtime/container.js +261 -0
  130. package/shared/runtime/containerErrors.js +22 -0
  131. package/shared/runtime/index.js +18 -0
  132. package/shared/runtime/kernelErrors.js +20 -0
  133. package/shared/runtime/serviceProvider.js +13 -0
  134. package/shared/support/formatDateTime.js +10 -0
  135. package/shared/support/formatDateTime.test.js +15 -0
  136. package/shared/support/index.js +14 -0
  137. package/shared/support/linkPath.js +67 -0
  138. package/shared/support/linkPath.test.js +35 -0
  139. package/shared/support/normalize.js +116 -0
  140. package/shared/support/normalize.test.js +48 -0
  141. package/shared/support/packageDescriptor.test.js +121 -0
  142. package/shared/support/permissions.js +50 -0
  143. package/shared/support/pickOwnProperties.js +17 -0
  144. package/shared/support/pickOwnProperties.test.js +25 -0
  145. package/shared/support/policies.js +11 -0
  146. package/shared/support/queryPath.js +33 -0
  147. package/shared/support/queryPath.test.js +19 -0
  148. package/shared/support/queryResilience.js +34 -0
  149. package/shared/support/queryResilience.test.js +33 -0
  150. package/shared/support/returnToPath.js +153 -0
  151. package/shared/support/returnToPath.test.js +123 -0
  152. package/shared/support/sorting.js +15 -0
  153. package/shared/support/tokens.js +23 -0
  154. package/shared/support/tokens.test.js +17 -0
  155. package/shared/support/visibility.js +56 -0
  156. package/shared/support/visibility.test.js +45 -0
  157. package/shared/surface/apiPaths.js +84 -0
  158. package/shared/surface/escapeRegExp.js +5 -0
  159. package/shared/surface/index.js +6 -0
  160. package/shared/surface/paths.js +273 -0
  161. package/shared/surface/registry.js +135 -0
  162. package/shared/surface/registry.test.js +44 -0
  163. package/shared/surface/runtime.js +357 -0
  164. package/shared/surface/runtime.test.js +319 -0
  165. package/shared/validators/createCursorListValidator.js +42 -0
  166. package/shared/validators/createCursorListValidator.test.js +34 -0
  167. package/shared/validators/cursorPaginationQueryValidator.js +31 -0
  168. package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
  169. package/shared/validators/index.js +12 -0
  170. package/shared/validators/inputNormalization.js +13 -0
  171. package/shared/validators/mergeObjectSchemas.js +31 -0
  172. package/shared/validators/mergeObjectSchemas.test.js +67 -0
  173. package/shared/validators/mergeValidators.js +89 -0
  174. package/shared/validators/mergeValidators.test.js +116 -0
  175. package/shared/validators/nestValidator.js +53 -0
  176. package/shared/validators/nestValidator.test.js +60 -0
  177. package/shared/validators/recordIdParamsValidator.js +36 -0
  178. package/shared/validators/recordIdParamsValidator.test.js +20 -0
  179. package/shared/validators/resourceRequiredMetadata.js +41 -0
  180. package/shared/validators/resourceRequiredMetadata.test.js +49 -0
  181. package/test/barrelExposure.test.js +106 -0
  182. package/test/dynamicImportPolicy.test.js +89 -0
  183. package/test/exportsContract.test.js +168 -0
  184. package/test/routeInputContractGuard.test.js +78 -0
  185. package/test/surfaceIndependence.test.js +109 -0
@@ -0,0 +1,474 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { createRouter } from "./router.js";
5
+ import { compileRouteValidator, defineRouteValidator, resolveRouteValidatorOptions } from "./routeValidator.js";
6
+
7
+ test("defineRouteValidator compiles body/query/params and maps query schema to querystring", () => {
8
+ const bodySchema = {
9
+ type: "object"
10
+ };
11
+ const querySchema = {
12
+ type: "object"
13
+ };
14
+ const paramsSchema = {
15
+ type: "object"
16
+ };
17
+ const responseSchema = {
18
+ 200: {
19
+ schema: {
20
+ type: "object"
21
+ }
22
+ }
23
+ };
24
+ const headersSchema = {
25
+ type: "object"
26
+ };
27
+
28
+ const normalizeBody = (body) => body;
29
+ const normalizeQuery = (query) => query;
30
+ const normalizeParams = (params) => params;
31
+
32
+ const validator = defineRouteValidator({
33
+ meta: {
34
+ tags: ["contacts", "intake"],
35
+ summary: "Create contact intake"
36
+ },
37
+ bodyValidator: {
38
+ schema: bodySchema,
39
+ normalize: normalizeBody
40
+ },
41
+ queryValidator: {
42
+ schema: querySchema,
43
+ normalize: normalizeQuery
44
+ },
45
+ paramsValidator: {
46
+ schema: paramsSchema
47
+ },
48
+ responseValidators: responseSchema,
49
+ advanced: {
50
+ fastifySchema: {
51
+ headers: headersSchema
52
+ },
53
+ jskitInput: {
54
+ params: normalizeParams
55
+ }
56
+ }
57
+ });
58
+
59
+ const compiled = validator.toRouteOptions();
60
+
61
+ assert.deepEqual(compiled.schema, {
62
+ tags: ["contacts", "intake"],
63
+ summary: "Create contact intake",
64
+ body: bodySchema,
65
+ querystring: querySchema,
66
+ params: paramsSchema,
67
+ response: {
68
+ 200: {
69
+ type: "object"
70
+ }
71
+ },
72
+ headers: headersSchema
73
+ });
74
+ assert.equal(compiled.input.body, normalizeBody);
75
+ assert.equal(compiled.input.query, normalizeQuery);
76
+ assert.equal(compiled.input.params, normalizeParams);
77
+ });
78
+
79
+ test("compileRouteValidator accepts plain validator objects", () => {
80
+ const querySchema = {
81
+ type: "object"
82
+ };
83
+ const normalizeQuery = (query) => ({
84
+ dryRun: Boolean(query?.dryRun)
85
+ });
86
+
87
+ const compiled = compileRouteValidator({
88
+ queryValidator: {
89
+ schema: querySchema,
90
+ normalize: normalizeQuery
91
+ }
92
+ });
93
+
94
+ assert.deepEqual(compiled.schema, {
95
+ querystring: querySchema
96
+ });
97
+ assert.equal(compiled.input.query, normalizeQuery);
98
+ });
99
+
100
+ test("compileRouteValidator creates pass-through request.input transforms for schema-only params and query", () => {
101
+ const querySchema = {
102
+ type: "object"
103
+ };
104
+ const paramsSchema = {
105
+ type: "object"
106
+ };
107
+
108
+ const compiled = compileRouteValidator({
109
+ queryValidator: {
110
+ schema: querySchema
111
+ },
112
+ paramsValidator: {
113
+ schema: paramsSchema
114
+ }
115
+ });
116
+
117
+ assert.deepEqual(compiled.schema, {
118
+ querystring: querySchema,
119
+ params: paramsSchema
120
+ });
121
+ assert.equal(typeof compiled.input.query, "function");
122
+ assert.equal(typeof compiled.input.params, "function");
123
+ assert.deepEqual(compiled.input.query({ workspaceSlug: "acme" }), { workspaceSlug: "acme" });
124
+ assert.deepEqual(compiled.input.params({ workspaceSlug: "acme" }), { workspaceSlug: "acme" });
125
+ });
126
+
127
+ test("compileRouteValidator accepts response validator objects and extracts only response schemas", () => {
128
+ const responseBodySchema = {
129
+ type: "object"
130
+ };
131
+ const normalizeOutput = (payload) => ({
132
+ ...payload,
133
+ normalized: true
134
+ });
135
+
136
+ const compiled = compileRouteValidator({
137
+ responseValidators: {
138
+ 200: {
139
+ schema: responseBodySchema,
140
+ normalize: normalizeOutput
141
+ },
142
+ 400: {
143
+ schema: {
144
+ type: "object"
145
+ }
146
+ }
147
+ }
148
+ });
149
+
150
+ assert.deepEqual(compiled.schema, {
151
+ response: {
152
+ 200: responseBodySchema,
153
+ 400: {
154
+ type: "object"
155
+ }
156
+ }
157
+ });
158
+ assert.equal(Object.prototype.hasOwnProperty.call(compiled, "output"), false);
159
+ });
160
+
161
+ test("compileRouteValidator merges query validator arrays automatically", () => {
162
+ const paginationQuery = {
163
+ schema: {
164
+ type: "object",
165
+ properties: {
166
+ cursor: {
167
+ type: "string"
168
+ }
169
+ },
170
+ additionalProperties: false
171
+ }
172
+ };
173
+ const searchQuery = {
174
+ schema: {
175
+ type: "object",
176
+ properties: {
177
+ search: {
178
+ type: "string"
179
+ }
180
+ },
181
+ additionalProperties: false
182
+ }
183
+ };
184
+
185
+ const compiled = compileRouteValidator({
186
+ queryValidator: [paginationQuery, searchQuery]
187
+ });
188
+
189
+ assert.deepEqual(compiled.schema, {
190
+ querystring: {
191
+ type: "object",
192
+ properties: {
193
+ cursor: {
194
+ type: "string"
195
+ },
196
+ search: {
197
+ type: "string"
198
+ }
199
+ },
200
+ required: ["cursor", "search"],
201
+ additionalProperties: false
202
+ }
203
+ });
204
+ });
205
+
206
+ test("compileRouteValidator merges params validator arrays automatically", () => {
207
+ const workspaceSlugParams = {
208
+ schema: {
209
+ type: "object",
210
+ properties: {
211
+ workspaceSlug: {
212
+ type: "string"
213
+ }
214
+ },
215
+ required: ["workspaceSlug"],
216
+ additionalProperties: false
217
+ }
218
+ };
219
+ const inviteIdParams = {
220
+ schema: {
221
+ type: "object",
222
+ properties: {
223
+ inviteId: {
224
+ type: "string"
225
+ }
226
+ },
227
+ required: ["inviteId"],
228
+ additionalProperties: false
229
+ }
230
+ };
231
+
232
+ const compiled = compileRouteValidator({
233
+ paramsValidator: [workspaceSlugParams, inviteIdParams]
234
+ });
235
+
236
+ assert.deepEqual(compiled.schema, {
237
+ params: {
238
+ type: "object",
239
+ properties: {
240
+ workspaceSlug: {
241
+ type: "string"
242
+ },
243
+ inviteId: {
244
+ type: "string"
245
+ }
246
+ },
247
+ required: ["workspaceSlug", "inviteId"],
248
+ additionalProperties: false
249
+ }
250
+ });
251
+ });
252
+
253
+ test("compileRouteValidator composes multiple query normalizers in validator arrays", () => {
254
+ const compiled = compileRouteValidator({
255
+ queryValidator: [
256
+ {
257
+ schema: {
258
+ type: "object",
259
+ properties: {
260
+ cursor: {
261
+ type: "string"
262
+ }
263
+ },
264
+ additionalProperties: false
265
+ },
266
+ normalize(query = {}) {
267
+ return {
268
+ cursor: String(query.cursor || "").trim()
269
+ };
270
+ }
271
+ },
272
+ {
273
+ schema: {
274
+ type: "object",
275
+ properties: {
276
+ search: {
277
+ type: "string"
278
+ }
279
+ },
280
+ additionalProperties: false
281
+ },
282
+ normalize(query = {}) {
283
+ return {
284
+ search: String(query.search || "").trim().toLowerCase()
285
+ };
286
+ }
287
+ }
288
+ ]
289
+ });
290
+
291
+ assert.deepEqual(compiled.input.query({ cursor: " 100 ", search: " ACME " }), {
292
+ cursor: "100",
293
+ search: "acme"
294
+ });
295
+ });
296
+
297
+ test("resolveRouteValidatorOptions ignores legacy schema/input definitions", () => {
298
+ const resolved = resolveRouteValidatorOptions({
299
+ method: "POST",
300
+ path: "/contacts",
301
+ options: {
302
+ schema: {
303
+ bodyValidator: {}
304
+ },
305
+ input: {
306
+ body: () => ({})
307
+ },
308
+ middleware: ["api"]
309
+ }
310
+ });
311
+
312
+ assert.equal(Object.prototype.hasOwnProperty.call(resolved, "schema"), false);
313
+ assert.equal(Object.prototype.hasOwnProperty.call(resolved, "input"), false);
314
+ assert.deepEqual(resolved.middleware, ["api"]);
315
+ });
316
+
317
+ test("resolveRouteValidatorOptions supports inline validator shape without wrapper", () => {
318
+ const bodySchema = {
319
+ type: "object"
320
+ };
321
+ const normalizeBody = (body) => ({
322
+ name: String(body?.name || "").trim()
323
+ });
324
+
325
+ const resolved = resolveRouteValidatorOptions({
326
+ method: "POST",
327
+ path: "/contacts",
328
+ options: {
329
+ meta: {
330
+ tags: ["contacts"],
331
+ summary: "Create contact"
332
+ },
333
+ bodyValidator: {
334
+ schema: bodySchema,
335
+ normalize: normalizeBody
336
+ },
337
+ middleware: ["api"]
338
+ }
339
+ });
340
+
341
+ assert.deepEqual(resolved.schema, {
342
+ tags: ["contacts"],
343
+ summary: "Create contact",
344
+ body: bodySchema
345
+ });
346
+ assert.equal(resolved.input.body, normalizeBody);
347
+ assert.deepEqual(resolved.middleware, ["api"]);
348
+ });
349
+
350
+ test("resolveRouteValidatorOptions ignores validator wrapper", () => {
351
+ const resolved = resolveRouteValidatorOptions({
352
+ method: "POST",
353
+ path: "/contacts",
354
+ options: {
355
+ validator: defineRouteValidator({}),
356
+ middleware: ["api"]
357
+ }
358
+ });
359
+
360
+ assert.equal(Object.prototype.hasOwnProperty.call(resolved, "validator"), false);
361
+ assert.deepEqual(resolved.middleware, ["api"]);
362
+ });
363
+
364
+ test("defineRouteValidator rejects unsupported advanced.jskitInput keys", () => {
365
+ assert.throws(
366
+ () =>
367
+ defineRouteValidator({
368
+ advanced: {
369
+ jskitInput: {
370
+ headers: () => ({})
371
+ }
372
+ }
373
+ }),
374
+ /advanced\.jskitInput\.headers is not supported/
375
+ );
376
+ });
377
+
378
+ test("defineRouteValidator validates meta fields", () => {
379
+ assert.throws(
380
+ () =>
381
+ defineRouteValidator({
382
+ meta: {
383
+ tags: ["ok", ""]
384
+ }
385
+ }),
386
+ /meta\.tags\[1\] must be a non-empty string/
387
+ );
388
+
389
+ assert.throws(
390
+ () =>
391
+ defineRouteValidator({
392
+ meta: {
393
+ summary: ""
394
+ }
395
+ }),
396
+ /meta\.summary must be a non-empty string/
397
+ );
398
+ });
399
+
400
+ test("HttpRouter.register ignores validator wrapper options", () => {
401
+ const router = createRouter();
402
+ router.register(
403
+ "POST",
404
+ "/contacts",
405
+ {
406
+ validator: defineRouteValidator({})
407
+ },
408
+ async () => {}
409
+ );
410
+
411
+ const [route] = router.list();
412
+ assert.equal(route.schema, undefined);
413
+ assert.equal(route.input, null);
414
+ });
415
+
416
+ test("HttpRouter.register ignores compiled legacy-style route options", () => {
417
+ const router = createRouter();
418
+ const querySchema = {
419
+ type: "object"
420
+ };
421
+ const normalizeQuery = (query) => ({
422
+ dryRun: query?.dryRun === true
423
+ });
424
+
425
+ const validator = defineRouteValidator({
426
+ queryValidator: {
427
+ schema: querySchema,
428
+ normalize: normalizeQuery
429
+ }
430
+ });
431
+
432
+ router.get(
433
+ "/contacts",
434
+ validator.toRouteOptions(),
435
+ async () => {}
436
+ );
437
+
438
+ const [route] = router.list();
439
+ assert.equal(route.schema, undefined);
440
+ assert.equal(route.input, null);
441
+ });
442
+
443
+ test("HttpRouter.register accepts inline validator shape directly", () => {
444
+ const router = createRouter();
445
+ const querySchema = {
446
+ type: "object"
447
+ };
448
+ const normalizeQuery = (query) => ({
449
+ dryRun: query?.dryRun === true
450
+ });
451
+
452
+ router.get(
453
+ "/contacts",
454
+ {
455
+ meta: {
456
+ tags: ["contacts"],
457
+ summary: "List contacts"
458
+ },
459
+ queryValidator: {
460
+ schema: querySchema,
461
+ normalize: normalizeQuery
462
+ }
463
+ },
464
+ async () => {}
465
+ );
466
+
467
+ const [route] = router.list();
468
+ assert.deepEqual(route.schema, {
469
+ tags: ["contacts"],
470
+ summary: "List contacts",
471
+ querystring: querySchema
472
+ });
473
+ assert.equal(route.input.query, normalizeQuery);
474
+ });
@@ -0,0 +1,206 @@
1
+ import { ensureNonEmptyText, normalizeArray, normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
2
+ import { RouteDefinitionError } from "./errors.js";
3
+ import { resolveRouteValidatorOptions } from "./routeValidator.js";
4
+ import { normalizeMiddlewareStack as normalizeSharedMiddlewareStack } from "./routeSupport.js";
5
+
6
+ function normalizeMethod(method) {
7
+ return ensureNonEmptyText(method, "route method").toUpperCase();
8
+ }
9
+
10
+ function normalizePath(pathname) {
11
+ const value = normalizeText(pathname);
12
+ if (!value.startsWith("/")) {
13
+ throw new RouteDefinitionError(`Route path must start with '/': ${value || "<empty>"}`);
14
+ }
15
+ return value.replace(/\/+/g, "/");
16
+ }
17
+
18
+ function joinPath(left, right) {
19
+ const leftPath = normalizeText(left);
20
+ const rightPath = normalizeText(right);
21
+
22
+ const normalizedLeft = leftPath ? `/${leftPath.replace(/^\/+|\/+$/g, "")}` : "";
23
+ const normalizedRight = rightPath ? `/${rightPath.replace(/^\/+|\/+$/g, "")}` : "";
24
+ const joined = `${normalizedLeft}${normalizedRight}`.replace(/\/+/g, "/");
25
+ return joined || "/";
26
+ }
27
+
28
+ function normalizeRouterMiddlewareStack(value, { context = "middleware" } = {}) {
29
+ return normalizeSharedMiddlewareStack(value, {
30
+ context,
31
+ ErrorType: RouteDefinitionError,
32
+ entryLabel: "entries",
33
+ includeIndex: false
34
+ });
35
+ }
36
+
37
+ function normalizeRouteInput(method, path, optionsOrHandler, maybeHandler) {
38
+ const options =
39
+ typeof optionsOrHandler === "function"
40
+ ? {}
41
+ : normalizeObject(optionsOrHandler, {
42
+ fallback: {}
43
+ });
44
+ const handler =
45
+ typeof optionsOrHandler === "function"
46
+ ? optionsOrHandler
47
+ : typeof maybeHandler === "function"
48
+ ? maybeHandler
49
+ : null;
50
+
51
+ if (typeof handler !== "function") {
52
+ throw new RouteDefinitionError(`Route ${method} ${path} requires a handler function.`);
53
+ }
54
+
55
+ return {
56
+ method: normalizeMethod(method),
57
+ path: normalizePath(path),
58
+ options,
59
+ handler
60
+ };
61
+ }
62
+
63
+ class HttpRouter {
64
+ constructor({ routes = null, prefix = "", middleware = [] } = {}) {
65
+ this._routes = Array.isArray(routes) ? routes : [];
66
+ this._prefix = normalizeText(prefix);
67
+ this._middleware = normalizeRouterMiddlewareStack(middleware, {
68
+ context: "router middleware"
69
+ });
70
+ }
71
+
72
+ register(method, path, optionsOrHandler, maybeHandler) {
73
+ const input = normalizeRouteInput(method, path, optionsOrHandler, maybeHandler);
74
+ const resolvedOptions = resolveRouteValidatorOptions({
75
+ method: input.method,
76
+ path: input.path,
77
+ options: input.options
78
+ });
79
+ const routeMiddleware = normalizeRouterMiddlewareStack(resolvedOptions.middleware, {
80
+ context: `Route ${input.method} ${input.path} middleware`
81
+ });
82
+ const routeInput = Object.prototype.hasOwnProperty.call(resolvedOptions, "input") ? resolvedOptions.input : null;
83
+ const routeOutput = Object.prototype.hasOwnProperty.call(resolvedOptions, "output") ? resolvedOptions.output : null;
84
+
85
+ const route = Object.freeze({
86
+ id: normalizeText(resolvedOptions.id),
87
+ method: input.method,
88
+ path: joinPath(this._prefix, input.path),
89
+ schema: resolvedOptions.schema,
90
+ input: routeInput,
91
+ output: routeOutput,
92
+ config: normalizeObject(resolvedOptions.config),
93
+ auth: resolvedOptions.auth,
94
+ contextPolicy: resolvedOptions.contextPolicy,
95
+ surface: resolvedOptions.surface,
96
+ visibility: resolvedOptions.visibility,
97
+ permission: resolvedOptions.permission,
98
+ ownerParam: resolvedOptions.ownerParam,
99
+ userField: resolvedOptions.userField,
100
+ ownerResolver: resolvedOptions.ownerResolver,
101
+ csrfProtection: resolvedOptions.csrfProtection,
102
+ bodyLimit: resolvedOptions.bodyLimit,
103
+ middleware: Object.freeze([...this._middleware, ...routeMiddleware]),
104
+ handler: input.handler
105
+ });
106
+
107
+ this._routes.push(route);
108
+ return this;
109
+ }
110
+
111
+ get(path, optionsOrHandler, maybeHandler) {
112
+ return this.register("GET", path, optionsOrHandler, maybeHandler);
113
+ }
114
+
115
+ post(path, optionsOrHandler, maybeHandler) {
116
+ return this.register("POST", path, optionsOrHandler, maybeHandler);
117
+ }
118
+
119
+ put(path, optionsOrHandler, maybeHandler) {
120
+ return this.register("PUT", path, optionsOrHandler, maybeHandler);
121
+ }
122
+
123
+ patch(path, optionsOrHandler, maybeHandler) {
124
+ return this.register("PATCH", path, optionsOrHandler, maybeHandler);
125
+ }
126
+
127
+ delete(path, optionsOrHandler, maybeHandler) {
128
+ return this.register("DELETE", path, optionsOrHandler, maybeHandler);
129
+ }
130
+
131
+ group({ prefix = "", middleware = [] } = {}, defineRoutes = null) {
132
+ if (typeof defineRoutes !== "function") {
133
+ throw new RouteDefinitionError("group() requires a callback.");
134
+ }
135
+
136
+ const nestedRouter = new HttpRouter({
137
+ routes: this._routes,
138
+ prefix: joinPath(this._prefix, prefix || ""),
139
+ middleware: [
140
+ ...this._middleware,
141
+ ...normalizeRouterMiddlewareStack(middleware, {
142
+ context: "group middleware"
143
+ })
144
+ ]
145
+ });
146
+
147
+ defineRoutes(nestedRouter);
148
+ return this;
149
+ }
150
+
151
+ resource(name, controller, options = {}) {
152
+ this._resource(name, controller, {
153
+ ...normalizeObject(options),
154
+ apiOnly: false
155
+ });
156
+ return this;
157
+ }
158
+
159
+ apiResource(name, controller, options = {}) {
160
+ this._resource(name, controller, {
161
+ ...normalizeObject(options),
162
+ apiOnly: true
163
+ });
164
+ return this;
165
+ }
166
+
167
+ _resource(name, controller, options = {}) {
168
+ const resourceName = ensureNonEmptyText(name, "resource name");
169
+ const idParam = normalizeText(options.idParam, { fallback: "id" });
170
+ const basePath = `/${resourceName}`;
171
+ const itemPath = `/${resourceName}/:${idParam}`;
172
+
173
+ const methods = normalizeObject(controller);
174
+ const middleware = normalizeRouterMiddlewareStack(options.middleware, {
175
+ context: `resource ${resourceName} middleware`
176
+ });
177
+
178
+ const requireMethod = (methodName) => {
179
+ const handler = methods[methodName];
180
+ if (typeof handler !== "function") {
181
+ throw new RouteDefinitionError(`resource controller for ${resourceName} is missing method ${methodName}().`);
182
+ }
183
+ return handler;
184
+ };
185
+
186
+ this.get(basePath, { middleware }, requireMethod("index"));
187
+ if (!options.apiOnly) {
188
+ this.get(`${basePath}/create`, { middleware }, requireMethod("create"));
189
+ this.get(`${itemPath}/edit`, { middleware }, requireMethod("edit"));
190
+ }
191
+ this.post(basePath, { middleware }, requireMethod("store"));
192
+ this.get(itemPath, { middleware }, requireMethod("show"));
193
+ this.put(itemPath, { middleware }, requireMethod("update"));
194
+ this.delete(itemPath, { middleware }, requireMethod("destroy"));
195
+ }
196
+
197
+ list() {
198
+ return Object.freeze([...this._routes]);
199
+ }
200
+ }
201
+
202
+ function createRouter(options = {}) {
203
+ return new HttpRouter(options);
204
+ }
205
+
206
+ export { HttpRouter, createRouter, joinPath };
@@ -0,0 +1,27 @@
1
+ import { Application, createApplication, createProviderClass } from "../../shared/runtime/application.js";
2
+ import { ServiceProvider } from "../../shared/runtime/serviceProvider.js";
3
+ import * as errors from "../../shared/runtime/kernelErrors.js";
4
+
5
+ const KERNEL_CORE_API = Object.freeze({
6
+ Application,
7
+ createApplication,
8
+ createProviderClass,
9
+ ServiceProvider,
10
+ errors: Object.freeze({ ...errors })
11
+ });
12
+
13
+ class KernelCoreServiceProvider {
14
+ static id = "runtime.kernel";
15
+
16
+ register(app) {
17
+ if (!app || typeof app.singleton !== "function") {
18
+ throw new Error("KernelCoreServiceProvider requires application singleton().");
19
+ }
20
+
21
+ app.singleton("runtime.kernel", () => KERNEL_CORE_API);
22
+ }
23
+
24
+ boot() {}
25
+ }
26
+
27
+ export { KernelCoreServiceProvider };
@@ -0,0 +1,10 @@
1
+ export { Application, createApplication, createProviderClass } from "../../shared/runtime/application.js";
2
+ export { ServiceProvider } from "../../shared/runtime/serviceProvider.js";
3
+ export {
4
+ KernelError,
5
+ ProviderNormalizationError,
6
+ DuplicateProviderError,
7
+ ProviderDependencyError,
8
+ ProviderLifecycleError
9
+ } from "../../shared/runtime/kernelErrors.js";
10
+ export { KernelCoreServiceProvider } from "./KernelCoreServiceProvider.js";