@object-ui/core 17.1.0 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +738 -0
  2. package/README.md +29 -0
  3. package/dist/actions/ActionEngine.d.ts +22 -7
  4. package/dist/actions/ActionEngine.js +40 -22
  5. package/dist/actions/ActionRunner.d.ts +133 -13
  6. package/dist/actions/ActionRunner.js +27 -4
  7. package/dist/actions/actionErrorDetail.d.ts +26 -0
  8. package/dist/actions/actionErrorDetail.js +37 -0
  9. package/dist/actions/actionKeys.d.ts +29 -12
  10. package/dist/actions/actionKeys.js +61 -10
  11. package/dist/actions/actionResponse.d.ts +42 -0
  12. package/dist/actions/actionResponse.js +104 -0
  13. package/dist/actions/index.d.ts +3 -0
  14. package/dist/actions/index.js +3 -0
  15. package/dist/actions/serverActionHandler.d.ts +143 -0
  16. package/dist/actions/serverActionHandler.js +137 -0
  17. package/dist/evaluator/ExpressionEvaluator.js +4 -2
  18. package/dist/evaluator/fieldRules.d.ts +26 -2
  19. package/dist/evaluator/fieldRules.js +63 -8
  20. package/dist/evaluator/index.d.ts +1 -0
  21. package/dist/evaluator/index.js +1 -0
  22. package/dist/evaluator/listConditional.d.ts +13 -1
  23. package/dist/evaluator/listConditional.js +0 -0
  24. package/dist/evaluator/optionRules.js +5 -1
  25. package/dist/evaluator/predicateInput.d.ts +84 -0
  26. package/dist/evaluator/predicateInput.js +72 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +12 -0
  29. package/dist/protocols/index.d.ts +0 -1
  30. package/dist/protocols/index.js +8 -1
  31. package/dist/query/query-ast.d.ts +6 -6
  32. package/dist/query/query-ast.js +2 -2
  33. package/dist/registry/PluginSystem.d.ts +15 -5
  34. package/dist/registry/PluginSystem.js +1 -1
  35. package/dist/registry/WidgetRegistry.d.ts +7 -7
  36. package/dist/registry/WidgetRegistry.js +1 -1
  37. package/dist/styling/scoped-styles.d.ts +14 -8
  38. package/dist/utils/chart-series.d.ts +14 -2
  39. package/dist/utils/dashboard-filters.d.ts +9 -0
  40. package/dist/utils/dashboard-filters.js +99 -4
  41. package/dist/utils/dataset-format.d.ts +18 -1
  42. package/dist/utils/dataset-format.js +10 -2
  43. package/dist/utils/dataset-pivot.d.ts +57 -0
  44. package/dist/utils/dataset-pivot.js +57 -0
  45. package/dist/utils/filter-converter.d.ts +39 -11
  46. package/dist/utils/filter-converter.js +87 -7
  47. package/dist/utils/filter-tokens.d.ts +13 -6
  48. package/dist/utils/filter-tokens.js +13 -6
  49. package/dist/utils/freeze-schema.d.ts +14 -6
  50. package/dist/utils/freeze-schema.js +14 -6
  51. package/dist/utils/managedBy.d.ts +78 -50
  52. package/dist/utils/managedBy.js +75 -52
  53. package/dist/utils/normalize-list-view.d.ts +12 -2
  54. package/dist/utils/predicate-fields.d.ts +96 -0
  55. package/dist/utils/predicate-fields.js +163 -0
  56. package/dist/utils/predicate-record.d.ts +32 -0
  57. package/dist/utils/predicate-record.js +120 -0
  58. package/dist/utils/record-title.js +0 -0
  59. package/dist/validation/index.d.ts +1 -0
  60. package/dist/validation/index.js +1 -0
  61. package/dist/validation/required-presence.d.ts +31 -0
  62. package/dist/validation/required-presence.js +36 -0
  63. package/dist/validation/schema-validator.d.ts +24 -8
  64. package/package.json +4 -4
  65. package/dist/protocols/NotificationProtocol.d.ts +0 -71
  66. package/dist/protocols/NotificationProtocol.js +0 -89
