@jskit-ai/kernel 0.1.54 → 0.1.56
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/package.json +3 -2
- package/server/actions/ActionRuntimeServiceProvider.test.js +23 -15
- package/server/http/lib/kernel.test.js +447 -0
- package/server/http/lib/routeRegistration.js +236 -15
- package/server/http/lib/routeTransport.js +126 -0
- package/server/http/lib/routeValidator.js +133 -198
- package/server/http/lib/routeValidator.test.js +385 -278
- package/server/http/lib/router.js +17 -2
- package/server/platform/providerRuntime.test.js +7 -7
- package/server/runtime/bootBootstrapRoutes.js +2 -18
- package/server/runtime/bootBootstrapRoutes.test.js +5 -14
- package/server/runtime/fastifyBootstrap.js +119 -0
- package/server/runtime/fastifyBootstrap.test.js +119 -1
- package/server/runtime/moduleConfig.js +32 -62
- package/server/runtime/moduleConfig.test.js +48 -24
- package/server/support/pageTargets.js +15 -9
- package/server/support/pageTargets.test.js +1 -1
- package/shared/actions/actionContributorHelpers.js +5 -11
- package/shared/actions/actionDefinitions.js +37 -150
- package/shared/actions/actionDefinitions.test.js +117 -136
- package/shared/actions/policies.js +25 -169
- package/shared/actions/policies.test.js +76 -87
- package/shared/actions/registry.test.js +24 -50
- package/shared/support/crudFieldContract.js +322 -0
- package/shared/support/crudFieldContract.test.js +67 -0
- package/shared/support/crudListFilters.js +582 -38
- package/shared/support/crudListFilters.test.js +178 -8
- package/shared/support/crudLookup.js +14 -7
- package/shared/support/crudLookup.test.js +91 -66
- package/shared/support/shellLayoutTargets.test.js +1 -1
- package/shared/validators/composeSchemaDefinitions.js +53 -0
- package/shared/validators/composeSchemaDefinitions.test.js +156 -0
- package/shared/validators/createCursorListValidator.js +22 -35
- package/shared/validators/createCursorListValidator.test.js +22 -23
- package/shared/validators/cursorPaginationQueryValidator.js +14 -24
- package/shared/validators/cursorPaginationQueryValidator.test.js +18 -8
- package/shared/validators/htmlTimeSchemas.js +6 -4
- package/shared/validators/index.js +15 -7
- package/shared/validators/jsonRestSchemaSupport.js +139 -0
- package/shared/validators/mergeObjectSchemas.js +44 -6
- package/shared/validators/mergeObjectSchemas.test.js +60 -35
- package/shared/validators/recordIdParamsValidator.js +19 -52
- package/shared/validators/recordIdParamsValidator.test.js +13 -8
- package/shared/validators/resourceRequiredMetadata.js +3 -3
- package/shared/validators/resourceRequiredMetadata.test.js +29 -16
- package/shared/validators/schemaDefinitions.js +126 -0
- package/shared/validators/schemaDefinitions.test.js +51 -0
- package/shared/validators/schemaPayloadValidation.js +65 -0
- package/test/barrelExposure.test.js +30 -0
- package/test/routeInputContractGuard.test.js +10 -6
- package/shared/validators/mergeValidators.js +0 -89
- package/shared/validators/mergeValidators.test.js +0 -116
- package/shared/validators/nestValidator.js +0 -53
- package/shared/validators/nestValidator.test.js +0 -60
- package/shared/validators/settingsFieldNormalization.js +0 -40
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { mergeObjectSchemas } from "./mergeObjectSchemas.js";
|
|
2
|
-
import { isRecord as isPlainObject } from "../support/normalize.js";
|
|
3
|
-
|
|
4
|
-
function isPromiseLike(value) {
|
|
5
|
-
return Boolean(value) && typeof value.then === "function";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function createErrorFactory(createError) {
|
|
9
|
-
if (typeof createError === "function") {
|
|
10
|
-
return createError;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return (message) => new Error(message);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function mergeValidators(validators = [], options = {}) {
|
|
17
|
-
const sourceValidators = Array.isArray(validators) ? validators : [];
|
|
18
|
-
const context = String(options?.context || "validator").trim() || "validator";
|
|
19
|
-
const requireSchema = options?.requireSchema === true;
|
|
20
|
-
const allowAsyncNormalize = options?.allowAsyncNormalize !== false;
|
|
21
|
-
const requiredSchemaMessage = String(options?.requiredSchemaMessage || `${context}.schema is required.`);
|
|
22
|
-
const normalizeResultMessage = String(options?.normalizeResultMessage || `${context}.normalize must return an object.`);
|
|
23
|
-
const makeError = createErrorFactory(options?.createError);
|
|
24
|
-
const schemas = [];
|
|
25
|
-
const normalizers = [];
|
|
26
|
-
|
|
27
|
-
for (const validator of sourceValidators) {
|
|
28
|
-
if (!isPlainObject(validator)) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (Object.prototype.hasOwnProperty.call(validator, "schema")) {
|
|
33
|
-
schemas.push(validator.schema);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (typeof validator.normalize === "function") {
|
|
37
|
-
normalizers.push(validator.normalize);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (requireSchema && schemas.length < 1) {
|
|
42
|
-
throw makeError(requiredSchemaMessage);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const merged = {};
|
|
46
|
-
if (schemas.length === 1) {
|
|
47
|
-
merged.schema = schemas[0];
|
|
48
|
-
} else if (schemas.length > 1) {
|
|
49
|
-
merged.schema = mergeObjectSchemas(schemas);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (normalizers.length === 1) {
|
|
53
|
-
merged.normalize = normalizers[0];
|
|
54
|
-
} else if (normalizers.length > 1) {
|
|
55
|
-
if (allowAsyncNormalize) {
|
|
56
|
-
merged.normalize = async function normalizeMergedValidators(payload, meta) {
|
|
57
|
-
const normalized = {};
|
|
58
|
-
|
|
59
|
-
for (const normalizer of normalizers) {
|
|
60
|
-
const result = await normalizer(payload, meta);
|
|
61
|
-
if (!isPlainObject(result)) {
|
|
62
|
-
throw makeError(normalizeResultMessage);
|
|
63
|
-
}
|
|
64
|
-
Object.assign(normalized, result);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return normalized;
|
|
68
|
-
};
|
|
69
|
-
} else {
|
|
70
|
-
merged.normalize = function normalizeMergedValidators(payload, meta) {
|
|
71
|
-
const normalized = {};
|
|
72
|
-
|
|
73
|
-
for (const normalizer of normalizers) {
|
|
74
|
-
const result = normalizer(payload, meta);
|
|
75
|
-
if (isPromiseLike(result) || !isPlainObject(result)) {
|
|
76
|
-
throw makeError(normalizeResultMessage);
|
|
77
|
-
}
|
|
78
|
-
Object.assign(normalized, result);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return normalized;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return Object.freeze(merged);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export { mergeValidators };
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
|
-
import { mergeValidators } from "./mergeValidators.js";
|
|
5
|
-
|
|
6
|
-
test("mergeValidators merges schemas and sync normalizers", () => {
|
|
7
|
-
const merged = mergeValidators(
|
|
8
|
-
[
|
|
9
|
-
{
|
|
10
|
-
schema: Type.Object(
|
|
11
|
-
{
|
|
12
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 }))
|
|
13
|
-
},
|
|
14
|
-
{ additionalProperties: false }
|
|
15
|
-
),
|
|
16
|
-
normalize(input = {}) {
|
|
17
|
-
return {
|
|
18
|
-
workspaceSlug: String(input.workspaceSlug || "").trim().toLowerCase()
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
schema: Type.Object(
|
|
24
|
-
{
|
|
25
|
-
recordId: Type.Optional(Type.Union([Type.Integer({ minimum: 1 }), Type.String({ minLength: 1 })]))
|
|
26
|
-
},
|
|
27
|
-
{ additionalProperties: false }
|
|
28
|
-
),
|
|
29
|
-
normalize(input = {}) {
|
|
30
|
-
const parsed = Number(input.recordId);
|
|
31
|
-
return {
|
|
32
|
-
recordId: Number.isInteger(parsed) && parsed > 0 ? parsed : 0
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
{
|
|
38
|
-
context: "route params",
|
|
39
|
-
allowAsyncNormalize: false
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
assert.equal(typeof merged.schema, "object");
|
|
44
|
-
assert.equal(typeof merged.normalize, "function");
|
|
45
|
-
assert.deepEqual(merged.normalize({ workspaceSlug: " ACME ", recordId: "42" }), {
|
|
46
|
-
workspaceSlug: "acme",
|
|
47
|
-
recordId: 42
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("mergeValidators merges async normalizers for action validators", async () => {
|
|
52
|
-
const merged = mergeValidators(
|
|
53
|
-
[
|
|
54
|
-
{
|
|
55
|
-
schema: Type.Object(
|
|
56
|
-
{
|
|
57
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 }))
|
|
58
|
-
},
|
|
59
|
-
{ additionalProperties: false }
|
|
60
|
-
),
|
|
61
|
-
async normalize(input = {}) {
|
|
62
|
-
return {
|
|
63
|
-
workspaceSlug: String(input.workspaceSlug || "").trim().toLowerCase()
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
schema: Type.Object(
|
|
69
|
-
{
|
|
70
|
-
invitesEnabled: Type.Optional(Type.Boolean())
|
|
71
|
-
},
|
|
72
|
-
{ additionalProperties: false }
|
|
73
|
-
),
|
|
74
|
-
normalize(input = {}) {
|
|
75
|
-
return {
|
|
76
|
-
invitesEnabled: input.invitesEnabled === true
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
],
|
|
81
|
-
{
|
|
82
|
-
context: "action input"
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
assert.equal(typeof merged.schema, "object");
|
|
87
|
-
assert.equal(typeof merged.normalize, "function");
|
|
88
|
-
const normalized = await merged.normalize({
|
|
89
|
-
workspaceSlug: " ACME ",
|
|
90
|
-
invitesEnabled: true
|
|
91
|
-
});
|
|
92
|
-
assert.deepEqual(normalized, {
|
|
93
|
-
workspaceSlug: "acme",
|
|
94
|
-
invitesEnabled: true
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("mergeValidators enforces schema requirement when requested", () => {
|
|
99
|
-
assert.throws(
|
|
100
|
-
() =>
|
|
101
|
-
mergeValidators(
|
|
102
|
-
[
|
|
103
|
-
{
|
|
104
|
-
normalize() {
|
|
105
|
-
return {};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
],
|
|
109
|
-
{
|
|
110
|
-
context: "action input",
|
|
111
|
-
requireSchema: true
|
|
112
|
-
}
|
|
113
|
-
),
|
|
114
|
-
/action input\.schema is required/
|
|
115
|
-
);
|
|
116
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { normalizeObjectInput } from "./inputNormalization.js";
|
|
3
|
-
|
|
4
|
-
function normalizeValidator(validator) {
|
|
5
|
-
if (!validator || typeof validator !== "object" || Array.isArray(validator)) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (!Object.hasOwn(validator, "schema")) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return validator;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function nestValidator(key, validator, { required = true } = {}) {
|
|
17
|
-
const normalizedKey = String(key || "").trim();
|
|
18
|
-
if (!normalizedKey) {
|
|
19
|
-
throw new TypeError("nestValidator requires a non-empty key.");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const normalizedValidator = normalizeValidator(validator);
|
|
23
|
-
if (!normalizedValidator) {
|
|
24
|
-
throw new TypeError(`nestValidator(\"${normalizedKey}\") requires a validator object with schema.`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const keySchema = required ? normalizedValidator.schema : Type.Optional(normalizedValidator.schema);
|
|
28
|
-
const schema = Type.Object(
|
|
29
|
-
{
|
|
30
|
-
[normalizedKey]: keySchema
|
|
31
|
-
},
|
|
32
|
-
{ additionalProperties: false }
|
|
33
|
-
);
|
|
34
|
-
const normalizeSection = typeof normalizedValidator.normalize === "function" ? normalizedValidator.normalize : null;
|
|
35
|
-
|
|
36
|
-
return Object.freeze({
|
|
37
|
-
schema,
|
|
38
|
-
async normalize(payload, meta) {
|
|
39
|
-
const source = normalizeObjectInput(payload);
|
|
40
|
-
if (!Object.hasOwn(source, normalizedKey)) {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const sectionPayload = source[normalizedKey];
|
|
45
|
-
const normalizedSection = normalizeSection ? await normalizeSection(sectionPayload, meta) : sectionPayload;
|
|
46
|
-
return {
|
|
47
|
-
[normalizedKey]: normalizedSection
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { nestValidator };
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { Check } from "typebox/value";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import { nestValidator } from "./nestValidator.js";
|
|
6
|
-
|
|
7
|
-
test("nestValidator wraps schema + normalize under one key", async () => {
|
|
8
|
-
const baseValidator = Object.freeze({
|
|
9
|
-
schema: Type.Object(
|
|
10
|
-
{
|
|
11
|
-
name: Type.Optional(Type.String({ minLength: 1 }))
|
|
12
|
-
},
|
|
13
|
-
{ additionalProperties: false }
|
|
14
|
-
),
|
|
15
|
-
normalize(payload = {}) {
|
|
16
|
-
return {
|
|
17
|
-
...(Object.hasOwn(payload, "name") ? { name: String(payload.name).trim() } : {})
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
const validator = nestValidator("payload", baseValidator);
|
|
22
|
-
|
|
23
|
-
const normalized = await validator.normalize({
|
|
24
|
-
payload: {
|
|
25
|
-
name: " Acme "
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
assert.deepEqual(normalized, {
|
|
30
|
-
payload: {
|
|
31
|
-
name: "Acme"
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
assert.equal(Check(validator.schema, normalized), true);
|
|
35
|
-
assert.equal(Check(validator.schema, {}), false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("nestValidator can define optional nested key", async () => {
|
|
39
|
-
const validator = nestValidator(
|
|
40
|
-
"patch",
|
|
41
|
-
Object.freeze({
|
|
42
|
-
schema: Type.Object(
|
|
43
|
-
{
|
|
44
|
-
title: Type.Optional(Type.String({ minLength: 1 }))
|
|
45
|
-
},
|
|
46
|
-
{ additionalProperties: false }
|
|
47
|
-
)
|
|
48
|
-
}),
|
|
49
|
-
{ required: false }
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
assert.deepEqual(await validator.normalize({}), {});
|
|
53
|
-
assert.equal(Check(validator.schema, {}), true);
|
|
54
|
-
assert.equal(Check(validator.schema, { patch: { title: "X" } }), true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("nestValidator rejects invalid key and validator", () => {
|
|
58
|
-
assert.throws(() => nestValidator("", { schema: Type.Object({}) }), /requires a non-empty key/);
|
|
59
|
-
assert.throws(() => nestValidator("payload", null), /requires a validator object with schema/);
|
|
60
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { normalizeObjectInput } from "./inputNormalization.js";
|
|
2
|
-
|
|
3
|
-
function normalizeSettingsFieldInput(payload = {}, fields = []) {
|
|
4
|
-
const source = normalizeObjectInput(payload);
|
|
5
|
-
const normalized = {};
|
|
6
|
-
|
|
7
|
-
for (const field of Array.isArray(fields) ? fields : []) {
|
|
8
|
-
if (!Object.hasOwn(source, field.key)) {
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
normalized[field.key] = field.normalizeInput(source[field.key], {
|
|
12
|
-
payload: source
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return normalized;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeSettingsFieldOutput(payload = {}, fields = []) {
|
|
20
|
-
const settingsSource = normalizeObjectInput(payload);
|
|
21
|
-
const normalized = {};
|
|
22
|
-
|
|
23
|
-
for (const field of Array.isArray(fields) ? fields : []) {
|
|
24
|
-
const rawValue = Object.hasOwn(settingsSource, field.key)
|
|
25
|
-
? settingsSource[field.key]
|
|
26
|
-
: field.resolveDefault({
|
|
27
|
-
settings: settingsSource
|
|
28
|
-
});
|
|
29
|
-
normalized[field.key] = field.normalizeOutput(rawValue, {
|
|
30
|
-
settings: settingsSource
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return normalized;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
normalizeSettingsFieldInput,
|
|
39
|
-
normalizeSettingsFieldOutput
|
|
40
|
-
};
|