@jskit-ai/kernel 0.1.17 → 0.1.19

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 (33) hide show
  1. package/client/moduleBootstrap.js +8 -22
  2. package/package.json +2 -1
  3. package/server/actions/ActionRuntimeServiceProvider.js +10 -15
  4. package/server/actions/ActionRuntimeServiceProvider.test.js +7 -8
  5. package/server/actions/index.js +0 -2
  6. package/server/http/lib/httpRuntime.js +5 -6
  7. package/server/http/lib/index.js +0 -1
  8. package/server/http/lib/kernel.test.js +18 -20
  9. package/server/http/lib/requestScope.js +4 -6
  10. package/server/http/lib/routeValidator.js +1 -1
  11. package/server/http/lib/router.js +1 -1
  12. package/server/platform/providerRuntime.js +2 -3
  13. package/server/registries/actionSurfaceSourceRegistry.js +7 -9
  14. package/server/registries/bootstrapPayloadContributorRegistry.js +2 -5
  15. package/server/registries/domainEventListenerRegistry.js +2 -5
  16. package/server/registries/index.js +0 -4
  17. package/server/registries/routeVisibilityResolverRegistry.js +2 -5
  18. package/server/registries/serviceRegistrationRegistry.js +6 -9
  19. package/server/runtime/ServerRuntimeCoreServiceProvider.js +1 -2
  20. package/server/runtime/bootBootstrapRoutes.js +1 -2
  21. package/server/runtime/bootBootstrapRoutes.test.js +1 -2
  22. package/server/runtime/fastifyBootstrap.js +3 -6
  23. package/server/support/SupportCoreServiceProvider.js +4 -2
  24. package/shared/support/containerToken.js +8 -0
  25. package/shared/support/index.js +1 -0
  26. package/shared/support/normalize.js +91 -0
  27. package/shared/support/normalize.test.js +96 -0
  28. package/shared/support/stringCase.js +26 -0
  29. package/shared/support/tokens.js +1 -23
  30. package/shared/support/tokens.test.js +2 -2
  31. package/test/barrelExposure.test.js +2 -0
  32. package/test/exportsContract.test.js +7 -1
  33. package/test/stringCase.test.js +22 -0
@@ -4,14 +4,6 @@ import { isRecord } from "../shared/support/normalize.js";
4
4
  import { normalizeDescriptorClientProviders, normalizeDescriptorUiRoutes } from "./descriptorSections.js";
5
5
  import { createStructuredLogger, summarizeRouterRoutes } from "./logging.js";
6
6
 
7
- const CLIENT_MODULE_RUNTIME_APP_TOKEN = Symbol.for("jskit.client.runtime.app");
8
- const CLIENT_MODULE_ROUTER_TOKEN = Symbol.for("jskit.client.router");
9
- const CLIENT_MODULE_VUE_APP_TOKEN = Symbol.for("jskit.client.vue.app");
10
- const CLIENT_MODULE_ENV_TOKEN = Symbol.for("jskit.client.env");
11
- const CLIENT_MODULE_SURFACE_RUNTIME_TOKEN = Symbol.for("jskit.client.surface.runtime");
12
- const CLIENT_MODULE_SURFACE_MODE_TOKEN = Symbol.for("jskit.client.surface.mode");
13
- const CLIENT_MODULE_LOGGER_TOKEN = Symbol.for("jskit.client.logger");
14
-
15
7
  function normalizePackageId(value) {
16
8
  return String(value || "").trim();
17
9
  }
@@ -501,13 +493,13 @@ function createClientRuntimeApp({
501
493
  strict: true
502
494
  });
503
495
 
504
- runtimeApp.instance(CLIENT_MODULE_RUNTIME_APP_TOKEN, runtimeApp);
505
- runtimeApp.instance(CLIENT_MODULE_ROUTER_TOKEN, router || null);
506
- runtimeApp.instance(CLIENT_MODULE_VUE_APP_TOKEN, app || null);
507
- runtimeApp.instance(CLIENT_MODULE_ENV_TOKEN, isRecord(env) ? { ...env } : {});
508
- runtimeApp.instance(CLIENT_MODULE_SURFACE_RUNTIME_TOKEN, surfaceRuntime || null);
509
- runtimeApp.instance(CLIENT_MODULE_SURFACE_MODE_TOKEN, String(surfaceMode || "").trim());
510
- runtimeApp.instance(CLIENT_MODULE_LOGGER_TOKEN, logger);
496
+ runtimeApp.instance("jskit.client.runtime.app", runtimeApp);
497
+ runtimeApp.instance("jskit.client.router", router || null);
498
+ runtimeApp.instance("jskit.client.vue.app", app || null);
499
+ runtimeApp.instance("jskit.client.env", isRecord(env) ? { ...env } : {});
500
+ runtimeApp.instance("jskit.client.surface.runtime", surfaceRuntime || null);
501
+ runtimeApp.instance("jskit.client.surface.mode", String(surfaceMode || "").trim());
502
+ runtimeApp.instance("jskit.client.logger", logger);
511
503
 
512
504
  return runtimeApp;
513
505
  }
@@ -661,10 +653,4 @@ async function bootClientModules({
661
653
  });
662
654
  }
663
655
 
664
- export {
665
- CLIENT_MODULE_ROUTER_TOKEN,
666
- CLIENT_MODULE_VUE_APP_TOKEN,
667
- CLIENT_MODULE_ENV_TOKEN,
668
- CLIENT_MODULE_SURFACE_RUNTIME_TOKEN,
669
- bootClientModules
670
- };
656
+ export { bootClientModules };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/kernel",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "typebox": "^1.0.81"
@@ -30,6 +30,7 @@
30
30
  "./shared/validators": "./shared/validators/index.js",
31
31
  "./shared/validators/inputNormalization": "./shared/validators/inputNormalization.js",
32
32
  "./shared/support": "./shared/support/index.js",
33
+ "./shared/support/stringCase": "./shared/support/stringCase.js",
33
34
  "./shared/support/tokens": "./shared/support/tokens.js",
34
35
  "./shared/support/normalize": "./shared/support/normalize.js",
35
36
  "./shared/support/permissions": "./shared/support/permissions.js",
@@ -4,8 +4,8 @@ import { createNoopIdempotencyAdapter } from "../../shared/actions/idempotency.j
4
4
  import { createNoopObservabilityAdapter } from "../../shared/actions/observability.js";
5
5
  import { createActionRegistry } from "../../shared/actions/registry.js";
6
6
  import { createSurfaceRuntime } from "../../shared/surface/runtime.js";
7
+ import { isContainerToken } from "../../shared/support/containerToken.js";
7
8
  import { normalizeObject } from "../../shared/support/normalize.js";
8
- import { KERNEL_TOKENS, isContainerToken } from "../../shared/support/tokens.js";
9
9
  import { installServiceRegistrationApi } from "../registries/serviceRegistrationRegistry.js";