@@ -24,6 +24,17 @@
24
24
  * block submit) and *fail-open* for readonly (a broken predicate leaves the
25
25
  * field editable) — matching the server, which logs and allows the change
26
26
  * through.
27
+ *
28
+ * Fail-open is **loud**, not silent (objectstack#5149): a predicate that
29
+ * cannot be evaluated — parse error, unbound identifier, engine fault — logs
30
+ * one `console.warn` per predicate text with the source and the failure
31
+ * reason, then still returns the caller's fallback. Without the warning a
32
+ * broken predicate is indistinguishable from an absent one, so
33
+ * conditional-visibility bugs survive inspection indefinitely. This is the
34
+ * client half of the server's "log and allow" convention (`readonlyWhen for
35
+ * 'x' failed to evaluate — change allowed through`). The *default* stays
36
+ * fail-open on purpose — flipping it is a shipped-behavior change tracked
37
+ * separately in objectstack#5149 (appeal 1, undecided).
27
38
  */
28
39
  import { ExpressionEngine } from '@objectstack/formula';
29
40
  /** Normalize a predicate into the `Expression` shape the engine expects. */
@@ -32,6 +43,26 @@ function toExpression(pred) {
32
43
  return { dialect: 'cel', source: pred };
33
44
  return { dialect: (pred.dialect ?? 'cel'), source: pred.source };
34
45
  }
46
+ const warnedPredicates = new Set();
47
+ /**
48
+ * One-time warning for a predicate that could not be evaluated. Deduped per
49
+ * predicate TEXT (dialect + source): a broken predicate is re-evaluated on
50
+ * every render/keystroke, and the point is one loud line, not a scrolling
51
+ * wall. The dedupe key is JSON-encoded — never a control-character separator
52
+ * (objectstack#5450 made a sibling of this file binary to grep that way).
53
+ */
54
+ function warnPredicateFailure(expr, fallback, reason, context) {
55
+ const key = JSON.stringify([expr.dialect, expr.source]);
56
+ if (warnedPredicates.has(key))
57
+ return;
58
+ warnedPredicates.add(key);
59
+ console.warn('[object-ui] A conditional predicate failed to evaluate' +
60
+ (context ? ` (${context})` : '') +
61
+ ` and was treated as its safe default (${String(fallback)}): ` +
62
+ JSON.stringify(expr.source) +
63
+ `. Reason: ${reason}. ` +
64
+ "Values are bound under 'record.' (e.g. record.status) — check the field names and CEL syntax.");
65
+ }
35
66
  /**
36
67
  * Evaluate a field-rule CEL predicate against a record.
37
68
  *
@@ -46,21 +77,37 @@ function toExpression(pred) {
46
77
  * e.g. `{ parent }` so an inline line-item cell can reference
47
78
  * its header (`parent.status == 'paid'`) as well as its own
48
79
  * row (`record.quantity`). Bound via the engine's `extra`.
80
+ * @param diagnostic Failure-warning options: a `context` locator for the
81
+ * one-time warning, and `warn: false` for callers that probe
82
+ * for faults and report them themselves. The returned value
83
+ * is unaffected either way.
49
84
  */
50
- export function evalFieldPredicate(pred, record, fallback, previous, scope) {
85
+ export function evalFieldPredicate(pred, record, fallback, previous, scope, diagnostic) {
51
86
  if (pred == null || (typeof pred === 'string' && !pred.trim()))
52
87
  return fallback;
88
+ const expr = toExpression(pred);
53
89
  try {
54
- const res = ExpressionEngine.evaluate(toExpression(pred), {
90
+ const res = ExpressionEngine.evaluate(expr, {
55
91
  record,
56
92
  previous,
57
93
  ...(scope ? { extra: scope } : {}),
58
94
  });
59
- if (!res.ok)
95
+ if (!res.ok) {
96
+ // Parse error, type error, unbound identifier, engine fault … — every
97
+ // not-ok verdict resolves to the fallback, but never silently (#5149).
98
+ if (diagnostic?.warn !== false) {
99
+ warnPredicateFailure(expr, fallback, `[${res.error.kind}] ${res.error.message}`, diagnostic?.context);
100
+ }
60
101
  return fallback;
102
+ }
61
103
  return res.value === true;
62
104
  }
63
- catch {
105
+ catch (err) {
106
+ // The engine contract is "never throws"; this guard is for anything that
107
+ // slips past it. Same loud fail-open as the not-ok branch.
108
+ if (diagnostic?.warn !== false) {
109
+ warnPredicateFailure(expr, fallback, `[throw] ${err instanceof Error ? err.message : String(err)}`, diagnostic?.context);
110
+ }
64
111
  return fallback;
65
112
  }
66
113
  }
@@ -70,18 +117,26 @@ export function evalFieldPredicate(pred, record, fallback, previous, scope) {
70
117
  * present, *overrides* the static flag. A static `true` is never weakened by a
71
118
  * `false` predicate result for required/readonly — but `visibleWhen` is
72
119
  * authoritative when present (so a field can be conditionally shown/hidden).
120
+ *
121
+ * @param fieldContext Optional locator for failure warnings — e.g.
122
+ * `"field 'amount'"`. The warning then reads
123
+ * `visibleWhen of field 'amount' …`; without it, the rule
124
+ * kind alone is reported.
73
125
  */
74
- export function resolveFieldRuleState(rules, record, statics, previous, scope) {
126
+ export function resolveFieldRuleState(rules, record, statics, previous, scope, fieldContext) {
127
+ const diag = (rule) => ({
128
+ context: fieldContext ? `${rule} of ${fieldContext}` : rule,
129
+ });
75
130
  const visible = rules.visibleWhen != null
76
- ? evalFieldPredicate(rules.visibleWhen, record, true, previous, scope)
131
+ ? evalFieldPredicate(rules.visibleWhen, record, true, previous, scope, diag('visibleWhen'))
77
132
  : true;
78
133
  const readonly = statics.readonly === true ||
79
134
  (rules.readonlyWhen != null
80
- ? evalFieldPredicate(rules.readonlyWhen, record, false, previous, scope)
135
+ ? evalFieldPredicate(rules.readonlyWhen, record, false, previous, scope, diag('readonlyWhen'))
81
136
  : false);
82
137
  const required = statics.required === true ||
83
138
  (rules.requiredWhen != null
84
- ? evalFieldPredicate(rules.requiredWhen, record, false, previous, scope)
139
+ ? evalFieldPredicate(rules.requiredWhen, record, false, previous, scope, diag('requiredWhen'))
85
140
  : false);
86
141
  return { visible, readonly, required };
87
142
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from './ExpressionContext.js';
9
9
  export * from './ExpressionEvaluator.js';
10
+ export * from './predicateInput.js';
10
11
  export * from './fieldRules.js';
11
12
  export * from './listConditional.js';
12
13
  export * from './optionRules.js';
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from './ExpressionContext.js';
9
9
  export * from './ExpressionEvaluator.js';
10
+ export * from './predicateInput.js';
10
11
  export * from './fieldRules.js';
11
12
  export * from './listConditional.js';
12
13
  export * from './optionRules.js';
@@ -34,6 +34,7 @@
34
34
  * (or nothing) to the server's CEL engine.
35
35
  */
36
36
  import { type FieldRulePredicate } from './fieldRules.js';
37
+ import { type FieldContainerLike } from '../utils/predicate-record.js';
37
38
  /** True when a *string* predicate uses legacy-only (non-CEL) syntax. */
38
39
  export declare function isLegacyDialectSource(source: string): boolean;
39
40
  /** Options for {@link evalRowPredicate}. */
@@ -49,6 +50,14 @@ export interface RowPredicateOptions {
49
50
  warnOnError?: boolean;
50
51
  /** Label used in warning messages (e.g. an action name or rule index). */
51
52
  label?: string;
53
+ /**
54
+ * The object's field definitions (`objectSchema.fields`). Supplying them lets
55
+ * a relation field be bound as the FOREIGN KEY the server stores, rather than
56
+ * as whatever `$expand` happened to substitute for it on this surface — see
57
+ * {@link toPredicateRecord}. Omitting them keeps the payload verbatim, so a
58
+ * caller with no schema at hand never guesses which fields are relations.
59
+ */
60
+ fields?: FieldContainerLike;
52
61
  }
53
62
  /**
54
63
  * Evaluate a single boolean predicate against a row record on the canonical CEL
@@ -89,5 +98,8 @@ export type ConditionalFormattingRuleLike = {
89
98
  *
90
99
  * @param scope Extra top-level scope (host predicate scope) bound alongside
91
100
  * the row, so `features.*` predicates keep resolving.
101
+ * @param fields The object's field definitions, so a rule comparing a relation
102
+ * field sees the stored foreign key rather than the expanded
103
+ * record (see {@link RowPredicateOptions.fields}).
92
104
  */
93
- export declare function resolveConditionalFormatting(record: Record<string, unknown> | null | undefined, rules?: readonly ConditionalFormattingRuleLike[] | null, scope?: Record<string, unknown>): Record<string, string>;
105
+ export declare function resolveConditionalFormatting(record: Record<string, unknown> | null | undefined, rules?: readonly ConditionalFormattingRuleLike[] | null, scope?: Record<string, unknown>, fields?: FieldContainerLike): Record<string, string>;
Binary file
@@ -39,7 +39,11 @@ export function isOptionGroupGated(dependsOn, record) {
39
39
  export function resolveVisibleOptions(options, record, scope) {
40
40
  if (!options || options.length === 0)
41
41
  return [];
42
- return options.filter((o) => o?.visibleWhen == null ? true : evalFieldPredicate(o.visibleWhen, record, true, undefined, scope));
42
+ return options.filter((o) => o?.visibleWhen == null
43
+ ? true
44
+ : evalFieldPredicate(o.visibleWhen, record, true, undefined, scope, {
45
+ context: `visibleWhen of option '${String(o.value)}'`,
46
+ }));
43
47
  }
44
48
  /**
45
49
  * Whether a scalar field value is still valid given the currently-visible
@@ -0,0 +1,84 @@
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
+ * The normalized shape {@link ExpressionEvaluator.evaluateCondition} consumes:
10
+ * a boolean (short-circuits), a `${…}` template string (legacy JS path), a
11
+ * `{ dialect: 'cel', source }` envelope (canonical `@objectstack/formula`
12
+ * path), or `undefined` for "no predicate declared".
13
+ *
14
+ * ## Why not `PredicateInput` (objectstack#4115 / objectui#3074)
15
+ *
16
+ * `@objectstack/spec` owns `PredicateInput` — and it is a genuinely DIFFERENT
17
+ * concept, so wearing that name would be the exact drift this repo's
18
+ * `check:spec-symbols` gate exists to stop. The spec's is
19
+ * `z.input<typeof PredicateInputSchema>`: what an author WRITES, before
20
+ * normalization — a bare string, or an envelope over the full dialect set
21
+ * (`cel` | `cron` | `template`) with optional `source` / `ast` / `meta`.
22
+ *
23
+ * This one is the OUTPUT of normalization: what the evaluator ACCEPTS. It
24
+ * admits `boolean` and the `${…}` template spelling (neither of which the spec
25
+ * models as a predicate), narrows `dialect` to `cel` alone (every other dialect
26
+ * has already been flattened onto the legacy path by `toPredicateInput`),
27
+ * requires a non-empty `source`, and carries no `ast` / `meta`. The two unions
28
+ * are not mutually assignable in either direction.
29
+ *
30
+ * The rename is pinned from both sides by the tripwire in
31
+ * `packages/core/src/utils/__tests__/spec-symbol-batch4.test.ts`: the spec must
32
+ * still own `PredicateInput` (else this rename has lost its reason), and must
33
+ * still NOT own `EvaluatorPredicateInput` (the objectui#3074 mistake of
34
+ * renaming onto another name the spec already holds).
35
+ */
36
+ export type EvaluatorPredicateInput = string | boolean | {
37
+ dialect: 'cel';
38
+ source: string;
39
+ } | undefined;
40
+ /**
41
+ * Normalize a schema-supplied predicate (`visible` / `enabled` / `disabled` /
42
+ * `hidden`) into the form {@link ExpressionEvaluator.evaluateCondition}
43
+ * expects.
44
+ *
45
+ * Accepts:
46
+ * - `boolean` → returned as-is (predicate evaluation short-circuits).
47
+ * - `string` → wrapped as `${string}` (legacy DX shorthand).
48
+ * - `Expression` envelope `{ dialect, source }` (the normalized form
49
+ * `@objectstack/spec`'s `ExpressionInputSchema` produces for every
50
+ * authored predicate, including bare strings) → a `cel` dialect keeps
51
+ * its envelope; every other dialect is unwrapped and wrapped as
52
+ * `${source}`.
53
+ * - `null` / `undefined` / empty / anything else → `undefined`
54
+ * (default visible/enabled).
55
+ *
56
+ * ## Why the `cel` envelope must survive normalization (#2661 / #3314)
57
+ *
58
+ * `evaluateCondition` routes to the canonical `@objectstack/formula` engine —
59
+ * the one the SERVER enforces with — only while the argument is still a
60
+ * `{ dialect: 'cel' }` object. Collapse it to `${source}` first and the
61
+ * predicate silently falls to the legacy JS evaluator, whose semantics
62
+ * differ: CEL has no `<` overload for `null`, so `null < null` faults and
63
+ * fail-closed hides, while JS quietly yields `false`; CEL-only builtins
64
+ * (`today()`, objectstack#3205) do not exist on the JS path at all. Two
65
+ * normalizers that disagree on this one branch therefore make the SAME
66
+ * `visible:` predicate reach different verdicts depending on whether the
67
+ * action was surfaced by `ActionEngine.getActionsForLocation` or rendered
68
+ * standalone (#3314).
69
+ *
70
+ * This is THE implementation, not one of two — `@object-ui/core` is the common
71
+ * dependency of every consumer, so engine-side code and renderer-side code
72
+ * share one normalization instead of hand-rolling envelope unwrapping per call
73
+ * site. `@object-ui/react`'s `toPredicateInput` is a re-export of this function
74
+ * (since #3367; it used to be an independent twin held in step by a 14-shape
75
+ * normalization parity table, which is exactly the arrangement the paragraph
76
+ * above describes the failure mode of). What pins that now is the identity
77
+ * assertion in
78
+ * `packages/react/src/hooks/__tests__/actionPredicate.parity.test.tsx` — the
79
+ * react export must BE this function object — alongside the engine-path vs
80
+ * renderer-path verdict parity suite in the same file, which is a separate
81
+ * claim and still earns its keep: sharing a normalizer does not by itself
82
+ * prove the two call paths reach the same verdict.
83
+ */
84
+ export declare function toPredicateInput(value: unknown): EvaluatorPredicateInput;
@@ -0,0 +1,72 @@
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
+ * Normalize a schema-supplied predicate (`visible` / `enabled` / `disabled` /
10
+ * `hidden`) into the form {@link ExpressionEvaluator.evaluateCondition}
11
+ * expects.
12
+ *
13
+ * Accepts:
14
+ * - `boolean` → returned as-is (predicate evaluation short-circuits).
15
+ * - `string` → wrapped as `${string}` (legacy DX shorthand).
16
+ * - `Expression` envelope `{ dialect, source }` (the normalized form
17
+ * `@objectstack/spec`'s `ExpressionInputSchema` produces for every
18
+ * authored predicate, including bare strings) → a `cel` dialect keeps
19
+ * its envelope; every other dialect is unwrapped and wrapped as
20
+ * `${source}`.
21
+ * - `null` / `undefined` / empty / anything else → `undefined`
22
+ * (default visible/enabled).
23
+ *
24
+ * ## Why the `cel` envelope must survive normalization (#2661 / #3314)
25
+ *
26
+ * `evaluateCondition` routes to the canonical `@objectstack/formula` engine —
27
+ * the one the SERVER enforces with — only while the argument is still a
28
+ * `{ dialect: 'cel' }` object. Collapse it to `${source}` first and the
29
+ * predicate silently falls to the legacy JS evaluator, whose semantics
30
+ * differ: CEL has no `<` overload for `null`, so `null < null` faults and
31
+ * fail-closed hides, while JS quietly yields `false`; CEL-only builtins
32
+ * (`today()`, objectstack#3205) do not exist on the JS path at all. Two
33
+ * normalizers that disagree on this one branch therefore make the SAME
34
+ * `visible:` predicate reach different verdicts depending on whether the
35
+ * action was surfaced by `ActionEngine.getActionsForLocation` or rendered
36
+ * standalone (#3314).
37
+ *
38
+ * This is THE implementation, not one of two — `@object-ui/core` is the common
39
+ * dependency of every consumer, so engine-side code and renderer-side code
40
+ * share one normalization instead of hand-rolling envelope unwrapping per call
41
+ * site. `@object-ui/react`'s `toPredicateInput` is a re-export of this function
42
+ * (since #3367; it used to be an independent twin held in step by a 14-shape
43
+ * normalization parity table, which is exactly the arrangement the paragraph
44
+ * above describes the failure mode of). What pins that now is the identity
45
+ * assertion in
46
+ * `packages/react/src/hooks/__tests__/actionPredicate.parity.test.tsx` — the
47
+ * react export must BE this function object — alongside the engine-path vs
48
+ * renderer-path verdict parity suite in the same file, which is a separate
49
+ * claim and still earns its keep: sharing a normalizer does not by itself
50
+ * prove the two call paths reach the same verdict.
51
+ */
52
+ export function toPredicateInput(value) {
53
+ if (value === null || value === undefined || value === '')
54
+ return undefined;
55
+ if (typeof value === 'boolean')
56
+ return value;
57
+ if (typeof value === 'string')
58
+ return `\${${value}}`;
59
+ if (typeof value === 'object' && typeof value.source === 'string') {
60
+ const src = value.source;
61
+ if (!src)
62
+ return undefined;
63
+ // Preserve a CEL-dialect envelope so `evaluateCondition` routes it to the
64
+ // canonical `@objectstack/formula` engine (identical verdict to the
65
+ // server) instead of collapsing it onto the legacy JS path.
66
+ if (value.dialect === 'cel')
67
+ return { dialect: 'cel', source: src };
68
+ // Every other dialect (template / unset) keeps the legacy `${…}` behavior.
69
+ return `\${${src}}`;
70
+ }
71
+ return undefined;
72
+ }
package/dist/index.d.ts CHANGED
@@ -48,7 +48,10 @@ export * from './utils/merge-filters.js';
48
48
  export * from './utils/compare-to.js';
49
49
  export * from './utils/chart-series.js';
50
50
  export * from './utils/dataset-format.js';
51
+ export * from './utils/dataset-pivot.js';
51
52
  export * from './utils/record-title.js';
52
53
  export * from './utils/export-filename.js';
53
54
  export * from './utils/reference-keys.js';
55
+ export * from './utils/predicate-record.js';
56
+ export * from './utils/predicate-fields.js';
54
57
  export * from './utils/normalize-list-view.js';
package/dist/index.js CHANGED
@@ -50,7 +50,19 @@ export * from './utils/merge-filters.js';
50
50
  export * from './utils/compare-to.js';
51
51
  export * from './utils/chart-series.js';
52
52
  export * from './utils/dataset-format.js';
53
+ // Pivot lookup-key encoders, shared by every cross-tab renderer so the
54
+ // dashboard widget and the report renderer key their buckets identically
55
+ // (objectstack#5473 / objectstack#5665).
56
+ export * from './utils/dataset-pivot.js';
53
57
  export * from './utils/record-title.js';
54
58
  export * from './utils/export-filename.js';
55
59
  export * from './utils/reference-keys.js';
60
+ // Binds a fetched record into an expression scope the way the SERVER binds it
61
+ // (a relation is its foreign key, never the expanded record) — see
62
+ // `toPredicateRecord` for why an unnormalized one gives the same predicate
63
+ // different verdicts on different surfaces.
64
+ export * from './utils/predicate-record.js';
65
+ // The other half of a view's field appetite: the fields its PREDICATES read,
66
+ // which the column-derived `$select` never asked the server for.
67
+ export * from './utils/predicate-fields.js';
56
68
  export * from './utils/normalize-list-view.js';
@@ -7,6 +7,5 @@
7
7
  */
8
8
  export * from './DndProtocol.js';
9
9
  export * from './KeyboardProtocol.js';
10
- export * from './NotificationProtocol.js';
11
10
  export * from './ResponsiveProtocol.js';
12
11
  export * from './SharingProtocol.js';
@@ -7,6 +7,13 @@
7
7
  */
8
8
  export * from './DndProtocol.js';
9
9
  export * from './KeyboardProtocol.js';
10
- export * from './NotificationProtocol.js';
10
+ // `NotificationProtocol` removed in the 17.0.0-rc.2 uptake: it bridged
11
+ // `@objectstack/spec/ui`'s `Notification` / `NotificationConfig`, which
12
+ // objectstack#4610 deleted with no successor. The bridge had zero consumers in
13
+ // this repo — the live notification config resolution is
14
+ // `resolveNotificationConfig` in `@object-ui/react`'s `NotificationContext`,
15
+ // which declares its own `NotificationSystemConfig` and is what every surface
16
+ // reads. Re-typing the bridge would have meant re-declaring a vocabulary the
17
+ // spec had just retired (AGENTS.md #0.1).
11
18
  export * from './ResponsiveProtocol.js';
12
19
  export * from './SharingProtocol.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
@@ -9,12 +9,12 @@
9
9
  * WidgetRegistry - Runtime widget management with auto-discovery.
10
10
  *
11
11
  * Provides registration, loading, and lookup of runtime widgets
12
- * described by WidgetManifest objects. Widgets can be loaded from
12
+ * described by RuntimeWidgetManifest objects. Widgets can be loaded from
13
13
  * ES module URLs, provided inline, or resolved from the component registry.
14
14
  *
15
15
  * @module
16
16
  */
17
- import type { WidgetManifest, ResolvedWidget, WidgetRegistryListener } from '@object-ui/types';
17
+ import type { RuntimeWidgetManifest, ResolvedWidget, WidgetRegistryListener } from '@object-ui/types';
18
18
  import type { Registry } from './Registry.js';
19
19
  /**
20
20
  * Options for creating a WidgetRegistry instance.
@@ -54,11 +54,11 @@ export declare class WidgetRegistry {
54
54
  * Register a widget manifest.
55
55
  * Does not load the widget; call `load()` to resolve it.
56
56
  */
57
- register(manifest: WidgetManifest): void;
57
+ register(manifest: RuntimeWidgetManifest): void;
58
58
  /**
59
59
  * Register multiple widget manifests at once.
60
60
  */
61
- registerAll(manifests: WidgetManifest[]): void;
61
+ registerAll(manifests: RuntimeWidgetManifest[]): void;
62
62
  /**
63
63
  * Unregister a widget by name.
64
64
  */
@@ -66,15 +66,15 @@ export declare class WidgetRegistry {
66
66
  /**
67
67
  * Get a widget manifest by name.
68
68
  */
69
- getManifest(name: string): WidgetManifest | undefined;
69
+ getManifest(name: string): RuntimeWidgetManifest | undefined;
70
70
  /**
71
71
  * Get all registered widget manifests.
72
72
  */
73
- getAllManifests(): WidgetManifest[];
73
+ getAllManifests(): RuntimeWidgetManifest[];
74
74
  /**
75
75
  * Get manifests filtered by category.
76
76
  */
77
- getByCategory(category: string): WidgetManifest[];
77
+ getByCategory(category: string): RuntimeWidgetManifest[];
78
78
  /**
79
79
  * Check if a widget is registered.
80
80
  */
@@ -244,7 +244,7 @@ export class WidgetRegistry {
244
244
  // source.url is only known at runtime.
245
245
  //
246
246
  // Security: Widget URLs must be from trusted sources only. Never pass
247
- // user-supplied URLs directly to WidgetManifest. URLs should be validated
247
+ // user-supplied URLs directly to RuntimeWidgetManifest. URLs should be validated
248
248
  // and controlled by the application developer.
249
249
  //
250
250
  // CSP Consideration: If your application uses strict Content Security Policy,
@@ -19,14 +19,20 @@
19
19
  * `large` is the unconditional base; `medium`/`small`/`xsmall` are `max-width`
20
20
  * overrides.
21
21
  */
22
- export type StyleMap = Record<string, string | number>;
23
- export interface ResponsiveStyles {
24
- /** Unconditional base (desktop-first). */
25
- large?: StyleMap;
26
- medium?: StyleMap;
27
- small?: StyleMap;
28
- xsmall?: StyleMap;
29
- }
22
+ import type { ResponsiveStyles } from '@objectstack/spec/ui';
23
+ /**
24
+ * `StyleMap` / `ResponsiveStyles` are **owned by the spec** (ADR-0065,
25
+ * `@objectstack/spec/ui`) and re-exported here, not re-declared.
26
+ *
27
+ * They used to be hand-written copies under the spec's own names. Both were
28
+ * member-for-member identical to the spec's at the time of the burn-down
29
+ * (objectstack#4115), which is exactly what makes a copy dangerous: it passes
30
+ * every structural comparison right up until the spec adds a breakpoint or
31
+ * widens a value type, and the doc comment above claims spec canonicity the
32
+ * whole time. Binding the names to the spec at compile time is the only form
33
+ * that cannot drift.
34
+ */
35
+ export type { StyleMap, ResponsiveStyles } from '@objectstack/spec/ui';
30
36
  /** max-width breakpoints (px), mirroring Builder.io's default device sizes. */
31
37
  export declare const STYLE_BREAKPOINTS: Record<'medium' | 'small' | 'xsmall', number>;
32
38
  /** True when a node actually carries responsive styles worth compiling. */
@@ -26,14 +26,26 @@ export interface ChartResultField {
26
26
  label?: string;
27
27
  format?: string;
28
28
  }
29
- export interface ChartSeries {
29
+ /**
30
+ * One renderer-internal series binding produced by {@link buildChartSeries}:
31
+ * WHICH result-set column to draw and what to call it.
32
+ *
33
+ * NOT the spec's `ChartSeries` (`@objectstack/spec/ui`), which this type was
34
+ * named after until objectstack#4115. That one is the AUTHORED dataset-binding
35
+ * descriptor — `{ name, label?, type?, color?, stack?, yAxis, variant?,
36
+ * dashArray?, opacity? }`, where `name` is a measure and no `data` is carried.
37
+ * A third shape, `ChartDataSeries` in `@object-ui/types` (renamed in the same
38
+ * burn-down), is the inline static-data series of an SDUI `ChartSchema` node.
39
+ * Three different concepts; this is the one the chart renderers consume.
40
+ */
41
+ export interface ChartSeriesBinding {
30
42
  dataKey: string;
31
43
  label: string;
32
44
  }
33
45
  export interface ChartSeriesResult {
34
46
  data: Array<Record<string, unknown>>;
35
47
  xAxisKey: string | undefined;
36
- series: ChartSeries[];
48
+ series: ChartSeriesBinding[];
37
49
  }
38
50
  export declare function buildChartSeries(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, fields?: ChartResultField[] | null): ChartSeriesResult;
39
51
  /**
@@ -78,6 +78,15 @@ export declare function dashboardFilterVariableDefs(defs: DashboardFilterDef[]):
78
78
  * Build the operator shape (the value side of a `FilterCondition` entry) for
79
79
  * one filter's current value. Returns `undefined` when the value imposes no
80
80
  * constraint. The caller keys the result by the bound field name.
81
+ *
82
+ * A `date`/`dateRange` value is held to three spellings (#3151): a known
83
+ * preset name → range bounds; a date-macro token or ISO date → equality on
84
+ * that day; **anything else → skipped with a console warning**, never
85
+ * silently downgraded to an equality nothing can match. That last branch is
86
+ * the same strictness `buildWidgetScopedFilter` applies to a default binding
87
+ * on an unknown field name, for the same reason: a query the backend answers
88
+ * `200 OK` with zero rows is indistinguishable from "this range has no data",
89
+ * so the typo has to be said out loud somewhere.
81
90
  */
82
91
  export declare function buildFilterCondition(def: DashboardFilterDef, value: unknown): Record<string, unknown> | unknown | undefined;
83
92
  /**