bank20baht-validator 0.1.2 → 0.1.4
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/README.md +7 -0
- package/dist/index.js +46 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -44,9 +44,16 @@ POST /api/submissions {"id":"todo-form","data":{"title":"ab"}}
|
|
|
44
44
|
→ 422 {"errors":[{"ref":"title","message":"Must be at least 3 characters"}]}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
## Compatibility
|
|
48
|
+
|
|
49
|
+
Peers on `bank20baht-ui` **0.2.x** (the `FieldType` union — including `password` since 0.2.2 —
|
|
50
|
+
comes from there). In a workspace that also builds `bank20baht-ui`, keep the peer range in
|
|
51
|
+
step with the core version or the validator's `tsc` build resolves a stale copy from npm.
|
|
52
|
+
|
|
47
53
|
## What it enforces
|
|
48
54
|
|
|
49
55
|
- Every declarative validator of the field descriptors: `required`, `minLength`, `maxLength`, `min`, `max`, `pattern`, `email`, and `custom` (via a `Registry`).
|
|
56
|
+
- Value **shape** per field type before any validator runs — a `password`/`text` must be a string, a `number` a finite number, a `dropdown` one of its option keys — so `{"$gt":""}` from a raw API call is rejected, not treated as "non-empty".
|
|
50
57
|
- **Workflow rules first** (`evaluate` from `bank20baht-ui/engine`): fields hidden by rules are excluded and smuggled values are cleared; `required` derived from rules is enforced; `setValue` results are computed.
|
|
51
58
|
- Approval steps: a `decision` must be present, and the comment policy (`required` / `requireOnReject`) is honored.
|
|
52
59
|
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// every value must have the shape the UI control could actually emit before
|
|
14
14
|
// the authored validators run.
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
|
-
import { applyFlags, approvalFields, defaultValue, evaluate, getCatalog, resolveViewFields, setCatalog, setLocale, t, validateField, walkPath, } from 'bank20baht-ui/engine';
|
|
16
|
+
import { applyFlags, approvalFields, defaultValue, evaluate, getCatalog, parseDisabledEntry, parseIsoDate, resolveViewFields, setCatalog, setLocale, t, validateField, walkPath, } from 'bank20baht-ui/engine';
|
|
17
17
|
/**
|
|
18
18
|
* Run `fn` with the catalog switched to `locale`, restoring the previous
|
|
19
19
|
* catalog after. The engine catalog is module-global; validation is fully
|
|
@@ -34,11 +34,13 @@ function withLocale(locale, fn) {
|
|
|
34
34
|
}
|
|
35
35
|
/** Keys that write through Object.prototype — never valid as field/form names. */
|
|
36
36
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
37
|
+
const DATE_MODES = new Set(['single', 'range', 'multiple']);
|
|
37
38
|
const FIELD_TYPES = new Set([
|
|
38
39
|
'text',
|
|
39
40
|
'textarea',
|
|
40
41
|
'number',
|
|
41
42
|
'email',
|
|
43
|
+
'password',
|
|
42
44
|
'dropdown',
|
|
43
45
|
'multiselect',
|
|
44
46
|
'checkbox',
|
|
@@ -87,8 +89,22 @@ function typeIssue(field, value) {
|
|
|
87
89
|
case 'text':
|
|
88
90
|
case 'textarea':
|
|
89
91
|
case 'email':
|
|
90
|
-
case '
|
|
92
|
+
case 'password':
|
|
91
93
|
return typeof value === 'string' ? null : t('pattern');
|
|
94
|
+
case 'date': {
|
|
95
|
+
// value shape follows dateMode; every day must be a real ISO calendar day
|
|
96
|
+
const isDay = (v) => parseIsoDate(v) !== null;
|
|
97
|
+
switch (field.dateMode ?? 'single') {
|
|
98
|
+
case 'range':
|
|
99
|
+
return Array.isArray(value) && value.length === 2 && value.every(isDay)
|
|
100
|
+
? null
|
|
101
|
+
: t('pattern');
|
|
102
|
+
case 'multiple':
|
|
103
|
+
return Array.isArray(value) && value.every(isDay) ? null : t('pattern');
|
|
104
|
+
default:
|
|
105
|
+
return isDay(value) ? null : t('pattern');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
92
108
|
case 'number':
|
|
93
109
|
return typeof value === 'number' && Number.isFinite(value) ? null : t('pattern');
|
|
94
110
|
case 'checkbox':
|
|
@@ -475,6 +491,8 @@ function assertFieldShape(field, formName) {
|
|
|
475
491
|
throw new Error(`invalid field key ${JSON.stringify(field.key)} on ${formName}`);
|
|
476
492
|
}
|
|
477
493
|
}
|
|
494
|
+
if (field.type === 'date')
|
|
495
|
+
assertDateProps(field, formName);
|
|
478
496
|
if (field.options !== undefined) {
|
|
479
497
|
if (!Array.isArray(field.options) ||
|
|
480
498
|
field.options.some((o) => !isRecord(o) || typeof o.key !== 'string')) {
|
|
@@ -508,6 +526,32 @@ function assertFieldShape(field, formName) {
|
|
|
508
526
|
}
|
|
509
527
|
}
|
|
510
528
|
}
|
|
529
|
+
/** Calendar constraints must be well-formed or the picker silently ignores them. */
|
|
530
|
+
function assertDateProps(field, formName) {
|
|
531
|
+
const where = `${formName}.${field.key}`;
|
|
532
|
+
if (field.dateMode !== undefined && !DATE_MODES.has(field.dateMode)) {
|
|
533
|
+
throw new Error(`invalid dateMode ${JSON.stringify(field.dateMode)} on ${where}`);
|
|
534
|
+
}
|
|
535
|
+
for (const bound of ['min', 'max']) {
|
|
536
|
+
const v = field[bound];
|
|
537
|
+
if (v !== undefined && parseIsoDate(v) === null) {
|
|
538
|
+
throw new Error(`${bound} on ${where} must be an ISO date (yyyy-mm-dd)`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (field.disabledDates !== undefined) {
|
|
542
|
+
const bad = !Array.isArray(field.disabledDates) ||
|
|
543
|
+
field.disabledDates.some((d) => typeof d !== 'string' || parseDisabledEntry(d) === null);
|
|
544
|
+
if (bad) {
|
|
545
|
+
throw new Error(`disabledDates on ${where} must be ISO days ("2026-04-13") or intervals ("2026-04-13/2026-04-15")`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (field.disabledWeekdays !== undefined) {
|
|
549
|
+
const bad = !Array.isArray(field.disabledWeekdays) ||
|
|
550
|
+
field.disabledWeekdays.some((d) => !Number.isInteger(d) || d < 0 || d > 6);
|
|
551
|
+
if (bad)
|
|
552
|
+
throw new Error(`disabledWeekdays on ${where} must be integers 0 (Sun) … 6 (Sat)`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
511
555
|
function assertValidRules(wf, formNames, refs) {
|
|
512
556
|
wf.rules.forEach((raw, i) => {
|
|
513
557
|
if (!isRecord(raw))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bank20baht-validator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime validation for bahtui form/workflow JSON — the exact engine rules the client runs, on the server.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"test:watch": "vitest"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"bank20baht-ui": "^0.
|
|
43
|
+
"bank20baht-ui": "^0.2.2",
|
|
44
44
|
"typescript": "^5.8.3",
|
|
45
45
|
"vitest": "^3.2.4"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"bank20baht-ui": ">=0.
|
|
48
|
+
"bank20baht-ui": ">=0.2.6 <0.3.0"
|
|
49
49
|
},
|
|
50
50
|
"main": "./dist/index.js",
|
|
51
51
|
"types": "./dist/index.d.ts",
|