10
10
  import {
11
11
  ensureActionSurfaceSourceRegistry,
@@ -24,9 +24,6 @@ const ACTION_RUNTIME_API = Object.freeze({
24
24
  createNoopAuditAdapter,
25
25
  createNoopObservabilityAdapter
26
26
  });
27
- const ACTION_RUNTIME_CONTRIBUTOR_TAG = Symbol.for("jskit.runtime.actions.contributors");
28
- const ACTION_CONTEXT_CONTRIBUTOR_TAG = Symbol.for("jskit.runtime.actions.contextContributors");
29
- const LOGGER_TOKEN = Symbol.for("jskit.logger");
30
27
  let ACTION_RUNTIME_CONTRIBUTOR_INDEX = 0;
31
28
 
32
29
  function createSurfaceRuntimeFromAppConfig(scope) {
@@ -36,7 +33,7 @@ function createSurfaceRuntimeFromAppConfig(scope) {
36
33
 
37
34
  if (!scope.has("appConfig")) {
38
35
  throw new Error(
39
- "ActionRuntimeServiceProvider requires appConfig.surfaceDefinitions when KERNEL_TOKENS.SurfaceRuntime is not registered."
36
+ "ActionRuntimeServiceProvider requires appConfig.surfaceDefinitions when jskit.surface.runtime is not registered."
40
37
  );
41
38
  }
42
39
 
@@ -44,7 +41,7 @@ function createSurfaceRuntimeFromAppConfig(scope) {
44
41
  const surfaceDefinitions = normalizeObject(appConfig.surfaceDefinitions);
45
42
  if (Object.keys(surfaceDefinitions).length < 1) {
46
43
  throw new Error(
47
- "ActionRuntimeServiceProvider requires appConfig.surfaceDefinitions when KERNEL_TOKENS.SurfaceRuntime is not registered."
44
+ "ActionRuntimeServiceProvider requires appConfig.surfaceDefinitions when jskit.surface.runtime is not registered."
48
45
  );
49
46
  }
50
47
 
@@ -74,13 +71,13 @@ function normalizeDependencyMap(value, { context = "action dependencies" } = {})
74
71
  }
75
72
 
76
73
  function resolveActionContributors(scope) {
77
- return resolveRegistryTaggedEntries(scope, ACTION_RUNTIME_CONTRIBUTOR_TAG).filter(
74
+ return resolveRegistryTaggedEntries(scope, "jskit.runtime.actions.contributors").filter(
78
75
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry)
79
76
  );
80
77
  }
81
78
 
82
79
  function resolveActionContextContributors(scope) {
83
- return resolveRegistryTaggedEntries(scope, ACTION_CONTEXT_CONTRIBUTOR_TAG)
80
+ return resolveRegistryTaggedEntries(scope, "jskit.runtime.actions.contextContributors")
84
81
  .map((entry) => normalizeContributorEntry(entry))
85
82
  .filter(Boolean);
86
83
  }
@@ -187,7 +184,7 @@ function registerActionDefinition(app, actionSpec, { context = "app.action" } =
187
184
  actions: Object.freeze([action])
188
185
  };
189
186
  },
190
- ACTION_RUNTIME_CONTRIBUTOR_TAG,
187
+ "jskit.runtime.actions.contributors",
191
188
  { context }
192
189
  );
193
190
  }
@@ -256,7 +253,7 @@ function registerActionContextContributor(app, token, factory) {
256
253
  app,
257
254
  token,
258
255
  factory,
259
- ACTION_CONTEXT_CONTRIBUTOR_TAG,
256
+ "jskit.runtime.actions.contextContributors",
260
257
  { context: "registerActionContextContributor" }
261
258
  );
262
259
  }
@@ -273,8 +270,8 @@ class ActionRuntimeServiceProvider {
273
270
  ensureActionSurfaceSourceRegistry(app);
274
271
  installServiceRegistrationApi(app);
275
272
 
276
- if (!app.has(KERNEL_TOKENS.SurfaceRuntime)) {
277
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, (scope) => createSurfaceRuntimeFromAppConfig(scope));
273
+ if (!app.has("jskit.surface.runtime")) {
274
+ app.singleton("jskit.surface.runtime", (scope) => createSurfaceRuntimeFromAppConfig(scope));
278
275
  }
279
276
 
280
277
  app.singleton("runtime.actions", () => ACTION_RUNTIME_API);
@@ -286,7 +283,7 @@ class ActionRuntimeServiceProvider {
286
283
  idempotencyAdapter: createNoopIdempotencyAdapter(),
287
284
  auditAdapter: createNoopAuditAdapter(),
288
285
  observabilityAdapter: createNoopObservabilityAdapter(),
289
- logger: scope.has(LOGGER_TOKEN) ? scope.make(LOGGER_TOKEN) : console
286
+ logger: scope.has("jskit.logger") ? scope.make("jskit.logger") : console
290
287
  });
291
288
  });
292
289
  }
@@ -300,8 +297,6 @@ class ActionRuntimeServiceProvider {
300
297
  }
301
298
 
