@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,30 @@
1
+ import { createProviderRuntimeApp, createProviderRuntimeFromApp } from "./providerRuntime.js";
2
+ import {
3
+ createPlatformRuntimeBundle,
4
+ createServerRuntime,
5
+ createServerRuntimeWithPlatformBundle
6
+ } from "./runtime.js";
7
+
8
+ const PLATFORM_SERVER_RUNTIME_API = Object.freeze({
9
+ createPlatformRuntimeBundle,
10
+ createServerRuntime,
11
+ createServerRuntimeWithPlatformBundle,
12
+ createProviderRuntimeApp,
13
+ createProviderRuntimeFromApp
14
+ });
15
+
16
+ class PlatformServerRuntimeServiceProvider {
17
+ static id = "runtime.platform-server";
18
+
19
+ register(app) {
20
+ if (!app || typeof app.singleton !== "function") {
21
+ throw new Error("PlatformServerRuntimeServiceProvider requires application singleton().");
22
+ }
23
+
24
+ app.singleton("runtime.platform-server", () => PLATFORM_SERVER_RUNTIME_API);
25
+ }
26
+
27
+ boot() {}
28
+ }
29
+
30
+ export { PlatformServerRuntimeServiceProvider };
@@ -0,0 +1,5 @@
1
+ export {
2
+ registerSurfaceRequestConstraint,
3
+ resolveRuntimeProfileFromSurface,
4
+ tryCreateProviderRuntimeFromApp
5
+ } from "./surfaceRuntime.js";
@@ -0,0 +1,170 @@
1
+ import path from "node:path";
2
+ import { sortStrings } from "../../../shared/support/sorting.js";
3
+ import { loadInstalledPackageDescriptor } from "../../../internal/node/installedPackageDescriptor.js";
4
+
5
+ function normalizeUiRoutePath(pathValue) {
6
+ const rawPath = String(pathValue || "").trim();
7
+ if (!rawPath || !rawPath.startsWith("/") || rawPath.startsWith("//")) {
8
+ return "";
9
+ }
10
+
11
+ const normalizedPath = rawPath.replace(/\/{2,}/g, "/");
12
+ if (normalizedPath === "/") {
13
+ return "/";
14
+ }
15
+
16
+ return normalizedPath.replace(/\/+$/, "") || "/";
17
+ }
18
+
19
+ function collectGlobalUiPaths(descriptorEntries) {
20
+ const globalUiPaths = [];
21
+ const entries = Array.isArray(descriptorEntries) ? descriptorEntries : [];
22
+
23
+ for (const descriptorEntry of entries) {
24
+ const uiRoutes = Array.isArray(descriptorEntry?.descriptor?.metadata?.ui?.routes)
25
+ ? descriptorEntry.descriptor.metadata.ui.routes
26
+ : [];
27
+
28
+ for (const routeEntry of uiRoutes) {
29
+ const routeRecord = routeEntry && typeof routeEntry === "object" && !Array.isArray(routeEntry) ? routeEntry : null;
30
+ if (!routeRecord) {
31
+ continue;
32
+ }
33
+
34
+ const scope = String(routeRecord.scope || "")
35
+ .trim()
36
+ .toLowerCase();
37
+ if (scope !== "global") {
38
+ continue;
39
+ }
40
+
41
+ const routePath = normalizeUiRoutePath(routeRecord.path);
42
+ if (!routePath) {
43
+ continue;
44
+ }
45
+ globalUiPaths.push(routePath);
46
+ }
47
+ }
48
+
49
+ return Object.freeze(sortStrings(globalUiPaths));
50
+ }
51
+
52
+ async function resolveInstalledPackageDescriptors({ appRoot, lock }) {
53
+ const installedPackages =
54
+ lock && typeof lock === "object" && lock.installedPackages && typeof lock.installedPackages === "object"
55
+ ? lock.installedPackages
56
+ : {};
57
+
58
+ const descriptorEntries = [];
59
+ for (const packageId of sortStrings(Object.keys(installedPackages))) {
60
+ const installedPackageState = installedPackages[packageId] || {};
61
+ const descriptorRecord = await loadInstalledPackageDescriptor({
62
+ appRoot,
63
+ installedPackageState,
64
+ packageId,
65
+ required: true
66
+ });
67
+ descriptorEntries.push({
68
+ packageId,
69
+ descriptor: descriptorRecord.descriptor,
70
+ descriptorPath: descriptorRecord.descriptorPath,
71
+ packageRoot: path.dirname(descriptorRecord.descriptorPath)
72
+ });
73
+ }
74
+
75
+ return descriptorEntries;
76
+ }
77
+
78
+ function resolveDescriptorLoadOrder(descriptorEntries) {
79
+ const byPackageId = new Map(descriptorEntries.map((entry) => [entry.packageId, entry]));
80
+ const visiting = new Set();
81
+ const visited = new Set();
82
+ const ordered = [];
83
+
84
+ function visit(packageId, lineage = []) {
85
+ if (visited.has(packageId)) {
86
+ return;
87
+ }
88
+ if (visiting.has(packageId)) {
89
+ throw new Error(`Package dependency cycle detected: ${[...lineage, packageId].join(" -> ")}`);
90
+ }
91
+
92
+ const entry = byPackageId.get(packageId);
93
+ if (!entry) {
94
+ return;
95
+ }
96
+
97
+ visiting.add(packageId);
98
+ const dependsOn = Array.isArray(entry.descriptor?.dependsOn) ? entry.descriptor.dependsOn : [];
99
+ for (const dependencyPackageId of dependsOn) {
100
+ if (byPackageId.has(dependencyPackageId)) {
101
+ visit(dependencyPackageId, [...lineage, packageId]);
102
+ }
103
+ }
104
+ visiting.delete(packageId);
105
+ visited.add(packageId);
106
+ ordered.push(entry);
107
+ }
108
+
109
+ for (const packageId of sortStrings([...byPackageId.keys()])) {
110
+ visit(packageId);
111
+ }
112
+
113
+ return ordered;
114
+ }
115
+
116
+ function registerCapabilityProvider(providersByCapability, capabilityId, providerPackageId) {
117
+ const normalizedCapabilityId = String(capabilityId || "").trim();
118
+ const normalizedProviderPackageId = String(providerPackageId || "").trim();
119
+ if (!normalizedCapabilityId || !normalizedProviderPackageId) {
120
+ return;
121
+ }
122
+
123
+ if (!providersByCapability.has(normalizedCapabilityId)) {
124
+ providersByCapability.set(normalizedCapabilityId, new Set());
125
+ }
126
+ providersByCapability.get(normalizedCapabilityId).add(normalizedProviderPackageId);
127
+ }
128
+
129
+ function validateDescriptorCapabilities(descriptorEntries, { builtinProvidersByCapability = {} } = {}) {
130
+ const providersByCapability = new Map();
131
+ for (const descriptorEntry of descriptorEntries) {
132
+ for (const capabilityId of Array.isArray(descriptorEntry.descriptor?.capabilities?.provides)
133
+ ? descriptorEntry.descriptor.capabilities.provides
134
+ : []) {
135
+ registerCapabilityProvider(providersByCapability, capabilityId, descriptorEntry.packageId);
136
+ }
137
+ }
138
+
139
+ for (const [capabilityId, providerPackageIds] of Object.entries(
140
+ builtinProvidersByCapability && typeof builtinProvidersByCapability === "object" ? builtinProvidersByCapability : {}
141
+ )) {
142
+ for (const providerPackageId of Array.isArray(providerPackageIds) ? providerPackageIds : [providerPackageIds]) {
143
+ registerCapabilityProvider(providersByCapability, capabilityId, providerPackageId);
144
+ }
145
+ }
146
+
147
+ for (const descriptorEntry of descriptorEntries) {
148
+ for (const capabilityId of Array.isArray(descriptorEntry.descriptor?.capabilities?.requires)
149
+ ? descriptorEntry.descriptor.capabilities.requires
150
+ : []) {
151
+ const normalizedCapabilityId = String(capabilityId || "").trim();
152
+ if (!normalizedCapabilityId) {
153
+ continue;
154
+ }
155
+ const providers = providersByCapability.get(normalizedCapabilityId);
156
+ if (!providers || providers.size < 1) {
157
+ throw new Error(
158
+ `Package ${descriptorEntry.packageId} requires capability ${normalizedCapabilityId}, but no installed package provides it.`
159
+ );
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ export {
166
+ collectGlobalUiPaths,
167
+ resolveInstalledPackageDescriptors,
168
+ resolveDescriptorLoadOrder,
169
+ validateDescriptorCapabilities
170
+ };
@@ -0,0 +1,45 @@
1
+ import path from "node:path";
2
+ import { escapeRegExp } from "../../../shared/surface/escapeRegExp.js";
3
+ import { fileExists } from "../../../internal/node/fileSystem.js";
4
+
5
+ function isIdentifier(value) {
6
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(String(value || ""));
7
+ }
8
+
9
+ function createWildcardMatcher(pattern) {
10
+ const rawPattern = String(pattern || "").trim();
11
+ if (!rawPattern) {
12
+ return null;
13
+ }
14
+
15
+ const escapedPattern = escapeRegExp(rawPattern).replace(/\\\*/g, ".*");
16
+ return new RegExp(`^${escapedPattern}$`);
17
+ }
18
+
19
+ function normalizeRelativePath(fromPath, targetPath) {
20
+ const relative = path.relative(path.resolve(fromPath), path.resolve(targetPath));
21
+ return relative.split(path.sep).join("/");
22
+ }
23
+
24
+ function isInsidePackageRoot(packageRoot, candidatePath) {
25
+ const relative = path.relative(path.resolve(packageRoot), path.resolve(candidatePath));
26
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
27
+ }
28
+
29
+ function toAbsoluteSortedUniquePaths(values) {
30
+ const source = Array.isArray(values) ? values : [];
31
+ return Object.freeze(
32
+ [...new Set(source.map((entry) => path.resolve(entry)).filter(Boolean))].sort((left, right) =>
33
+ left.localeCompare(right)
34
+ )
35
+ );
36
+ }
37
+
38
+ export {
39
+ isIdentifier,
40
+ createWildcardMatcher,
41
+ normalizeRelativePath,
42
+ fileExists,
43
+ isInsidePackageRoot,
44
+ toAbsoluteSortedUniquePaths
45
+ };
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { fileExists } from "../../../internal/node/fileSystem.js";
4
+
5
+ async function readLockFromApp({ appRoot, lockPath = ".jskit/lock.json" } = {}) {
6
+ if (!appRoot || typeof appRoot !== "string") {
7
+ throw new TypeError("readLockFromApp requires appRoot.");
8
+ }
9
+
10
+ const absoluteLockPath = path.resolve(appRoot, String(lockPath || ".jskit/lock.json"));
11
+ if (!(await fileExists(absoluteLockPath))) {
12
+ throw new Error(`Lock file not found: ${absoluteLockPath}`);
13
+ }
14
+
15
+ const source = await readFile(absoluteLockPath, "utf8");
16
+ const lock = JSON.parse(source);
17
+ if (!lock || typeof lock !== "object") {
18
+ throw new TypeError(`Invalid lock file payload at ${absoluteLockPath}.`);
19
+ }
20
+
21
+ return {
22
+ lock,
23
+ lockPath: absoluteLockPath
24
+ };
25
+ }
26
+
27
+ export { readLockFromApp };
@@ -0,0 +1,283 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import {
5
+ isIdentifier,
6
+ createWildcardMatcher,
7
+ normalizeRelativePath,
8
+ fileExists,
9
+ isInsidePackageRoot,
10
+ toAbsoluteSortedUniquePaths
11
+ } from "./helpers.js";
12
+
13
+ function normalizeServerProviderDefinitions(descriptor, packageId) {
14
+ const runtime =
15
+ descriptor && typeof descriptor.runtime === "object" && descriptor.runtime && !Array.isArray(descriptor.runtime)
16
+ ? descriptor.runtime
17
+ : {};
18
+ const server =
19
+ runtime && typeof runtime.server === "object" && runtime.server && !Array.isArray(runtime.server)
20
+ ? runtime.server
21
+ : null;
22
+ if (!server) {
23
+ return Object.freeze([]);
24
+ }
25
+
26
+ const providers = Array.isArray(server.providers) ? server.providers : [];
27
+ const normalizedProviders = [];
28
+
29
+ for (const providerDefinition of providers) {
30
+ const entry = providerDefinition && typeof providerDefinition === "object" ? providerDefinition : {};
31
+ const providerEntrypoint = String(entry.entrypoint || "").trim();
32
+ const providerExport = String(entry.export || "").trim();
33
+ const discoverConfig =
34
+ entry.discover && typeof entry.discover === "object" && !Array.isArray(entry.discover) ? entry.discover : null;
35
+
36
+ if (discoverConfig) {
37
+ if (providerEntrypoint || providerExport) {
38
+ throw new Error(
39
+ `Package ${packageId} runtime.server.providers[] discover entries cannot include entrypoint/export fields.`
40
+ );
41
+ }
42
+
43
+ const discoverDir = String(discoverConfig.dir || "").trim();
44
+ if (!discoverDir) {
45
+ throw new Error(`Package ${packageId} runtime.server.providers[] discover.dir is required.`);
46
+ }
47
+ if (discoverDir.startsWith("/") || discoverDir.startsWith("//")) {
48
+ throw new Error(`Package ${packageId} runtime.server.providers[] discover.dir must be relative.`);
49
+ }
50
+ if (discoverDir.includes("*")) {
51
+ throw new Error(`Package ${packageId} runtime.server.providers[] discover.dir cannot contain wildcard "*".`);
52
+ }
53
+
54
+ const discoverPattern = String(discoverConfig.pattern || "*Provider.js").trim() || "*Provider.js";
55
+ if (discoverPattern.includes(path.sep) || discoverPattern.includes("/")) {
56
+ throw new Error(
57
+ `Package ${packageId} runtime.server.providers[] discover.pattern must match filenames only.`
58
+ );
59
+ }
60
+ if (!createWildcardMatcher(discoverPattern)) {
61
+ throw new Error(`Package ${packageId} runtime.server.providers[] discover.pattern is invalid.`);
62
+ }
63
+
64
+ const discoverRecursive = discoverConfig.recursive === true;
65
+ normalizedProviders.push(
66
+ Object.freeze({
67
+ type: "discover",
68
+ discoverDir,
69
+ discoverPattern,
70
+ discoverRecursive
71
+ })
72
+ );
73
+ continue;
74
+ }
75
+
76
+ if (!providerEntrypoint) {
77
+ throw new Error(`Package ${packageId} runtime.server.providers[] entrypoint is required.`);
78
+ }
79
+ if (!providerExport) {
80
+ throw new Error(`Package ${packageId} runtime.server.providers[] export is required for ${providerEntrypoint}.`);
81
+ }
82
+ if (!isIdentifier(providerExport)) {
83
+ throw new Error(`Package ${packageId} runtime.server.providers[] export is invalid: ${providerExport}`);
84
+ }
85
+
86
+ normalizedProviders.push(
87
+ Object.freeze({
88
+ type: "explicit",
89
+ providerEntrypoint,
90
+ providerExport
91
+ })
92
+ );
93
+ }
94
+
95
+ return Object.freeze(normalizedProviders);
96
+ }
97
+
98
+ function isProviderDefinition(value) {
99
+ if (typeof value !== "function") {
100
+ return false;
101
+ }
102
+ const prototype = value.prototype && typeof value.prototype === "object" ? value.prototype : null;
103
+ const hasLifecycleMethods = Boolean(
104
+ (prototype && typeof prototype.register === "function") ||
105
+ (prototype && typeof prototype.boot === "function") ||
106
+ (prototype && typeof prototype.shutdown === "function")
107
+ );
108
+ const rawId = value.id;
109
+ const hasProviderId = rawId !== undefined && rawId !== null && String(rawId).trim().length > 0;
110
+ return hasLifecycleMethods && hasProviderId;
111
+ }
112
+
113
+ function normalizeProviderExportValue(value, { packageId, label }) {
114
+ const output = [];
115
+ const queue = Array.isArray(value) ? [...value] : [value];
116
+ while (queue.length > 0) {
117
+ const candidate = queue.shift();
118
+ if (!candidate) {
119
+ continue;
120
+ }
121
+ if (Array.isArray(candidate)) {
122
+ queue.push(...candidate);
123
+ continue;
124
+ }
125
+ if (isProviderDefinition(candidate)) {
126
+ output.push(candidate);
127
+ continue;
128
+ }
129
+ throw new Error(
130
+ `Package ${packageId} ${label} must export a provider class/function or an array of provider classes/functions.`
131
+ );
132
+ }
133
+ return output;
134
+ }
135
+
136
+ function resolveProviderClassesFromModule(moduleNamespace, { packageId, providerExport }) {
137
+ const namespace = moduleNamespace && typeof moduleNamespace === "object" ? moduleNamespace : {};
138
+ if (providerExport) {
139
+ if (!Object.prototype.hasOwnProperty.call(namespace, providerExport)) {
140
+ throw new Error(`Package ${packageId} provider export "${providerExport}" was not found.`);
141
+ }
142
+ return normalizeProviderExportValue(namespace[providerExport], {
143
+ packageId,
144
+ label: `provider export "${providerExport}"`
145
+ });
146
+ }
147
+
148
+ const discovered = [];
149
+ for (const [exportName, exportValue] of Object.entries(namespace)) {
150
+ if (!isProviderDefinition(exportValue)) {
151
+ continue;
152
+ }
153
+ discovered.push(...normalizeProviderExportValue(exportValue, { packageId, label: `export "${exportName}"` }));
154
+ }
155
+
156
+ if (discovered.length < 1) {
157
+ throw new Error(
158
+ `Package ${packageId} provider entrypoint did not export any provider classes/functions.`
159
+ );
160
+ }
161
+
162
+ return discovered;
163
+ }
164
+
165
+ async function collectDiscoveredProviderModulePaths({ descriptorEntry, providerDefinition }) {
166
+ const discoverRoot = path.resolve(descriptorEntry.packageRoot, providerDefinition.discoverDir);
167
+ if (!isInsidePackageRoot(descriptorEntry.packageRoot, discoverRoot)) {
168
+ throw new Error(
169
+ `Package ${descriptorEntry.packageId} runtime.server.providers[] discover.dir escapes package root: ${providerDefinition.discoverDir}`
170
+ );
171
+ }
172
+ if (!(await fileExists(discoverRoot))) {
173
+ return Object.freeze([]);
174
+ }
175
+
176
+ const patternMatcher = createWildcardMatcher(providerDefinition.discoverPattern);
177
+ if (!(patternMatcher instanceof RegExp)) {
178
+ throw new Error(
179
+ `Package ${descriptorEntry.packageId} runtime.server.providers[] discover.pattern is invalid: ${providerDefinition.discoverPattern}`
180
+ );
181
+ }
182
+ const collectedPaths = [];
183
+
184
+ async function walk(currentPath) {
185
+ const entries = await readdir(currentPath, { withFileTypes: true });
186
+ entries.sort((left, right) => left.name.localeCompare(right.name));
187
+
188
+ for (const entry of entries) {
189
+ const entryName = String(entry?.name || "");
190
+ if (!entryName || entryName.startsWith(".")) {
191
+ continue;
192
+ }
193
+
194
+ const absoluteEntryPath = path.join(currentPath, entryName);
195
+ if (entry.isDirectory()) {
196
+ if (entryName === "node_modules") {
197
+ continue;
198
+ }
199
+ if (providerDefinition.discoverRecursive === true) {
200
+ await walk(absoluteEntryPath);
201
+ }
202
+ continue;
203
+ }
204
+
205
+ if (!entry.isFile()) {
206
+ continue;
207
+ }
208
+
209
+ if (patternMatcher.test(entryName)) {
210
+ collectedPaths.push(absoluteEntryPath);
211
+ }
212
+ }
213
+ }
214
+
215
+ await walk(discoverRoot);
216
+ return toAbsoluteSortedUniquePaths(collectedPaths);
217
+ }
218
+
219
+ function registerProviderClass({ providerClass, sourceId, seenProviderIds, orderedProviderClasses }) {
220
+ const providerId = String(providerClass.id || providerClass.name || "<anonymous-provider>").trim();
221
+ if (seenProviderIds.has(providerId)) {
222
+ const existingSourceId = seenProviderIds.get(providerId);
223
+ throw new Error(`Provider id "${providerId}" is duplicated between ${existingSourceId} and ${sourceId}.`);
224
+ }
225
+
226
+ seenProviderIds.set(providerId, sourceId);
227
+ orderedProviderClasses.push(providerClass);
228
+ }
229
+
230
+ async function loadPackageProviders({ descriptorEntry }) {
231
+ const providerDefinitions = normalizeServerProviderDefinitions(descriptorEntry.descriptor, descriptorEntry.packageId);
232
+ if (providerDefinitions.length < 1) {
233
+ return Object.freeze([]);
234
+ }
235
+
236
+ const providerClasses = [];
237
+ for (const providerDefinition of providerDefinitions) {
238
+ if (providerDefinition.type === "discover") {
239
+ const discoveredProviderPaths = await collectDiscoveredProviderModulePaths({
240
+ descriptorEntry,
241
+ providerDefinition
242
+ });
243
+
244
+ for (const discoveredProviderPath of discoveredProviderPaths) {
245
+ const providerModule = await import(pathToFileURL(discoveredProviderPath).href);
246
+ providerClasses.push(
247
+ ...resolveProviderClassesFromModule(providerModule, {
248
+ packageId: `${descriptorEntry.packageId} (${normalizeRelativePath(
249
+ descriptorEntry.packageRoot,
250
+ discoveredProviderPath
251
+ )})`,
252
+ providerExport: ""
253
+ })
254
+ );
255
+ }
256
+ continue;
257
+ }
258
+
259
+ const providerModulePath = path.resolve(descriptorEntry.packageRoot, providerDefinition.providerEntrypoint);
260
+ if (!isInsidePackageRoot(descriptorEntry.packageRoot, providerModulePath)) {
261
+ throw new Error(
262
+ `Package ${descriptorEntry.packageId} runtime.server.providers[] entrypoint escapes package root: ${providerDefinition.providerEntrypoint}`
263
+ );
264
+ }
265
+ if (!(await fileExists(providerModulePath))) {
266
+ throw new Error(
267
+ `Package ${descriptorEntry.packageId} runtime.server.providers[] entrypoint not found: ${providerDefinition.providerEntrypoint}`
268
+ );
269
+ }
270
+
271
+ const providerModule = await import(pathToFileURL(providerModulePath).href);
272
+ providerClasses.push(
273
+ ...resolveProviderClassesFromModule(providerModule, {
274
+ packageId: descriptorEntry.packageId,
275
+ providerExport: providerDefinition.providerExport
276
+ })
277
+ );
278
+ }
279
+
280
+ return Object.freeze(providerClasses);
281
+ }
282
+
283
+ export { loadPackageProviders, registerProviderClass };