bank20baht-validator 0.2.0 → 0.3.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/README.md +1 -0
- package/dist/index.js +78 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ step with the core version or the validator's `tsc` build resolves a stale copy
|
|
|
56
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".
|
|
57
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.
|
|
58
58
|
- Approval steps: a `decision` must be present, and the comment policy (`required` / `requireOnReject`) is honored.
|
|
59
|
+
- **Client `submit` scripts** (spec 01 §14): re-run headlessly with the same `registry.scripts` — `g.setValue()` lands in canonical data, `g.addErrorMessage()` becomes an issue on `$` (`form_name.$` in a workflow), returning `false` blocks. `load` / `change` scripts are UI-only and never run here, so a field a client script hides is still validated: keep submit-relevant logic in rules or validators. `parseSchema` rejects a `scripts[]` entry with an unknown `on`, no `handler`, or a `change` hook on a field that does not exist.
|
|
59
60
|
|
|
60
61
|
## API
|
|
61
62
|
|
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, parseDisabledEntry, parseIsoDate, resolveViewFields, setCatalog, setLocale, t, validateField, walkPath, normalizeLegacySchema, } from 'bank20baht-ui/engine';
|
|
16
|
+
import { applyFlags, approvalFields, createScriptContext, defaultValue, evaluate, hasScripts, runScripts, getCatalog, parseDisabledEntry, parseIsoDate, resolveViewFields, setCatalog, setLocale, t, validateField, walkPath, normalizeLegacySchema, } 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
|
|
@@ -204,8 +204,51 @@ function formDataResult(form, input, registry) {
|
|
|
204
204
|
issues.push({ ref: err.key, message: err.message });
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
+
issues.push(...submitScriptIssues(form, data, registry));
|
|
207
208
|
return { valid: issues.length === 0, issues, data };
|
|
208
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Re-run the form's `submit` client scripts (spec 01 §14) headlessly — the
|
|
212
|
+
* same gate `<baht-form>.validate()` applies after its validators. `load` and
|
|
213
|
+
* `change` scripts are UI-only and never run here. `g.setValue()` writes into
|
|
214
|
+
* canonical `data`; UI state/message calls are accepted and discarded, except
|
|
215
|
+
* `addErrorMessage()`, which becomes an issue on `$` (prefixed by the form name
|
|
216
|
+
* inside a workflow). A handler returning `false` with no message falls back to
|
|
217
|
+
* the catalog's `scriptBlocked`.
|
|
218
|
+
*/
|
|
219
|
+
function submitScriptIssues(form, data, registry, refPrefix = '') {
|
|
220
|
+
if (!hasScripts(form, 'submit'))
|
|
221
|
+
return [];
|
|
222
|
+
const messages = [];
|
|
223
|
+
const g = createScriptContext(form, {
|
|
224
|
+
getData: () => data,
|
|
225
|
+
setValue: (key, value) => {
|
|
226
|
+
data[key] = value;
|
|
227
|
+
},
|
|
228
|
+
setState: () => { },
|
|
229
|
+
resetState: () => { },
|
|
230
|
+
setFieldMessage: () => { },
|
|
231
|
+
addFormMessage: (m) => {
|
|
232
|
+
messages.push(m);
|
|
233
|
+
},
|
|
234
|
+
clearFormMessages: () => {
|
|
235
|
+
messages.length = 0;
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
const ok = runScripts({
|
|
239
|
+
form,
|
|
240
|
+
registry: registry ?? {},
|
|
241
|
+
g,
|
|
242
|
+
on: 'submit',
|
|
243
|
+
detail: { isLoading: false, data: { ...data } },
|
|
244
|
+
});
|
|
245
|
+
const issues = messages
|
|
246
|
+
.filter((m) => m.type === 'error')
|
|
247
|
+
.map((m) => ({ ref: `${refPrefix}$`, message: m.message }));
|
|
248
|
+
if (!ok && issues.length === 0)
|
|
249
|
+
issues.push({ ref: `${refPrefix}$`, message: t('scriptBlocked') });
|
|
250
|
+
return issues;
|
|
251
|
+
}
|
|
209
252
|
/**
|
|
210
253
|
* Validate a whole workflow submission — mirrors what `<baht-workflow>`
|
|
211
254
|
* enforces across its steps: rules are evaluated first (`evaluate`), invisible
|
|
@@ -297,6 +340,10 @@ function workflowDataResult(workflow, input, registry) {
|
|
|
297
340
|
issues.push({ ref, message: err.message });
|
|
298
341
|
}
|
|
299
342
|
}
|
|
343
|
+
if (hasScripts(form, 'submit')) {
|
|
344
|
+
const formData = (derived.data[form.form_name] ??= {});
|
|
345
|
+
issues.push(...submitScriptIssues(form, formData, registry, `${form.form_name}.`));
|
|
346
|
+
}
|
|
300
347
|
}
|
|
301
348
|
return { valid: issues.length === 0, issues, data: derived.data };
|
|
302
349
|
}
|
|
@@ -372,6 +419,7 @@ export function parseSchema(json) {
|
|
|
372
419
|
assertValidFields(f);
|
|
373
420
|
assertValidViews(f);
|
|
374
421
|
assertValidRepeaters(f);
|
|
422
|
+
assertValidScripts(f);
|
|
375
423
|
if (f.view !== undefined && !f.views?.some((v) => v.name === f.view)) {
|
|
376
424
|
throw new Error(`step view ${JSON.stringify(f.view)} on ${f.form_name} matches no view`);
|
|
377
425
|
}
|
|
@@ -422,6 +470,7 @@ export function parseSchema(json) {
|
|
|
422
470
|
assertValidFields(parsed);
|
|
423
471
|
assertValidViews(parsed);
|
|
424
472
|
assertValidRepeaters(parsed);
|
|
473
|
+
assertValidScripts(parsed);
|
|
425
474
|
return parsed;
|
|
426
475
|
}
|
|
427
476
|
throw new Error('missing workflow_id/forms/rules (workflow) or form_id/form_name/fields (form)');
|
|
@@ -683,6 +732,34 @@ function assertValidRepeaters(form) {
|
|
|
683
732
|
}
|
|
684
733
|
}
|
|
685
734
|
}
|
|
735
|
+
const SCRIPT_EVENTS = new Set(['load', 'change', 'submit']);
|
|
736
|
+
/** `scripts[]` (spec 01 §14): known `on`, a handler name, and a real field for `change`. */
|
|
737
|
+
function assertValidScripts(form) {
|
|
738
|
+
if (form.scripts === undefined)
|
|
739
|
+
return;
|
|
740
|
+
if (!Array.isArray(form.scripts)) {
|
|
741
|
+
throw new Error(`scripts must be an array on ${form.form_name}`);
|
|
742
|
+
}
|
|
743
|
+
const keys = new Set(form.fields.map((f) => f.key));
|
|
744
|
+
for (const s of form.scripts) {
|
|
745
|
+
if (!isRecord(s))
|
|
746
|
+
throw new Error(`invalid script on ${form.form_name}: expected an object`);
|
|
747
|
+
if (typeof s.on !== 'string' || !SCRIPT_EVENTS.has(s.on)) {
|
|
748
|
+
throw new Error(`invalid script on=${JSON.stringify(s.on)} on ${form.form_name}`);
|
|
749
|
+
}
|
|
750
|
+
if (typeof s.handler !== 'string' || s.handler === '') {
|
|
751
|
+
throw new Error(`script on ${form.form_name} needs a handler name`);
|
|
752
|
+
}
|
|
753
|
+
if (s.on === 'change') {
|
|
754
|
+
if (typeof s.field !== 'string' || s.field === '') {
|
|
755
|
+
throw new Error(`change script "${s.handler}" on ${form.form_name} needs a field`);
|
|
756
|
+
}
|
|
757
|
+
if (!keys.has(s.field)) {
|
|
758
|
+
throw new Error(`change script "${s.handler}" on ${form.form_name} targets unknown field ${JSON.stringify(s.field)}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
686
763
|
function assertValidViews(form) {
|
|
687
764
|
if (form.views === undefined)
|
|
688
765
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bank20baht-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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.5.0",
|
|
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.5.0 <0.6.0"
|
|
49
49
|
},
|
|
50
50
|
"main": "./dist/index.js",
|
|
51
51
|
"types": "./dist/index.d.ts",
|