302
299
  export {
303
- ACTION_RUNTIME_CONTRIBUTOR_TAG,
304
- ACTION_CONTEXT_CONTRIBUTOR_TAG,
305
300
  resolveActionContributors,
306
301
  resolveActionContextContributors,
307
302
  registerActionContextContributor,
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
 
4
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
5
4
  import {
6
5
  EMPTY_INPUT_VALIDATOR,
7
6
  OBJECT_INPUT_VALIDATOR
@@ -71,7 +70,7 @@ test("ActionRuntimeServiceProvider registers runtime actions api and action exec
71
70
  assert.equal(app.singletons.has("runtime.actions"), true);
72
71
  assert.equal(app.singletons.has("actionRegistry"), true);
73
72
  assert.equal(app.singletons.has("actionExecutor"), true);
74
- assert.equal(app.singletons.has(KERNEL_TOKENS.SurfaceRuntime), true);
73
+ assert.equal(app.singletons.has("jskit.surface.runtime"), true);
75
74
  assert.equal(typeof app.action, "function");
76
75
  assert.equal(typeof app.actions, "function");
77
76
  assert.equal(typeof app.actionSurfaceSource, "function");
@@ -87,7 +86,7 @@ test("ActionRuntimeServiceProvider materializes dependencies and surfaces for ap
87
86
  const provider = new ActionRuntimeServiceProvider();
88
87
  provider.register(app);
89
88
 
90
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
89
+ app.singleton("jskit.surface.runtime", () => ({
91
90
  listEnabledSurfaceIds() {
92
91
  return ["app", "admin", "console"];
93
92
  }
@@ -175,7 +174,7 @@ test("ActionRuntimeServiceProvider materializes custom surfacesFrom aliases regi
175
174
  const provider = new ActionRuntimeServiceProvider();
176
175
  provider.register(app);
177
176
 
178
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
177
+ app.singleton("jskit.surface.runtime", () => ({
179
178
  listEnabledSurfaceIds() {
180
179
  return ["home", "app", "admin", "console"];
181
180
  }
@@ -247,7 +246,7 @@ test("app.actions + resolveActionContributors provide canonical contributor wiri
247
246
  const app = createSingletonApp();
248
247
  const provider = new ActionRuntimeServiceProvider();
249
248
  provider.register(app);
250
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
249
+ app.singleton("jskit.surface.runtime", () => ({
251
250
  listEnabledSurfaceIds() {
252
251
  return ["app", "admin", "console"];
253
252
  }
@@ -300,7 +299,7 @@ test("action runtime execute merges static and per-execution dependencies", asyn
300
299
  const provider = new ActionRuntimeServiceProvider();
301
300
  provider.register(app);
302
301
 
303
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
302
+ app.singleton("jskit.surface.runtime", () => ({
304
303
  listEnabledSurfaceIds() {
305
304
  return ["app"];
306
305
  }
@@ -421,7 +420,7 @@ test("EMPTY_INPUT_VALIDATOR allows empty input and rejects unexpected fields", a
421
420
  const provider = new ActionRuntimeServiceProvider();
422
421
  provider.register(app);
423
422
 
424
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
423
+ app.singleton("jskit.surface.runtime", () => ({
425
424
  listEnabledSurfaceIds() {
426
425
  return ["app"];
427
426
  }
@@ -520,7 +519,7 @@ test("app.actions rejects unsupported surfacesFrom aliases", () => {
520
519
  const provider = new ActionRuntimeServiceProvider();
521
520
  provider.register(app);
522
521
 
523
- app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
522
+ app.singleton("jskit.surface.runtime", () => ({
524
523
  listEnabledSurfaceIds() {
525
524
  return ["app", "admin"];
526
525
  }
@@ -1,6 +1,4 @@
1
1
  export {
2
- ACTION_RUNTIME_CONTRIBUTOR_TAG,
3
- ACTION_CONTEXT_CONTRIBUTOR_TAG,
4
2
  resolveActionContributors,
5
3
  resolveActionContextContributors,
6
4
  registerActionContextContributor,
@@ -1,4 +1,3 @@
1
- import { KERNEL_TOKENS } from "../../../shared/support/tokens.js";
2
1
  import { normalizeObject } from "../../../shared/support/normalize.js";
3
2
  import { ensureApiErrorHandling } from "../../runtime/fastifyBootstrap.js";
4
3
  import { resolveDefaultSurfaceId } from "../../support/appConfig.js";
@@ -13,8 +12,8 @@ function registerHttpRuntime(app, options = {}) {
13
12
 
14
13
  const runtimeOptions = normalizeObject(options);
15
14
  const {
16
- fastifyToken = KERNEL_TOKENS.Fastify,
17
- routerToken = KERNEL_TOKENS.HttpRouter,
15
+ fastifyToken = "jskit.fastify",
16
+ routerToken = "jskit.http.router",
18
17
  autoRegisterApiErrorHandling = true,
19
18
  apiErrorHandling = {},
20
19
  ...routeRegistrationOptions
@@ -55,13 +54,13 @@ function createHttpRuntime(
55
54
  }
56
55
 
57
56
  const runtimeRouter = router || createRouter();
58
- app.singleton(KERNEL_TOKENS.HttpRouter, () => runtimeRouter);
57
+ app.singleton("jskit.http.router", () => runtimeRouter);
59
58
 
60
59
  if (fastify) {
61
- app.instance(KERNEL_TOKENS.Fastify, fastify);
60
+ app.instance("jskit.fastify", fastify);
62
61
  if (autoRegisterApiErrorHandling !== false) {
63
62
  ensureApiErrorHandling(app, {
64
- fastifyToken: KERNEL_TOKENS.Fastify,
63
+ fastifyToken: "jskit.fastify",
65
64
  ...normalizeObject(apiErrorHandling)
66
65
  });
67
66
  }
@@ -11,7 +11,6 @@ export {
11
11
  createHttpRuntime
12
12
  } from "./kernel.js";
13
13
  export {
14
- ROUTE_VISIBILITY_RESOLVER_TAG,
15
14
  resolveRouteVisibilityResolvers,
16
15
  registerRouteVisibilityResolver,
17
16
  resolveRouteVisibilityContext
@@ -1,7 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
-
4
- import { KERNEL_TOKENS } from "../../../shared/support/tokens.js";
5
3
  import { registerActionContextContributor } from "../../actions/ActionRuntimeServiceProvider.js";
6
4
  import { createApplication } from "../../kernel/index.js";
7
5
  import { createRouter } from "./router.js";
@@ -59,16 +57,16 @@ test("registerRoutes attaches request scope and request context tokens", async (
59
57
  path: "/scope-check",
60
58
  middleware: [
61
59
  (request) => {
62
- observed.middlewareRequest = request.scope.make(KERNEL_TOKENS.Request);
63
- observed.middlewareReply = request.scope.make(KERNEL_TOKENS.Reply);
64
- observed.middlewareRequestId = request.scope.make(KERNEL_TOKENS.RequestId);
65
- observed.middlewareScope = request.scope.make(KERNEL_TOKENS.RequestScope);
60
+ observed.middlewareRequest = request.scope.make("jskit.http.request");
61
+ observed.middlewareReply = request.scope.make("jskit.http.reply");
62
+ observed.middlewareRequestId = request.scope.make("jskit.http.requestId");
63
+ observed.middlewareScope = request.scope.make("jskit.http.requestScope");
66
64
  }
67
65
  ],
68
66
  handler: async (request, reply) => {
69
67
  observed.handlerScope = request.scope;
70
- observed.handlerRequest = request.scope.make(KERNEL_TOKENS.Request);
71
- observed.handlerRequestId = request.scope.make(KERNEL_TOKENS.RequestId);
68
+ observed.handlerRequest = request.scope.make("jskit.http.request");
69
+ observed.handlerRequestId = request.scope.make("jskit.http.requestId");
72
70
  reply.code(200).send({ ok: true });
73
71
  }
74
72
  }
@@ -441,7 +439,7 @@ test("registerRoutes supports custom request scope property and requestId resolv
441
439
  assert.equal(Boolean(request.scope), false);
442
440
  assert.equal(Boolean(request.requestScope), true);
443
441
  assert.equal(request.requestScope.scopeId, "request:r-42");
444
- assert.equal(request.requestScope.make(KERNEL_TOKENS.RequestId), "r-42");
442
+ assert.equal(request.requestScope.make("jskit.http.requestId"), "r-42");
445
443
  reply.code(200).send({ ok: true });
446
444
  }
447
445
  }
@@ -467,12 +465,12 @@ test("registerHttpRuntime passes app context so request scope is available", asy
467
465
  const router = createRouter();
468
466
 
469
467
  router.get("/runtime-scope", async (request, reply) => {
470
- const requestId = request.scope.make(KERNEL_TOKENS.RequestId);
468
+ const requestId = request.scope.make("jskit.http.requestId");
471
469
  reply.code(200).send({ requestId });
472
470
  });
473
471
 
474
- app.instance(KERNEL_TOKENS.Fastify, fastify);
475
- app.instance(KERNEL_TOKENS.HttpRouter, router);
472
+ app.instance("jskit.fastify", fastify);
473
+ app.instance("jskit.http.router", router);
476
474
 
477
475
  const registration = registerHttpRuntime(app);
478
476
  assert.equal(registration.routeCount, 1);
@@ -492,8 +490,8 @@ test("registerHttpRuntime installs API error handling once by default", () => {
492
490
  const fastify = createFastifyStub();
493
491
  const router = createRouter();
494
492
 
495
- app.instance(KERNEL_TOKENS.Fastify, fastify);
496
- app.instance(KERNEL_TOKENS.HttpRouter, router);
493
+ app.instance("jskit.fastify", fastify);
494
+ app.instance("jskit.http.router", router);
497
495
 
498
496
  registerHttpRuntime(app);
499
497
  registerHttpRuntime(app);
@@ -507,8 +505,8 @@ test("registerHttpRuntime can disable automatic API error handling", () => {
507
505
  const fastify = createFastifyStub();
508
506
  const router = createRouter();
509
507
 
510
- app.instance(KERNEL_TOKENS.Fastify, fastify);
511
- app.instance(KERNEL_TOKENS.HttpRouter, router);
508
+ app.instance("jskit.fastify", fastify);
509
+ app.instance("jskit.http.router", router);
512
510
 
513
511
  registerHttpRuntime(app, {
514
512
  autoRegisterApiErrorHandling: false
@@ -530,8 +528,8 @@ test("registerHttpRuntime resolves request action default surface from appConfig
530
528
  reply.code(200).send({ ok: true });
531
529
  });
532
530
 
533
- app.instance(KERNEL_TOKENS.Fastify, fastify);
534
- app.instance(KERNEL_TOKENS.HttpRouter, router);
531
+ app.instance("jskit.fastify", fastify);
532
+ app.instance("jskit.http.router", router);
535
533
  app.instance("appConfig", {
536
534
  surfaceDefaultId: "home"
537
535
  });
@@ -621,8 +619,8 @@ test("registerHttpRuntime forwards middleware alias/group config to route execut
621
619
  }
622
620
  );
623
621
 
624
- app.instance(KERNEL_TOKENS.Fastify, fastify);
625
- app.instance(KERNEL_TOKENS.HttpRouter, router);
622
+ app.instance("jskit.fastify", fastify);
623
+ app.instance("jskit.http.router", router);
626
624
 
627
625
  registerHttpRuntime(app, {
628
626
  middleware: {
@@ -1,5 +1,3 @@
1
- import { KERNEL_TOKENS } from "../../../shared/support/tokens.js";
2
-
3
1
  function resolveRequestRuntimeId({ request = null, requestIdResolver = null } = {}) {
4
2
  if (typeof requestIdResolver === "function") {
5
3
  const resolvedByResolver = String(requestIdResolver(request) || "").trim();
@@ -44,10 +42,10 @@ function attachRequestScope({
44
42
  return null;
45
43
  }
46
44
 
47
- scope.instance(KERNEL_TOKENS.Request, request);
48
- scope.instance(KERNEL_TOKENS.Reply, reply);
49
- scope.instance(KERNEL_TOKENS.RequestId, runtimeRequestId);
50
- scope.instance(KERNEL_TOKENS.RequestScope, scope);
45
+ scope.instance("jskit.http.request", request);
46
+ scope.instance("jskit.http.reply", reply);
47
+ scope.instance("jskit.http.requestId", runtimeRequestId);
48
+ scope.instance("jskit.http.requestScope", scope);
51
49
 
52
50
  if (request && typeof request === "object") {
53
51
  request[normalizeRequestScopeProperty(requestScopeProperty)] = scope;
@@ -3,7 +3,7 @@ import { mergeValidators } from "../../../shared/validators/mergeValidators.js";
3
3
  import { RouteDefinitionError } from "./errors.js";
4
4
  import { resolveRouteLabel } from "./routeSupport.js";
5
5
 
6
- const ROUTE_VALIDATOR_SYMBOL = Symbol.for("@jskit-ai/kernel/http/routeValidator");
6
+ const ROUTE_VALIDATOR_SYMBOL = "@jskit-ai/kernel/http/routeValidator";
7
7
  const VALIDATOR_OPTION_KEYS = Object.freeze([
8
8
  "meta",
9
9
  "bodyValidator",
@@ -1,4 +1,4 @@
1
- import { ensureNonEmptyText, normalizeArray, normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
1
+ import { ensureNonEmptyText, normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
2
2
  import { RouteDefinitionError } from "./errors.js";
3
3
  import { resolveRouteValidatorOptions } from "./routeValidator.js";
4
4
  import { normalizeMiddlewareStack as normalizeSharedMiddlewareStack } from "./routeSupport.js";
@@ -2,7 +2,6 @@ import { ActionRuntimeServiceProvider } from "../actions/ActionRuntimeServicePro
2
2
  import { ServerRuntimeCoreServiceProvider } from "../runtime/ServerRuntimeCoreServiceProvider.js";
3
3
  import { createApplication } from "../kernel/index.js";
4
4
  import { createHttpRuntime } from "../http/lib/kernel.js";
5
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
6
5
  import { readLockFromApp } from "./providerRuntime/lockfile.js";
7
6
  import {
8
7
  collectGlobalUiPaths,
@@ -27,8 +26,8 @@ async function createProviderRuntimeApp({
27
26
  profile
28
27
  });
29
28
 
30
- app.instance(KERNEL_TOKENS.Env, env && typeof env === "object" ? { ...env } : {});
31
- app.instance(KERNEL_TOKENS.Logger, logger || console);
29
+ app.instance("jskit.env", env && typeof env === "object" ? { ...env } : {});
30
+ app.instance("jskit.logger", logger || console);
32
31
 
33
32
  let httpRuntime = null;
34
33
  if (fastify && typeof fastify.route === "function") {
@@ -1,7 +1,5 @@
1
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
2
1
  import { assertTaggableApp } from "./primitives.js";
3
2
 
4
- const ACTION_SURFACE_SOURCE_REGISTRY_TOKEN = Symbol.for("jskit.runtime.actions.surfaceSourceRegistry");
5
3
  const ACTION_SURFACE_SOURCE_NAME_PATTERN = /^[a-z][a-z0-9_.-]*$/;
6
4
 
7
5
  function normalizeSurfaceIdValue(value) {
@@ -22,11 +20,11 @@ function resolveSurfaceRuntime(scope) {
22
20
  throw new Error("Action definition materialization requires scope.has()/make().");
23
21
  }
24
22
 
25
- if (!scope.has(KERNEL_TOKENS.SurfaceRuntime)) {
26
- throw new Error("Action definition surfacesFrom requires KERNEL_TOKENS.SurfaceRuntime.");
23
+ if (!scope.has("jskit.surface.runtime")) {
24
+ throw new Error("Action definition surfacesFrom requires jskit.surface.runtime.");
27
25
  }
28
26
 
29
- return scope.make(KERNEL_TOKENS.SurfaceRuntime);
27
+ return scope.make("jskit.surface.runtime");
30
28
  }
31
29
 
32
30
  function resolveEnabledSurfaceIds(surfaceRuntime) {
@@ -104,10 +102,10 @@ function resolveActionSurfaceSourceRegistry(scope) {
104
102
  if (!scope || typeof scope.has !== "function" || typeof scope.make !== "function") {
105
103
  throw new Error("Action surface source resolution requires scope.has()/make().");
106
104
  }
107
- if (!scope.has(ACTION_SURFACE_SOURCE_REGISTRY_TOKEN)) {
105
+ if (!scope.has("jskit.runtime.actions.surfaceSourceRegistry")) {
108
106
  throw new Error("Action surface source registry is not registered.");
109
107
  }
110
- return scope.make(ACTION_SURFACE_SOURCE_REGISTRY_TOKEN);
108
+ return scope.make("jskit.runtime.actions.surfaceSourceRegistry");
111
109
  }
112
110
 
113
111
  function resolveActionSurfaceSourceIds(scope, sourceName, { context = "action.surfacesFrom" } = {}) {
@@ -141,8 +139,8 @@ function ensureActionSurfaceSourceRegistry(app) {
141
139
  throw new Error("ensureActionSurfaceSourceRegistry requires app.has().");
142
140
  }
143
141
 
144
- if (!app.has(ACTION_SURFACE_SOURCE_REGISTRY_TOKEN)) {
145
- app.singleton(ACTION_SURFACE_SOURCE_REGISTRY_TOKEN, () => createActionSurfaceSourceRegistry());
142
+ if (!app.has("jskit.runtime.actions.surfaceSourceRegistry")) {
143
+ app.singleton("jskit.runtime.actions.surfaceSourceRegistry", () => createActionSurfaceSourceRegistry());
146
144
  }
147
145
  installActionSurfaceSourceRegistrationApi(app);
148
146
  }
@@ -1,16 +1,14 @@
1
1
  import { normalizeObject } from "../../shared/support/normalize.js";
2
2
  import { normalizeContributorEntry, registerTaggedSingleton, resolveTaggedEntries } from "./primitives.js";
3
3
 
4
- const BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TAG = Symbol.for("jskit.runtime.bootstrap.payloadContributors");
5
-
6
4
  function registerBootstrapPayloadContributor(app, token, factory) {
7
- registerTaggedSingleton(app, token, factory, BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TAG, {
5
+ registerTaggedSingleton(app, token, factory, "jskit.runtime.bootstrap.payloadContributors", {
8
6
  context: "registerBootstrapPayloadContributor"
9
7
  });
10
8
  }
11
9
 
12
10
  function resolveBootstrapPayloadContributors(scope) {
13
- return resolveTaggedEntries(scope, BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TAG)
11
+ return resolveTaggedEntries(scope, "jskit.runtime.bootstrap.payloadContributors")
14
12
  .map((entry) => normalizeContributorEntry(entry))
15
13
  .filter(Boolean);
16
14
  }
@@ -34,7 +32,6 @@ async function resolveBootstrapPayload(scope, context = {}) {
34
32
  }
35
33
 
36
34
  export {
37
- BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TAG,
38
35
  registerBootstrapPayloadContributor,
39
36
  resolveBootstrapPayloadContributors,
40
37
  resolveBootstrapPayload
@@ -1,8 +1,6 @@
1
1
  import { normalizeObject } from "../../shared/support/normalize.js";
2
2
  import { registerTaggedSingleton, resolveTaggedEntries } from "./primitives.js";
3
3
 
4
- const DOMAIN_EVENT_LISTENER_TAG = Symbol.for("jskit.runtime.domainEvent.listeners");
5
-
6
4
  function normalizeDomainEventListener(entry) {
7
5
  if (typeof entry === "function") {
8
6
  return {
@@ -24,13 +22,13 @@ function normalizeDomainEventListener(entry) {
24
22
  }
25
23
 
26
24
  function registerDomainEventListener(app, token, factory) {
27
- registerTaggedSingleton(app, token, factory, DOMAIN_EVENT_LISTENER_TAG, {
25
+ registerTaggedSingleton(app, token, factory, "jskit.runtime.domainEvent.listeners", {
28
26
  context: "registerDomainEventListener"
29
27
  });
30
28
  }
31
29
 
32
30
  function resolveDomainEventListeners(scope) {
33
- return resolveTaggedEntries(scope, DOMAIN_EVENT_LISTENER_TAG)
31
+ return resolveTaggedEntries(scope, "jskit.runtime.domainEvent.listeners")
34
32
  .map((entry) => normalizeDomainEventListener(entry))
35
33
  .filter(Boolean);
36
34
  }
@@ -54,7 +52,6 @@ function createDomainEvents(scope) {
54
52
  }
55
53
 
56
54
  export {
57
- DOMAIN_EVENT_LISTENER_TAG,
58
55
  registerDomainEventListener,
59
56
  resolveDomainEventListeners,
60
57
  createDomainEvents
@@ -1,17 +1,14 @@
1
1
  export {
2
- BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TAG,
3
2
  registerBootstrapPayloadContributor,
4
3
  resolveBootstrapPayloadContributors,
5
4
  resolveBootstrapPayload
6
5
  } from "./bootstrapPayloadContributorRegistry.js";
7
6
  export {
8
- DOMAIN_EVENT_LISTENER_TAG,
9
7
  registerDomainEventListener,
10
8
  resolveDomainEventListeners,
11
9
  createDomainEvents
12
10
  } from "./domainEventListenerRegistry.js";
13
11
  export {
14
- SERVICE_REGISTRATION_TAG,
15
12
  normalizeServiceRegistration,
16
13
  materializeServiceRegistration,
17
14
  registerServiceRegistration,
@@ -19,7 +16,6 @@ export {
19
16
  installServiceRegistrationApi
20
17
  } from "./serviceRegistrationRegistry.js";
21
18
  export {
22
- ROUTE_VISIBILITY_RESOLVER_TAG,
23
19
  resolveRouteVisibilityResolvers,
24
20
  registerRouteVisibilityResolver,
25
21
  resolveRouteVisibilityContext
@@ -4,8 +4,6 @@ import { ROUTE_VISIBILITY_PUBLIC } from "../../shared/support/policies.js";
4
4
  import { RouteRegistrationError } from "../http/lib/errors.js";
5
5
  import { registerTaggedSingleton, resolveTaggedEntries } from "./primitives.js";
6
6
 
7
- const ROUTE_VISIBILITY_RESOLVER_TAG = Symbol.for("jskit.runtime.http.visibilityResolvers");
8
-
9
7
  function normalizeRouteVisibilityResolver(entry) {
10
8
  if (typeof entry === "function") {
11
9
  return {
@@ -25,13 +23,13 @@ function normalizeRouteVisibilityResolver(entry) {
25
23
  }
26
24
 
27
25
  function resolveRouteVisibilityResolvers(scope) {
28
- return resolveTaggedEntries(scope, ROUTE_VISIBILITY_RESOLVER_TAG)
26
+ return resolveTaggedEntries(scope, "jskit.runtime.http.visibilityResolvers")
29
27
  .map((entry) => normalizeRouteVisibilityResolver(entry))
30
28
  .filter(Boolean);
31
29
  }
32
30
 
33
31
  function registerRouteVisibilityResolver(app, token, factory) {
34
- registerTaggedSingleton(app, token, factory, ROUTE_VISIBILITY_RESOLVER_TAG, {
32
+ registerTaggedSingleton(app, token, factory, "jskit.runtime.http.visibilityResolvers", {
35
33
  context: "registerRouteVisibilityResolver",
36
34
  ErrorType: RouteRegistrationError
37
35
  });
@@ -80,7 +78,6 @@ async function resolveRouteVisibilityContext({
80
78
  }
81
79
 
82
80
  export {
83
- ROUTE_VISIBILITY_RESOLVER_TAG,
84
81
  resolveRouteVisibilityResolvers,
85
82
  registerRouteVisibilityResolver,
86
83
  resolveRouteVisibilityContext
@@ -1,5 +1,5 @@
1
1
  import { normalizeObject, normalizePositiveInteger, normalizeText } from "../../shared/support/normalize.js";
2
- import { isContainerToken } from "../../shared/support/tokens.js";
2
+ import { isContainerToken } from "../../shared/support/containerToken.js";
3
3
  import { createEntityChangePublisher } from "../runtime/entityChangeEvents.js";
4
4
  import {
5
5
  assertTaggableApp,
@@ -8,8 +8,6 @@ import {
8
8
  resolveTaggedEntries
9
9
  } from "./primitives.js";
10
10
 
11
- const SERVICE_REGISTRATION_TAG = Symbol.for("jskit.runtime.services.registrations");
12
- const ENTITY_CHANGED_EVENT_TYPE = "entity.changed";
13
11
  const DEFAULT_REALTIME_AUDIENCE = "event_scope";
14
12
  let SERVICE_REGISTRATION_INDEX = 0;
15
13
 
@@ -28,8 +26,8 @@ function createServiceRegistrationToken() {
28
26
 
29
27
  function normalizeServiceEventType(value, { context = "service event" } = {}) {
30
28
  const normalizedType = normalizeText(value).toLowerCase();
31
- if (normalizedType !== ENTITY_CHANGED_EVENT_TYPE) {
32
- throw new TypeError(`${context}.type must be "${ENTITY_CHANGED_EVENT_TYPE}".`);
29
+ if (normalizedType !== "entity.changed") {
30
+ throw new TypeError(`${context}.type must be "entity.changed".`);
33
31
  }
34
32
  return normalizedType;
35
33
  }
@@ -173,7 +171,7 @@ function normalizeServiceEventsForDefinition(serviceDefinition, serviceMetadata)
173
171
  for (const [index, entry] of normalizedEntries.entries()) {
174
172
  if (!entry.source || !entry.entity) {
175
173
  throw new TypeError(
176
- `service metadata.events.${methodName}[${index}] requires source and entity for "${ENTITY_CHANGED_EVENT_TYPE}".`
174
+ `service metadata.events.${methodName}[${index}] requires source and entity for "entity.changed".`
177
175
  );
178
176
  }
179
177
  }
@@ -374,13 +372,13 @@ function normalizeServiceRegistration(value = {}) {
374
372
  }
375
373
 
376
374
  function registerServiceRegistration(app, token, factory) {
377
- registerTaggedSingleton(app, token, factory, SERVICE_REGISTRATION_TAG, {
375
+ registerTaggedSingleton(app, token, factory, "jskit.runtime.services.registrations", {
378
376
  context: "registerServiceRegistration"
379
377
  });
380
378
  }
381
379
 
382
380
  function resolveServiceRegistrations(scope) {
383
- return resolveTaggedEntries(scope, SERVICE_REGISTRATION_TAG)
381
+ return resolveTaggedEntries(scope, "jskit.runtime.services.registrations")
384
382
  .map((entry) => normalizeObject(entry))
385
383
  .filter((entry) => Object.keys(entry).length > 0)
386
384
  .sort((left, right) => String(left.serviceToken || "").localeCompare(String(right.serviceToken || "")));
@@ -422,7 +420,6 @@ function installServiceRegistrationApi(app) {
422
420
  }
423
421
 
424
422
  export {
425
- SERVICE_REGISTRATION_TAG,
426
423
  normalizeServiceRegistration,
427
424
  materializeServiceRegistration,
428
425
  registerServiceRegistration,
@@ -17,7 +17,6 @@ import * as serviceAuthorization from "./serviceAuthorization.js";
17
17
  import * as serviceRegistration from "../registries/serviceRegistrationRegistry.js";
18
18
  import * as entityChangeEvents from "./entityChangeEvents.js";
19
19
  import * as securityAudit from "./securityAudit.js";
20
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
21
20
 
22
21
  const SERVER_RUNTIME_CORE_API = Object.freeze({
23
22
  apiRouteRegistration: Object.freeze({ ...apiRouteRegistration }),
@@ -56,7 +55,7 @@ class ServerRuntimeCoreServiceProvider {
56
55
  }
57
56
 
58
57
  boot(app) {
59
- if (app.has(KERNEL_TOKENS.HttpRouter)) {
58
+ if (app.has("jskit.http.router")) {
60
59
  bootstrapRoutes.bootBootstrapRoutes(app);
61
60
  }
62
61
  }
@@ -1,7 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { normalizeObjectInput } from "../../shared/validators/inputNormalization.js";
3
3
  import { AUTH_POLICY_PUBLIC } from "../../shared/support/policies.js";
4
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
5
4
  import { resolveBootstrapPayload } from "../registries/bootstrapPayloadContributorRegistry.js";
6
5
 
7
6
  const bootstrapQueryValidator = Object.freeze({
@@ -15,7 +14,7 @@ const bootstrapOutputValidator = Object.freeze({
15
14
  });
16
15
 
17
16
  function bootBootstrapRoutes(app) {
18
- const router = app.make(KERNEL_TOKENS.HttpRouter);
17
+ const router = app.make("jskit.http.router");
19
18
 
20
19
  router.register(
21
20
  "GET",
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { createContainer } from "../container/index.js";
4
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
5
4
  import { registerBootstrapPayloadContributor } from "../registries/bootstrapPayloadContributorRegistry.js";
6
5
  import { bootBootstrapRoutes, bootstrapQueryValidator } from "./bootBootstrapRoutes.js";
7
6
 
@@ -42,7 +41,7 @@ test("bootBootstrapRoutes registers GET /api/bootstrap and resolves contributors
42
41
  }
43
42
  };
44
43
 
45
- app.instance(KERNEL_TOKENS.HttpRouter, router);
44
+ app.instance("jskit.http.router", router);
46
45
  registerBootstrapPayloadContributor(app, "test.bootstrap.payload", () => ({
47
46
  contributorId: "test.bootstrap.payload",
48
47
  contribute({ query }) {
@@ -1,11 +1,8 @@
1
- import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
2
1
  import { ActionRuntimeError } from "../../shared/actions/actionDefinitions.js";
3
2
  import { normalizeOpaqueId } from "../../shared/support/normalize.js";
4
3
  import { isAppError } from "./errors.js";
5
4
  import { resolveDefaultSurfaceId } from "../support/appConfig.js";
6
5
 
7
- const API_ERROR_HANDLER_MARKER_TOKEN = "kernel.runtime.apiErrorHandlerRegistered";
8
-
9
6
  function resolveLoggerLevel({ configuredLevel = "", nodeEnv = "development", allowedLevels = [] } = {}) {
10
7
  const normalizedConfiguredLevel = String(configuredLevel || "")
11
8
  .trim()
@@ -228,8 +225,8 @@ function registerApiErrorHandler(
228
225
  function ensureApiErrorHandling(
229
226
  app,
230
227
  {
231
- fastifyToken = KERNEL_TOKENS.Fastify,
232
- markerToken = API_ERROR_HANDLER_MARKER_TOKEN,
228
+ fastifyToken = "jskit.fastify",
229
+ markerToken = "kernel.runtime.apiErrorHandlerRegistered",
233
230
  isAppError: isAppErrorOverride,
234
231
  autoRegister = true,
235
232
  ...handlerOptions
@@ -243,7 +240,7 @@ function ensureApiErrorHandling(
243
240
  return false;
244
241
  }
245
242
 
246
- const normalizedMarkerToken = String(markerToken || "").trim() || API_ERROR_HANDLER_MARKER_TOKEN;
243
+ const normalizedMarkerToken = String(markerToken || "").trim() || "kernel.runtime.apiErrorHandlerRegistered";
247
244
  if (app.has(normalizedMarkerToken)) {
248
245
  return false;
249
246
  }
@@ -1,11 +1,13 @@
1
1
  import * as normalize from "../../shared/support/normalize.js";
2
2
  import * as sorting from "../../shared/support/sorting.js";
3
- import * as tokens from "../../shared/support/tokens.js";
3
+ import { isContainerToken } from "../../shared/support/containerToken.js";
4
4
 
5
5
  const SUPPORT_CORE_API = Object.freeze({
6
6
  normalize: Object.freeze({ ...normalize }),
7
7
  sorting: Object.freeze({ ...sorting }),
8
- tokens: Object.freeze({ ...tokens })
8
+ tokens: Object.freeze({
9
+ isContainerToken
10
+ })
9
11
  });
10
12
 
11
13
  class SupportCoreServiceProvider {
@@ -0,0 +1,8 @@
1
+ function isContainerToken(value) {
2
+ if (typeof value === "string") {
3
+ return value.trim().length > 0;
4
+ }
5
+ return typeof value === "symbol" || typeof value === "function";
6
+ }
7
+
8
+ export { isContainerToken };
@@ -7,6 +7,7 @@ export {
7
7
  normalizeReturnToPath,
8
8
  resolveAllowedOriginsFromPlacementContext
9
9
  } from "./returnToPath.js";
10
+ export { toCamelCase, toSnakeCase } from "./stringCase.js";
10
11
  export {
11
12
  isTransientQueryError,
12
13
  shouldRetryTransientQueryFailure,
@@ -3,6 +3,47 @@ function normalizeText(value, { fallback = "" } = {}) {
3
3
  return normalized || fallback;
4
4
  }
5
5
 
6
+ function normalizeBoolean(value) {
7
+ if (typeof value === "boolean") {
8
+ return value;
9
+ }
10
+
11
+ if (typeof value === "number") {
12
+ if (value === 1) {
13
+ return true;
14
+ }
15
+ if (value === 0) {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ const normalized = normalizeText(value).toLowerCase();
21
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
22
+ return true;
23
+ }
24
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
25
+ return false;
26
+ }
27
+
28
+ throw new TypeError("Boolean field must be true or false.");
29
+ }
30
+
31
+ function normalizeFiniteNumber(value) {
32
+ const normalized = Number(value);
33
+ if (!Number.isFinite(normalized)) {
34
+ throw new TypeError("Number field must be a valid number.");
35
+ }
36
+ return normalized;
37
+ }
38
+
39
+ function normalizeFiniteInteger(value) {
40
+ const normalized = normalizeFiniteNumber(value);
41
+ if (!Number.isInteger(normalized)) {
42
+ throw new TypeError("Number field must be an integer.");
43
+ }
44
+ return normalized;
45
+ }
46
+
6
47
  function normalizeLowerText(value, { fallback = "" } = {}) {
7
48
  return normalizeText(value, {
8
49
  fallback
@@ -21,6 +62,50 @@ function normalizeObject(value, { fallback = {} } = {}) {
21
62
  return value;
22
63
  }
23
64
 
65
+ function normalizeIfInSource(source, normalized, fieldName, normalizer = (value) => value) {
66
+ if (!isRecord(source)) {
67
+ return normalized;
68
+ }
69
+ if (!isRecord(normalized)) {
70
+ throw new TypeError("normalizeIfInSource requires target object.");
71
+ }
72
+
73
+ const normalizedFieldName = normalizeText(fieldName);
74
+ if (!normalizedFieldName) {
75
+ throw new TypeError("normalizeIfInSource requires fieldName.");
76
+ }
77
+ if (typeof normalizer !== "function") {
78
+ throw new TypeError("normalizeIfInSource requires normalizer function.");
79
+ }
80
+
81
+ if (!Object.hasOwn(source, normalizedFieldName)) {
82
+ return normalized;
83
+ }
84
+
85
+ const sourceValue = source[normalizedFieldName];
86
+ if (sourceValue == null) {
87
+ normalized[normalizedFieldName] = sourceValue;
88
+ return normalized;
89
+ }
90
+
91
+ normalized[normalizedFieldName] = normalizer(sourceValue);
92
+ return normalized;
93
+ }
94
+
95
+ function normalizeIfPresent(value, normalizer = (entry) => entry) {
96
+ if (typeof normalizer !== "function") {
97
+ throw new TypeError("normalizeIfPresent requires normalizer function.");
98
+ }
99
+ return value == null ? value : normalizer(value);
100
+ }
101
+
102
+ function normalizeOrNull(value, normalizer = (entry) => entry) {
103
+ if (typeof normalizer !== "function") {
104
+ throw new TypeError("normalizeOrNull requires normalizer function.");
105
+ }
106
+ return value == null ? null : normalizer(value);
107
+ }
108
+
24
109
  function isRecord(value) {
25
110
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
26
111
  }
@@ -108,9 +193,15 @@ function ensureNonEmptyText(value, label = "value") {
108
193
 
109
194
  export {
110
195
  normalizeText,
196
+ normalizeBoolean,
197
+ normalizeFiniteNumber,
198
+ normalizeFiniteInteger,
111
199
  normalizeLowerText,
112
200
  normalizeQueryToken,
113
201
  normalizeObject,
202
+ normalizeIfInSource,
203
+ normalizeIfPresent,
204
+ normalizeOrNull,
114
205
  isRecord,
115
206
  normalizeArray,
116
207
  normalizeUniqueTextList,
@@ -1,10 +1,17 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import {
4
+ normalizeBoolean,
5
+ normalizeFiniteInteger,
6
+ normalizeFiniteNumber,
7
+ normalizeIfInSource,
8
+ normalizeIfPresent,
9
+ normalizeOrNull,
4
10
  normalizeOpaqueId,
5
11
  normalizePositiveInteger,
6
12
  normalizeOneOf,
7
13
  normalizeQueryToken,
14
+ normalizeText,
8
15
  normalizeUniqueTextList
9
16
  } from "./normalize.js";
10
17
 
@@ -39,6 +46,95 @@ test("normalizePositiveInteger normalizes valid integers and applies fallback",
39
46
  );
40
47
  });
41
48
 
49
+ test("normalizeBoolean normalizes supported boolean-like values", () => {
50
+ assert.equal(normalizeBoolean(true), true);
51
+ assert.equal(normalizeBoolean(false), false);
52
+ assert.equal(normalizeBoolean(1), true);
53
+ assert.equal(normalizeBoolean(0), false);
54
+ assert.equal(normalizeBoolean(" yes "), true);
55
+ assert.equal(normalizeBoolean("No"), false);
56
+ assert.throws(() => normalizeBoolean("maybe"), /Boolean field must be true or false/);
57
+ });
58
+
59
+ test("normalizeFiniteNumber normalizes numeric values", () => {
60
+ assert.equal(normalizeFiniteNumber("42.5"), 42.5);
61
+ assert.equal(normalizeFiniteNumber("7"), 7);
62
+ assert.throws(() => normalizeFiniteNumber("abc"), /Number field must be a valid number/);
63
+ });
64
+
65
+ test("normalizeFiniteInteger normalizes integer values", () => {
66
+ assert.equal(normalizeFiniteInteger("7"), 7);
67
+ assert.throws(
68
+ () => normalizeFiniteInteger("3.2"),
69
+ /Number field must be an integer/
70
+ );
71
+ });
72
+
73
+ test("normalizeIfInSource normalizes only present source fields", () => {
74
+ const source = {
75
+ firstName: " Ada "
76
+ };
77
+ const normalized = {};
78
+
79
+ normalizeIfInSource(source, normalized, "firstName", normalizeText);
80
+ normalizeIfInSource(source, normalized, "lastName", normalizeText);
81
+
82
+ assert.deepEqual(normalized, {
83
+ firstName: "Ada"
84
+ });
85
+ });
86
+
87
+ test("normalizeIfInSource passes through nullish values without normalizer", () => {
88
+ const source = {
89
+ firstName: null,
90
+ lastName: undefined
91
+ };
92
+ const normalized = {};
93
+
94
+ normalizeIfInSource(source, normalized, "firstName", normalizeText);
95
+ normalizeIfInSource(source, normalized, "lastName", normalizeText);
96
+
97
+ assert.deepEqual(normalized, {
98
+ firstName: null,
99
+ lastName: undefined
100
+ });
101
+ });
102
+
103
+ test("normalizeIfInSource validates target and function arguments", () => {
104
+ assert.throws(
105
+ () => normalizeIfInSource({ firstName: "Ada" }, null, "firstName", normalizeText),
106
+ /requires target object/
107
+ );
108
+ assert.throws(
109
+ () => normalizeIfInSource({ firstName: "Ada" }, {}, "", normalizeText),
110
+ /requires fieldName/
111
+ );
112
+ assert.throws(
113
+ () => normalizeIfInSource({ firstName: "Ada" }, {}, "firstName", null),
114
+ /requires normalizer function/
115
+ );
116
+ });
117
+
118
+ test("normalizeIfPresent normalizes non-nullish values and preserves nullish values", () => {
119
+ assert.equal(normalizeIfPresent(" Ada ", normalizeText), "Ada");
120
+ assert.equal(normalizeIfPresent(null, normalizeText), null);
121
+ assert.equal(normalizeIfPresent(undefined, normalizeText), undefined);
122
+ assert.throws(
123
+ () => normalizeIfPresent("Ada", null),
124
+ /requires normalizer function/
125
+ );
126
+ });
127
+
128
+ test("normalizeOrNull normalizes non-nullish values and coerces nullish to null", () => {
129
+ assert.equal(normalizeOrNull(" Ada ", normalizeText), "Ada");
130
+ assert.equal(normalizeOrNull(null, normalizeText), null);
131
+ assert.equal(normalizeOrNull(undefined, normalizeText), null);
132
+ assert.throws(
133
+ () => normalizeOrNull("Ada", null),
134
+ /requires normalizer function/
135
+ );
136
+ });
137
+
42
138
  test("normalizeOpaqueId preserves opaque identifiers", () => {
43
139
  assert.equal(normalizeOpaqueId(" user-123 "), "user-123");
44
140
  assert.equal(normalizeOpaqueId(7), 7);
@@ -0,0 +1,26 @@
1
+ function toCamelCase(value = "") {
2
+ return String(value || "")
3
+ .replace(/[-_]+([a-zA-Z0-9])/g, (_, nextChar) => String(nextChar || "").toUpperCase())
4
+ .replace(/^[A-Z]/, (char) => char.toLowerCase())
5
+ .replace(/[-_]+$/g, "");
6
+ }
7
+
8
+ function toSnakeCase(value = "") {
9
+ const source = String(value || "");
10
+ if (!source) {
11
+ return "";
12
+ }
13
+
14
+ const replaced = source
15
+ .replace(/([A-Z0-9]+)([A-Z][a-z])/g, "$1_$2")
16
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
17
+ .replace(/[-\s]+/g, "_")
18
+ .replace(/__+/g, "_");
19
+
20
+ return replaced
21
+ .toLowerCase()
22
+ .replace(/^_+/, "")
23
+ .replace(/_+$/, "");
24
+ }
25
+
26
+ export { toCamelCase, toSnakeCase };
@@ -1,23 +1 @@
1
- const KERNEL_TOKENS = Object.freeze({
2
- Logger: Symbol.for("jskit.logger"),
3
- Env: Symbol.for("jskit.env"),
4
- Fastify: Symbol.for("jskit.fastify"),
5
- HttpRouter: Symbol.for("jskit.http.router"),
6
- Request: Symbol.for("jskit.http.request"),
7
- Reply: Symbol.for("jskit.http.reply"),
8
- RequestId: Symbol.for("jskit.http.requestId"),
9
- RequestScope: Symbol.for("jskit.http.requestScope"),
10
- Knex: Symbol.for("jskit.database.knex"),
11
- TransactionManager: Symbol.for("jskit.database.transactionManager"),
12
- Storage: Symbol.for("jskit.storage"),
13
- SurfaceRuntime: Symbol.for("jskit.surface.runtime")
14
- });
15
-
16
- function isContainerToken(value) {
17
- if (typeof value === "string") {
18
- return value.trim().length > 0;
19
- }
20
- return typeof value === "symbol" || typeof value === "function";
21
- }
22
-
23
- export { KERNEL_TOKENS, isContainerToken };
1
+ export { isContainerToken } from "./containerToken.js";
@@ -1,10 +1,10 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { isContainerToken } from "./tokens.js";
3
+ import { isContainerToken } from "./containerToken.js";
4
4
 
5
5
  test("isContainerToken accepts valid container token types", () => {
6
6
  assert.equal(isContainerToken("appConfig"), true);
7
- assert.equal(isContainerToken(Symbol.for("jskit.test")), true);
7
+ assert.equal(isContainerToken("jskit.test"), true);
8
8
  assert.equal(isContainerToken(() => {}), true);
9
9
  });
10
10
 
@@ -34,6 +34,8 @@ const BARREL_EXPECTATIONS = Object.freeze([
34
34
  "pickOwnProperties",
35
35
  "resolveAllowedOriginsFromPlacementContext",
36
36
  "splitPathQueryAndHash",
37
+ "toCamelCase",
38
+ "toSnakeCase",
37
39
  "shouldRetryTransientQueryFailure",
38
40
  "transientQueryRetryDelay"
39
41
  ])
@@ -20,7 +20,13 @@ const SCANNED_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".vue"
20
20
  const IGNORED_DIRECTORIES = new Set(["node_modules", ".git", "dist", "coverage"]);
21
21
  const EXCLUDED_USAGE_PATH_SEGMENTS = new Set(["test", "tests", "__tests__", "test-support"]);
22
22
  const TEST_FILENAME_PATTERN = /\.(test|spec)\.[A-Za-z0-9]+$/;
23
- const EXPORTED_UNUSED_ALLOWLIST = new Set(["./_testable"]);
23
+ const EXPORTED_UNUSED_ALLOWLIST = new Set([
24
+ "./_testable",
25
+ // Used by generated app bootstrap code emitted from kernel client vite plugin.
26
+ "./client/moduleBootstrap",
27
+ // Intentionally retained as an explicit low-level kernel API subpath.
28
+ "./shared/support/tokens"
29
+ ]);
24
30
 
25
31
  function normalizeSlash(value) {
26
32
  return String(value || "").replace(/\\/g, "/");
@@ -0,0 +1,22 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { toCamelCase, toSnakeCase } from "../shared/support/stringCase.js";
4
+
5
+ test("toCamelCase converts snake and kebab to camelCase", () => {
6
+ assert.equal(toCamelCase("created_at"), "createdAt");
7
+ assert.equal(toCamelCase("user-id"), "userId");
8
+ assert.equal(toCamelCase("__leading__dots__"), "leadingDots");
9
+ });
10
+
11
+ test("toSnakeCase converts camelCase and PascalCase to snake_case", () => {
12
+ assert.equal(toSnakeCase("createdAt"), "created_at");
13
+ assert.equal(toSnakeCase("XMLHttpRequest"), "xml_http_request");
14
+ assert.equal(toSnakeCase("userID"), "user_id");
15
+ assert.equal(toSnakeCase("already_snake"), "already_snake");
16
+ });
17
+
18
+ test("toSnakeCase handles empty inputs gracefully", () => {
19
+ assert.equal(toSnakeCase(""), "");
20
+ assert.equal(toSnakeCase(null), "");
21
+ assert.equal(toSnakeCase(" "), "");
22
+ });