@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.
Files changed (185) hide show
  1. package/README.md +24 -0
  2. package/_testable/index.js +4 -0
  3. package/client/appConfig.js +33 -0
  4. package/client/componentInteraction.js +51 -0
  5. package/client/componentInteraction.test.js +111 -0
  6. package/client/descriptorSections.js +75 -0
  7. package/client/index.d.ts +70 -0
  8. package/client/index.js +3 -0
  9. package/client/logging.js +38 -0
  10. package/client/moduleBootstrap.js +670 -0
  11. package/client/moduleBootstrap.test.js +403 -0
  12. package/client/shellBootstrap.js +233 -0
  13. package/client/shellBootstrap.test.js +185 -0
  14. package/client/shellRouting.js +321 -0
  15. package/client/shellRouting.test.js +113 -0
  16. package/client/vite/clientBootstrapPlugin.js +259 -0
  17. package/client/vite/clientBootstrapPlugin.test.js +563 -0
  18. package/client/vite/index.js +3 -0
  19. package/internal/node/fileSystem.js +21 -0
  20. package/internal/node/installedPackageDescriptor.js +104 -0
  21. package/package.json +43 -0
  22. package/server/actions/ActionRuntimeServiceProvider.js +309 -0
  23. package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
  24. package/server/actions/index.js +8 -0
  25. package/server/container/ContainerCoreServiceProvider.js +27 -0
  26. package/server/container/index.js +10 -0
  27. package/server/exportPolicy.test.js +68 -0
  28. package/server/http/HttpFastifyServiceProvider.js +25 -0
  29. package/server/http/_testable/index.js +2 -0
  30. package/server/http/index.js +1 -0
  31. package/server/http/lib/controller.js +183 -0
  32. package/server/http/lib/controller.test.js +143 -0
  33. package/server/http/lib/errors.js +12 -0
  34. package/server/http/lib/httpRuntime.js +82 -0
  35. package/server/http/lib/index.js +18 -0
  36. package/server/http/lib/kernel.js +15 -0
  37. package/server/http/lib/kernel.test.js +880 -0
  38. package/server/http/lib/middlewareRuntime.js +149 -0
  39. package/server/http/lib/requestActionExecutor.js +258 -0
  40. package/server/http/lib/requestScope.js +59 -0
  41. package/server/http/lib/routeRegistration.js +165 -0
  42. package/server/http/lib/routeSupport.js +45 -0
  43. package/server/http/lib/routeValidator.js +469 -0
  44. package/server/http/lib/routeValidator.test.js +474 -0
  45. package/server/http/lib/router.js +206 -0
  46. package/server/kernel/KernelCoreServiceProvider.js +27 -0
  47. package/server/kernel/index.js +10 -0
  48. package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
  49. package/server/platform/index.js +5 -0
  50. package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
  51. package/server/platform/providerRuntime/helpers.js +45 -0
  52. package/server/platform/providerRuntime/lockfile.js +27 -0
  53. package/server/platform/providerRuntime/providerLoader.js +283 -0
  54. package/server/platform/providerRuntime.js +142 -0
  55. package/server/platform/providerRuntime.test.js +217 -0
  56. package/server/platform/runtime.js +40 -0
  57. package/server/platform/surfaceRuntime.js +150 -0
  58. package/server/platform/surfaceRuntime.test.js +136 -0
  59. package/server/registries/actionSurfaceSourceRegistry.js +150 -0
  60. package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
  61. package/server/registries/domainEventListenerRegistry.js +61 -0
  62. package/server/registries/index.js +36 -0
  63. package/server/registries/primitives.js +63 -0
  64. package/server/registries/routeVisibilityResolverRegistry.js +87 -0
  65. package/server/registries/serviceRegistrationRegistry.js +431 -0
  66. package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
  67. package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
  68. package/server/runtime/apiRoutePolicyParity.test.js +109 -0
  69. package/server/runtime/apiRouteRegistration.js +65 -0
  70. package/server/runtime/bootBootstrapRoutes.js +46 -0
  71. package/server/runtime/bootBootstrapRoutes.test.js +79 -0
  72. package/server/runtime/bootstrapContributors.test.js +114 -0
  73. package/server/runtime/canonicalJson.js +74 -0
  74. package/server/runtime/composition.js +142 -0
  75. package/server/runtime/domainEvents.test.js +114 -0
  76. package/server/runtime/domainRules.js +50 -0
  77. package/server/runtime/domainRules.test.js +87 -0
  78. package/server/runtime/entityChangeEvents.js +182 -0
  79. package/server/runtime/entityChangeEvents.test.js +211 -0
  80. package/server/runtime/errors.js +68 -0
  81. package/server/runtime/errors.test.js +73 -0
  82. package/server/runtime/fastifyBootstrap.js +372 -0
  83. package/server/runtime/fastifyBootstrap.test.js +194 -0
  84. package/server/runtime/index.js +6 -0
  85. package/server/runtime/integers.js +13 -0
  86. package/server/runtime/moduleConfig.js +269 -0
  87. package/server/runtime/moduleConfig.test.js +141 -0
  88. package/server/runtime/pagination.js +13 -0
  89. package/server/runtime/realtimeNormalization.js +21 -0
  90. package/server/runtime/requestUrl.js +38 -0
  91. package/server/runtime/routeUtils.js +20 -0
  92. package/server/runtime/runtimeAssembly.js +113 -0
  93. package/server/runtime/runtimeKernel.js +55 -0
  94. package/server/runtime/securityAudit.js +269 -0
  95. package/server/runtime/securityAudit.test.js +41 -0
  96. package/server/runtime/serviceAuthorization.js +113 -0
  97. package/server/runtime/serviceAuthorization.test.js +100 -0
  98. package/server/runtime/serviceRegistration.test.js +197 -0
  99. package/server/support/SupportCoreServiceProvider.js +25 -0
  100. package/server/support/appConfig.js +37 -0
  101. package/server/support/appConfig.test.js +94 -0
  102. package/server/support/defaultMissingHandler.js +7 -0
  103. package/server/support/index.js +2 -0
  104. package/server/support/routePolicyConfig.js +51 -0
  105. package/server/support/symlinkSafeRequire.js +78 -0
  106. package/server/support/symlinkSafeRequire.test.js +27 -0
  107. package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
  108. package/server/surface/index.js +19 -0
  109. package/shared/actions/actionContributorHelpers.js +34 -0
  110. package/shared/actions/actionContributorHelpers.test.js +16 -0
  111. package/shared/actions/actionDefinitions.js +488 -0
  112. package/shared/actions/actionDefinitions.test.js +212 -0
  113. package/shared/actions/audit.js +7 -0
  114. package/shared/actions/executionContext.js +97 -0
  115. package/shared/actions/executionContext.test.js +66 -0
  116. package/shared/actions/idempotency.js +62 -0
  117. package/shared/actions/index.js +2 -0
  118. package/shared/actions/observability.js +10 -0
  119. package/shared/actions/pipeline.js +287 -0
  120. package/shared/actions/policies.js +342 -0
  121. package/shared/actions/policies.test.js +233 -0
  122. package/shared/actions/registry.js +187 -0
  123. package/shared/actions/registry.test.js +381 -0
  124. package/shared/actions/requestMeta.js +36 -0
  125. package/shared/actions/textNormalization.js +3 -0
  126. package/shared/actions/withActionDefaults.js +34 -0
  127. package/shared/index.js +2 -0
  128. package/shared/runtime/application.js +323 -0
  129. package/shared/runtime/container.js +261 -0
  130. package/shared/runtime/containerErrors.js +22 -0
  131. package/shared/runtime/index.js +18 -0
  132. package/shared/runtime/kernelErrors.js +20 -0
  133. package/shared/runtime/serviceProvider.js +13 -0
  134. package/shared/support/formatDateTime.js +10 -0
  135. package/shared/support/formatDateTime.test.js +15 -0
  136. package/shared/support/index.js +14 -0
  137. package/shared/support/linkPath.js +67 -0
  138. package/shared/support/linkPath.test.js +35 -0
  139. package/shared/support/normalize.js +116 -0
  140. package/shared/support/normalize.test.js +48 -0
  141. package/shared/support/packageDescriptor.test.js +121 -0
  142. package/shared/support/permissions.js +50 -0
  143. package/shared/support/pickOwnProperties.js +17 -0
  144. package/shared/support/pickOwnProperties.test.js +25 -0
  145. package/shared/support/policies.js +11 -0
  146. package/shared/support/queryPath.js +33 -0
  147. package/shared/support/queryPath.test.js +19 -0
  148. package/shared/support/queryResilience.js +34 -0
  149. package/shared/support/queryResilience.test.js +33 -0
  150. package/shared/support/returnToPath.js +153 -0
  151. package/shared/support/returnToPath.test.js +123 -0
  152. package/shared/support/sorting.js +15 -0
  153. package/shared/support/tokens.js +23 -0
  154. package/shared/support/tokens.test.js +17 -0
  155. package/shared/support/visibility.js +56 -0
  156. package/shared/support/visibility.test.js +45 -0
  157. package/shared/surface/apiPaths.js +84 -0
  158. package/shared/surface/escapeRegExp.js +5 -0
  159. package/shared/surface/index.js +6 -0
  160. package/shared/surface/paths.js +273 -0
  161. package/shared/surface/registry.js +135 -0
  162. package/shared/surface/registry.test.js +44 -0
  163. package/shared/surface/runtime.js +357 -0
  164. package/shared/surface/runtime.test.js +319 -0
  165. package/shared/validators/createCursorListValidator.js +42 -0
  166. package/shared/validators/createCursorListValidator.test.js +34 -0
  167. package/shared/validators/cursorPaginationQueryValidator.js +31 -0
  168. package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
  169. package/shared/validators/index.js +12 -0
  170. package/shared/validators/inputNormalization.js +13 -0
  171. package/shared/validators/mergeObjectSchemas.js +31 -0
  172. package/shared/validators/mergeObjectSchemas.test.js +67 -0
  173. package/shared/validators/mergeValidators.js +89 -0
  174. package/shared/validators/mergeValidators.test.js +116 -0
  175. package/shared/validators/nestValidator.js +53 -0
  176. package/shared/validators/nestValidator.test.js +60 -0
  177. package/shared/validators/recordIdParamsValidator.js +36 -0
  178. package/shared/validators/recordIdParamsValidator.test.js +20 -0
  179. package/shared/validators/resourceRequiredMetadata.js +41 -0
  180. package/shared/validators/resourceRequiredMetadata.test.js +49 -0
  181. package/test/barrelExposure.test.js +106 -0
  182. package/test/dynamicImportPolicy.test.js +89 -0
  183. package/test/exportsContract.test.js +168 -0
  184. package/test/routeInputContractGuard.test.js +78 -0
  185. 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
+ });