@jskit-ai/kernel 0.1.245 → 0.1.246
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/client/appConfig.js +2 -1
- package/client/asyncModuleRecovery.js +2 -4
- package/client/index.d.ts +13 -75
- package/client/index.js +1 -1
- package/client/logging.js +8 -2
- package/client/moduleBootstrap.js +41 -33
- package/client/moduleBootstrap.test.js +35 -0
- package/client/shellBootstrap.js +1 -8
- package/client/shellBootstrap.test.js +32 -5
- package/client/shellRouting.js +16 -61
- package/client/shellRouting.test.js +30 -1
- package/client/vite/clientBootstrapPlugin.js +0 -6
- package/client/vite/clientBootstrapPlugin.test.js +2 -3
- package/internal/node/installedPackages.js +14 -58
- package/package.json +2 -4
- package/server/actions/actionCatalogue.js +13 -34
- package/{shared → server}/actions/actionContributorHelpers.js +0 -5
- package/{shared → server}/actions/actionDefinitions.js +24 -131
- package/{shared → server}/actions/actionDefinitions.test.js +75 -21
- package/server/actions/actionEvents.js +97 -2
- package/server/actions/actionEvents.test.js +79 -0
- package/{shared → server}/actions/executionContext.js +3 -13
- package/server/actions/idempotency.js +24 -0
- package/server/actions/pipeline.js +87 -0
- package/{shared → server}/actions/policies.js +20 -33
- package/{shared → server}/actions/policies.test.js +6 -6
- package/{shared → server}/actions/registry.js +3 -22
- package/{shared → server}/actions/registry.test.js +70 -32
- package/server/actions/requestMeta.js +17 -0
- package/server/http/capabilityHttpRuntime.js +1 -1
- package/server/http/capabilityHttpRuntime.test.js +56 -0
- package/server/http/errorHandling.js +291 -0
- package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
- package/server/http/lib/routeRegistration.js +36 -150
- package/server/http/lib/routeTransport.js +2 -0
- package/server/http/lib/routeValidator.js +5 -47
- package/server/http/lib/routeValidator.test.js +15 -17
- package/server/http/lib/router.js +10 -94
- package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
- package/server/platform/providerRuntime/helpers.js +1 -34
- package/server/runtime/integers.js +1 -5
- package/server/runtime/serviceAuthorization.js +4 -9
- package/server/support/appConfig.js +2 -1
- package/server/support/appConfigFiles.js +1 -38
- package/server/support/appConfigFiles.test.js +3 -39
- package/server/support/index.js +1 -20
- package/shared/capabilities/defineProvider.js +1 -1
- package/shared/capabilities/index.js +1 -1
- package/shared/index.js +1 -1
- package/shared/support/index.js +1 -4
- package/shared/support/mobileConfig.js +99 -0
- package/shared/support/mobileConfig.test.js +113 -0
- package/shared/support/normalize.js +0 -108
- package/shared/support/normalize.test.js +0 -134
- package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
- package/shared/support/returnToPath.js +1 -20
- package/shared/support/returnToPath.test.js +1 -36
- package/shared/support/shellLayoutTargets.js +2 -140
- package/shared/support/shellLayoutTargets.test.js +1 -110
- package/shared/support/sorting.js +1 -9
- package/shared/surface/apiPaths.js +1 -82
- package/shared/surface/paths.js +38 -171
- package/shared/surface/registry.js +2 -1
- package/shared/surface/runtime.js +18 -109
- package/shared/surface/runtime.test.js +57 -0
- package/shared/validators/index.js +1 -4
- package/shared/validators/jsonRestSchemaSupport.js +0 -9
- package/shared/validators/recordIdParamsValidator.js +0 -6
- package/shared/validators/schemaDefinitions.js +4 -30
- package/shared/validators/schemaPayloadValidation.js +2 -6
- package/test/barrelExposure.test.js +3 -7
- package/test/exportsContract.test.js +1 -3
- package/client/mobileLaunchRouting.js +0 -231
- package/client/mobileLaunchRouting.test.js +0 -230
- package/server/http/lib/controller.js +0 -183
- package/server/http/lib/controller.test.js +0 -143
- package/server/http/lib/index.js +0 -5
- package/server/runtime/canonicalJson.js +0 -74
- package/server/runtime/entityChangeEvents.js +0 -307
- package/server/runtime/entityChangeEvents.test.js +0 -274
- package/server/runtime/fastifyBootstrap.js +0 -553
- package/server/runtime/moduleConfig.js +0 -223
- package/server/runtime/moduleConfig.test.js +0 -165
- package/server/runtime/pagination.js +0 -13
- package/server/runtime/realtimeNormalization.js +0 -21
- package/server/runtime/routeUtils.js +0 -20
- package/server/runtime/securityAudit.js +0 -269
- package/server/runtime/securityAudit.test.js +0 -41
- package/server/support/pageTargets.js +0 -772
- package/server/support/pageTargets.test.js +0 -659
- package/server/support/shellOutlets.js +0 -576
- package/server/support/shellOutlets.test.js +0 -336
- package/server/surface/index.js +0 -18
- package/shared/actions/audit.js +0 -7
- package/shared/actions/idempotency.js +0 -62
- package/shared/actions/observability.js +0 -10
- package/shared/actions/pipeline.js +0 -309
- package/shared/actions/requestMeta.js +0 -36
- package/shared/actions/textNormalization.js +0 -3
- package/shared/actions/withActionDefaults.js +0 -34
- /package/{shared → server}/actions/executionContext.test.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { normalizeObject } from "../../shared/support/normalize.js";
|
|
4
4
|
import { sortStrings } from "../../shared/support/sorting.js";
|
|
5
|
-
import { fileExists } from "./fileSystem.js";
|
|
5
|
+
import { fileExists, readJsonFile } from "./fileSystem.js";
|
|
6
6
|
|
|
7
7
|
const ROOT_DEPENDENCY_SECTIONS = Object.freeze([
|
|
8
8
|
"dependencies",
|
|
@@ -18,14 +18,6 @@ const JSKIT_PACKAGE_CONFIG_KEYS = Object.freeze([
|
|
|
18
18
|
"vite"
|
|
19
19
|
]);
|
|
20
20
|
|
|
21
|
-
async function readJsonFile(filePath, fallback = {}) {
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(await readFile(filePath, "utf8"));
|
|
24
|
-
} catch {
|
|
25
|
-
return fallback;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
21
|
function collectRootDependencySpecifiers(packageJson = {}) {
|
|
30
22
|
const specifiers = new Map();
|
|
31
23
|
for (const sectionName of ROOT_DEPENDENCY_SECTIONS) {
|
|
@@ -42,7 +34,7 @@ function collectRootDependencySpecifiers(packageJson = {}) {
|
|
|
42
34
|
|
|
43
35
|
function collectPackageDependencyIds(packageJson = {}) {
|
|
44
36
|
const dependencyIds = new Set();
|
|
45
|
-
for (const sectionName of
|
|
37
|
+
for (const sectionName of ROOT_DEPENDENCY_SECTIONS) {
|
|
46
38
|
for (const packageId of Object.keys(normalizeObject(packageJson?.[sectionName]))) {
|
|
47
39
|
const normalizedPackageId = String(packageId || "").trim();
|
|
48
40
|
if (normalizedPackageId) {
|
|
@@ -175,56 +167,27 @@ async function discoverInstalledPackages({ appRoot } = {}) {
|
|
|
175
167
|
const rootPackageJson = await readJsonFile(path.resolve(normalizedAppRoot, "package.json"), {});
|
|
176
168
|
const directSpecifiers = collectRootDependencySpecifiers(rootPackageJson);
|
|
177
169
|
const directPackageIds = new Set(directSpecifiers.keys());
|
|
178
|
-
const runtimeRootPackageIds = new Set(
|
|
179
|
-
["dependencies", "optionalDependencies", "peerDependencies"]
|
|
180
|
-
.flatMap((sectionName) => Object.keys(normalizeObject(rootPackageJson?.[sectionName])))
|
|
181
|
-
);
|
|
182
170
|
const visited = new Set();
|
|
171
|
+
// Breadth-first walk of the dependency graph; the queue stays ordered by packageId.
|
|
183
172
|
const queue = [];
|
|
184
|
-
const queued = new
|
|
173
|
+
const queued = new Set();
|
|
185
174
|
const packages = [];
|
|
186
175
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
if (left.traverseDependencies !== right.traverseDependencies) {
|
|
190
|
-
return left.traverseDependencies ? -1 : 1;
|
|
191
|
-
}
|
|
192
|
-
return left.packageId.localeCompare(right.packageId);
|
|
193
|
-
});
|
|
194
|
-
};
|
|
195
|
-
const enqueue = ({ packageId, parentPackageRoot = "", traverseDependencies = false }) => {
|
|
196
|
-
const normalizedPackageId = String(packageId || "").trim();
|
|
197
|
-
if (!normalizedPackageId || visited.has(normalizedPackageId)) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const existing = queued.get(normalizedPackageId);
|
|
201
|
-
if (existing) {
|
|
202
|
-
if (traverseDependencies && !existing.traverseDependencies) {
|
|
203
|
-
existing.traverseDependencies = true;
|
|
204
|
-
existing.parentPackageRoot = parentPackageRoot || existing.parentPackageRoot;
|
|
205
|
-
sortQueue();
|
|
206
|
-
}
|
|
176
|
+
const enqueue = ({ packageId, parentPackageRoot = "" }) => {
|
|
177
|
+
if (!packageId || visited.has(packageId) || queued.has(packageId)) {
|
|
207
178
|
return;
|
|
208
179
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
traverseDependencies: Boolean(traverseDependencies)
|
|
213
|
-
};
|
|
214
|
-
queue.push(entry);
|
|
215
|
-
queued.set(normalizedPackageId, entry);
|
|
216
|
-
sortQueue();
|
|
180
|
+
queue.push({ packageId, parentPackageRoot });
|
|
181
|
+
queued.add(packageId);
|
|
182
|
+
queue.sort((left, right) => left.packageId.localeCompare(right.packageId));
|
|
217
183
|
};
|
|
218
184
|
|
|
219
185
|
for (const packageId of sortStrings([...directPackageIds])) {
|
|
220
|
-
enqueue({
|
|
221
|
-
packageId,
|
|
222
|
-
traverseDependencies: runtimeRootPackageIds.has(packageId)
|
|
223
|
-
});
|
|
186
|
+
enqueue({ packageId });
|
|
224
187
|
}
|
|
225
188
|
|
|
226
189
|
while (queue.length > 0) {
|
|
227
|
-
const { packageId, parentPackageRoot
|
|
190
|
+
const { packageId, parentPackageRoot } = queue.shift();
|
|
228
191
|
queued.delete(packageId);
|
|
229
192
|
if (!packageId || visited.has(packageId)) {
|
|
230
193
|
continue;
|
|
@@ -252,15 +215,8 @@ async function discoverInstalledPackages({ appRoot } = {}) {
|
|
|
252
215
|
}
|
|
253
216
|
|
|
254
217
|
const dependencyIds = collectPackageDependencyIds(packageJson);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const normalizedDependencyId = String(dependencyId || "").trim();
|
|
258
|
-
enqueue({
|
|
259
|
-
packageId: normalizedDependencyId,
|
|
260
|
-
parentPackageRoot: roots.packageRoot,
|
|
261
|
-
traverseDependencies: true
|
|
262
|
-
});
|
|
263
|
-
}
|
|
218
|
+
for (const dependencyId of dependencyIds) {
|
|
219
|
+
enqueue({ packageId: dependencyId, parentPackageRoot: roots.packageRoot });
|
|
264
220
|
}
|
|
265
221
|
|
|
266
222
|
const packageMetadata = createPackageMetadata(packageJson);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/kernel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.246",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"json-rest-schema": "^1.0.17"
|
|
@@ -25,18 +25,16 @@
|
|
|
25
25
|
"./client/moduleBootstrap": "./client/moduleBootstrap.js",
|
|
26
26
|
"./client/vite": "./client/vite/index.js",
|
|
27
27
|
"./server/actions": "./server/actions/index.js",
|
|
28
|
+
"./server/actions/actionContributorHelpers": "./server/actions/actionContributorHelpers.js",
|
|
28
29
|
"./server/features": "./server/features/index.js",
|
|
29
30
|
"./server/http": "./server/http/index.js",
|
|
30
31
|
"./server/platform": "./server/platform/index.js",
|
|
31
32
|
"./server/runtime": "./server/runtime/index.js",
|
|
32
|
-
"./server/runtime/entityChangeEvents": "./server/runtime/entityChangeEvents.js",
|
|
33
33
|
"./server/runtime/errors": "./server/runtime/errors.js",
|
|
34
34
|
"./server/runtime/requestUrl": "./server/runtime/requestUrl.js",
|
|
35
35
|
"./server/support": "./server/support/index.js",
|
|
36
36
|
"./shared": "./shared/index.js",
|
|
37
37
|
"./shared/capabilities": "./shared/capabilities/index.js",
|
|
38
|
-
"./shared/actions/actionContributorHelpers": "./shared/actions/actionContributorHelpers.js",
|
|
39
|
-
"./shared/actions/textNormalization": "./shared/actions/textNormalization.js",
|
|
40
38
|
"./shared/validators": "./shared/validators/index.js",
|
|
41
39
|
"./shared/validators/inputNormalization": "./shared/validators/inputNormalization.js",
|
|
42
40
|
"./shared/support": "./shared/support/index.js",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createActionRegistry } from "
|
|
1
|
+
import { createActionRegistry } from "./registry.js";
|
|
2
2
|
import { isRecord } from "../../shared/support/normalize.js";
|
|
3
3
|
|
|
4
4
|
function normalizeRegistration(value) {
|
|
@@ -9,21 +9,12 @@ function normalizeRegistration(value) {
|
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function createActionCatalogue({
|
|
13
|
-
idempotencyAdapter,
|
|
14
|
-
auditAdapter,
|
|
15
|
-
observabilityAdapter,
|
|
16
|
-
events,
|
|
17
|
-
logger = null
|
|
18
|
-
} = {}) {
|
|
12
|
+
function createActionCatalogue({ events, logger = null } = {}) {
|
|
19
13
|
const contributors = [];
|
|
20
14
|
const contextContributors = [];
|
|
21
15
|
const contextContributorIds = new Set();
|
|
22
16
|
let registry = createActionRegistry({
|
|
23
17
|
contributors,
|
|
24
|
-
idempotencyAdapter,
|
|
25
|
-
auditAdapter,
|
|
26
|
-
observabilityAdapter,
|
|
27
18
|
events,
|
|
28
19
|
logger
|
|
29
20
|
});
|
|
@@ -36,9 +27,6 @@ function createActionCatalogue({
|
|
|
36
27
|
const nextContributors = [...contributors, normalizeRegistration(contributor)];
|
|
37
28
|
const nextRegistry = createActionRegistry({
|
|
38
29
|
contributors: nextContributors,
|
|
39
|
-
idempotencyAdapter,
|
|
40
|
-
auditAdapter,
|
|
41
|
-
observabilityAdapter,
|
|
42
30
|
events,
|
|
43
31
|
logger
|
|
44
32
|
});
|
|
@@ -104,40 +92,31 @@ function createActionCatalogue({
|
|
|
104
92
|
return Object.freeze(enriched);
|
|
105
93
|
}
|
|
106
94
|
|
|
107
|
-
async function
|
|
95
|
+
async function run(method, payload = {}) {
|
|
108
96
|
const source = payload && typeof payload === "object" ? payload : {};
|
|
109
97
|
const input = source.input && typeof source.input === "object" ? source.input : {};
|
|
98
|
+
const version = source.version == null ? null : source.version;
|
|
110
99
|
const context = await buildExecutionContext({
|
|
111
100
|
actionId: source.actionId,
|
|
112
|
-
version
|
|
101
|
+
version,
|
|
113
102
|
input,
|
|
114
103
|
context: source.context
|
|
115
104
|
});
|
|
116
|
-
return currentRegistry()
|
|
105
|
+
return currentRegistry()[method]({
|
|
117
106
|
actionId: source.actionId,
|
|
118
|
-
version
|
|
107
|
+
version,
|
|
119
108
|
input,
|
|
120
109
|
context,
|
|
121
110
|
deps: isRecord(source.deps) ? source.deps : {}
|
|
122
111
|
});
|
|
123
112
|
}
|
|
124
113
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
input,
|
|
132
|
-
context: source.context
|
|
133
|
-
});
|
|
134
|
-
return currentRegistry().executeStream({
|
|
135
|
-
actionId: source.actionId,
|
|
136
|
-
version: source.version == null ? null : source.version,
|
|
137
|
-
input,
|
|
138
|
-
context,
|
|
139
|
-
deps: isRecord(source.deps) ? source.deps : {}
|
|
140
|
-
});
|
|
114
|
+
function execute(payload) {
|
|
115
|
+
return run("execute", payload);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function executeStream(payload) {
|
|
119
|
+
return run("executeStream", payload);
|
|
141
120
|
}
|
|
142
121
|
|
|
143
122
|
function listDefinitions() {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
-
import { normalizeObject, normalizePositiveInteger as toPositiveInteger } from "../support/normalize.js";
|
|
3
|
-
import { hasPermission } from "../support/permissions.js";
|
|
4
2
|
|
|
5
3
|
function requireServiceMethod(service, methodName, contributorId, { serviceLabel } = {}) {
|
|
6
4
|
if (!service || typeof service[methodName] !== "function") {
|
|
@@ -19,10 +17,7 @@ const emptyInputValidator = Object.freeze({
|
|
|
19
17
|
});
|
|
20
18
|
|
|
21
19
|
export {
|
|
22
|
-
normalizeObject,
|
|
23
|
-
toPositiveInteger,
|
|
24
20
|
requireServiceMethod,
|
|
25
21
|
resolveRequest,
|
|
26
|
-
hasPermission,
|
|
27
22
|
emptyInputValidator
|
|
28
23
|
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from "
|
|
4
|
-
import {
|
|
5
|
-
import { normalizePermissionList } from "../support/permissions.js";
|
|
6
|
-
import { normalizeText } from "./textNormalization.js";
|
|
1
|
+
import { normalizeSingleSchemaDefinition } from "../../shared/validators/index.js";
|
|
2
|
+
import { isRecord as isPlainObject, normalizePositiveInteger, normalizeText } from "../../shared/support/normalize.js";
|
|
3
|
+
import { normalizePermissionList } from "../../shared/support/permissions.js";
|
|
4
|
+
import { ARCHITECTURE_ID_PATTERN } from "../../shared/capabilities/defineProvider.js";
|
|
7
5
|
|
|
8
6
|
const ACTION_KINDS = Object.freeze(["query", "command", "stream"]);
|
|
9
7
|
const ACTION_IDEMPOTENCY_POLICIES = Object.freeze(["none", "optional", "required", "domain_native"]);
|
|
@@ -12,7 +10,6 @@ const ACTION_PERMISSION_REQUIRE_MODES = Object.freeze(["none", "authenticated",
|
|
|
12
10
|
const ACTION_KIND_SET = new Set(ACTION_KINDS);
|
|
13
11
|
const ACTION_IDEMPOTENCY_SET = new Set(ACTION_IDEMPOTENCY_POLICIES);
|
|
14
12
|
const ACTION_PERMISSION_REQUIRE_SET = new Set(ACTION_PERMISSION_REQUIRE_MODES);
|
|
15
|
-
const ACTION_DOMAIN_PATTERN = /^[a-z][a-z0-9_.-]*$/;
|
|
16
13
|
|
|
17
14
|
class ActionRuntimeError extends Error {
|
|
18
15
|
constructor(status, message, options = {}) {
|
|
@@ -38,10 +35,10 @@ function normalizeActionDomain(value, { context = "domain", errorCode = "ACTION_
|
|
|
38
35
|
code: errorCode
|
|
39
36
|
});
|
|
40
37
|
}
|
|
41
|
-
if (!
|
|
38
|
+
if (!ARCHITECTURE_ID_PATTERN.test(domain)) {
|
|
42
39
|
throw createActionRuntimeError(
|
|
43
40
|
500,
|
|
44
|
-
`${context} must match ${
|
|
41
|
+
`${context} must match ${ARCHITECTURE_ID_PATTERN.toString()} (lowercase letters, digits, _, -, .).`,
|
|
45
42
|
{
|
|
46
43
|
code: errorCode
|
|
47
44
|
}
|
|
@@ -51,23 +48,13 @@ function normalizeActionDomain(value, { context = "domain", errorCode = "ACTION_
|
|
|
51
48
|
return domain;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
function normalizeStringArray(value,
|
|
51
|
+
function normalizeStringArray(value, fieldName) {
|
|
55
52
|
const source = Array.isArray(value) ? value : [];
|
|
56
53
|
const normalized = Array.from(
|
|
57
|
-
new Set(
|
|
58
|
-
source
|
|
59
|
-
.map((entry) => normalizeText(entry).toLowerCase())
|
|
60
|
-
.filter(Boolean)
|
|
61
|
-
.filter((entry) => {
|
|
62
|
-
if (!(allowedSet instanceof Set)) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
return allowedSet.has(entry);
|
|
66
|
-
})
|
|
67
|
-
)
|
|
54
|
+
new Set(source.map((entry) => normalizeText(entry).toLowerCase()).filter(Boolean))
|
|
68
55
|
);
|
|
69
56
|
|
|
70
|
-
if (
|
|
57
|
+
if (normalized.length < 1) {
|
|
71
58
|
throw createActionRuntimeError(500, `Action definition ${fieldName} must include at least one value.`, {
|
|
72
59
|
code: "ACTION_DEFINITION_INVALID"
|
|
73
60
|
});
|
|
@@ -76,7 +63,7 @@ function normalizeStringArray(value, { fieldName, allowedSet, allowEmpty = false
|
|
|
76
63
|
return normalized;
|
|
77
64
|
}
|
|
78
65
|
|
|
79
|
-
function
|
|
66
|
+
function normalizeActionSchema(value, fieldName, { required = false, defaultMode = "" } = {}) {
|
|
80
67
|
if (value == null) {
|
|
81
68
|
if (!required) {
|
|
82
69
|
return null;
|
|
@@ -87,6 +74,12 @@ function normalizeSingleActionSchema(value, fieldName, { required = false, defau
|
|
|
87
74
|
});
|
|
88
75
|
}
|
|
89
76
|
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
|
|
79
|
+
code: "ACTION_DEFINITION_INVALID"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
90
83
|
if (!isPlainObject(value)) {
|
|
91
84
|
throw createActionRuntimeError(500, `Action definition ${fieldName} must be a json-rest-schema schema definition.`, {
|
|
92
85
|
code: "ACTION_DEFINITION_INVALID"
|
|
@@ -105,52 +98,6 @@ function normalizeSingleActionSchema(value, fieldName, { required = false, defau
|
|
|
105
98
|
}
|
|
106
99
|
}
|
|
107
100
|
|
|
108
|
-
function normalizeActionInputDefinition(value, fieldName, { required = false } = {}) {
|
|
109
|
-
if (value == null) {
|
|
110
|
-
if (!required) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
throw createActionRuntimeError(500, `Action definition ${fieldName} is required.`, {
|
|
115
|
-
code: "ACTION_DEFINITION_INVALID"
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (Array.isArray(value)) {
|
|
120
|
-
throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
|
|
121
|
-
code: "ACTION_DEFINITION_INVALID"
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return normalizeSingleActionSchema(value, fieldName, {
|
|
126
|
-
required,
|
|
127
|
-
defaultMode: "patch"
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function normalizeActionOutputDefinition(value, fieldName, { required = false } = {}) {
|
|
132
|
-
if (value == null) {
|
|
133
|
-
if (!required) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
throw createActionRuntimeError(500, `Action definition ${fieldName} is required.`, {
|
|
138
|
-
code: "ACTION_DEFINITION_INVALID"
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (Array.isArray(value)) {
|
|
143
|
-
throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
|
|
144
|
-
code: "ACTION_DEFINITION_INVALID"
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return normalizeSingleActionSchema(value, fieldName, {
|
|
149
|
-
required,
|
|
150
|
-
defaultMode: "replace"
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
101
|
function normalizeActionPermission(permission, actionId) {
|
|
155
102
|
if (permission == null) {
|
|
156
103
|
return Object.freeze({
|
|
@@ -190,42 +137,6 @@ function normalizeActionPermission(permission, actionId) {
|
|
|
190
137
|
});
|
|
191
138
|
}
|
|
192
139
|
|
|
193
|
-
function normalizeAuditConfig(audit, { actionId }) {
|
|
194
|
-
const source = audit && typeof audit === "object" ? audit : {};
|
|
195
|
-
const actionName = normalizeText(source.actionName || actionId);
|
|
196
|
-
const metadataBuilder = source.metadataBuilder;
|
|
197
|
-
const piiTags = normalizeStringArray(source.piiTags, {
|
|
198
|
-
fieldName: "audit.piiTags",
|
|
199
|
-
allowEmpty: true
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
if (metadataBuilder != null && typeof metadataBuilder !== "function") {
|
|
203
|
-
throw createActionRuntimeError(500, "Action definition audit.metadataBuilder must be a function when provided.", {
|
|
204
|
-
code: "ACTION_DEFINITION_INVALID"
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return Object.freeze({
|
|
209
|
-
actionName,
|
|
210
|
-
metadataBuilder: typeof metadataBuilder === "function" ? metadataBuilder : null,
|
|
211
|
-
piiTags
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function normalizeObservabilityConfig(observability) {
|
|
216
|
-
const source = observability && typeof observability === "object" ? observability : {};
|
|
217
|
-
const metricTags = normalizeStringArray(source.metricTags, {
|
|
218
|
-
fieldName: "observability.metricTags",
|
|
219
|
-
allowEmpty: true
|
|
220
|
-
});
|
|
221
|
-
const sampleRate = Number(source.sampleRate);
|
|
222
|
-
|
|
223
|
-
return Object.freeze({
|
|
224
|
-
metricTags,
|
|
225
|
-
sampleRate: Number.isFinite(sampleRate) && sampleRate >= 0 && sampleRate <= 1 ? sampleRate : null
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
140
|
function normalizeActionExtensions(value) {
|
|
230
141
|
if (value == null) {
|
|
231
142
|
return Object.freeze({});
|
|
@@ -286,13 +197,8 @@ function normalizeActionDefinition(definition, { contributorId = "", contributor
|
|
|
286
197
|
});
|
|
287
198
|
}
|
|
288
199
|
|
|
289
|
-
const channels = normalizeStringArray(source.channels,
|
|
290
|
-
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
const surfaces = normalizeStringArray(source.surfaces, {
|
|
294
|
-
fieldName: "surfaces"
|
|
295
|
-
});
|
|
200
|
+
const channels = normalizeStringArray(source.channels, "channels");
|
|
201
|
+
const surfaces = normalizeStringArray(source.surfaces, "surfaces");
|
|
296
202
|
|
|
297
203
|
const idempotency = normalizeText(source.idempotency || "none").toLowerCase();
|
|
298
204
|
if (!ACTION_IDEMPOTENCY_SET.has(idempotency)) {
|
|
@@ -318,18 +224,15 @@ function normalizeActionDefinition(definition, { contributorId = "", contributor
|
|
|
318
224
|
kind,
|
|
319
225
|
channels,
|
|
320
226
|
surfaces,
|
|
321
|
-
input:
|
|
322
|
-
required: true
|
|
227
|
+
input: normalizeActionSchema(source.input, "input", {
|
|
228
|
+
required: true,
|
|
229
|
+
defaultMode: "patch"
|
|
323
230
|
}),
|
|
324
|
-
output:
|
|
325
|
-
|
|
231
|
+
output: normalizeActionSchema(source.output, "output", {
|
|
232
|
+
defaultMode: "replace"
|
|
326
233
|
}),
|
|
327
234
|
idempotency,
|
|
328
235
|
permission: normalizeActionPermission(source.permission, id),
|
|
329
|
-
audit: normalizeAuditConfig(source.audit, {
|
|
330
|
-
actionId: id
|
|
331
|
-
}),
|
|
332
|
-
observability: normalizeObservabilityConfig(source.observability),
|
|
333
236
|
extensions: normalizeActionExtensions(source.extensions),
|
|
334
237
|
events: normalizeActionEvents(source.events, id),
|
|
335
238
|
execute: source.execute,
|
|
@@ -371,15 +274,7 @@ function createActionVersionKey(actionId, version) {
|
|
|
371
274
|
}
|
|
372
275
|
|
|
373
276
|
const __testables = {
|
|
374
|
-
|
|
375
|
-
isPlainObject,
|
|
376
|
-
normalizeStringArray,
|
|
377
|
-
normalizeSingleActionSchema,
|
|
378
|
-
normalizeActionInputDefinition,
|
|
379
|
-
normalizeActionOutputDefinition,
|
|
380
|
-
normalizeActionPermission,
|
|
381
|
-
normalizeAuditConfig,
|
|
382
|
-
normalizeObservabilityConfig,
|
|
277
|
+
normalizeActionSchema,
|
|
383
278
|
normalizeActionExtensions
|
|
384
279
|
};
|
|
385
280
|
|
|
@@ -388,8 +283,6 @@ export {
|
|
|
388
283
|
createActionRuntimeError,
|
|
389
284
|
normalizeActionDefinition,
|
|
390
285
|
normalizeActionContributor,
|
|
391
|
-
normalizeActionDomain,
|
|
392
286
|
createActionVersionKey,
|
|
393
|
-
isPlainObject,
|
|
394
287
|
__testables
|
|
395
288
|
};
|
|
@@ -56,34 +56,34 @@ function createPatchSchema() {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
test("
|
|
60
|
-
const definition = __testables.
|
|
59
|
+
test("normalizeActionSchema accepts a single schema definition", () => {
|
|
60
|
+
const definition = __testables.normalizeActionSchema(
|
|
61
61
|
createPatchSchema(),
|
|
62
62
|
"input",
|
|
63
|
-
{ required: true }
|
|
63
|
+
{ required: true, defaultMode: "patch" }
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
assertJsonRestSchemaDefinition(definition);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
test("
|
|
69
|
+
test("normalizeActionSchema rejects section-map syntax", () => {
|
|
70
70
|
assert.throws(
|
|
71
71
|
() =>
|
|
72
|
-
__testables.
|
|
72
|
+
__testables.normalizeActionSchema(
|
|
73
73
|
{
|
|
74
74
|
payload: createPatchSchema()
|
|
75
75
|
},
|
|
76
76
|
"input",
|
|
77
|
-
{ required: true }
|
|
77
|
+
{ required: true, defaultMode: "patch" }
|
|
78
78
|
),
|
|
79
79
|
/Action definition input must be a schema definition object/
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("
|
|
83
|
+
test("normalizeActionSchema rejects bare schema instances", () => {
|
|
84
84
|
assert.throws(
|
|
85
85
|
() =>
|
|
86
|
-
__testables.
|
|
86
|
+
__testables.normalizeActionSchema(
|
|
87
87
|
createSchema({
|
|
88
88
|
name: {
|
|
89
89
|
type: "string",
|
|
@@ -91,22 +91,22 @@ test("normalizeActionInputDefinition rejects bare schema instances", () => {
|
|
|
91
91
|
}
|
|
92
92
|
}),
|
|
93
93
|
"input",
|
|
94
|
-
{ required: true }
|
|
94
|
+
{ required: true, defaultMode: "patch" }
|
|
95
95
|
),
|
|
96
96
|
/Action definition input must be a schema definition object/
|
|
97
97
|
);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
test("
|
|
100
|
+
test("normalizeActionSchema rejects validator arrays", () => {
|
|
101
101
|
assert.throws(
|
|
102
102
|
() =>
|
|
103
|
-
__testables.
|
|
103
|
+
__testables.normalizeActionSchema(
|
|
104
104
|
[
|
|
105
105
|
createWorkspaceSlugSchema(),
|
|
106
106
|
createPatchSchema()
|
|
107
107
|
],
|
|
108
108
|
"input",
|
|
109
|
-
{ required: true }
|
|
109
|
+
{ required: true, defaultMode: "patch" }
|
|
110
110
|
),
|
|
111
111
|
/input must be a single schema definition/
|
|
112
112
|
);
|
|
@@ -148,8 +148,8 @@ test("normalizeActionDefinition stays channel-agnostic and ignores unknown extra
|
|
|
148
148
|
assert.equal(Object.prototype.hasOwnProperty.call(definition, "customExtension"), false);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test("
|
|
152
|
-
const output = __testables.
|
|
151
|
+
test("normalizeActionSchema accepts single schema definitions", () => {
|
|
152
|
+
const output = __testables.normalizeActionSchema(
|
|
153
153
|
{
|
|
154
154
|
schema: createSchema({
|
|
155
155
|
ok: {
|
|
@@ -159,35 +159,89 @@ test("normalizeActionOutputDefinition accepts single schema definitions", () =>
|
|
|
159
159
|
})
|
|
160
160
|
},
|
|
161
161
|
"output",
|
|
162
|
-
{
|
|
162
|
+
{ defaultMode: "replace" }
|
|
163
163
|
);
|
|
164
164
|
|
|
165
165
|
assertJsonRestSchemaDefinition(output);
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
test("
|
|
168
|
+
test("normalizeActionSchema rejects section-map syntax", () => {
|
|
169
169
|
assert.throws(
|
|
170
170
|
() =>
|
|
171
|
-
__testables.
|
|
171
|
+
__testables.normalizeActionSchema(
|
|
172
172
|
{
|
|
173
173
|
payload: createPatchSchema()
|
|
174
174
|
},
|
|
175
175
|
"output",
|
|
176
|
-
{
|
|
176
|
+
{ defaultMode: "replace" }
|
|
177
177
|
),
|
|
178
178
|
/Action definition output must be a schema definition object/
|
|
179
179
|
);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
test("
|
|
183
|
-
const definition = __testables.
|
|
182
|
+
test("normalizeActionSchema preserves mode for json-rest-schema definitions", () => {
|
|
183
|
+
const definition = __testables.normalizeActionSchema(
|
|
184
184
|
{
|
|
185
185
|
schema: createMockJsonRestSchema(),
|
|
186
186
|
mode: "patch"
|
|
187
187
|
},
|
|
188
188
|
"input",
|
|
189
|
-
{ required: true }
|
|
189
|
+
{ required: true, defaultMode: "patch" }
|
|
190
190
|
);
|
|
191
191
|
|
|
192
192
|
assert.equal(definition.mode, "patch");
|
|
193
193
|
});
|
|
194
|
+
|
|
195
|
+
test("normalizeActionSchema rejects a missing required schema and allows a missing optional one", () => {
|
|
196
|
+
assert.equal(__testables.normalizeActionSchema(null, "output"), null);
|
|
197
|
+
assert.throws(
|
|
198
|
+
() => __testables.normalizeActionSchema(undefined, "input", { required: true }),
|
|
199
|
+
/Action definition input is required/
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("normalizeActionDefinition keeps only the contract the runtime enforces", () => {
|
|
204
|
+
const definition = normalizeActionDefinition({
|
|
205
|
+
id: "demo.status.read",
|
|
206
|
+
domain: "demo",
|
|
207
|
+
channels: ["api"],
|
|
208
|
+
surfaces: ["app"],
|
|
209
|
+
input: { schema: createSchema({}) },
|
|
210
|
+
audit: { actionName: "demo.status.read" },
|
|
211
|
+
observability: {},
|
|
212
|
+
execute: async () => ({})
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
assert.equal(Object.hasOwn(definition, "audit"), false);
|
|
216
|
+
assert.equal(Object.hasOwn(definition, "observability"), false);
|
|
217
|
+
assert.deepEqual(Object.keys(definition).sort(), [
|
|
218
|
+
"channels",
|
|
219
|
+
"contributorId",
|
|
220
|
+
"domain",
|
|
221
|
+
"events",
|
|
222
|
+
"execute",
|
|
223
|
+
"extensions",
|
|
224
|
+
"id",
|
|
225
|
+
"idempotency",
|
|
226
|
+
"input",
|
|
227
|
+
"kind",
|
|
228
|
+
"output",
|
|
229
|
+
"permission",
|
|
230
|
+
"surfaces",
|
|
231
|
+
"version"
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("normalizeActionDefinition validates domains with the architecture id pattern", () => {
|
|
236
|
+
const base = {
|
|
237
|
+
id: "demo.status.read",
|
|
238
|
+
channels: ["api"],
|
|
239
|
+
surfaces: ["app"],
|
|
240
|
+
input: { schema: createSchema({}) },
|
|
241
|
+
execute: async () => ({})
|
|
242
|
+
};
|
|
243
|
+
assert.equal(normalizeActionDefinition({ ...base, domain: " Demo.Status " }).domain, "demo.status");
|
|
244
|
+
assert.throws(() => normalizeActionDefinition({ ...base, domain: "1demo" }), {
|
|
245
|
+
code: "ACTION_DEFINITION_INVALID"
|
|
246
|
+
});
|
|
247
|
+
});
|