@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,243 @@
|
|
|
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, i.e. step 2's worklist ─────────────────────────
|
|
36
|
+
* `ActionDef` declares 34 keys, the spec's `ActionSchema` declares 36, and they
|
|
37
|
+
* overlap on 17. The two halves of the difference are different problems:
|
|
38
|
+
*
|
|
39
|
+
* 18 keys the SPEC owns that `ActionDef` never declared — `ai`, `aria`,
|
|
40
|
+
* `bodyExtra`, `bodyShape`, `bulkEnabled`, `component`, `icon`, `locations`,
|
|
41
|
+
* `mode`, `objectName`, `order`, `recordIdField`, `recordIdParam`,
|
|
42
|
+
* `requiredPermissions`, `requiresFeature`, `shortcut`, `variant`, `visible`.
|
|
43
|
+
* These are authored today and reach readers through the index signature.
|
|
44
|
+
* `ActionEngine` reads two of them (`visible` at the location filter, `locations`
|
|
45
|
+
* at registration) through an `as any` cast — a cast that exists ONLY because the
|
|
46
|
+
* field is undeclared, and that goes away when step 2 promotes them.
|
|
47
|
+
*
|
|
48
|
+
* 17 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
|
|
49
|
+
* `chain`, `chainMode`, `close`, `condition`, `confirm`, `endpoint`, `modal`,
|
|
50
|
+
* `navigate`, `onClick`, `onFailure`, `onSuccess`, `redirect`, `reload`, `toast`,
|
|
51
|
+
* `actionParams`. Several are already marked legacy at their declaration. These
|
|
52
|
+
* are objectui's own dialect and need the #4115 treatment: kept and documented as
|
|
53
|
+
* deliberate, or retired — but not silently carried.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Every property `ActionDef` declares, in declaration order.
|
|
57
|
+
*
|
|
58
|
+
* Kept as data because TypeScript types do not survive to runtime — and
|
|
59
|
+
* `keyof ActionDef` cannot substitute for this list: the index signature widens
|
|
60
|
+
* it to `string | number`, which is precisely the problem being worked around.
|
|
61
|
+
*/
|
|
62
|
+
export const ACTION_DEF_KEYS = [
|
|
63
|
+
'type',
|
|
64
|
+
'actionType',
|
|
65
|
+
'name',
|
|
66
|
+
'label',
|
|
67
|
+
'confirmText',
|
|
68
|
+
'confirm',
|
|
69
|
+
'condition',
|
|
70
|
+
'disabled',
|
|
71
|
+
'api',
|
|
72
|
+
'endpoint',
|
|
73
|
+
'method',
|
|
74
|
+
'navigate',
|
|
75
|
+
'onClick',
|
|
76
|
+
'reload',
|
|
77
|
+
'close',
|
|
78
|
+
'redirect',
|
|
79
|
+
'toast',
|
|
80
|
+
'successMessage',
|
|
81
|
+
'errorMessage',
|
|
82
|
+
'refreshAfter',
|
|
83
|
+
'undoable',
|
|
84
|
+
'params',
|
|
85
|
+
'actionParams',
|
|
86
|
+
'resultDialog',
|
|
87
|
+
'target',
|
|
88
|
+
'body',
|
|
89
|
+
'openIn',
|
|
90
|
+
'modal',
|
|
91
|
+
'chain',
|
|
92
|
+
'chainMode',
|
|
93
|
+
'onSuccess',
|
|
94
|
+
'onFailure',
|
|
95
|
+
'opensInNewTab',
|
|
96
|
+
'newTabUrl',
|
|
97
|
+
];
|
|
98
|
+
/**
|
|
99
|
+
* Every property `@objectstack/spec`'s `ActionSchema` declares.
|
|
100
|
+
*
|
|
101
|
+
* `ActionDef` mirrors that schema, so a key the spec owns is legitimate even
|
|
102
|
+
* when `ActionDef` has not declared it yet — several are read today only through
|
|
103
|
+
* an `as any` cast (`visible` and `locations` in `ActionEngine`), which is the
|
|
104
|
+
* cast this list exists to eventually retire.
|
|
105
|
+
*
|
|
106
|
+
* Pinned rather than read off the schema at runtime: the spec exports
|
|
107
|
+
* `ActionSchema` as a lazy proxy that does not forward `.shape`, so resolving it
|
|
108
|
+
* means walking zod internals (`_def.in` / `_def.innerType`). That is fine in a
|
|
109
|
+
* test and wrong in shipped code — `@object-ui/types` made the same call for
|
|
110
|
+
* `ActionComponent`. The pin test does the walk and fails the day this drifts.
|
|
111
|
+
*/
|
|
112
|
+
export const SPEC_ACTION_KEYS = [
|
|
113
|
+
'ai',
|
|
114
|
+
'aria',
|
|
115
|
+
'body',
|
|
116
|
+
'bodyExtra',
|
|
117
|
+
'bodyShape',
|
|
118
|
+
'bulkEnabled',
|
|
119
|
+
'component',
|
|
120
|
+
'confirmText',
|
|
121
|
+
'disabled',
|
|
122
|
+
'errorMessage',
|
|
123
|
+
'execute',
|
|
124
|
+
'icon',
|
|
125
|
+
'label',
|
|
126
|
+
'locations',
|
|
127
|
+
'method',
|
|
128
|
+
'mode',
|
|
129
|
+
'name',
|
|
130
|
+
'newTabUrl',
|
|
131
|
+
'objectName',
|
|
132
|
+
'openIn',
|
|
133
|
+
'opensInNewTab',
|
|
134
|
+
'order',
|
|
135
|
+
'params',
|
|
136
|
+
'recordIdField',
|
|
137
|
+
'recordIdParam',
|
|
138
|
+
'refreshAfter',
|
|
139
|
+
'requiredPermissions',
|
|
140
|
+
'requiresFeature',
|
|
141
|
+
'resultDialog',
|
|
142
|
+
'shortcut',
|
|
143
|
+
'successMessage',
|
|
144
|
+
'target',
|
|
145
|
+
'type',
|
|
146
|
+
'undoable',
|
|
147
|
+
'variant',
|
|
148
|
+
'visible',
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* The `navigation` alias's own spelling of a target, read off the action itself
|
|
152
|
+
* when no nested `navigate` object is present (`ActionRunner.executeNavigation`).
|
|
153
|
+
*
|
|
154
|
+
* A declared objectui-side dialect, not spec vocabulary: `replace` is documented
|
|
155
|
+
* at its read site as "the one thing the `navigation` shape carries that
|
|
156
|
+
* `ActionSchema` has no field for". Listing it here is what keeps it from being
|
|
157
|
+
* silent dialect — the alternative is a warning that cries wolf on a shape the
|
|
158
|
+
* runner itself supports.
|
|
159
|
+
*/
|
|
160
|
+
export const NAVIGATION_ALIAS_KEYS = ['to', 'external', 'newTab', 'replace'];
|
|
161
|
+
/**
|
|
162
|
+
* Keys the spec has TOMBSTONED: still present in `ActionSchema` so the parser can
|
|
163
|
+
* reject them BY NAME with a rename prescription, rather than fail with a bare
|
|
164
|
+
* "unrecognized key".
|
|
165
|
+
*
|
|
166
|
+
* Warned about separately from unknown keys, and more loudly: an unknown key is
|
|
167
|
+
* probably a typo, while a retired key is metadata that used to work. That
|
|
168
|
+
* distinction is why `executeScript` carries a runtime branch returning the
|
|
169
|
+
* rename prescription — a branch that exists solely to compensate for the index
|
|
170
|
+
* signature, and that can retire with it in step 3.
|
|
171
|
+
*/
|
|
172
|
+
export const RETIRED_ACTION_KEYS = {
|
|
173
|
+
execute: '`execute` was removed in @objectstack/spec 17 (#3855) — rename the key to `target`. ' +
|
|
174
|
+
'The value (a script name or expression) is unchanged. ' +
|
|
175
|
+
'Run `os migrate meta --from 16` to rewrite it automatically.',
|
|
176
|
+
};
|
|
177
|
+
/** Every key an action may legitimately carry today. */
|
|
178
|
+
export const KNOWN_ACTION_KEYS = new Set([
|
|
179
|
+
...ACTION_DEF_KEYS,
|
|
180
|
+
...SPEC_ACTION_KEYS,
|
|
181
|
+
...NAVIGATION_ALIAS_KEYS,
|
|
182
|
+
].filter((key) => !(key in RETIRED_ACTION_KEYS)));
|
|
183
|
+
/** Split an action's own keys into the two things worth saying out loud. */
|
|
184
|
+
export function classifyActionKeys(action) {
|
|
185
|
+
const unknown = [];
|
|
186
|
+
const retired = [];
|
|
187
|
+
if (!action || typeof action !== 'object')
|
|
188
|
+
return { unknown, retired };
|
|
189
|
+
for (const key of Object.keys(action)) {
|
|
190
|
+
if (key in RETIRED_ACTION_KEYS)
|
|
191
|
+
retired.push(key);
|
|
192
|
+
else if (!KNOWN_ACTION_KEYS.has(key))
|
|
193
|
+
unknown.push(key);
|
|
194
|
+
}
|
|
195
|
+
return { unknown, retired };
|
|
196
|
+
}
|
|
197
|
+
// Warn once per (action, problem), not once per execution: an unrecognized key
|
|
198
|
+
// usually sits in metadata driving a button that gets clicked repeatedly, and a
|
|
199
|
+
// warning that floods the console is a warning that gets muted.
|
|
200
|
+
//
|
|
201
|
+
// Keyed by action name as well as by the keys, deliberately. Keying on the keys
|
|
202
|
+
// alone would report the FIRST action carrying `targt` and stay silent about
|
|
203
|
+
// every other one — sending the author to fix a button that was only the first
|
|
204
|
+
// symptom. The memo is bounded by the number of authored actions either way.
|
|
205
|
+
const warned = new Set();
|
|
206
|
+
/** Reset the warn-once memo. Exported for tests. */
|
|
207
|
+
export function resetActionKeyWarnings() {
|
|
208
|
+
warned.clear();
|
|
209
|
+
}
|
|
210
|
+
const isDev = () => globalThis.process?.env?.NODE_ENV !==
|
|
211
|
+
'production';
|
|
212
|
+
/**
|
|
213
|
+
* Dev-mode only: name the keys an action carries that no reader will ever look
|
|
214
|
+
* at. Non-breaking by construction — it changes no types and rejects nothing.
|
|
215
|
+
*
|
|
216
|
+
* Silently binding no handler is the #2169 "Mark Done does nothing" shape, and
|
|
217
|
+
* the compiler cannot help while the index signature stands. Until it comes
|
|
218
|
+
* down, this is the audible half.
|
|
219
|
+
*/
|
|
220
|
+
export function warnOnUnknownActionKeys(action, where = 'ActionRunner') {
|
|
221
|
+
if (!isDev())
|
|
222
|
+
return;
|
|
223
|
+
const { unknown, retired } = classifyActionKeys(action);
|
|
224
|
+
const name = action?.name ?? action?.type ?? '(unnamed)';
|
|
225
|
+
for (const key of retired) {
|
|
226
|
+
const memo = `retired:${String(name)}:${key}`;
|
|
227
|
+
if (warned.has(memo))
|
|
228
|
+
continue;
|
|
229
|
+
warned.add(memo);
|
|
230
|
+
console.warn(`[${where}] action "${String(name)}" carries the retired key \`${key}\`. ${RETIRED_ACTION_KEYS[key]}`);
|
|
231
|
+
}
|
|
232
|
+
if (unknown.length === 0)
|
|
233
|
+
return;
|
|
234
|
+
const memo = `unknown:${String(name)}:${unknown.slice().sort().join(',')}`;
|
|
235
|
+
if (warned.has(memo))
|
|
236
|
+
return;
|
|
237
|
+
warned.add(memo);
|
|
238
|
+
console.warn(`[${where}] action "${String(name)}" carries ${unknown.length === 1 ? 'a key' : 'keys'} no reader ` +
|
|
239
|
+
`recognizes: \`${unknown.join('`, `')}\`. Nothing will read ${unknown.length === 1 ? 'it' : 'them'} — ` +
|
|
240
|
+
'check for a typo, or promote the key to an explicit field on `ActionDef` ' +
|
|
241
|
+
'(packages/core/src/actions/actionKeys.ts). Warned rather than rejected because `ActionDef` ' +
|
|
242
|
+
'still carries `[key: string]: any`, so the compiler cannot see this (objectstack#4075).');
|
|
243
|
+
}
|
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';
|
|
@@ -62,6 +62,22 @@ export type ComponentConfig<T = any> = ComponentMeta & {
|
|
|
62
62
|
type: string;
|
|
63
63
|
component: ComponentRenderer<T>;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* A CONTRACT-surface entry (ADR-0080), as returned by
|
|
67
|
+
* {@link Registry.getPublicConfigs}.
|
|
68
|
+
*
|
|
69
|
+
* Same shape as {@link ComponentConfig} except `component` is absent while the
|
|
70
|
+
* entry is still a pending `registerLazy` stub: the plugin module has not been
|
|
71
|
+
* imported yet, so there is no renderer to hand out. Consumers render such an
|
|
72
|
+
* entry through `SchemaRenderer`, which triggers the loader and shows a
|
|
73
|
+
* placeholder in the meantime (objectui#2953).
|
|
74
|
+
*/
|
|
75
|
+
export type PublicComponentConfig<T = any> = ComponentMeta & {
|
|
76
|
+
type: string;
|
|
77
|
+
component?: ComponentRenderer<T>;
|
|
78
|
+
/** True while this entry is a `registerLazy` stub whose loader has not run. */
|
|
79
|
+
lazy?: boolean;
|
|
80
|
+
};
|
|
65
81
|
/**
|
|
66
82
|
* Lazy loader function used by `Registry.registerLazy`. The loader is invoked
|
|
67
83
|
* the first time a missing component type is requested through `getAsync`/the
|
|
@@ -79,6 +95,14 @@ export declare class Registry<T = any> {
|
|
|
79
95
|
* load completes.
|
|
80
96
|
*/
|
|
81
97
|
private listeners;
|
|
98
|
+
/**
|
|
99
|
+
* Bumped whenever the set of KNOWN types changes — a registration, an
|
|
100
|
+
* unregistration, or a new lazy stub. A cheap cache key for consumers that
|
|
101
|
+
* derive something from the whole registry (whitelists, manifests, palettes)
|
|
102
|
+
* and need to rebuild when it grows. Counting types is not a substitute: a
|
|
103
|
+
* registration paired with an unregistration leaves the count untouched.
|
|
104
|
+
*/
|
|
105
|
+
private version;
|
|
82
106
|
/**
|
|
83
107
|
* Register a component with optional namespace support.
|
|
84
108
|
* If namespace is provided in meta, the component will be registered as "namespace:type".
|
|
@@ -141,6 +165,11 @@ export declare class Registry<T = any> {
|
|
|
141
165
|
*/
|
|
142
166
|
subscribe(listener: () => void): () => void;
|
|
143
167
|
private notify;
|
|
168
|
+
/**
|
|
169
|
+
* Monotonic counter of changes to the set of known types. Rebuild anything
|
|
170
|
+
* derived from the whole registry when this moves — see {@link getKnownTypes}.
|
|
171
|
+
*/
|
|
172
|
+
getVersion(): number;
|
|
144
173
|
/**
|
|
145
174
|
* Get a component by type. Supports both namespaced and non-namespaced lookups.
|
|
146
175
|
*
|
|
@@ -164,6 +193,13 @@ export declare class Registry<T = any> {
|
|
|
164
193
|
/**
|
|
165
194
|
* Get component configuration by type with namespace support.
|
|
166
195
|
*
|
|
196
|
+
* LOADED registrations only — a type that exists solely as a `registerLazy`
|
|
197
|
+
* stub returns `undefined` here, because callers read `.component` off the
|
|
198
|
+
* result and a stub has no renderer until its loader runs. The *contract*
|
|
199
|
+
* question ("is this tag part of the public surface?") must not depend on
|
|
200
|
+
* load order, so {@link getPublicConfigs} resolves lazy stubs separately
|
|
201
|
+
* (objectui#2953). Use {@link hasLazy} / {@link loadLazy} to reach a stub.
|
|
202
|
+
*
|
|
167
203
|
* @param type - Component type (e.g., 'button' or 'ui:button')
|
|
168
204
|
* @param namespace - Optional namespace for lookup priority
|
|
169
205
|
* @returns Component configuration or undefined
|
|
@@ -178,23 +214,66 @@ export declare class Registry<T = any> {
|
|
|
178
214
|
*/
|
|
179
215
|
has(type: string, namespace?: string): boolean;
|
|
180
216
|
/**
|
|
181
|
-
* Get all
|
|
217
|
+
* Get all LOADED component types — what can be rendered right now.
|
|
218
|
+
*
|
|
219
|
+
* Pending `registerLazy` stubs are not included; use {@link getKnownTypes}
|
|
220
|
+
* for the question "is this a type this app knows about", which is what a
|
|
221
|
+
* whitelist or manifest wants (objectui#2953).
|
|
182
222
|
*
|
|
183
223
|
* @returns Array of all component type identifiers
|
|
184
224
|
*/
|
|
185
225
|
getAllTypes(): string[];
|
|
226
|
+
/**
|
|
227
|
+
* Every type the registry can resolve — loaded registrations PLUS pending
|
|
228
|
+
* lazy stubs, deduped. Both the bare and namespaced keys are included, as
|
|
229
|
+
* they are in {@link getAllTypes}.
|
|
230
|
+
*
|
|
231
|
+
* This is the set to build a whitelist or a manifest from. Keying one off
|
|
232
|
+
* `getAllTypes()` instead makes it depend on which plugin chunks happen to
|
|
233
|
+
* have loaded: a lazily-registered block gets rejected as unknown, and
|
|
234
|
+
* whether it does varies by session (objectui#2953).
|
|
235
|
+
*/
|
|
236
|
+
getKnownTypes(): string[];
|
|
237
|
+
/**
|
|
238
|
+
* Metadata for `type` — from the loaded registration when there is one,
|
|
239
|
+
* otherwise from a pending `registerLazy` stub.
|
|
240
|
+
*
|
|
241
|
+
* Use this when you need to know what a type IS (namespace, container-ness,
|
|
242
|
+
* declared inputs). {@link getConfig} answers a different question — "can I
|
|
243
|
+
* render this right now" — and is deliberately loaded-only, since callers
|
|
244
|
+
* read `.component` off it.
|
|
245
|
+
*
|
|
246
|
+
* A stub carries only what `registerLazy` was given, so `inputs` is usually
|
|
247
|
+
* absent until the chunk loads. Consumers should treat that as "not yet
|
|
248
|
+
* known", not as "declares no props".
|
|
249
|
+
*/
|
|
250
|
+
getMeta(type: string, namespace?: string): ComponentMeta | undefined;
|
|
186
251
|
/**
|
|
187
252
|
* Get all registered component configurations.
|
|
188
253
|
*
|
|
189
254
|
* @returns Array of all component configurations
|
|
190
255
|
*/
|
|
191
256
|
getAllConfigs(): ComponentConfig<T>[];
|
|
257
|
+
/**
|
|
258
|
+
* Resolve `tag` for the CONTRACT surface: the loaded registration when the
|
|
259
|
+
* component is already in the registry, otherwise the metadata of a pending
|
|
260
|
+
* `registerLazy` stub.
|
|
261
|
+
*
|
|
262
|
+
* A block registered lazily is a first-class member of the contract — the
|
|
263
|
+
* loader is recorded at boot and the plugin chunk is imported on first use —
|
|
264
|
+
* so contract membership must not hinge on whether that import happened to
|
|
265
|
+
* have run yet (objectui#2953).
|
|
266
|
+
*/
|
|
267
|
+
private getContractConfig;
|
|
192
268
|
/**
|
|
193
269
|
* Get the curated PUBLIC-tier component configs (ADR-0080) — those registered
|
|
194
270
|
* with `tier: 'public'`. This is the contract/AI-vocabulary surface, a subset
|
|
195
271
|
* of the full rendering capability returned by {@link getAllConfigs}.
|
|
272
|
+
*
|
|
273
|
+
* Includes blocks that are only lazily registered so far; those come back
|
|
274
|
+
* with `lazy: true` and no `component` (see {@link PublicComponentConfig}).
|
|
196
275
|
*/
|
|
197
|
-
getPublicConfigs():
|
|
276
|
+
getPublicConfigs(): PublicComponentConfig<T>[];
|
|
198
277
|
/**
|
|
199
278
|
* Get all components in a specific namespace.
|
|
200
279
|
*
|