@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,149 @@
1
+ import { normalizeArray, normalizeText } from "../../../shared/support/normalize.js";
2
+ import { RouteRegistrationError } from "./errors.js";
3
+ import { normalizeMiddlewareEntry, resolveRouteLabel } from "./routeSupport.js";
4
+
5
+ async function executeMiddlewareStack(middleware, request, reply) {
6
+ for (const handler of normalizeArray(middleware)) {
7
+ await handler(request, reply);
8
+ if (reply?.sent) {
9
+ return;
10
+ }
11
+ }
12
+ }
13
+
14
+ function normalizeMiddlewareName(value) {
15
+ return normalizeText(value);
16
+ }
17
+
18
+ function normalizeMiddlewareAliases(sourceAliases) {
19
+ const source = sourceAliases && typeof sourceAliases === "object" && !Array.isArray(sourceAliases) ? sourceAliases : {};
20
+ const aliases = new Map();
21
+
22
+ for (const [rawName, handler] of Object.entries(source)) {
23
+ const name = normalizeMiddlewareName(rawName);
24
+ if (!name) {
25
+ continue;
26
+ }
27
+
28
+ if (typeof handler !== "function") {
29
+ throw new RouteRegistrationError(`middleware.aliases["${name}"] must be a function.`);
30
+ }
31
+
32
+ aliases.set(name, handler);
33
+ }
34
+
35
+ return aliases;
36
+ }
37
+
38
+ function normalizeMiddlewareGroups(sourceGroups) {
39
+ const source = sourceGroups && typeof sourceGroups === "object" && !Array.isArray(sourceGroups) ? sourceGroups : {};
40
+ const groups = new Map();
41
+
42
+ for (const [rawName, entries] of Object.entries(source)) {
43
+ const name = normalizeMiddlewareName(rawName);
44
+ if (!name) {
45
+ continue;
46
+ }
47
+
48
+ const normalizedEntries = normalizeArray(entries).map((entry, index) =>
49
+ normalizeMiddlewareEntry(entry, {
50
+ context: `middleware.groups["${name}"]`,
51
+ index,
52
+ ErrorType: RouteRegistrationError
53
+ })
54
+ );
55
+
56
+ groups.set(name, Object.freeze(normalizedEntries));
57
+ }
58
+
59
+ return groups;
60
+ }
61
+
62
+ function normalizeRuntimeMiddlewareConfig(runtimeMiddleware) {
63
+ const source = runtimeMiddleware && typeof runtimeMiddleware === "object" && !Array.isArray(runtimeMiddleware) ? runtimeMiddleware : {};
64
+ const aliases = normalizeMiddlewareAliases(source.aliases);
65
+ const groups = normalizeMiddlewareGroups(source.groups);
66
+
67
+ for (const groupName of groups.keys()) {
68
+ if (aliases.has(groupName)) {
69
+ throw new RouteRegistrationError(`middleware name "${groupName}" cannot be both an alias and a group.`);
70
+ }
71
+ }
72
+
73
+ return {
74
+ aliases,
75
+ groups
76
+ };
77
+ }
78
+
79
+ function expandMiddlewareEntry({
80
+ entry,
81
+ runtimeMiddlewareConfig,
82
+ resolvedHandlers,
83
+ groupStack,
84
+ routeLabel
85
+ }) {
86
+ if (typeof entry === "function") {
87
+ resolvedHandlers.push(entry);
88
+ return;
89
+ }
90
+
91
+ const name = normalizeMiddlewareName(entry);
92
+ if (!name) {
93
+ throw new RouteRegistrationError(`Route ${routeLabel} middleware entries must be functions or non-empty strings.`);
94
+ }
95
+
96
+ if (runtimeMiddlewareConfig.aliases.has(name)) {
97
+ resolvedHandlers.push(runtimeMiddlewareConfig.aliases.get(name));
98
+ return;
99
+ }
100
+
101
+ if (runtimeMiddlewareConfig.groups.has(name)) {
102
+ if (groupStack.includes(name)) {
103
+ const cycle = [...groupStack, name].join(" -> ");
104
+ throw new RouteRegistrationError(`Route ${routeLabel} middleware group cycle detected: ${cycle}.`);
105
+ }
106
+
107
+ const nextGroupStack = [...groupStack, name];
108
+ const groupEntries = runtimeMiddlewareConfig.groups.get(name);
109
+ for (const groupEntry of groupEntries) {
110
+ expandMiddlewareEntry({
111
+ entry: groupEntry,
112
+ runtimeMiddlewareConfig,
113
+ resolvedHandlers,
114
+ groupStack: nextGroupStack,
115
+ routeLabel
116
+ });
117
+ }
118
+ return;
119
+ }
120
+
121
+ throw new RouteRegistrationError(
122
+ `Route ${routeLabel} references unknown middleware "${name}". Define it under middleware.aliases or middleware.groups.`
123
+ );
124
+ }
125
+
126
+ function resolveRouteMiddlewareHandlers(route, runtimeMiddlewareConfig) {
127
+ const routeLabel = resolveRouteLabel(route);
128
+ const sourceEntries = normalizeArray(route?.middleware).map((entry, index) =>
129
+ normalizeMiddlewareEntry(entry, {
130
+ context: `Route ${routeLabel} middleware`,
131
+ index
132
+ })
133
+ );
134
+
135
+ const resolvedHandlers = [];
136
+ for (const entry of sourceEntries) {
137
+ expandMiddlewareEntry({
138
+ entry,
139
+ runtimeMiddlewareConfig,
140
+ resolvedHandlers,
141
+ groupStack: [],
142
+ routeLabel
143
+ });
144
+ }
145
+
146
+ return Object.freeze(resolvedHandlers);
147
+ }
148
+
149
+ export { executeMiddlewareStack, normalizeRuntimeMiddlewareConfig, resolveRouteMiddlewareHandlers };
@@ -0,0 +1,258 @@
1
+ import { normalizeObject, normalizeText } from "../../../shared/support/normalize.js";
2
+ import { normalizeRouteVisibilityToken } from "../../../shared/support/visibility.js";
3
+ import { resolveActionContextContributors } from "../../actions/ActionRuntimeServiceProvider.js";
4
+ import { resolveRouteVisibilityContext } from "../../registries/routeVisibilityResolverRegistry.js";
5
+ import { resolveDefaultSurfaceId } from "../../support/appConfig.js";
6
+ import { RouteRegistrationError } from "./errors.js";
7
+ import { normalizeRequestScopeProperty } from "./requestScope.js";
8
+
9
+ function normalizeRequestActionExecutorProperty(value) {
10
+ const normalized = String(value || "").trim();
11
+ return normalized || "executeAction";
12
+ }
13
+
14
+ function resolveSurfaceFromRequest(request, explicitSurface = "", defaultSurfaceId = "") {
15
+ const normalizedExplicitSurface = normalizeText(explicitSurface).toLowerCase();
16
+ if (normalizedExplicitSurface) {
17
+ return normalizedExplicitSurface;
18
+ }
19
+
20
+ const normalizedRouteSurface = normalizeText(request?.routeOptions?.config?.surface).toLowerCase();
21
+ if (normalizedRouteSurface) {
22
+ return normalizedRouteSurface;
23
+ }
24
+
25
+ const normalizedRequestSurface = normalizeText(request?.surface).toLowerCase();
26
+ if (normalizedRequestSurface) {
27
+ return normalizedRequestSurface;
28
+ }
29
+
30
+ return resolveDefaultSurfaceId(null, {
31
+ defaultSurfaceId
32
+ });
33
+ }
34
+
35
+ function buildActionExecutionContext({ request = null, context = {}, channel = "api", defaultSurfaceId = "" } = {}) {
36
+ const source = normalizeObject(context);
37
+ const sourceRequestMeta = normalizeObject(source.requestMeta);
38
+
39
+ return {
40
+ ...source,
41
+ channel: normalizeText(source.channel || channel).toLowerCase() || "api",
42
+ surface: resolveSurfaceFromRequest(request, source.surface, defaultSurfaceId),
43
+ requestMeta: {
44
+ ...sourceRequestMeta,
45
+ request
46
+ }
47
+ };
48
+ }
49
+
50
+ function applyActionContextContributionDefaults(targetContext, contribution) {
51
+ const patch = normalizeObject(contribution);
52
+ if (Object.keys(patch).length < 1) {
53
+ return targetContext;
54
+ }
55
+
56
+ for (const [key, value] of Object.entries(patch)) {
57
+ if (key === "requestMeta") {
58
+ continue;
59
+ }
60
+ if (Object.prototype.hasOwnProperty.call(targetContext, key)) {
61
+ continue;
62
+ }
63
+ targetContext[key] = value;
64
+ }
65
+
66
+ if (Object.prototype.hasOwnProperty.call(patch, "requestMeta")) {
67
+ const targetRequestMeta = normalizeObject(targetContext.requestMeta);
68
+ const patchRequestMeta = normalizeObject(patch.requestMeta);
69
+ for (const [key, value] of Object.entries(patchRequestMeta)) {
70
+ if (Object.prototype.hasOwnProperty.call(targetRequestMeta, key)) {
71
+ continue;
72
+ }
73
+ targetRequestMeta[key] = value;
74
+ }
75
+ targetContext.requestMeta = targetRequestMeta;
76
+ }
77
+
78
+ return targetContext;
79
+ }
80
+
81
+ async function enrichActionExecutionContext({
82
+ resolutionScope = null,
83
+ request = null,
84
+ actionId = "",
85
+ version = null,
86
+ input = {},
87
+ deps = {},
88
+ channel = "api",
89
+ baseContext = {}
90
+ } = {}) {
91
+ const contributors = resolveActionContextContributors(resolutionScope);
92
+ if (contributors.length < 1) {
93
+ return baseContext;
94
+ }
95
+
96
+ const normalizedActionId = normalizeText(actionId);
97
+ const normalizedChannel = normalizeText(channel).toLowerCase() || "api";
98
+ const normalizedInput = normalizeObject(input);
99
+ const normalizedDeps = normalizeObject(deps);
100
+ const mutableContext = normalizeObject(baseContext);
101
+
102
+ for (const contributor of contributors) {
103
+ if (!contributor || typeof contributor.contribute !== "function") {
104
+ continue;
105
+ }
106
+
107
+ const contribution = await contributor.contribute({
108
+ request,
109
+ actionId: normalizedActionId,
110
+ version: version == null ? null : version,
111
+ input: normalizedInput,
112
+ deps: normalizedDeps,
113
+ channel: normalizedChannel,
114
+ surface: mutableContext.surface,
115
+ context: { ...mutableContext }
116
+ });
117
+
118
+ applyActionContextContributionDefaults(mutableContext, contribution);
119
+ }
120
+
121
+ return mutableContext;
122
+ }
123
+
124
+ function resolveActionExecutorScope({ app = null, request = null, requestScopeProperty = "scope" } = {}) {
125
+ const normalizedScopeProperty = normalizeRequestScopeProperty(requestScopeProperty);
126
+ const requestScope =
127
+ request && typeof request === "object" && request[normalizedScopeProperty] && typeof request[normalizedScopeProperty] === "object"
128
+ ? request[normalizedScopeProperty]
129
+ : null;
130
+
131
+ if (requestScope && typeof requestScope.make === "function") {
132
+ return requestScope;
133
+ }
134
+
135
+ if (app && typeof app.make === "function") {
136
+ return app;
137
+ }
138
+
139
+ return null;
140
+ }
141
+
142
+ function resolveRouteVisibilityFromRequestAndPayload(request, payload = {}) {
143
+ const routeConfig =
144
+ request?.routeOptions?.config && typeof request.routeOptions.config === "object" ? request.routeOptions.config : null;
145
+ if (routeConfig && Object.prototype.hasOwnProperty.call(routeConfig, "visibility")) {
146
+ return normalizeRouteVisibilityToken(routeConfig.visibility);
147
+ }
148
+
149
+ return normalizeRouteVisibilityToken(payload.visibility);
150
+ }
151
+
152
+ function attachRequestActionExecutor({
153
+ app = null,
154
+ request = null,
155
+ requestScopeProperty = "scope",
156
+ requestActionExecutorProperty = "executeAction",
157
+ actionExecutorToken = "actionExecutor",
158
+ defaultChannel = "api",
159
+ defaultSurfaceId = ""
160
+ } = {}) {
161
+ if (!request || typeof request !== "object") {
162
+ return null;
163
+ }
164
+
165
+ const normalizedProperty = normalizeRequestActionExecutorProperty(requestActionExecutorProperty);
166
+ if (typeof request[normalizedProperty] === "function") {
167
+ return request[normalizedProperty];
168
+ }
169
+
170
+ const normalizedActionExecutorToken = normalizeText(actionExecutorToken) || "actionExecutor";
171
+ const normalizedDefaultChannel = normalizeText(defaultChannel).toLowerCase() || "api";
172
+ const initialResolutionScope = resolveActionExecutorScope({
173
+ app,
174
+ request,
175
+ requestScopeProperty
176
+ });
177
+
178
+ if (!initialResolutionScope || typeof initialResolutionScope.has !== "function" || typeof initialResolutionScope.make !== "function") {
179
+ return null;
180
+ }
181
+
182
+ const executeAction = async (payload = {}) => {
183
+ const source = normalizeObject(payload);
184
+ const normalizedInput = normalizeObject(source.input);
185
+ const normalizedDeps = normalizeObject(source.deps);
186
+ const normalizedChannel = normalizeText(source.channel || normalizedDefaultChannel).toLowerCase() || normalizedDefaultChannel;
187
+ const resolutionScope = resolveActionExecutorScope({
188
+ app,
189
+ request,
190
+ requestScopeProperty
191
+ });
192
+
193
+ if (!resolutionScope || typeof resolutionScope.has !== "function" || typeof resolutionScope.make !== "function") {
194
+ throw new RouteRegistrationError("request.executeAction requires a container scope with has()/make().");
195
+ }
196
+ if (!resolutionScope.has(normalizedActionExecutorToken)) {
197
+ throw new RouteRegistrationError(`request.executeAction requires "${normalizedActionExecutorToken}" binding.`);
198
+ }
199
+
200
+ const actionExecutor = resolutionScope.make(normalizedActionExecutorToken);
201
+ if (!actionExecutor || typeof actionExecutor.execute !== "function") {
202
+ throw new RouteRegistrationError(`"${normalizedActionExecutorToken}" must provide execute().`);
203
+ }
204
+
205
+ const baseContext = buildActionExecutionContext({
206
+ request,
207
+ context: normalizeObject(source.context),
208
+ channel: normalizedChannel,
209
+ defaultSurfaceId
210
+ });
211
+ const executionContext = await enrichActionExecutionContext({
212
+ resolutionScope,
213
+ request,
214
+ actionId: source.actionId,
215
+ version: source.version == null ? null : source.version,
216
+ input: normalizedInput,
217
+ deps: normalizedDeps,
218
+ channel: normalizedChannel,
219
+ baseContext
220
+ });
221
+ const visibilityContext = await resolveRouteVisibilityContext({
222
+ resolutionScope,
223
+ request,
224
+ routeVisibility: resolveRouteVisibilityFromRequestAndPayload(request, source),
225
+ context: executionContext,
226
+ input: normalizedInput,
227
+ deps: normalizedDeps,
228
+ actionId: source.actionId,
229
+ version: source.version == null ? null : source.version,
230
+ channel: normalizedChannel
231
+ });
232
+ executionContext.visibilityContext = visibilityContext;
233
+ executionContext.requestMeta = {
234
+ ...normalizeObject(executionContext.requestMeta),
235
+ visibilityContext,
236
+ routeVisibility: visibilityContext.visibility
237
+ };
238
+
239
+ return actionExecutor.execute({
240
+ actionId: source.actionId,
241
+ version: source.version == null ? null : source.version,
242
+ input: normalizedInput,
243
+ context: executionContext,
244
+ deps: normalizedDeps
245
+ });
246
+ };
247
+
248
+ Object.defineProperty(request, normalizedProperty, {
249
+ configurable: true,
250
+ enumerable: false,
251
+ writable: true,
252
+ value: executeAction
253
+ });
254
+
255
+ return request[normalizedProperty];
256
+ }
257
+
258
+ export { buildActionExecutionContext, attachRequestActionExecutor };
@@ -0,0 +1,59 @@
1
+ import { KERNEL_TOKENS } from "../../../shared/support/tokens.js";
2
+
3
+ function resolveRequestRuntimeId({ request = null, requestIdResolver = null } = {}) {
4
+ if (typeof requestIdResolver === "function") {
5
+ const resolvedByResolver = String(requestIdResolver(request) || "").trim();
6
+ if (resolvedByResolver) {
7
+ return resolvedByResolver;
8
+ }
9
+ }
10
+
11
+ const resolvedFromRequest = String(request?.id || "").trim();
12
+ if (resolvedFromRequest) {
13
+ return resolvedFromRequest;
14
+ }
15
+
16
+ return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
17
+ }
18
+
19
+ function normalizeRequestScopeProperty(value) {
20
+ const normalized = String(value || "").trim();
21
+ return normalized || "scope";
22
+ }
23
+
24
+ function attachRequestScope({
25
+ app = null,
26
+ request = null,
27
+ reply = null,
28
+ requestScopeProperty = "scope",
29
+ requestScopeIdPrefix = "http",
30
+ requestIdResolver = null
31
+ } = {}) {
32
+ if (!app || typeof app.createScope !== "function") {
33
+ return null;
34
+ }
35
+
36
+ const runtimeRequestId = resolveRequestRuntimeId({
37
+ request,
38
+ requestIdResolver
39
+ });
40
+
41
+ const scopePrefix = String(requestScopeIdPrefix || "").trim() || "http";
42
+ const scope = app.createScope(`${scopePrefix}:${runtimeRequestId}`);
43
+ if (!scope || typeof scope.instance !== "function") {
44
+ return null;
45
+ }
46
+
47
+ scope.instance(KERNEL_TOKENS.Request, request);
48
+ scope.instance(KERNEL_TOKENS.Reply, reply);
49
+ scope.instance(KERNEL_TOKENS.RequestId, runtimeRequestId);
50
+ scope.instance(KERNEL_TOKENS.RequestScope, scope);
51
+
52
+ if (request && typeof request === "object") {
53
+ request[normalizeRequestScopeProperty(requestScopeProperty)] = scope;
54
+ }
55
+
56
+ return scope;
57
+ }
58
+
59
+ export { normalizeRequestScopeProperty, attachRequestScope };
@@ -0,0 +1,165 @@
1
+ import { normalizeArray, normalizeObject } from "../../../shared/support/normalize.js";
2
+ import { defaultApplyRoutePolicy } from "../../support/routePolicyConfig.js";
3
+ import { resolveDefaultSurfaceId } from "../../support/appConfig.js";
4
+ import { defaultMissingHandler } from "../../support/defaultMissingHandler.js";
5
+ import { RouteRegistrationError } from "./errors.js";
6
+ import { executeMiddlewareStack, normalizeRuntimeMiddlewareConfig, resolveRouteMiddlewareHandlers } from "./middlewareRuntime.js";
7
+ import { attachRequestScope } from "./requestScope.js";
8
+ import { attachRequestActionExecutor } from "./requestActionExecutor.js";
9
+
10
+ const { structuredClone: cloneRouteSchema } = globalThis;
11
+
12
+ function toFastifyRouteOptions(route) {
13
+ const sourceRoute = normalizeObject(route);
14
+ const schema = cloneRouteSchema(sourceRoute.schema);
15
+ return {
16
+ method: sourceRoute.method,
17
+ url: sourceRoute.path,
18
+ ...(schema ? { schema } : {}),
19
+ ...(sourceRoute.bodyLimit ? { bodyLimit: sourceRoute.bodyLimit } : {}),
20
+ config: {
21
+ ...(sourceRoute.config || {})
22
+ }
23
+ };
24
+ }
25
+
26
+ function normalizeRouteInputTransforms(route) {
27
+ const routeInput = route?.input;
28
+ if (routeInput == null) {
29
+ return null;
30
+ }
31
+
32
+ if (!routeInput || typeof routeInput !== "object" || Array.isArray(routeInput)) {
33
+ throw new RouteRegistrationError(
34
+ `Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} input must be an object.`
35
+ );
36
+ }
37
+
38
+ const normalized = {};
39
+ for (const key of ["body", "query", "params"]) {
40
+ if (!Object.prototype.hasOwnProperty.call(routeInput, key)) {
41
+ continue;
42
+ }
43
+
44
+ const transform = routeInput[key];
45
+ if (transform == null) {
46
+ continue;
47
+ }
48
+
49
+ if (typeof transform !== "function") {
50
+ throw new RouteRegistrationError(
51
+ `Route ${String(route?.method || "<unknown>")} ${String(route?.path || "<unknown>")} input.${key} must be a function.`
52
+ );
53
+ }
54
+
55
+ normalized[key] = transform;
56
+ }
57
+
58
+ return Object.freeze(normalized);
59
+ }
60
+
61
+ function buildRequestInput({ request = null, inputTransforms = null } = {}) {
62
+ const transforms = inputTransforms && typeof inputTransforms === "object" ? inputTransforms : {};
63
+
64
+ const body = typeof transforms.body === "function" ? transforms.body(request?.body, request) : request?.body;
65
+ const query = typeof transforms.query === "function" ? transforms.query(request?.query, request) : request?.query;
66
+ const params = typeof transforms.params === "function" ? transforms.params(request?.params, request) : request?.params;
67
+
68
+ return Object.freeze({
69
+ body,
70
+ query,
71
+ params
72
+ });
73
+ }
74
+
75
+ function registerRoutes(
76
+ fastify,
77
+ {
78
+ routes = [],
79
+ app = null,
80
+ applyRoutePolicy = defaultApplyRoutePolicy,
81
+ missingHandler = defaultMissingHandler,
82
+ enableRequestScope = true,
83
+ requestScopeProperty = "scope",
84
+ requestActionExecutorProperty = "executeAction",
85
+ actionExecutorToken = "actionExecutor",
86
+ requestActionDefaultChannel = "api",
87
+ requestActionDefaultSurface = "",
88
+ requestScopeIdPrefix = "http",
89
+ requestIdResolver = null,
90
+ middleware = {}
91
+ } = {}
92
+ ) {
93
+ if (!fastify || typeof fastify.route !== "function") {
94
+ throw new RouteRegistrationError("registerRoutes requires a Fastify instance.");
95
+ }
96
+
97
+ const normalizedRoutes = normalizeArray(routes);
98
+ const policyApplier = typeof applyRoutePolicy === "function" ? applyRoutePolicy : defaultApplyRoutePolicy;
99
+ const fallbackHandler = typeof missingHandler === "function" ? missingHandler : defaultMissingHandler;
100
+ const runtimeMiddlewareConfig = normalizeRuntimeMiddlewareConfig(middleware);
101
+
102
+ for (const route of normalizedRoutes) {
103
+ const baseOptions = toFastifyRouteOptions(route);
104
+ const routeOptions = policyApplier(baseOptions, route);
105
+ const routeHandler = typeof route?.handler === "function" ? route.handler : fallbackHandler;
106
+ const resolvedMiddlewareHandlers = resolveRouteMiddlewareHandlers(route, runtimeMiddlewareConfig);
107
+ const routeInputTransforms = normalizeRouteInputTransforms(route);
108
+ const routeActionDefaultSurface = resolveDefaultSurfaceId(null, {
109
+ defaultSurfaceId: route?.surface || routeOptions?.config?.surface || requestActionDefaultSurface
110
+ });
111
+
112
+ fastify.route({
113
+ ...routeOptions,
114
+ handler: async (request, reply) => {
115
+ if (!request.routeOptions || typeof request.routeOptions !== "object") {
116
+ request.routeOptions = {
117
+ config: normalizeObject(routeOptions?.config)
118
+ };
119
+ } else if (!request.routeOptions.config || typeof request.routeOptions.config !== "object") {
120
+ request.routeOptions.config = normalizeObject(routeOptions?.config);
121
+ }
122
+
123
+ if (enableRequestScope) {
124
+ attachRequestScope({
125
+ app,
126
+ request,
127
+ reply,
128
+ requestScopeProperty,
129
+ requestScopeIdPrefix,
130
+ requestIdResolver
131
+ });
132
+ }
133
+
134
+ attachRequestActionExecutor({
135
+ app,
136
+ request,
137
+ requestScopeProperty,
138
+ requestActionExecutorProperty,
139
+ actionExecutorToken,
140
+ defaultChannel: requestActionDefaultChannel,
141
+ defaultSurfaceId: routeActionDefaultSurface
142
+ });
143
+
144
+ if (routeInputTransforms) {
145
+ request.input = buildRequestInput({
146
+ request,
147
+ inputTransforms: routeInputTransforms
148
+ });
149
+ }
150
+
151
+ await executeMiddlewareStack(resolvedMiddlewareHandlers, request, reply);
152
+ if (reply?.sent) {
153
+ return;
154
+ }
155
+ await routeHandler(request, reply);
156
+ }
157
+ });
158
+ }
159
+
160
+ return {
161
+ routeCount: normalizedRoutes.length
162
+ };
163
+ }
164
+
165
+ export { defaultMissingHandler, registerRoutes };
@@ -0,0 +1,45 @@
1
+ import { normalizeArray, normalizeText } from "../../../shared/support/normalize.js";
2
+
3
+ function resolveRouteLabel({ method = "", path = "" } = {}) {
4
+ const normalizedMethod = normalizeText(method, {
5
+ fallback: "<unknown>"
6
+ }).toUpperCase();
7
+ const normalizedPath = normalizeText(path, {
8
+ fallback: "<unknown>"
9
+ });
10
+ return `${normalizedMethod} ${normalizedPath}`;
11
+ }
12
+
13
+ function normalizeMiddlewareEntry(
14
+ entry,
15
+ { context = "middleware", index = -1, ErrorType = Error, entryLabel = "entry" } = {}
16
+ ) {
17
+ if (typeof entry === "function") {
18
+ return entry;
19
+ }
20
+
21
+ const normalizedName = normalizeText(entry);
22
+ if (normalizedName) {
23
+ return normalizedName;
24
+ }
25
+
26
+ const indexSuffix = Number.isInteger(index) && index >= 0 ? ` at index ${index}` : "";
27
+ const normalizedEntryLabel = String(entryLabel || "entry").trim() || "entry";
28
+ throw new ErrorType(`${context} ${normalizedEntryLabel}${indexSuffix} must be a function or non-empty string.`);
29
+ }
30
+
31
+ function normalizeMiddlewareStack(
32
+ value,
33
+ { context = "middleware", ErrorType = Error, entryLabel = "entry", includeIndex = true } = {}
34
+ ) {
35
+ return normalizeArray(value).map((entry, index) =>
36
+ normalizeMiddlewareEntry(entry, {
37
+ context,
38
+ index: includeIndex ? index : -1,
39
+ ErrorType,
40
+ entryLabel
41
+ })
42
+ );
43
+ }
44
+
45
+ export { resolveRouteLabel, normalizeMiddlewareEntry, normalizeMiddlewareStack };