@object-ui/core 17.3.0 → 17.5.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 +1224 -0
- package/README.md +2 -12
- package/dist/actions/ActionEngine.d.ts +42 -25
- package/dist/actions/ActionEngine.js +66 -52
- package/dist/actions/ActionRunner.d.ts +199 -21
- package/dist/actions/ActionRunner.js +332 -16
- package/dist/actions/actionKeys.d.ts +96 -20
- package/dist/actions/actionKeys.js +157 -18
- package/dist/data-scope/ViewDataProvider.d.ts +37 -12
- package/dist/data-scope/ViewDataProvider.js +83 -5
- package/dist/data-scope/element-data-source.d.ts +113 -0
- package/dist/data-scope/element-data-source.js +216 -0
- package/dist/data-scope/index.d.ts +2 -1
- package/dist/data-scope/index.js +1 -0
- package/dist/evaluator/declaredPredicate.d.ts +119 -0
- package/dist/evaluator/declaredPredicate.js +152 -0
- package/dist/evaluator/fieldRules.d.ts +16 -0
- package/dist/evaluator/fieldRules.js +51 -16
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +5 -2
- package/dist/evaluator/listConditional.js +58 -16
- package/dist/evaluator/predicateInput.d.ts +51 -3
- package/dist/evaluator/predicateInput.js +62 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +14 -1
- package/dist/protocols/DndProtocol.d.ts +42 -1
- package/dist/protocols/KeyboardProtocol.d.ts +31 -1
- package/dist/protocols/SharingProtocol.d.ts +11 -1
- package/dist/registry/Registry.d.ts +24 -0
- package/dist/theme/ThemeEngine.d.ts +0 -8
- package/dist/theme/ThemeEngine.js +22 -103
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +7 -1
- package/dist/types/index.d.ts +42 -8
- package/dist/utils/chart-series.d.ts +258 -5
- package/dist/utils/chart-series.js +423 -20
- package/dist/utils/compare-to.d.ts +40 -13
- package/dist/utils/compare-to.js +4 -32
- package/dist/utils/dashboard-filters.d.ts +54 -8
- package/dist/utils/dashboard-filters.js +261 -12
- package/dist/utils/dataset-format.d.ts +73 -14
- package/dist/utils/dataset-format.js +130 -9
- package/dist/utils/dataset-pivot.d.ts +28 -7
- package/dist/utils/dataset-pivot.js +27 -6
- package/dist/utils/dom-props.d.ts +137 -0
- package/dist/utils/dom-props.js +153 -0
- package/dist/utils/normalize-list-view.d.ts +26 -6
- package/dist/utils/normalize-list-view.js +30 -8
- package/dist/utils/number-display.d.ts +151 -0
- package/dist/utils/number-display.js +71 -0
- package/dist/utils/resolve-view-id.d.ts +31 -0
- package/dist/utils/resolve-view-id.js +45 -0
- package/dist/utils/sort-query.d.ts +55 -0
- package/dist/utils/sort-query.js +35 -0
- package/package.json +5 -5
- package/dist/utils/merge-views-into-objects.d.ts +0 -19
- package/dist/utils/merge-views-into-objects.js +0 -39
|
@@ -6,26 +6,46 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* What an action is allowed to carry — objectstack#4075
|
|
9
|
+
* What an action is allowed to carry — objectstack#4075, all three steps.
|
|
10
10
|
*
|
|
11
|
-
* `ActionDef`
|
|
12
|
-
* Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
-
* errors even though the field had just been removed, and stale
|
|
14
|
-
* authoring `execute: 'markDone'` type-
|
|
15
|
-
* `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
-
* produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
-
* retired key; the other
|
|
11
|
+
* `ActionDef` used to end with `[key: string]: any`, so it accepted any key of
|
|
12
|
+
* any type. Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
+
* ZERO compile errors even though the field had just been removed, and stale
|
|
14
|
+
* metadata authoring `execute: 'markDone'` type-checked. The same deletion
|
|
15
|
+
* against `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
+
* correctly produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
+
* could catch a retired key; the other was structurally incapable.
|
|
18
18
|
*
|
|
19
|
-
* That asymmetry
|
|
19
|
+
* That asymmetry was the issue. An open key set on a DECLARED METADATA CONTRACT
|
|
20
20
|
* is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
|
|
21
21
|
* sail through to a runner that then silently does nothing — the #2169 "Mark
|
|
22
22
|
* Done does nothing" shape.
|
|
23
23
|
*
|
|
24
|
-
* This module
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* This module was step 1 of the staged narrowing: make the key set VISIBLE and
|
|
25
|
+
* warn on anything outside it, without changing a single type. Step 2 promoted
|
|
26
|
+
* the legitimate keys to explicit optional fields; **step 3 removed the index
|
|
27
|
+
* signature**, so `tsc` now rejects a typo or a retired key outright.
|
|
28
|
+
*
|
|
29
|
+
* ── Why this module SURVIVES step 3 ──────────────────────────────────────────
|
|
30
|
+
* Step 1 wrote that the dev-mode warning "can retire" once the index signature
|
|
31
|
+
* came down. Step 3 measured that and kept it, because the two mechanisms cover
|
|
32
|
+
* DISJOINT populations rather than the same one twice:
|
|
33
|
+
*
|
|
34
|
+
* - `tsc` sees action literals AUTHORED IN CODE. It rejects an unknown key at
|
|
35
|
+
* the construction site, which is the strongest possible signal — but only
|
|
36
|
+
* for actions that exist as TypeScript.
|
|
37
|
+
* - the warning below sees actions that arrive as DATA. objectstack#3903
|
|
38
|
+
* established that stored `sys_metadata` rows are rehydrated UNPARSED, so
|
|
39
|
+
* authored metadata reaches `runner.execute()` as a plain object no
|
|
40
|
+
* compiler ever looked at. That is the population `execute: 'markDone'`
|
|
41
|
+
* actually lives in, and no type can reach it.
|
|
42
|
+
*
|
|
43
|
+
* Retiring the warning would therefore have re-opened the runtime half of the
|
|
44
|
+
* gap while closing the compile-time half — which is why `executeScript`'s
|
|
45
|
+
* rename-prescription branch also survives, for the same rows and the same
|
|
46
|
+
* reason. What DID retire with the index signature is the inverted pin in
|
|
47
|
+
* `actionKeys.pin.test.ts` that asserted the signature was still there; it now
|
|
48
|
+
* asserts the opposite, which was always its stated completion condition.
|
|
29
49
|
*
|
|
30
50
|
* The lists are pinned by `__tests__/actionKeys.pin.test.ts`, which re-derives
|
|
31
51
|
* each one from its actual source. A hand-maintained list that drifts from the
|
|
@@ -66,8 +86,22 @@
|
|
|
66
86
|
* deprecating them toward a spelling that does not exist would be worse than
|
|
67
87
|
* leaving them declared.
|
|
68
88
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
89
|
+
* ── What step 3 found the first two steps had missed ─────────────────────────
|
|
90
|
+
* Deleting the index signature is the only thing that can name the keys still
|
|
91
|
+
* hiding behind it, and it named five, in two groups:
|
|
92
|
+
*
|
|
93
|
+
* `to` / `external` / `newTab` / `replace` — the `navigation` alias's own
|
|
94
|
+
* spelling. Step 1 had already ruled these legitimate and listed them below in
|
|
95
|
+
* {@link NAVIGATION_ALIAS_KEYS}, but step 2's scope was the SPEC-owned keys, so
|
|
96
|
+
* they were declared as data and never as fields; `executeNavigation` read them
|
|
97
|
+
* through the index signature. Promoted, and deliberately not `@deprecated` —
|
|
98
|
+
* step 2's acceptance ruled on exactly that point.
|
|
99
|
+
*
|
|
100
|
+
* `description` — objectui vocabulary from `@object-ui/types`' renderer view of
|
|
101
|
+
* an action, forwarded by all four action renderers (`check:action-forward-parity`
|
|
102
|
+
* requires it) and read by the param-collection dialog for its subtitle
|
|
103
|
+
* (objectui#4192). Authorable, forwarded, read — and undeclared. These were the
|
|
104
|
+
* only two `TS2353`s the deletion produced across the whole workspace.
|
|
71
105
|
*/
|
|
72
106
|
/**
|
|
73
107
|
* Every property `ActionDef` declares, in declaration order.
|
|
@@ -81,6 +115,10 @@ export const ACTION_DEF_KEYS = [
|
|
|
81
115
|
'actionType',
|
|
82
116
|
'name',
|
|
83
117
|
'label',
|
|
118
|
+
// Promoted by step 3 — objectui vocabulary (`@object-ui/types`' renderer view
|
|
119
|
+
// of an action), forwarded by all four action renderers and read by the
|
|
120
|
+
// param-collection dialog, but never declared on `ActionDef`.
|
|
121
|
+
'description',
|
|
84
122
|
'confirmText',
|
|
85
123
|
'confirm',
|
|
86
124
|
'condition',
|
|
@@ -89,6 +127,14 @@ export const ACTION_DEF_KEYS = [
|
|
|
89
127
|
'endpoint',
|
|
90
128
|
'method',
|
|
91
129
|
'navigate',
|
|
130
|
+
// The `navigation` alias's own spelling, promoted to real fields by step 3.
|
|
131
|
+
// Listed a second time in `NAVIGATION_ALIAS_KEYS` below, which keeps the
|
|
132
|
+
// separate fact that these four are objectui dialect with a spec-adoption
|
|
133
|
+
// tripwire; this list is only "what the interface declares".
|
|
134
|
+
'to',
|
|
135
|
+
'external',
|
|
136
|
+
'newTab',
|
|
137
|
+
'replace',
|
|
92
138
|
'onClick',
|
|
93
139
|
'reload',
|
|
94
140
|
'close',
|
|
@@ -169,6 +215,15 @@ export const SPEC_ACTION_KEYS = [
|
|
|
169
215
|
'bulkEnabled',
|
|
170
216
|
'component',
|
|
171
217
|
'confirmText',
|
|
218
|
+
// Added to `ActionSchema` in @objectstack/spec 17.0.0-rc.6. Listed here purely
|
|
219
|
+
// because this array's contract is "every property the spec's `ActionSchema`
|
|
220
|
+
// declares" — restating the spec, not adopting a feature. `ActionDef` does NOT
|
|
221
|
+
// declare `description` and is not changed: whether the runner or any renderer
|
|
222
|
+
// should READ it is a separate question, and `actionKeys.pin.test.ts`'s
|
|
223
|
+
// `ActionDef` half stays green without it. What this entry buys is that an
|
|
224
|
+
// action carrying a `description` is no longer reported as having an unknown
|
|
225
|
+
// key, which is the only thing the inventory is consulted for.
|
|
226
|
+
'description',
|
|
172
227
|
'disabled',
|
|
173
228
|
'errorMessage',
|
|
174
229
|
'execute',
|
|
@@ -217,8 +272,15 @@ export const NAVIGATION_ALIAS_KEYS = ['to', 'external', 'newTab', 'replace'];
|
|
|
217
272
|
* Warned about separately from unknown keys, and more loudly: an unknown key is
|
|
218
273
|
* probably a typo, while a retired key is metadata that used to work. That
|
|
219
274
|
* distinction is why `executeScript` carries a runtime branch returning the
|
|
220
|
-
* rename prescription
|
|
221
|
-
*
|
|
275
|
+
* rename prescription.
|
|
276
|
+
*
|
|
277
|
+
* Step 1 expected that branch to retire with the index signature. It does not,
|
|
278
|
+
* and the reason is the one in this module's header: `tsc` closed the
|
|
279
|
+
* code-authored half of the gap, while `execute: 'markDone'` lives in stored
|
|
280
|
+
* rows that reach the runner UNPARSED (objectstack#3903). The branch reads the
|
|
281
|
+
* key off an untyped view of the action precisely so that `ActionDef` can go on
|
|
282
|
+
* NOT declaring it — declaring it to make the read compile would re-legitimize
|
|
283
|
+
* a tombstone the spec keeps only in order to reject it by name.
|
|
222
284
|
*/
|
|
223
285
|
export const RETIRED_ACTION_KEYS = {
|
|
224
286
|
execute: '`execute` was removed in @objectstack/spec 17 (#3855) — rename the key to `target`. ' +
|
|
@@ -292,3 +354,80 @@ export function warnOnUnknownActionKeys(action, where = 'ActionRunner') {
|
|
|
292
354
|
'(packages/core/src/actions/actionKeys.ts). Warned rather than rejected because `ActionDef` ' +
|
|
293
355
|
'still carries `[key: string]: any`, so the compiler cannot see this (objectstack#4075).');
|
|
294
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Keys a HOST stashes into an action's `params` for the runner's own plumbing,
|
|
359
|
+
* rather than an author writing them there as a request payload.
|
|
360
|
+
*
|
|
361
|
+
* {@link warnOnDeprecatedObjectParams} has to tell "the author put the payload in
|
|
362
|
+
* `params`" (the shape objectstack#5777 retires) apart from "a host stashed row
|
|
363
|
+
* context there" (a shape the runner itself asks for, and that is not going
|
|
364
|
+
* away). `DeclaredActionsBar` dispatches `params: { _rowRecord: record }`,
|
|
365
|
+
* `RelatedRecordActionsBridge` does the same, and `ObjectGrid` adds
|
|
366
|
+
* `_selectedIds`; the api handler lifts `recordId` out of the field values
|
|
367
|
+
* before it updates. Warning on those would fire on nearly every declared-action
|
|
368
|
+
* click and prescribe `bodyExtra`, which is the wrong home for all of them.
|
|
369
|
+
*
|
|
370
|
+
* The `_` prefix is the existing convention for the stash — `recordId` is its one
|
|
371
|
+
* unprefixed member, so it is listed rather than inferred.
|
|
372
|
+
*/
|
|
373
|
+
const HOST_STASHED_PARAM_KEYS = new Set(['recordId']);
|
|
374
|
+
const isAuthoredParamKey = (key) => !key.startsWith('_') && !HOST_STASHED_PARAM_KEYS.has(key);
|
|
375
|
+
/**
|
|
376
|
+
* Does this action carry the DEPRECATED object-form `params` — a static request
|
|
377
|
+
* payload written under the key that means "parameter definitions"?
|
|
378
|
+
*
|
|
379
|
+
* The discriminator is `Array.isArray` and the type guard is `api`, mirroring
|
|
380
|
+
* `@objectstack/spec`'s `inline-action-api-params-to-body-extra` conversion
|
|
381
|
+
* (ADR-0087 D2) key for key. Contract parity with the producer is the point: the
|
|
382
|
+
* conversion rewrites exactly this shape to `bodyExtra` at load, so the runner
|
|
383
|
+
* must recognize exactly the same shape as "the thing that got rewritten".
|
|
384
|
+
*
|
|
385
|
+
* The `api` guard is also what keeps the deprecation out of objectstack#6828's
|
|
386
|
+
* territory — object-form `params` on a `type: 'url'` action is a THIRD meaning
|
|
387
|
+
* (`interpolateTarget`'s `${param.X}` scope, `executeUrl`'s `params.newTab`) and
|
|
388
|
+
* `bodyExtra` is NOT its replacement. Do not widen this guard without that
|
|
389
|
+
* ruling.
|
|
390
|
+
*/
|
|
391
|
+
export function hasDeprecatedObjectParams(action) {
|
|
392
|
+
if (!action || typeof action !== 'object')
|
|
393
|
+
return false;
|
|
394
|
+
const { type, actionType, params } = action;
|
|
395
|
+
if ((type ?? actionType) !== 'api')
|
|
396
|
+
return false;
|
|
397
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
398
|
+
return false;
|
|
399
|
+
return Object.keys(params).some(isAuthoredParamKey);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Dev-mode only: name the object-form `params` that `@objectstack/spec` 17 already
|
|
403
|
+
* refuses at the authoring door, and prescribe `bodyExtra`.
|
|
404
|
+
*
|
|
405
|
+
* This is the audible half of the compat window the maintainer's 2026-08-06
|
|
406
|
+
* ruling on objectstack#5777 ordered (direction A — a separate key, no same-name
|
|
407
|
+
* union): the runner keeps READING a non-array `params` as the static payload for
|
|
408
|
+
* one version window, says so, and the arm is deleted at 18. The loud half lives
|
|
409
|
+
* at the producer, where it belongs — spec's `params` field refuses the object
|
|
410
|
+
* form outright with a message naming `bodyExtra`, and the ADR-0087 conversion
|
|
411
|
+
* rewrites sources that still carry it. So a warning is the correct severity
|
|
412
|
+
* HERE: by the time metadata reaches this runner the producer has already had its
|
|
413
|
+
* say, and what is left to catch is a host composing an ActionDef in code.
|
|
414
|
+
*
|
|
415
|
+
* Dev-only and warn-once, matching {@link warnOnUnknownActionKeys} — a warning
|
|
416
|
+
* that floods a production console is a warning that gets muted.
|
|
417
|
+
*/
|
|
418
|
+
export function warnOnDeprecatedObjectParams(action, where = 'ActionRunner') {
|
|
419
|
+
if (!isDev())
|
|
420
|
+
return;
|
|
421
|
+
if (!hasDeprecatedObjectParams(action))
|
|
422
|
+
return;
|
|
423
|
+
const name = action?.name ?? '(unnamed)';
|
|
424
|
+
const memo = `deprecated-object-params:${String(name)}`;
|
|
425
|
+
if (warned.has(memo))
|
|
426
|
+
return;
|
|
427
|
+
warned.add(memo);
|
|
428
|
+
console.warn(`[${where}] action "${String(name)}" carries a static request payload in \`params\`. ` +
|
|
429
|
+
'`params` is the parameter DEFINITION array (fields collected from the user before the ' +
|
|
430
|
+
'action runs), never a payload map — put the payload in `bodyExtra` instead ' +
|
|
431
|
+
'(objectstack#5777). Still read as the payload for one version window; the arm is ' +
|
|
432
|
+
'removed at 18. `bodyExtra` is merged LAST, so it overrides anything left here.');
|
|
433
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* @module data-scope
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
21
|
+
import { type ElementDataSourceConfig, type ElementSavedView } from './element-data-source.js';
|
|
21
22
|
/** ViewData union — matches @objectstack/spec ViewDataSchema */
|
|
22
23
|
export type ViewDataConfig = {
|
|
23
24
|
provider: 'object';
|
|
@@ -38,17 +39,12 @@ export type ViewDataConfig = {
|
|
|
38
39
|
provider: 'value';
|
|
39
40
|
items: any[];
|
|
40
41
|
};
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
field: string;
|
|
48
|
-
order: 'asc' | 'desc';
|
|
49
|
-
}>;
|
|
50
|
-
limit?: number;
|
|
51
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* Re-exported from `./element-data-source.js`, where the binding's type now
|
|
44
|
+
* lives alongside the logic that consumes it. Kept on this module path because
|
|
45
|
+
* that is where it was declared before objectstack#5576.
|
|
46
|
+
*/
|
|
47
|
+
export type { ElementDataSourceConfig };
|
|
52
48
|
/** Fetcher interface — consumers provide the actual fetch implementation */
|
|
53
49
|
export interface DataFetcher {
|
|
54
50
|
/** Fetch records for an object */
|
|
@@ -82,6 +78,19 @@ export interface DataFetcher {
|
|
|
82
78
|
headers?: Record<string, string>;
|
|
83
79
|
body?: any;
|
|
84
80
|
}): Promise<any>;
|
|
81
|
+
/**
|
|
82
|
+
* Fetch the object's saved views, so an `ElementDataSource.view` name can be
|
|
83
|
+
* resolved to real columns / filter / sort (objectstack#5576).
|
|
84
|
+
*
|
|
85
|
+
* Optional because most fetchers only read records. A fetcher WITHOUT it
|
|
86
|
+
* cannot honour a named view, and {@link ViewDataProvider.resolveElementDataSource}
|
|
87
|
+
* says so in `error` rather than dropping the key — the whole point of #5576
|
|
88
|
+
* is that a `view` the runtime cannot apply must never look applied.
|
|
89
|
+
*
|
|
90
|
+
* Either shape stored views come in is accepted: a map keyed by view id, or
|
|
91
|
+
* the array of overlay rows an adapter's `listViews()` returns.
|
|
92
|
+
*/
|
|
93
|
+
fetchViews?(object: string): Promise<Record<string, ElementSavedView> | ElementSavedView[]>;
|
|
85
94
|
}
|
|
86
95
|
/** Resolved data result */
|
|
87
96
|
export interface ResolvedData {
|
|
@@ -138,6 +147,22 @@ export declare class ViewDataProvider {
|
|
|
138
147
|
private resolveApi;
|
|
139
148
|
/** Resolve object-based data (metadata-driven) */
|
|
140
149
|
private resolveObject;
|
|
141
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Resolve an element-level data source (`PageComponentSchema.dataSource`).
|
|
152
|
+
*
|
|
153
|
+
* `view` is HONOURED here, not dropped. Until objectstack#5576 this method
|
|
154
|
+
* forwarded `filter`/`sort`/`limit` and silently discarded `view`, so a page
|
|
155
|
+
* component that named a saved view got every record the object had — the
|
|
156
|
+
* declared binding was inert while looking applied.
|
|
157
|
+
*
|
|
158
|
+
* Three outcomes, all explicit:
|
|
159
|
+
* - no `view` → unchanged behaviour, the binding's own keys are forwarded;
|
|
160
|
+
* - `view` resolved → its columns become the `fields` projection and its
|
|
161
|
+
* filter/sort/limit the baseline the binding's keys compose with (see
|
|
162
|
+
* {@link composeElementDataSource} for the per-key rule);
|
|
163
|
+
* - `view` unresolvable, or the fetcher cannot list views at all → an
|
|
164
|
+
* `error` naming the view and what the object does have. NOT an empty
|
|
165
|
+
* result, and NOT a silent fall back to the unfiltered object.
|
|
166
|
+
*/
|
|
142
167
|
resolveElementDataSource(config: ElementDataSourceConfig): Promise<ResolvedData>;
|
|
143
168
|
}
|
|
@@ -5,6 +5,20 @@
|
|
|
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
|
+
* @object-ui/core - View Data Provider
|
|
10
|
+
*
|
|
11
|
+
* Resolves @objectstack/spec ViewData discriminated union into
|
|
12
|
+
* a unified data access interface. Supports three data modes:
|
|
13
|
+
*
|
|
14
|
+
* 1. 'object' — Metadata-driven via ObjectStack API (object name → fields/records)
|
|
15
|
+
* 2. 'api' — Custom REST API endpoints (read/write URLs)
|
|
16
|
+
* 3. 'value' — Static data (pre-loaded items array)
|
|
17
|
+
*
|
|
18
|
+
* @module data-scope
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
import { collectSavedViews, composeElementDataSource, elementDataSourceViewNotFoundMessage, resolveSavedView, } from './element-data-source.js';
|
|
8
22
|
/** Extract records array from various common API response shapes */
|
|
9
23
|
function extractRecords(data) {
|
|
10
24
|
if (Array.isArray(data))
|
|
@@ -142,12 +156,76 @@ export class ViewDataProvider {
|
|
|
142
156
|
};
|
|
143
157
|
}
|
|
144
158
|
}
|
|
145
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Resolve an element-level data source (`PageComponentSchema.dataSource`).
|
|
161
|
+
*
|
|
162
|
+
* `view` is HONOURED here, not dropped. Until objectstack#5576 this method
|
|
163
|
+
* forwarded `filter`/`sort`/`limit` and silently discarded `view`, so a page
|
|
164
|
+
* component that named a saved view got every record the object had — the
|
|
165
|
+
* declared binding was inert while looking applied.
|
|
166
|
+
*
|
|
167
|
+
* Three outcomes, all explicit:
|
|
168
|
+
* - no `view` → unchanged behaviour, the binding's own keys are forwarded;
|
|
169
|
+
* - `view` resolved → its columns become the `fields` projection and its
|
|
170
|
+
* filter/sort/limit the baseline the binding's keys compose with (see
|
|
171
|
+
* {@link composeElementDataSource} for the per-key rule);
|
|
172
|
+
* - `view` unresolvable, or the fetcher cannot list views at all → an
|
|
173
|
+
* `error` naming the view and what the object does have. NOT an empty
|
|
174
|
+
* result, and NOT a silent fall back to the unfiltered object.
|
|
175
|
+
*/
|
|
146
176
|
async resolveElementDataSource(config) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
177
|
+
let view;
|
|
178
|
+
if (config.view) {
|
|
179
|
+
if (!this.fetcher?.fetchViews) {
|
|
180
|
+
return {
|
|
181
|
+
records: [],
|
|
182
|
+
total: 0,
|
|
183
|
+
provider: 'object',
|
|
184
|
+
loading: false,
|
|
185
|
+
error: `dataSource.view "${config.view}" cannot be resolved: this data ` +
|
|
186
|
+
`fetcher does not implement fetchViews().`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
let views;
|
|
190
|
+
try {
|
|
191
|
+
views = collectSavedViews(await this.fetcher.fetchViews(config.object));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
records: [],
|
|
196
|
+
total: 0,
|
|
197
|
+
provider: 'object',
|
|
198
|
+
loading: false,
|
|
199
|
+
error: error.message,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
view = resolveSavedView(views, config.view, config.object);
|
|
203
|
+
if (!view) {
|
|
204
|
+
return {
|
|
205
|
+
records: [],
|
|
206
|
+
total: 0,
|
|
207
|
+
provider: 'object',
|
|
208
|
+
loading: false,
|
|
209
|
+
error: elementDataSourceViewNotFoundMessage(config.object, config.view, Object.keys(views)),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const composed = composeElementDataSource(config, view);
|
|
214
|
+
const fields = Array.isArray(composed.columns)
|
|
215
|
+
? composed.columns.filter((c) => typeof c === 'string' && !!c)
|
|
216
|
+
: undefined;
|
|
217
|
+
return this.resolve({ provider: 'object', object: composed.object }, {
|
|
218
|
+
// `DataFetcher.fetchRecords`'s `filter` is declared
|
|
219
|
+
// `Record<string, any>`, which predates the three filter shapes that
|
|
220
|
+
// legitimately circulate here: a MongoDB-style condition object, an
|
|
221
|
+
// ObjectQL AST node array, and a spec `ViewFilterRule[]`. The last two
|
|
222
|
+
// are arrays, so a saved view's filter does not fit the declared option
|
|
223
|
+
// — the cast is that gap, not a coercion. Widening the declared option
|
|
224
|
+
// is a separate change with its own consumers.
|
|
225
|
+
filter: composed.filter,
|
|
226
|
+
sort: composed.sort,
|
|
227
|
+
limit: composed.limit,
|
|
228
|
+
...(fields && fields.length > 0 ? { fields } : {}),
|
|
151
229
|
});
|
|
152
230
|
}
|
|
153
231
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
/** Sort entry, as both the spec's `SortItem` and objectui's view configs spell it. */
|
|
9
|
+
export interface ElementDataSourceSort {
|
|
10
|
+
field: string;
|
|
11
|
+
order: 'asc' | 'desc';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Element-level data source — matches `@objectstack/spec` `ElementDataSourceSchema`.
|
|
15
|
+
*
|
|
16
|
+
* `filter` is typed `unknown` rather than the spec's `FilterCondition` because
|
|
17
|
+
* three shapes legitimately reach a renderer here (a MongoDB-style condition
|
|
18
|
+
* object, an ObjectQL AST node array, a spec `ViewFilterRule[]`) and
|
|
19
|
+
* {@link mergeFilterNodes} is the single sink that lowers all three. Narrowing
|
|
20
|
+
* the type here would only move the cast, not remove it.
|
|
21
|
+
*/
|
|
22
|
+
export interface ElementDataSourceConfig {
|
|
23
|
+
object: string;
|
|
24
|
+
view?: string;
|
|
25
|
+
filter?: unknown;
|
|
26
|
+
sort?: ElementDataSourceSort[];
|
|
27
|
+
limit?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The subset of a saved view's config that an `ElementDataSource` `view` name
|
|
31
|
+
* contributes. Deliberately loose: stored view metadata is user data and carries
|
|
32
|
+
* both the spec vocabulary and the legacy objectui spellings that
|
|
33
|
+
* `normalizeListViewSchema` folds at the component boundary.
|
|
34
|
+
*/
|
|
35
|
+
export type ElementSavedView = Record<string, unknown>;
|
|
36
|
+
/** What {@link composeElementDataSource} produces for a renderer to consume. */
|
|
37
|
+
export interface ComposedElementDataSource {
|
|
38
|
+
/** Object to query. */
|
|
39
|
+
object: string;
|
|
40
|
+
/** Columns the named view shows; absent when no view was named. */
|
|
41
|
+
columns?: unknown;
|
|
42
|
+
/** View filter AND binding filter, combined; `undefined` when neither. */
|
|
43
|
+
filter?: unknown;
|
|
44
|
+
/** Binding sort if given, else the view's. */
|
|
45
|
+
sort?: unknown;
|
|
46
|
+
/** Binding limit if given, else the view's page size. */
|
|
47
|
+
limit?: number;
|
|
48
|
+
/** The view's render kind (grid / kanban / …), when the view declares one. */
|
|
49
|
+
viewType?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Is this value the spec's `dataSource` METADATA, rather than a runtime
|
|
53
|
+
* data-source adapter?
|
|
54
|
+
*
|
|
55
|
+
* This guard exists because the two collide by name and the collision was live:
|
|
56
|
+
* `SchemaRenderer` spreads every non-metadata schema key onto the component as a
|
|
57
|
+
* React prop, so a spec-compliant `dataSource: { object, view }` on a page
|
|
58
|
+
* component landed on `ListView`'s `dataSource` PROP — which is the injected
|
|
59
|
+
* adapter — and shadowed it. The first `dataSource.find(…)` then threw
|
|
60
|
+
* `dataSource.find is not a function`, which the list reported as
|
|
61
|
+
* "Couldn't load records": writing the binding the spec documents BROKE the
|
|
62
|
+
* component (objectstack#5576).
|
|
63
|
+
*
|
|
64
|
+
* The test is positive on the metadata rather than negative on the adapter: a
|
|
65
|
+
* binding is a plain object with a non-empty string `object` and no `find`
|
|
66
|
+
* method. An adapter never has a string `object`; metadata never has `find`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isElementDataSourceConfig(value: unknown): value is ElementDataSourceConfig;
|
|
69
|
+
/**
|
|
70
|
+
* Flatten every place saved views for one object can live into `id → config`.
|
|
71
|
+
*
|
|
72
|
+
* Two sources are real and both are read, in the order app-shell's `ObjectView`
|
|
73
|
+
* reads them: the object definition's embedded `listViews` / `list_views` map
|
|
74
|
+
* (metadata-defined) and the metadata overlay rows an adapter returns from
|
|
75
|
+
* `listViews()` (user-defined). Later sources win, so a saved view shadows a
|
|
76
|
+
* metadata view of the same id — the same precedence `ObjectView` applies when
|
|
77
|
+
* it dedups its tab list.
|
|
78
|
+
*
|
|
79
|
+
* Accepts a record keyed by view id or an array of view configs (overlay rows
|
|
80
|
+
* are an array and carry their identity in `name`, falling back to `id`).
|
|
81
|
+
*/
|
|
82
|
+
export declare function collectSavedViews(...sources: unknown[]): Record<string, ElementSavedView>;
|
|
83
|
+
/**
|
|
84
|
+
* Find the saved view a `dataSource.view` name refers to.
|
|
85
|
+
*
|
|
86
|
+
* Name matching is {@link resolveViewId}'s — the same short/qualified tolerance
|
|
87
|
+
* the object page applies to a URL-requested view — so a name that works in one
|
|
88
|
+
* surface works in the other. Returns `undefined` when nothing matches; the
|
|
89
|
+
* caller must REPORT that rather than fall back to the object's default view.
|
|
90
|
+
* Silently widening a named view to "all records" is the failure mode this whole
|
|
91
|
+
* binding exists to remove.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveSavedView(views: Record<string, ElementSavedView>, requested: string | undefined | null, object: string): ElementSavedView | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Compose an `ElementDataSource` binding with the saved view it names.
|
|
96
|
+
*
|
|
97
|
+
* Pure — the caller resolves the view (see {@link resolveSavedView}) and owns
|
|
98
|
+
* the "named a view that does not exist" decision. Passing `undefined` /`null`
|
|
99
|
+
* for `view` composes a binding with no view, which is exactly the pre-#5576
|
|
100
|
+
* behaviour for the other four keys.
|
|
101
|
+
*
|
|
102
|
+
* See the module doc for the per-key rule table; `filter` is the only key that
|
|
103
|
+
* combines rather than overrides, because the spec describes the binding's
|
|
104
|
+
* filter as *additional*.
|
|
105
|
+
*/
|
|
106
|
+
export declare function composeElementDataSource(config: ElementDataSourceConfig, view?: ElementSavedView | null): ComposedElementDataSource;
|
|
107
|
+
/**
|
|
108
|
+
* The message shown when `dataSource.view` names a view the object does not
|
|
109
|
+
* have. Built here so the render path and `ViewDataProvider` report the same
|
|
110
|
+
* defect the same way, and so the known-view list (the thing that actually gets
|
|
111
|
+
* an author unstuck) is never omitted by one caller.
|
|
112
|
+
*/
|
|
113
|
+
export declare function elementDataSourceViewNotFoundMessage(object: string, view: string, knownViews: readonly string[]): string;
|