@jskit-ai/kernel 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/_testable/index.js +4 -0
- package/client/appConfig.js +33 -0
- package/client/componentInteraction.js +51 -0
- package/client/componentInteraction.test.js +111 -0
- package/client/descriptorSections.js +75 -0
- package/client/index.d.ts +70 -0
- package/client/index.js +3 -0
- package/client/logging.js +38 -0
- package/client/moduleBootstrap.js +670 -0
- package/client/moduleBootstrap.test.js +403 -0
- package/client/shellBootstrap.js +233 -0
- package/client/shellBootstrap.test.js +185 -0
- package/client/shellRouting.js +321 -0
- package/client/shellRouting.test.js +113 -0
- package/client/vite/clientBootstrapPlugin.js +259 -0
- package/client/vite/clientBootstrapPlugin.test.js +563 -0
- package/client/vite/index.js +3 -0
- package/internal/node/fileSystem.js +21 -0
- package/internal/node/installedPackageDescriptor.js +104 -0
- package/package.json +43 -0
- package/server/actions/ActionRuntimeServiceProvider.js +309 -0
- package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
- package/server/actions/index.js +8 -0
- package/server/container/ContainerCoreServiceProvider.js +27 -0
- package/server/container/index.js +10 -0
- package/server/exportPolicy.test.js +68 -0
- package/server/http/HttpFastifyServiceProvider.js +25 -0
- package/server/http/_testable/index.js +2 -0
- package/server/http/index.js +1 -0
- package/server/http/lib/controller.js +183 -0
- package/server/http/lib/controller.test.js +143 -0
- package/server/http/lib/errors.js +12 -0
- package/server/http/lib/httpRuntime.js +82 -0
- package/server/http/lib/index.js +18 -0
- package/server/http/lib/kernel.js +15 -0
- package/server/http/lib/kernel.test.js +880 -0
- package/server/http/lib/middlewareRuntime.js +149 -0
- package/server/http/lib/requestActionExecutor.js +258 -0
- package/server/http/lib/requestScope.js +59 -0
- package/server/http/lib/routeRegistration.js +165 -0
- package/server/http/lib/routeSupport.js +45 -0
- package/server/http/lib/routeValidator.js +469 -0
- package/server/http/lib/routeValidator.test.js +474 -0
- package/server/http/lib/router.js +206 -0
- package/server/kernel/KernelCoreServiceProvider.js +27 -0
- package/server/kernel/index.js +10 -0
- package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
- package/server/platform/index.js +5 -0
- package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
- package/server/platform/providerRuntime/helpers.js +45 -0
- package/server/platform/providerRuntime/lockfile.js +27 -0
- package/server/platform/providerRuntime/providerLoader.js +283 -0
- package/server/platform/providerRuntime.js +142 -0
- package/server/platform/providerRuntime.test.js +217 -0
- package/server/platform/runtime.js +40 -0
- package/server/platform/surfaceRuntime.js +150 -0
- package/server/platform/surfaceRuntime.test.js +136 -0
- package/server/registries/actionSurfaceSourceRegistry.js +150 -0
- package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
- package/server/registries/domainEventListenerRegistry.js +61 -0
- package/server/registries/index.js +36 -0
- package/server/registries/primitives.js +63 -0
- package/server/registries/routeVisibilityResolverRegistry.js +87 -0
- package/server/registries/serviceRegistrationRegistry.js +431 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
- package/server/runtime/apiRoutePolicyParity.test.js +109 -0
- package/server/runtime/apiRouteRegistration.js +65 -0
- package/server/runtime/bootBootstrapRoutes.js +46 -0
- package/server/runtime/bootBootstrapRoutes.test.js +79 -0
- package/server/runtime/bootstrapContributors.test.js +114 -0
- package/server/runtime/canonicalJson.js +74 -0
- package/server/runtime/composition.js +142 -0
- package/server/runtime/domainEvents.test.js +114 -0
- package/server/runtime/domainRules.js +50 -0
- package/server/runtime/domainRules.test.js +87 -0
- package/server/runtime/entityChangeEvents.js +182 -0
- package/server/runtime/entityChangeEvents.test.js +211 -0
- package/server/runtime/errors.js +68 -0
- package/server/runtime/errors.test.js +73 -0
- package/server/runtime/fastifyBootstrap.js +372 -0
- package/server/runtime/fastifyBootstrap.test.js +194 -0
- package/server/runtime/index.js +6 -0
- package/server/runtime/integers.js +13 -0
- package/server/runtime/moduleConfig.js +269 -0
- package/server/runtime/moduleConfig.test.js +141 -0
- package/server/runtime/pagination.js +13 -0
- package/server/runtime/realtimeNormalization.js +21 -0
- package/server/runtime/requestUrl.js +38 -0
- package/server/runtime/routeUtils.js +20 -0
- package/server/runtime/runtimeAssembly.js +113 -0
- package/server/runtime/runtimeKernel.js +55 -0
- package/server/runtime/securityAudit.js +269 -0
- package/server/runtime/securityAudit.test.js +41 -0
- package/server/runtime/serviceAuthorization.js +113 -0
- package/server/runtime/serviceAuthorization.test.js +100 -0
- package/server/runtime/serviceRegistration.test.js +197 -0
- package/server/support/SupportCoreServiceProvider.js +25 -0
- package/server/support/appConfig.js +37 -0
- package/server/support/appConfig.test.js +94 -0
- package/server/support/defaultMissingHandler.js +7 -0
- package/server/support/index.js +2 -0
- package/server/support/routePolicyConfig.js +51 -0
- package/server/support/symlinkSafeRequire.js +78 -0
- package/server/support/symlinkSafeRequire.test.js +27 -0
- package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
- package/server/surface/index.js +19 -0
- package/shared/actions/actionContributorHelpers.js +34 -0
- package/shared/actions/actionContributorHelpers.test.js +16 -0
- package/shared/actions/actionDefinitions.js +488 -0
- package/shared/actions/actionDefinitions.test.js +212 -0
- package/shared/actions/audit.js +7 -0
- package/shared/actions/executionContext.js +97 -0
- package/shared/actions/executionContext.test.js +66 -0
- package/shared/actions/idempotency.js +62 -0
- package/shared/actions/index.js +2 -0
- package/shared/actions/observability.js +10 -0
- package/shared/actions/pipeline.js +287 -0
- package/shared/actions/policies.js +342 -0
- package/shared/actions/policies.test.js +233 -0
- package/shared/actions/registry.js +187 -0
- package/shared/actions/registry.test.js +381 -0
- package/shared/actions/requestMeta.js +36 -0
- package/shared/actions/textNormalization.js +3 -0
- package/shared/actions/withActionDefaults.js +34 -0
- package/shared/index.js +2 -0
- package/shared/runtime/application.js +323 -0
- package/shared/runtime/container.js +261 -0
- package/shared/runtime/containerErrors.js +22 -0
- package/shared/runtime/index.js +18 -0
- package/shared/runtime/kernelErrors.js +20 -0
- package/shared/runtime/serviceProvider.js +13 -0
- package/shared/support/formatDateTime.js +10 -0
- package/shared/support/formatDateTime.test.js +15 -0
- package/shared/support/index.js +14 -0
- package/shared/support/linkPath.js +67 -0
- package/shared/support/linkPath.test.js +35 -0
- package/shared/support/normalize.js +116 -0
- package/shared/support/normalize.test.js +48 -0
- package/shared/support/packageDescriptor.test.js +121 -0
- package/shared/support/permissions.js +50 -0
- package/shared/support/pickOwnProperties.js +17 -0
- package/shared/support/pickOwnProperties.test.js +25 -0
- package/shared/support/policies.js +11 -0
- package/shared/support/queryPath.js +33 -0
- package/shared/support/queryPath.test.js +19 -0
- package/shared/support/queryResilience.js +34 -0
- package/shared/support/queryResilience.test.js +33 -0
- package/shared/support/returnToPath.js +153 -0
- package/shared/support/returnToPath.test.js +123 -0
- package/shared/support/sorting.js +15 -0
- package/shared/support/tokens.js +23 -0
- package/shared/support/tokens.test.js +17 -0
- package/shared/support/visibility.js +56 -0
- package/shared/support/visibility.test.js +45 -0
- package/shared/surface/apiPaths.js +84 -0
- package/shared/surface/escapeRegExp.js +5 -0
- package/shared/surface/index.js +6 -0
- package/shared/surface/paths.js +273 -0
- package/shared/surface/registry.js +135 -0
- package/shared/surface/registry.test.js +44 -0
- package/shared/surface/runtime.js +357 -0
- package/shared/surface/runtime.test.js +319 -0
- package/shared/validators/createCursorListValidator.js +42 -0
- package/shared/validators/createCursorListValidator.test.js +34 -0
- package/shared/validators/cursorPaginationQueryValidator.js +31 -0
- package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
- package/shared/validators/index.js +12 -0
- package/shared/validators/inputNormalization.js +13 -0
- package/shared/validators/mergeObjectSchemas.js +31 -0
- package/shared/validators/mergeObjectSchemas.test.js +67 -0
- package/shared/validators/mergeValidators.js +89 -0
- package/shared/validators/mergeValidators.test.js +116 -0
- package/shared/validators/nestValidator.js +53 -0
- package/shared/validators/nestValidator.test.js +60 -0
- package/shared/validators/recordIdParamsValidator.js +36 -0
- package/shared/validators/recordIdParamsValidator.test.js +20 -0
- package/shared/validators/resourceRequiredMetadata.js +41 -0
- package/shared/validators/resourceRequiredMetadata.test.js +49 -0
- package/test/barrelExposure.test.js +106 -0
- package/test/dynamicImportPolicy.test.js +89 -0
- package/test/exportsContract.test.js +168 -0
- package/test/routeInputContractGuard.test.js +78 -0
- package/test/surfaceIndependence.test.js +109 -0
|
@@ -0,0 +1,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 };
|