@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,323 @@
1
+ import { createContainer } from "./container.js";
2
+ import {
3
+ DuplicateProviderError,
4
+ ProviderDependencyError,
5
+ ProviderLifecycleError,
6
+ ProviderNormalizationError
7
+ } from "./kernelErrors.js";
8
+ import { ServiceProvider } from "./serviceProvider.js";
9
+ import { normalizeText } from "../support/normalize.js";
10
+
11
+ function normalizeStringArray(value) {
12
+ if (!Array.isArray(value)) {
13
+ return [];
14
+ }
15
+
16
+ return [...new Set(value.map((entry) => String(entry || "").trim()).filter(Boolean))].sort((left, right) =>
17
+ left.localeCompare(right)
18
+ );
19
+ }
20
+
21
+ function nowMilliseconds() {
22
+ return Date.now();
23
+ }
24
+
25
+ class Application {
26
+ constructor({ profile = "", strict = true, container = null } = {}) {
27
+ this.profile = normalizeText(profile);
28
+ this.strict = strict !== false;
29
+ this.container = container || createContainer({ scopeId: "root" });
30
+
31
+ this.providerEntries = [];
32
+ this.registeredProviders = [];
33
+ this.bootedProviders = [];
34
+
35
+ this.diagnostics = {
36
+ profile: this.profile,
37
+ providerOrder: [],
38
+ registeredOrder: [],
39
+ bootedOrder: [],
40
+ timings: {
41
+ register: {},
42
+ boot: {},
43
+ shutdown: {}
44
+ }
45
+ };
46
+ }
47
+
48
+ bind(token, factory) {
49
+ this.container.bind(token, factory);
50
+ return this;
51
+ }
52
+
53
+ singleton(token, factory) {
54
+ this.container.singleton(token, factory);
55
+ return this;
56
+ }
57
+
58
+ scoped(token, factory) {
59
+ this.container.scoped(token, factory);
60
+ return this;
61
+ }
62
+
63
+ instance(token, value) {
64
+ this.container.instance(token, value);
65
+ return this;
66
+ }
67
+
68
+ make(token) {
69
+ return this.container.make(token);
70
+ }
71
+
72
+ has(token) {
73
+ return this.container.has(token);
74
+ }
75
+
76
+ createScope(scopeId) {
77
+ return this.container.createScope(scopeId);
78
+ }
79
+
80
+ tag(token, tagName) {
81
+ this.container.tag(token, tagName);
82
+ return this;
83
+ }
84
+
85
+ resolveTag(tagName) {
86
+ return this.container.resolveTag(tagName);
87
+ }
88
+
89
+ normalizeProviderEntries(providers = []) {
90
+ if (!Array.isArray(providers)) {
91
+ throw new ProviderNormalizationError("Providers must be an array.");
92
+ }
93
+
94
+ const entries = [];
95
+ const seenIds = new Set();
96
+
97
+ for (const rawProvider of providers) {
98
+ const entry = this.normalizeProviderEntry(rawProvider);
99
+ if (seenIds.has(entry.id)) {
100
+ throw new DuplicateProviderError(`Provider \"${entry.id}\" is duplicated.`);
101
+ }
102
+ seenIds.add(entry.id);
103
+ entries.push(entry);
104
+ }
105
+
106
+ return entries.sort((left, right) => left.id.localeCompare(right.id));
107
+ }
108
+
109
+ normalizeProviderEntry(rawProvider) {
110
+ if (!rawProvider) {
111
+ throw new ProviderNormalizationError("Provider entry is required.");
112
+ }
113
+
114
+ if (typeof rawProvider === "function") {
115
+ const providerInstance = new rawProvider(this);
116
+ const providerId = normalizeText(rawProvider.id || providerInstance.id || rawProvider.name);
117
+ if (!providerId) {
118
+ throw new ProviderNormalizationError("Provider class must define a stable id.");
119
+ }
120
+
121
+ return {
122
+ id: providerId,
123
+ dependsOn: normalizeStringArray(rawProvider.dependsOn || providerInstance.dependsOn),
124
+ provider: providerInstance
125
+ };
126
+ }
127
+
128
+ if (typeof rawProvider === "object") {
129
+ const provider = rawProvider;
130
+ const providerId = normalizeText(provider.id || provider.constructor?.id || provider.constructor?.name);
131
+ if (!providerId) {
132
+ throw new ProviderNormalizationError("Provider object must define id.");
133
+ }
134
+
135
+ return {
136
+ id: providerId,
137
+ dependsOn: normalizeStringArray(provider.dependsOn || provider.constructor?.dependsOn),
138
+ provider
139
+ };
140
+ }
141
+
142
+ throw new ProviderNormalizationError("Provider entry must be a class or object instance.");
143
+ }
144
+
145
+ sortProviderGraph(entries = []) {
146
+ const byId = new Map(entries.map((entry) => [entry.id, entry]));
147
+ const visited = new Set();
148
+ const visiting = new Set();
149
+ const ordered = [];
150
+
151
+ const visit = (providerId, lineage = []) => {
152
+ if (visited.has(providerId)) {
153
+ return;
154
+ }
155
+ if (visiting.has(providerId)) {
156
+ throw new ProviderDependencyError(`Provider dependency cycle detected: ${[...lineage, providerId].join(" -> ")}`);
157
+ }
158
+
159
+ const entry = byId.get(providerId);
160
+ if (!entry) {
161
+ throw new ProviderDependencyError(`Provider \"${lineage[lineage.length - 1] || "<unknown>"}\" depends on missing provider \"${providerId}\".`);
162
+ }
163
+
164
+ visiting.add(providerId);
165
+ for (const dependencyId of entry.dependsOn) {
166
+ visit(dependencyId, [...lineage, providerId]);
167
+ }
168
+ visiting.delete(providerId);
169
+ visited.add(providerId);
170
+ ordered.push(entry);
171
+ };
172
+
173
+ for (const entry of [...entries].sort((left, right) => left.id.localeCompare(right.id))) {
174
+ visit(entry.id, []);
175
+ }
176
+
177
+ return ordered;
178
+ }
179
+
180
+ configureProviders(providers = []) {
181
+ const normalized = this.normalizeProviderEntries(providers);
182
+ const ordered = this.sortProviderGraph(normalized);
183
+
184
+ this.providerEntries = ordered;
185
+ this.diagnostics.providerOrder = ordered.map((entry) => entry.id);
186
+ return this;
187
+ }
188
+
189
+ async registerProviders() {
190
+ this.registeredProviders = [];
191
+
192
+ for (const entry of this.providerEntries) {
193
+ const startedAt = nowMilliseconds();
194
+ try {
195
+ if (typeof entry.provider.register === "function") {
196
+ await entry.provider.register(this);
197
+ }
198
+ } catch (error) {
199
+ throw new ProviderLifecycleError(`Provider \"${entry.id}\" failed during register().`, {
200
+ providerId: entry.id,
201
+ phase: "register",
202
+ cause: error
203
+ });
204
+ } finally {
205
+ this.diagnostics.timings.register[entry.id] = nowMilliseconds() - startedAt;
206
+ }
207
+
208
+ this.registeredProviders.push(entry);
209
+ this.diagnostics.registeredOrder.push(entry.id);
210
+ }
211
+
212
+ return this;
213
+ }
214
+
215
+ async bootProviders() {
216
+ this.bootedProviders = [];
217
+
218
+ for (const entry of this.providerEntries) {
219
+ const startedAt = nowMilliseconds();
220
+ try {
221
+ if (typeof entry.provider.boot === "function") {
222
+ await entry.provider.boot(this);
223
+ }
224
+ } catch (error) {
225
+ throw new ProviderLifecycleError(`Provider \"${entry.id}\" failed during boot().`, {
226
+ providerId: entry.id,
227
+ phase: "boot",
228
+ cause: error
229
+ });
230
+ } finally {
231
+ this.diagnostics.timings.boot[entry.id] = nowMilliseconds() - startedAt;
232
+ }
233
+
234
+ this.bootedProviders.push(entry);
235
+ this.diagnostics.bootedOrder.push(entry.id);
236
+ }
237
+
238
+ return this;
239
+ }
240
+
241
+ async start({ providers = [] } = {}) {
242
+ this.configureProviders(providers);
243
+ await this.registerProviders();
244
+ await this.bootProviders();
245
+ return this;
246
+ }
247
+
248
+ async shutdown() {
249
+ const shutdownOrder = [...this.bootedProviders].reverse();
250
+
251
+ for (const entry of shutdownOrder) {
252
+ const startedAt = nowMilliseconds();
253
+ try {
254
+ if (typeof entry.provider.shutdown === "function") {
255
+ await entry.provider.shutdown(this);
256
+ }
257
+ } catch (error) {
258
+ throw new ProviderLifecycleError(`Provider \"${entry.id}\" failed during shutdown().`, {
259
+ providerId: entry.id,
260
+ phase: "shutdown",
261
+ cause: error
262
+ });
263
+ } finally {
264
+ this.diagnostics.timings.shutdown[entry.id] = nowMilliseconds() - startedAt;
265
+ }
266
+ }
267
+
268
+ return shutdownOrder.map((entry) => entry.id);
269
+ }
270
+
271
+ getDiagnostics() {
272
+ return Object.freeze({
273
+ profile: this.diagnostics.profile,
274
+ providerOrder: Object.freeze([...this.diagnostics.providerOrder]),
275
+ registeredOrder: Object.freeze([...this.diagnostics.registeredOrder]),
276
+ bootedOrder: Object.freeze([...this.diagnostics.bootedOrder]),
277
+ timings: Object.freeze({
278
+ register: Object.freeze({ ...this.diagnostics.timings.register }),
279
+ boot: Object.freeze({ ...this.diagnostics.timings.boot }),
280
+ shutdown: Object.freeze({ ...this.diagnostics.timings.shutdown })
281
+ })
282
+ });
283
+ }
284
+ }
285
+
286
+ function createApplication(options = {}) {
287
+ return new Application(options);
288
+ }
289
+
290
+ function createProviderClass({ id, dependsOn = [], register = null, boot = null, shutdown = null } = {}) {
291
+ const providerId = normalizeText(id);
292
+ if (!providerId) {
293
+ throw new ProviderNormalizationError("createProviderClass requires id.");
294
+ }
295
+
296
+ class DynamicProvider extends ServiceProvider {
297
+ static id = providerId;
298
+
299
+ static dependsOn = normalizeStringArray(dependsOn);
300
+
301
+ async register(app) {
302
+ if (typeof register === "function") {
303
+ await register(app);
304
+ }
305
+ }
306
+
307
+ async boot(app) {
308
+ if (typeof boot === "function") {
309
+ await boot(app);
310
+ }
311
+ }
312
+
313
+ async shutdown(app) {
314
+ if (typeof shutdown === "function") {
315
+ await shutdown(app);
316
+ }
317
+ }
318
+ }
319
+
320
+ return DynamicProvider;
321
+ }
322
+
323
+ export { Application, createApplication, createProviderClass };
@@ -0,0 +1,261 @@
1
+ import {
2
+ CircularDependencyError,
3
+ DuplicateBindingError,
4
+ InvalidFactoryError,
5
+ InvalidTokenError,
6
+ UnresolvedTokenError
7
+ } from "./containerErrors.js";
8
+
9
+ const LIFETIME_TRANSIENT = "transient";
10
+ const LIFETIME_SINGLETON = "singleton";
11
+ const LIFETIME_SCOPED = "scoped";
12
+
13
+ function normalizeToken(token) {
14
+ const kind = typeof token;
15
+ if (kind === "string") {
16
+ const normalized = token.trim();
17
+ if (!normalized) {
18
+ throw new InvalidTokenError("Container token string cannot be empty.");
19
+ }
20
+ return normalized;
21
+ }
22
+
23
+ if (kind === "symbol" || kind === "function") {
24
+ return token;
25
+ }
26
+
27
+ throw new InvalidTokenError("Container token must be a non-empty string, symbol, or function.", {
28
+ receivedType: kind
29
+ });
30
+ }
31
+
32
+ function tokenLabel(token) {
33
+ if (typeof token === "string") {
34
+ return token;
35
+ }
36
+
37
+ if (typeof token === "symbol") {
38
+ return token.description ? `Symbol(${token.description})` : String(token);
39
+ }
40
+
41
+ if (typeof token === "function") {
42
+ return token.name ? `Function(${token.name})` : "Function(<anonymous>)";
43
+ }
44
+
45
+ return String(token);
46
+ }
47
+
48
+ function ensureFactory(factory, token) {
49
+ if (typeof factory !== "function") {
50
+ throw new InvalidFactoryError(`Factory for token \"${tokenLabel(token)}\" must be a function.`);
51
+ }
52
+ }
53
+
54
+ function normalizeTagName(tagName) {
55
+ const normalized = String(tagName || "").trim();
56
+ if (!normalized) {
57
+ throw new TypeError("Tag name is required.");
58
+ }
59
+ return normalized;
60
+ }
61
+
62
+ function normalizeScopeId(scopeId) {
63
+ const normalized = String(scopeId || "").trim();
64
+ return normalized || "scope";
65
+ }
66
+
67
+ class Container {
68
+ constructor({ parent = null, scopeId = "root" } = {}) {
69
+ this.parent = parent instanceof Container ? parent : null;
70
+ this.scopeId = normalizeScopeId(scopeId);
71
+ this.bindings = new Map();
72
+ this.instances = new Map();
73
+ this.scopedInstances = new Map();
74
+
75
+ if (!this.parent) {
76
+ this.tags = new Map();
77
+ this.resolutionStack = [];
78
+ }
79
+ }
80
+
81
+ root() {
82
+ let node = this;
83
+ while (node.parent) {
84
+ node = node.parent;
85
+ }
86
+ return node;
87
+ }
88
+
89
+ has(token) {
90
+ const normalizedToken = normalizeToken(token);
91
+ return this.findBindingRecord(normalizedToken) !== null || this.findInstanceRecord(normalizedToken) !== null;
92
+ }
93
+
94
+ bind(token, factory) {
95
+ return this.setBinding(token, factory, LIFETIME_TRANSIENT);
96
+ }
97
+
98
+ singleton(token, factory) {
99
+ return this.setBinding(token, factory, LIFETIME_SINGLETON);
100
+ }
101
+
102
+ scoped(token, factory) {
103
+ return this.setBinding(token, factory, LIFETIME_SCOPED);
104
+ }
105
+
106
+ instance(token, value) {
107
+ const normalizedToken = normalizeToken(token);
108
+ if (this.bindings.has(normalizedToken) || this.instances.has(normalizedToken)) {
109
+ throw new DuplicateBindingError(`Token \"${tokenLabel(normalizedToken)}\" is already bound.`);
110
+ }
111
+
112
+ this.instances.set(normalizedToken, value);
113
+ return this;
114
+ }
115
+
116
+ tag(token, tagName) {
117
+ const normalizedToken = normalizeToken(token);
118
+ const normalizedTagName = normalizeTagName(tagName);
119
+
120
+ if (!this.has(normalizedToken)) {
121
+ throw new UnresolvedTokenError(`Cannot tag unresolved token \"${tokenLabel(normalizedToken)}\".`);
122
+ }
123
+
124
+ const rootContainer = this.root();
125
+ if (!rootContainer.tags.has(normalizedTagName)) {
126
+ rootContainer.tags.set(normalizedTagName, new Set());
127
+ }
128
+ rootContainer.tags.get(normalizedTagName).add(normalizedToken);
129
+ return this;
130
+ }
131
+
132
+ resolveTag(tagName) {
133
+ const normalizedTagName = normalizeTagName(tagName);
134
+ const rootContainer = this.root();
135
+ const tokens = rootContainer.tags.get(normalizedTagName);
136
+ if (!tokens || tokens.size < 1) {
137
+ return [];
138
+ }
139
+
140
+ return [...tokens]
141
+ .sort((left, right) => tokenLabel(left).localeCompare(tokenLabel(right)))
142
+ .map((token) => this.make(token));
143
+ }
144
+
145
+ createScope(scopeId = "scope") {
146
+ return new Container({
147
+ parent: this,
148
+ scopeId: normalizeScopeId(scopeId)
149
+ });
150
+ }
151
+
152
+ make(token) {
153
+ const normalizedToken = normalizeToken(token);
154
+ const instanceRecord = this.findInstanceRecord(normalizedToken);
155
+ if (instanceRecord) {
156
+ return instanceRecord.value;
157
+ }
158
+
159
+ const bindingRecord = this.findBindingRecord(normalizedToken);
160
+ if (!bindingRecord) {
161
+ throw new UnresolvedTokenError(`Token \"${tokenLabel(normalizedToken)}\" is not registered.`);
162
+ }
163
+
164
+ const rootContainer = this.root();
165
+ const stack = rootContainer.resolutionStack;
166
+ const stackIndex = stack.indexOf(normalizedToken);
167
+ if (stackIndex >= 0) {
168
+ const cycle = stack
169
+ .slice(stackIndex)
170
+ .concat([normalizedToken])
171
+ .map((entry) => tokenLabel(entry));
172
+ throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")}.`, {
173
+ cycle
174
+ });
175
+ }
176
+
177
+ stack.push(normalizedToken);
178
+ try {
179
+ return this.resolveFromBindingRecord(bindingRecord);
180
+ } finally {
181
+ stack.pop();
182
+ }
183
+ }
184
+
185
+ setBinding(token, factory, lifetime) {
186
+ const normalizedToken = normalizeToken(token);
187
+ ensureFactory(factory, normalizedToken);
188
+
189
+ if (this.bindings.has(normalizedToken) || this.instances.has(normalizedToken)) {
190
+ throw new DuplicateBindingError(`Token \"${tokenLabel(normalizedToken)}\" is already bound.`);
191
+ }
192
+
193
+ this.bindings.set(normalizedToken, {
194
+ token: normalizedToken,
195
+ factory,
196
+ lifetime
197
+ });
198
+
199
+ return this;
200
+ }
201
+
202
+ findBindingRecord(token) {
203
+ let node = this;
204
+ while (node) {
205
+ if (node.bindings.has(token)) {
206
+ return {
207
+ container: node,
208
+ binding: node.bindings.get(token)
209
+ };
210
+ }
211
+ node = node.parent;
212
+ }
213
+
214
+ return null;
215
+ }
216
+
217
+ findInstanceRecord(token) {
218
+ let node = this;
219
+ while (node) {
220
+ if (node.instances.has(token)) {
221
+ return {
222
+ container: node,
223
+ value: node.instances.get(token)
224
+ };
225
+ }
226
+ node = node.parent;
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ resolveFromBindingRecord(record) {
233
+ const { container, binding } = record;
234
+
235
+ if (binding.lifetime === LIFETIME_SINGLETON) {
236
+ if (container.instances.has(binding.token)) {
237
+ return container.instances.get(binding.token);
238
+ }
239
+ const created = binding.factory(this);
240
+ container.instances.set(binding.token, created);
241
+ return created;
242
+ }
243
+
244
+ if (binding.lifetime === LIFETIME_SCOPED) {
245
+ if (this.scopedInstances.has(binding.token)) {
246
+ return this.scopedInstances.get(binding.token);
247
+ }
248
+ const created = binding.factory(this);
249
+ this.scopedInstances.set(binding.token, created);
250
+ return created;
251
+ }
252
+
253
+ return binding.factory(this);
254
+ }
255
+ }
256
+
257
+ function createContainer(options = {}) {
258
+ return new Container(options);
259
+ }
260
+
261
+ export { Container, createContainer, tokenLabel };
@@ -0,0 +1,22 @@
1
+ class ContainerError extends Error {
2
+ constructor(message, details = {}) {
3
+ super(String(message || "Container error."));
4
+ this.name = this.constructor.name;
5
+ this.details = details && typeof details === "object" ? { ...details } : {};
6
+ }
7
+ }
8
+
9
+ class InvalidTokenError extends ContainerError {}
10
+ class InvalidFactoryError extends ContainerError {}
11
+ class DuplicateBindingError extends ContainerError {}
12
+ class UnresolvedTokenError extends ContainerError {}
13
+ class CircularDependencyError extends ContainerError {}
14
+
15
+ export {
16
+ ContainerError,
17
+ InvalidTokenError,
18
+ InvalidFactoryError,
19
+ DuplicateBindingError,
20
+ UnresolvedTokenError,
21
+ CircularDependencyError
22
+ };
@@ -0,0 +1,18 @@
1
+ export { Container, createContainer, tokenLabel } from "./container.js";
2
+ export {
3
+ ContainerError,
4
+ InvalidTokenError,
5
+ InvalidFactoryError,
6
+ DuplicateBindingError,
7
+ UnresolvedTokenError,
8
+ CircularDependencyError
9
+ } from "./containerErrors.js";
10
+ export { Application, createApplication, createProviderClass } from "./application.js";
11
+ export { ServiceProvider } from "./serviceProvider.js";
12
+ export {
13
+ KernelError,
14
+ ProviderNormalizationError,
15
+ DuplicateProviderError,
16
+ ProviderDependencyError,
17
+ ProviderLifecycleError
18
+ } from "./kernelErrors.js";
@@ -0,0 +1,20 @@
1
+ class KernelError extends Error {
2
+ constructor(message, details = {}) {
3
+ super(String(message || "Kernel error."));
4
+ this.name = this.constructor.name;
5
+ this.details = details && typeof details === "object" ? { ...details } : {};
6
+ }
7
+ }
8
+
9
+ class ProviderNormalizationError extends KernelError {}
10
+ class DuplicateProviderError extends KernelError {}
11
+ class ProviderDependencyError extends KernelError {}
12
+ class ProviderLifecycleError extends KernelError {}
13
+
14
+ export {
15
+ KernelError,
16
+ ProviderNormalizationError,
17
+ DuplicateProviderError,
18
+ ProviderDependencyError,
19
+ ProviderLifecycleError
20
+ };
@@ -0,0 +1,13 @@
1
+ class ServiceProvider {
2
+ constructor(app) {
3
+ this.app = app;
4
+ }
5
+
6
+ register() {}
7
+
8
+ boot() {}
9
+
10
+ shutdown() {}
11
+ }
12
+
13
+ export { ServiceProvider };
@@ -0,0 +1,10 @@
1
+ function formatDateTime(value, { fallback = "unknown" } = {}) {
2
+ const parsedDate = new Date(value);
3
+ if (Number.isNaN(parsedDate.getTime())) {
4
+ return fallback;
5
+ }
6
+
7
+ return parsedDate.toLocaleString();
8
+ }
9
+
10
+ export { formatDateTime };
@@ -0,0 +1,15 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { formatDateTime } from "./formatDateTime.js";
4
+
5
+ test("formatDateTime returns fallback for invalid values", () => {
6
+ assert.equal(formatDateTime("not-a-date"), "unknown");
7
+ assert.equal(formatDateTime("not-a-date", { fallback: "n/a" }), "n/a");
8
+ });
9
+
10
+ test("formatDateTime formats valid date-like values", () => {
11
+ const formatted = formatDateTime("2026-03-14T12:34:56.000Z");
12
+ assert.equal(typeof formatted, "string");
13
+ assert.ok(formatted.length > 0);
14
+ assert.notEqual(formatted, "unknown");
15
+ });
@@ -0,0 +1,14 @@
1
+ export { isRecord } from "./normalize.js";
2
+ export { pickOwnProperties } from "./pickOwnProperties.js";
3
+ export { formatDateTime } from "./formatDateTime.js";
4
+ export { appendQueryString } from "./queryPath.js";
5
+ export { normalizePermissionList, hasPermission } from "./permissions.js";
6
+ export {
7
+ normalizeReturnToPath,
8
+ resolveAllowedOriginsFromPlacementContext
9
+ } from "./returnToPath.js";
10
+ export {
11
+ isTransientQueryError,
12
+ shouldRetryTransientQueryFailure,
13
+ transientQueryRetryDelay
14
+ } from "./queryResilience.js";