@object-ui/core 16.1.0 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1881 -4
- package/dist/actions/ActionEngine.d.ts +11 -1
- package/dist/actions/ActionEngine.js +14 -3
- package/dist/actions/ActionRunner.d.ts +199 -24
- package/dist/actions/ActionRunner.js +189 -84
- package/dist/actions/actionKeys.d.ts +135 -0
- package/dist/actions/actionKeys.js +283 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/adapters/ApiDataSource.js +10 -1
- package/dist/adapters/ValueDataSource.d.ts +2 -2
- package/dist/evaluator/fieldRules.d.ts +0 -2
- package/dist/evaluator/fieldRules.js +3 -2
- package/dist/evaluator/optionLint.js +5 -2
- package/dist/evaluator/optionRules.d.ts +13 -6
- package/dist/evaluator/optionRules.js +0 -23
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/query/query-ast.d.ts +6 -6
- package/dist/query/query-ast.js +2 -2
- package/dist/registry/PluginSystem.d.ts +15 -5
- package/dist/registry/PluginSystem.js +1 -1
- package/dist/registry/Registry.d.ts +81 -2
- package/dist/registry/Registry.js +102 -2
- package/dist/registry/WidgetRegistry.d.ts +7 -7
- package/dist/registry/WidgetRegistry.js +1 -1
- package/dist/registry/public-blocks.js +52 -1
- package/dist/styling/scoped-styles.d.ts +14 -8
- package/dist/theme/ThemeEngine.js +0 -12
- package/dist/utils/chart-series.d.ts +42 -2
- package/dist/utils/chart-series.js +55 -0
- package/dist/utils/column-identity.d.ts +110 -0
- package/dist/utils/column-identity.js +210 -0
- package/dist/utils/dashboard-filters.d.ts +11 -2
- package/dist/utils/dashboard-filters.js +99 -4
- package/dist/utils/dataset-format.d.ts +13 -1
- package/dist/utils/dataset-format.js +10 -2
- package/dist/utils/expand-fields.js +8 -8
- package/dist/utils/filter-converter.d.ts +47 -0
- package/dist/utils/filter-converter.js +94 -7
- package/dist/utils/filter-tokens.d.ts +108 -0
- package/dist/utils/filter-tokens.js +180 -0
- package/dist/utils/freeze-schema.d.ts +14 -6
- package/dist/utils/freeze-schema.js +14 -6
- package/dist/utils/managedBy.d.ts +97 -51
- package/dist/utils/managedBy.js +117 -53
- package/dist/utils/normalize-list-view.d.ts +109 -0
- package/dist/utils/normalize-list-view.js +255 -0
- package/dist/utils/sort-values.d.ts +52 -0
- package/dist/utils/sort-values.js +125 -0
- package/dist/validation/index.d.ts +10 -1
- package/dist/validation/index.js +10 -1
- package/dist/validation/required-presence.d.ts +31 -0
- package/dist/validation/required-presence.js +36 -0
- package/dist/validation/schema-validator.d.ts +24 -8
- package/dist/validation/validators/index.d.ts +7 -1
- package/dist/validation/validators/index.js +7 -1
- package/dist/validation/validators/object-validation-engine.d.ts +151 -31
- package/dist/validation/validators/object-validation-engine.js +275 -142
- package/package.json +4 -4
|
@@ -1,51 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* managedBy — the
|
|
3
|
-
*
|
|
2
|
+
* managedBy — the UI end of the platform's `managedBy` → CRUD affordance
|
|
3
|
+
* contract (ADR-0103).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* (`@objectstack/spec/data/object.zod.ts`, ADR-0103). Lives in `@object-ui/core`
|
|
7
|
-
* — React-free and reachable by every UI package (app-shell, plugin-detail,
|
|
8
|
-
* plugin-form, plugin-grid) — so the bucket logic is defined ONCE instead of
|
|
9
|
-
* hand-mirrored in each. Components ask "should I show New / Import / Edit /
|
|
10
|
-
* Delete / Export?" and get an answer that tracks the object's lifecycle without
|
|
11
|
-
* special-casing `sys_*` names.
|
|
5
|
+
* ## What is spec-owned here, and what is not (objectstack#4115)
|
|
12
6
|
*
|
|
13
|
-
* The bucket
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* The bucket logic is **the spec's**, and this module now says so in code
|
|
8
|
+
* rather than in a comment. `CrudAffordances` / `RowCrudPredicates` are
|
|
9
|
+
* re-exported from `@objectstack/spec/data`, and the bucket resolution itself
|
|
10
|
+
* delegates to the spec's `resolveCrudAffordances()`.
|
|
11
|
+
*
|
|
12
|
+
* This module used to describe itself as a "UI-side mirror" of that function
|
|
13
|
+
* and carry its own `DEFAULTS` table — a line-for-line copy of the spec's
|
|
14
|
+
* `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of its override parser. A faithful
|
|
15
|
+
* copy passes every value comparison and every behavioural test, so nothing
|
|
16
|
+
* would have caught the release in which the spec re-buckets an object class;
|
|
17
|
+
* reference identity is the only check that distinguishes a re-export from a
|
|
18
|
+
* fork (objectui#3003).
|
|
19
|
+
*
|
|
20
|
+
* What is genuinely objectui's and keeps a local name:
|
|
21
|
+
* {@link resolveEffectiveCrudAffordances} — the spec's object-level matrix
|
|
22
|
+
* INTERSECTED with the server-resolved effective API operation set (#3391), so
|
|
23
|
+
* the UI never offers a button the server would 405. The spec's function has
|
|
24
|
+
* no such notion; naming ours after it was the misdescription this burn-down
|
|
25
|
+
* removes.
|
|
26
|
+
*
|
|
27
|
+
* Lives in `@object-ui/core` — React-free and reachable by every UI package
|
|
28
|
+
* (app-shell, plugin-detail, plugin-form, plugin-grid) — so the intersection is
|
|
29
|
+
* defined ONCE instead of hand-mirrored in each. The bucket UNION type lives in
|
|
30
|
+
* `@object-ui/types` (`ManagedByBucket`) and is re-exported here for
|
|
31
|
+
* convenience. All translated copy (badge variants, empty-state messages) stays
|
|
32
|
+
* in `@object-ui/app-shell`.
|
|
17
33
|
*/
|
|
34
|
+
import { type CrudAffordances, type RowCrudPredicates } from '@objectstack/spec/data';
|
|
18
35
|
export type { ManagedByBucket } from '@object-ui/types';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Per-record CEL predicates for the built-in row Edit/Delete actions,
|
|
32
|
-
* present only when `userActions.edit` / `delete` used the object form
|
|
33
|
-
* (objectui#2614). Carried through as authored for row renderers to evaluate;
|
|
34
|
-
* they never affect the object-level booleans above.
|
|
35
|
-
*/
|
|
36
|
-
editPredicates?: RowCrudPredicates;
|
|
37
|
-
deletePredicates?: RowCrudPredicates;
|
|
38
|
-
}
|
|
39
|
-
/** Per-record predicates from the #2614 object form of a userActions flag. */
|
|
40
|
-
export interface RowCrudPredicates {
|
|
41
|
-
visibleWhen?: unknown;
|
|
42
|
-
disabledWhen?: unknown;
|
|
43
|
-
}
|
|
36
|
+
/**
|
|
37
|
+
* The resolved CRUD affordance matrix and its per-record predicate envelope
|
|
38
|
+
* are **spec-owned** (`@objectstack/spec/data`, ADR-0103) and re-exported here,
|
|
39
|
+
* not re-declared.
|
|
40
|
+
*
|
|
41
|
+
* Note this also TIGHTENS the predicate types: the local copies typed
|
|
42
|
+
* `visibleWhen` / `disabledWhen` as `unknown`, while the spec types them as
|
|
43
|
+
* `Expression | ExpressionInput` — the authored CEL shorthand or its envelope.
|
|
44
|
+
* The local `unknown` was imprecision, not a deliberate dialect.
|
|
45
|
+
*/
|
|
46
|
+
export type { CrudAffordances, RowCrudPredicates };
|
|
44
47
|
/** `edit`/`delete` accept a bare boolean or the #2614 object form. */
|
|
45
48
|
export type UserActionOverride = boolean | {
|
|
46
49
|
enabled?: boolean;
|
|
47
|
-
visibleWhen?:
|
|
48
|
-
disabledWhen?:
|
|
50
|
+
visibleWhen?: RowCrudPredicates['visibleWhen'];
|
|
51
|
+
disabledWhen?: RowCrudPredicates['disabledWhen'];
|
|
49
52
|
};
|
|
50
53
|
export interface UserActionsOverride {
|
|
51
54
|
create?: boolean;
|
|
@@ -63,11 +66,14 @@ export interface SchemaLike {
|
|
|
63
66
|
* the given bucket default, surfacing any per-record predicates alongside.
|
|
64
67
|
*
|
|
65
68
|
* THE single parser for the `userActions.{edit,delete}` override shape — every
|
|
66
|
-
* UI package (grid row affordances, related-list row predicates
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
* UI package (grid row affordances, related-list row predicates) routes through
|
|
70
|
+
* here instead of re-implementing the boolean/object-form parse locally. The
|
|
71
|
+
* spec keeps its equivalent (`normalizeRowCrudOverride`) module-private, so
|
|
72
|
+
* this stays a local declaration — under a name the spec does not own.
|
|
73
|
+
*
|
|
74
|
+
* `base` is the bucket default an omitted `enabled` falls back to (callers that
|
|
75
|
+
* only gate on an explicit opt-out pass `true`; predicate extraction is
|
|
76
|
+
* independent of `base`).
|
|
71
77
|
*/
|
|
72
78
|
export declare function normalizeUserAction(v: UserActionOverride | undefined | null, base: boolean): {
|
|
73
79
|
enabled: boolean;
|
|
@@ -81,14 +87,47 @@ export declare function normalizeUserAction(v: UserActionOverride | undefined |
|
|
|
81
87
|
* {@link normalizeUserAction} so the parse lives in exactly one place.
|
|
82
88
|
*/
|
|
83
89
|
export declare function userActionPredicates(v: UserActionOverride | undefined | null): RowCrudPredicates | undefined;
|
|
84
|
-
/**
|
|
85
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the CRUD affordances a UI surface should expose for an object —
|
|
92
|
+
* the spec's object-level matrix, INTERSECTED with what the server will
|
|
93
|
+
* actually accept.
|
|
94
|
+
*
|
|
95
|
+
* The bucket/`userActions` half is **not computed here**: it is delegated to
|
|
96
|
+
* `resolveCrudAffordances()` from `@objectstack/spec/data` (ADR-0103), so the
|
|
97
|
+
* bucket table has exactly one definition on the platform. Only the
|
|
98
|
+
* intersection below is objectui's, which is why this function no longer
|
|
99
|
+
* carries the spec's name (objectstack#4115).
|
|
100
|
+
*
|
|
101
|
+
* @param obj The object schema (managedBy bucket + userActions overrides).
|
|
102
|
+
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
103
|
+
* set for this object (from `/me/permissions` `apiOperations`, #3391). When
|
|
104
|
+
* provided, each affordance bit is ANDed with the corresponding API operation
|
|
105
|
+
* (create/import→create/import, edit→update, delete→delete, exportCsv→export),
|
|
106
|
+
* so the UI never shows a button the server would 405. Passing `undefined`
|
|
107
|
+
* (old backend / no effective set) leaves the affordances untouched —
|
|
108
|
+
* backward-compatible. An empty array means "expose nothing" → all bits off.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveEffectiveCrudAffordances(obj: SchemaLike | null | undefined, effectiveApiOperations?: readonly string[] | null): CrudAffordances;
|
|
86
111
|
/** True only when a `userActions` flag (bare boolean or #2614 object form) opts the write in. */
|
|
87
112
|
export declare function isWriteOptedIn(v: UserActionOverride | undefined | null): boolean;
|
|
88
113
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
114
|
+
* True for a platform-defined schema whose DATA is admin/user-writable — the
|
|
115
|
+
* `system-data` bucket. Used to pick the writable-system badge/empty-state copy
|
|
116
|
+
* instead of the engine-owned "read-only monitoring surface" copy.
|
|
117
|
+
*
|
|
118
|
+
* **Simplified in protocol 17 (objectstack#3355).** Under ADR-0103's v16 split
|
|
119
|
+
* this had to RECOVER the distinction from `userActions`: `system` doubled as
|
|
120
|
+
* both the engine-owned default and the writable set, so "did it open any
|
|
121
|
+
* write?" was the only way to tell a Notification Preferences grid from an
|
|
122
|
+
* automation-run log. v17 retired that overload — the writable half is now the
|
|
123
|
+
* `system-data` bucket (writable by DEFAULT) and the engine-owned half is
|
|
124
|
+
* `engine-owned` — so the bucket answers the question directly and the
|
|
125
|
+
* `userActions` probe would only re-derive what the value already states.
|
|
126
|
+
*
|
|
127
|
+
* The narrowing case is deliberately still `true`: a `system-data` grid that
|
|
128
|
+
* narrows to edit-only is still platform-schema/user-data and should read as
|
|
129
|
+
* such. The spec refuses `system-data` on an object that narrows away every
|
|
130
|
+
* write, so there is no "writable bucket with no writes" shape to worry about.
|
|
92
131
|
*/
|
|
93
132
|
export declare function isSystemWritable(obj: SchemaLike | null | undefined): boolean;
|
|
94
133
|
/**
|
|
@@ -97,5 +136,12 @@ export declare function isSystemWritable(obj: SchemaLike | null | undefined): bo
|
|
|
97
136
|
* `append-only` / `better-auth` objects resolve to `false` unless they opened
|
|
98
137
|
* `userActions.edit`. (Per-record predicates gate row action buttons, not
|
|
99
138
|
* object-level editability, so only the resolved boolean matters here.)
|
|
139
|
+
*
|
|
140
|
+
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
141
|
+
* set for this object (`/me/permissions` `apiOperations`, #3391/#3546). When
|
|
142
|
+
* provided, inline-edit is additionally ANDed with the server allowing
|
|
143
|
+
* `update`, so a record page never offers double-click/pencil editing the
|
|
144
|
+
* server would 405. `undefined` (old backend / unrestricted) leaves the
|
|
145
|
+
* bucket affordance untouched.
|
|
100
146
|
*/
|
|
101
|
-
export declare function isObjectInlineEditable(obj: SchemaLike | null | undefined): boolean;
|
|
147
|
+
export declare function isObjectInlineEditable(obj: SchemaLike | null | undefined, effectiveApiOperations?: readonly string[] | null): boolean;
|
package/dist/utils/managedBy.js
CHANGED
|
@@ -1,38 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* managedBy — the
|
|
3
|
-
*
|
|
2
|
+
* managedBy — the UI end of the platform's `managedBy` → CRUD affordance
|
|
3
|
+
* contract (ADR-0103).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* (`@objectstack/spec/data/object.zod.ts`, ADR-0103). Lives in `@object-ui/core`
|
|
7
|
-
* — React-free and reachable by every UI package (app-shell, plugin-detail,
|
|
8
|
-
* plugin-form, plugin-grid) — so the bucket logic is defined ONCE instead of
|
|
9
|
-
* hand-mirrored in each. Components ask "should I show New / Import / Edit /
|
|
10
|
-
* Delete / Export?" and get an answer that tracks the object's lifecycle without
|
|
11
|
-
* special-casing `sys_*` names.
|
|
5
|
+
* ## What is spec-owned here, and what is not (objectstack#4115)
|
|
12
6
|
*
|
|
13
|
-
* The bucket
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* The bucket logic is **the spec's**, and this module now says so in code
|
|
8
|
+
* rather than in a comment. `CrudAffordances` / `RowCrudPredicates` are
|
|
9
|
+
* re-exported from `@objectstack/spec/data`, and the bucket resolution itself
|
|
10
|
+
* delegates to the spec's `resolveCrudAffordances()`.
|
|
11
|
+
*
|
|
12
|
+
* This module used to describe itself as a "UI-side mirror" of that function
|
|
13
|
+
* and carry its own `DEFAULTS` table — a line-for-line copy of the spec's
|
|
14
|
+
* `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of its override parser. A faithful
|
|
15
|
+
* copy passes every value comparison and every behavioural test, so nothing
|
|
16
|
+
* would have caught the release in which the spec re-buckets an object class;
|
|
17
|
+
* reference identity is the only check that distinguishes a re-export from a
|
|
18
|
+
* fork (objectui#3003).
|
|
19
|
+
*
|
|
20
|
+
* What is genuinely objectui's and keeps a local name:
|
|
21
|
+
* {@link resolveEffectiveCrudAffordances} — the spec's object-level matrix
|
|
22
|
+
* INTERSECTED with the server-resolved effective API operation set (#3391), so
|
|
23
|
+
* the UI never offers a button the server would 405. The spec's function has
|
|
24
|
+
* no such notion; naming ours after it was the misdescription this burn-down
|
|
25
|
+
* removes.
|
|
26
|
+
*
|
|
27
|
+
* Lives in `@object-ui/core` — React-free and reachable by every UI package
|
|
28
|
+
* (app-shell, plugin-detail, plugin-form, plugin-grid) — so the intersection is
|
|
29
|
+
* defined ONCE instead of hand-mirrored in each. The bucket UNION type lives in
|
|
30
|
+
* `@object-ui/types` (`ManagedByBucket`) and is re-exported here for
|
|
31
|
+
* convenience. All translated copy (badge variants, empty-state messages) stays
|
|
32
|
+
* in `@object-ui/app-shell`.
|
|
17
33
|
*/
|
|
18
|
-
|
|
19
|
-
platform: { create: true, import: true, edit: true, delete: true, exportCsv: true },
|
|
20
|
-
config: { create: true, import: false, edit: true, delete: true, exportCsv: true },
|
|
21
|
-
system: { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
22
|
-
'engine-owned': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
23
|
-
'append-only': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
24
|
-
'better-auth': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
25
|
-
};
|
|
34
|
+
import { resolveCrudAffordances as resolveSpecCrudAffordances, } from '@objectstack/spec/data';
|
|
26
35
|
/**
|
|
27
36
|
* Collapse an `edit`/`delete` override (boolean or #2614 object form) onto
|
|
28
37
|
* the given bucket default, surfacing any per-record predicates alongside.
|
|
29
38
|
*
|
|
30
39
|
* THE single parser for the `userActions.{edit,delete}` override shape — every
|
|
31
|
-
* UI package (grid row affordances, related-list row predicates
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
40
|
+
* UI package (grid row affordances, related-list row predicates) routes through
|
|
41
|
+
* here instead of re-implementing the boolean/object-form parse locally. The
|
|
42
|
+
* spec keeps its equivalent (`normalizeRowCrudOverride`) module-private, so
|
|
43
|
+
* this stays a local declaration — under a name the spec does not own.
|
|
44
|
+
*
|
|
45
|
+
* `base` is the bucket default an omitted `enabled` falls back to (callers that
|
|
46
|
+
* only gate on an explicit opt-out pass `true`; predicate extraction is
|
|
47
|
+
* independent of `base`).
|
|
36
48
|
*/
|
|
37
49
|
export function normalizeUserAction(v, base) {
|
|
38
50
|
if (v == null)
|
|
@@ -59,24 +71,58 @@ export function normalizeUserAction(v, base) {
|
|
|
59
71
|
export function userActionPredicates(v) {
|
|
60
72
|
return normalizeUserAction(v, false).predicates;
|
|
61
73
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The affordance-bit → server API-operation mapping used to intersect the
|
|
76
|
+
* UI-intent affordances with the server's effective API operation set (#3391).
|
|
77
|
+
* This is the objectui end of the framework's `API_METHOD_DERIVATION` contract:
|
|
78
|
+
* the button predicate is `affordance(op) ∧ effective.api(op)`.
|
|
79
|
+
*/
|
|
80
|
+
const AFFORDANCE_TO_API_OPERATION = {
|
|
81
|
+
create: 'create',
|
|
82
|
+
import: 'import',
|
|
83
|
+
edit: 'update',
|
|
84
|
+
delete: 'delete',
|
|
85
|
+
exportCsv: 'export',
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the CRUD affordances a UI surface should expose for an object —
|
|
89
|
+
* the spec's object-level matrix, INTERSECTED with what the server will
|
|
90
|
+
* actually accept.
|
|
91
|
+
*
|
|
92
|
+
* The bucket/`userActions` half is **not computed here**: it is delegated to
|
|
93
|
+
* `resolveCrudAffordances()` from `@objectstack/spec/data` (ADR-0103), so the
|
|
94
|
+
* bucket table has exactly one definition on the platform. Only the
|
|
95
|
+
* intersection below is objectui's, which is why this function no longer
|
|
96
|
+
* carries the spec's name (objectstack#4115).
|
|
97
|
+
*
|
|
98
|
+
* @param obj The object schema (managedBy bucket + userActions overrides).
|
|
99
|
+
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
100
|
+
* set for this object (from `/me/permissions` `apiOperations`, #3391). When
|
|
101
|
+
* provided, each affordance bit is ANDed with the corresponding API operation
|
|
102
|
+
* (create/import→create/import, edit→update, delete→delete, exportCsv→export),
|
|
103
|
+
* so the UI never shows a button the server would 405. Passing `undefined`
|
|
104
|
+
* (old backend / no effective set) leaves the affordances untouched —
|
|
105
|
+
* backward-compatible. An empty array means "expose nothing" → all bits off.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveEffectiveCrudAffordances(obj, effectiveApiOperations) {
|
|
108
|
+
// The spec's resolver is the single definition of the bucket defaults and the
|
|
109
|
+
// `userActions` override parse (including the #2614 object form). Its input
|
|
110
|
+
// type does not model the `null`s objectui's loosely-typed schemas carry, so
|
|
111
|
+
// they are normalised away at this one seam rather than re-deriving anything.
|
|
112
|
+
const out = resolveSpecCrudAffordances({
|
|
113
|
+
managedBy: obj?.managedBy ?? undefined,
|
|
114
|
+
userActions: (obj?.userActions ?? undefined),
|
|
115
|
+
});
|
|
116
|
+
// [#3391] Intersect with the server's effective API operations when present.
|
|
117
|
+
// The frontend consumes the effective set the server hands down; it never
|
|
118
|
+
// reads the raw `apiMethods` nor re-derives.
|
|
119
|
+
if (Array.isArray(effectiveApiOperations)) {
|
|
120
|
+
const eff = new Set(effectiveApiOperations);
|
|
121
|
+
for (const [bit, op] of Object.entries(AFFORDANCE_TO_API_OPERATION)) {
|
|
122
|
+
if (out[bit] && !eff.has(op))
|
|
123
|
+
out[bit] = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
80
126
|
return out;
|
|
81
127
|
}
|
|
82
128
|
/** True only when a `userActions` flag (bare boolean or #2614 object form) opts the write in. */
|
|
@@ -84,15 +130,26 @@ export function isWriteOptedIn(v) {
|
|
|
84
130
|
return v === true || (typeof v === 'object' && v !== null && v.enabled === true);
|
|
85
131
|
}
|
|
86
132
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
133
|
+
* True for a platform-defined schema whose DATA is admin/user-writable — the
|
|
134
|
+
* `system-data` bucket. Used to pick the writable-system badge/empty-state copy
|
|
135
|
+
* instead of the engine-owned "read-only monitoring surface" copy.
|
|
136
|
+
*
|
|
137
|
+
* **Simplified in protocol 17 (objectstack#3355).** Under ADR-0103's v16 split
|
|
138
|
+
* this had to RECOVER the distinction from `userActions`: `system` doubled as
|
|
139
|
+
* both the engine-owned default and the writable set, so "did it open any
|
|
140
|
+
* write?" was the only way to tell a Notification Preferences grid from an
|
|
141
|
+
* automation-run log. v17 retired that overload — the writable half is now the
|
|
142
|
+
* `system-data` bucket (writable by DEFAULT) and the engine-owned half is
|
|
143
|
+
* `engine-owned` — so the bucket answers the question directly and the
|
|
144
|
+
* `userActions` probe would only re-derive what the value already states.
|
|
145
|
+
*
|
|
146
|
+
* The narrowing case is deliberately still `true`: a `system-data` grid that
|
|
147
|
+
* narrows to edit-only is still platform-schema/user-data and should read as
|
|
148
|
+
* such. The spec refuses `system-data` on an object that narrows away every
|
|
149
|
+
* write, so there is no "writable bucket with no writes" shape to worry about.
|
|
90
150
|
*/
|
|
91
151
|
export function isSystemWritable(obj) {
|
|
92
|
-
|
|
93
|
-
return false;
|
|
94
|
-
const o = obj?.userActions ?? {};
|
|
95
|
-
return o.create === true || isWriteOptedIn(o.edit) || isWriteOptedIn(o.delete);
|
|
152
|
+
return obj?.managedBy === 'system-data';
|
|
96
153
|
}
|
|
97
154
|
/**
|
|
98
155
|
* Whether an object's rows may be edited in place (detail inline-edit / form
|
|
@@ -100,7 +157,14 @@ export function isSystemWritable(obj) {
|
|
|
100
157
|
* `append-only` / `better-auth` objects resolve to `false` unless they opened
|
|
101
158
|
* `userActions.edit`. (Per-record predicates gate row action buttons, not
|
|
102
159
|
* object-level editability, so only the resolved boolean matters here.)
|
|
160
|
+
*
|
|
161
|
+
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
162
|
+
* set for this object (`/me/permissions` `apiOperations`, #3391/#3546). When
|
|
163
|
+
* provided, inline-edit is additionally ANDed with the server allowing
|
|
164
|
+
* `update`, so a record page never offers double-click/pencil editing the
|
|
165
|
+
* server would 405. `undefined` (old backend / unrestricted) leaves the
|
|
166
|
+
* bucket affordance untouched.
|
|
103
167
|
*/
|
|
104
|
-
export function isObjectInlineEditable(obj) {
|
|
105
|
-
return
|
|
168
|
+
export function isObjectInlineEditable(obj, effectiveApiOperations) {
|
|
169
|
+
return resolveEffectiveCrudAffordances(obj, effectiveApiOperations).edit;
|
|
106
170
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI — list-view vocabulary canonicalization
|
|
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
|
+
import type { RowHeight } from '@objectstack/spec/ui';
|
|
9
|
+
/** ListView's toolbar density vocabulary — three steps, not the spec's five. */
|
|
10
|
+
export type DensityMode = 'compact' | 'comfortable' | 'spacious';
|
|
11
|
+
/**
|
|
12
|
+
* The spec's `RowHeightSchema` vocabulary — **re-exported, not re-declared**.
|
|
13
|
+
*
|
|
14
|
+
* The hand-written union that used to sit here already advertised itself as
|
|
15
|
+
* "the spec's `RowHeightSchema` vocabulary" in its doc comment, which is the
|
|
16
|
+
* failure class objectstack#4115 is about: the claim was true when written and
|
|
17
|
+
* nothing would have caught the day it stopped being true. The maps below are
|
|
18
|
+
* `Record<RowHeight, …>`, so a spec-side addition now fails the build here
|
|
19
|
+
* instead of silently leaving a row height with no density mapping.
|
|
20
|
+
*/
|
|
21
|
+
export type { RowHeight };
|
|
22
|
+
/**
|
|
23
|
+
* `densityMode` → `rowHeight`. The two vocabularies are not the same size: the
|
|
24
|
+
* spec has five row heights, ListView's toolbar offers three. Widening maps
|
|
25
|
+
* each density onto the spec value the renderer already resolves it back to
|
|
26
|
+
* (see {@link ROW_HEIGHT_TO_DENSITY_MODE}), so a fold followed by a read is a
|
|
27
|
+
* round trip — `spacious` → `tall` → `spacious`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DENSITY_MODE_TO_ROW_HEIGHT: Record<DensityMode, RowHeight>;
|
|
30
|
+
/**
|
|
31
|
+
* `rowHeight` → `densityMode`, the read direction. Narrowing five values onto
|
|
32
|
+
* three is lossy by construction: `short` collapses into `compact` and
|
|
33
|
+
* `extra_tall` into `spacious`. Exported so the renderer and the persistence
|
|
34
|
+
* layer agree on the collapse instead of each hard-coding its own table.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ROW_HEIGHT_TO_DENSITY_MODE: Record<RowHeight, DensityMode>;
|
|
37
|
+
/**
|
|
38
|
+
* Tolerant reader for {@link ROW_HEIGHT_TO_DENSITY_MODE}. View metadata is
|
|
39
|
+
* user-authored, so `rowHeight` is not guaranteed to be one of the five spec
|
|
40
|
+
* values — an unknown one lands on `comfortable` rather than rendering an
|
|
41
|
+
* undefined density. Keeping the fallback here means every surface collapses
|
|
42
|
+
* the same way.
|
|
43
|
+
*/
|
|
44
|
+
export declare function rowHeightToDensityMode(rowHeight: unknown): DensityMode;
|
|
45
|
+
/**
|
|
46
|
+
* ObjectUI's `list-view` node historically used a different vocabulary from
|
|
47
|
+
* `@objectstack/spec` for the same concepts (`fields` where the spec says
|
|
48
|
+
* `columns`, `viewType` where it says `type`, …). Issue #2231 closed the
|
|
49
|
+
* type-level fork; #2890 closes the vocabulary fork.
|
|
50
|
+
*
|
|
51
|
+
* Stored view metadata in user databases still carries the legacy keys, so the
|
|
52
|
+
* renderer cannot simply stop accepting them. Per AGENTS.md Commandment #0.1 the
|
|
53
|
+
* answer is NOT a per-read-site `??` fallback — those fossilize a second de-facto
|
|
54
|
+
* contract and drift apart (they already had: `ObjectGrid` preferred `columns`
|
|
55
|
+
* in one branch and `fields` in another). Instead legacy acceptance lives HERE,
|
|
56
|
+
* in one documented normalizer at the component boundary, mirroring
|
|
57
|
+
* `normalizeSchemaReferenceKeys` (object schemas) and the spec's own
|
|
58
|
+
* `normalizeVisibleWhen` / `normalizeFilterOperator` migration bridges.
|
|
59
|
+
*
|
|
60
|
+
* Note this cannot be a `z.preprocess` on `ListViewSchema`: nothing on the
|
|
61
|
+
* render path parses view metadata through zod (the zod schemas are used by the
|
|
62
|
+
* CLI validator, the VS Code extension and tests only), so a schema-level fold
|
|
63
|
+
* would never run. The guarantee comes from the call site instead — `ListView`
|
|
64
|
+
* normalizes before it reads anything.
|
|
65
|
+
*
|
|
66
|
+
* The fold is deliberately one-directional: the canonical key wins when both are
|
|
67
|
+
* present, and the legacy key is REMOVED from the result so a read-site that was
|
|
68
|
+
* missed fails loudly instead of quietly taking the legacy path. Like a spec
|
|
69
|
+
* migration bridge, this is expected to be dropped in a future major once stored
|
|
70
|
+
* metadata has been migrated.
|
|
71
|
+
*
|
|
72
|
+
* Non-mutating and allocation-frugal: returns the input by reference when there
|
|
73
|
+
* is nothing to fold, so `ListView`'s downstream `useMemo`s keep a stable
|
|
74
|
+
* dependency identity on the common (already-canonical) path.
|
|
75
|
+
*
|
|
76
|
+
* Currently folded:
|
|
77
|
+
* - `fields` → `columns` (#2890 scope A step 1)
|
|
78
|
+
* - `densityMode` → `rowHeight` (#2890 scope A step 2)
|
|
79
|
+
* - the `show*` toolbar flags → `userActions` (#2890 scope A step 3), and
|
|
80
|
+
* `showDescription` → `appearance.showDescription`. The canonical key wins
|
|
81
|
+
* per-flag, so a view may carry `userActions` for some toggles and a legacy
|
|
82
|
+
* flag for others. NOTE the fold does not apply any default: an absent flag
|
|
83
|
+
* stays absent, because the defaults are per-toggle (search/sort/filter/
|
|
84
|
+
* rowHeight/group default ON, hideFields/rowColor default OFF) and belong to
|
|
85
|
+
* the renderer, not to the vocabulary bridge.
|
|
86
|
+
* - `aria: { label, describedBy }` → the spec's `AriaProps`
|
|
87
|
+
* (`{ ariaLabel, ariaDescribedBy }`), and `sharing: { visibility, enabled }`
|
|
88
|
+
* → the spec's `ViewSharing` (`{ type }`) — #2890 scope A step 5. `aria.live`
|
|
89
|
+
* survives untouched: it has no spec counterpart.
|
|
90
|
+
* - `filters` → `filter` (#2890 scope A step 4). A key rename only: BOTH keys
|
|
91
|
+
* carry an ObjectQL FilterNode array (`[['stage','=','won']]`) everywhere in
|
|
92
|
+
* objectui — every consumer passes the value straight to `$filter`. The spec
|
|
93
|
+
* types `filter` as `ViewFilterRule[]` (`{field, operator, value}` objects),
|
|
94
|
+
* so objectui's field is typed from the spec but used as something else.
|
|
95
|
+
* That mismatch is real and out of scope here; converting formats inside a
|
|
96
|
+
* vocabulary fold would change what reaches the data source.
|
|
97
|
+
* - `viewType`: a missing kind, or the view CATEGORY `'list'` that AI-authored
|
|
98
|
+
* metadata stores and hosts forward verbatim, becomes the renderable `'grid'`
|
|
99
|
+
* — otherwise it reaches the renderer's typeless default branch and shows as
|
|
100
|
+
* a red "Unknown component type" box.
|
|
101
|
+
* - each `columns` entry's IDENTITY — `name` / `fieldName` → the spec's `field`
|
|
102
|
+
* (#3104). This one MIRRORS instead of deleting, for the reason given on
|
|
103
|
+
* {@link normalizeColumnIdentities}: `columns` entries cross the package
|
|
104
|
+
* boundary into host renderers, so dropping `name` from under them is a
|
|
105
|
+
* breaking change with no inventory. Runs AFTER the `fields` → `columns` fold
|
|
106
|
+
* above, so a view that spells its column list the legacy way still gets its
|
|
107
|
+
* entries canonicalized.
|
|
108
|
+
*/
|
|
109
|
+
export declare function normalizeListViewSchema<T>(schema: T): T;
|