@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +1881 -4
  2. package/dist/actions/ActionEngine.d.ts +11 -1
  3. package/dist/actions/ActionEngine.js +14 -3
  4. package/dist/actions/ActionRunner.d.ts +199 -24
  5. package/dist/actions/ActionRunner.js +189 -84
  6. package/dist/actions/actionKeys.d.ts +135 -0
  7. package/dist/actions/actionKeys.js +283 -0
  8. package/dist/actions/index.d.ts +1 -0
  9. package/dist/actions/index.js +1 -0
  10. package/dist/adapters/ApiDataSource.js +10 -1
  11. package/dist/adapters/ValueDataSource.d.ts +2 -2
  12. package/dist/evaluator/fieldRules.d.ts +0 -2
  13. package/dist/evaluator/fieldRules.js +3 -2
  14. package/dist/evaluator/optionLint.js +5 -2
  15. package/dist/evaluator/optionRules.d.ts +13 -6
  16. package/dist/evaluator/optionRules.js +0 -23
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +7 -0
  19. package/dist/query/query-ast.d.ts +6 -6
  20. package/dist/query/query-ast.js +2 -2
  21. package/dist/registry/PluginSystem.d.ts +15 -5
  22. package/dist/registry/PluginSystem.js +1 -1
  23. package/dist/registry/Registry.d.ts +81 -2
  24. package/dist/registry/Registry.js +102 -2
  25. package/dist/registry/WidgetRegistry.d.ts +7 -7
  26. package/dist/registry/WidgetRegistry.js +1 -1
  27. package/dist/registry/public-blocks.js +52 -1
  28. package/dist/styling/scoped-styles.d.ts +14 -8
  29. package/dist/theme/ThemeEngine.js +0 -12
  30. package/dist/utils/chart-series.d.ts +42 -2
  31. package/dist/utils/chart-series.js +55 -0
  32. package/dist/utils/column-identity.d.ts +110 -0
  33. package/dist/utils/column-identity.js +210 -0
  34. package/dist/utils/dashboard-filters.d.ts +11 -2
  35. package/dist/utils/dashboard-filters.js +99 -4
  36. package/dist/utils/dataset-format.d.ts +13 -1
  37. package/dist/utils/dataset-format.js +10 -2
  38. package/dist/utils/expand-fields.js +8 -8
  39. package/dist/utils/filter-converter.d.ts +47 -0
  40. package/dist/utils/filter-converter.js +94 -7
  41. package/dist/utils/filter-tokens.d.ts +108 -0
  42. package/dist/utils/filter-tokens.js +180 -0
  43. package/dist/utils/freeze-schema.d.ts +14 -6
  44. package/dist/utils/freeze-schema.js +14 -6
  45. package/dist/utils/managedBy.d.ts +97 -51
  46. package/dist/utils/managedBy.js +117 -53
  47. package/dist/utils/normalize-list-view.d.ts +109 -0
  48. package/dist/utils/normalize-list-view.js +255 -0
  49. package/dist/utils/sort-values.d.ts +52 -0
  50. package/dist/utils/sort-values.js +125 -0
  51. package/dist/validation/index.d.ts +10 -1
  52. package/dist/validation/index.js +10 -1
  53. package/dist/validation/required-presence.d.ts +31 -0
  54. package/dist/validation/required-presence.js +36 -0
  55. package/dist/validation/schema-validator.d.ts +24 -8
  56. package/dist/validation/validators/index.d.ts +7 -1
  57. package/dist/validation/validators/index.js +7 -1
  58. package/dist/validation/validators/object-validation-engine.d.ts +151 -31
  59. package/dist/validation/validators/object-validation-engine.js +275 -142
  60. 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
+ }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from './ActionRunner.js';
9
9
  export * from './ActionEngine.js';
10
+ export * from './actionKeys.js';
10
11
  export * from './TransactionManager.js';
11
12
  export * from './UndoManager.js';
12
13
  export * from './bulkFastPath.js';
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from './ActionRunner.js';
9
9
  export * from './ActionEngine.js';
10
+ export * from './actionKeys.js';
10
11
  export * from './TransactionManager.js';
11
12
  export * from './UndoManager.js';
12
13
  export * from './bulkFastPath.js';
@@ -44,7 +44,16 @@ function queryParamsToRecord(params) {
44
44
  out.$filter = JSON.stringify(params.$filter);
45
45
  }
46
46
  if (params.$orderby) {
47
- if (Array.isArray(params.$orderby)) {
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, MutationEvent, QueryParams, QueryResult, AggregateParams, AggregateResult } from '@object-ui/types';
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: MutationEvent<T>) => void): () => void;
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
- (requiredPred != null ? evalFieldPredicate(requiredPred, record, false, previous, scope) : false);
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
- domainByField.set(f.name, new Set(f.options.map((o) => o.value)));
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: string;
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
- /** A field's `dependsOn` as authored: a bare name, or a list of names / `{field,param}`. */
44
- export type DependsOnInput = string | Array<string | {
45
- field: string;
46
- param?: string;
47
- }> | undefined | null;
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: QuerySchema AST implementation
3
+ * Phase 3.3: DriverQueryConfig AST implementation
4
4
  * ObjectStack Spec v2.0.1: Window functions support
5
5
  */
6
- import type { QueryAST, QuerySchema } from '@object-ui/types';
6
+ import type { SqlQueryAST, DriverQueryConfig } from '@object-ui/types';
7
7
  /**
8
- * Query AST Builder - Converts QuerySchema to AST
8
+ * Query AST Builder - Converts DriverQueryConfig to AST
9
9
  */
10
10
  export declare class QueryASTBuilder {
11
- build(query: QuerySchema): QueryAST;
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: QueryAST): QueryAST;
29
+ optimize(ast: SqlQueryAST): SqlQueryAST;
30
30
  }
31
31
  export declare const defaultQueryASTBuilder: QueryASTBuilder;
32
- export declare function buildQueryAST(query: QuerySchema): QueryAST;
32
+ export declare function buildQueryAST(query: DriverQueryConfig): SqlQueryAST;
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * ObjectUI - Query AST Builder
3
- * Phase 3.3: QuerySchema AST implementation
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 QuerySchema to AST
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
- export interface PluginDefinition {
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: PluginDefinition, registry: Registry, useScope?: boolean): Promise<void>;
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): PluginDefinition | undefined;
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(): PluginDefinition[];
75
+ getAllPlugins(): RegistryPluginDefinition[];
66
76
  /**
67
77
  * Install an AppMetadataPlugin at runtime.
68
78
  *
69
- * Wraps the plugin as a PluginDefinition, calls its `init()` and `start()`
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 PluginDefinition, calls its `init()` and `start()`
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