@object-ui/core 16.1.0 → 17.1.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/CHANGELOG.md +1355 -4
- package/dist/actions/ActionRunner.d.ts +82 -14
- package/dist/actions/ActionRunner.js +164 -81
- package/dist/actions/actionKeys.d.ts +118 -0
- package/dist/actions/actionKeys.js +243 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/adapters/ApiDataSource.js +10 -1
- package/dist/adapters/ValueDataSource.d.ts +2 -2
- package/dist/evaluator/fieldRules.d.ts +0 -2
- package/dist/evaluator/fieldRules.js +3 -2
- package/dist/evaluator/optionLint.js +5 -2
- package/dist/evaluator/optionRules.d.ts +13 -6
- package/dist/evaluator/optionRules.js +0 -23
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/registry/Registry.d.ts +81 -2
- package/dist/registry/Registry.js +102 -2
- package/dist/registry/public-blocks.js +52 -1
- package/dist/theme/ThemeEngine.js +0 -12
- package/dist/utils/chart-series.d.ts +28 -0
- package/dist/utils/chart-series.js +55 -0
- package/dist/utils/column-identity.d.ts +110 -0
- package/dist/utils/column-identity.js +210 -0
- package/dist/utils/dashboard-filters.d.ts +2 -2
- package/dist/utils/expand-fields.js +8 -8
- package/dist/utils/filter-converter.d.ts +47 -0
- package/dist/utils/filter-converter.js +94 -7
- package/dist/utils/filter-tokens.d.ts +101 -0
- package/dist/utils/filter-tokens.js +173 -0
- package/dist/utils/managedBy.d.ts +21 -3
- package/dist/utils/managedBy.js +45 -4
- package/dist/utils/normalize-list-view.d.ts +99 -0
- package/dist/utils/normalize-list-view.js +255 -0
- package/dist/utils/sort-values.d.ts +52 -0
- package/dist/utils/sort-values.js +125 -0
- package/dist/validation/index.d.ts +9 -1
- package/dist/validation/index.js +9 -1
- package/dist/validation/validators/index.d.ts +7 -1
- package/dist/validation/validators/index.js +7 -1
- package/dist/validation/validators/object-validation-engine.d.ts +151 -31
- package/dist/validation/validators/object-validation-engine.js +275 -142
- package/package.json +4 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/** A cell value reduced to something two rows can be ordered by. */
|
|
9
|
+
export type SortValue = string | number | boolean | null;
|
|
10
|
+
export interface SortValueOptions {
|
|
11
|
+
/**
|
|
12
|
+
* `id → label` map for relational columns whose cell value is still a RAW id
|
|
13
|
+
* because the surface resolves labels client-side instead of asking the
|
|
14
|
+
* server for `$expand` (RelatedList does exactly this). When the raw value
|
|
15
|
+
* has an entry, the label the cell renders becomes the sort key.
|
|
16
|
+
*/
|
|
17
|
+
labels?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reduce one cell value to the primitive a client-side sort should order by.
|
|
21
|
+
*
|
|
22
|
+
* - empty (`null` / `undefined` / `''`) → `null`, so callers can place blanks
|
|
23
|
+
* consistently rather than sorting `undefined` against a string;
|
|
24
|
+
* - a `labels` hit (by the raw id) wins — that IS the string in the cell;
|
|
25
|
+
* - an expanded reference record resolves through {@link getRecordDisplayName},
|
|
26
|
+
* the ONE shared display-name resolver (ADR-0079), so the sort key and the
|
|
27
|
+
* lookup cell agree on which field names a record;
|
|
28
|
+
* - arrays join their resolved parts, mirroring how a multi-value cell reads;
|
|
29
|
+
* - primitives pass through unchanged (numbers stay numbers, so a number column
|
|
30
|
+
* still sorts numerically rather than lexicographically).
|
|
31
|
+
*/
|
|
32
|
+
export declare function getSortValue(value: unknown, options?: SortValueOptions): SortValue;
|
|
33
|
+
/**
|
|
34
|
+
* Compare two {@link SortValue}s **ascending**.
|
|
35
|
+
*
|
|
36
|
+
* Callers negate the result for descending, which is also why blanks are placed
|
|
37
|
+
* last here rather than "always last": negating moves them to the front, the
|
|
38
|
+
* same convention the inline comparators this replaces already had.
|
|
39
|
+
*
|
|
40
|
+
* Same-typed values compare in their own domain (numbers numerically, booleans
|
|
41
|
+
* false-before-true); anything else falls back to a locale-aware string compare
|
|
42
|
+
* with `numeric: true`, so `Item 2` precedes `Item 10`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function compareSortValues(a: SortValue, b: SortValue): number;
|
|
45
|
+
/**
|
|
46
|
+
* Convenience wrapper: reduce both cell values and compare them ascending.
|
|
47
|
+
*
|
|
48
|
+
* Prefer resolving the sort key ONCE per row (decorate → sort → undecorate)
|
|
49
|
+
* when sorting a whole collection — `getRecordDisplayName` walks the record, so
|
|
50
|
+
* calling it inside the comparator repeats that walk `O(n log n)` times.
|
|
51
|
+
*/
|
|
52
|
+
export declare function compareCellValues(a: unknown, b: unknown, options?: SortValueOptions): number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* sort-values — reduce a *cell value* to the primitive a CLIENT-SIDE sort should
|
|
10
|
+
* order by, and compare two of those primitives (objectui#3096).
|
|
11
|
+
*
|
|
12
|
+
* Why this exists: a relational column (`lookup` / `master_detail` / `tree` /
|
|
13
|
+
* `user` — see `EXPANDABLE_FIELD_TYPES`) does not hold the string its cell
|
|
14
|
+
* shows. Depending on the surface it holds either the `$expand`-ed related
|
|
15
|
+
* RECORD (`{ id, name, … }`) or a raw foreign-key id whose label was resolved
|
|
16
|
+
* separately. Both break the naive comparators these surfaces used to inline:
|
|
17
|
+
*
|
|
18
|
+
* - `a[col] < b[col]` on two objects is ALWAYS false, so the comparator
|
|
19
|
+
* collapses to the constant `1` and the "sort" permutes rows into an order
|
|
20
|
+
* with no relation to anything on screen;
|
|
21
|
+
* - `String(a[col])` on an object is `"[object Object]"` — every row compares
|
|
22
|
+
* equal, so the sort silently does nothing;
|
|
23
|
+
* - comparing raw ids (`rec_7f3…`) orders a column of human-readable names by
|
|
24
|
+
* an opaque key, which reads to the user as "sorting is broken".
|
|
25
|
+
*
|
|
26
|
+
* Reducing the value to the label the cell renders makes a client-side sort
|
|
27
|
+
* order by what the user actually sees. This CANNOT fix a server `$orderby`,
|
|
28
|
+
* where the sort key is the stored id by construction — surfaces that sort on
|
|
29
|
+
* the server must instead not offer relational columns as sort keys (that half
|
|
30
|
+
* of #3096 lives at each entry point).
|
|
31
|
+
*
|
|
32
|
+
* Pure (no React / i18n), so every renderer package can share it.
|
|
33
|
+
*/
|
|
34
|
+
import { getRecordDisplayName } from './record-title.js';
|
|
35
|
+
/**
|
|
36
|
+
* Reduce one cell value to the primitive a client-side sort should order by.
|
|
37
|
+
*
|
|
38
|
+
* - empty (`null` / `undefined` / `''`) → `null`, so callers can place blanks
|
|
39
|
+
* consistently rather than sorting `undefined` against a string;
|
|
40
|
+
* - a `labels` hit (by the raw id) wins — that IS the string in the cell;
|
|
41
|
+
* - an expanded reference record resolves through {@link getRecordDisplayName},
|
|
42
|
+
* the ONE shared display-name resolver (ADR-0079), so the sort key and the
|
|
43
|
+
* lookup cell agree on which field names a record;
|
|
44
|
+
* - arrays join their resolved parts, mirroring how a multi-value cell reads;
|
|
45
|
+
* - primitives pass through unchanged (numbers stay numbers, so a number column
|
|
46
|
+
* still sorts numerically rather than lexicographically).
|
|
47
|
+
*/
|
|
48
|
+
export function getSortValue(value, options) {
|
|
49
|
+
if (value === null || value === undefined || value === '')
|
|
50
|
+
return null;
|
|
51
|
+
const labels = options?.labels;
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
return labels?.[value] ?? value;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === 'number') {
|
|
56
|
+
if (!Number.isFinite(value))
|
|
57
|
+
return null;
|
|
58
|
+
// A numeric foreign key still deserves its resolved label.
|
|
59
|
+
return labels?.[String(value)] ?? value;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === 'boolean')
|
|
62
|
+
return value;
|
|
63
|
+
if (value instanceof Date) {
|
|
64
|
+
const time = value.getTime();
|
|
65
|
+
return Number.isNaN(time) ? null : time;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
const parts = value
|
|
69
|
+
.map((entry) => getSortValue(entry, options))
|
|
70
|
+
.filter((entry) => entry !== null)
|
|
71
|
+
.map((entry) => String(entry));
|
|
72
|
+
return parts.length > 0 ? parts.join(', ') : null;
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === 'object') {
|
|
75
|
+
const record = value;
|
|
76
|
+
// An expanded record can still carry a client-resolved label (the surface
|
|
77
|
+
// built its map from ids, the server expanded the same field).
|
|
78
|
+
const id = record.id ?? record._id;
|
|
79
|
+
if (id !== null && id !== undefined) {
|
|
80
|
+
const mapped = labels?.[String(id)];
|
|
81
|
+
if (mapped)
|
|
82
|
+
return mapped;
|
|
83
|
+
}
|
|
84
|
+
// `fallback: ''` so a record with nothing name-like reduces to `null`
|
|
85
|
+
// (sorted with the blanks) instead of the literal string "Untitled",
|
|
86
|
+
// which would bunch unrelated empty records under "U".
|
|
87
|
+
const name = getRecordDisplayName(undefined, record, { fallback: '' });
|
|
88
|
+
return name === '' ? null : name;
|
|
89
|
+
}
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compare two {@link SortValue}s **ascending**.
|
|
94
|
+
*
|
|
95
|
+
* Callers negate the result for descending, which is also why blanks are placed
|
|
96
|
+
* last here rather than "always last": negating moves them to the front, the
|
|
97
|
+
* same convention the inline comparators this replaces already had.
|
|
98
|
+
*
|
|
99
|
+
* Same-typed values compare in their own domain (numbers numerically, booleans
|
|
100
|
+
* false-before-true); anything else falls back to a locale-aware string compare
|
|
101
|
+
* with `numeric: true`, so `Item 2` precedes `Item 10`.
|
|
102
|
+
*/
|
|
103
|
+
export function compareSortValues(a, b) {
|
|
104
|
+
if (a === null && b === null)
|
|
105
|
+
return 0;
|
|
106
|
+
if (a === null)
|
|
107
|
+
return 1;
|
|
108
|
+
if (b === null)
|
|
109
|
+
return -1;
|
|
110
|
+
if (typeof a === 'boolean' && typeof b === 'boolean')
|
|
111
|
+
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
112
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
113
|
+
return a - b;
|
|
114
|
+
return String(a).localeCompare(String(b), undefined, { numeric: true });
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Convenience wrapper: reduce both cell values and compare them ascending.
|
|
118
|
+
*
|
|
119
|
+
* Prefer resolving the sort key ONCE per row (decorate → sort → undecorate)
|
|
120
|
+
* when sorting a whole collection — `getRecordDisplayName` walks the record, so
|
|
121
|
+
* calling it inside the comparator repeats that walk `O(n log n)` times.
|
|
122
|
+
*/
|
|
123
|
+
export function compareCellValues(a, b, options) {
|
|
124
|
+
return compareSortValues(getSortValue(a, options), getSortValue(b, options));
|
|
125
|
+
}
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
* @object-ui/core - Validation Module
|
|
3
3
|
*
|
|
4
4
|
* Phase 3.5: Validation engine
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Object-level validation. The rule vocabulary is owned by
|
|
7
|
+
* `@objectstack/spec/data` and derived in `@object-ui/types`; canonicity is
|
|
8
|
+
* carried by that derivation and its parity gate, not by this comment.
|
|
9
|
+
*
|
|
10
|
+
* The object-level rule ENGINE (`validators/`) is @deprecated (#3110) — the
|
|
11
|
+
* server is the single implementation of rule enforcement. Field/record SHAPE
|
|
12
|
+
* validation (`schema-validator.js`, `validation-engine.js`) is unaffected: it
|
|
13
|
+
* is client-side form validation, not a mirror of a server rule.
|
|
6
14
|
*/
|
|
7
15
|
export * from './validation-engine.js';
|
|
8
16
|
export * from './schema-validator.js';
|
package/dist/validation/index.js
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
* @object-ui/core - Validation Module
|
|
3
3
|
*
|
|
4
4
|
* Phase 3.5: Validation engine
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Object-level validation. The rule vocabulary is owned by
|
|
7
|
+
* `@objectstack/spec/data` and derived in `@object-ui/types`; canonicity is
|
|
8
|
+
* carried by that derivation and its parity gate, not by this comment.
|
|
9
|
+
*
|
|
10
|
+
* The object-level rule ENGINE (`validators/`) is @deprecated (#3110) — the
|
|
11
|
+
* server is the single implementation of rule enforcement. Field/record SHAPE
|
|
12
|
+
* validation (`schema-validator.js`, `validation-engine.js`) is unaffected: it
|
|
13
|
+
* is client-side form validation, not a mirror of a server rule.
|
|
6
14
|
*/
|
|
7
15
|
export * from './validation-engine.js';
|
|
8
16
|
export * from './schema-validator.js';
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @object-ui/core - Validators
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Publishes the @deprecated `ObjectValidationEngine` (#3110). Object-level
|
|
12
|
+
* validation rules are enforcement, and enforcement is single-implementation on
|
|
13
|
+
* the server (`objectql`'s rule-validator); objectui renders the server's
|
|
14
|
+
* rejection rather than predicting it, and pre-submit feedback belongs in a
|
|
15
|
+
* validate-only (dry-run) write. This barrel keeps the API exported for host
|
|
16
|
+
* applications that already depend on it — re-exporting is not wiring, and
|
|
17
|
+
* `validation-engine-stays-unwired.test.ts` enforces that distinction.
|
|
12
18
|
*
|
|
13
19
|
* @module validators
|
|
14
20
|
* @packageDocumentation
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @object-ui/core - Validators
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Publishes the @deprecated `ObjectValidationEngine` (#3110). Object-level
|
|
12
|
+
* validation rules are enforcement, and enforcement is single-implementation on
|
|
13
|
+
* the server (`objectql`'s rule-validator); objectui renders the server's
|
|
14
|
+
* rejection rather than predicting it, and pre-submit feedback belongs in a
|
|
15
|
+
* validate-only (dry-run) write. This barrel keeps the API exported for host
|
|
16
|
+
* applications that already depend on it — re-exporting is not wiring, and
|
|
17
|
+
* `validation-engine-stays-unwired.test.ts` enforces that distinction.
|
|
12
18
|
*
|
|
13
19
|
* @module validators
|
|
14
20
|
* @packageDocumentation
|
|
@@ -6,23 +6,92 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* @object-ui/core - Object-Level Validation Engine
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
9
|
+
* @object-ui/core - Object-Level Validation Engine — **DEPRECATED** (#3110)
|
|
10
|
+
*
|
|
11
|
+
* A client-side pre-check of the rules an object declares in
|
|
12
|
+
* `ObjectSchema.validations`. The authority is the server
|
|
13
|
+
* (`objectql/src/validation/rule-validator.ts`), which evaluates the same rules
|
|
14
|
+
* on the write path.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this is deprecated rather than finished
|
|
17
|
+
*
|
|
18
|
+
* Validation rules are ENFORCEMENT, and enforcement is single-implementation on
|
|
19
|
+
* the server by design. `evaluator/fieldRules.ts` draws that line explicitly:
|
|
20
|
+
* PRESENTATION predicates (`visibleWhen` / `readonlyWhen` / `requiredWhen`) are
|
|
21
|
+
* evaluated client-side by delegating to the canonical `ExpressionEngine`,
|
|
22
|
+
* "rather than re-implementing a parallel evaluator" (ADR-0036). This module is
|
|
23
|
+
* that parallel evaluator, on the enforcement side of the line.
|
|
24
|
+
*
|
|
25
|
+
* #3103 converged its semantics onto the server rule-for-rule, with eight
|
|
26
|
+
* mutation-tested gates — and it STILL left a known divergence: the server
|
|
27
|
+
* carries ADR-0113's legacy-violation exemption (reject only when the merged
|
|
28
|
+
* state violates AND this write makes it worse, so a pre-existing violation on
|
|
29
|
+
* an old row passes), and this engine does not. Editing an unrelated field on a
|
|
30
|
+
* legacy row would be blocked here and accepted there. That is the argument in
|
|
31
|
+
* one line: mirroring cross-repo behaviour is structurally unreliable, not
|
|
32
|
+
* unreliable-this-time.
|
|
33
|
+
*
|
|
34
|
+
* ## What to use instead
|
|
35
|
+
*
|
|
36
|
+
* Let the write fail and render the server's rejection — it is already
|
|
37
|
+
* structured (`field` / `code` / `message`, plus a label since objectstack#3957).
|
|
38
|
+
* If pre-submit feedback is wanted, the answer is a **validate-only (dry-run)
|
|
39
|
+
* write** on the server: identical UX, zero parity risk, and it also covers the
|
|
40
|
+
* two rule kinds a client can never check — `unique` (needs the database) and
|
|
41
|
+
* `json_schema` (ajv lives server-side).
|
|
42
|
+
*
|
|
43
|
+
* Nothing here has been removed or changed in behaviour; existing callers keep
|
|
44
|
+
* working. `validation-engine-stays-unwired.test.ts` fails if a production
|
|
45
|
+
* module starts importing this one, so the decision is a mechanism rather than
|
|
46
|
+
* this comment (#3017).
|
|
47
|
+
*
|
|
48
|
+
* ## When to revisit
|
|
49
|
+
*
|
|
50
|
+
* Offline writes (`OfflineConfig`). With no server to ask, a client evaluator
|
|
51
|
+
* stops being redundant and becomes necessary. The precondition then is
|
|
52
|
+
* conformance fixtures shipped by `@objectstack/spec` — golden
|
|
53
|
+
* (rule, record, verdict) triples both repos run in CI — because that is the
|
|
54
|
+
* only thing that turns cross-repo parity into a mechanism instead of a habit.
|
|
55
|
+
* Reverse the decision in #3110 before reversing it here.
|
|
56
|
+
*
|
|
57
|
+
* ## Semantics (unchanged, for as long as this exists)
|
|
58
|
+
*
|
|
59
|
+
* The rule types come from `@object-ui/types`, which derives them from
|
|
60
|
+
* `@objectstack/spec/data` (objectstack#4115). Semantics mirrored from the
|
|
61
|
+
* server:
|
|
62
|
+
*
|
|
63
|
+
* - **Polarity.** A `script` / `cross_field` `condition` and a `conditional`
|
|
64
|
+
* `when` express the FAILURE condition: predicate TRUE ⇒ rule VIOLATED.
|
|
65
|
+
* - **Fail-open on broken, fail-closed on violated.** A predicate that cannot
|
|
66
|
+
* be evaluated (parse error, unknown identifier, unusable expression
|
|
67
|
+
* envelope) is a broken rule, not a violated one — it is skipped, not
|
|
68
|
+
* reported. Likewise a malformed `regex`.
|
|
69
|
+
* - **Spec defaults, not falsy reads.** `active` defaults to `true` and
|
|
70
|
+
* `events` to `['insert','update']` — they arrive absent from `/meta`
|
|
71
|
+
* because the spec applies those defaults at parse time. Reading an absent
|
|
72
|
+
* `active` as "off" would silently disable every rule authored without it.
|
|
73
|
+
* - **`priority` orders execution** (lower first, stable).
|
|
74
|
+
* - **`format` and `state_machine` only fire when the write TOUCHES the
|
|
75
|
+
* field**, matching the server; emptiness and requiredness belong to the
|
|
76
|
+
* field-shape validator.
|
|
77
|
+
*
|
|
78
|
+
* Two documented divergences from the server, both in the safe direction:
|
|
79
|
+
*
|
|
80
|
+
* 1. The default evaluator is a small non-CEL parser (see
|
|
81
|
+
* `SimpleExpressionEvaluator`), so a predicate using real CEL constructs
|
|
82
|
+
* evaluates to nothing and fails open. Pass a CEL-backed
|
|
83
|
+
* `ValidationExpressionEvaluator` to close that gap.
|
|
84
|
+
* 2. `unique` / `async` / `range` rules are still dispatched. They are
|
|
85
|
+
* @deprecated objectui-local variants that the spec's `ValidationRuleSchema`
|
|
86
|
+
* rejects outright, so they cannot arrive from `/meta` — only a caller
|
|
87
|
+
* building rules by hand can produce one.
|
|
21
88
|
*
|
|
22
89
|
* @module object-validation-engine
|
|
23
90
|
* @packageDocumentation
|
|
24
91
|
*/
|
|
25
92
|
import type { ObjectValidationRule } from '@object-ui/types';
|
|
93
|
+
/** Write contexts a rule can run on. The spec retired `delete` (objectstack#3184). */
|
|
94
|
+
export type ObjectValidationEvent = 'insert' | 'update';
|
|
26
95
|
/**
|
|
27
96
|
* Validation context for object-level validations
|
|
28
97
|
*/
|
|
@@ -56,63 +125,114 @@ export interface ValidationExpressionEvaluator {
|
|
|
56
125
|
evaluate(expression: string, context: Record<string, any>): any;
|
|
57
126
|
}
|
|
58
127
|
/**
|
|
59
|
-
* Object-Level Validation Engine
|
|
60
|
-
*
|
|
128
|
+
* Object-Level Validation Engine — the client pre-check described at the top of
|
|
129
|
+
* this module.
|
|
130
|
+
*
|
|
131
|
+
* @deprecated (#3110) Validation rules are enforcement, and enforcement is
|
|
132
|
+
* single-implementation on the server (`objectql`'s rule-validator). Render the
|
|
133
|
+
* server's rejection instead; for pre-submit feedback, use a validate-only
|
|
134
|
+
* (dry-run) write. Still functional and still correct as of #3103 — but it
|
|
135
|
+
* cannot stay in step with the server without a conformance-fixture mechanism
|
|
136
|
+
* that does not exist yet. See the module header.
|
|
61
137
|
*/
|
|
62
138
|
export declare class ObjectValidationEngine {
|
|
63
139
|
private expressionEvaluator;
|
|
64
140
|
private uniquenessChecker?;
|
|
65
141
|
constructor(expressionEvaluator?: ValidationExpressionEvaluator, uniquenessChecker?: (fields: string[], values: Record<string, any>, scope?: string, context?: ObjectValidationContext) => Promise<boolean>);
|
|
66
142
|
/**
|
|
67
|
-
* Validate a record against a set of validation rules
|
|
143
|
+
* Validate a record against a set of validation rules.
|
|
144
|
+
*
|
|
145
|
+
* Rules run in `priority` order (lower first, stable within a priority), and
|
|
146
|
+
* only when `active` is not explicitly `false` and `events` covers this write
|
|
147
|
+
* context — reading the spec's defaults, not the falsiness of an absent key.
|
|
68
148
|
*/
|
|
69
|
-
validateRecord(rules: ObjectValidationRule[], context: ObjectValidationContext, event?:
|
|
149
|
+
validateRecord(rules: ObjectValidationRule[], context: ObjectValidationContext, event?: ObjectValidationEvent): Promise<ObjectValidationResult[]>;
|
|
70
150
|
/**
|
|
71
|
-
* Validate a single rule
|
|
151
|
+
* Validate a single rule.
|
|
152
|
+
*
|
|
153
|
+
* `rule` is widened past `ObjectValidationRule` because a `conditional`'s
|
|
154
|
+
* `then` / `otherwise` branch may be any rule the SPEC's union admits —
|
|
155
|
+
* including `json_schema`, which this engine does not implement.
|
|
72
156
|
*/
|
|
73
157
|
private validateRule;
|
|
74
158
|
/**
|
|
75
|
-
*
|
|
159
|
+
* The evaluation scope for a predicate.
|
|
160
|
+
*
|
|
161
|
+
* The server binds `{ record, previous }`, so a spec-authored predicate reads
|
|
162
|
+
* `record.amount`. objectui's own rules have always been written against the
|
|
163
|
+
* bare field name (`amount`). Both are bound — the roots last so a field
|
|
164
|
+
* literally called `record` cannot shadow them.
|
|
76
165
|
*/
|
|
77
|
-
private
|
|
166
|
+
private scopeFor;
|
|
167
|
+
/**
|
|
168
|
+
* Evaluate a `script` / `cross_field` predicate.
|
|
169
|
+
*
|
|
170
|
+
* Both variants share one path on the server, and so do they here. The
|
|
171
|
+
* predicate is the FAILURE condition: TRUE means the rule is violated. A
|
|
172
|
+
* predicate that cannot be evaluated is a BROKEN rule and fails open.
|
|
173
|
+
*/
|
|
174
|
+
private validatePredicate;
|
|
78
175
|
/**
|
|
79
176
|
* Validate uniqueness constraint
|
|
80
177
|
*/
|
|
81
178
|
private validateUniqueness;
|
|
82
179
|
/**
|
|
83
|
-
* Validate state machine transitions
|
|
180
|
+
* Validate state machine transitions (ADR-0020).
|
|
181
|
+
*
|
|
182
|
+
* A flat `field` plus a `{ from: [allowedTo] }` map. Semantics mirror the
|
|
183
|
+
* server-side rule-validator:
|
|
184
|
+
*
|
|
185
|
+
* INSERT
|
|
186
|
+
* - no `initialStates` declared → allow (legacy no-op)
|
|
187
|
+
* - field absent from the write → allow (requiredness is not our job)
|
|
188
|
+
* - value empty → allow (ditto)
|
|
189
|
+
* - value ∉ initialStates → reject (the FSM entry point, #3165)
|
|
190
|
+
*
|
|
191
|
+
* UPDATE
|
|
192
|
+
* - field absent from the write → allow (no transition to police)
|
|
193
|
+
* - no prior record → allow (cannot reason)
|
|
194
|
+
* - field unchanged, or cleared → allow (no transition)
|
|
195
|
+
* - `from` not declared in the map → lenient allow (unconstrained)
|
|
196
|
+
* - `to` ∉ transitions[from] → reject
|
|
84
197
|
*/
|
|
85
198
|
private validateStateMachine;
|
|
86
|
-
/**
|
|
87
|
-
* Validate cross-field constraints
|
|
88
|
-
*/
|
|
89
|
-
private validateCrossField;
|
|
90
199
|
/**
|
|
91
200
|
* Validate async/remote validation
|
|
92
201
|
*/
|
|
93
202
|
private validateAsync;
|
|
94
203
|
/**
|
|
95
|
-
* Validate conditional
|
|
204
|
+
* Validate a conditional rule.
|
|
205
|
+
*
|
|
206
|
+
* Evaluates `when`, then recurses into `then` (TRUE) or `otherwise` (FALSE).
|
|
207
|
+
* The nested rule supplies the violation; an un-evaluable `when` is a broken
|
|
208
|
+
* rule and fails open. A branch whose own `active` is explicitly `false` is
|
|
209
|
+
* skipped, matching the server.
|
|
96
210
|
*/
|
|
97
211
|
private validateConditional;
|
|
98
212
|
/**
|
|
99
|
-
* Validate format/
|
|
213
|
+
* Validate a field's format against `regex` and/or a named `format`.
|
|
214
|
+
*
|
|
215
|
+
* Mirrors the server: the rule only fires when the write TOUCHES the field and
|
|
216
|
+
* the value is non-empty (requiredness and type-shape belong to the
|
|
217
|
+
* field-level validator, so an absent or blank value is not a format
|
|
218
|
+
* violation). Both checks apply when both are declared. A `regex` that will
|
|
219
|
+
* not compile is a BROKEN rule — logged and skipped, never a violation.
|
|
100
220
|
*/
|
|
101
221
|
private validateFormat;
|
|
102
222
|
/**
|
|
103
223
|
* Validate range constraints
|
|
104
224
|
*/
|
|
105
225
|
private validateRange;
|
|
106
|
-
/**
|
|
107
|
-
* Get predefined regex pattern
|
|
108
|
-
*/
|
|
109
|
-
private getPredefinedPattern;
|
|
110
226
|
}
|
|
111
227
|
/**
|
|
112
228
|
* Default instance
|
|
229
|
+
*
|
|
230
|
+
* @deprecated (#3110) See `ObjectValidationEngine`.
|
|
113
231
|
*/
|
|
114
232
|
export declare const defaultObjectValidationEngine: ObjectValidationEngine;
|
|
115
233
|
/**
|
|
116
234
|
* Convenience function to validate a record
|
|
235
|
+
*
|
|
236
|
+
* @deprecated (#3110) See `ObjectValidationEngine`.
|
|
117
237
|
*/
|
|
118
|
-
export declare function validateRecord(rules: ObjectValidationRule[], context: ObjectValidationContext, event?:
|
|
238
|
+
export declare function validateRecord(rules: ObjectValidationRule[], context: ObjectValidationContext, event?: ObjectValidationEvent): Promise<ObjectValidationResult[]>;
|