@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.
- package/client/moduleBootstrap.js +8 -22
- package/package.json +2 -1
- package/server/actions/ActionRuntimeServiceProvider.js +10 -15
- package/server/actions/ActionRuntimeServiceProvider.test.js +7 -8
- package/server/actions/index.js +0 -2
- package/server/http/lib/httpRuntime.js +5 -6
- package/server/http/lib/index.js +0 -1
- package/server/http/lib/kernel.test.js +18 -20
- package/server/http/lib/requestScope.js +4 -6
- package/server/http/lib/routeValidator.js +1 -1
- package/server/http/lib/router.js +1 -1
- package/server/platform/providerRuntime.js +2 -3
- package/server/registries/actionSurfaceSourceRegistry.js +7 -9
- package/server/registries/bootstrapPayloadContributorRegistry.js +2 -5
- package/server/registries/domainEventListenerRegistry.js +2 -5
- package/server/registries/index.js +0 -4
- package/server/registries/routeVisibilityResolverRegistry.js +2 -5
- package/server/registries/serviceRegistrationRegistry.js +6 -9
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +1 -2
- package/server/runtime/bootBootstrapRoutes.js +1 -2
- package/server/runtime/bootBootstrapRoutes.test.js +1 -2
- package/server/runtime/fastifyBootstrap.js +3 -6
- package/server/support/SupportCoreServiceProvider.js +4 -2
- package/shared/support/containerToken.js +8 -0
- package/shared/support/index.js +1 -0
- package/shared/support/normalize.js +91 -0
- package/shared/support/normalize.test.js +96 -0
- package/shared/support/stringCase.js +26 -0
- package/shared/support/tokens.js +1 -23
- package/shared/support/tokens.test.js +2 -2
- package/test/barrelExposure.test.js +2 -0
- package/test/exportsContract.test.js +7 -1
- 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(
|
|
505
|
-
runtimeApp.instance(
|
|
506
|
-
runtimeApp.instance(
|
|
507
|
-
runtimeApp.instance(
|
|
508
|
-
runtimeApp.instance(
|
|
509
|
-
runtimeApp.instance(
|
|
510
|
-
runtimeApp.instance(
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
277
|
-
app.singleton(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
522
|
+
app.singleton("jskit.surface.runtime", () => ({
|
|
524
523
|
listEnabledSurfaceIds() {
|
|
525
524
|
return ["app", "admin"];
|
|
526
525
|
}
|
package/server/actions/index.js
CHANGED
|
@@ -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 =
|
|
17
|
-
routerToken =
|
|
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(
|
|
57
|
+
app.singleton("jskit.http.router", () => runtimeRouter);
|
|
59
58
|
|
|
60
59
|
if (fastify) {
|
|
61
|
-
app.instance(
|
|
60
|
+
app.instance("jskit.fastify", fastify);
|
|
62
61
|
if (autoRegisterApiErrorHandling !== false) {
|
|
63
62
|
ensureApiErrorHandling(app, {
|
|
64
|
-
fastifyToken:
|
|
63
|
+
fastifyToken: "jskit.fastify",
|
|
65
64
|
...normalizeObject(apiErrorHandling)
|
|
66
65
|
});
|
|
67
66
|
}
|
package/server/http/lib/index.js
CHANGED
|
@@ -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(
|
|
63
|
-
observed.middlewareReply = request.scope.make(
|
|
64
|
-
observed.middlewareRequestId = request.scope.make(
|
|
65
|
-
observed.middlewareScope = request.scope.make(
|
|
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(
|
|
71
|
-
observed.handlerRequestId = request.scope.make(
|
|
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(
|
|
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(
|
|
468
|
+
const requestId = request.scope.make("jskit.http.requestId");
|
|
471
469
|
reply.code(200).send({ requestId });
|
|
472
470
|
});
|
|
473
471
|
|
|
474
|
-
app.instance(
|
|
475
|
-
app.instance(
|
|
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(
|
|
496
|
-
app.instance(
|
|
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(
|
|
511
|
-
app.instance(
|
|
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(
|
|
534
|
-
app.instance(
|
|
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(
|
|
625
|
-
app.instance(
|
|
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(
|
|
48
|
-
scope.instance(
|
|
49
|
-
scope.instance(
|
|
50
|
-
scope.instance(
|
|
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 =
|
|
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,
|
|
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(
|
|
31
|
-
app.instance(
|
|
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(
|
|
26
|
-
throw new Error("Action definition surfacesFrom requires
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
145
|
-
app.singleton(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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/
|
|
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 !==
|
|
32
|
-
throw new TypeError(`${context}.type must be "
|
|
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 "
|
|
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,
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
232
|
-
markerToken =
|
|
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() ||
|
|
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
|
|
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({
|
|
8
|
+
tokens: Object.freeze({
|
|
9
|
+
isContainerToken
|
|
10
|
+
})
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
class SupportCoreServiceProvider {
|
package/shared/support/index.js
CHANGED
|
@@ -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 };
|
package/shared/support/tokens.js
CHANGED
|
@@ -1,23 +1 @@
|
|
|
1
|
-
|
|
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 "./
|
|
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(
|
|
7
|
+
assert.equal(isContainerToken("jskit.test"), true);
|
|
8
8
|
assert.equal(isContainerToken(() => {}), true);
|
|
9
9
|
});
|
|
10
10
|
|
|
@@ -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([
|
|
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
|
+
});
|