@object-ui/core 17.6.0 → 17.7.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 +6610 -0
- package/README.md +101 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +288 -27
- package/dist/actions/ActionRunner.js +443 -63
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +57 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +203 -31
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- package/dist/evaluator/subjectPermissions.d.ts +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +80 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +358 -31
- package/dist/registry/Registry.js +263 -11
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +131 -5
- package/dist/utils/chart-series.js +98 -16
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +389 -46
- package/dist/utils/date-display.d.ts +354 -0
- package/dist/utils/date-display.js +463 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +76 -6
- package/dist/utils/expand-fields.js +76 -6
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +121 -1
- package/dist/utils/normalize-list-view.js +186 -6
- package/dist/utils/number-display.d.ts +63 -14
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +45 -12
- package/dist/utils/predicate-fields.js +137 -12
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/sort-query.d.ts +115 -15
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +200 -82
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI — served per-column sortability projection (consumer side)
|
|
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 consumer half of objectstack#10235 (maintainer ruling A, 2026-08-23):
|
|
10
|
+
* the platform serves an explicit per-column sortability signal, and a grid
|
|
11
|
+
* reads THAT to decide whether a header offers a sort click — it never
|
|
12
|
+
* re-derives "virtual ⇒ unsortable" from the field's `type`.
|
|
13
|
+
*
|
|
14
|
+
* ## What the platform serves
|
|
15
|
+
*
|
|
16
|
+
* `GET /api/v1/meta/object/:name` answers `GetMetaItemResponseSchema`, whose
|
|
17
|
+
* ENVELOPE (never the document under `item` — `FieldSchema` is a
|
|
18
|
+
* `strictObject`, so the key must stay un-authorable) carries an optional
|
|
19
|
+
* `sortability` key when the served type is `object`, on every serving branch
|
|
20
|
+
* including the cached one:
|
|
21
|
+
*
|
|
22
|
+
* ```jsonc
|
|
23
|
+
* { "type": "object", "name": "crm_opportunity", "item": { … },
|
|
24
|
+
* "sortability": { "fields": {
|
|
25
|
+
* "amount": { "sortable": true },
|
|
26
|
+
* "expected_revenue": { "sortable": false, "reason": "virtual-type" },
|
|
27
|
+
* "remote_anchor": { "sortable": true, "caveat": "unprovisioned-anchor" }
|
|
28
|
+
* } } }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Shape verified against the MERGED upstream change, not against a description
|
|
32
|
+
* of it: `@objectstack/spec`'s `ObjectSortabilitySchema` /
|
|
33
|
+
* `FieldSortabilitySchema` and `GetMetaItemResponseSchema.sortability`.
|
|
34
|
+
*
|
|
35
|
+
* ## The contract, and the asymmetry that makes it easy to get backwards
|
|
36
|
+
*
|
|
37
|
+
* Offer a sort affordance on a column **iff the projection has an entry for
|
|
38
|
+
* the column's field name AND that entry says `sortable: true`**.
|
|
39
|
+
*
|
|
40
|
+
* ABSENCE OF AN ENTRY MEANS "no platform sort behind this name" — never
|
|
41
|
+
* "assume sortable". The projection's domain is exactly the served field map
|
|
42
|
+
* plus the always-provisioned `id`, so an unknown name, a dotted path
|
|
43
|
+
* (`account.name`) and an unprovisioned audit column all arrive as absence,
|
|
44
|
+
* and all three are refused by the runtime doors. A `!== false` test — the
|
|
45
|
+
* spelling every other optional flag in this repo uses — answers `true` for
|
|
46
|
+
* absence and so gets exactly that family backwards. {@link isPlatformSortableField}
|
|
47
|
+
* is the only spelling of this test; do not inline a comparison at a call site.
|
|
48
|
+
*
|
|
49
|
+
* ## Present-vs-absent PROJECTION is a different question
|
|
50
|
+
*
|
|
51
|
+
* `readObjectSortability` returns `undefined` when the served metadata carried
|
|
52
|
+
* no `sortability` key at all — a backend older than the upstream change, an
|
|
53
|
+
* inline/mock data source, a fixture. That is NOT "every column is unsortable":
|
|
54
|
+
* it is "no signal was served", and the caller decides what it did before the
|
|
55
|
+
* signal existed. The two are deliberately different types (`undefined` vs an
|
|
56
|
+
* empty `fields` map) so a caller cannot conflate them by accident.
|
|
57
|
+
*
|
|
58
|
+
* ## `caveat: 'unprovisioned-anchor'` is NOT a refusal
|
|
59
|
+
*
|
|
60
|
+
* Platform-injected anchors on ADR-0015 `external` objects come back
|
|
61
|
+
* `sortable: true, caveat: 'unprovisioned-anchor'`: the runtime ACCEPTS the
|
|
62
|
+
* sort, but silently drops it (`asc` === `desc` under a `200`) when the remote
|
|
63
|
+
* table carries no such column — a degradation the platform cannot prove
|
|
64
|
+
* either way at serve time. This module treats the caveat as advisory and the
|
|
65
|
+
* `sortable` verdict as the enforcement fact, so a caveated column keeps its
|
|
66
|
+
* click. Refusing what the platform does not refuse would recreate
|
|
67
|
+
* declared-≠-enforced drift in mirror image; whether to badge or withhold on
|
|
68
|
+
* the caveat is an open affordance question, not something to settle here.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* The served shape is the SPEC's, re-exported rather than restated.
|
|
72
|
+
*
|
|
73
|
+
* `FIELD_UNSORTABLE_VIRTUAL_TYPE` / `FIELD_SORTABLE_UNPROVISIONED_ANCHOR` are
|
|
74
|
+
* the two string literals the projection can carry, and `FieldSortability` /
|
|
75
|
+
* `ObjectSortability` are the wire types themselves (`z.input` of the
|
|
76
|
+
* schemas the REST layer answers with). Re-exporting is the point of a
|
|
77
|
+
* contract-first consumer: a local declaration under a spec export's name is
|
|
78
|
+
* read by the next agent as the spec's own definition, and then drifts from
|
|
79
|
+
* it — which is what `check:spec-symbols` guards, and what it caught here.
|
|
80
|
+
*/
|
|
81
|
+
export { FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR, } from '@objectstack/spec/api';
|
|
82
|
+
export type { FieldSortability, ObjectSortability } from '@objectstack/spec/api';
|
|
83
|
+
import type { ObjectSortability } from '@objectstack/spec/api';
|
|
84
|
+
/**
|
|
85
|
+
* Where the projection rides on the client-side object schema.
|
|
86
|
+
*
|
|
87
|
+
* A GLOBAL-REGISTRY SYMBOL, deliberately — the same carrier shape
|
|
88
|
+
* `INFLIGHT_GET_REGISTRY_KEY` uses in `@object-ui/types`. Three properties
|
|
89
|
+
* this signal needs and a string key would not give it:
|
|
90
|
+
*
|
|
91
|
+
* - `JSON.stringify` drops symbol-keyed properties, so a schema object that
|
|
92
|
+
* is ever round-tripped back at a metadata WRITE endpoint cannot carry the
|
|
93
|
+
* projection into a document the server parses `strict` and rejects by
|
|
94
|
+
* name. The upstream change kept the key off `item` for exactly that
|
|
95
|
+
* reason; stamping a string key here would undo it one repo away.
|
|
96
|
+
* - `Object.keys` / spread / `for…in` do not see it, so every existing reader
|
|
97
|
+
* that enumerates the schema (column generators, diffing, form builders)
|
|
98
|
+
* is unchanged by its presence.
|
|
99
|
+
* - `Symbol.for` is the cross-realm registry, so two copies of this module
|
|
100
|
+
* (a workspace build and a published one in the same graph) agree on the
|
|
101
|
+
* key rather than silently reading past each other.
|
|
102
|
+
*/
|
|
103
|
+
export declare const OBJECT_SORTABILITY_KEY: unique symbol;
|
|
104
|
+
/**
|
|
105
|
+
* Normalize a served `sortability` envelope value into the projection, or
|
|
106
|
+
* `undefined` when the envelope carried nothing usable.
|
|
107
|
+
*
|
|
108
|
+
* Tolerant on purpose (the same contract the upstream resolver carries): the
|
|
109
|
+
* adapter hands this whatever came back over the wire, unparsed.
|
|
110
|
+
*/
|
|
111
|
+
export declare function normalizeObjectSortability(served: unknown): ObjectSortability | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Stamp the served projection onto the client-side object schema, under
|
|
114
|
+
* {@link OBJECT_SORTABILITY_KEY}.
|
|
115
|
+
*
|
|
116
|
+
* Non-enumerable and non-writable-by-accident: the property is defined rather
|
|
117
|
+
* than assigned so it never shows up in a spread of the schema and cannot be
|
|
118
|
+
* clobbered by one. Returns the same object it was handed (the adapter caches
|
|
119
|
+
* that instance), and is a no-op when there is nothing usable to attach — so
|
|
120
|
+
* an older backend leaves the schema exactly as it found it rather than
|
|
121
|
+
* stamping an empty projection that would read as "nothing is sortable".
|
|
122
|
+
*/
|
|
123
|
+
export declare function attachObjectSortability<T>(schema: T, served: unknown): T;
|
|
124
|
+
/**
|
|
125
|
+
* Read the served projection off a client-side object schema.
|
|
126
|
+
*
|
|
127
|
+
* `undefined` means NO SIGNAL WAS SERVED — not "nothing is sortable". See the
|
|
128
|
+
* module docblock: callers must branch on this before applying the contract.
|
|
129
|
+
*/
|
|
130
|
+
export declare function readObjectSortability(schema: unknown): ObjectSortability | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* THE contract test: does the platform offer a sort over this field name?
|
|
133
|
+
*
|
|
134
|
+
* `true` iff the projection has an entry for `fieldName` and that entry says
|
|
135
|
+
* `sortable: true`. Every other case — no entry, `sortable: false`, a
|
|
136
|
+
* malformed entry — answers `false`, because absence means "no platform sort
|
|
137
|
+
* behind this name".
|
|
138
|
+
*
|
|
139
|
+
* `sortability` is REQUIRED and non-optional on purpose: a caller holding
|
|
140
|
+
* `undefined` (no signal served) has a different question to answer and must
|
|
141
|
+
* not reach this function with it. See {@link readObjectSortability}.
|
|
142
|
+
*/
|
|
143
|
+
export declare function isPlatformSortableField(sortability: ObjectSortability, fieldName: string | undefined): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Drop from a sort list every entry the platform will not order by.
|
|
146
|
+
*
|
|
147
|
+
* The RESTORE leg of the same contract: withholding the header click stops a
|
|
148
|
+
* fresh unsortable sort from being created, but a sort persisted BEFORE the
|
|
149
|
+
* signal existed is already in stored view state, and replaying it would keep
|
|
150
|
+
* a refused `$orderby` alive and re-persist it on the next unrelated edit.
|
|
151
|
+
* Filtering the list at the seam the grid both renders from and emits through
|
|
152
|
+
* means an already-stored entry is inert and is dropped the first time
|
|
153
|
+
* anything writes the sort back.
|
|
154
|
+
*/
|
|
155
|
+
export declare function filterPlatformSortableSort<T extends {
|
|
156
|
+
field?: string;
|
|
157
|
+
}>(sort: readonly T[] | undefined, sortability: ObjectSortability): T[];
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI — served per-column sortability projection (consumer side)
|
|
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 consumer half of objectstack#10235 (maintainer ruling A, 2026-08-23):
|
|
10
|
+
* the platform serves an explicit per-column sortability signal, and a grid
|
|
11
|
+
* reads THAT to decide whether a header offers a sort click — it never
|
|
12
|
+
* re-derives "virtual ⇒ unsortable" from the field's `type`.
|
|
13
|
+
*
|
|
14
|
+
* ## What the platform serves
|
|
15
|
+
*
|
|
16
|
+
* `GET /api/v1/meta/object/:name` answers `GetMetaItemResponseSchema`, whose
|
|
17
|
+
* ENVELOPE (never the document under `item` — `FieldSchema` is a
|
|
18
|
+
* `strictObject`, so the key must stay un-authorable) carries an optional
|
|
19
|
+
* `sortability` key when the served type is `object`, on every serving branch
|
|
20
|
+
* including the cached one:
|
|
21
|
+
*
|
|
22
|
+
* ```jsonc
|
|
23
|
+
* { "type": "object", "name": "crm_opportunity", "item": { … },
|
|
24
|
+
* "sortability": { "fields": {
|
|
25
|
+
* "amount": { "sortable": true },
|
|
26
|
+
* "expected_revenue": { "sortable": false, "reason": "virtual-type" },
|
|
27
|
+
* "remote_anchor": { "sortable": true, "caveat": "unprovisioned-anchor" }
|
|
28
|
+
* } } }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Shape verified against the MERGED upstream change, not against a description
|
|
32
|
+
* of it: `@objectstack/spec`'s `ObjectSortabilitySchema` /
|
|
33
|
+
* `FieldSortabilitySchema` and `GetMetaItemResponseSchema.sortability`.
|
|
34
|
+
*
|
|
35
|
+
* ## The contract, and the asymmetry that makes it easy to get backwards
|
|
36
|
+
*
|
|
37
|
+
* Offer a sort affordance on a column **iff the projection has an entry for
|
|
38
|
+
* the column's field name AND that entry says `sortable: true`**.
|
|
39
|
+
*
|
|
40
|
+
* ABSENCE OF AN ENTRY MEANS "no platform sort behind this name" — never
|
|
41
|
+
* "assume sortable". The projection's domain is exactly the served field map
|
|
42
|
+
* plus the always-provisioned `id`, so an unknown name, a dotted path
|
|
43
|
+
* (`account.name`) and an unprovisioned audit column all arrive as absence,
|
|
44
|
+
* and all three are refused by the runtime doors. A `!== false` test — the
|
|
45
|
+
* spelling every other optional flag in this repo uses — answers `true` for
|
|
46
|
+
* absence and so gets exactly that family backwards. {@link isPlatformSortableField}
|
|
47
|
+
* is the only spelling of this test; do not inline a comparison at a call site.
|
|
48
|
+
*
|
|
49
|
+
* ## Present-vs-absent PROJECTION is a different question
|
|
50
|
+
*
|
|
51
|
+
* `readObjectSortability` returns `undefined` when the served metadata carried
|
|
52
|
+
* no `sortability` key at all — a backend older than the upstream change, an
|
|
53
|
+
* inline/mock data source, a fixture. That is NOT "every column is unsortable":
|
|
54
|
+
* it is "no signal was served", and the caller decides what it did before the
|
|
55
|
+
* signal existed. The two are deliberately different types (`undefined` vs an
|
|
56
|
+
* empty `fields` map) so a caller cannot conflate them by accident.
|
|
57
|
+
*
|
|
58
|
+
* ## `caveat: 'unprovisioned-anchor'` is NOT a refusal
|
|
59
|
+
*
|
|
60
|
+
* Platform-injected anchors on ADR-0015 `external` objects come back
|
|
61
|
+
* `sortable: true, caveat: 'unprovisioned-anchor'`: the runtime ACCEPTS the
|
|
62
|
+
* sort, but silently drops it (`asc` === `desc` under a `200`) when the remote
|
|
63
|
+
* table carries no such column — a degradation the platform cannot prove
|
|
64
|
+
* either way at serve time. This module treats the caveat as advisory and the
|
|
65
|
+
* `sortable` verdict as the enforcement fact, so a caveated column keeps its
|
|
66
|
+
* click. Refusing what the platform does not refuse would recreate
|
|
67
|
+
* declared-≠-enforced drift in mirror image; whether to badge or withhold on
|
|
68
|
+
* the caveat is an open affordance question, not something to settle here.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* The served shape is the SPEC's, re-exported rather than restated.
|
|
72
|
+
*
|
|
73
|
+
* `FIELD_UNSORTABLE_VIRTUAL_TYPE` / `FIELD_SORTABLE_UNPROVISIONED_ANCHOR` are
|
|
74
|
+
* the two string literals the projection can carry, and `FieldSortability` /
|
|
75
|
+
* `ObjectSortability` are the wire types themselves (`z.input` of the
|
|
76
|
+
* schemas the REST layer answers with). Re-exporting is the point of a
|
|
77
|
+
* contract-first consumer: a local declaration under a spec export's name is
|
|
78
|
+
* read by the next agent as the spec's own definition, and then drifts from
|
|
79
|
+
* it — which is what `check:spec-symbols` guards, and what it caught here.
|
|
80
|
+
*/
|
|
81
|
+
export { FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR, } from '@objectstack/spec/api';
|
|
82
|
+
/**
|
|
83
|
+
* Where the projection rides on the client-side object schema.
|
|
84
|
+
*
|
|
85
|
+
* A GLOBAL-REGISTRY SYMBOL, deliberately — the same carrier shape
|
|
86
|
+
* `INFLIGHT_GET_REGISTRY_KEY` uses in `@object-ui/types`. Three properties
|
|
87
|
+
* this signal needs and a string key would not give it:
|
|
88
|
+
*
|
|
89
|
+
* - `JSON.stringify` drops symbol-keyed properties, so a schema object that
|
|
90
|
+
* is ever round-tripped back at a metadata WRITE endpoint cannot carry the
|
|
91
|
+
* projection into a document the server parses `strict` and rejects by
|
|
92
|
+
* name. The upstream change kept the key off `item` for exactly that
|
|
93
|
+
* reason; stamping a string key here would undo it one repo away.
|
|
94
|
+
* - `Object.keys` / spread / `for…in` do not see it, so every existing reader
|
|
95
|
+
* that enumerates the schema (column generators, diffing, form builders)
|
|
96
|
+
* is unchanged by its presence.
|
|
97
|
+
* - `Symbol.for` is the cross-realm registry, so two copies of this module
|
|
98
|
+
* (a workspace build and a published one in the same graph) agree on the
|
|
99
|
+
* key rather than silently reading past each other.
|
|
100
|
+
*/
|
|
101
|
+
export const OBJECT_SORTABILITY_KEY = Symbol.for('objectui.objectSortability');
|
|
102
|
+
/** Narrow one served entry, tolerating an unparsed / hand-built record. */
|
|
103
|
+
function asFieldSortability(value) {
|
|
104
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
105
|
+
return undefined;
|
|
106
|
+
const sortable = value.sortable;
|
|
107
|
+
if (typeof sortable !== 'boolean')
|
|
108
|
+
return undefined;
|
|
109
|
+
const reason = value.reason;
|
|
110
|
+
const caveat = value.caveat;
|
|
111
|
+
// `reason` / `caveat` are typed as closed literal unions by the spec, and
|
|
112
|
+
// this is unparsed input off the wire. Preserve whatever string arrived
|
|
113
|
+
// rather than dropping an annotation a NEWER platform added — the verdict
|
|
114
|
+
// this module acts on is `sortable`, and an unrecognised advisory string is
|
|
115
|
+
// strictly better carried than silently erased. The assertion is the
|
|
116
|
+
// boundary cast that admission implies, and it is confined to this function.
|
|
117
|
+
return {
|
|
118
|
+
sortable,
|
|
119
|
+
...(typeof reason === 'string' ? { reason } : {}),
|
|
120
|
+
...(typeof caveat === 'string' ? { caveat } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Normalize a served `sortability` envelope value into the projection, or
|
|
125
|
+
* `undefined` when the envelope carried nothing usable.
|
|
126
|
+
*
|
|
127
|
+
* Tolerant on purpose (the same contract the upstream resolver carries): the
|
|
128
|
+
* adapter hands this whatever came back over the wire, unparsed.
|
|
129
|
+
*/
|
|
130
|
+
export function normalizeObjectSortability(served) {
|
|
131
|
+
if (!served || typeof served !== 'object' || Array.isArray(served))
|
|
132
|
+
return undefined;
|
|
133
|
+
const rawFields = served.fields;
|
|
134
|
+
if (!rawFields || typeof rawFields !== 'object' || Array.isArray(rawFields))
|
|
135
|
+
return undefined;
|
|
136
|
+
const fields = {};
|
|
137
|
+
for (const [name, entry] of Object.entries(rawFields)) {
|
|
138
|
+
const verdict = asFieldSortability(entry);
|
|
139
|
+
if (verdict)
|
|
140
|
+
fields[name] = verdict;
|
|
141
|
+
}
|
|
142
|
+
return { fields };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Stamp the served projection onto the client-side object schema, under
|
|
146
|
+
* {@link OBJECT_SORTABILITY_KEY}.
|
|
147
|
+
*
|
|
148
|
+
* Non-enumerable and non-writable-by-accident: the property is defined rather
|
|
149
|
+
* than assigned so it never shows up in a spread of the schema and cannot be
|
|
150
|
+
* clobbered by one. Returns the same object it was handed (the adapter caches
|
|
151
|
+
* that instance), and is a no-op when there is nothing usable to attach — so
|
|
152
|
+
* an older backend leaves the schema exactly as it found it rather than
|
|
153
|
+
* stamping an empty projection that would read as "nothing is sortable".
|
|
154
|
+
*/
|
|
155
|
+
export function attachObjectSortability(schema, served) {
|
|
156
|
+
const projection = normalizeObjectSortability(served);
|
|
157
|
+
if (!projection || !schema || typeof schema !== 'object')
|
|
158
|
+
return schema;
|
|
159
|
+
Object.defineProperty(schema, OBJECT_SORTABILITY_KEY, {
|
|
160
|
+
value: projection,
|
|
161
|
+
enumerable: false,
|
|
162
|
+
configurable: true,
|
|
163
|
+
writable: true,
|
|
164
|
+
});
|
|
165
|
+
return schema;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Read the served projection off a client-side object schema.
|
|
169
|
+
*
|
|
170
|
+
* `undefined` means NO SIGNAL WAS SERVED — not "nothing is sortable". See the
|
|
171
|
+
* module docblock: callers must branch on this before applying the contract.
|
|
172
|
+
*/
|
|
173
|
+
export function readObjectSortability(schema) {
|
|
174
|
+
if (!schema || typeof schema !== 'object')
|
|
175
|
+
return undefined;
|
|
176
|
+
const carried = schema[OBJECT_SORTABILITY_KEY];
|
|
177
|
+
if (!carried || typeof carried !== 'object')
|
|
178
|
+
return undefined;
|
|
179
|
+
const fields = carried.fields;
|
|
180
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields))
|
|
181
|
+
return undefined;
|
|
182
|
+
return carried;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* THE contract test: does the platform offer a sort over this field name?
|
|
186
|
+
*
|
|
187
|
+
* `true` iff the projection has an entry for `fieldName` and that entry says
|
|
188
|
+
* `sortable: true`. Every other case — no entry, `sortable: false`, a
|
|
189
|
+
* malformed entry — answers `false`, because absence means "no platform sort
|
|
190
|
+
* behind this name".
|
|
191
|
+
*
|
|
192
|
+
* `sortability` is REQUIRED and non-optional on purpose: a caller holding
|
|
193
|
+
* `undefined` (no signal served) has a different question to answer and must
|
|
194
|
+
* not reach this function with it. See {@link readObjectSortability}.
|
|
195
|
+
*/
|
|
196
|
+
export function isPlatformSortableField(sortability, fieldName) {
|
|
197
|
+
if (typeof fieldName !== 'string' || fieldName.length === 0)
|
|
198
|
+
return false;
|
|
199
|
+
const entry = sortability.fields?.[fieldName];
|
|
200
|
+
return asFieldSortability(entry)?.sortable === true;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Drop from a sort list every entry the platform will not order by.
|
|
204
|
+
*
|
|
205
|
+
* The RESTORE leg of the same contract: withholding the header click stops a
|
|
206
|
+
* fresh unsortable sort from being created, but a sort persisted BEFORE the
|
|
207
|
+
* signal existed is already in stored view state, and replaying it would keep
|
|
208
|
+
* a refused `$orderby` alive and re-persist it on the next unrelated edit.
|
|
209
|
+
* Filtering the list at the seam the grid both renders from and emits through
|
|
210
|
+
* means an already-stored entry is inert and is dropped the first time
|
|
211
|
+
* anything writes the sort back.
|
|
212
|
+
*/
|
|
213
|
+
export function filterPlatformSortableSort(sort, sortability) {
|
|
214
|
+
if (!Array.isArray(sort))
|
|
215
|
+
return [];
|
|
216
|
+
return sort.filter((item) => isPlatformSortableField(sortability, item?.field));
|
|
217
|
+
}
|
|
@@ -27,6 +27,31 @@ export interface DashboardFilterDef {
|
|
|
27
27
|
name: string;
|
|
28
28
|
/** Default target field when a widget declares no explicit binding. */
|
|
29
29
|
field: string;
|
|
30
|
+
/**
|
|
31
|
+
* The object `field` lives on, carried through from
|
|
32
|
+
* @objectstack/spec's `GlobalFilterSchema.object` (`061f5e829`).
|
|
33
|
+
*
|
|
34
|
+
* The spec's own describe text for that key states the contract: "Object
|
|
35
|
+
* whose `fields.<object>.<field>` translation-bundle entry resolves this
|
|
36
|
+
* filter's field label and option labels". It was shipped on the spec side
|
|
37
|
+
* and never reached a consumer here, because this builder names the keys it
|
|
38
|
+
* copies and this one was not among them — so the value could not reach a
|
|
39
|
+
* renderer even in principle.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ NOT `optionsFrom.object`, which the spec separates deliberately: that
|
|
42
|
+
* one names the object dynamic OPTIONS are fetched from and may differ
|
|
43
|
+
* (filtering `opportunity` by `owner` with options sourced from `user`),
|
|
44
|
+
* while this one names the object whose translation bundle is keyed by
|
|
45
|
+
* `field`. Reading the former for label resolution resolves against the
|
|
46
|
+
* wrong object for exactly that filter.
|
|
47
|
+
*
|
|
48
|
+
* Like {@link DashboardFilterDef.label}, this module carries it through
|
|
49
|
+
* UNRESOLVED — `@object-ui/core` is locale-free by design and the bundle
|
|
50
|
+
* read belongs to the render side. Omitted entirely when the author declared
|
|
51
|
+
* none, so a dashboard that never names an object resolves to a def with the
|
|
52
|
+
* same key set it always had.
|
|
53
|
+
*/
|
|
54
|
+
object?: string;
|
|
30
55
|
/**
|
|
31
56
|
* Display label, in @objectstack/spec's `I18nLabel` vocabulary — a plain
|
|
32
57
|
* string, or an inline per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`).
|
|
@@ -49,10 +74,11 @@ export interface DashboardFilterDef {
|
|
|
49
74
|
* `resolveDashboardFilterDefs` — consumers always see the object form.
|
|
50
75
|
*
|
|
51
76
|
* The canonical authoring form is @objectstack/spec's `{ value, label }`
|
|
52
|
-
* pair, and it is the ONLY one the platform accepts at publish.
|
|
53
|
-
* shorthand
|
|
54
|
-
*
|
|
55
|
-
*
|
|
77
|
+
* pair, and it is the ONLY one the platform accepts at publish. The
|
|
78
|
+
* bare-string shorthand a STORED document may still carry is NOT lifted
|
|
79
|
+
* (objectui#4356, retired on the objectstack#7917 option-② schedule): such
|
|
80
|
+
* a member yields no option, and `normalizeFilterOptions` says so once. Do
|
|
81
|
+
* not author one; rewrite `"X"` as `{ "value": "X", "label": "X" }`.
|
|
56
82
|
*
|
|
57
83
|
* The PAIR SHAPE is normalized; the label's own vocabulary is not. `label`
|
|
58
84
|
* is `I18nLabel` in `GlobalFilterSchema.options[]` too, and it reaches the
|
|
@@ -99,12 +125,18 @@ export interface DateRangeValue {
|
|
|
99
125
|
* protect.
|
|
100
126
|
*
|
|
101
127
|
* What stays local is the BOUNDS table above, which is the other half of
|
|
102
|
-
* #4614's design ("the two vocabularies live one import apart and neither
|
|
128
|
+
* objectstack-ai/objectstack#4614's design ("the two vocabularies live one import apart and neither
|
|
103
129
|
* restates the other's grammar"): the spec owns which presets exist, this
|
|
104
130
|
* module owns what each one resolves to in date-macro tokens.
|
|
105
131
|
*/
|
|
106
132
|
export { DATE_RANGE_PRESETS };
|
|
107
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Reset the dropped-shorthand warn-once memo. Exported for tests.
|
|
135
|
+
*
|
|
136
|
+
* Kept through the objectui#4356 retirement: the memo it clears outlived the
|
|
137
|
+
* deprecation warning, because the warning that replaced it (a member was
|
|
138
|
+
* DROPPED, not lifted) dedupes on the same key for the same render-path reason.
|
|
139
|
+
*/
|
|
108
140
|
export declare function resetDashboardFilterWarnings(): void;
|
|
109
141
|
/**
|
|
110
142
|
* Normalize a dashboard schema's filter declarations into a flat list of
|