@jskit-ai/kernel 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/_testable/index.js +4 -0
- package/client/appConfig.js +33 -0
- package/client/componentInteraction.js +51 -0
- package/client/componentInteraction.test.js +111 -0
- package/client/descriptorSections.js +75 -0
- package/client/index.d.ts +70 -0
- package/client/index.js +3 -0
- package/client/logging.js +38 -0
- package/client/moduleBootstrap.js +670 -0
- package/client/moduleBootstrap.test.js +403 -0
- package/client/shellBootstrap.js +233 -0
- package/client/shellBootstrap.test.js +185 -0
- package/client/shellRouting.js +321 -0
- package/client/shellRouting.test.js +113 -0
- package/client/vite/clientBootstrapPlugin.js +259 -0
- package/client/vite/clientBootstrapPlugin.test.js +563 -0
- package/client/vite/index.js +3 -0
- package/internal/node/fileSystem.js +21 -0
- package/internal/node/installedPackageDescriptor.js +104 -0
- package/package.json +43 -0
- package/server/actions/ActionRuntimeServiceProvider.js +309 -0
- package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
- package/server/actions/index.js +8 -0
- package/server/container/ContainerCoreServiceProvider.js +27 -0
- package/server/container/index.js +10 -0
- package/server/exportPolicy.test.js +68 -0
- package/server/http/HttpFastifyServiceProvider.js +25 -0
- package/server/http/_testable/index.js +2 -0
- package/server/http/index.js +1 -0
- package/server/http/lib/controller.js +183 -0
- package/server/http/lib/controller.test.js +143 -0
- package/server/http/lib/errors.js +12 -0
- package/server/http/lib/httpRuntime.js +82 -0
- package/server/http/lib/index.js +18 -0
- package/server/http/lib/kernel.js +15 -0
- package/server/http/lib/kernel.test.js +880 -0
- package/server/http/lib/middlewareRuntime.js +149 -0
- package/server/http/lib/requestActionExecutor.js +258 -0
- package/server/http/lib/requestScope.js +59 -0
- package/server/http/lib/routeRegistration.js +165 -0
- package/server/http/lib/routeSupport.js +45 -0
- package/server/http/lib/routeValidator.js +469 -0
- package/server/http/lib/routeValidator.test.js +474 -0
- package/server/http/lib/router.js +206 -0
- package/server/kernel/KernelCoreServiceProvider.js +27 -0
- package/server/kernel/index.js +10 -0
- package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
- package/server/platform/index.js +5 -0
- package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
- package/server/platform/providerRuntime/helpers.js +45 -0
- package/server/platform/providerRuntime/lockfile.js +27 -0
- package/server/platform/providerRuntime/providerLoader.js +283 -0
- package/server/platform/providerRuntime.js +142 -0
- package/server/platform/providerRuntime.test.js +217 -0
- package/server/platform/runtime.js +40 -0
- package/server/platform/surfaceRuntime.js +150 -0
- package/server/platform/surfaceRuntime.test.js +136 -0
- package/server/registries/actionSurfaceSourceRegistry.js +150 -0
- package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
- package/server/registries/domainEventListenerRegistry.js +61 -0
- package/server/registries/index.js +36 -0
- package/server/registries/primitives.js +63 -0
- package/server/registries/routeVisibilityResolverRegistry.js +87 -0
- package/server/registries/serviceRegistrationRegistry.js +431 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
- package/server/runtime/apiRoutePolicyParity.test.js +109 -0
- package/server/runtime/apiRouteRegistration.js +65 -0
- package/server/runtime/bootBootstrapRoutes.js +46 -0
- package/server/runtime/bootBootstrapRoutes.test.js +79 -0
- package/server/runtime/bootstrapContributors.test.js +114 -0
- package/server/runtime/canonicalJson.js +74 -0
- package/server/runtime/composition.js +142 -0
- package/server/runtime/domainEvents.test.js +114 -0
- package/server/runtime/domainRules.js +50 -0
- package/server/runtime/domainRules.test.js +87 -0
- package/server/runtime/entityChangeEvents.js +182 -0
- package/server/runtime/entityChangeEvents.test.js +211 -0
- package/server/runtime/errors.js +68 -0
- package/server/runtime/errors.test.js +73 -0
- package/server/runtime/fastifyBootstrap.js +372 -0
- package/server/runtime/fastifyBootstrap.test.js +194 -0
- package/server/runtime/index.js +6 -0
- package/server/runtime/integers.js +13 -0
- package/server/runtime/moduleConfig.js +269 -0
- package/server/runtime/moduleConfig.test.js +141 -0
- package/server/runtime/pagination.js +13 -0
- package/server/runtime/realtimeNormalization.js +21 -0
- package/server/runtime/requestUrl.js +38 -0
- package/server/runtime/routeUtils.js +20 -0
- package/server/runtime/runtimeAssembly.js +113 -0
- package/server/runtime/runtimeKernel.js +55 -0
- package/server/runtime/securityAudit.js +269 -0
- package/server/runtime/securityAudit.test.js +41 -0
- package/server/runtime/serviceAuthorization.js +113 -0
- package/server/runtime/serviceAuthorization.test.js +100 -0
- package/server/runtime/serviceRegistration.test.js +197 -0
- package/server/support/SupportCoreServiceProvider.js +25 -0
- package/server/support/appConfig.js +37 -0
- package/server/support/appConfig.test.js +94 -0
- package/server/support/defaultMissingHandler.js +7 -0
- package/server/support/index.js +2 -0
- package/server/support/routePolicyConfig.js +51 -0
- package/server/support/symlinkSafeRequire.js +78 -0
- package/server/support/symlinkSafeRequire.test.js +27 -0
- package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
- package/server/surface/index.js +19 -0
- package/shared/actions/actionContributorHelpers.js +34 -0
- package/shared/actions/actionContributorHelpers.test.js +16 -0
- package/shared/actions/actionDefinitions.js +488 -0
- package/shared/actions/actionDefinitions.test.js +212 -0
- package/shared/actions/audit.js +7 -0
- package/shared/actions/executionContext.js +97 -0
- package/shared/actions/executionContext.test.js +66 -0
- package/shared/actions/idempotency.js +62 -0
- package/shared/actions/index.js +2 -0
- package/shared/actions/observability.js +10 -0
- package/shared/actions/pipeline.js +287 -0
- package/shared/actions/policies.js +342 -0
- package/shared/actions/policies.test.js +233 -0
- package/shared/actions/registry.js +187 -0
- package/shared/actions/registry.test.js +381 -0
- package/shared/actions/requestMeta.js +36 -0
- package/shared/actions/textNormalization.js +3 -0
- package/shared/actions/withActionDefaults.js +34 -0
- package/shared/index.js +2 -0
- package/shared/runtime/application.js +323 -0
- package/shared/runtime/container.js +261 -0
- package/shared/runtime/containerErrors.js +22 -0
- package/shared/runtime/index.js +18 -0
- package/shared/runtime/kernelErrors.js +20 -0
- package/shared/runtime/serviceProvider.js +13 -0
- package/shared/support/formatDateTime.js +10 -0
- package/shared/support/formatDateTime.test.js +15 -0
- package/shared/support/index.js +14 -0
- package/shared/support/linkPath.js +67 -0
- package/shared/support/linkPath.test.js +35 -0
- package/shared/support/normalize.js +116 -0
- package/shared/support/normalize.test.js +48 -0
- package/shared/support/packageDescriptor.test.js +121 -0
- package/shared/support/permissions.js +50 -0
- package/shared/support/pickOwnProperties.js +17 -0
- package/shared/support/pickOwnProperties.test.js +25 -0
- package/shared/support/policies.js +11 -0
- package/shared/support/queryPath.js +33 -0
- package/shared/support/queryPath.test.js +19 -0
- package/shared/support/queryResilience.js +34 -0
- package/shared/support/queryResilience.test.js +33 -0
- package/shared/support/returnToPath.js +153 -0
- package/shared/support/returnToPath.test.js +123 -0
- package/shared/support/sorting.js +15 -0
- package/shared/support/tokens.js +23 -0
- package/shared/support/tokens.test.js +17 -0
- package/shared/support/visibility.js +56 -0
- package/shared/support/visibility.test.js +45 -0
- package/shared/surface/apiPaths.js +84 -0
- package/shared/surface/escapeRegExp.js +5 -0
- package/shared/surface/index.js +6 -0
- package/shared/surface/paths.js +273 -0
- package/shared/surface/registry.js +135 -0
- package/shared/surface/registry.test.js +44 -0
- package/shared/surface/runtime.js +357 -0
- package/shared/surface/runtime.test.js +319 -0
- package/shared/validators/createCursorListValidator.js +42 -0
- package/shared/validators/createCursorListValidator.test.js +34 -0
- package/shared/validators/cursorPaginationQueryValidator.js +31 -0
- package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
- package/shared/validators/index.js +12 -0
- package/shared/validators/inputNormalization.js +13 -0
- package/shared/validators/mergeObjectSchemas.js +31 -0
- package/shared/validators/mergeObjectSchemas.test.js +67 -0
- package/shared/validators/mergeValidators.js +89 -0
- package/shared/validators/mergeValidators.test.js +116 -0
- package/shared/validators/nestValidator.js +53 -0
- package/shared/validators/nestValidator.test.js +60 -0
- package/shared/validators/recordIdParamsValidator.js +36 -0
- package/shared/validators/recordIdParamsValidator.test.js +20 -0
- package/shared/validators/resourceRequiredMetadata.js +41 -0
- package/shared/validators/resourceRequiredMetadata.test.js +49 -0
- package/test/barrelExposure.test.js +106 -0
- package/test/dynamicImportPolicy.test.js +89 -0
- package/test/exportsContract.test.js +168 -0
- package/test/routeInputContractGuard.test.js +78 -0
- package/test/surfaceIndependence.test.js +109 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
|
|
5
|
+
import {
|
|
6
|
+
EMPTY_INPUT_VALIDATOR,
|
|
7
|
+
OBJECT_INPUT_VALIDATOR
|
|
8
|
+
} from "../../shared/actions/actionContributorHelpers.js";
|
|
9
|
+
import {
|
|
10
|
+
ActionRuntimeServiceProvider,
|
|
11
|
+
registerActionContextContributor,
|
|
12
|
+
resolveActionContributors,
|
|
13
|
+
resolveActionContextContributors
|
|
14
|
+
} from "./ActionRuntimeServiceProvider.js";
|
|
15
|
+
|
|
16
|
+
function createSingletonApp() {
|
|
17
|
+
const singletons = new Map();
|
|
18
|
+
const instances = new Map();
|
|
19
|
+
const tags = new Map();
|
|
20
|
+
return {
|
|
21
|
+
singletons,
|
|
22
|
+
has(token) {
|
|
23
|
+
return singletons.has(token) || instances.has(token);
|
|
24
|
+
},
|
|
25
|
+
singleton(token, factory) {
|
|
26
|
+
singletons.set(token, {
|
|
27
|
+
factory,
|
|
28
|
+
resolved: false,
|
|
29
|
+
value: undefined
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
tag(token, tagName) {
|
|
33
|
+
if (!this.has(token)) {
|
|
34
|
+
throw new Error(`Cannot tag unresolved token "${String(token)}".`);
|
|
35
|
+
}
|
|
36
|
+
if (!tags.has(tagName)) {
|
|
37
|
+
tags.set(tagName, new Set());
|
|
38
|
+
}
|
|
39
|
+
tags.get(tagName).add(token);
|
|
40
|
+
},
|
|
41
|
+
resolveTag(tagName) {
|
|
42
|
+
const tagged = tags.get(tagName);
|
|
43
|
+
if (!tagged) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return [...tagged].map((token) => this.make(token));
|
|
47
|
+
},
|
|
48
|
+
make(token) {
|
|
49
|
+
if (instances.has(token)) {
|
|
50
|
+
return instances.get(token);
|
|
51
|
+
}
|
|
52
|
+
if (!singletons.has(token)) {
|
|
53
|
+
throw new Error(`Token "${String(token)}" is not registered.`);
|
|
54
|
+
}
|
|
55
|
+
const entry = singletons.get(token);
|
|
56
|
+
if (!entry.resolved) {
|
|
57
|
+
entry.value = entry.factory(this);
|
|
58
|
+
entry.resolved = true;
|
|
59
|
+
instances.set(token, entry.value);
|
|
60
|
+
}
|
|
61
|
+
return entry.value;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test("ActionRuntimeServiceProvider registers runtime actions api and action executor", () => {
|
|
67
|
+
const app = createSingletonApp();
|
|
68
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
69
|
+
provider.register(app);
|
|
70
|
+
|
|
71
|
+
assert.equal(app.singletons.has("runtime.actions"), true);
|
|
72
|
+
assert.equal(app.singletons.has("actionRegistry"), true);
|
|
73
|
+
assert.equal(app.singletons.has("actionExecutor"), true);
|
|
74
|
+
assert.equal(app.singletons.has(KERNEL_TOKENS.SurfaceRuntime), true);
|
|
75
|
+
assert.equal(typeof app.action, "function");
|
|
76
|
+
assert.equal(typeof app.actions, "function");
|
|
77
|
+
assert.equal(typeof app.actionSurfaceSource, "function");
|
|
78
|
+
assert.equal(typeof app.service, "function");
|
|
79
|
+
|
|
80
|
+
const api = app.make("runtime.actions");
|
|
81
|
+
assert.equal(typeof api.createActionRegistry, "function");
|
|
82
|
+
assert.equal(typeof app.make("actionExecutor")?.execute, "function");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("ActionRuntimeServiceProvider materializes dependencies and surfaces for app.actions arrays", async () => {
|
|
86
|
+
const app = createSingletonApp();
|
|
87
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
88
|
+
provider.register(app);
|
|
89
|
+
|
|
90
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
91
|
+
listEnabledSurfaceIds() {
|
|
92
|
+
return ["app", "admin", "console"];
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
app.singleton("test.echo.service", () => ({
|
|
97
|
+
echo(input) {
|
|
98
|
+
return { echoed: input, ok: true };
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
app.actions([
|
|
103
|
+
{
|
|
104
|
+
id: "test.echo",
|
|
105
|
+
domain: "workspace",
|
|
106
|
+
version: 1,
|
|
107
|
+
kind: "query",
|
|
108
|
+
channels: ["internal"],
|
|
109
|
+
surfacesFrom: "enabled",
|
|
110
|
+
dependencies: {
|
|
111
|
+
echoService: "test.echo.service"
|
|
112
|
+
},
|
|
113
|
+
inputValidator: { schema: OBJECT_INPUT_VALIDATOR },
|
|
114
|
+
idempotency: "none",
|
|
115
|
+
audit: { actionName: "test.echo" },
|
|
116
|
+
observability: {},
|
|
117
|
+
async execute(input, _context, deps) {
|
|
118
|
+
return deps.echoService.echo(input);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
const actionExecutor = app.make("actionExecutor");
|
|
124
|
+
const definitions = actionExecutor.listDefinitions();
|
|
125
|
+
assert.equal(definitions.some((definition) => definition.id === "test.echo"), true);
|
|
126
|
+
assert.deepEqual(definitions.find((definition) => definition.id === "test.echo")?.surfaces, ["app", "admin", "console"]);
|
|
127
|
+
|
|
128
|
+
const result = await actionExecutor.execute({
|
|
129
|
+
actionId: "test.echo",
|
|
130
|
+
input: { value: "ok" },
|
|
131
|
+
context: { channel: "internal", surface: "app" }
|
|
132
|
+
});
|
|
133
|
+
assert.deepEqual(result, { echoed: { value: "ok" }, ok: true });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("ActionRuntimeServiceProvider registers SurfaceRuntime from appConfig when token is absent", async () => {
|
|
137
|
+
const app = createSingletonApp();
|
|
138
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
139
|
+
provider.register(app);
|
|
140
|
+
|
|
141
|
+
app.singleton("appConfig", () => ({
|
|
142
|
+
surfaceModeAll: "all",
|
|
143
|
+
surfaceDefaultId: "home",
|
|
144
|
+
surfaceDefinitions: {
|
|
145
|
+
home: { id: "home", pagesRoot: "", enabled: true, requiresAuth: false, requiresWorkspace: false },
|
|
146
|
+
console: { id: "console", pagesRoot: "console", enabled: true, requiresAuth: true, requiresWorkspace: false }
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
app.actions([
|
|
151
|
+
{
|
|
152
|
+
id: "test.surfaces.from.appconfig",
|
|
153
|
+
domain: "workspace",
|
|
154
|
+
version: 1,
|
|
155
|
+
kind: "query",
|
|
156
|
+
channels: ["internal"],
|
|
157
|
+
surfacesFrom: "enabled",
|
|
158
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
159
|
+
idempotency: "none",
|
|
160
|
+
audit: { actionName: "test.surfaces.from.appconfig" },
|
|
161
|
+
observability: {},
|
|
162
|
+
async execute() {
|
|
163
|
+
return { ok: true };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
const actionExecutor = app.make("actionExecutor");
|
|
169
|
+
const definition = actionExecutor.getDefinition("test.surfaces.from.appconfig");
|
|
170
|
+
assert.deepEqual(definition.surfaces, ["home", "console"]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("ActionRuntimeServiceProvider materializes custom surfacesFrom aliases registered via app.actionSurfaceSource", async () => {
|
|
174
|
+
const app = createSingletonApp();
|
|
175
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
176
|
+
provider.register(app);
|
|
177
|
+
|
|
178
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
179
|
+
listEnabledSurfaceIds() {
|
|
180
|
+
return ["home", "app", "admin", "console"];
|
|
181
|
+
}
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
app.actionSurfaceSource("workspace", () => ["app", "admin"]);
|
|
185
|
+
|
|
186
|
+
app.actions([
|
|
187
|
+
{
|
|
188
|
+
id: "test.workspace.alias",
|
|
189
|
+
domain: "workspace",
|
|
190
|
+
version: 1,
|
|
191
|
+
kind: "query",
|
|
192
|
+
channels: ["internal"],
|
|
193
|
+
surfacesFrom: "workspace",
|
|
194
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
195
|
+
idempotency: "none",
|
|
196
|
+
audit: { actionName: "test.workspace.alias" },
|
|
197
|
+
observability: {},
|
|
198
|
+
async execute() {
|
|
199
|
+
return { ok: true };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const actionExecutor = app.make("actionExecutor");
|
|
205
|
+
const definition = actionExecutor.getDefinition("test.workspace.alias");
|
|
206
|
+
assert.deepEqual(definition?.surfaces, ["app", "admin"]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("ActionRuntimeServiceProvider does not infer service method bindings from action source", () => {
|
|
210
|
+
const app = createSingletonApp();
|
|
211
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
212
|
+
provider.register(app);
|
|
213
|
+
|
|
214
|
+
app.singleton("test.customer.service", () => ({
|
|
215
|
+
createRecord(input) {
|
|
216
|
+
return input;
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
app.actions([
|
|
221
|
+
{
|
|
222
|
+
id: "test.customer.create",
|
|
223
|
+
domain: "workspace",
|
|
224
|
+
version: 1,
|
|
225
|
+
kind: "command",
|
|
226
|
+
channels: ["internal"],
|
|
227
|
+
surfaces: ["admin"],
|
|
228
|
+
dependencies: {
|
|
229
|
+
customerService: "test.customer.service"
|
|
230
|
+
},
|
|
231
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
232
|
+
idempotency: "optional",
|
|
233
|
+
audit: { actionName: "test.customer.create" },
|
|
234
|
+
observability: {},
|
|
235
|
+
async execute(input, _context, deps) {
|
|
236
|
+
return deps.customerService.createRecord(input);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
const contributors = resolveActionContributors(app);
|
|
242
|
+
const action = contributors[0]?.actions?.[0];
|
|
243
|
+
assert.equal(Object.hasOwn(action || {}, "serviceMethodBindings"), false);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("app.actions + resolveActionContributors provide canonical contributor wiring", () => {
|
|
247
|
+
const app = createSingletonApp();
|
|
248
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
249
|
+
provider.register(app);
|
|
250
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
251
|
+
listEnabledSurfaceIds() {
|
|
252
|
+
return ["app", "admin", "console"];
|
|
253
|
+
}
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
app.actions([
|
|
257
|
+
{
|
|
258
|
+
id: "alpha.one",
|
|
259
|
+
domain: "settings",
|
|
260
|
+
version: 1,
|
|
261
|
+
kind: "query",
|
|
262
|
+
channels: ["internal"],
|
|
263
|
+
surfaces: ["app"],
|
|
264
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
265
|
+
idempotency: "none",
|
|
266
|
+
audit: { actionName: "alpha.one" },
|
|
267
|
+
observability: {},
|
|
268
|
+
async execute() {
|
|
269
|
+
return { ok: true };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
]);
|
|
273
|
+
app.actions([
|
|
274
|
+
{
|
|
275
|
+
id: "beta.one",
|
|
276
|
+
domain: "auth",
|
|
277
|
+
version: 1,
|
|
278
|
+
kind: "query",
|
|
279
|
+
channels: ["internal"],
|
|
280
|
+
surfaces: ["app"],
|
|
281
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
282
|
+
idempotency: "none",
|
|
283
|
+
audit: { actionName: "beta.one" },
|
|
284
|
+
observability: {},
|
|
285
|
+
async execute() {
|
|
286
|
+
return { ok: true };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
const contributors = resolveActionContributors(app);
|
|
292
|
+
assert.deepEqual(
|
|
293
|
+
contributors.map((entry) => entry.contributorId).sort(),
|
|
294
|
+
["action.alpha.one", "action.beta.one"]
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("action runtime execute merges static and per-execution dependencies", async () => {
|
|
299
|
+
const app = createSingletonApp();
|
|
300
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
301
|
+
provider.register(app);
|
|
302
|
+
|
|
303
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
304
|
+
listEnabledSurfaceIds() {
|
|
305
|
+
return ["app"];
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
|
|
309
|
+
app.singleton("test.static.service", () => ({
|
|
310
|
+
label: "static"
|
|
311
|
+
}));
|
|
312
|
+
|
|
313
|
+
app.actions([
|
|
314
|
+
{
|
|
315
|
+
id: "test.deps.merge",
|
|
316
|
+
domain: "workspace",
|
|
317
|
+
version: 1,
|
|
318
|
+
kind: "query",
|
|
319
|
+
channels: ["internal"],
|
|
320
|
+
surfaces: ["app"],
|
|
321
|
+
dependencies: {
|
|
322
|
+
staticService: "test.static.service"
|
|
323
|
+
},
|
|
324
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
325
|
+
idempotency: "none",
|
|
326
|
+
audit: { actionName: "test.deps.merge" },
|
|
327
|
+
observability: {},
|
|
328
|
+
async execute(_input, _context, deps) {
|
|
329
|
+
return {
|
|
330
|
+
staticLabel: deps.staticService?.label || "",
|
|
331
|
+
dynamicValue: deps.dynamicValue || ""
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
const actionExecutor = app.make("actionExecutor");
|
|
338
|
+
const result = await actionExecutor.execute({
|
|
339
|
+
actionId: "test.deps.merge",
|
|
340
|
+
context: {
|
|
341
|
+
channel: "internal",
|
|
342
|
+
surface: "app"
|
|
343
|
+
},
|
|
344
|
+
deps: {
|
|
345
|
+
dynamicValue: "runtime"
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
assert.deepEqual(result, {
|
|
350
|
+
staticLabel: "static",
|
|
351
|
+
dynamicValue: "runtime"
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("app.actions accepts custom action domains", () => {
|
|
356
|
+
const app = createSingletonApp();
|
|
357
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
358
|
+
provider.register(app);
|
|
359
|
+
|
|
360
|
+
app.actions([
|
|
361
|
+
{
|
|
362
|
+
id: "custom.domain.check",
|
|
363
|
+
domain: "completeCalendar",
|
|
364
|
+
version: 1,
|
|
365
|
+
kind: "query",
|
|
366
|
+
channels: ["internal"],
|
|
367
|
+
surfaces: ["app"],
|
|
368
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
369
|
+
idempotency: "none",
|
|
370
|
+
audit: { actionName: "custom.domain.check" },
|
|
371
|
+
observability: {},
|
|
372
|
+
async execute() {
|
|
373
|
+
return { ok: true };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
const contributors = resolveActionContributors(app);
|
|
379
|
+
assert.equal(contributors.length, 1);
|
|
380
|
+
assert.equal(contributors[0].contributorId, "action.custom.domain.check");
|
|
381
|
+
assert.equal(contributors[0].domain, "completecalendar");
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("app.action registers a single action with default contributor id", () => {
|
|
385
|
+
const app = createSingletonApp();
|
|
386
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
387
|
+
provider.register(app);
|
|
388
|
+
|
|
389
|
+
app.action({
|
|
390
|
+
id: "test.single",
|
|
391
|
+
domain: "workspace",
|
|
392
|
+
kind: "query",
|
|
393
|
+
channels: ["internal"],
|
|
394
|
+
surfaces: ["app"],
|
|
395
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
396
|
+
idempotency: "none",
|
|
397
|
+
audit: { actionName: "test.single" },
|
|
398
|
+
observability: {},
|
|
399
|
+
async execute() {
|
|
400
|
+
return { ok: true };
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const contributors = resolveActionContributors(app);
|
|
405
|
+
assert.equal(contributors.length, 1);
|
|
406
|
+
assert.equal(contributors[0].contributorId, "action.test.single");
|
|
407
|
+
assert.equal(contributors[0].actions.length, 1);
|
|
408
|
+
assert.equal(contributors[0].actions[0].id, "test.single");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("app.actions requires an array", () => {
|
|
412
|
+
const app = createSingletonApp();
|
|
413
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
414
|
+
provider.register(app);
|
|
415
|
+
|
|
416
|
+
assert.throws(() => app.actions({}), /requires an array/);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("EMPTY_INPUT_VALIDATOR allows empty input and rejects unexpected fields", async () => {
|
|
420
|
+
const app = createSingletonApp();
|
|
421
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
422
|
+
provider.register(app);
|
|
423
|
+
|
|
424
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
425
|
+
listEnabledSurfaceIds() {
|
|
426
|
+
return ["app"];
|
|
427
|
+
}
|
|
428
|
+
}));
|
|
429
|
+
|
|
430
|
+
app.actions([
|
|
431
|
+
{
|
|
432
|
+
id: "test.empty-input",
|
|
433
|
+
domain: "settings",
|
|
434
|
+
version: 1,
|
|
435
|
+
kind: "query",
|
|
436
|
+
channels: ["internal"],
|
|
437
|
+
surfaces: ["app"],
|
|
438
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
439
|
+
idempotency: "none",
|
|
440
|
+
audit: { actionName: "test.empty-input" },
|
|
441
|
+
observability: {},
|
|
442
|
+
async execute() {
|
|
443
|
+
return { ok: true };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
const actionExecutor = app.make("actionExecutor");
|
|
449
|
+
|
|
450
|
+
const okResult = await actionExecutor.execute({
|
|
451
|
+
actionId: "test.empty-input",
|
|
452
|
+
context: { channel: "internal", surface: "app" }
|
|
453
|
+
});
|
|
454
|
+
assert.deepEqual(okResult, { ok: true });
|
|
455
|
+
|
|
456
|
+
await assert.rejects(
|
|
457
|
+
() =>
|
|
458
|
+
actionExecutor.execute({
|
|
459
|
+
actionId: "test.empty-input",
|
|
460
|
+
input: { unexpected: true },
|
|
461
|
+
context: { channel: "internal", surface: "app" }
|
|
462
|
+
}),
|
|
463
|
+
(error) => error?.code === "ACTION_VALIDATION_FAILED"
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("registerActionContextContributor + resolveActionContextContributors provide context contributor wiring", () => {
|
|
468
|
+
const app = createSingletonApp();
|
|
469
|
+
|
|
470
|
+
registerActionContextContributor(app, "test.actionContextContributor.alpha", () => ({
|
|
471
|
+
contributorId: "alpha",
|
|
472
|
+
contribute() {
|
|
473
|
+
return { actor: null };
|
|
474
|
+
}
|
|
475
|
+
}));
|
|
476
|
+
registerActionContextContributor(app, "test.actionContextContributor.beta", () => ({
|
|
477
|
+
contributorId: "beta",
|
|
478
|
+
contribute() {
|
|
479
|
+
return { permissions: [] };
|
|
480
|
+
}
|
|
481
|
+
}));
|
|
482
|
+
|
|
483
|
+
const contributors = resolveActionContextContributors(app);
|
|
484
|
+
assert.deepEqual(
|
|
485
|
+
contributors.map((entry) => entry.contributorId).sort(),
|
|
486
|
+
["alpha", "beta"]
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("app.actions rejects invalid domain identifiers", () => {
|
|
491
|
+
const app = createSingletonApp();
|
|
492
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
493
|
+
provider.register(app);
|
|
494
|
+
|
|
495
|
+
assert.throws(
|
|
496
|
+
() =>
|
|
497
|
+
app.actions([
|
|
498
|
+
{
|
|
499
|
+
id: "invalid.domain",
|
|
500
|
+
domain: "invalid domain",
|
|
501
|
+
version: 1,
|
|
502
|
+
kind: "query",
|
|
503
|
+
channels: ["internal"],
|
|
504
|
+
surfaces: ["app"],
|
|
505
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
506
|
+
idempotency: "none",
|
|
507
|
+
audit: { actionName: "invalid.domain" },
|
|
508
|
+
observability: {},
|
|
509
|
+
async execute() {
|
|
510
|
+
return {};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
]),
|
|
514
|
+
/must match/
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("app.actions rejects unsupported surfacesFrom aliases", () => {
|
|
519
|
+
const app = createSingletonApp();
|
|
520
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
521
|
+
provider.register(app);
|
|
522
|
+
|
|
523
|
+
app.singleton(KERNEL_TOKENS.SurfaceRuntime, () => ({
|
|
524
|
+
listEnabledSurfaceIds() {
|
|
525
|
+
return ["app", "admin"];
|
|
526
|
+
}
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
app.actions([
|
|
530
|
+
{
|
|
531
|
+
id: "workspace.alias.invalid",
|
|
532
|
+
domain: "workspace",
|
|
533
|
+
version: 1,
|
|
534
|
+
kind: "query",
|
|
535
|
+
channels: ["internal"],
|
|
536
|
+
surfacesFrom: "workspace",
|
|
537
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
538
|
+
idempotency: "none",
|
|
539
|
+
audit: { actionName: "workspace.alias.invalid" },
|
|
540
|
+
observability: {},
|
|
541
|
+
async execute() {
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
]);
|
|
546
|
+
|
|
547
|
+
assert.throws(
|
|
548
|
+
() => app.make("actionExecutor"),
|
|
549
|
+
/references unknown surface source "workspace". Register it via app.actionSurfaceSource\(\)\./
|
|
550
|
+
);
|
|
551
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Container, createContainer, tokenLabel } from "../../shared/runtime/container.js";
|
|
2
|
+
import * as containerErrors from "../../shared/runtime/containerErrors.js";
|
|
3
|
+
|
|
4
|
+
const CONTAINER_CORE_API = Object.freeze({
|
|
5
|
+
Container,
|
|
6
|
+
createContainer,
|
|
7
|
+
tokenLabel,
|
|
8
|
+
errors: Object.freeze({
|
|
9
|
+
...containerErrors
|
|
10
|
+
})
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
class ContainerCoreServiceProvider {
|
|
14
|
+
static id = "runtime.container";
|
|
15
|
+
|
|
16
|
+
register(app) {
|
|
17
|
+
if (!app || typeof app.singleton !== "function") {
|
|
18
|
+
throw new Error("ContainerCoreServiceProvider requires application singleton().");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
app.singleton("runtime.container", () => CONTAINER_CORE_API);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
boot() {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { ContainerCoreServiceProvider };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { Container, createContainer, tokenLabel } from "../../shared/runtime/container.js";
|
|
2
|
+
export {
|
|
3
|
+
ContainerError,
|
|
4
|
+
InvalidTokenError,
|
|
5
|
+
InvalidFactoryError,
|
|
6
|
+
DuplicateBindingError,
|
|
7
|
+
UnresolvedTokenError,
|
|
8
|
+
CircularDependencyError
|
|
9
|
+
} from "../../shared/runtime/containerErrors.js";
|
|
10
|
+
export { ContainerCoreServiceProvider } from "./ContainerCoreServiceProvider.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
|
|
9
|
+
function collectExportTargets(exportsField) {
|
|
10
|
+
const targets = [];
|
|
11
|
+
|
|
12
|
+
const visit = (value) => {
|
|
13
|
+
if (typeof value === "string") {
|
|
14
|
+
targets.push(value);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
for (const item of value) {
|
|
19
|
+
visit(item);
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!value || typeof value !== "object") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const nested of Object.values(value)) {
|
|
27
|
+
visit(nested);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
visit(exportsField);
|
|
32
|
+
return [...new Set(targets)];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("kernel exported JS targets do not use star re-exports", async () => {
|
|
36
|
+
const packageJsonPath = path.join(PACKAGE_ROOT, "package.json");
|
|
37
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
38
|
+
const exportTargets = collectExportTargets(packageJson.exports);
|
|
39
|
+
const exportedJsTargets = exportTargets
|
|
40
|
+
.filter((target) => typeof target === "string")
|
|
41
|
+
.filter((target) => target.startsWith("./"))
|
|
42
|
+
.filter((target) => target.endsWith(".js"))
|
|
43
|
+
.filter((target) => !target.includes("*"));
|
|
44
|
+
|
|
45
|
+
const violations = [];
|
|
46
|
+
for (const target of exportedJsTargets) {
|
|
47
|
+
const absolutePath = path.join(PACKAGE_ROOT, target.slice(2));
|
|
48
|
+
const source = await readFile(absolutePath, "utf8");
|
|
49
|
+
const lines = source.split(/\r?\n/);
|
|
50
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
51
|
+
const line = String(lines[index] || "");
|
|
52
|
+
if (/^\s*export\s+\*\s+from\s+["'][^"']+["']\s*;?\s*$/.test(line)) {
|
|
53
|
+
violations.push(`${target}:${index + 1}`);
|
|
54
|
+
}
|
|
55
|
+
if (/^\s*export\s+\*\s+as\s+[A-Za-z_$][A-Za-z0-9_$]*\s+from\s+["'][^"']+["']\s*;?\s*$/.test(line)) {
|
|
56
|
+
violations.push(`${target}:${index + 1}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
assert.deepEqual(violations, []);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("kernel package does not expose deprecated ./server aggregate entrypoint", async () => {
|
|
65
|
+
const packageJsonPath = path.join(PACKAGE_ROOT, "package.json");
|
|
66
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
67
|
+
assert.equal(Object.hasOwn(packageJson.exports || {}, "./server"), false);
|
|
68
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as errors from "./lib/errors.js";
|
|
2
|
+
import * as router from "./lib/router.js";
|
|
3
|
+
import * as kernel from "./lib/kernel.js";
|
|
4
|
+
|
|
5
|
+
const HTTP_FASTIFY_SERVER_API = Object.freeze({
|
|
6
|
+
errors: Object.freeze({ ...errors }),
|
|
7
|
+
router: Object.freeze({ ...router }),
|
|
8
|
+
kernel: Object.freeze({ ...kernel })
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
class HttpFastifyServiceProvider {
|
|
12
|
+
static id = "runtime.http-fastify";
|
|
13
|
+
|
|
14
|
+
register(app) {
|
|
15
|
+
if (!app || typeof app.singleton !== "function") {
|
|
16
|
+
throw new Error("HttpFastifyServiceProvider requires application singleton().");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
app.singleton("runtime.http-fastify", () => HTTP_FASTIFY_SERVER_API);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
boot() {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { HttpFastifyServiceProvider };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerRouteVisibilityResolver } from "../registries/routeVisibilityResolverRegistry.js";
|