@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.
Files changed (55) hide show
  1. package/package.json +3 -2
  2. package/server/actions/ActionRuntimeServiceProvider.test.js +23 -15
  3. package/server/http/lib/kernel.test.js +447 -0
  4. package/server/http/lib/routeRegistration.js +236 -15
  5. package/server/http/lib/routeTransport.js +126 -0
  6. package/server/http/lib/routeValidator.js +133 -198
  7. package/server/http/lib/routeValidator.test.js +385 -278
  8. package/server/http/lib/router.js +17 -2
  9. package/server/platform/providerRuntime.test.js +7 -7
  10. package/server/runtime/bootBootstrapRoutes.js +2 -18
  11. package/server/runtime/bootBootstrapRoutes.test.js +5 -14
  12. package/server/runtime/fastifyBootstrap.js +119 -0
  13. package/server/runtime/fastifyBootstrap.test.js +119 -1
  14. package/server/runtime/moduleConfig.js +32 -62
  15. package/server/runtime/moduleConfig.test.js +48 -24
  16. package/server/support/pageTargets.js +15 -9
  17. package/server/support/pageTargets.test.js +1 -1
  18. package/shared/actions/actionContributorHelpers.js +5 -11
  19. package/shared/actions/actionDefinitions.js +37 -150
  20. package/shared/actions/actionDefinitions.test.js +117 -136
  21. package/shared/actions/policies.js +25 -169
  22. package/shared/actions/policies.test.js +76 -87
  23. package/shared/actions/registry.test.js +24 -50
  24. package/shared/support/crudFieldContract.js +322 -0
  25. package/shared/support/crudFieldContract.test.js +67 -0
  26. package/shared/support/crudListFilters.js +582 -38
  27. package/shared/support/crudListFilters.test.js +178 -8
  28. package/shared/support/crudLookup.js +14 -7
  29. package/shared/support/crudLookup.test.js +91 -66
  30. package/shared/support/shellLayoutTargets.test.js +1 -1
  31. package/shared/validators/composeSchemaDefinitions.js +53 -0
  32. package/shared/validators/composeSchemaDefinitions.test.js +156 -0
  33. package/shared/validators/createCursorListValidator.js +22 -35
  34. package/shared/validators/createCursorListValidator.test.js +22 -23
  35. package/shared/validators/cursorPaginationQueryValidator.js +14 -24
  36. package/shared/validators/cursorPaginationQueryValidator.test.js +18 -8
  37. package/shared/validators/htmlTimeSchemas.js +6 -4
  38. package/shared/validators/index.js +15 -7
  39. package/shared/validators/jsonRestSchemaSupport.js +139 -0
  40. package/shared/validators/mergeObjectSchemas.js +44 -6
  41. package/shared/validators/mergeObjectSchemas.test.js +60 -35
  42. package/shared/validators/recordIdParamsValidator.js +19 -52
  43. package/shared/validators/recordIdParamsValidator.test.js +13 -8
  44. package/shared/validators/resourceRequiredMetadata.js +3 -3
  45. package/shared/validators/resourceRequiredMetadata.test.js +29 -16
  46. package/shared/validators/schemaDefinitions.js +126 -0
  47. package/shared/validators/schemaDefinitions.test.js +51 -0
  48. package/shared/validators/schemaPayloadValidation.js +65 -0
  49. package/test/barrelExposure.test.js +30 -0
  50. package/test/routeInputContractGuard.test.js +10 -6
  51. package/shared/validators/mergeValidators.js +0 -89
  52. package/shared/validators/mergeValidators.test.js +0 -116
  53. package/shared/validators/nestValidator.js +0 -53
  54. package/shared/validators/nestValidator.test.js +0 -60
  55. 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
- };