@jskit-ai/kernel 0.1.159 → 0.1.161
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/_testable/index.js +1 -3
- package/client/componentRegistry.js +44 -0
- package/client/componentRegistry.test.js +16 -0
- package/client/moduleBootstrap.js +54 -97
- package/client/moduleBootstrap.test.js +47 -32
- package/client/vite/clientBootstrapPlugin.test.js +2 -2
- package/internal/node/installedPackages.js +2 -7
- package/internal/node/installedPackages.test.js +26 -5
- package/package.json +3 -5
- package/server/actions/actionCatalogue.js +162 -0
- package/server/actions/actionEvents.js +66 -0
- package/server/actions/actionProvider.js +23 -0
- package/server/actions/index.js +3 -6
- package/server/features/defineFeature.js +131 -0
- package/server/features/defineFeature.test.js +238 -0
- package/server/features/index.js +1 -0
- package/server/http/HttpProvider.js +23 -0
- package/server/http/_testable/index.js +0 -1
- package/server/http/capabilityHttpRuntime.js +123 -0
- package/server/http/capabilityHttpRuntime.test.js +161 -0
- package/server/http/directRequestActionExecutor.js +78 -0
- package/server/http/index.js +2 -1
- package/server/http/lib/index.js +1 -13
- package/server/http/lib/routeRegistration.js +9 -25
- package/server/platform/index.js +2 -2
- package/server/platform/installedRuntime.js +122 -0
- package/server/platform/installedRuntime.test.js +103 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.js +100 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.test.js +80 -0
- package/server/platform/providerRuntime/packageCatalog.js +4 -1
- package/server/platform/surfaceRuntime.js +1 -15
- package/server/runtime/BootstrapProvider.js +69 -0
- package/server/runtime/EventProvider.js +74 -0
- package/server/runtime/EventProvider.test.js +32 -0
- package/server/runtime/entityChangeEvents.js +7 -7
- package/server/runtime/entityChangeEvents.test.js +2 -2
- package/server/runtime/fastifyBootstrap.js +0 -40
- package/server/runtime/index.js +2 -3
- package/server/surface/index.js +0 -1
- package/shared/actions/actionDefinitions.js +20 -0
- package/shared/actions/pipeline.js +22 -0
- package/shared/actions/policies.js +1 -1
- package/shared/actions/registry.js +3 -0
- package/shared/capabilities/defineProvider.js +99 -0
- package/shared/capabilities/index.js +2 -0
- package/shared/capabilities/runtime.js +243 -0
- package/shared/capabilities/runtime.test.js +170 -0
- package/test/barrelExposure.test.js +36 -13
- package/test/exportsContract.test.js +8 -3
- package/server/actions/ActionRuntimeServiceProvider.js +0 -304
- package/server/actions/ActionRuntimeServiceProvider.test.js +0 -558
- package/server/container/ContainerCoreServiceProvider.js +0 -27
- package/server/container/index.js +0 -10
- package/server/http/HttpFastifyServiceProvider.js +0 -25
- package/server/http/lib/httpRuntime.js +0 -83
- package/server/http/lib/kernel.js +0 -15
- package/server/http/lib/kernel.test.js +0 -1565
- package/server/http/lib/requestActionExecutor.js +0 -265
- package/server/http/lib/requestScope.js +0 -57
- package/server/kernel/KernelCoreServiceProvider.js +0 -27
- package/server/kernel/index.js +0 -10
- package/server/platform/PlatformServerRuntimeServiceProvider.js +0 -30
- package/server/platform/providerRuntime/providerLoader.js +0 -283
- package/server/platform/providerRuntime.js +0 -146
- package/server/platform/providerRuntime.test.js +0 -214
- package/server/platform/runtime.js +0 -40
- package/server/registries/actionSurfaceSourceRegistry.js +0 -148
- package/server/registries/bootstrapPayloadContributorRegistry.js +0 -51
- package/server/registries/domainEventListenerRegistry.js +0 -58
- package/server/registries/index.js +0 -32
- package/server/registries/primitives.js +0 -63
- package/server/registries/routeVisibilityResolverRegistry.js +0 -84
- package/server/registries/serviceRegistrationRegistry.js +0 -488
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +0 -64
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +0 -53
- package/server/runtime/apiRoutePolicyParity.test.js +0 -109
- package/server/runtime/apiRouteRegistration.js +0 -65
- package/server/runtime/bootBootstrapRoutes.js +0 -29
- package/server/runtime/bootBootstrapRoutes.test.js +0 -69
- package/server/runtime/bootstrapContributors.test.js +0 -166
- package/server/runtime/composition.js +0 -142
- package/server/runtime/domainEvents.test.js +0 -114
- package/server/runtime/domainRules.js +0 -50
- package/server/runtime/domainRules.test.js +0 -87
- package/server/runtime/runtimeAssembly.js +0 -113
- package/server/runtime/runtimeKernel.js +0 -55
- package/server/runtime/serviceRegistration.test.js +0 -251
- package/server/support/SupportCoreServiceProvider.js +0 -27
- package/server/surface/SurfaceRoutingServiceProvider.js +0 -27
- package/shared/actions/index.js +0 -2
- package/shared/runtime/application.js +0 -324
- package/shared/runtime/container.js +0 -261
- package/shared/runtime/containerErrors.js +0 -22
- package/shared/runtime/index.js +0 -18
- package/shared/runtime/kernelErrors.js +0 -25
- package/shared/runtime/kernelErrors.test.js +0 -120
- package/shared/runtime/serviceProvider.js +0 -13
- package/shared/support/containerToken.js +0 -8
- package/shared/support/generatedUiContract.js +0 -542
- package/shared/support/generatedUiContract.test.js +0 -218
- package/shared/support/tokens.js +0 -1
- package/shared/support/tokens.test.js +0 -17
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createContainer } from "../container/index.js";
|
|
4
|
-
import { registerDomainEventListener } from "../registries/domainEventListenerRegistry.js";
|
|
5
|
-
import { ServerRuntimeCoreServiceProvider } from "./ServerRuntimeCoreServiceProvider.js";
|
|
6
|
-
|
|
7
|
-
test("ServerRuntimeCoreServiceProvider registers runtime.server and default domainEvents", () => {
|
|
8
|
-
const app = createContainer();
|
|
9
|
-
const provider = new ServerRuntimeCoreServiceProvider();
|
|
10
|
-
provider.register(app);
|
|
11
|
-
|
|
12
|
-
assert.equal(typeof app.service, "function");
|
|
13
|
-
|
|
14
|
-
const runtimeServer = app.make("runtime.server");
|
|
15
|
-
assert.equal(typeof runtimeServer, "object");
|
|
16
|
-
|
|
17
|
-
const domainEvents = app.make("domainEvents");
|
|
18
|
-
assert.equal(typeof domainEvents.publish, "function");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("ServerRuntimeCoreServiceProvider default domainEvents dispatches registered listeners", async () => {
|
|
22
|
-
const app = createContainer();
|
|
23
|
-
const provider = new ServerRuntimeCoreServiceProvider();
|
|
24
|
-
provider.register(app);
|
|
25
|
-
|
|
26
|
-
const received = [];
|
|
27
|
-
registerDomainEventListener(app, "test.domainEvents.alpha", () => ({
|
|
28
|
-
listenerId: "alpha",
|
|
29
|
-
async handle(payload) {
|
|
30
|
-
received.push(payload);
|
|
31
|
-
}
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
const domainEvents = app.make("domainEvents");
|
|
35
|
-
const eventPayload = {
|
|
36
|
-
source: "test"
|
|
37
|
-
};
|
|
38
|
-
const result = await domainEvents.publish(eventPayload);
|
|
39
|
-
|
|
40
|
-
assert.equal(result, null);
|
|
41
|
-
assert.deepEqual(received, [eventPayload]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("ServerRuntimeCoreServiceProvider owns domainEvents binding", async () => {
|
|
45
|
-
const app = createContainer();
|
|
46
|
-
app.singleton("domainEvents", () => ({ publish: async () => "existing" }));
|
|
47
|
-
|
|
48
|
-
const provider = new ServerRuntimeCoreServiceProvider();
|
|
49
|
-
assert.throws(
|
|
50
|
-
() => provider.register(app),
|
|
51
|
-
/Token "domainEvents" is already bound\./
|
|
52
|
-
);
|
|
53
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
defaultApplyRoutePolicy as applyHttpRoutePolicy,
|
|
5
|
-
normalizeRoutePolicyConfig as normalizeHttpRoutePolicyConfig
|
|
6
|
-
} from "../http/lib/kernel.js";
|
|
7
|
-
import { __testables as runtimeRouteRegistrationTestables } from "./apiRouteRegistration.js";
|
|
8
|
-
import {
|
|
9
|
-
defaultApplyRoutePolicy as applyCanonicalRoutePolicy,
|
|
10
|
-
normalizeRoutePolicyConfig as normalizeCanonicalRoutePolicyConfig
|
|
11
|
-
} from "../support/routePolicyConfig.js";
|
|
12
|
-
|
|
13
|
-
test("route policy mapping is sourced from one canonical mapper in both registration paths", () => {
|
|
14
|
-
assert.equal(applyHttpRoutePolicy, applyCanonicalRoutePolicy);
|
|
15
|
-
assert.equal(runtimeRouteRegistrationTestables.defaultApplyRoutePolicy, applyCanonicalRoutePolicy);
|
|
16
|
-
assert.equal(normalizeHttpRoutePolicyConfig, normalizeCanonicalRoutePolicyConfig);
|
|
17
|
-
assert.equal(runtimeRouteRegistrationTestables.normalizeRoutePolicyConfig, normalizeCanonicalRoutePolicyConfig);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("route policy parity matrix stays aligned across http/runtime registration", () => {
|
|
21
|
-
const ownerResolver = () => 42;
|
|
22
|
-
|
|
23
|
-
const matrix = [
|
|
24
|
-
{
|
|
25
|
-
label: "auth maps to authPolicy",
|
|
26
|
-
route: { auth: "required" },
|
|
27
|
-
expectedConfig: { seed: "x", authPolicy: "required" }
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
label: "contextPolicy maps through",
|
|
31
|
-
route: { contextPolicy: "required" },
|
|
32
|
-
expectedConfig: { seed: "x", contextPolicy: "required" }
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
label: "surface maps through",
|
|
36
|
-
route: { surface: "admin" },
|
|
37
|
-
expectedConfig: { seed: "x", surface: "admin" }
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
label: "visibility is normalized",
|
|
41
|
-
route: { visibility: "USER" },
|
|
42
|
-
expectedConfig: { seed: "x", visibility: "user" }
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
label: "permission maps through",
|
|
46
|
-
route: { permission: "workspace.settings.read" },
|
|
47
|
-
expectedConfig: { seed: "x", permission: "workspace.settings.read" }
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
label: "ownerParam maps through",
|
|
51
|
-
route: { ownerParam: "userId" },
|
|
52
|
-
expectedConfig: { seed: "x", ownerParam: "userId" }
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
label: "userField maps through",
|
|
56
|
-
route: { userField: "accountId" },
|
|
57
|
-
expectedConfig: { seed: "x", userField: "accountId" }
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
label: "ownerResolver maps through",
|
|
61
|
-
route: { ownerResolver },
|
|
62
|
-
expectedConfig: { seed: "x", ownerResolver }
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
label: "csrfProtection maps through",
|
|
66
|
-
route: { csrfProtection: false },
|
|
67
|
-
expectedConfig: { seed: "x", csrfProtection: false }
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
label: "route fields override existing config values",
|
|
71
|
-
routeOptionsConfig: { authPolicy: "public", visibility: "public", seed: "x" },
|
|
72
|
-
route: { auth: "own", visibility: "WORKSPACE" },
|
|
73
|
-
expectedConfig: { seed: "x", authPolicy: "own", visibility: "workspace" }
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
label: "unmapped route fields do not alter config",
|
|
77
|
-
route: { rateLimit: { max: 1, timeWindow: "1 second" } },
|
|
78
|
-
expectedConfig: { seed: "x" }
|
|
79
|
-
}
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
for (const testCase of matrix) {
|
|
83
|
-
const routeOptions = {
|
|
84
|
-
method: "GET",
|
|
85
|
-
url: "/demo",
|
|
86
|
-
config: {
|
|
87
|
-
seed: "x",
|
|
88
|
-
...(testCase.routeOptionsConfig || {})
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
const route = {
|
|
92
|
-
method: "GET",
|
|
93
|
-
path: "/demo",
|
|
94
|
-
...(testCase.route || {})
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const canonical = applyCanonicalRoutePolicy(routeOptions, route);
|
|
98
|
-
const fromHttp = applyHttpRoutePolicy(routeOptions, route);
|
|
99
|
-
const fromRuntime = runtimeRouteRegistrationTestables.defaultApplyRoutePolicy(routeOptions, route);
|
|
100
|
-
|
|
101
|
-
assert.deepEqual(
|
|
102
|
-
canonical.config,
|
|
103
|
-
testCase.expectedConfig,
|
|
104
|
-
`canonical mapping failed for case: ${testCase.label}`
|
|
105
|
-
);
|
|
106
|
-
assert.deepEqual(fromHttp, canonical, `http mapping drifted for case: ${testCase.label}`);
|
|
107
|
-
assert.deepEqual(fromRuntime, canonical, `runtime mapping drifted for case: ${testCase.label}`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { defaultMissingHandler } from "./routeUtils.js";
|
|
2
|
-
import { defaultApplyRoutePolicy, normalizeRoutePolicyConfig } from "../support/routePolicyConfig.js";
|
|
3
|
-
import { isRecord } from "../../shared/support/normalize.js";
|
|
4
|
-
|
|
5
|
-
function buildBaseRouteOptions(route) {
|
|
6
|
-
if (!isRecord(route)) {
|
|
7
|
-
throw new TypeError("Route definition must be an object.");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const sourceRoute = route;
|
|
11
|
-
const routeOptions = {
|
|
12
|
-
method: sourceRoute.method,
|
|
13
|
-
url: sourceRoute.path,
|
|
14
|
-
config: {}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
if (sourceRoute.schema) {
|
|
18
|
-
routeOptions.schema = sourceRoute.schema;
|
|
19
|
-
}
|
|
20
|
-
if (sourceRoute.bodyLimit) {
|
|
21
|
-
routeOptions.bodyLimit = sourceRoute.bodyLimit;
|
|
22
|
-
}
|
|
23
|
-
if (sourceRoute.rateLimit) {
|
|
24
|
-
routeOptions.config.rateLimit = sourceRoute.rateLimit;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return routeOptions;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function registerApiRouteDefinitions(
|
|
31
|
-
fastify,
|
|
32
|
-
{ routes = [], applyRoutePolicy = defaultApplyRoutePolicy, resolveRequestUrl = null, missingHandler } = {}
|
|
33
|
-
) {
|
|
34
|
-
if (!fastify || typeof fastify.route !== "function") {
|
|
35
|
-
throw new TypeError("registerApiRouteDefinitions requires a Fastify instance.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const routeList = Array.isArray(routes) ? routes : [];
|
|
39
|
-
const toRequestUrl = typeof resolveRequestUrl === "function" ? resolveRequestUrl : () => null;
|
|
40
|
-
const routePolicyApplier = typeof applyRoutePolicy === "function" ? applyRoutePolicy : defaultApplyRoutePolicy;
|
|
41
|
-
const fallbackHandler = typeof missingHandler === "function" ? missingHandler : defaultMissingHandler;
|
|
42
|
-
|
|
43
|
-
for (const route of routeList) {
|
|
44
|
-
const routeOptions = routePolicyApplier(buildBaseRouteOptions(route), route);
|
|
45
|
-
let routeHandler = fallbackHandler;
|
|
46
|
-
if (isRecord(route) && typeof route.handler === "function") {
|
|
47
|
-
routeHandler = route.handler;
|
|
48
|
-
}
|
|
49
|
-
fastify.route({
|
|
50
|
-
...routeOptions,
|
|
51
|
-
handler: async (request, reply) => {
|
|
52
|
-
await routeHandler(request, reply, toRequestUrl(request));
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const __testables = {
|
|
59
|
-
buildBaseRouteOptions,
|
|
60
|
-
defaultApplyRoutePolicy,
|
|
61
|
-
normalizeRoutePolicyConfig,
|
|
62
|
-
defaultMissingHandler
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export { registerApiRouteDefinitions, __testables };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { AUTH_POLICY_PUBLIC } from "../../shared/support/policies.js";
|
|
2
|
-
import { resolveBootstrapPayload } from "../registries/bootstrapPayloadContributorRegistry.js";
|
|
3
|
-
|
|
4
|
-
function bootBootstrapRoutes(app) {
|
|
5
|
-
const router = app.make("jskit.http.router");
|
|
6
|
-
|
|
7
|
-
router.register(
|
|
8
|
-
"GET",
|
|
9
|
-
"/api/bootstrap",
|
|
10
|
-
{
|
|
11
|
-
auth: AUTH_POLICY_PUBLIC,
|
|
12
|
-
meta: {
|
|
13
|
-
tags: ["bootstrap"],
|
|
14
|
-
summary: "Resolve app bootstrap payload from registered contributors"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
async function (request, reply) {
|
|
18
|
-
const payload = await resolveBootstrapPayload(app, {
|
|
19
|
-
request,
|
|
20
|
-
reply,
|
|
21
|
-
query: request.query || {}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
reply.code(200).send(payload);
|
|
25
|
-
}
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export { bootBootstrapRoutes };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createContainer } from "../container/index.js";
|
|
4
|
-
import { registerBootstrapPayloadContributor } from "../registries/bootstrapPayloadContributorRegistry.js";
|
|
5
|
-
import { bootBootstrapRoutes } from "./bootBootstrapRoutes.js";
|
|
6
|
-
|
|
7
|
-
function createReplyDouble() {
|
|
8
|
-
return {
|
|
9
|
-
statusCode: 200,
|
|
10
|
-
payload: null,
|
|
11
|
-
code(value) {
|
|
12
|
-
this.statusCode = value;
|
|
13
|
-
return this;
|
|
14
|
-
},
|
|
15
|
-
send(value) {
|
|
16
|
-
this.payload = value;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
test("bootBootstrapRoutes registers GET /api/bootstrap and resolves contributors", async () => {
|
|
23
|
-
const app = createContainer();
|
|
24
|
-
const routes = [];
|
|
25
|
-
const router = {
|
|
26
|
-
register(method, path, route, handler) {
|
|
27
|
-
routes.push({
|
|
28
|
-
method,
|
|
29
|
-
path,
|
|
30
|
-
route,
|
|
31
|
-
handler
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
app.instance("jskit.http.router", router);
|
|
37
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.payload", () => ({
|
|
38
|
-
contributorId: "test.bootstrap.payload",
|
|
39
|
-
contribute({ query }) {
|
|
40
|
-
return {
|
|
41
|
-
source: "test",
|
|
42
|
-
workspaceSlug: query.workspaceSlug
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
bootBootstrapRoutes(app);
|
|
48
|
-
|
|
49
|
-
const bootstrapRoute = routes.find((entry) => entry.method === "GET" && entry.path === "/api/bootstrap");
|
|
50
|
-
assert.ok(bootstrapRoute);
|
|
51
|
-
assert.equal(Object.prototype.hasOwnProperty.call(bootstrapRoute.route, "query"), false);
|
|
52
|
-
assert.equal(Object.prototype.hasOwnProperty.call(bootstrapRoute.route, "responses"), false);
|
|
53
|
-
|
|
54
|
-
const reply = createReplyDouble();
|
|
55
|
-
await bootstrapRoute.handler(
|
|
56
|
-
{
|
|
57
|
-
query: {
|
|
58
|
-
workspaceSlug: "acme"
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
reply
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
assert.equal(reply.statusCode, 200);
|
|
65
|
-
assert.deepEqual(reply.payload, {
|
|
66
|
-
source: "test",
|
|
67
|
-
workspaceSlug: "acme"
|
|
68
|
-
});
|
|
69
|
-
});
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createContainer } from "../container/index.js";
|
|
4
|
-
import {
|
|
5
|
-
registerBootstrapPayloadContributor,
|
|
6
|
-
resolveBootstrapPayloadContributors,
|
|
7
|
-
resolveBootstrapPayload
|
|
8
|
-
} from "../registries/bootstrapPayloadContributorRegistry.js";
|
|
9
|
-
|
|
10
|
-
test("registerBootstrapPayloadContributor + resolveBootstrapPayloadContributors register canonical contributors", () => {
|
|
11
|
-
const app = createContainer();
|
|
12
|
-
|
|
13
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.alpha", () => ({
|
|
14
|
-
contributorId: "alpha",
|
|
15
|
-
contribute() {
|
|
16
|
-
return {
|
|
17
|
-
alpha: true
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
const contributors = resolveBootstrapPayloadContributors(app);
|
|
23
|
-
assert.equal(contributors.length, 1);
|
|
24
|
-
assert.equal(contributors[0].contributorId, "alpha");
|
|
25
|
-
assert.equal(typeof contributors[0].contribute, "function");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("resolveBootstrapPayload applies contributors in deterministic token order", async () => {
|
|
29
|
-
const app = createContainer();
|
|
30
|
-
const calls = [];
|
|
31
|
-
|
|
32
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.zeta", () => ({
|
|
33
|
-
contributorId: "zeta",
|
|
34
|
-
contribute({ payload, query }) {
|
|
35
|
-
calls.push({
|
|
36
|
-
contributorId: "zeta",
|
|
37
|
-
payload,
|
|
38
|
-
query
|
|
39
|
-
});
|
|
40
|
-
return {
|
|
41
|
-
last: true
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.alpha", () => ({
|
|
47
|
-
contributorId: "alpha",
|
|
48
|
-
contribute({ payload, query }) {
|
|
49
|
-
calls.push({
|
|
50
|
-
contributorId: "alpha",
|
|
51
|
-
payload,
|
|
52
|
-
query
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
first: true
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
const payload = await resolveBootstrapPayload(app, {
|
|
61
|
-
query: {
|
|
62
|
-
workspaceSlug: "acme"
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
assert.deepEqual(calls, [
|
|
67
|
-
{
|
|
68
|
-
contributorId: "alpha",
|
|
69
|
-
payload: {},
|
|
70
|
-
query: {
|
|
71
|
-
workspaceSlug: "acme"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
contributorId: "zeta",
|
|
76
|
-
payload: {
|
|
77
|
-
first: true
|
|
78
|
-
},
|
|
79
|
-
query: {
|
|
80
|
-
workspaceSlug: "acme"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
]);
|
|
84
|
-
assert.deepEqual(payload, {
|
|
85
|
-
first: true,
|
|
86
|
-
last: true
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("resolveBootstrapPayload honors explicit contributor order before token order", async () => {
|
|
91
|
-
const app = createContainer();
|
|
92
|
-
const calls = [];
|
|
93
|
-
|
|
94
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.alpha", () => ({
|
|
95
|
-
contributorId: "alpha",
|
|
96
|
-
order: 200,
|
|
97
|
-
contribute({ payload }) {
|
|
98
|
-
calls.push({
|
|
99
|
-
contributorId: "alpha",
|
|
100
|
-
payload
|
|
101
|
-
});
|
|
102
|
-
return {
|
|
103
|
-
alpha: true
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.zeta", () => ({
|
|
109
|
-
contributorId: "zeta",
|
|
110
|
-
order: 100,
|
|
111
|
-
contribute({ payload }) {
|
|
112
|
-
calls.push({
|
|
113
|
-
contributorId: "zeta",
|
|
114
|
-
payload
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
zeta: true
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
const payload = await resolveBootstrapPayload(app);
|
|
123
|
-
|
|
124
|
-
assert.deepEqual(calls, [
|
|
125
|
-
{
|
|
126
|
-
contributorId: "zeta",
|
|
127
|
-
payload: {}
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
contributorId: "alpha",
|
|
131
|
-
payload: {
|
|
132
|
-
zeta: true
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
]);
|
|
136
|
-
assert.deepEqual(payload, {
|
|
137
|
-
zeta: true,
|
|
138
|
-
alpha: true
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("resolveBootstrapPayload ignores non-object contributions", async () => {
|
|
143
|
-
const app = createContainer();
|
|
144
|
-
|
|
145
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.noop", () => ({
|
|
146
|
-
contributorId: "noop",
|
|
147
|
-
contribute() {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
}));
|
|
151
|
-
registerBootstrapPayloadContributor(app, "test.bootstrap.ok", () => ({
|
|
152
|
-
contributorId: "ok",
|
|
153
|
-
contribute() {
|
|
154
|
-
return {
|
|
155
|
-
ok: true
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}));
|
|
159
|
-
|
|
160
|
-
const payload = await resolveBootstrapPayload(app, {
|
|
161
|
-
ignored: true
|
|
162
|
-
});
|
|
163
|
-
assert.deepEqual(payload, {
|
|
164
|
-
ok: true
|
|
165
|
-
});
|
|
166
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
function normalizeRegistryDefinitions(definitions, { registryKind = "registry" } = {}) {
|
|
2
|
-
const source = Array.isArray(definitions) ? definitions : [];
|
|
3
|
-
const normalized = [];
|
|
4
|
-
const seenIds = new Set();
|
|
5
|
-
|
|
6
|
-
for (const entry of source) {
|
|
7
|
-
const id = String(entry?.id || "").trim();
|
|
8
|
-
const create = entry?.create;
|
|
9
|
-
|
|
10
|
-
if (!id) {
|
|
11
|
-
throw new TypeError(`${registryKind} definition id is required.`);
|
|
12
|
-
}
|
|
13
|
-
if (seenIds.has(id)) {
|
|
14
|
-
throw new TypeError(`${registryKind} definition "${id}" is duplicated.`);
|
|
15
|
-
}
|
|
16
|
-
if (typeof create !== "function") {
|
|
17
|
-
throw new TypeError(`${registryKind} definition "${id}" create must be a function.`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
seenIds.add(id);
|
|
21
|
-
normalized.push({
|
|
22
|
-
id,
|
|
23
|
-
create
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return normalized;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function createRegistryFromDefinitions(definitions, createArgsFactory, options = {}) {
|
|
31
|
-
const normalizedDefinitions = normalizeRegistryDefinitions(definitions, options);
|
|
32
|
-
const registry = {};
|
|
33
|
-
|
|
34
|
-
for (const definition of normalizedDefinitions) {
|
|
35
|
-
const createArgs =
|
|
36
|
-
typeof createArgsFactory === "function" ? createArgsFactory({ definition, registry }) : Object.freeze({});
|
|
37
|
-
registry[definition.id] = definition.create(createArgs || {});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return registry;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function createRepositoryRegistry(definitions) {
|
|
44
|
-
return createRegistryFromDefinitions(definitions, () => ({}), {
|
|
45
|
-
registryKind: "repository"
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createServiceRegistry({ definitions, dependencies = {} } = {}) {
|
|
50
|
-
return createRegistryFromDefinitions(
|
|
51
|
-
definitions,
|
|
52
|
-
({ registry }) => ({
|
|
53
|
-
...(dependencies || {}),
|
|
54
|
-
services: registry
|
|
55
|
-
}),
|
|
56
|
-
{
|
|
57
|
-
registryKind: "service"
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function createControllerRegistry({ definitions, services = {}, dependencies = {} } = {}) {
|
|
63
|
-
return createRegistryFromDefinitions(
|
|
64
|
-
definitions,
|
|
65
|
-
({ registry }) => ({
|
|
66
|
-
...(dependencies || {}),
|
|
67
|
-
services,
|
|
68
|
-
controllers: registry
|
|
69
|
-
}),
|
|
70
|
-
{
|
|
71
|
-
registryKind: "controller"
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function selectRuntimeServices(services, selectedIds = []) {
|
|
77
|
-
const source = services && typeof services === "object" ? services : {};
|
|
78
|
-
const ids = Array.isArray(selectedIds) ? selectedIds : [];
|
|
79
|
-
const runtimeServices = {};
|
|
80
|
-
|
|
81
|
-
for (const rawId of ids) {
|
|
82
|
-
const id = String(rawId || "").trim();
|
|
83
|
-
if (!id) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (!Object.hasOwn(source, id)) {
|
|
87
|
-
throw new Error(`Runtime service "${id}" is not defined.`);
|
|
88
|
-
}
|
|
89
|
-
runtimeServices[id] = source[id];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return runtimeServices;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function createRuntimeComposition({
|
|
96
|
-
repositoryDefinitions = [],
|
|
97
|
-
serviceDefinitions = [],
|
|
98
|
-
controllerDefinitions = [],
|
|
99
|
-
runtimeServiceIds = [],
|
|
100
|
-
repositoryDependencies = {},
|
|
101
|
-
serviceDependencies = {},
|
|
102
|
-
controllerDependencies = {}
|
|
103
|
-
} = {}) {
|
|
104
|
-
const repositories = createRepositoryRegistry(repositoryDefinitions);
|
|
105
|
-
const services = createServiceRegistry({
|
|
106
|
-
definitions: serviceDefinitions,
|
|
107
|
-
dependencies: {
|
|
108
|
-
...(repositoryDependencies || {}),
|
|
109
|
-
...(serviceDependencies || {}),
|
|
110
|
-
repositories
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
const controllers = createControllerRegistry({
|
|
114
|
-
definitions: controllerDefinitions,
|
|
115
|
-
services,
|
|
116
|
-
dependencies: {
|
|
117
|
-
...(controllerDependencies || {})
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
const runtimeServices = selectRuntimeServices(services, runtimeServiceIds);
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
repositories,
|
|
124
|
-
services,
|
|
125
|
-
controllers,
|
|
126
|
-
runtimeServices
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const __testables = {
|
|
131
|
-
normalizeRegistryDefinitions,
|
|
132
|
-
createRegistryFromDefinitions
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
export {
|
|
136
|
-
createRepositoryRegistry,
|
|
137
|
-
createServiceRegistry,
|
|
138
|
-
createControllerRegistry,
|
|
139
|
-
selectRuntimeServices,
|
|
140
|
-
createRuntimeComposition,
|
|
141
|
-
__testables
|
|
142
|
-
};
|