@object-ui/core 16.1.0 → 17.2.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 +1881 -4
- package/dist/actions/ActionEngine.d.ts +11 -1
- package/dist/actions/ActionEngine.js +14 -3
- package/dist/actions/ActionRunner.d.ts +199 -24
- package/dist/actions/ActionRunner.js +189 -84
- package/dist/actions/actionKeys.d.ts +135 -0
- package/dist/actions/actionKeys.js +283 -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/query/query-ast.d.ts +6 -6
- package/dist/query/query-ast.js +2 -2
- package/dist/registry/PluginSystem.d.ts +15 -5
- package/dist/registry/PluginSystem.js +1 -1
- package/dist/registry/Registry.d.ts +81 -2
- package/dist/registry/Registry.js +102 -2
- package/dist/registry/WidgetRegistry.d.ts +7 -7
- package/dist/registry/WidgetRegistry.js +1 -1
- package/dist/registry/public-blocks.js +52 -1
- package/dist/styling/scoped-styles.d.ts +14 -8
- package/dist/theme/ThemeEngine.js +0 -12
- package/dist/utils/chart-series.d.ts +42 -2
- 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 +11 -2
- package/dist/utils/dashboard-filters.js +99 -4
- package/dist/utils/dataset-format.d.ts +13 -1
- package/dist/utils/dataset-format.js +10 -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 +108 -0
- package/dist/utils/filter-tokens.js +180 -0
- package/dist/utils/freeze-schema.d.ts +14 -6
- package/dist/utils/freeze-schema.js +14 -6
- package/dist/utils/managedBy.d.ts +97 -51
- package/dist/utils/managedBy.js +117 -53
- package/dist/utils/normalize-list-view.d.ts +109 -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 +10 -1
- package/dist/validation/index.js +10 -1
- package/dist/validation/required-presence.d.ts +31 -0
- package/dist/validation/required-presence.js +36 -0
- package/dist/validation/schema-validator.d.ts +24 -8
- 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,283 @@
|
|
|
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
|
+
* What an action is allowed to carry — objectstack#4075 step 1.
|
|
10
|
+
*
|
|
11
|
+
* `ActionDef` ends with `[key: string]: any`, so it accepts any key of any type.
|
|
12
|
+
* Concretely (objectui#2990): deleting `ActionDef.execute` produced ZERO compile
|
|
13
|
+
* errors even though the field had just been removed, and stale metadata still
|
|
14
|
+
* authoring `execute: 'markDone'` type-checks today. The same deletion against
|
|
15
|
+
* `@object-ui/types`' `ActionSchema` — which has no index signature — correctly
|
|
16
|
+
* produced `TS2353` at the authoring site. One of the two readers can catch a
|
|
17
|
+
* retired key; the other is structurally incapable.
|
|
18
|
+
*
|
|
19
|
+
* That asymmetry is the issue. An open key set on a DECLARED METADATA CONTRACT
|
|
20
|
+
* is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
|
|
21
|
+
* sail through to a runner that then silently does nothing — the #2169 "Mark
|
|
22
|
+
* Done does nothing" shape.
|
|
23
|
+
*
|
|
24
|
+
* This module is step 1 of the staged narrowing: it makes the key set VISIBLE
|
|
25
|
+
* and warns on anything outside it, without changing a single type. Nothing
|
|
26
|
+
* breaks, and an invisible failure becomes an audible one. Steps 2 and 3 —
|
|
27
|
+
* promoting the legitimate keys to explicit optional fields, then removing the
|
|
28
|
+
* index signature — are what finally let `tsc` catch typos and retired keys.
|
|
29
|
+
*
|
|
30
|
+
* The lists are pinned by `__tests__/actionKeys.pin.test.ts`, which re-derives
|
|
31
|
+
* each one from its actual source. A hand-maintained list that drifts from the
|
|
32
|
+
* interface it mirrors would be the same "declared ≠ enforced" trap this whole
|
|
33
|
+
* thread is about.
|
|
34
|
+
*
|
|
35
|
+
* ── What the inventory found, and what step 2 did with it ────────────────────
|
|
36
|
+
* Step 1 found `ActionDef` declaring 34 keys against the spec `ActionSchema`'s
|
|
37
|
+
* 36, overlapping on 17. The two halves of the difference were different
|
|
38
|
+
* problems, and step 2 (objectstack#4075) treated them differently:
|
|
39
|
+
*
|
|
40
|
+
* 18 keys the SPEC owns that `ActionDef` never declared — `ai`, `aria`,
|
|
41
|
+
* `bodyExtra`, `bodyShape`, `bulkEnabled`, `component`, `icon`, `locations`,
|
|
42
|
+
* `mode`, `objectName`, `order`, `recordIdField`, `recordIdParam`,
|
|
43
|
+
* `requiredPermissions`, `requiresFeature`, `shortcut`, `variant`, `visible`.
|
|
44
|
+
* These are authored today and used to reach readers through the index
|
|
45
|
+
* signature. **Step 2 promoted all 18 to explicit optional fields**, typed by
|
|
46
|
+
* DERIVATION from `@objectstack/spec`'s `ActionInput` rather than by hand-copied
|
|
47
|
+
* shapes. The `as any` casts `ActionEngine` needed to read `visible`,
|
|
48
|
+
* `locations` and `requiredPermissions` are gone with them — those casts existed
|
|
49
|
+
* only because the fields were undeclared.
|
|
50
|
+
*
|
|
51
|
+
* Two of the 18 landed as `undefined` rather than as usable types, which is the
|
|
52
|
+
* correct outcome: spec 17 retired `shortcut` and `bulkEnabled` as `retiredKey()`
|
|
53
|
+
* tombstones (`z.never()`), so deriving turns "authoring this is a parse
|
|
54
|
+
* rejection" into a compile error at no cost. Hand-copying would have quietly
|
|
55
|
+
* re-legitimized two dead keys — which is why the types are derived.
|
|
56
|
+
*
|
|
57
|
+
* 17 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
|
|
58
|
+
* `chain`, `chainMode`, `close`, `condition`, `confirm`, `endpoint`, `modal`,
|
|
59
|
+
* `navigate`, `onClick`, `onFailure`, `onSuccess`, `redirect`, `reload`, `toast`,
|
|
60
|
+
* `actionParams`. Step 2 marked `@deprecated`, with the spec spelling to use
|
|
61
|
+
* instead, ONLY the four the runner itself proves are aliases: `actionType` (→
|
|
62
|
+
* `type`), `api` and `endpoint` (→ `target`; `executeAPI` resolves
|
|
63
|
+
* `api || endpoint || target`), and `navigate` (→ flat `target`/`openIn`;
|
|
64
|
+
* `executeNavigation` already falls back to them). The rest are runner mechanics
|
|
65
|
+
* with no spec counterpart — chaining, toasts, post-execution reload/close — and
|
|
66
|
+
* deprecating them toward a spelling that does not exist would be worse than
|
|
67
|
+
* leaving them declared.
|
|
68
|
+
*
|
|
69
|
+
* Step 3 remains: remove the index signature, at which point `tsc` catches both
|
|
70
|
+
* typos and retired keys and the dev-mode warning below can retire with it.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Every property `ActionDef` declares, in declaration order.
|
|
74
|
+
*
|
|
75
|
+
* Kept as data because TypeScript types do not survive to runtime — and
|
|
76
|
+
* `keyof ActionDef` cannot substitute for this list: the index signature widens
|
|
77
|
+
* it to `string | number`, which is precisely the problem being worked around.
|
|
78
|
+
*/
|
|
79
|
+
export const ACTION_DEF_KEYS = [
|
|
80
|
+
'type',
|
|
81
|
+
'actionType',
|
|
82
|
+
'name',
|
|
83
|
+
'label',
|
|
84
|
+
'confirmText',
|
|
85
|
+
'confirm',
|
|
86
|
+
'condition',
|
|
87
|
+
'disabled',
|
|
88
|
+
'api',
|
|
89
|
+
'endpoint',
|
|
90
|
+
'method',
|
|
91
|
+
'navigate',
|
|
92
|
+
'onClick',
|
|
93
|
+
'reload',
|
|
94
|
+
'close',
|
|
95
|
+
'redirect',
|
|
96
|
+
'toast',
|
|
97
|
+
'successMessage',
|
|
98
|
+
'errorMessage',
|
|
99
|
+
'refreshAfter',
|
|
100
|
+
'undoable',
|
|
101
|
+
'params',
|
|
102
|
+
'actionParams',
|
|
103
|
+
'resultDialog',
|
|
104
|
+
'target',
|
|
105
|
+
'body',
|
|
106
|
+
'openIn',
|
|
107
|
+
'modal',
|
|
108
|
+
'chain',
|
|
109
|
+
'chainMode',
|
|
110
|
+
'onSuccess',
|
|
111
|
+
'onFailure',
|
|
112
|
+
'opensInNewTab',
|
|
113
|
+
'newTabUrl',
|
|
114
|
+
// Promoted out of the index signature by objectstack#4075 step 2 — the 18 keys
|
|
115
|
+
// the spec owns that `ActionDef` had never declared. Their types are derived
|
|
116
|
+
// from `@objectstack/spec`'s `ActionInput`, so this list is the only
|
|
117
|
+
// hand-maintained half, and `actionKeys.pin.test.ts` re-derives it from the
|
|
118
|
+
// interface's AST.
|
|
119
|
+
'locations',
|
|
120
|
+
'visible',
|
|
121
|
+
'requiredPermissions',
|
|
122
|
+
'icon',
|
|
123
|
+
'variant',
|
|
124
|
+
'order',
|
|
125
|
+
'component',
|
|
126
|
+
'objectName',
|
|
127
|
+
'ai',
|
|
128
|
+
'aria',
|
|
129
|
+
'bodyExtra',
|
|
130
|
+
'bodyShape',
|
|
131
|
+
'mode',
|
|
132
|
+
'recordIdField',
|
|
133
|
+
'recordIdParam',
|
|
134
|
+
'requiresFeature',
|
|
135
|
+
'shortcut',
|
|
136
|
+
'bulkEnabled',
|
|
137
|
+
];
|
|
138
|
+
/**
|
|
139
|
+
* Every property `@objectstack/spec`'s `ActionSchema` declares.
|
|
140
|
+
*
|
|
141
|
+
* `ActionDef` mirrors that schema, so a key the spec owns is legitimate even
|
|
142
|
+
* when `ActionDef` has not declared it yet — several are read today only through
|
|
143
|
+
* an `as any` cast (`visible` and `locations` in `ActionEngine`), which is the
|
|
144
|
+
* cast this list exists to eventually retire.
|
|
145
|
+
*
|
|
146
|
+
* Pinned rather than read off the schema at runtime: the spec exports
|
|
147
|
+
* `ActionSchema` as a lazy proxy that does not forward `.shape`, so resolving it
|
|
148
|
+
* means walking zod internals (`_def.in` / `_def.innerType`). That is fine in a
|
|
149
|
+
* test and wrong in shipped code — `@object-ui/types` made the same call for
|
|
150
|
+
* `ActionComponent`. The pin test does the walk and fails the day this drifts.
|
|
151
|
+
*/
|
|
152
|
+
export const SPEC_ACTION_KEYS = [
|
|
153
|
+
'ai',
|
|
154
|
+
'aria',
|
|
155
|
+
'body',
|
|
156
|
+
'bodyExtra',
|
|
157
|
+
'bodyShape',
|
|
158
|
+
'bulkEnabled',
|
|
159
|
+
'component',
|
|
160
|
+
'confirmText',
|
|
161
|
+
'disabled',
|
|
162
|
+
'errorMessage',
|
|
163
|
+
'execute',
|
|
164
|
+
'icon',
|
|
165
|
+
'label',
|
|
166
|
+
'locations',
|
|
167
|
+
'method',
|
|
168
|
+
'mode',
|
|
169
|
+
'name',
|
|
170
|
+
'newTabUrl',
|
|
171
|
+
'objectName',
|
|
172
|
+
'openIn',
|
|
173
|
+
'opensInNewTab',
|
|
174
|
+
'order',
|
|
175
|
+
'params',
|
|
176
|
+
'recordIdField',
|
|
177
|
+
'recordIdParam',
|
|
178
|
+
'refreshAfter',
|
|
179
|
+
'requiredPermissions',
|
|
180
|
+
'requiresFeature',
|
|
181
|
+
'resultDialog',
|
|
182
|
+
'shortcut',
|
|
183
|
+
'successMessage',
|
|
184
|
+
'target',
|
|
185
|
+
'type',
|
|
186
|
+
'undoable',
|
|
187
|
+
'variant',
|
|
188
|
+
'visible',
|
|
189
|
+
];
|
|
190
|
+
/**
|
|
191
|
+
* The `navigation` alias's own spelling of a target, read off the action itself
|
|
192
|
+
* when no nested `navigate` object is present (`ActionRunner.executeNavigation`).
|
|
193
|
+
*
|
|
194
|
+
* A declared objectui-side dialect, not spec vocabulary: `replace` is documented
|
|
195
|
+
* at its read site as "the one thing the `navigation` shape carries that
|
|
196
|
+
* `ActionSchema` has no field for". Listing it here is what keeps it from being
|
|
197
|
+
* silent dialect — the alternative is a warning that cries wolf on a shape the
|
|
198
|
+
* runner itself supports.
|
|
199
|
+
*/
|
|
200
|
+
export const NAVIGATION_ALIAS_KEYS = ['to', 'external', 'newTab', 'replace'];
|
|
201
|
+
/**
|
|
202
|
+
* Keys the spec has TOMBSTONED: still present in `ActionSchema` so the parser can
|
|
203
|
+
* reject them BY NAME with a rename prescription, rather than fail with a bare
|
|
204
|
+
* "unrecognized key".
|
|
205
|
+
*
|
|
206
|
+
* Warned about separately from unknown keys, and more loudly: an unknown key is
|
|
207
|
+
* probably a typo, while a retired key is metadata that used to work. That
|
|
208
|
+
* distinction is why `executeScript` carries a runtime branch returning the
|
|
209
|
+
* rename prescription — a branch that exists solely to compensate for the index
|
|
210
|
+
* signature, and that can retire with it in step 3.
|
|
211
|
+
*/
|
|
212
|
+
export const RETIRED_ACTION_KEYS = {
|
|
213
|
+
execute: '`execute` was removed in @objectstack/spec 17 (#3855) — rename the key to `target`. ' +
|
|
214
|
+
'The value (a script name or expression) is unchanged. ' +
|
|
215
|
+
'Run `os migrate meta --from 16` to rewrite it automatically.',
|
|
216
|
+
};
|
|
217
|
+
/** Every key an action may legitimately carry today. */
|
|
218
|
+
export const KNOWN_ACTION_KEYS = new Set([
|
|
219
|
+
...ACTION_DEF_KEYS,
|
|
220
|
+
...SPEC_ACTION_KEYS,
|
|
221
|
+
...NAVIGATION_ALIAS_KEYS,
|
|
222
|
+
].filter((key) => !(key in RETIRED_ACTION_KEYS)));
|
|
223
|
+
/** Split an action's own keys into the two things worth saying out loud. */
|
|
224
|
+
export function classifyActionKeys(action) {
|
|
225
|
+
const unknown = [];
|
|
226
|
+
const retired = [];
|
|
227
|
+
if (!action || typeof action !== 'object')
|
|
228
|
+
return { unknown, retired };
|
|
229
|
+
for (const key of Object.keys(action)) {
|
|
230
|
+
if (key in RETIRED_ACTION_KEYS)
|
|
231
|
+
retired.push(key);
|
|
232
|
+
else if (!KNOWN_ACTION_KEYS.has(key))
|
|
233
|
+
unknown.push(key);
|
|
234
|
+
}
|
|
235
|
+
return { unknown, retired };
|
|
236
|
+
}
|
|
237
|
+
// Warn once per (action, problem), not once per execution: an unrecognized key
|
|
238
|
+
// usually sits in metadata driving a button that gets clicked repeatedly, and a
|
|
239
|
+
// warning that floods the console is a warning that gets muted.
|
|
240
|
+
//
|
|
241
|
+
// Keyed by action name as well as by the keys, deliberately. Keying on the keys
|
|
242
|
+
// alone would report the FIRST action carrying `targt` and stay silent about
|
|
243
|
+
// every other one — sending the author to fix a button that was only the first
|
|
244
|
+
// symptom. The memo is bounded by the number of authored actions either way.
|
|
245
|
+
const warned = new Set();
|
|
246
|
+
/** Reset the warn-once memo. Exported for tests. */
|
|
247
|
+
export function resetActionKeyWarnings() {
|
|
248
|
+
warned.clear();
|
|
249
|
+
}
|
|
250
|
+
const isDev = () => globalThis.process?.env?.NODE_ENV !==
|
|
251
|
+
'production';
|
|
252
|
+
/**
|
|
253
|
+
* Dev-mode only: name the keys an action carries that no reader will ever look
|
|
254
|
+
* at. Non-breaking by construction — it changes no types and rejects nothing.
|
|
255
|
+
*
|
|
256
|
+
* Silently binding no handler is the #2169 "Mark Done does nothing" shape, and
|
|
257
|
+
* the compiler cannot help while the index signature stands. Until it comes
|
|
258
|
+
* down, this is the audible half.
|
|
259
|
+
*/
|
|
260
|
+
export function warnOnUnknownActionKeys(action, where = 'ActionRunner') {
|
|
261
|
+
if (!isDev())
|
|
262
|
+
return;
|
|
263
|
+
const { unknown, retired } = classifyActionKeys(action);
|
|
264
|
+
const name = action?.name ?? action?.type ?? '(unnamed)';
|
|
265
|
+
for (const key of retired) {
|
|
266
|
+
const memo = `retired:${String(name)}:${key}`;
|
|
267
|
+
if (warned.has(memo))
|
|
268
|
+
continue;
|
|
269
|
+
warned.add(memo);
|
|
270
|
+
console.warn(`[${where}] action "${String(name)}" carries the retired key \`${key}\`. ${RETIRED_ACTION_KEYS[key]}`);
|
|
271
|
+
}
|
|
272
|
+
if (unknown.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
const memo = `unknown:${String(name)}:${unknown.slice().sort().join(',')}`;
|
|
275
|
+
if (warned.has(memo))
|
|
276
|
+
return;
|
|
277
|
+
warned.add(memo);
|
|
278
|
+
console.warn(`[${where}] action "${String(name)}" carries ${unknown.length === 1 ? 'a key' : 'keys'} no reader ` +
|
|
279
|
+
`recognizes: \`${unknown.join('`, `')}\`. Nothing will read ${unknown.length === 1 ? 'it' : 'them'} — ` +
|
|
280
|
+
'check for a typo, or promote the key to an explicit field on `ActionDef` ' +
|
|
281
|
+
'(packages/core/src/actions/actionKeys.ts). Warned rather than rejected because `ActionDef` ' +
|
|
282
|
+
'still carries `[key: string]: any`, so the compiler cannot see this (objectstack#4075).');
|
|
283
|
+
}
|
package/dist/actions/index.d.ts
CHANGED
package/dist/actions/index.js
CHANGED
|
@@ -44,7 +44,16 @@ function queryParamsToRecord(params) {
|
|
|
44
44
|
out.$filter = JSON.stringify(params.$filter);
|
|
45
45
|
}
|
|
46
46
|
if (params.$orderby) {
|
|
47
|
-
if (
|
|
47
|
+
if (typeof params.$orderby === 'string') {
|
|
48
|
+
// Already an OData order clause ('name asc', 'a asc,b desc'). It MUST be
|
|
49
|
+
// passed through verbatim: the `Object.entries` branch below would walk a
|
|
50
|
+
// string index-by-index and emit `0 n,1 a,2 m,…`, which the server dutifully
|
|
51
|
+
// sorts by — columns that don't exist — and the whole page comes back empty
|
|
52
|
+
// (objectstack#3821). Callers that build the clause by hand (ObjectGrid's
|
|
53
|
+
// schema `sort`, RecipientPickerField) hit exactly that.
|
|
54
|
+
out.$orderby = params.$orderby;
|
|
55
|
+
}
|
|
56
|
+
else if (Array.isArray(params.$orderby)) {
|
|
48
57
|
if (typeof params.$orderby[0] === 'string') {
|
|
49
58
|
out.$orderby = params.$orderby.join(',');
|
|
50
59
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* A DataSource adapter for the `provider: 'value'` ViewData mode.
|
|
9
9
|
* Operates entirely on an in-memory array — no network requests.
|
|
10
10
|
*/
|
|
11
|
-
import type { DataSource, BatchTransactionOperation,
|
|
11
|
+
import type { DataSource, BatchTransactionOperation, DataSourceMutationEvent, QueryParams, QueryResult, AggregateParams, AggregateResult } from '@object-ui/types';
|
|
12
12
|
export interface ValueDataSourceConfig<T = any> {
|
|
13
13
|
/** The static data array */
|
|
14
14
|
items: T[];
|
|
@@ -60,7 +60,7 @@ export declare class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
60
60
|
getView(_objectName: string, _viewId: string): Promise<any | null>;
|
|
61
61
|
getApp(_appId: string): Promise<any | null>;
|
|
62
62
|
aggregate(_resource: string, params: AggregateParams): Promise<AggregateResult[]>;
|
|
63
|
-
onMutation(callback: (event:
|
|
63
|
+
onMutation(callback: (event: DataSourceMutationEvent<T>) => void): () => void;
|
|
64
64
|
/** Get the current number of items */
|
|
65
65
|
get count(): number;
|
|
66
66
|
/** Get a snapshot of all items (cloned) */
|
|
@@ -37,8 +37,6 @@ export declare function resolveFieldRuleState(rules: {
|
|
|
37
37
|
visibleWhen?: FieldRulePredicate;
|
|
38
38
|
readonlyWhen?: FieldRulePredicate;
|
|
39
39
|
requiredWhen?: FieldRulePredicate;
|
|
40
|
-
/** Back-compat alias of `requiredWhen` (spec @deprecated). */
|
|
41
|
-
conditionalRequired?: FieldRulePredicate;
|
|
42
40
|
}, record: Record<string, unknown>, statics: {
|
|
43
41
|
required?: boolean;
|
|
44
42
|
readonly?: boolean;
|
|
@@ -79,8 +79,9 @@ export function resolveFieldRuleState(rules, record, statics, previous, scope) {
|
|
|
79
79
|
(rules.readonlyWhen != null
|
|
80
80
|
? evalFieldPredicate(rules.readonlyWhen, record, false, previous, scope)
|
|
81
81
|
: false);
|
|
82
|
-
const requiredPred = rules.requiredWhen ?? rules.conditionalRequired;
|
|
83
82
|
const required = statics.required === true ||
|
|
84
|
-
(
|
|
83
|
+
(rules.requiredWhen != null
|
|
84
|
+
? evalFieldPredicate(rules.requiredWhen, record, false, previous, scope)
|
|
85
|
+
: false);
|
|
85
86
|
return { visible, readonly, required };
|
|
86
87
|
}
|
|
@@ -109,7 +109,10 @@ export function lintOptionPredicates(fields) {
|
|
|
109
109
|
continue;
|
|
110
110
|
knownFields.add(f.name);
|
|
111
111
|
if (OPTION_FIELD_TYPES.has(fieldType(f)) && f.options && f.options.length > 0) {
|
|
112
|
-
|
|
112
|
+
// Values stringify at this boundary (#3090 widened OptionLike.value):
|
|
113
|
+
// the domain is compared against CEL literal TEXT, so the canonical
|
|
114
|
+
// string form is the right footing for numeric/boolean options too.
|
|
115
|
+
domainByField.set(f.name, new Set(f.options.map((o) => String(o.value))));
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
const issues = [];
|
|
@@ -123,7 +126,7 @@ export function lintOptionPredicates(fields) {
|
|
|
123
126
|
const source = predicateSource(opt.visibleWhen);
|
|
124
127
|
if (!source.trim())
|
|
125
128
|
continue;
|
|
126
|
-
const option = opt.value;
|
|
129
|
+
const option = String(opt.value); // report key — display form (#3090)
|
|
127
130
|
// 1. Syntax — canonical CEL parse (no schema hint = no scope false-positives).
|
|
128
131
|
const parsed = validateExpression('predicate', source);
|
|
129
132
|
if (!parsed.ok) {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* security boundary: when an option is gated for access-control reasons the
|
|
29
29
|
* server must also reject writes of its value.
|
|
30
30
|
*/
|
|
31
|
+
import type { DependsOnInput } from '@object-ui/types';
|
|
31
32
|
import { type FieldRulePredicate } from './fieldRules.js';
|
|
32
33
|
/**
|
|
33
34
|
* Minimal shape of a select/radio option this module reads. Deliberately has no
|
|
@@ -36,15 +37,21 @@ import { type FieldRulePredicate } from './fieldRules.js';
|
|
|
36
37
|
*/
|
|
37
38
|
export interface OptionLike {
|
|
38
39
|
label: string;
|
|
39
|
-
value:
|
|
40
|
+
/** Widened with `SelectOption.value` (#3090): the option engines treat the
|
|
41
|
+
* value opaquely (gate/filter by `visibleWhen`, compare by identity), so a
|
|
42
|
+
* numeric/boolean authored value flows through unchanged. */
|
|
43
|
+
value: string | number | boolean;
|
|
40
44
|
/** Per-option visibility predicate (CEL). Omit = always available. */
|
|
41
45
|
visibleWhen?: FieldRulePredicate;
|
|
42
46
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* A field's `dependsOn` as authored — imported from `@object-ui/types`, where
|
|
49
|
+
* `FormField.dependsOn` is declared with it (framework#4074; formerly a local
|
|
50
|
+
* declaration that only the reader knew about, while the public
|
|
51
|
+
* `FormField.dependsOn` said `string`). Re-exported for this module's existing
|
|
52
|
+
* consumers.
|
|
53
|
+
*/
|
|
54
|
+
export type { DependsOnInput } from '@object-ui/types';
|
|
48
55
|
/**
|
|
49
56
|
* Normalize a field's `dependsOn` to the list of sibling field names it reacts
|
|
50
57
|
* to. The lookup-only `{field,param}` form contributes just its `field`.
|
|
@@ -5,29 +5,6 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* Client-side resolution of per-option visibility for `select` / `radio` /
|
|
10
|
-
* `multiselect` fields — cascading (dependent) options and role/context gating.
|
|
11
|
-
*
|
|
12
|
-
* An option carries an optional `visibleWhen` CEL predicate; the option is
|
|
13
|
-
* offered only when it evaluates TRUE against the live record + `current_user`
|
|
14
|
-
* (the SAME engine and binding environment as a field-level `visibleWhen`, via
|
|
15
|
-
* {@link evalFieldPredicate}). This expresses both:
|
|
16
|
-
* - cascades — `record.country == 'cn'` (the dependent list narrows as the
|
|
17
|
-
* controlling field changes), and
|
|
18
|
-
* - role/context gating — `'admin' in current_user.positions`.
|
|
19
|
-
*
|
|
20
|
-
* A field declares which sibling fields its option list reacts to via
|
|
21
|
-
* `dependsOn` (aligns with `@objectstack/spec` Field.dependsOn — the same knob
|
|
22
|
-
* lookups use). While any dependency is empty the list is *gated*: we surface a
|
|
23
|
-
* "select the parent first" hint instead of an unfiltered set, mirroring the
|
|
24
|
-
* dependent-lookup UX (#2215).
|
|
25
|
-
*
|
|
26
|
-
* Evaluation is fail-open (a broken/absent predicate keeps the option) — the
|
|
27
|
-
* same safe default as field visibility. Client-side hiding is UX, not a
|
|
28
|
-
* security boundary: when an option is gated for access-control reasons the
|
|
29
|
-
* server must also reject writes of its value.
|
|
30
|
-
*/
|
|
31
8
|
import { evalFieldPredicate } from './fieldRules.js';
|
|
32
9
|
/**
|
|
33
10
|
* Normalize a field's `dependsOn` to the list of sibling field names it reacts
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export * from './utils/filter-converter.js';
|
|
|
17
17
|
export * from './utils/managedBy.js';
|
|
18
18
|
export * from './utils/extract-records.js';
|
|
19
19
|
export * from './utils/expand-fields.js';
|
|
20
|
+
export * from './utils/column-identity.js';
|
|
21
|
+
export * from './utils/sort-values.js';
|
|
20
22
|
export * from './evaluator/index.js';
|
|
21
23
|
export * from './actions/index.js';
|
|
22
24
|
export * from './query/index.js';
|
|
@@ -40,6 +42,7 @@ export * from './runtime/capabilities.js';
|
|
|
40
42
|
export { composeStacks } from '@objectstack/spec';
|
|
41
43
|
export * from './utils/drill-down.js';
|
|
42
44
|
export * from './utils/date-macros.js';
|
|
45
|
+
export * from './utils/filter-tokens.js';
|
|
43
46
|
export * from './utils/dashboard-filters.js';
|
|
44
47
|
export * from './utils/merge-filters.js';
|
|
45
48
|
export * from './utils/compare-to.js';
|
|
@@ -48,3 +51,4 @@ export * from './utils/dataset-format.js';
|
|
|
48
51
|
export * from './utils/record-title.js';
|
|
49
52
|
export * from './utils/export-filename.js';
|
|
50
53
|
export * from './utils/reference-keys.js';
|
|
54
|
+
export * from './utils/normalize-list-view.js';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export * from './utils/filter-converter.js';
|
|
|
16
16
|
export * from './utils/managedBy.js';
|
|
17
17
|
export * from './utils/extract-records.js';
|
|
18
18
|
export * from './utils/expand-fields.js';
|
|
19
|
+
export * from './utils/column-identity.js';
|
|
20
|
+
export * from './utils/sort-values.js';
|
|
19
21
|
export * from './evaluator/index.js';
|
|
20
22
|
export * from './actions/index.js';
|
|
21
23
|
export * from './query/index.js';
|
|
@@ -39,6 +41,10 @@ export * from './runtime/capabilities.js';
|
|
|
39
41
|
export { composeStacks } from '@objectstack/spec';
|
|
40
42
|
export * from './utils/drill-down.js';
|
|
41
43
|
export * from './utils/date-macros.js';
|
|
44
|
+
// Session-scoped filter placeholders ({current_user_id} / {current_org_id})
|
|
45
|
+
// plus `resolveFilterPlaceholders`, the single entry point every surface
|
|
46
|
+
// should call so no vocabulary is silently skipped (framework #3574).
|
|
47
|
+
export * from './utils/filter-tokens.js';
|
|
42
48
|
export * from './utils/dashboard-filters.js';
|
|
43
49
|
export * from './utils/merge-filters.js';
|
|
44
50
|
export * from './utils/compare-to.js';
|
|
@@ -47,3 +53,4 @@ export * from './utils/dataset-format.js';
|
|
|
47
53
|
export * from './utils/record-title.js';
|
|
48
54
|
export * from './utils/export-filename.js';
|
|
49
55
|
export * from './utils/reference-keys.js';
|
|
56
|
+
export * from './utils/normalize-list-view.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ObjectUI - Query AST Builder
|
|
3
|
-
* Phase 3.3:
|
|
3
|
+
* Phase 3.3: DriverQueryConfig AST implementation
|
|
4
4
|
* ObjectStack Spec v2.0.1: Window functions support
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { SqlQueryAST, DriverQueryConfig } from '@object-ui/types';
|
|
7
7
|
/**
|
|
8
|
-
* Query AST Builder - Converts
|
|
8
|
+
* Query AST Builder - Converts DriverQueryConfig to AST
|
|
9
9
|
*/
|
|
10
10
|
export declare class QueryASTBuilder {
|
|
11
|
-
build(query:
|
|
11
|
+
build(query: DriverQueryConfig): SqlQueryAST;
|
|
12
12
|
private buildSelect;
|
|
13
13
|
private buildFrom;
|
|
14
14
|
private buildWhere;
|
|
@@ -26,7 +26,7 @@ export declare class QueryASTBuilder {
|
|
|
26
26
|
* Build window function node (ObjectStack Spec v2.0.1)
|
|
27
27
|
*/
|
|
28
28
|
private buildWindow;
|
|
29
|
-
optimize(ast:
|
|
29
|
+
optimize(ast: SqlQueryAST): SqlQueryAST;
|
|
30
30
|
}
|
|
31
31
|
export declare const defaultQueryASTBuilder: QueryASTBuilder;
|
|
32
|
-
export declare function buildQueryAST(query:
|
|
32
|
+
export declare function buildQueryAST(query: DriverQueryConfig): SqlQueryAST;
|
package/dist/query/query-ast.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ObjectUI - Query AST Builder
|
|
3
|
-
* Phase 3.3:
|
|
3
|
+
* Phase 3.3: DriverQueryConfig AST implementation
|
|
4
4
|
* ObjectStack Spec v2.0.1: Window functions support
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* Query AST Builder - Converts
|
|
7
|
+
* Query AST Builder - Converts DriverQueryConfig to AST
|
|
8
8
|
*/
|
|
9
9
|
export class QueryASTBuilder {
|
|
10
10
|
build(query) {
|
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Registry } from './Registry.js';
|
|
9
9
|
import type { PluginScope, PluginScopeConfig, AppMetadataPlugin, AppPluginContext } from '@object-ui/types';
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* A plugin for objectui's component REGISTRY: it declares its name/version and
|
|
12
|
+
* a `register(registry | scope)` callback that contributes renderers.
|
|
13
|
+
*
|
|
14
|
+
* NOT the spec's `PluginDefinition` (`@objectstack/spec/kernel`), whose name
|
|
15
|
+
* this interface carried until objectstack#4115. That one is the platform
|
|
16
|
+
* PACKAGE manifest — `{ id?, type?, slug?, staticPath?, version?, author?,
|
|
17
|
+
* homepage?, onInstall?, onEnable?, … }` — a marketplace/install-time document
|
|
18
|
+
* with no `register` seam, and it shares no required member with this type.
|
|
19
|
+
*/
|
|
20
|
+
export interface RegistryPluginDefinition {
|
|
11
21
|
name: string;
|
|
12
22
|
version: string;
|
|
13
23
|
dependencies?: string[];
|
|
@@ -28,7 +38,7 @@ export declare class PluginSystem {
|
|
|
28
38
|
* @param useScope Whether to use scoped loading (default: true for better isolation)
|
|
29
39
|
* @throws Error if dependencies are missing
|
|
30
40
|
*/
|
|
31
|
-
loadPlugin(plugin:
|
|
41
|
+
loadPlugin(plugin: RegistryPluginDefinition, registry: Registry, useScope?: boolean): Promise<void>;
|
|
32
42
|
/**
|
|
33
43
|
* Unload a plugin from the system
|
|
34
44
|
* @param name The name of the plugin to unload
|
|
@@ -52,7 +62,7 @@ export declare class PluginSystem {
|
|
|
52
62
|
* @param name The name of the plugin
|
|
53
63
|
* @returns The plugin definition or undefined
|
|
54
64
|
*/
|
|
55
|
-
getPlugin(name: string):
|
|
65
|
+
getPlugin(name: string): RegistryPluginDefinition | undefined;
|
|
56
66
|
/**
|
|
57
67
|
* Get all loaded plugin names
|
|
58
68
|
* @returns Array of loaded plugin names
|
|
@@ -62,11 +72,11 @@ export declare class PluginSystem {
|
|
|
62
72
|
* Get all plugin definitions
|
|
63
73
|
* @returns Array of all plugin definitions
|
|
64
74
|
*/
|
|
65
|
-
getAllPlugins():
|
|
75
|
+
getAllPlugins(): RegistryPluginDefinition[];
|
|
66
76
|
/**
|
|
67
77
|
* Install an AppMetadataPlugin at runtime.
|
|
68
78
|
*
|
|
69
|
-
* Wraps the plugin as a
|
|
79
|
+
* Wraps the plugin as a RegistryPluginDefinition, calls its `init()` and `start()`
|
|
70
80
|
* lifecycle hooks, and loads it into the system.
|
|
71
81
|
*
|
|
72
82
|
* @param plugin - An AppMetadataPlugin instance
|
|
@@ -142,7 +142,7 @@ export class PluginSystem {
|
|
|
142
142
|
/**
|
|
143
143
|
* Install an AppMetadataPlugin at runtime.
|
|
144
144
|
*
|
|
145
|
-
* Wraps the plugin as a
|
|
145
|
+
* Wraps the plugin as a RegistryPluginDefinition, calls its `init()` and `start()`
|
|
146
146
|
* lifecycle hooks, and loads it into the system.
|
|
147
147
|
*
|
|
148
148
|
* @param plugin - An AppMetadataPlugin instance
|