@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,183 @@
|
|
|
1
|
+
const DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE = Object.freeze({
|
|
2
|
+
domain_validation_failed: 422,
|
|
3
|
+
validation_failed: 422,
|
|
4
|
+
invalid_input: 422,
|
|
5
|
+
duplicate: 409,
|
|
6
|
+
conflict: 409,
|
|
7
|
+
not_found: 404,
|
|
8
|
+
forbidden: 403,
|
|
9
|
+
unauthorized: 401,
|
|
10
|
+
rate_limited: 429
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function normalizeStatus(status, fallbackStatus) {
|
|
14
|
+
const numeric = Number(status);
|
|
15
|
+
if (Number.isInteger(numeric) && numeric >= 100 && numeric <= 599) {
|
|
16
|
+
return numeric;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const fallbackNumeric = Number(fallbackStatus);
|
|
20
|
+
if (Number.isInteger(fallbackNumeric) && fallbackNumeric >= 100 && fallbackNumeric <= 599) {
|
|
21
|
+
return fallbackNumeric;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 500;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeCode(code) {
|
|
28
|
+
const normalized = String(code || "")
|
|
29
|
+
.trim()
|
|
30
|
+
.toLowerCase();
|
|
31
|
+
return normalized || "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveDomainErrorStatus({
|
|
35
|
+
status,
|
|
36
|
+
code,
|
|
37
|
+
domainErrorStatusByCode = DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE,
|
|
38
|
+
fallbackStatus = 422
|
|
39
|
+
} = {}) {
|
|
40
|
+
const explicitStatus = Number(status);
|
|
41
|
+
if (Number.isInteger(explicitStatus) && explicitStatus >= 100 && explicitStatus <= 599) {
|
|
42
|
+
return explicitStatus;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const normalizedCode = normalizeCode(code);
|
|
46
|
+
if (normalizedCode) {
|
|
47
|
+
const mapSource =
|
|
48
|
+
domainErrorStatusByCode && typeof domainErrorStatusByCode === "object"
|
|
49
|
+
? domainErrorStatusByCode
|
|
50
|
+
: DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE;
|
|
51
|
+
const mappedStatus = Number(mapSource[normalizedCode]);
|
|
52
|
+
if (Number.isInteger(mappedStatus) && mappedStatus >= 100 && mappedStatus <= 599) {
|
|
53
|
+
return mappedStatus;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return normalizeStatus(fallbackStatus, 422);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureReply(reply, methodName) {
|
|
61
|
+
if (!reply || typeof reply.code !== "function" || typeof reply.send !== "function") {
|
|
62
|
+
throw new TypeError(`${methodName} requires a Fastify reply-like object with code() and send().`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function applyHeaders(reply, headers) {
|
|
67
|
+
if (!headers || typeof headers !== "object" || typeof reply.header !== "function") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
72
|
+
reply.header(name, value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class BaseController {
|
|
77
|
+
constructor({ domainErrorStatusByCode = DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE } = {}) {
|
|
78
|
+
this.domainErrorStatusByCode =
|
|
79
|
+
domainErrorStatusByCode && typeof domainErrorStatusByCode === "object"
|
|
80
|
+
? { ...DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE, ...domainErrorStatusByCode }
|
|
81
|
+
: { ...DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ok(reply, payload, { status = 200, headers = null } = {}) {
|
|
85
|
+
ensureReply(reply, "BaseController.ok");
|
|
86
|
+
applyHeaders(reply, headers);
|
|
87
|
+
|
|
88
|
+
const normalizedStatus = normalizeStatus(status, 200);
|
|
89
|
+
reply.code(normalizedStatus);
|
|
90
|
+
|
|
91
|
+
if (normalizedStatus === 204) {
|
|
92
|
+
return reply.send();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return reply.send(payload);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
created(reply, payload, { headers = null } = {}) {
|
|
99
|
+
return this.ok(reply, payload, {
|
|
100
|
+
status: 201,
|
|
101
|
+
headers
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
noContent(reply, { headers = null } = {}) {
|
|
106
|
+
return this.ok(reply, undefined, {
|
|
107
|
+
status: 204,
|
|
108
|
+
headers
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fail(
|
|
113
|
+
reply,
|
|
114
|
+
{ status, code, message = "Request failed.", details, fieldErrors, headers = null, fallbackStatus = 422 } = {}
|
|
115
|
+
) {
|
|
116
|
+
ensureReply(reply, "BaseController.fail");
|
|
117
|
+
|
|
118
|
+
const resolvedStatus = resolveDomainErrorStatus({
|
|
119
|
+
status,
|
|
120
|
+
code,
|
|
121
|
+
domainErrorStatusByCode: this.domainErrorStatusByCode,
|
|
122
|
+
fallbackStatus
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const payload = {
|
|
126
|
+
error: String(message || "Request failed.")
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const normalizedCode = normalizeCode(code);
|
|
130
|
+
if (normalizedCode) {
|
|
131
|
+
payload.code = String(code || "").trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (details !== undefined) {
|
|
135
|
+
payload.details = details;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (fieldErrors !== undefined) {
|
|
139
|
+
payload.fieldErrors = fieldErrors;
|
|
140
|
+
if (payload.details === undefined) {
|
|
141
|
+
payload.details = { fieldErrors };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
applyHeaders(reply, headers);
|
|
146
|
+
reply.code(resolvedStatus);
|
|
147
|
+
return reply.send(payload);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
sendActionResult(
|
|
151
|
+
reply,
|
|
152
|
+
result,
|
|
153
|
+
{ successStatus = 200, defaultErrorStatus = 422, defaultErrorMessage = "Request failed." } = {}
|
|
154
|
+
) {
|
|
155
|
+
if (!result || typeof result !== "object") {
|
|
156
|
+
throw new TypeError("BaseController.sendActionResult requires an object result.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (result.ok === true) {
|
|
160
|
+
const payload = Object.prototype.hasOwnProperty.call(result, "data") ? result.data : undefined;
|
|
161
|
+
return this.ok(reply, payload, {
|
|
162
|
+
status: result.status ?? successStatus,
|
|
163
|
+
headers: result.headers
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (result.ok === false) {
|
|
168
|
+
return this.fail(reply, {
|
|
169
|
+
status: result.status,
|
|
170
|
+
code: result.code,
|
|
171
|
+
message: result.message || defaultErrorMessage,
|
|
172
|
+
details: result.details,
|
|
173
|
+
fieldErrors: result.fieldErrors,
|
|
174
|
+
headers: result.headers,
|
|
175
|
+
fallbackStatus: defaultErrorStatus
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
throw new TypeError("BaseController.sendActionResult expects result.ok to be true or false.");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { BaseController, DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE, resolveDomainErrorStatus };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { BaseController, resolveDomainErrorStatus } from "./controller.js";
|
|
5
|
+
|
|
6
|
+
function createReplyStub() {
|
|
7
|
+
const state = {
|
|
8
|
+
statusCode: 200,
|
|
9
|
+
payload: undefined,
|
|
10
|
+
headers: {}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
state,
|
|
15
|
+
code(statusCode) {
|
|
16
|
+
state.statusCode = Number(statusCode);
|
|
17
|
+
return this;
|
|
18
|
+
},
|
|
19
|
+
header(name, value) {
|
|
20
|
+
state.headers[name] = value;
|
|
21
|
+
return this;
|
|
22
|
+
},
|
|
23
|
+
send(payload) {
|
|
24
|
+
state.payload = payload;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("resolveDomainErrorStatus prefers explicit status", () => {
|
|
31
|
+
const status = resolveDomainErrorStatus({
|
|
32
|
+
status: 418,
|
|
33
|
+
code: "duplicate"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
assert.equal(status, 418);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolveDomainErrorStatus maps known domain codes", () => {
|
|
40
|
+
assert.equal(resolveDomainErrorStatus({ code: "duplicate" }), 409);
|
|
41
|
+
assert.equal(resolveDomainErrorStatus({ code: "domain_validation_failed" }), 422);
|
|
42
|
+
assert.equal(resolveDomainErrorStatus({ code: "not_found" }), 404);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("BaseController.sendActionResult sends success payload and headers", () => {
|
|
46
|
+
const controller = new BaseController();
|
|
47
|
+
const reply = createReplyStub();
|
|
48
|
+
|
|
49
|
+
controller.sendActionResult(reply, {
|
|
50
|
+
ok: true,
|
|
51
|
+
data: { ok: true, value: 42 },
|
|
52
|
+
status: 201,
|
|
53
|
+
headers: {
|
|
54
|
+
"x-test": "success"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.equal(reply.state.statusCode, 201);
|
|
59
|
+
assert.deepEqual(reply.state.payload, { ok: true, value: 42 });
|
|
60
|
+
assert.equal(reply.state.headers["x-test"], "success");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("BaseController.sendActionResult maps domain failure", () => {
|
|
64
|
+
const controller = new BaseController();
|
|
65
|
+
const reply = createReplyStub();
|
|
66
|
+
|
|
67
|
+
controller.sendActionResult(reply, {
|
|
68
|
+
ok: false,
|
|
69
|
+
code: "duplicate",
|
|
70
|
+
details: ["already exists"]
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.equal(reply.state.statusCode, 409);
|
|
74
|
+
assert.deepEqual(reply.state.payload, {
|
|
75
|
+
error: "Request failed.",
|
|
76
|
+
code: "duplicate",
|
|
77
|
+
details: ["already exists"]
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("BaseController supports module-specific domain error codes through overrides", () => {
|
|
82
|
+
const controller = new BaseController({
|
|
83
|
+
domainErrorStatusByCode: {
|
|
84
|
+
duplicate_contact: 409
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const reply = createReplyStub();
|
|
88
|
+
|
|
89
|
+
controller.sendActionResult(reply, {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: "duplicate_contact",
|
|
92
|
+
details: ["already exists"]
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
assert.equal(reply.state.statusCode, 409);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("BaseController.fail includes fieldErrors and default details shape", () => {
|
|
99
|
+
const controller = new BaseController();
|
|
100
|
+
const reply = createReplyStub();
|
|
101
|
+
|
|
102
|
+
controller.fail(reply, {
|
|
103
|
+
code: "domain_validation_failed",
|
|
104
|
+
message: "Validation failed.",
|
|
105
|
+
fieldErrors: {
|
|
106
|
+
email: "Invalid email."
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
assert.equal(reply.state.statusCode, 422);
|
|
111
|
+
assert.deepEqual(reply.state.payload, {
|
|
112
|
+
error: "Validation failed.",
|
|
113
|
+
code: "domain_validation_failed",
|
|
114
|
+
fieldErrors: {
|
|
115
|
+
email: "Invalid email."
|
|
116
|
+
},
|
|
117
|
+
details: {
|
|
118
|
+
fieldErrors: {
|
|
119
|
+
email: "Invalid email."
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("BaseController.noContent sends HTTP 204", () => {
|
|
126
|
+
const controller = new BaseController();
|
|
127
|
+
const reply = createReplyStub();
|
|
128
|
+
|
|
129
|
+
controller.noContent(reply);
|
|
130
|
+
|
|
131
|
+
assert.equal(reply.state.statusCode, 204);
|
|
132
|
+
assert.equal(reply.state.payload, undefined);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("BaseController.sendActionResult rejects invalid result shape", () => {
|
|
136
|
+
const controller = new BaseController();
|
|
137
|
+
const reply = createReplyStub();
|
|
138
|
+
|
|
139
|
+
assert.throws(
|
|
140
|
+
() => controller.sendActionResult(reply, { data: { ok: true } }),
|
|
141
|
+
/expects result\.ok to be true or false/
|
|
142
|
+
);
|
|
143
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class HttpKernelError extends Error {
|
|
2
|
+
constructor(message, details = {}) {
|
|
3
|
+
super(String(message || "HTTP kernel error."));
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
this.details = details && typeof details === "object" ? { ...details } : {};
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class RouteDefinitionError extends HttpKernelError {}
|
|
10
|
+
class RouteRegistrationError extends HttpKernelError {}
|
|
11
|
+
|
|
12
|
+
export { HttpKernelError, RouteDefinitionError, RouteRegistrationError };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { KERNEL_TOKENS } from "../../../shared/support/tokens.js";
|
|
2
|
+
import { normalizeObject } from "../../../shared/support/normalize.js";
|
|
3
|
+
import { ensureApiErrorHandling } from "../../runtime/fastifyBootstrap.js";
|
|
4
|
+
import { resolveDefaultSurfaceId } from "../../support/appConfig.js";
|
|
5
|
+
import { RouteRegistrationError } from "./errors.js";
|
|
6
|
+
import { createRouter } from "./router.js";
|
|
7
|
+
import { registerRoutes } from "./routeRegistration.js";
|
|
8
|
+
|
|
9
|
+
function registerHttpRuntime(app, options = {}) {
|
|
10
|
+
if (!app || typeof app.make !== "function") {
|
|
11
|
+
throw new RouteRegistrationError("registerHttpRuntime requires an application instance.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const runtimeOptions = normalizeObject(options);
|
|
15
|
+
const {
|
|
16
|
+
fastifyToken = KERNEL_TOKENS.Fastify,
|
|
17
|
+
routerToken = KERNEL_TOKENS.HttpRouter,
|
|
18
|
+
autoRegisterApiErrorHandling = true,
|
|
19
|
+
apiErrorHandling = {},
|
|
20
|
+
...routeRegistrationOptions
|
|
21
|
+
} = runtimeOptions;
|
|
22
|
+
const fastify = app.make(fastifyToken);
|
|
23
|
+
const router = app.make(routerToken);
|
|
24
|
+
const routes = typeof router?.list === "function" ? router.list() : [];
|
|
25
|
+
const resolvedDefaultActionSurface = resolveDefaultSurfaceId(app, {
|
|
26
|
+
defaultSurfaceId: routeRegistrationOptions.requestActionDefaultSurface
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (autoRegisterApiErrorHandling !== false) {
|
|
30
|
+
ensureApiErrorHandling(app, {
|
|
31
|
+
fastifyToken,
|
|
32
|
+
...normalizeObject(apiErrorHandling)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return registerRoutes(fastify, {
|
|
37
|
+
...routeRegistrationOptions,
|
|
38
|
+
requestActionDefaultSurface: resolvedDefaultActionSurface,
|
|
39
|
+
app,
|
|
40
|
+
routes
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createHttpRuntime(
|
|
45
|
+
{
|
|
46
|
+
app = null,
|
|
47
|
+
fastify = null,
|
|
48
|
+
router = null,
|
|
49
|
+
autoRegisterApiErrorHandling = true,
|
|
50
|
+
apiErrorHandling = {}
|
|
51
|
+
} = {}
|
|
52
|
+
) {
|
|
53
|
+
if (!app || typeof app.singleton !== "function") {
|
|
54
|
+
throw new RouteRegistrationError("createHttpRuntime requires an application instance.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const runtimeRouter = router || createRouter();
|
|
58
|
+
app.singleton(KERNEL_TOKENS.HttpRouter, () => runtimeRouter);
|
|
59
|
+
|
|
60
|
+
if (fastify) {
|
|
61
|
+
app.instance(KERNEL_TOKENS.Fastify, fastify);
|
|
62
|
+
if (autoRegisterApiErrorHandling !== false) {
|
|
63
|
+
ensureApiErrorHandling(app, {
|
|
64
|
+
fastifyToken: KERNEL_TOKENS.Fastify,
|
|
65
|
+
...normalizeObject(apiErrorHandling)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
router: runtimeRouter,
|
|
72
|
+
registerRoutes(runtimeOptions = {}) {
|
|
73
|
+
return registerHttpRuntime(app, {
|
|
74
|
+
autoRegisterApiErrorHandling,
|
|
75
|
+
apiErrorHandling: normalizeObject(apiErrorHandling),
|
|
76
|
+
...normalizeObject(runtimeOptions)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { registerHttpRuntime, createHttpRuntime };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { HttpKernelError, RouteDefinitionError, RouteRegistrationError } from "./errors.js";
|
|
2
|
+
export { HttpRouter, createRouter, joinPath } from "./router.js";
|
|
3
|
+
export { defineRouteValidator, compileRouteValidator, resolveRouteValidatorOptions } from "./routeValidator.js";
|
|
4
|
+
export { BaseController, DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE, resolveDomainErrorStatus } from "./controller.js";
|
|
5
|
+
export {
|
|
6
|
+
defaultMissingHandler,
|
|
7
|
+
defaultApplyRoutePolicy,
|
|
8
|
+
normalizeRoutePolicyConfig,
|
|
9
|
+
registerRoutes,
|
|
10
|
+
registerHttpRuntime,
|
|
11
|
+
createHttpRuntime
|
|
12
|
+
} from "./kernel.js";
|
|
13
|
+
export {
|
|
14
|
+
ROUTE_VISIBILITY_RESOLVER_TAG,
|
|
15
|
+
resolveRouteVisibilityResolvers,
|
|
16
|
+
registerRouteVisibilityResolver,
|
|
17
|
+
resolveRouteVisibilityContext
|
|
18
|
+
} from "../../registries/routeVisibilityResolverRegistry.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defaultApplyRoutePolicy, normalizeRoutePolicyConfig } from "../../support/routePolicyConfig.js";
|
|
2
|
+
import { defaultMissingHandler, registerRoutes } from "./routeRegistration.js";
|
|
3
|
+
import { buildActionExecutionContext, attachRequestActionExecutor } from "./requestActionExecutor.js";
|
|
4
|
+
import { registerHttpRuntime, createHttpRuntime } from "./httpRuntime.js";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
defaultMissingHandler,
|
|
8
|
+
defaultApplyRoutePolicy,
|
|
9
|
+
normalizeRoutePolicyConfig,
|
|
10
|
+
buildActionExecutionContext,
|
|
11
|
+
attachRequestActionExecutor,
|
|
12
|
+
registerRoutes,
|
|
13
|
+
registerHttpRuntime,
|
|
14
|
+
createHttpRuntime
|
|
15
|
+
};
|