@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,142 @@
|
|
|
1
|
+
import { ActionRuntimeServiceProvider } from "../actions/ActionRuntimeServiceProvider.js";
|
|
2
|
+
import { ServerRuntimeCoreServiceProvider } from "../runtime/ServerRuntimeCoreServiceProvider.js";
|
|
3
|
+
import { createApplication } from "../kernel/index.js";
|
|
4
|
+
import { createHttpRuntime } from "../http/lib/kernel.js";
|
|
5
|
+
import { KERNEL_TOKENS } from "../../shared/support/tokens.js";
|
|
6
|
+
import { readLockFromApp } from "./providerRuntime/lockfile.js";
|
|
7
|
+
import {
|
|
8
|
+
collectGlobalUiPaths,
|
|
9
|
+
resolveInstalledPackageDescriptors,
|
|
10
|
+
resolveDescriptorLoadOrder,
|
|
11
|
+
validateDescriptorCapabilities
|
|
12
|
+
} from "./providerRuntime/descriptorCatalog.js";
|
|
13
|
+
import { loadPackageProviders, registerProviderClass } from "./providerRuntime/providerLoader.js";
|
|
14
|
+
|
|
15
|
+
const KERNEL_BUILTIN_CAPABILITY_PROVIDERS = Object.freeze({
|
|
16
|
+
"runtime.actions": Object.freeze(["@jskit-ai/kernel"])
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
async function createProviderRuntimeApp({
|
|
20
|
+
profile = "",
|
|
21
|
+
providers = [],
|
|
22
|
+
env = {},
|
|
23
|
+
logger = console,
|
|
24
|
+
fastify = null
|
|
25
|
+
} = {}) {
|
|
26
|
+
const app = createApplication({
|
|
27
|
+
profile
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
app.instance(KERNEL_TOKENS.Env, env && typeof env === "object" ? { ...env } : {});
|
|
31
|
+
app.instance(KERNEL_TOKENS.Logger, logger || console);
|
|
32
|
+
|
|
33
|
+
let httpRuntime = null;
|
|
34
|
+
if (fastify && typeof fastify.route === "function") {
|
|
35
|
+
httpRuntime = createHttpRuntime({
|
|
36
|
+
app,
|
|
37
|
+
fastify
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await app.start({ providers });
|
|
42
|
+
|
|
43
|
+
const routeRegistration = httpRuntime ? httpRuntime.registerRoutes() : { routeCount: 0 };
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
app,
|
|
46
|
+
routeCount: routeRegistration.routeCount,
|
|
47
|
+
routeRegistration,
|
|
48
|
+
diagnostics: app.getDiagnostics()
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function createProviderRuntimeFromApp({
|
|
53
|
+
appRoot,
|
|
54
|
+
lockPath = ".jskit/lock.json",
|
|
55
|
+
profile = "",
|
|
56
|
+
env = {},
|
|
57
|
+
logger = console,
|
|
58
|
+
fastify = null
|
|
59
|
+
} = {}) {
|
|
60
|
+
if (!appRoot || typeof appRoot !== "string") {
|
|
61
|
+
throw new TypeError("createProviderRuntimeFromApp requires appRoot.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { lock } = await readLockFromApp({
|
|
65
|
+
appRoot,
|
|
66
|
+
lockPath
|
|
67
|
+
});
|
|
68
|
+
const descriptors = await resolveInstalledPackageDescriptors({
|
|
69
|
+
appRoot,
|
|
70
|
+
lock
|
|
71
|
+
});
|
|
72
|
+
validateDescriptorCapabilities(descriptors, {
|
|
73
|
+
builtinProvidersByCapability: KERNEL_BUILTIN_CAPABILITY_PROVIDERS
|
|
74
|
+
});
|
|
75
|
+
const orderedDescriptors = resolveDescriptorLoadOrder(descriptors);
|
|
76
|
+
const catalog = Object.freeze({
|
|
77
|
+
packageOrder: Object.freeze(orderedDescriptors.map((entry) => entry.packageId)),
|
|
78
|
+
globalUiPaths: collectGlobalUiPaths(orderedDescriptors),
|
|
79
|
+
descriptors: Object.freeze(orderedDescriptors)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const orderedProviderClasses = [];
|
|
83
|
+
const providerPackageIds = [];
|
|
84
|
+
const seenProviderIds = new Map();
|
|
85
|
+
|
|
86
|
+
for (const descriptorEntry of catalog.descriptors) {
|
|
87
|
+
const packageProviders = await loadPackageProviders({ descriptorEntry });
|
|
88
|
+
if (packageProviders.length < 1) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
providerPackageIds.push(descriptorEntry.packageId);
|
|
92
|
+
|
|
93
|
+
for (const providerClass of packageProviders) {
|
|
94
|
+
registerProviderClass({
|
|
95
|
+
providerClass,
|
|
96
|
+
sourceId: descriptorEntry.packageId,
|
|
97
|
+
seenProviderIds,
|
|
98
|
+
orderedProviderClasses
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!seenProviderIds.has(ActionRuntimeServiceProvider.id)) {
|
|
104
|
+
registerProviderClass({
|
|
105
|
+
providerClass: ActionRuntimeServiceProvider,
|
|
106
|
+
sourceId: "@jskit-ai/kernel",
|
|
107
|
+
seenProviderIds,
|
|
108
|
+
orderedProviderClasses
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!seenProviderIds.has(ServerRuntimeCoreServiceProvider.id)) {
|
|
113
|
+
registerProviderClass({
|
|
114
|
+
providerClass: ServerRuntimeCoreServiceProvider,
|
|
115
|
+
sourceId: "@jskit-ai/kernel",
|
|
116
|
+
seenProviderIds,
|
|
117
|
+
orderedProviderClasses
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const envRecord = env && typeof env === "object" ? { ...env } : {};
|
|
122
|
+
const loggerInstance = logger || console;
|
|
123
|
+
|
|
124
|
+
const providerRuntime = await createProviderRuntimeApp({
|
|
125
|
+
profile,
|
|
126
|
+
providers: orderedProviderClasses,
|
|
127
|
+
env: envRecord,
|
|
128
|
+
logger: loggerInstance,
|
|
129
|
+
fastify
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
...providerRuntime,
|
|
134
|
+
routeCount: providerRuntime.routeCount,
|
|
135
|
+
packageOrder: catalog.packageOrder,
|
|
136
|
+
globalUiPaths: catalog.globalUiPaths,
|
|
137
|
+
providerPackageOrder: Object.freeze(providerPackageIds),
|
|
138
|
+
appLocalProviderOrder: Object.freeze([])
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { createProviderRuntimeApp, createProviderRuntimeFromApp };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { createProviderRuntimeFromApp } from "./providerRuntime.js";
|
|
8
|
+
|
|
9
|
+
async function createTestAppRoot(prefix) {
|
|
10
|
+
const appRoot = await mkdtemp(path.join(tmpdir(), prefix));
|
|
11
|
+
await mkdir(path.join(appRoot, ".jskit"), { recursive: true });
|
|
12
|
+
await writeFile(
|
|
13
|
+
path.join(appRoot, ".jskit", "lock.json"),
|
|
14
|
+
`${JSON.stringify({ lockVersion: 1, installedPackages: {} }, null, 2)}\n`,
|
|
15
|
+
"utf8"
|
|
16
|
+
);
|
|
17
|
+
return appRoot;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test("createProviderRuntimeFromApp discovers package providers from descriptor discover entries", async () => {
|
|
21
|
+
const appRoot = await createTestAppRoot("kernel-provider-runtime-discover-");
|
|
22
|
+
try {
|
|
23
|
+
await mkdir(path.join(appRoot, "packages", "local-example", "src", "server", "providers"), { recursive: true });
|
|
24
|
+
await writeFile(
|
|
25
|
+
path.join(appRoot, ".jskit", "lock.json"),
|
|
26
|
+
`${JSON.stringify(
|
|
27
|
+
{
|
|
28
|
+
lockVersion: 1,
|
|
29
|
+
installedPackages: {
|
|
30
|
+
"@local/example": {
|
|
31
|
+
packageId: "@local/example",
|
|
32
|
+
version: "0.1.0",
|
|
33
|
+
source: {
|
|
34
|
+
type: "local-package",
|
|
35
|
+
packagePath: "packages/local-example"
|
|
36
|
+
},
|
|
37
|
+
managed: {
|
|
38
|
+
packageJson: {
|
|
39
|
+
dependencies: {},
|
|
40
|
+
devDependencies: {},
|
|
41
|
+
scripts: {}
|
|
42
|
+
},
|
|
43
|
+
text: {},
|
|
44
|
+
files: []
|
|
45
|
+
},
|
|
46
|
+
options: {},
|
|
47
|
+
installedAt: "2026-01-01T00:00:00.000Z"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2
|
|
53
|
+
)}\n`,
|
|
54
|
+
"utf8"
|
|
55
|
+
);
|
|
56
|
+
await writeFile(
|
|
57
|
+
path.join(appRoot, "packages", "local-example", "package.descriptor.mjs"),
|
|
58
|
+
[
|
|
59
|
+
"export default Object.freeze({",
|
|
60
|
+
" packageVersion: 1,",
|
|
61
|
+
" packageId: \"@local/example\",",
|
|
62
|
+
" version: \"0.1.0\",",
|
|
63
|
+
" description: \"Local example package\",",
|
|
64
|
+
" dependsOn: [],",
|
|
65
|
+
" capabilities: {",
|
|
66
|
+
" provides: [],",
|
|
67
|
+
" requires: []",
|
|
68
|
+
" },",
|
|
69
|
+
" runtime: {",
|
|
70
|
+
" server: {",
|
|
71
|
+
" providers: [",
|
|
72
|
+
" { discover: { dir: \"src/server/providers\", pattern: \"*Provider.js\" } }",
|
|
73
|
+
" ]",
|
|
74
|
+
" }",
|
|
75
|
+
" }",
|
|
76
|
+
"});"
|
|
77
|
+
].join("\n"),
|
|
78
|
+
"utf8"
|
|
79
|
+
);
|
|
80
|
+
await writeFile(
|
|
81
|
+
path.join(appRoot, "packages", "local-example", "src", "server", "providers", "AlphaProvider.js"),
|
|
82
|
+
[
|
|
83
|
+
"export default class AlphaProvider {",
|
|
84
|
+
" static id = \"example.alpha\";",
|
|
85
|
+
" register(app) {",
|
|
86
|
+
" app.instance(\"example.alpha.value\", 42);",
|
|
87
|
+
" }",
|
|
88
|
+
" boot() {}",
|
|
89
|
+
"}"
|
|
90
|
+
].join("\n"),
|
|
91
|
+
"utf8"
|
|
92
|
+
);
|
|
93
|
+
await writeFile(
|
|
94
|
+
path.join(appRoot, "packages", "local-example", "src", "server", "providers", "ignored.js"),
|
|
95
|
+
"export const value = 1;\n",
|
|
96
|
+
"utf8"
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const runtime = await createProviderRuntimeFromApp({
|
|
100
|
+
appRoot,
|
|
101
|
+
profile: "app"
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
assert.deepEqual(runtime.packageOrder, ["@local/example"]);
|
|
105
|
+
assert.deepEqual(runtime.providerPackageOrder, ["@local/example"]);
|
|
106
|
+
assert.equal(runtime.appLocalProviderOrder.length, 0);
|
|
107
|
+
assert.deepEqual(runtime.diagnostics.providerOrder, ["example.alpha", "runtime.actions", "runtime.server"]);
|
|
108
|
+
assert.equal(runtime.app.make("example.alpha.value"), 42);
|
|
109
|
+
assert.equal(typeof runtime.app.make("actionExecutor")?.execute, "function");
|
|
110
|
+
} finally {
|
|
111
|
+
await rm(appRoot, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("createProviderRuntimeFromApp ignores legacy app local src/server/providers folder", async () => {
|
|
116
|
+
const appRoot = await createTestAppRoot("kernel-provider-runtime-legacy-app-local-");
|
|
117
|
+
try {
|
|
118
|
+
await mkdir(path.join(appRoot, "src", "server", "providers"), { recursive: true });
|
|
119
|
+
await writeFile(
|
|
120
|
+
path.join(appRoot, "src", "server", "providers", "LegacyProvider.js"),
|
|
121
|
+
[
|
|
122
|
+
"export default class LegacyProvider {",
|
|
123
|
+
" static id = \"legacy.app.local\";",
|
|
124
|
+
" register(app) {",
|
|
125
|
+
" app.instance(\"legacy.value\", true);",
|
|
126
|
+
" }",
|
|
127
|
+
" boot() {}",
|
|
128
|
+
"}"
|
|
129
|
+
].join("\n"),
|
|
130
|
+
"utf8"
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const runtime = await createProviderRuntimeFromApp({
|
|
134
|
+
appRoot,
|
|
135
|
+
profile: "app"
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
assert.deepEqual(runtime.packageOrder, []);
|
|
139
|
+
assert.deepEqual(runtime.providerPackageOrder, []);
|
|
140
|
+
assert.equal(runtime.appLocalProviderOrder.length, 0);
|
|
141
|
+
assert.deepEqual(runtime.diagnostics.providerOrder, ["runtime.actions", "runtime.server"]);
|
|
142
|
+
assert.equal(runtime.app.has("legacy.value"), false);
|
|
143
|
+
} finally {
|
|
144
|
+
await rm(appRoot, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("createProviderRuntimeFromApp resolves descriptor using source.packagePath", async () => {
|
|
149
|
+
const appRoot = await createTestAppRoot("kernel-provider-runtime-local-package-");
|
|
150
|
+
try {
|
|
151
|
+
await mkdir(path.join(appRoot, "packages", "local-example"), { recursive: true });
|
|
152
|
+
await writeFile(
|
|
153
|
+
path.join(appRoot, ".jskit", "lock.json"),
|
|
154
|
+
`${JSON.stringify(
|
|
155
|
+
{
|
|
156
|
+
lockVersion: 1,
|
|
157
|
+
installedPackages: {
|
|
158
|
+
"@local/example": {
|
|
159
|
+
packageId: "@local/example",
|
|
160
|
+
version: "0.1.0",
|
|
161
|
+
source: {
|
|
162
|
+
type: "local-package",
|
|
163
|
+
packagePath: "packages/local-example"
|
|
164
|
+
},
|
|
165
|
+
managed: {
|
|
166
|
+
packageJson: {
|
|
167
|
+
dependencies: {},
|
|
168
|
+
devDependencies: {},
|
|
169
|
+
scripts: {}
|
|
170
|
+
},
|
|
171
|
+
text: {},
|
|
172
|
+
files: []
|
|
173
|
+
},
|
|
174
|
+
options: {},
|
|
175
|
+
installedAt: "2026-01-01T00:00:00.000Z"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2
|
|
181
|
+
)}\n`,
|
|
182
|
+
"utf8"
|
|
183
|
+
);
|
|
184
|
+
await writeFile(
|
|
185
|
+
path.join(appRoot, "packages", "local-example", "package.descriptor.mjs"),
|
|
186
|
+
[
|
|
187
|
+
"export default Object.freeze({",
|
|
188
|
+
" packageVersion: 1,",
|
|
189
|
+
" packageId: \"@local/example\",",
|
|
190
|
+
" version: \"0.1.0\",",
|
|
191
|
+
" description: \"Local example package\",",
|
|
192
|
+
" dependsOn: [],",
|
|
193
|
+
" capabilities: {",
|
|
194
|
+
" provides: [],",
|
|
195
|
+
" requires: []",
|
|
196
|
+
" },",
|
|
197
|
+
" runtime: {",
|
|
198
|
+
" server: {",
|
|
199
|
+
" providers: []",
|
|
200
|
+
" }",
|
|
201
|
+
" }",
|
|
202
|
+
"});"
|
|
203
|
+
].join("\n"),
|
|
204
|
+
"utf8"
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const runtime = await createProviderRuntimeFromApp({
|
|
208
|
+
appRoot,
|
|
209
|
+
profile: "app"
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
assert.deepEqual(runtime.packageOrder, ["@local/example"]);
|
|
213
|
+
assert.deepEqual(runtime.providerPackageOrder, []);
|
|
214
|
+
} finally {
|
|
215
|
+
await rm(appRoot, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRuntimeAssembly } from "../runtime/runtimeAssembly.js";
|
|
2
|
+
|
|
3
|
+
function createPlatformRuntimeBundle({
|
|
4
|
+
repositoryDefinitions = [],
|
|
5
|
+
serviceDefinitions = [],
|
|
6
|
+
controllerDefinitions = [],
|
|
7
|
+
runtimeServiceIds = []
|
|
8
|
+
} = {}) {
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
repositoryDefinitions,
|
|
11
|
+
serviceDefinitions,
|
|
12
|
+
controllerDefinitions,
|
|
13
|
+
runtimeServiceIds
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createServerRuntime({ bundles = [], dependencies = {} } = {}) {
|
|
18
|
+
return createRuntimeAssembly({
|
|
19
|
+
bundles,
|
|
20
|
+
dependencies
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createServerRuntimeWithPlatformBundle({
|
|
25
|
+
platformBundle,
|
|
26
|
+
appFeatureBundle,
|
|
27
|
+
dependencies = {}
|
|
28
|
+
} = {}) {
|
|
29
|
+
if (!platformBundle || typeof platformBundle !== "object") {
|
|
30
|
+
throw new Error("platformBundle is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bundles = appFeatureBundle ? [platformBundle, appFeatureBundle] : [platformBundle];
|
|
34
|
+
return createServerRuntime({
|
|
35
|
+
bundles,
|
|
36
|
+
dependencies
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { createPlatformRuntimeBundle, createServerRuntime, createServerRuntimeWithPlatformBundle };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createProviderRuntimeFromApp } from "./providerRuntime.js";
|
|
2
|
+
import { matchesPathPrefix, normalizePathname } from "../../shared/surface/paths.js";
|
|
3
|
+
|
|
4
|
+
function toRequestPathname(urlValue) {
|
|
5
|
+
const rawUrl = String(urlValue || "").trim() || "/";
|
|
6
|
+
try {
|
|
7
|
+
return normalizePathname(new URL(rawUrl, "http://localhost").pathname || "/");
|
|
8
|
+
} catch {
|
|
9
|
+
const [pathname] = rawUrl.split("?");
|
|
10
|
+
return normalizePathname(pathname || "/");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function matchesGlobalUiPath(pathname, globalUiPaths = []) {
|
|
15
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
16
|
+
const normalizedGlobalUiPaths = [...new Set((Array.isArray(globalUiPaths) ? globalUiPaths : []).map(normalizePathname))]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.sort((left, right) => right.length - left.length);
|
|
19
|
+
|
|
20
|
+
for (const globalUiPath of normalizedGlobalUiPaths) {
|
|
21
|
+
if (matchesPathPrefix(normalizedPathname, globalUiPath)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function shouldServePathForSurface({
|
|
30
|
+
surfaceRuntime,
|
|
31
|
+
pathname,
|
|
32
|
+
serverSurface,
|
|
33
|
+
apiPathPrefix = "/api/",
|
|
34
|
+
globalUiPaths = []
|
|
35
|
+
} = {}) {
|
|
36
|
+
if (!surfaceRuntime || typeof surfaceRuntime.normalizeSurfaceMode !== "function") {
|
|
37
|
+
throw new TypeError("shouldServePathForSurface requires surfaceRuntime.normalizeSurfaceMode().");
|
|
38
|
+
}
|
|
39
|
+
if (typeof surfaceRuntime.resolveSurfaceFromPathname !== "function") {
|
|
40
|
+
throw new TypeError("shouldServePathForSurface requires surfaceRuntime.resolveSurfaceFromPathname().");
|
|
41
|
+
}
|
|
42
|
+
if (typeof surfaceRuntime.isSurfaceEnabled !== "function") {
|
|
43
|
+
throw new TypeError("shouldServePathForSurface requires surfaceRuntime.isSurfaceEnabled().");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const normalizedSurface = surfaceRuntime.normalizeSurfaceMode(serverSurface);
|
|
47
|
+
const allMode = String(surfaceRuntime.SURFACE_MODE_ALL || "all").trim().toLowerCase() || "all";
|
|
48
|
+
const normalizedPathname = String(pathname || "").trim() || "/";
|
|
49
|
+
const normalizedApiPrefix = String(apiPathPrefix || "/api/").trim() || "/api/";
|
|
50
|
+
|
|
51
|
+
if (normalizedPathname.startsWith(normalizedApiPrefix)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (matchesGlobalUiPath(normalizedPathname, globalUiPaths)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const routeSurface = surfaceRuntime.resolveSurfaceFromPathname(normalizedPathname);
|
|
60
|
+
if (!surfaceRuntime.isSurfaceEnabled(routeSurface)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (normalizedSurface === allMode) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return routeSurface === normalizedSurface;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function registerSurfaceRequestConstraint({
|
|
72
|
+
fastify,
|
|
73
|
+
surfaceRuntime,
|
|
74
|
+
serverSurface,
|
|
75
|
+
apiPathPrefix = "/api/",
|
|
76
|
+
globalUiPaths = []
|
|
77
|
+
} = {}) {
|
|
78
|
+
if (!fastify || typeof fastify.addHook !== "function") {
|
|
79
|
+
throw new TypeError("registerSurfaceRequestConstraint requires fastify.addHook().");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const normalizedSurface = surfaceRuntime.normalizeSurfaceMode(serverSurface);
|
|
83
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
84
|
+
const pathname = toRequestPathname(request?.url);
|
|
85
|
+
if (
|
|
86
|
+
shouldServePathForSurface({
|
|
87
|
+
surfaceRuntime,
|
|
88
|
+
pathname,
|
|
89
|
+
serverSurface: normalizedSurface,
|
|
90
|
+
apiPathPrefix,
|
|
91
|
+
globalUiPaths
|
|
92
|
+
})
|
|
93
|
+
) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
reply.code(404).type("application/json").send({
|
|
98
|
+
ok: false,
|
|
99
|
+
error: `Path ${pathname} is not served by ${normalizedSurface} surface server.`
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveRuntimeProfileFromSurface({
|
|
105
|
+
surfaceRuntime,
|
|
106
|
+
serverSurface,
|
|
107
|
+
defaultProfile = ""
|
|
108
|
+
} = {}) {
|
|
109
|
+
if (!surfaceRuntime || typeof surfaceRuntime.normalizeSurfaceMode !== "function") {
|
|
110
|
+
throw new TypeError("resolveRuntimeProfileFromSurface requires surfaceRuntime.normalizeSurfaceMode().");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalizedSurface = surfaceRuntime.normalizeSurfaceMode(serverSurface);
|
|
114
|
+
const allMode = String(surfaceRuntime.SURFACE_MODE_ALL || "all").trim().toLowerCase() || "all";
|
|
115
|
+
if (normalizedSurface !== allMode) {
|
|
116
|
+
return normalizedSurface;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const normalizedDefaultProfile = surfaceRuntime.normalizeSurfaceMode(defaultProfile);
|
|
120
|
+
if (normalizedDefaultProfile && normalizedDefaultProfile !== allMode) {
|
|
121
|
+
return normalizedDefaultProfile;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const normalizedRuntimeDefaultSurface = surfaceRuntime.normalizeSurfaceMode(surfaceRuntime.DEFAULT_SURFACE_ID);
|
|
125
|
+
if (normalizedRuntimeDefaultSurface && normalizedRuntimeDefaultSurface !== allMode) {
|
|
126
|
+
return normalizedRuntimeDefaultSurface;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function tryCreateProviderRuntimeFromApp(options = {}) {
|
|
133
|
+
try {
|
|
134
|
+
return await createProviderRuntimeFromApp(options);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = String(error?.message || "");
|
|
137
|
+
if (message.includes("Lock file not found:")) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
toRequestPathname,
|
|
146
|
+
shouldServePathForSurface,
|
|
147
|
+
registerSurfaceRequestConstraint,
|
|
148
|
+
resolveRuntimeProfileFromSurface,
|
|
149
|
+
tryCreateProviderRuntimeFromApp
|
|
150
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveRuntimeProfileFromSurface,
|
|
6
|
+
shouldServePathForSurface,
|
|
7
|
+
toRequestPathname
|
|
8
|
+
} from "./surfaceRuntime.js";
|
|
9
|
+
|
|
10
|
+
function createFakeSurfaceRuntime() {
|
|
11
|
+
return {
|
|
12
|
+
SURFACE_MODE_ALL: "all",
|
|
13
|
+
normalizeSurfaceMode(value) {
|
|
14
|
+
const normalized = String(value || "")
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase();
|
|
17
|
+
if (!normalized || normalized === "all") {
|
|
18
|
+
return "all";
|
|
19
|
+
}
|
|
20
|
+
return ["app", "admin", "console"].includes(normalized) ? normalized : "all";
|
|
21
|
+
},
|
|
22
|
+
resolveSurfaceFromPathname(pathname) {
|
|
23
|
+
const normalized = String(pathname || "").trim();
|
|
24
|
+
if (normalized === "/admin" || normalized.startsWith("/admin/")) {
|
|
25
|
+
return "admin";
|
|
26
|
+
}
|
|
27
|
+
if (normalized === "/console" || normalized.startsWith("/console/")) {
|
|
28
|
+
return "console";
|
|
29
|
+
}
|
|
30
|
+
return "app";
|
|
31
|
+
},
|
|
32
|
+
isSurfaceEnabled(surfaceId) {
|
|
33
|
+
return ["app", "admin"].includes(surfaceId);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test("toRequestPathname strips query and survives invalid url", () => {
|
|
39
|
+
assert.equal(toRequestPathname("/admin/users?id=1"), "/admin/users");
|
|
40
|
+
assert.equal(toRequestPathname("///bad path"), "/bad path");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("shouldServePathForSurface allows api and matching enabled surfaces", () => {
|
|
44
|
+
const surfaceRuntime = createFakeSurfaceRuntime();
|
|
45
|
+
assert.equal(
|
|
46
|
+
shouldServePathForSurface({
|
|
47
|
+
surfaceRuntime,
|
|
48
|
+
pathname: "/api/health",
|
|
49
|
+
serverSurface: "admin"
|
|
50
|
+
}),
|
|
51
|
+
true
|
|
52
|
+
);
|
|
53
|
+
assert.equal(
|
|
54
|
+
shouldServePathForSurface({
|
|
55
|
+
surfaceRuntime,
|
|
56
|
+
pathname: "/admin/users",
|
|
57
|
+
serverSurface: "admin"
|
|
58
|
+
}),
|
|
59
|
+
true
|
|
60
|
+
);
|
|
61
|
+
assert.equal(
|
|
62
|
+
shouldServePathForSurface({
|
|
63
|
+
surfaceRuntime,
|
|
64
|
+
pathname: "/console",
|
|
65
|
+
serverSurface: "all"
|
|
66
|
+
}),
|
|
67
|
+
false
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("shouldServePathForSurface allows descriptor-declared global ui paths", () => {
|
|
72
|
+
const surfaceRuntime = createFakeSurfaceRuntime();
|
|
73
|
+
assert.equal(
|
|
74
|
+
shouldServePathForSurface({
|
|
75
|
+
surfaceRuntime,
|
|
76
|
+
pathname: "/auth/login",
|
|
77
|
+
serverSurface: "admin",
|
|
78
|
+
globalUiPaths: ["/auth/login", "/auth/signout"]
|
|
79
|
+
}),
|
|
80
|
+
true
|
|
81
|
+
);
|
|
82
|
+
assert.equal(
|
|
83
|
+
shouldServePathForSurface({
|
|
84
|
+
surfaceRuntime,
|
|
85
|
+
pathname: "/auth/signout/complete",
|
|
86
|
+
serverSurface: "admin",
|
|
87
|
+
globalUiPaths: ["/auth/login", "/auth/signout"]
|
|
88
|
+
}),
|
|
89
|
+
true
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("resolveRuntimeProfileFromSurface maps all mode to default profile", () => {
|
|
94
|
+
const surfaceRuntime = createFakeSurfaceRuntime();
|
|
95
|
+
assert.equal(
|
|
96
|
+
resolveRuntimeProfileFromSurface({
|
|
97
|
+
surfaceRuntime,
|
|
98
|
+
serverSurface: "all",
|
|
99
|
+
defaultProfile: "app"
|
|
100
|
+
}),
|
|
101
|
+
"app"
|
|
102
|
+
);
|
|
103
|
+
assert.equal(
|
|
104
|
+
resolveRuntimeProfileFromSurface({
|
|
105
|
+
surfaceRuntime,
|
|
106
|
+
serverSurface: "admin",
|
|
107
|
+
defaultProfile: "app"
|
|
108
|
+
}),
|
|
109
|
+
"admin"
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("resolveRuntimeProfileFromSurface uses runtime default surface when default profile is not configured", () => {
|
|
114
|
+
const surfaceRuntime = {
|
|
115
|
+
SURFACE_MODE_ALL: "all",
|
|
116
|
+
DEFAULT_SURFACE_ID: "home",
|
|
117
|
+
normalizeSurfaceMode(value) {
|
|
118
|
+
const normalized = String(value || "")
|
|
119
|
+
.trim()
|
|
120
|
+
.toLowerCase();
|
|
121
|
+
if (!normalized || normalized === "all") {
|
|
122
|
+
return "all";
|
|
123
|
+
}
|
|
124
|
+
return ["home", "admin"].includes(normalized) ? normalized : "all";
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
assert.equal(
|
|
129
|
+
resolveRuntimeProfileFromSurface({
|
|
130
|
+
surfaceRuntime,
|
|
131
|
+
serverSurface: "all",
|
|
132
|
+
defaultProfile: "app"
|
|
133
|
+
}),
|
|
134
|
+
"home"
|
|
135
|
+
);
|
|
136
|
+
});
|