@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,97 @@
|
|
|
1
|
+
import { normalizeRequestMeta } from "./requestMeta.js";
|
|
2
|
+
import { normalizeLowerText, normalizeText } from "./textNormalization.js";
|
|
3
|
+
|
|
4
|
+
function normalizePermissions(value) {
|
|
5
|
+
const source = Array.isArray(value) ? value : [];
|
|
6
|
+
return Array.from(new Set(source.map((entry) => normalizeText(entry)).filter(Boolean)));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function copyRecord(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...value
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeActor(actor) {
|
|
20
|
+
const source = copyRecord(actor);
|
|
21
|
+
if (!source) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!Object.prototype.hasOwnProperty.call(source, "id")) {
|
|
26
|
+
source.id = null;
|
|
27
|
+
} else if (source.id == null) {
|
|
28
|
+
source.id = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return source;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeMembership(membership) {
|
|
35
|
+
return copyRecord(membership);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeTimeMeta(timeMeta) {
|
|
39
|
+
const source = timeMeta && typeof timeMeta === "object" ? timeMeta : {};
|
|
40
|
+
const nowValue = source.now instanceof Date ? source.now : new Date();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
now: nowValue,
|
|
44
|
+
timezone: normalizeText(source.timezone),
|
|
45
|
+
locale: normalizeText(source.locale)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const RESERVED_CONTEXT_KEYS = new Set([
|
|
50
|
+
"actor",
|
|
51
|
+
"membership",
|
|
52
|
+
"permissions",
|
|
53
|
+
"surface",
|
|
54
|
+
"channel",
|
|
55
|
+
"requestMeta",
|
|
56
|
+
"timeMeta"
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
function copyPassthroughContextEntries(source = {}) {
|
|
60
|
+
const passthrough = {};
|
|
61
|
+
for (const [key, value] of Object.entries(source)) {
|
|
62
|
+
if (RESERVED_CONTEXT_KEYS.has(key)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
passthrough[key] = value;
|
|
66
|
+
}
|
|
67
|
+
return passthrough;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeExecutionContext(context = {}) {
|
|
71
|
+
const source = context && typeof context === "object" ? context : {};
|
|
72
|
+
const passthrough = copyPassthroughContextEntries(source);
|
|
73
|
+
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
...passthrough,
|
|
76
|
+
actor: normalizeActor(source.actor),
|
|
77
|
+
membership: normalizeMembership(source.membership),
|
|
78
|
+
permissions: normalizePermissions(source.permissions),
|
|
79
|
+
surface: normalizeLowerText(source.surface),
|
|
80
|
+
channel: normalizeLowerText(source.channel) || "internal",
|
|
81
|
+
requestMeta: normalizeRequestMeta(source.requestMeta),
|
|
82
|
+
timeMeta: normalizeTimeMeta(source.timeMeta)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const __testables = {
|
|
87
|
+
normalizeText,
|
|
88
|
+
normalizeLowerText,
|
|
89
|
+
normalizePermissions,
|
|
90
|
+
copyRecord,
|
|
91
|
+
normalizeActor,
|
|
92
|
+
normalizeMembership,
|
|
93
|
+
normalizeRequestMeta,
|
|
94
|
+
normalizeTimeMeta
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { normalizeExecutionContext, __testables };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { normalizeExecutionContext } from "./executionContext.js";
|
|
5
|
+
|
|
6
|
+
test("normalizeExecutionContext preserves non-core context fields", () => {
|
|
7
|
+
const context = normalizeExecutionContext({
|
|
8
|
+
tenant: {
|
|
9
|
+
id: 7,
|
|
10
|
+
slug: "team-alpha",
|
|
11
|
+
ownerUserId: 42
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
assert.equal(context.tenant.ownerUserId, 42);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("normalizeExecutionContext keeps custom requestMeta fields", () => {
|
|
19
|
+
const resolvedWorkspaceContext = {
|
|
20
|
+
workspace: {
|
|
21
|
+
id: 7,
|
|
22
|
+
ownerUserId: 42
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const context = normalizeExecutionContext({
|
|
27
|
+
requestMeta: {
|
|
28
|
+
resolvedWorkspaceContext
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.deepEqual(context.requestMeta.resolvedWorkspaceContext, resolvedWorkspaceContext);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("normalizeExecutionContext leaves surface empty when missing", () => {
|
|
36
|
+
const context = normalizeExecutionContext({});
|
|
37
|
+
assert.equal(context.surface, "");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("normalizeExecutionContext keeps actor payload generic", () => {
|
|
41
|
+
const context = normalizeExecutionContext({
|
|
42
|
+
actor: {
|
|
43
|
+
id: "user_1",
|
|
44
|
+
email: "UPPER@EXAMPLE.COM",
|
|
45
|
+
roleId: "OWNER",
|
|
46
|
+
customFlag: true
|
|
47
|
+
},
|
|
48
|
+
membership: {
|
|
49
|
+
roleId: "OWNER",
|
|
50
|
+
status: "ACTIVE",
|
|
51
|
+
extra: "x"
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.deepEqual(context.actor, {
|
|
56
|
+
id: "user_1",
|
|
57
|
+
email: "UPPER@EXAMPLE.COM",
|
|
58
|
+
roleId: "OWNER",
|
|
59
|
+
customFlag: true
|
|
60
|
+
});
|
|
61
|
+
assert.deepEqual(context.membership, {
|
|
62
|
+
roleId: "OWNER",
|
|
63
|
+
status: "ACTIVE",
|
|
64
|
+
extra: "x"
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createActionRuntimeError } from "./actionDefinitions.js";
|
|
2
|
+
import { normalizeText } from "./textNormalization.js";
|
|
3
|
+
|
|
4
|
+
function resolveRequestIdempotencyKey(context) {
|
|
5
|
+
return normalizeText(context?.requestMeta?.idempotencyKey || context?.requestMeta?.commandId || "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function resolveActionIdempotencyKey(definition, context) {
|
|
9
|
+
const policy = normalizeText(definition?.idempotency || "none").toLowerCase();
|
|
10
|
+
if (policy === "none") {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return resolveRequestIdempotencyKey(context);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createNoopIdempotencyAdapter() {
|
|
18
|
+
return {
|
|
19
|
+
async claimOrReplay() {
|
|
20
|
+
return {
|
|
21
|
+
type: "proceed",
|
|
22
|
+
idempotencyReplay: false,
|
|
23
|
+
claim: null,
|
|
24
|
+
replayResult: null
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
async markSucceeded() {},
|
|
28
|
+
async markFailed() {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ensureIdempotencyKeyIfRequired(definition, context, key) {
|
|
33
|
+
const policy = normalizeText(definition?.idempotency || "none").toLowerCase();
|
|
34
|
+
if (policy !== "required") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (normalizeText(key)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw createActionRuntimeError(400, "Validation failed.", {
|
|
43
|
+
code: "ACTION_IDEMPOTENCY_KEY_REQUIRED",
|
|
44
|
+
details: {
|
|
45
|
+
fieldErrors: {
|
|
46
|
+
idempotencyKey: "Idempotency key is required for this action."
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const __testables = {
|
|
53
|
+
normalizeText,
|
|
54
|
+
resolveRequestIdempotencyKey
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
resolveActionIdempotencyKey,
|
|
59
|
+
ensureIdempotencyKeyIfRequired,
|
|
60
|
+
createNoopIdempotencyAdapter,
|
|
61
|
+
__testables
|
|
62
|
+
};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureActionChannelAllowed,
|
|
3
|
+
ensureActionSurfaceAllowed,
|
|
4
|
+
ensureActionPermissionAllowed,
|
|
5
|
+
normalizeActionInput,
|
|
6
|
+
normalizeActionOutput
|
|
7
|
+
} from "./policies.js";
|
|
8
|
+
import {
|
|
9
|
+
createNoopIdempotencyAdapter,
|
|
10
|
+
resolveActionIdempotencyKey,
|
|
11
|
+
ensureIdempotencyKeyIfRequired
|
|
12
|
+
} from "./idempotency.js";
|
|
13
|
+
import { createNoopAuditAdapter } from "./audit.js";
|
|
14
|
+
import { createNoopObservabilityAdapter } from "./observability.js";
|
|
15
|
+
import { normalizeExecutionContext } from "./executionContext.js";
|
|
16
|
+
|
|
17
|
+
function normalizeOutcomeErrorCode(error) {
|
|
18
|
+
return String(error?.code || "ACTION_EXECUTION_FAILED").trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildActionLogPayload({ definition, context, outcome, durationMs, errorCode, idempotencyReplay }) {
|
|
22
|
+
return {
|
|
23
|
+
action: definition?.id,
|
|
24
|
+
version: definition?.version,
|
|
25
|
+
channel: context?.channel,
|
|
26
|
+
surface: context?.surface,
|
|
27
|
+
requestId: context?.requestMeta?.requestId || null,
|
|
28
|
+
durationMs,
|
|
29
|
+
outcome,
|
|
30
|
+
errorCode: errorCode || null,
|
|
31
|
+
idempotencyReplay: Boolean(idempotencyReplay)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function emitAuditEvent(adapter, payload) {
|
|
36
|
+
if (!adapter || typeof adapter.emitExecution !== "function") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await adapter.emitExecution(payload);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function executeIdempotencyClaim(adapter, payload) {
|
|
44
|
+
if (!adapter || typeof adapter.claimOrReplay !== "function") {
|
|
45
|
+
return {
|
|
46
|
+
type: "proceed",
|
|
47
|
+
idempotencyReplay: false,
|
|
48
|
+
claim: null,
|
|
49
|
+
replayResult: null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await adapter.claimOrReplay(payload);
|
|
54
|
+
if (!result || typeof result !== "object") {
|
|
55
|
+
return {
|
|
56
|
+
type: "proceed",
|
|
57
|
+
idempotencyReplay: false,
|
|
58
|
+
claim: null,
|
|
59
|
+
replayResult: null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
type: String(result.type || "proceed"),
|
|
65
|
+
idempotencyReplay: result.idempotencyReplay === true,
|
|
66
|
+
claim: result.claim || null,
|
|
67
|
+
replayResult: Object.hasOwn(result, "replayResult") ? result.replayResult : null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function executeActionPipeline({
|
|
72
|
+
definition,
|
|
73
|
+
input,
|
|
74
|
+
context,
|
|
75
|
+
deps = {},
|
|
76
|
+
idempotencyAdapter,
|
|
77
|
+
auditAdapter,
|
|
78
|
+
observabilityAdapter,
|
|
79
|
+
logger = console
|
|
80
|
+
} = {}) {
|
|
81
|
+
const normalizedContext = normalizeExecutionContext(context);
|
|
82
|
+
const normalizedIdempotencyAdapter = idempotencyAdapter || createNoopIdempotencyAdapter();
|
|
83
|
+
const normalizedAuditAdapter = auditAdapter || createNoopAuditAdapter();
|
|
84
|
+
const normalizedObservabilityAdapter = observabilityAdapter || createNoopObservabilityAdapter();
|
|
85
|
+
const startedAt = Date.now();
|
|
86
|
+
|
|
87
|
+
if (typeof normalizedObservabilityAdapter.recordExecutionStart === "function") {
|
|
88
|
+
normalizedObservabilityAdapter.recordExecutionStart({
|
|
89
|
+
definition,
|
|
90
|
+
context: normalizedContext
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let idempotencyKey = "";
|
|
95
|
+
let idempotencyClaim = null;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
ensureActionChannelAllowed(definition, normalizedContext);
|
|
99
|
+
ensureActionSurfaceAllowed(definition, normalizedContext);
|
|
100
|
+
ensureActionPermissionAllowed(definition, normalizedContext);
|
|
101
|
+
|
|
102
|
+
const normalizedInput = await normalizeActionInput(definition, input, normalizedContext);
|
|
103
|
+
|
|
104
|
+
idempotencyKey = resolveActionIdempotencyKey(definition, normalizedContext);
|
|
105
|
+
ensureIdempotencyKeyIfRequired(definition, normalizedContext, idempotencyKey);
|
|
106
|
+
|
|
107
|
+
const idempotencyPolicy = String(definition?.idempotency || "none").trim().toLowerCase();
|
|
108
|
+
if (idempotencyPolicy !== "none") {
|
|
109
|
+
const claimResult = await executeIdempotencyClaim(normalizedIdempotencyAdapter, {
|
|
110
|
+
definition,
|
|
111
|
+
context: normalizedContext,
|
|
112
|
+
input: normalizedInput,
|
|
113
|
+
idempotencyKey
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
idempotencyClaim = claimResult.claim;
|
|
117
|
+
if (claimResult.type === "replay") {
|
|
118
|
+
if (typeof normalizedObservabilityAdapter.recordIdempotentReplay === "function") {
|
|
119
|
+
normalizedObservabilityAdapter.recordIdempotentReplay({
|
|
120
|
+
definition,
|
|
121
|
+
context: normalizedContext
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const replayDurationMs = Date.now() - startedAt;
|
|
126
|
+
if (typeof normalizedObservabilityAdapter.recordExecutionFinish === "function") {
|
|
127
|
+
normalizedObservabilityAdapter.recordExecutionFinish({
|
|
128
|
+
definition,
|
|
129
|
+
context: normalizedContext,
|
|
130
|
+
outcome: "replay",
|
|
131
|
+
durationMs: replayDurationMs,
|
|
132
|
+
idempotencyReplay: true
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await emitAuditEvent(normalizedAuditAdapter, {
|
|
137
|
+
definition,
|
|
138
|
+
context: normalizedContext,
|
|
139
|
+
outcome: "success",
|
|
140
|
+
result: claimResult.replayResult,
|
|
141
|
+
error: null,
|
|
142
|
+
durationMs: replayDurationMs,
|
|
143
|
+
idempotencyReplay: true
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (logger && typeof logger.debug === "function") {
|
|
147
|
+
logger.debug(
|
|
148
|
+
buildActionLogPayload({
|
|
149
|
+
definition,
|
|
150
|
+
context: normalizedContext,
|
|
151
|
+
outcome: "replay",
|
|
152
|
+
durationMs: replayDurationMs,
|
|
153
|
+
errorCode: null,
|
|
154
|
+
idempotencyReplay: true
|
|
155
|
+
}),
|
|
156
|
+
"action.execution"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
result: claimResult.replayResult,
|
|
162
|
+
idempotencyReplay: true
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const executionResult = await definition.execute(normalizedInput, normalizedContext, deps);
|
|
168
|
+
const normalizedResult = await normalizeActionOutput(definition, executionResult, normalizedContext);
|
|
169
|
+
|
|
170
|
+
if (idempotencyClaim && typeof normalizedIdempotencyAdapter.markSucceeded === "function") {
|
|
171
|
+
await normalizedIdempotencyAdapter.markSucceeded({
|
|
172
|
+
definition,
|
|
173
|
+
context: normalizedContext,
|
|
174
|
+
input: normalizedInput,
|
|
175
|
+
result: normalizedResult,
|
|
176
|
+
claim: idempotencyClaim,
|
|
177
|
+
idempotencyKey
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const durationMs = Date.now() - startedAt;
|
|
182
|
+
if (typeof normalizedObservabilityAdapter.recordExecutionFinish === "function") {
|
|
183
|
+
normalizedObservabilityAdapter.recordExecutionFinish({
|
|
184
|
+
definition,
|
|
185
|
+
context: normalizedContext,
|
|
186
|
+
outcome: "success",
|
|
187
|
+
durationMs,
|
|
188
|
+
idempotencyReplay: false
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await emitAuditEvent(normalizedAuditAdapter, {
|
|
193
|
+
definition,
|
|
194
|
+
context: normalizedContext,
|
|
195
|
+
outcome: "success",
|
|
196
|
+
result: normalizedResult,
|
|
197
|
+
error: null,
|
|
198
|
+
durationMs,
|
|
199
|
+
idempotencyReplay: false
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (logger && typeof logger.debug === "function") {
|
|
203
|
+
logger.debug(
|
|
204
|
+
buildActionLogPayload({
|
|
205
|
+
definition,
|
|
206
|
+
context: normalizedContext,
|
|
207
|
+
outcome: "success",
|
|
208
|
+
durationMs,
|
|
209
|
+
errorCode: null,
|
|
210
|
+
idempotencyReplay: false
|
|
211
|
+
}),
|
|
212
|
+
"action.execution"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
result: normalizedResult,
|
|
218
|
+
idempotencyReplay: false
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const executionError = error;
|
|
222
|
+
|
|
223
|
+
if (executionError?.code === "ACTION_VALIDATION_FAILED") {
|
|
224
|
+
if (typeof normalizedObservabilityAdapter.recordValidationFailure === "function") {
|
|
225
|
+
normalizedObservabilityAdapter.recordValidationFailure({
|
|
226
|
+
definition,
|
|
227
|
+
context: normalizedContext,
|
|
228
|
+
error: executionError
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (idempotencyClaim && typeof normalizedIdempotencyAdapter.markFailed === "function") {
|
|
234
|
+
await normalizedIdempotencyAdapter.markFailed({
|
|
235
|
+
definition,
|
|
236
|
+
context: normalizedContext,
|
|
237
|
+
input,
|
|
238
|
+
error: executionError,
|
|
239
|
+
claim: idempotencyClaim,
|
|
240
|
+
idempotencyKey
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const durationMs = Date.now() - startedAt;
|
|
245
|
+
const errorCode = normalizeOutcomeErrorCode(executionError);
|
|
246
|
+
|
|
247
|
+
if (typeof normalizedObservabilityAdapter.recordExecutionFinish === "function") {
|
|
248
|
+
normalizedObservabilityAdapter.recordExecutionFinish({
|
|
249
|
+
definition,
|
|
250
|
+
context: normalizedContext,
|
|
251
|
+
outcome: "failure",
|
|
252
|
+
durationMs,
|
|
253
|
+
error: executionError,
|
|
254
|
+
errorCode,
|
|
255
|
+
idempotencyReplay: false
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await emitAuditEvent(normalizedAuditAdapter, {
|
|
260
|
+
definition,
|
|
261
|
+
context: normalizedContext,
|
|
262
|
+
outcome: "failure",
|
|
263
|
+
result: null,
|
|
264
|
+
error: executionError,
|
|
265
|
+
durationMs,
|
|
266
|
+
idempotencyReplay: false
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (logger && typeof logger.debug === "function") {
|
|
270
|
+
logger.debug(
|
|
271
|
+
buildActionLogPayload({
|
|
272
|
+
definition,
|
|
273
|
+
context: normalizedContext,
|
|
274
|
+
outcome: "failure",
|
|
275
|
+
durationMs,
|
|
276
|
+
errorCode,
|
|
277
|
+
idempotencyReplay: false
|
|
278
|
+
}),
|
|
279
|
+
"action.execution"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
throw executionError;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export { executeActionPipeline };
|