@beechcms/core 0.4.3 → 0.5.0
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.d.ts.map +1 -1
- package/dist/engine.js +7 -3
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +205 -152
- package/package.json +1 -1
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EAKN,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAgFnB;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAsBtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAuB5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAkBpD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAc1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAgCxD;AAID;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EAKN,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAgFnB;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAsBtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAuB5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAkBpD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAc1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAgCxD;AAID;;;;GAIG;AAGH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,kBAAkB,CAqE5F;AAgHD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE,CAc7D;AAID;;;GAGG;AAEH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAmCrF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA6BzE"}
|
package/dist/engine.js
CHANGED
|
@@ -212,6 +212,7 @@ export function generateFtsTriggers(seed) {
|
|
|
212
212
|
* Never uses json_extract — every column is a real column.
|
|
213
213
|
* Unknown columns in filters/orderBy are ignored (fail-closed).
|
|
214
214
|
*/
|
|
215
|
+
//TODO: buildSelectQuery ha troppe responsabilità, va suddiviso in più funzioni
|
|
215
216
|
export function buildSelectQuery(seed, options = {}) {
|
|
216
217
|
const table = tableName(seed);
|
|
217
218
|
const { filters = [], orderBy, pagination, status, search, fields } = options;
|
|
@@ -278,6 +279,7 @@ export function buildSelectQuery(seed, options = {}) {
|
|
|
278
279
|
}
|
|
279
280
|
return { sql, bindings };
|
|
280
281
|
}
|
|
282
|
+
//TODO: normalizeFilterValue ha troppe responsabilità, va suddiviso in più funzioni
|
|
281
283
|
function normalizeFilterValue(type, value) {
|
|
282
284
|
if (value === null || value === undefined)
|
|
283
285
|
return null;
|
|
@@ -286,7 +288,7 @@ function normalizeFilterValue(type, value) {
|
|
|
286
288
|
return value;
|
|
287
289
|
if (typeof value === 'string' && value.trim() !== '') {
|
|
288
290
|
const d = new Date(value);
|
|
289
|
-
return isNaN(d.getTime()) ? null : Math.floor(d.getTime() / 1000);
|
|
291
|
+
return Number.isNaN(d.getTime()) ? null : Math.floor(d.getTime() / 1000);
|
|
290
292
|
}
|
|
291
293
|
return null;
|
|
292
294
|
}
|
|
@@ -298,12 +300,13 @@ function normalizeFilterValue(type, value) {
|
|
|
298
300
|
return value;
|
|
299
301
|
if (typeof value === 'string' && value.trim() !== '') {
|
|
300
302
|
const n = Number(value);
|
|
301
|
-
return isNaN(n) ? null : n;
|
|
303
|
+
return Number.isNaN(n) ? null : n;
|
|
302
304
|
}
|
|
303
305
|
return null;
|
|
304
306
|
}
|
|
305
307
|
return value;
|
|
306
308
|
}
|
|
309
|
+
//TODO: buildFilterCondition ha troppe responsabilità, va suddiviso in più funzioni
|
|
307
310
|
function buildFilterCondition(col, type, cond, bindings) {
|
|
308
311
|
const { op, value } = cond;
|
|
309
312
|
if (op === 'is_empty') {
|
|
@@ -392,6 +395,7 @@ export function getExpectedColumns(seed) {
|
|
|
392
395
|
* Serializes a value for writing to the DB.
|
|
393
396
|
* boolean → 0/1 | date → Unix timestamp | json/asset-list → JSON string
|
|
394
397
|
*/
|
|
398
|
+
//TODO: serializeForDb ha troppe responsabilità, va suddiviso in più funzioni
|
|
395
399
|
export function serializeForDb(branch, value) {
|
|
396
400
|
if (value === null || value === undefined)
|
|
397
401
|
return null;
|
|
@@ -407,7 +411,7 @@ export function serializeForDb(branch, value) {
|
|
|
407
411
|
return value;
|
|
408
412
|
if (typeof value === 'string') {
|
|
409
413
|
const d = new Date(value);
|
|
410
|
-
if (isNaN(d.getTime()))
|
|
414
|
+
if (Number.isNaN(d.getTime()))
|
|
411
415
|
return null;
|
|
412
416
|
if (branch.format === 'date') {
|
|
413
417
|
const midnight = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file' | 'tags';
|
|
2
|
+
/** Configurazioni specializzate per il tipo di branch 'number' */
|
|
3
|
+
export interface NumberFieldOptions {
|
|
4
|
+
/** Stile di presentazione visiva. Default: 'decimal' */
|
|
5
|
+
format?: 'decimal' | 'currency' | 'percentage' | 'compact';
|
|
6
|
+
/** Codice valuta ISO 4217 (obbligatorio se format === 'currency'). Es. 'EUR', 'USD' */
|
|
7
|
+
currency?: string;
|
|
8
|
+
/** Testo personalizzato da anteporre al valore formattato */
|
|
9
|
+
prefix?: string;
|
|
10
|
+
/** Unità di misura personalizzata da appendere al valore formattato (es. 'kg', 'm²') */
|
|
11
|
+
suffix?: string;
|
|
12
|
+
/** Numero esplicito di cifre decimali da visualizzare e imporre */
|
|
13
|
+
decimals?: number;
|
|
14
|
+
/** Abilita/disabilita il separatore delle migliaia. Default: true */
|
|
15
|
+
grouping?: boolean;
|
|
16
|
+
/** Meccanismo di input alternativo per l'editor delle entità. Default: 'input' */
|
|
17
|
+
control?: 'input' | 'slider' | 'rating' | 'stepper';
|
|
18
|
+
/** Valore minimo consentito */
|
|
19
|
+
min?: number;
|
|
20
|
+
/** Valore massimo consentito */
|
|
21
|
+
max?: number;
|
|
22
|
+
/** Incremento del valore (es. 1 per interi stretti, 0.5 per mezzi passi) */
|
|
23
|
+
step?: number;
|
|
24
|
+
}
|
|
2
25
|
/** Branch: definizione di una proprietà. alias = nome colonna SQL. */
|
|
3
26
|
export interface Branch {
|
|
4
27
|
/** Alias human-readable, usato nel payload API e come nome colonna SQL nella tabella dedicata */
|
|
@@ -45,6 +68,8 @@ export interface Branch {
|
|
|
45
68
|
/** Il campo è incluso nelle risposte della Public API. Default: true */
|
|
46
69
|
public?: boolean;
|
|
47
70
|
};
|
|
71
|
+
/** Opzioni avanzate per i campi numerici. Ignorato se type !== 'number' */
|
|
72
|
+
numberOptions?: NumberFieldOptions;
|
|
48
73
|
}
|
|
49
74
|
/** Dashboard-specific config embedded in a Seed. All fields optional — defaults applied by the dashboard. */
|
|
50
75
|
export interface DashboardSeedConfig {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;AAEvG,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,iGAAiG;IACjG,KAAK,EAAE,MAAM,CAAA;IACb,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,sBAAsB;IACtB,IAAI,EAAE,UAAU,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAA;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE;QACT,yDAAyD;QACzD,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;QACtC,0EAA0E;QAC1E,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;QACzC,yEAAyE;QACzE,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,mFAAmF;QACnF,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,wFAAwF;QACxF,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,wEAAwE;QACxE,MAAM,CAAC,EAAE,OAAO,CAAA;KACjB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;AAEvG,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IAEjC,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAA;IAC1D,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAGlB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAA;IAGnD,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,iGAAiG;IACjG,KAAK,EAAE,MAAM,CAAA;IACb,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,sBAAsB;IACtB,IAAI,EAAE,UAAU,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAA;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE;QACT,yDAAyD;QACzD,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;QACtC,0EAA0E;QAC1E,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;QACzC,yEAAyE;QACzE,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,mFAAmF;QACnF,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,wFAAwF;QACxF,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,wEAAwE;QACxE,MAAM,CAAC,EAAE,OAAO,CAAA;KACjB,CAAA;IACD,2EAA2E;IAC3E,aAAa,CAAC,EAAE,kBAAkB,CAAA;CACnC;AAED,6GAA6G;AAC7G,MAAM,WAAW,mBAAmB;IAClC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAA;CACF;AAED,6DAA6D;AAC7D,MAAM,WAAW,IAAI;IACnB,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAA;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,mBAAmB,CAAA;CAChC;AAID,MAAM,MAAM,cAAc,GACtB,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,UAAU,GACV,cAAc,GACd,aAAa,GACb,WAAW,GACX,UAAU,GACV,cAAc,GACd,IAAI,GACJ,QAAQ,GACR,SAAS,GACT,aAAa,GACb,cAAc,CAAA;AAElB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEvG,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,cAAc,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAAA;CAC9D;AAED,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,UAAU,EAAE,eAAe,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,WAAW,EAAE,CAAA;IACvB,OAAO,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAA;IACjD,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,EAAE,CAAA;CAC/C"}
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,IAAI,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,OAAO,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,IAAI,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAgkBD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CAyC3B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,QAAQ,GAAG,WAAW,CAE9F;AAED;;;;;GAKG"}
|
package/dist/validation.js
CHANGED
|
@@ -225,91 +225,130 @@ function requiredAliases(seed, operation) {
|
|
|
225
225
|
.filter((branch) => (operation === 'create' ? branch.requiredOnCreate : branch.requiredOnUpdate))
|
|
226
226
|
.map((branch) => branch.alias);
|
|
227
227
|
}
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
228
|
+
function getPreprocessEmpty(allowNull) {
|
|
229
|
+
return (schema) => z.preprocess((val) => (val === '' || val === null ? (allowNull ? null : undefined) : val), schema.optional());
|
|
230
|
+
}
|
|
231
|
+
function buildTextSchema(options, nullable) {
|
|
232
|
+
const schema = stringSchema
|
|
233
|
+
.transform((value) => sanitizePlainString(value))
|
|
234
|
+
.refine((value) => value.length <= options.maxTextLength, {
|
|
235
|
+
message: `Expected string(max:${options.maxTextLength})`,
|
|
236
|
+
});
|
|
237
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
238
|
+
}
|
|
239
|
+
function buildRichtextSchema(options, nullable) {
|
|
240
|
+
const schema = z.any().transform((value, ctx) => {
|
|
241
|
+
const sanitized = sanitizeRichtext(value);
|
|
242
|
+
if (!sanitized.valid) {
|
|
243
|
+
ctx.addIssue({
|
|
244
|
+
code: 'custom',
|
|
245
|
+
message: 'Expected richtext-json|string',
|
|
246
|
+
params: { expected: 'richtext-json|string' },
|
|
237
247
|
});
|
|
238
|
-
return nullable ? z.union([schema, nullable]) : schema;
|
|
239
248
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
code: 'custom',
|
|
246
|
-
message: 'Expected richtext-json|string',
|
|
247
|
-
params: { expected: 'richtext-json|string' },
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
if (sanitized.size > options.maxTextLength) {
|
|
251
|
-
ctx.addIssue({
|
|
252
|
-
code: 'custom',
|
|
253
|
-
message: `Expected richtext(max:${options.maxTextLength})`,
|
|
254
|
-
params: { expected: `richtext(max:${options.maxTextLength})` },
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
if (sanitized.dangerous) {
|
|
258
|
-
ctx.addIssue({
|
|
259
|
-
code: 'custom',
|
|
260
|
-
message: 'Dangerous richtext content',
|
|
261
|
-
params: { dangerous: true },
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return sanitized.value;
|
|
249
|
+
if (sanitized.size > options.maxTextLength) {
|
|
250
|
+
ctx.addIssue({
|
|
251
|
+
code: 'custom',
|
|
252
|
+
message: `Expected richtext(max:${options.maxTextLength})`,
|
|
253
|
+
params: { expected: `richtext(max:${options.maxTextLength})` },
|
|
265
254
|
});
|
|
266
|
-
return nullable ? z.union([schema, nullable]) : schema;
|
|
267
255
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
256
|
+
if (sanitized.dangerous) {
|
|
257
|
+
ctx.addIssue({
|
|
258
|
+
code: 'custom',
|
|
259
|
+
message: 'Dangerous richtext content',
|
|
260
|
+
params: { dangerous: true },
|
|
261
|
+
});
|
|
271
262
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
263
|
+
return sanitized.value;
|
|
264
|
+
});
|
|
265
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
266
|
+
}
|
|
267
|
+
function buildNumberSchema(branch, allowNull, nullable) {
|
|
268
|
+
const opts = branch.numberOptions;
|
|
269
|
+
const numSchema = z.number();
|
|
270
|
+
const refinedSchema = numSchema.superRefine((val, ctx) => {
|
|
271
|
+
if (!Number.isFinite(val)) {
|
|
272
|
+
ctx.addIssue({ code: 'custom', message: 'Expected finite number' });
|
|
273
|
+
return z.NEVER;
|
|
275
274
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
if (opts?.min !== undefined && val < opts.min) {
|
|
276
|
+
ctx.addIssue({ code: 'custom', message: `Expected number(min:${opts.min})` });
|
|
277
|
+
return z.NEVER;
|
|
278
|
+
}
|
|
279
|
+
if (opts?.max !== undefined && val > opts.max) {
|
|
280
|
+
ctx.addIssue({ code: 'custom', message: `Expected number(max:${opts.max})` });
|
|
281
|
+
return z.NEVER;
|
|
282
|
+
}
|
|
283
|
+
if (opts?.step !== undefined) {
|
|
284
|
+
const step = opts.step;
|
|
285
|
+
const min = opts.min ?? 0;
|
|
286
|
+
const valDecimals = (val.toString().split('.')[1] || '').length;
|
|
287
|
+
const stepDecimals = (step.toString().split('.')[1] || '').length;
|
|
288
|
+
const decimals = Math.max(valDecimals, stepDecimals);
|
|
289
|
+
const multiplier = Math.pow(10, decimals);
|
|
290
|
+
if ((Math.round((val - min) * multiplier) % Math.round(step * multiplier)) !== 0) {
|
|
291
|
+
ctx.addIssue({ code: 'custom', message: `Expected number(step:${step})` });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
const schema = getPreprocessEmpty(allowNull)(refinedSchema);
|
|
296
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
297
|
+
}
|
|
298
|
+
function buildDateSchema(allowNull, nullable) {
|
|
299
|
+
const schema = getPreprocessEmpty(allowNull)(stringSchema
|
|
300
|
+
.transform((value) => sanitizePlainString(value))
|
|
301
|
+
.refine((value) => isIsoDateString(value), { message: 'Expected date(ISO)' }));
|
|
302
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
303
|
+
}
|
|
304
|
+
function buildFileSchema(branch, allowNull, nullable) {
|
|
305
|
+
if (isAssetListBranch(branch)) {
|
|
306
|
+
const schema = getPreprocessEmpty(allowNull)(z.any()
|
|
307
|
+
.transform((rawValue, ctx) => {
|
|
308
|
+
const normalized = normalizeAssetListValue(rawValue);
|
|
309
|
+
if (!normalized) {
|
|
310
|
+
ctx.addIssue({ code: 'custom', message: 'Expected url-string[]' });
|
|
311
|
+
return z.NEVER;
|
|
312
|
+
}
|
|
313
|
+
return normalized;
|
|
314
|
+
})
|
|
315
|
+
.pipe(z.array(z.string().url())));
|
|
316
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
317
|
+
}
|
|
318
|
+
const schema = getPreprocessEmpty(allowNull)(z.any()
|
|
319
|
+
.transform((rawValue, ctx) => {
|
|
320
|
+
const normalized = normalizeHttpUrl(rawValue);
|
|
321
|
+
if (!normalized) {
|
|
322
|
+
ctx.addIssue({ code: 'custom', message: 'Expected url-string' });
|
|
323
|
+
return z.NEVER;
|
|
324
|
+
}
|
|
325
|
+
return normalized;
|
|
326
|
+
})
|
|
327
|
+
.pipe(z.string().url()));
|
|
328
|
+
return nullable ? z.union([schema, nullable]) : schema;
|
|
329
|
+
}
|
|
330
|
+
function buildBranchSchema(branch, options) {
|
|
331
|
+
const nullable = options.allowNull ? z.null() : null;
|
|
332
|
+
switch (branch.type) {
|
|
333
|
+
case 'text':
|
|
334
|
+
return buildTextSchema(options, nullable);
|
|
335
|
+
case 'richtext':
|
|
336
|
+
return buildRichtextSchema(options, nullable);
|
|
337
|
+
case 'number':
|
|
338
|
+
return buildNumberSchema(branch, options.allowNull, nullable);
|
|
339
|
+
case 'boolean': {
|
|
340
|
+
const schema = getPreprocessEmpty(options.allowNull)(booleanSchema);
|
|
280
341
|
return nullable ? z.union([schema, nullable]) : schema;
|
|
281
342
|
}
|
|
343
|
+
case 'date':
|
|
344
|
+
return buildDateSchema(options.allowNull, nullable);
|
|
282
345
|
case 'json':
|
|
283
346
|
case 'tags': {
|
|
284
|
-
const schema =
|
|
285
|
-
return nullable ? z.union([schema, nullable]) : schema;
|
|
286
|
-
}
|
|
287
|
-
case 'file': {
|
|
288
|
-
if (isAssetListBranch(branch)) {
|
|
289
|
-
const schema = preprocessEmpty(z.any()
|
|
290
|
-
.transform((rawValue, ctx) => {
|
|
291
|
-
const normalized = normalizeAssetListValue(rawValue);
|
|
292
|
-
if (!normalized) {
|
|
293
|
-
ctx.addIssue({ code: 'custom', message: 'Expected url-string[]' });
|
|
294
|
-
return z.NEVER;
|
|
295
|
-
}
|
|
296
|
-
return normalized;
|
|
297
|
-
})
|
|
298
|
-
.pipe(z.array(z.string().url())));
|
|
299
|
-
return nullable ? z.union([schema, nullable]) : schema;
|
|
300
|
-
}
|
|
301
|
-
const schema = preprocessEmpty(z.any()
|
|
302
|
-
.transform((rawValue, ctx) => {
|
|
303
|
-
const normalized = normalizeHttpUrl(rawValue);
|
|
304
|
-
if (!normalized) {
|
|
305
|
-
ctx.addIssue({ code: 'custom', message: 'Expected url-string' });
|
|
306
|
-
return z.NEVER;
|
|
307
|
-
}
|
|
308
|
-
return normalized;
|
|
309
|
-
})
|
|
310
|
-
.pipe(z.string().url()));
|
|
347
|
+
const schema = getPreprocessEmpty(options.allowNull)(jsonObjectOrArraySchema);
|
|
311
348
|
return nullable ? z.union([schema, nullable]) : schema;
|
|
312
349
|
}
|
|
350
|
+
case 'file':
|
|
351
|
+
return buildFileSchema(branch, options.allowNull, nullable);
|
|
313
352
|
default:
|
|
314
353
|
throw new Error(`Unhandled branch type: ${branch.type}`);
|
|
315
354
|
}
|
|
@@ -325,6 +364,7 @@ function seedFingerprint(seed) {
|
|
|
325
364
|
multiple: branch.multiple ?? false,
|
|
326
365
|
requiredOnCreate: branch.requiredOnCreate ?? false,
|
|
327
366
|
requiredOnUpdate: branch.requiredOnUpdate ?? false,
|
|
367
|
+
numberOptions: branch.numberOptions ?? null,
|
|
328
368
|
})),
|
|
329
369
|
});
|
|
330
370
|
}
|
|
@@ -370,29 +410,9 @@ function isIsoDateString(value) {
|
|
|
370
410
|
const d = new Date(parsed);
|
|
371
411
|
return d.toISOString().startsWith(value.substring(0, 10));
|
|
372
412
|
}
|
|
373
|
-
|
|
374
|
-
* Common foundation for schema-driven validation and sanitization of payloads.
|
|
375
|
-
* Used by the Public API and reusable in the Botanical Engine.
|
|
376
|
-
*
|
|
377
|
-
* Mandatory execution order at the time of writing:
|
|
378
|
-
* validate raw → hash (privacy policy) → store
|
|
379
|
-
* Hashing occurs AFTER validation, not before.
|
|
380
|
-
* This ensures that the validated value is the original raw value,
|
|
381
|
-
* not the digest — avoiding false failures on fields with formats (e.g., email).
|
|
382
|
-
*/
|
|
383
|
-
export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
384
|
-
const normalizedOptions = {
|
|
385
|
-
allowNull: options.allowNull ?? false,
|
|
386
|
-
operation: options.operation ?? 'create',
|
|
387
|
-
requireAtLeastOneValidField: options.requireAtLeastOneValidField ?? true,
|
|
388
|
-
enforceRequiredFields: options.enforceRequiredFields ?? true,
|
|
389
|
-
maxTextLength: options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH,
|
|
390
|
-
};
|
|
391
|
-
const details = [];
|
|
392
|
-
const data = {};
|
|
413
|
+
function filterPayloadAliases(seed, payload) {
|
|
393
414
|
const unknownAliases = [];
|
|
394
|
-
const
|
|
395
|
-
const requiredFieldsMissing = [];
|
|
415
|
+
const details = [];
|
|
396
416
|
const allowedAliases = new Set(seed.branches.map((branch) => branch.alias));
|
|
397
417
|
const filteredPayload = {};
|
|
398
418
|
for (const [alias, rawValue] of Object.entries(payload)) {
|
|
@@ -408,67 +428,100 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
|
408
428
|
}
|
|
409
429
|
filteredPayload[alias] = rawValue;
|
|
410
430
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
details.push({
|
|
422
|
-
field: alias,
|
|
423
|
-
expected: 'known-seed-alias',
|
|
424
|
-
received: 'unknown-alias',
|
|
425
|
-
message: `Field '${alias}' is not defined in seed '${seed.slug}'`,
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (issue.message === 'Required' && normalizedOptions.enforceRequiredFields) {
|
|
431
|
-
continue; // Let enforceRequiredFields logic below handle it properly with detailed messages
|
|
432
|
-
}
|
|
433
|
-
const field = String(issue.path[0] ?? 'payload');
|
|
434
|
-
const receivedValue = filteredPayload[field];
|
|
435
|
-
const expected = typeof issue.message === 'string' && issue.message.startsWith('Expected ')
|
|
436
|
-
? issue.message.replace('Expected ', '')
|
|
437
|
-
: 'valid-field-value';
|
|
438
|
-
if (issue.params?.dangerous === true) {
|
|
439
|
-
dangerousFields.push(field);
|
|
440
|
-
}
|
|
441
|
-
details.push(makeDetail(field, expected, receivedValue));
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (normalizedOptions.enforceRequiredFields) {
|
|
445
|
-
for (const branch of seed.branches) {
|
|
446
|
-
const isRequired = normalizedOptions.operation === 'create' ? branch.requiredOnCreate : branch.requiredOnUpdate;
|
|
447
|
-
if (!isRequired)
|
|
448
|
-
continue;
|
|
449
|
-
const hasProvidedAlias = Object.hasOwn(payload, branch.alias);
|
|
450
|
-
if (!hasProvidedAlias) {
|
|
451
|
-
requiredFieldsMissing.push(branch.alias);
|
|
452
|
-
details.push({
|
|
453
|
-
field: branch.alias,
|
|
454
|
-
expected: 'required-field',
|
|
455
|
-
received: 'missing',
|
|
456
|
-
message: `Field '${branch.alias}' is required for ${normalizedOptions.operation}`,
|
|
457
|
-
});
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
const valueToCheck = parsed.success ? data[branch.alias] : filteredPayload[branch.alias];
|
|
461
|
-
if (isMissingRequiredValue(valueToCheck)) {
|
|
462
|
-
requiredFieldsMissing.push(branch.alias);
|
|
431
|
+
return { filteredPayload, unknownAliases, unknownDetails: details };
|
|
432
|
+
}
|
|
433
|
+
function collectZodIssueDetails(seed, issues, filteredPayload, normalizedOptions) {
|
|
434
|
+
const unknownAliases = [];
|
|
435
|
+
const details = [];
|
|
436
|
+
const dangerousFields = [];
|
|
437
|
+
for (const issue of issues) {
|
|
438
|
+
if (issue.code === 'unrecognized_keys') {
|
|
439
|
+
for (const alias of issue.keys) {
|
|
440
|
+
unknownAliases.push(alias);
|
|
463
441
|
details.push({
|
|
464
|
-
field:
|
|
465
|
-
expected: '
|
|
466
|
-
received: '
|
|
467
|
-
message: `Field '${
|
|
442
|
+
field: alias,
|
|
443
|
+
expected: 'known-seed-alias',
|
|
444
|
+
received: 'unknown-alias',
|
|
445
|
+
message: `Field '${alias}' is not defined in seed '${seed.slug}'`,
|
|
468
446
|
});
|
|
469
447
|
}
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (issue.message === 'Required' && normalizedOptions.enforceRequiredFields) {
|
|
451
|
+
continue; // Let enforceRequiredFields logic handle it properly
|
|
452
|
+
}
|
|
453
|
+
const field = String(issue.path[0] ?? 'payload');
|
|
454
|
+
const receivedValue = filteredPayload[field];
|
|
455
|
+
const expected = typeof issue.message === 'string' && issue.message.startsWith('Expected ')
|
|
456
|
+
? issue.message.replace('Expected ', '')
|
|
457
|
+
: 'valid-field-value';
|
|
458
|
+
if (issue.params?.dangerous === true) {
|
|
459
|
+
dangerousFields.push(field);
|
|
460
|
+
}
|
|
461
|
+
details.push(makeDetail(field, expected, receivedValue));
|
|
462
|
+
}
|
|
463
|
+
return { unknownAliases, zodDetails: details, dangerousFields };
|
|
464
|
+
}
|
|
465
|
+
function enforceRequiredFields(seed, payload, filteredPayload, data, isSuccess, normalizedOptions) {
|
|
466
|
+
const requiredFieldsMissing = [];
|
|
467
|
+
const details = [];
|
|
468
|
+
for (const branch of seed.branches) {
|
|
469
|
+
const isRequired = normalizedOptions.operation === 'create' ? branch.requiredOnCreate : branch.requiredOnUpdate;
|
|
470
|
+
if (!isRequired)
|
|
471
|
+
continue;
|
|
472
|
+
const hasProvidedAlias = Object.hasOwn(payload, branch.alias);
|
|
473
|
+
if (!hasProvidedAlias) {
|
|
474
|
+
requiredFieldsMissing.push(branch.alias);
|
|
475
|
+
details.push({
|
|
476
|
+
field: branch.alias,
|
|
477
|
+
expected: 'required-field',
|
|
478
|
+
received: 'missing',
|
|
479
|
+
message: `Field '${branch.alias}' is required for ${normalizedOptions.operation}`,
|
|
480
|
+
});
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const valueToCheck = isSuccess ? data[branch.alias] : filteredPayload[branch.alias];
|
|
484
|
+
if (isMissingRequiredValue(valueToCheck)) {
|
|
485
|
+
requiredFieldsMissing.push(branch.alias);
|
|
486
|
+
details.push({
|
|
487
|
+
field: branch.alias,
|
|
488
|
+
expected: 'required-field',
|
|
489
|
+
received: 'empty',
|
|
490
|
+
message: `Field '${branch.alias}' cannot be empty for ${normalizedOptions.operation}`,
|
|
491
|
+
});
|
|
470
492
|
}
|
|
471
493
|
}
|
|
494
|
+
return { requiredFieldsMissing, requiredDetails: details };
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Common foundation for schema-driven validation and sanitization of payloads.
|
|
498
|
+
* Used by the Public API and reusable in the Botanical Engine.
|
|
499
|
+
*
|
|
500
|
+
* Mandatory execution order at the time of writing:
|
|
501
|
+
* validate raw → hash (privacy policy) → store
|
|
502
|
+
* Hashing occurs AFTER validation, not before.
|
|
503
|
+
* This ensures that the validated value is the original raw value,
|
|
504
|
+
* not the digest — avoiding false failures on fields with formats (e.g., email).
|
|
505
|
+
*/
|
|
506
|
+
export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
507
|
+
const normalizedOptions = {
|
|
508
|
+
allowNull: options.allowNull ?? false,
|
|
509
|
+
operation: options.operation ?? 'create',
|
|
510
|
+
requireAtLeastOneValidField: options.requireAtLeastOneValidField ?? true,
|
|
511
|
+
enforceRequiredFields: options.enforceRequiredFields ?? true,
|
|
512
|
+
maxTextLength: options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH,
|
|
513
|
+
};
|
|
514
|
+
const { filteredPayload, unknownAliases: initialUnknown, unknownDetails } = filterPayloadAliases(seed, payload);
|
|
515
|
+
const schema = compileSeedSchema(seed, normalizedOptions);
|
|
516
|
+
const parsed = schema.safeParse(filteredPayload);
|
|
517
|
+
const data = parsed.success ? { ...parsed.data } : {};
|
|
518
|
+
const { unknownAliases: zodUnknown, zodDetails, dangerousFields } = parsed.success
|
|
519
|
+
? { unknownAliases: [], zodDetails: [], dangerousFields: [] }
|
|
520
|
+
: collectZodIssueDetails(seed, parsed.error.issues, filteredPayload, normalizedOptions);
|
|
521
|
+
const { requiredFieldsMissing, requiredDetails } = normalizedOptions.enforceRequiredFields
|
|
522
|
+
? enforceRequiredFields(seed, payload, filteredPayload, data, parsed.success, normalizedOptions)
|
|
523
|
+
: { requiredFieldsMissing: [], requiredDetails: [] };
|
|
524
|
+
const details = [...unknownDetails, ...zodDetails, ...requiredDetails];
|
|
472
525
|
if (normalizedOptions.requireAtLeastOneValidField && Object.keys(data).length === 0) {
|
|
473
526
|
details.push({
|
|
474
527
|
field: 'data',
|
|
@@ -480,7 +533,7 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
|
480
533
|
return {
|
|
481
534
|
data,
|
|
482
535
|
details,
|
|
483
|
-
unknownAliases: [...new Set(
|
|
536
|
+
unknownAliases: [...new Set([...initialUnknown, ...zodUnknown])],
|
|
484
537
|
dangerousFields: [...new Set(dangerousFields)],
|
|
485
538
|
requiredFieldsMissing,
|
|
486
539
|
hasAnyValidField: Object.keys(data).length > 0,
|