@beechcms/core 0.6.0 → 0.6.2
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/dist/engine/ddl.d.ts.map +1 -1
- package/dist/engine/ddl.js +7 -1
- package/dist/engine/query.d.ts.map +1 -1
- package/dist/engine/query.js +10 -1
- package/dist/engine/types.d.ts +3 -1
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/validation/cache.d.ts.map +1 -1
- package/dist/engine/validation/cache.js +42 -21
- package/dist/engine/validation/file-branch.d.ts +1 -1
- package/dist/engine/validation/file-branch.d.ts.map +1 -1
- package/dist/engine/validation/file-branch.js +7 -1
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine/validation/index.js +8 -5
- package/dist/engine/validation/primitives.d.ts +2 -1
- package/dist/engine/validation/primitives.d.ts.map +1 -1
- package/dist/engine/validation/primitives.js +6 -2
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.js +14 -4
- package/dist/engine/validation/schema-builders.d.ts.map +1 -1
- package/dist/engine/validation/schema-builders.js +102 -11
- package/package.json +1 -1
package/dist/engine/ddl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../src/engine/ddl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EAOP,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../src/engine/ddl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EAOP,MAAM,YAAY,CAAC;AA8BpB;;GAEG;AACH,eAAO,MAAM,cAAc,aAAgE,CAAA;AAG3F;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAcD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE/C;AAGD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAG9D;AAGD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAM5D;AAgCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAwBtD;AAGD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAwB5D;AAGD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAGD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAoBpD;AAGD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAc1D;AAGD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAgCxD;AAGD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,2BAA2B;IAC3B,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACpC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAA;IAChB,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAA;CACd;AAGD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE,CAgB7D;AAGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/E;AAGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAGD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5E;AAGD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapF;AAGD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAwBtD;AAGD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBtE;AAGD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBnF;AAGD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzE"}
|
package/dist/engine/ddl.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolvePolicies } from './policies.js';
|
|
1
2
|
/**
|
|
2
3
|
* Mapping of core branch types to SQLite column types.
|
|
3
4
|
*/
|
|
@@ -63,7 +64,12 @@ export function isValidColumn(seed, col) {
|
|
|
63
64
|
* @returns An array of indexable branches.
|
|
64
65
|
*/
|
|
65
66
|
export function indexableSearchBranches(seed) {
|
|
66
|
-
return seed.branches.filter(b =>
|
|
67
|
+
return seed.branches.filter(b => {
|
|
68
|
+
if (b.type !== 'text' && b.type !== 'richtext')
|
|
69
|
+
return false;
|
|
70
|
+
const { search, public: isPublic } = resolvePolicies(b);
|
|
71
|
+
return search && isPublic;
|
|
72
|
+
});
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
* The default ON DELETE referential integrity rule for single-value relations.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/engine/query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EAMJ,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/engine/query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EAMJ,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,kBAAkB,CAoF5F"}
|
package/dist/engine/query.js
CHANGED
|
@@ -43,17 +43,26 @@ export function buildSelectQuery(seed, options = {}) {
|
|
|
43
43
|
whereClauses.push(`${table}.status = ?`);
|
|
44
44
|
bindings.push(status);
|
|
45
45
|
}
|
|
46
|
+
const groupClauses = [];
|
|
46
47
|
for (const group of filters) {
|
|
47
48
|
if (!isValidColumn(seed, group.column))
|
|
48
49
|
continue;
|
|
49
50
|
const col = SYSTEM_COLUMNS.has(group.column)
|
|
50
51
|
? `${table}.${group.column}`
|
|
51
52
|
: group.column;
|
|
53
|
+
const condClauses = [];
|
|
52
54
|
for (const cond of group.conditions) {
|
|
53
55
|
const clause = buildFilterCondition(col, group.type, cond, bindings);
|
|
54
56
|
if (clause)
|
|
55
|
-
|
|
57
|
+
condClauses.push(clause);
|
|
56
58
|
}
|
|
59
|
+
if (condClauses.length > 0) {
|
|
60
|
+
groupClauses.push(condClauses.length > 1 ? `(${condClauses.join(' AND ')})` : condClauses[0]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (groupClauses.length > 0) {
|
|
64
|
+
const joiner = options.filterLogic === 'OR' ? ' OR ' : ' AND ';
|
|
65
|
+
whereClauses.push(groupClauses.length > 1 ? `(${groupClauses.join(joiner)})` : groupClauses[0]);
|
|
57
66
|
}
|
|
58
67
|
let selectCols = options.isCount ? 'COUNT(*) as total' : `${table}.*`;
|
|
59
68
|
if (!options.isCount && fields && fields.length > 0) {
|
package/dist/engine/types.d.ts
CHANGED
|
@@ -235,8 +235,10 @@ export interface FilterGroup {
|
|
|
235
235
|
conditions: FilterCondition[];
|
|
236
236
|
}
|
|
237
237
|
export interface SelectOptions {
|
|
238
|
-
/** Filter groups
|
|
238
|
+
/** Filter groups. Joined by `filterLogic` (default AND); conditions within a group are always ANDed. */
|
|
239
239
|
filters?: FilterGroup[];
|
|
240
|
+
/** How top-level `filters` groups are combined. Defaults to 'AND'. */
|
|
241
|
+
filterLogic?: 'AND' | 'OR';
|
|
240
242
|
/** Sort column and direction. Ignored when `kanbanOrder` is set. */
|
|
241
243
|
orderBy?: {
|
|
242
244
|
column: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAsB,IAAI,EAAwC,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAsB,IAAI,EAAwC,MAAM,aAAa,CAAA;AAEjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAgIjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAsCjH"}
|
|
@@ -2,37 +2,46 @@
|
|
|
2
2
|
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { schemaForBranch } from './schema-builders.js';
|
|
5
|
-
/** Max entries kept
|
|
5
|
+
/** Max entries kept per cache map before evicting the least recently used. */
|
|
6
6
|
const SEED_SCHEMA_CACHE_MAX_SIZE = 150;
|
|
7
|
-
/** Cache storing compiled Zod schemas for seeds
|
|
7
|
+
/** Cache storing compiled Zod schemas for relation-free seeds. */
|
|
8
8
|
const seedSchemaCache = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Per-`idGenerator`-instance caches for seeds with relation branches.
|
|
11
|
+
* Partitioning by instance identity keeps schemas compiled with different
|
|
12
|
+
* generators (and thus different `isValid()` semantics) from colliding,
|
|
13
|
+
* without needing a stable identifier on {@link IIdGenerator}.
|
|
14
|
+
*/
|
|
15
|
+
const relationSchemaCacheByGenerator = new WeakMap();
|
|
9
16
|
/**
|
|
10
17
|
* Retrieves a cache entry and marks it as most recently used.
|
|
11
18
|
*
|
|
19
|
+
* @param cache - The cache map to read from.
|
|
12
20
|
* @param key - The cache key.
|
|
13
21
|
* @returns The cached schema, or undefined if absent.
|
|
14
22
|
*/
|
|
15
|
-
function getCachedSchema(key) {
|
|
16
|
-
const cached =
|
|
23
|
+
function getCachedSchema(cache, key) {
|
|
24
|
+
const cached = cache.get(key);
|
|
17
25
|
if (cached) {
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
cache.delete(key);
|
|
27
|
+
cache.set(key, cached);
|
|
20
28
|
}
|
|
21
29
|
return cached;
|
|
22
30
|
}
|
|
23
31
|
/**
|
|
24
32
|
* Stores a cache entry, evicting the least recently used one if over capacity.
|
|
25
33
|
*
|
|
34
|
+
* @param cache - The cache map to write to.
|
|
26
35
|
* @param key - The cache key.
|
|
27
36
|
* @param value - The compiled schema to cache.
|
|
28
37
|
*/
|
|
29
|
-
function setCachedSchema(key, value) {
|
|
30
|
-
if (
|
|
31
|
-
const oldestKey =
|
|
38
|
+
function setCachedSchema(cache, key, value) {
|
|
39
|
+
if (cache.size >= SEED_SCHEMA_CACHE_MAX_SIZE) {
|
|
40
|
+
const oldestKey = cache.keys().next().value;
|
|
32
41
|
if (oldestKey !== undefined)
|
|
33
|
-
|
|
42
|
+
cache.delete(oldestKey);
|
|
34
43
|
}
|
|
35
|
-
|
|
44
|
+
cache.set(key, value);
|
|
36
45
|
}
|
|
37
46
|
/**
|
|
38
47
|
* Recursively generates a typesafe fingerprint object for a branch and its sub-branches.
|
|
@@ -89,13 +98,27 @@ function buildCacheKey(seed, options) {
|
|
|
89
98
|
* @returns The compiled strict Zod object schema.
|
|
90
99
|
*/
|
|
91
100
|
export function compileSeedSchema(seed, options) {
|
|
92
|
-
// Seeds with relation branches
|
|
93
|
-
//
|
|
94
|
-
// SystemIdGenerator vs
|
|
101
|
+
// Seeds with relation branches capture the idGenerator by closure, so their
|
|
102
|
+
// schemas are cached per generator instance instead of in the shared cache
|
|
103
|
+
// (different generators, e.g. SystemIdGenerator vs a test double, have
|
|
104
|
+
// different isValid() semantics).
|
|
95
105
|
const hasRelation = seed.branches.some((b) => b.type === 'relation');
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
const key = buildCacheKey(seed, options);
|
|
107
|
+
let cache;
|
|
108
|
+
if (hasRelation) {
|
|
109
|
+
if (options.idGenerator) {
|
|
110
|
+
cache = relationSchemaCacheByGenerator.get(options.idGenerator);
|
|
111
|
+
if (!cache) {
|
|
112
|
+
cache = new Map();
|
|
113
|
+
relationSchemaCacheByGenerator.set(options.idGenerator, cache);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
cache = seedSchemaCache;
|
|
119
|
+
}
|
|
120
|
+
if (cache) {
|
|
121
|
+
const cached = getCachedSchema(cache, key);
|
|
99
122
|
if (cached)
|
|
100
123
|
return cached;
|
|
101
124
|
}
|
|
@@ -107,9 +130,7 @@ export function compileSeedSchema(seed, options) {
|
|
|
107
130
|
shape[branch.alias] = isRequired ? branchSchema : branchSchema.optional();
|
|
108
131
|
}
|
|
109
132
|
const compiled = z.object(shape).strict();
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
setCachedSchema(key, compiled);
|
|
113
|
-
}
|
|
133
|
+
if (cache)
|
|
134
|
+
setCachedSchema(cache, key, compiled);
|
|
114
135
|
return compiled;
|
|
115
136
|
}
|
|
@@ -42,7 +42,7 @@ export declare function extractFileCandidate(candidate: unknown): FileCandidate
|
|
|
42
42
|
* input representing an asset list.
|
|
43
43
|
*
|
|
44
44
|
* @param raw - The raw input data (can be array, JSON string, or single item).
|
|
45
|
-
* @returns The list of unique file candidates, or null if any item is invalid.
|
|
45
|
+
* @returns The list of unique file candidates, or null if any item is invalid or the list exceeds the cap.
|
|
46
46
|
*/
|
|
47
47
|
export declare function collectAssetListItems(raw: unknown): FileCandidate[] | null;
|
|
48
48
|
//# sourceMappingURL=file-branch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-branch.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/file-branch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAoB3D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAM1F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AA6BD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAI7E;
|
|
1
|
+
{"version":3,"file":"file-branch.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/file-branch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAoB3D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAM1F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AA6BD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAI7E;AAKD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI,CAiB1E"}
|
|
@@ -100,16 +100,22 @@ export function extractFileCandidate(candidate) {
|
|
|
100
100
|
return null;
|
|
101
101
|
return { url, size: extractSizeFromCandidate(candidate) };
|
|
102
102
|
}
|
|
103
|
+
/** Maximum number of items accepted in an asset list to prevent unbounded processing. */
|
|
104
|
+
const MAX_ASSET_LIST_ITEMS = 100;
|
|
103
105
|
/**
|
|
104
106
|
* Collects and deduplicates valid file candidates (url + optional size) from a raw
|
|
105
107
|
* input representing an asset list.
|
|
106
108
|
*
|
|
107
109
|
* @param raw - The raw input data (can be array, JSON string, or single item).
|
|
108
|
-
* @returns The list of unique file candidates, or null if any item is invalid.
|
|
110
|
+
* @returns The list of unique file candidates, or null if any item is invalid or the list exceeds the cap.
|
|
109
111
|
*/
|
|
110
112
|
export function collectAssetListItems(raw) {
|
|
113
|
+
if (raw === null)
|
|
114
|
+
return null;
|
|
111
115
|
const source = typeof raw === 'string' ? tryParseJson(raw) : raw;
|
|
112
116
|
const items = Array.isArray(source) ? source : [source];
|
|
117
|
+
if (items.length > MAX_ASSET_LIST_ITEMS)
|
|
118
|
+
return null;
|
|
113
119
|
const seen = new Set();
|
|
114
120
|
const out = [];
|
|
115
121
|
for (const item of items) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAOhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,0GAA0G;IAC1G,QAAQ,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,+EAA+E;IAC/E,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,8FAA8F;IAC9F,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,4FAA4F;IAC5F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,sEAAsE;IACtE,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,uFAAuF;IACvF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC9B,2BAA2B,EAAE,OAAO,CAAA;IACpC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,YAAY,GAAG,SAAS,CAAA;CACtC,CAAA;AAKD,0CAA0C;AAC1C,QAAA,MAAM,aAAa,YAAI,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAOhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,0GAA0G;IAC1G,QAAQ,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,+EAA+E;IAC/E,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,8FAA8F;IAC9F,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,4FAA4F;IAC5F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,sEAAsE;IACtE,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,uFAAuF;IACvF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC9B,2BAA2B,EAAE,OAAO,CAAA;IACpC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,YAAY,GAAG,SAAS,CAAA;CACtC,CAAA;AAKD,0CAA0C;AAC1C,QAAA,MAAM,aAAa,YAAI,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAA;AAqR/D;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CAqD3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAE5F"}
|
|
@@ -17,9 +17,10 @@ const statusSchema = z.enum(STATUS_VALUES);
|
|
|
17
17
|
* Checks if a value is effectively empty (e.g. null, undefined, empty string, empty array, or empty rich text).
|
|
18
18
|
*
|
|
19
19
|
* @param value - The value to check.
|
|
20
|
+
* @param branchType - The type of the branch being checked.
|
|
20
21
|
* @returns True if effectively empty, false otherwise.
|
|
21
22
|
*/
|
|
22
|
-
function isEffectivelyEmpty(value) {
|
|
23
|
+
function isEffectivelyEmpty(value, branchType) {
|
|
23
24
|
if (value === null || value === undefined)
|
|
24
25
|
return true;
|
|
25
26
|
if (typeof value === 'string')
|
|
@@ -27,10 +28,12 @@ function isEffectivelyEmpty(value) {
|
|
|
27
28
|
if (Array.isArray(value))
|
|
28
29
|
return value.length === 0;
|
|
29
30
|
if (isPlainObject(value)) {
|
|
31
|
+
// Richtext envelope: delegate to the richtext emptiness check.
|
|
30
32
|
if (isRichtextEnvelopeV1(value))
|
|
31
33
|
return isRichtextDocEmpty(value.doc);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
// Raw TipTap doc root: only delegate for richtext branches to avoid misclassifying generic json fields.
|
|
35
|
+
if (branchType === 'richtext' && value.type === 'doc')
|
|
36
|
+
return isRichtextDocEmpty(value);
|
|
34
37
|
return Object.keys(value).length === 0;
|
|
35
38
|
}
|
|
36
39
|
return false;
|
|
@@ -65,7 +68,7 @@ function detectMissingRequired(seed, rawPayload, filtered, parsedData, parseSucc
|
|
|
65
68
|
continue;
|
|
66
69
|
}
|
|
67
70
|
const candidate = parseSucceeded ? parsedData[branch.alias] : filtered[branch.alias];
|
|
68
|
-
if (isEffectivelyEmpty(candidate)) {
|
|
71
|
+
if (isEffectivelyEmpty(candidate, branch.type)) {
|
|
69
72
|
missing.push(branch.alias);
|
|
70
73
|
details.push({
|
|
71
74
|
field: branch.alias,
|
|
@@ -310,7 +313,7 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
|
310
313
|
field: 'data',
|
|
311
314
|
expected: 'at-least-one-valid-field',
|
|
312
315
|
received: 'empty',
|
|
313
|
-
message: 'Payload
|
|
316
|
+
message: 'Payload contains no valid fields after validation; check field types and values',
|
|
314
317
|
});
|
|
315
318
|
}
|
|
316
319
|
return {
|
|
@@ -15,7 +15,8 @@ export declare function stripControlChars(input: string): string;
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function cleanString(input: string): string;
|
|
17
17
|
/**
|
|
18
|
-
* Type-guard checking if a value is a plain object (excluding arrays and
|
|
18
|
+
* Type-guard checking if a value is a plain object (excluding arrays, null, and class instances).
|
|
19
|
+
* Accepts only objects whose prototype is `Object.prototype` or `null` (created via `Object.create(null)`).
|
|
19
20
|
*
|
|
20
21
|
* @param input - The value to check.
|
|
21
22
|
* @returns True if the value is a plain object, false otherwise.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/primitives.ts"],"names":[],"mappings":"AAGA,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAoD,CAAA;AAEpF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/primitives.ts"],"names":[],"mappings":"AAGA,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAoD,CAAA;AAEpF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -21,13 +21,17 @@ export function cleanString(input) {
|
|
|
21
21
|
return stripControlChars(input).trim();
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Type-guard checking if a value is a plain object (excluding arrays and
|
|
24
|
+
* Type-guard checking if a value is a plain object (excluding arrays, null, and class instances).
|
|
25
|
+
* Accepts only objects whose prototype is `Object.prototype` or `null` (created via `Object.create(null)`).
|
|
25
26
|
*
|
|
26
27
|
* @param input - The value to check.
|
|
27
28
|
* @returns True if the value is a plain object, false otherwise.
|
|
28
29
|
*/
|
|
29
30
|
export function isPlainObject(input) {
|
|
30
|
-
|
|
31
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
32
|
+
return false;
|
|
33
|
+
const proto = Object.getPrototypeOf(input);
|
|
34
|
+
return proto === Object.prototype || proto === null;
|
|
31
35
|
}
|
|
32
36
|
const byteLengthEncoder = new TextEncoder();
|
|
33
37
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"richtext-sanitizer.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/richtext-sanitizer.ts"],"names":[],"mappings":"AA0BA,+DAA+D;AAC/D,UAAU,sBAAsB;IAC9B,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAA;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;
|
|
1
|
+
{"version":3,"file":"richtext-sanitizer.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/richtext-sanitizer.ts"],"names":[],"mappings":"AA0BA,+DAA+D;AAC/D,UAAU,sBAAsB;IAC9B,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAA;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AA4GD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CAgCvF;AA2BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS1D"}
|
|
@@ -108,7 +108,14 @@ function sanitizeRichtextJson(raw) {
|
|
|
108
108
|
const cleaned = walkRichtextNode(raw, state);
|
|
109
109
|
const asObject = isPlainObject(cleaned) ? cleaned : {};
|
|
110
110
|
const valid = asObject.type === 'doc';
|
|
111
|
-
|
|
111
|
+
let serialized;
|
|
112
|
+
try {
|
|
113
|
+
serialized = JSON.stringify(asObject);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { value: asObject, dangerous: state.dangerous, valid: false, size: 0 };
|
|
117
|
+
}
|
|
118
|
+
return { value: asObject, dangerous: state.dangerous, valid, size: byteLength(serialized) };
|
|
112
119
|
}
|
|
113
120
|
/**
|
|
114
121
|
* Main entrance helper to sanitize rich text, handling both v1 envelope formats and raw JSON payloads.
|
|
@@ -153,11 +160,14 @@ export function sanitizeRichtext(raw, maxBytes) {
|
|
|
153
160
|
*
|
|
154
161
|
* @param node - The rich text node to inspect.
|
|
155
162
|
* @param sink - Accumulated text chunks.
|
|
163
|
+
* @param depth - Current recursion depth (guard against pathological structures).
|
|
156
164
|
*/
|
|
157
|
-
function gatherRichtextText(node, sink) {
|
|
165
|
+
function gatherRichtextText(node, sink, depth = 0) {
|
|
166
|
+
if (depth > RICHTEXT_MAX_DEPTH)
|
|
167
|
+
return;
|
|
158
168
|
if (Array.isArray(node)) {
|
|
159
169
|
for (const child of node)
|
|
160
|
-
gatherRichtextText(child, sink);
|
|
170
|
+
gatherRichtextText(child, sink, depth + 1);
|
|
161
171
|
return;
|
|
162
172
|
}
|
|
163
173
|
if (!isPlainObject(node))
|
|
@@ -170,7 +180,7 @@ function gatherRichtextText(node, sink) {
|
|
|
170
180
|
}
|
|
171
181
|
if (Array.isArray(node.content)) {
|
|
172
182
|
for (const child of node.content)
|
|
173
|
-
gatherRichtextText(child, sink);
|
|
183
|
+
gatherRichtextText(child, sink, depth + 1);
|
|
174
184
|
}
|
|
175
185
|
}
|
|
176
186
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-builders.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/schema-builders.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"schema-builders.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/schema-builders.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmajD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,UAAU,CAMtF"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { extensionFromUrl, isExtensionAccepted } from '../../media/file-types.js';
|
|
5
|
-
import { cleanString, byteLength } from './primitives.js';
|
|
5
|
+
import { cleanString, stripControlChars, byteLength, isPlainObject } from './primitives.js';
|
|
6
6
|
import { sanitizeRichtext } from './richtext-sanitizer.js';
|
|
7
7
|
import { resolveFileOptions, isAssetListBranch, collectAssetListItems, extractFileCandidate } from './file-branch.js';
|
|
8
8
|
/**
|
|
@@ -12,12 +12,19 @@ import { resolveFileOptions, isAssetListBranch, collectAssetListItems, extractFi
|
|
|
12
12
|
* @returns True if the string is a valid ISO date, false otherwise.
|
|
13
13
|
*/
|
|
14
14
|
function isValidIsoDate(value) {
|
|
15
|
-
if (!/^\d{4}-\d{2}-\d{2}
|
|
15
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}(?::?\d{2})?)?)?$/.test(value)) {
|
|
16
16
|
return false;
|
|
17
|
+
}
|
|
17
18
|
const ts = Date.parse(value);
|
|
18
19
|
if (Number.isNaN(ts))
|
|
19
20
|
return false;
|
|
20
|
-
|
|
21
|
+
const y = parseInt(value.slice(0, 4), 10);
|
|
22
|
+
const m = parseInt(value.slice(5, 7), 10);
|
|
23
|
+
const d = parseInt(value.slice(8, 10), 10);
|
|
24
|
+
const date = new Date(Date.UTC(y, m - 1, d));
|
|
25
|
+
return (date.getUTCFullYear() === y &&
|
|
26
|
+
date.getUTCMonth() === m - 1 &&
|
|
27
|
+
date.getUTCDate() === d);
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* Wraps a Zod schema to allow null values if `allowNull` is enabled.
|
|
@@ -39,7 +46,13 @@ function withNullable(schema, allowNull) {
|
|
|
39
46
|
function withEmptyPreprocessing(schema, allowNull) {
|
|
40
47
|
const fallback = allowNull ? null : undefined;
|
|
41
48
|
const wrapped = allowNull ? schema.optional().nullable() : schema.optional();
|
|
42
|
-
return z.preprocess((val) =>
|
|
49
|
+
return z.preprocess((val) => {
|
|
50
|
+
if (val === '')
|
|
51
|
+
return fallback;
|
|
52
|
+
if (val === null)
|
|
53
|
+
return allowNull ? null : val;
|
|
54
|
+
return val;
|
|
55
|
+
}, wrapped);
|
|
43
56
|
}
|
|
44
57
|
/**
|
|
45
58
|
* Compiles a Zod validation schema for a text branch.
|
|
@@ -165,14 +178,91 @@ function dateSchema(allowNull) {
|
|
|
165
178
|
.refine((value) => isValidIsoDate(value), { message: 'Expected date(ISO)' });
|
|
166
179
|
return withEmptyPreprocessing(base, allowNull);
|
|
167
180
|
}
|
|
181
|
+
/** Max nesting depth accepted when walking a `json` branch value. */
|
|
182
|
+
const JSON_MAX_DEPTH = 50;
|
|
183
|
+
/** Max number of items accepted in a `tags` branch array. */
|
|
184
|
+
const MAX_TAGS_COUNT = 100;
|
|
168
185
|
/**
|
|
169
|
-
*
|
|
186
|
+
* Recursively strips control characters from string leaves while enforcing a
|
|
187
|
+
* depth cap, mirroring the richtext sanitizer's DoS guards for arbitrary JSON.
|
|
170
188
|
*
|
|
189
|
+
* @param value - The value (or sub-value) being walked.
|
|
190
|
+
* @param depth - Current recursion depth.
|
|
191
|
+
* @returns The sanitized value, or `undefined` with `tooDeep: true` if the cap was exceeded.
|
|
192
|
+
*/
|
|
193
|
+
function sanitizeJsonValue(value, depth) {
|
|
194
|
+
if (depth > JSON_MAX_DEPTH)
|
|
195
|
+
return { value: undefined, tooDeep: true };
|
|
196
|
+
if (typeof value === 'string')
|
|
197
|
+
return { value: stripControlChars(value), tooDeep: false };
|
|
198
|
+
if (Array.isArray(value)) {
|
|
199
|
+
const out = [];
|
|
200
|
+
for (const item of value) {
|
|
201
|
+
const res = sanitizeJsonValue(item, depth + 1);
|
|
202
|
+
if (res.tooDeep)
|
|
203
|
+
return res;
|
|
204
|
+
out.push(res.value);
|
|
205
|
+
}
|
|
206
|
+
return { value: out, tooDeep: false };
|
|
207
|
+
}
|
|
208
|
+
if (isPlainObject(value)) {
|
|
209
|
+
const out = {};
|
|
210
|
+
for (const [k, v] of Object.entries(value)) {
|
|
211
|
+
const res = sanitizeJsonValue(v, depth + 1);
|
|
212
|
+
if (res.tooDeep)
|
|
213
|
+
return res;
|
|
214
|
+
out[k] = res.value;
|
|
215
|
+
}
|
|
216
|
+
return { value: out, tooDeep: false };
|
|
217
|
+
}
|
|
218
|
+
return { value, tooDeep: false };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Compiles a Zod validation schema for a `json` branch, bounding size and nesting
|
|
222
|
+
* depth like richtext, and stripping control characters from string leaves.
|
|
223
|
+
*
|
|
224
|
+
* @param options - The resolved validation options.
|
|
171
225
|
* @param allowNull - Whether null values are allowed.
|
|
172
|
-
* @returns The compiled JSON
|
|
226
|
+
* @returns The compiled JSON schema.
|
|
173
227
|
*/
|
|
174
|
-
function
|
|
175
|
-
const base = z
|
|
228
|
+
function jsonSchema(options, allowNull) {
|
|
229
|
+
const base = z
|
|
230
|
+
.union([z.record(z.string(), z.unknown()), z.array(z.unknown())])
|
|
231
|
+
.transform((val, ctx) => {
|
|
232
|
+
// Fail-fast size check before the sanitizing walk, same order as richtext.
|
|
233
|
+
const rawSize = byteLength(JSON.stringify(val));
|
|
234
|
+
if (rawSize > options.maxTextLength) {
|
|
235
|
+
ctx.addIssue({ code: 'custom', message: `Expected json(max:${options.maxTextLength})` });
|
|
236
|
+
return z.NEVER;
|
|
237
|
+
}
|
|
238
|
+
const { value, tooDeep } = sanitizeJsonValue(val, 0);
|
|
239
|
+
if (tooDeep) {
|
|
240
|
+
ctx.addIssue({ code: 'custom', message: `Expected json(maxDepth:${JSON_MAX_DEPTH})` });
|
|
241
|
+
return z.NEVER;
|
|
242
|
+
}
|
|
243
|
+
return value;
|
|
244
|
+
});
|
|
245
|
+
return withEmptyPreprocessing(base, allowNull);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Compiles a Zod validation schema for a `tags` branch, constraining it to a
|
|
249
|
+
* bounded array of cleaned strings (fixes #183: previously any array/object
|
|
250
|
+
* shape passed, breaking callers like the kanban swap that cast to `string[]`).
|
|
251
|
+
*
|
|
252
|
+
* @param options - The resolved validation options.
|
|
253
|
+
* @param allowNull - Whether null values are allowed.
|
|
254
|
+
* @returns The compiled tags schema.
|
|
255
|
+
*/
|
|
256
|
+
function tagsSchema(options, allowNull) {
|
|
257
|
+
const tagSchema = z
|
|
258
|
+
.string()
|
|
259
|
+
.transform((value) => cleanString(value))
|
|
260
|
+
.refine((value) => byteLength(value) <= options.maxTextLength, {
|
|
261
|
+
message: `Expected string(max:${options.maxTextLength})`,
|
|
262
|
+
});
|
|
263
|
+
const base = z.array(tagSchema).max(MAX_TAGS_COUNT, {
|
|
264
|
+
message: `Expected tags(max:${MAX_TAGS_COUNT})`,
|
|
265
|
+
});
|
|
176
266
|
return withEmptyPreprocessing(base, allowNull);
|
|
177
267
|
}
|
|
178
268
|
/**
|
|
@@ -275,7 +365,8 @@ function repeaterSchema(branch, options) {
|
|
|
275
365
|
const shape = {};
|
|
276
366
|
for (const sub of subBranches) {
|
|
277
367
|
const subSchema = schemaForBranch(sub, options);
|
|
278
|
-
|
|
368
|
+
const isRequired = options.enforceRequiredFields && sub[requiredFlag];
|
|
369
|
+
shape[sub.alias] = isRequired ? subSchema : subSchema.optional();
|
|
279
370
|
}
|
|
280
371
|
// z.object() strips unknown keys by default — old item shapes from a renamed/
|
|
281
372
|
// removed sub-field are dropped rather than rejected (sprint 10 §5.1).
|
|
@@ -300,8 +391,8 @@ const BRANCH_SCHEMA_BUILDERS = {
|
|
|
300
391
|
number: (branch, options) => numberSchema(branch, options.allowNull),
|
|
301
392
|
boolean: (_branch, options) => booleanSchema(options.allowNull),
|
|
302
393
|
date: (_branch, options) => dateSchema(options.allowNull),
|
|
303
|
-
json: (_branch, options) =>
|
|
304
|
-
tags: (_branch, options) =>
|
|
394
|
+
json: (_branch, options) => jsonSchema(options, options.allowNull),
|
|
395
|
+
tags: (_branch, options) => tagsSchema(options, options.allowNull),
|
|
305
396
|
file: (branch, options) => fileSchema(branch, options.allowNull),
|
|
306
397
|
relation: (branch, options) => relationSchema(branch, options),
|
|
307
398
|
repeater: (branch, options) => repeaterSchema(branch, options),
|