@object-ui/data-objectstack 17.7.0 → 18.0.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 +368 -0
- package/README.md +172 -2
- package/dist/index.cjs +875 -362
- package/dist/index.d.cts +742 -307
- package/dist/index.d.ts +742 -307
- package/dist/index.js +870 -360
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,373 @@
|
|
|
1
1
|
# @object-ui/data-objectstack
|
|
2
2
|
|
|
3
|
+
## 18.0.0
|
|
4
|
+
|
|
5
|
+
### Dependency floors
|
|
6
|
+
|
|
7
|
+
Computed from `.objectstack-sha` `06cab4849a8a`: the earliest published `@objectstack/spec` that contains it is `18.0.0`.
|
|
8
|
+
|
|
9
|
+
- `@objectstack/client` (`dependencies`): `^18.0.0-next.1` → `^18.0.0`
|
|
10
|
+
- `@objectstack/spec` (`dependencies`): `^18.0.0-next.1` → `^18.0.0`
|
|
11
|
+
|
|
12
|
+
### Major Changes
|
|
13
|
+
|
|
14
|
+
- 6f3299f: objectui moves to major 18, following `@objectstack/*` from 17 to 18 (objectui#12334). objectui's major is not a count of its own breaking changes: it tracks the major of the `@objectstack/spec` its packages depend on, so that the same major means compatible across the two projects. These packages already declare `@objectstack/*` on the 18 line (objectui#12030), so every package in the fixed release group crosses to 18 together. It is the one `major` that rule admits: objectui's own breaking changes ship as `minor`, described in their entries.
|
|
15
|
+
|
|
16
|
+
The 18 line is released from Changesets pre mode on the `next` tag (`.changeset/pre.json`), as `@objectstack/*` 18 is: these releases are `18.0.0-next.N` on the npm `next` dist-tag, and they do not move `latest`. The first `18.0.0` on `latest` comes when `@objectstack` 18 is generally available and this repository leaves pre mode.
|
|
17
|
+
|
|
18
|
+
What a consumer must do to take it is in the `@objectstack/*` 18 entry of this release (objectui#12030): move its own `@objectstack/*` packages to the 18 line at the same time.
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- da35453: Objects with a picklist-bound field save again from the OWD overview, the Setup fields and objects pages, the metadata-admin embedded-item editor, and `MetadataService.saveFields` (objectui#11692).
|
|
23
|
+
|
|
24
|
+
The runtime serves a field that names a shared picklist (`picklist: 'NAME'`) with the options it resolved from the list. The authoring door refuses `options` beside `picklist`, with `422 INVALID_METADATA` at `fields.FIELD.options`, and the refusal covers the whole object. objectui#10202 fixed the two designers that host the select-field editor. These writers also seed their save from a served object read, so they sent the resolved options back and were refused, whatever had been edited:
|
|
25
|
+
|
|
26
|
+
- **Studio → Access → OWD overview.** Changing an object's sharing model and saving was refused.
|
|
27
|
+
- **The Setup fields page** (`MetadataFieldsPage`). Editing any field of such an object was refused.
|
|
28
|
+
- **The Setup objects page** (`MetadataObjectsPage`). Relabelling such an object was refused.
|
|
29
|
+
- **The metadata-admin embedded-item editor** (`EmbeddedItemEditor`, opened from an object's fields, indexes or validations). Saving any item of such an object was refused.
|
|
30
|
+
- **`MetadataService.saveFields`.** A field list built from the served object was refused.
|
|
31
|
+
|
|
32
|
+
Each now leaves `options` out of every field that names a picklist, before the PUT. A field without `picklist` keeps its inline `options` unchanged, and no other key is touched.
|
|
33
|
+
|
|
34
|
+
`dropServedPicklistOptions` is now exported from `@object-ui/data-objectstack`. It is the served-to-authored conversion these writers apply, and it moves here from `@object-ui/app-shell` (it was not exported there), because `@object-ui/plugin-designer` does not depend on `app-shell`. It is for code that builds an object PUT from a served read. `MetadataClient.save` does not apply it, and neither do `MetadataService.saveObject` and `MetadataService.saveMetadataItem`, which read nothing: a body that pairs `picklist` with `options` without a served read behind it is still refused by the server, with its prescription.
|
|
35
|
+
- b9814b3: Studio's first draft save after opening an item no longer replaces a draft that was saved elsewhere in the meantime (objectui#11773).
|
|
36
|
+
|
|
37
|
+
Until now an editor's draft saves were pinned only from its second save on: the `/meta` draft read served no version, so two editors who each opened an item and saved it once overwrote each other's draft in silence. The `@objectstack/*` 18 draft read serves the version, and each editor's load now pins its first save to what it read:
|
|
38
|
+
|
|
39
|
+
- an item with a pending draft is saved with `If-Match` and that draft's version;
|
|
40
|
+
- an item with no pending draft is saved with `If-None-Match: *`, so a draft another editor created in the meantime is refused, not overwritten.
|
|
41
|
+
|
|
42
|
+
The editors read the draft at the package their saves write to (`?package=`), since a read's version describes the row a save to the same address compares against. This covers the metadata editor, the permission matrix's package door, the Studio pillars (objects, flows, interfaces and app navigation), the list-view panel and the hooks panel.
|
|
43
|
+
|
|
44
|
+
A create sends `If-None-Match: *`, so creating an item under a name that already holds a draft is refused instead of writing the new skeleton over it.
|
|
45
|
+
|
|
46
|
+
In the conflict dialog, **Overwrite** now re-sends the draft pinned to the version the refusal names (`currentVersion`), so a save that lands while the dialog is open is refused and asked about again rather than overwritten. A server whose refusal names no `currentVersion` is overwritten unpinned, as before.
|
|
47
|
+
|
|
48
|
+
`@object-ui/data-objectstack`: `MetadataClientSaveOptions` gains `ifNoneMatch?: '*'`, sent as `If-None-Match: *`. It never travels with `ifMatch`: `MetadataClient.save` throws before sending a request that sets both. The `ifMatch` docblock now names the token's real sources (the item read's `version`, a save receipt's `version`, a conflict's `currentVersion`) instead of "the `checksum` returned by the last read".
|
|
49
|
+
- c5406c2: Depend on the `@objectstack/*` 18 prerelease line (objectui#12030).
|
|
50
|
+
|
|
51
|
+
Every `@objectstack/*` range these packages declare in `dependencies` or `peerDependencies` moves from `^17.x` to `^18.0.0-next.0`, the `next` prerelease of `@objectstack/*` 18 (raised to `^18.0.0-next.1` by objectui#12370 before release).
|
|
52
|
+
|
|
53
|
+
**Breaking for consumers:** an install that resolves `@objectstack/spec` (or `@objectstack/client`, `@objectstack/formula`, `@objectstack/lint`) 17.x no longer satisfies these ranges. A consumer must move its own `@objectstack/*` packages to the 18 line, starting with `18.0.0-next.1` (objectui#12370), at the same time as it takes this release.
|
|
54
|
+
|
|
55
|
+
**objectui's own surface moves with it:**
|
|
56
|
+
|
|
57
|
+
- `element:record_picker`'s registration stops publishing the `object`, `filter`, `sort` and `limit` inputs, which spec 18 retires; a JSX page that writes one now gets `unknown-prop`.
|
|
58
|
+
- The `CapabilityMultiSelectField` widget's curated capability set (`@object-ui/fields`) follows spec 18: `manage_mailboxes` in, `manage_org_presentation` out.
|
|
59
|
+
- `element:number` and `element:repeater` nodes that bind no object are accepted, as spec 18's row and `PageComponentSchema` accept them; the two `ELEMENT_*_OBJECT_REQUIRED` refusals are gone.
|
|
60
|
+
|
|
61
|
+
**Retired with the spec:** `@objectstack/spec` 18 tombstones `object-grid`'s `defaultFilters` and refuses it by name, and `ObjectGrid` and `ObjectView` stop reading it (`table.defaultFilters` on an `object-view` included). A view that still carries it renders unfiltered: rename the key to `filter`, whose value is the same rule array.
|
|
62
|
+
- 6757a93: Show a write answer's advisory `warnings` to the user after save (objectui#12107).
|
|
63
|
+
|
|
64
|
+
- `@object-ui/data-objectstack`: `WriteWarningEvent` gains an optional `warnings` member, typed by the spec's `ValidateDataIssue` (the element of `CreateDataResponseSchema` / `UpdateDataResponseSchema`'s `warnings`). A single-record `create` / `update` parses the answer's `warnings` through `ValidateDataIssueSchema` and emits the write-warning event when either `droppedFields` or `warnings` is non-empty; `droppedFields` stays required and is `[]` when the event exists only for warnings. An answer without the key emits nothing new, including from a runtime that predates it.
|
|
65
|
+
- `@object-ui/app-shell`: the write-warning toast shows each distinct warning message as the server wrote it, in its own toast titled by the new `detail.writeWarningsTitle` key. The stripped-fields toast is unchanged.
|
|
66
|
+
- `@object-ui/i18n`: adds `detail.writeWarningsTitle` to all ten packs.
|
|
67
|
+
- 43e1505: **A dashboard or a list view the server refuses you, opened by its URL, now says it was refused** (objectui#12112, the console half of objectstack#22611's ruling A).
|
|
68
|
+
|
|
69
|
+
Since objectstack#22731 the server applies a dashboard's and a list view's `requiredPermissions`: its dashboard and view lists carry only what you may open, and a by-name read of one you may not answers `403 PERMISSION_DENIED`. The console read only the lists, so a refused dashboard's URL showed "Dashboard Not Found", and a refused list view's URL silently showed the object's default view instead.
|
|
70
|
+
|
|
71
|
+
- **The dashboard page and the object's list page** ask a missed name by name. A `PERMISSION_DENIED` answer shows "You don't have access" with the server's own sentence, which names the item: `You do not have permission to open the 'NAME' dashboard.` (or `view`). Any other answer keeps today's face: "Dashboard Not Found", or the fall-back to the default view. Nothing is asked in draft preview, or by a host whose data source has no probe. The title reuses existing translations; no translation key is added.
|
|
72
|
+
- **`@object-ui/data-objectstack`: `ObjectStackAdapter.probeMetaItemAccess(type, name)`**, and its result type `MetaItemAccess`. It is `probeAppAccess` for any type the by-name meta route gates per caller, and it also returns the refusal's `message` with a `denied` verdict. `probeAppAccess` now delegates to it and answers as before.
|
|
73
|
+
- An object's own list view, served whole inside the object's metadata to a user who may edit the object, is still listed and opens. The server serves it to that user, and the console adds no rule of its own.
|
|
74
|
+
- dbc8c11: Studio's Automations rail and the open flow's Status pill read the automation engine's registration verdict, which the flow list now serves beside its items (`registrations`, `FlowRegistrationSchema` in `@objectstack/spec`). A flow the engine refused at boot reads "Refused by the engine", titled with the engine's message as sent, instead of "Unpublished draft" or "Enabled"; a flow the engine holds no registration for reads "Not running here". A registered flow, and a flow with no verdict, read as before, and the flow's stored `status` is never rewritten. `MetadataClient.listResponse` returns a list read with what the server serves beside the items; `list` is unchanged.
|
|
75
|
+
|
|
76
|
+
**Clause-②: yes (widening).** `@object-ui/data-objectstack` gains one method, `MetadataClient.listResponse`, and one exported type, `MetadataListAnswer` (`items` plus the optional `registrations` of the spec's `GetMetaItemsResponse`). No export, prop or member is removed or changes type; `@object-ui/app-shell`'s exports are unchanged.
|
|
77
|
+
- ff9df88: Depend on `@objectstack/*` `18.0.0-next.1` (objectui#12370).
|
|
78
|
+
|
|
79
|
+
Every `@objectstack/*` range these packages declare moves from `^18.0.0-next.0` to `^18.0.0-next.1`; the console and the runner bundle `18.0.0-next.1`. A consumer moves its own `@objectstack/*` packages to `18.0.0-next.1` with this release.
|
|
80
|
+
|
|
81
|
+
**Accept sets that narrow with it.** `@object-ui/types` validates page action rows and view data bindings through the spec's own rows, so its faces (`safeValidateSchema` and `StrictAnyComponentSchema`) now refuse what `18.0.0-next.1` refuses. Each was accepted on `18.0.0-next.0`:
|
|
82
|
+
|
|
83
|
+
- `onSuccess` on an `action:button` / `action:icon` row, or an `action:group` / `action:menu` member, whose executor is the `url`, `navigation`, `modal`, `flow` or `form` built-in, or that has no executor at all. Write the hop on an `api` or `script` action (or a host handler name), or drop it.
|
|
84
|
+
- `onSuccess` beside `operation: 'update'`, and an `onSuccess` block carrying a key outside `{ navigate, openIn }`.
|
|
85
|
+
- `actionType: 'navigation'` on a row (and `type: 'navigation'` on a container member): write `'url'`.
|
|
86
|
+
- A `url` executor's `target` that is not openable, judged on the executor the runner runs, including a row whose `name` selects it.
|
|
87
|
+
- A view `data` binding with `provider: 'api'`: bind the object or static rows; an API call is the page element's binding.
|
|
88
|
+
- 919d5cd: "Publish all" publishes each language of a package-less email template by its own bundle member (objectui#12402, the publish half of objectui#12271).
|
|
89
|
+
|
|
90
|
+
Since `@objectstack/spec` 18.0.0-next.1 each `GET /meta/_drafts` row carries `member` for a type whose identity is a pair (today only `email_template`, `(name, locale)`), and the by-name publish door reads it back as `?member=`. A publish that names no member promotes the canonical member's draft, so two languages of one template drafted outside a package were published as the canonical one twice.
|
|
91
|
+
|
|
92
|
+
- **`MetadataClient.publishDraft(type, name, options?)`** takes an optional `{ member }` and sends `?member=` when it is set. Without one the parameter is omitted, never sent empty. This widens the method's accepted arguments; a two-argument call sends exactly the request it sent before.
|
|
93
|
+
- **`usePublishAllDrafts`** (Home's "Publish all" and the draft-preview bar) keeps each draft row's `member` and passes it on the package-less path. Rows without a member send no selector.
|
|
94
|
+
- **Unchanged:** package-bound drafts still publish through `publishPackageDrafts(packageId)`.
|
|
95
|
+
- 431cece: `MetadataDraftHeader`, the row type `MetadataClient.listDrafts()` declares, now declares `member` (objectui#12413).
|
|
96
|
+
|
|
97
|
+
`@objectstack/spec` declares `ListDraftsResponseSchema.drafts[].member`, an optional string, and `GET /meta/_drafts` serves it on every draft of a type whose identity is a pair (today `email_template`, `(name, locale)`). The published row type had no `member`, so a reader had to cast the row to reach it.
|
|
98
|
+
|
|
99
|
+
- **`@object-ui/data-objectstack`, additive widening.** `MetadataDraftHeader` is now derived from the spec row by `Pick` over `type`, `name`, `member`, `packageId`, `updatedAt` and `updatedBy`. The five keys it declared before keep their exact types; `member?: string` is the one key added. Nothing is removed or narrowed. `MetadataSaveAdvisoryEvent` gains an optional `member`, set by `publishDraft()` when the publish named one, so an advisory raised on one language's publish is attributed to that member. Every other door emits the event as before, without the key.
|
|
100
|
+
- **`@object-ui/app-shell`, no behaviour change.** The publish-all hook reads each draft row's `member` through the declared type instead of an `any[]` cast. The URLs it sends are unchanged.
|
|
101
|
+
- caac3b2: A list's sort, column layout, Filter panel and tab order are now each user's own, and follow them to another browser (objectui#12418).
|
|
102
|
+
|
|
103
|
+
**What changes for users.** Sorting a list, resizing or moving its columns, and dragging the tabs in Manage Views is now personal: it no longer changes the view for everyone. Before this change, a header sort or a column drag was saved into the shared view, and a Manage Views drag saved a new order onto every saved view, so one user's click re-sorted or reordered the list for every other user. Under a walled tenancy posture (`group` or `isolated`), where only the platform operator may write environment metadata, the same click ended in an error. The view's own sort, columns, filter and tab order stay the default for every user who has not changed them, and the view editor (the view settings panel and Studio) is still how an author changes them for everyone. Density and hidden fields are not part of this change: they are still saved to the view.
|
|
104
|
+
|
|
105
|
+
- **Where it is kept.** One `sys_user_preference` row per user and object, key `ui.list.<object>`, holding `{ viewOrder, perView: { <view id>: { sort, filters, columnState } } }`. The console reads every such row in its one preference read at load (objectui#12187), so this adds no request. localStorage (`list-state:<user>:<object>`) is the write-through cache, and the only store when no preference backend is attached. The first read of an object with nothing cached takes that browser's older tab order (`viewOrder:<object>`) and Filter panel conditions (`list-filters:…`). The search term stays where it was.
|
|
106
|
+
- **Opening a list.** A list now opens on the user's stored sort and Filter panel conditions, and the page waits until the preference read has answered (2.5 s at most) before drawing it. A link that carries list state still wins over the stored state, as before (objectui#11860).
|
|
107
|
+
- **Resetting.** The Filter panel's "Clear all" clears the user's stored filters for the view. A new "Reset view" entry in the page header's ⋯ menu, beside the favorites entry, appears when the user has stored anything for the active view. It drops that view's sort, filters and column layout and opens the list as the view declares it. An administrator can delete a user's row under Setup → User Preferences.
|
|
108
|
+
|
|
109
|
+
`@object-ui/data-objectstack` adds `createObjectStackUserPreferenceFamily` (with the `UserPreferenceFamily` and `ObjectStackUserPreferenceFamilyOptions` types and the `PREFERENCE_FAMILY_ROW_LIMIT` constant). It loads every row whose key starts with a prefix, through the same shared read as the per-key adapters. A read that includes a prefix asks for its keys or that prefix, with a `$top` of the key count plus 200 rows per prefix. When an answer fills that `$top`, a key whose row is missing from it reads its own row. `createObjectStackUserStateAdapter` keeps its signature and its behaviour.
|
|
110
|
+
|
|
111
|
+
`@object-ui/i18n` adds `console.objectView.resetView` and `console.objectView.resetViewTitle` in all ten packs.
|
|
112
|
+
- 3eb3ec1: The console's list toolbar no longer has a write into the shared view, and the data-source method that write used is retired (objectui#12504).
|
|
113
|
+
|
|
114
|
+
**BREAKING:** `DataSource.updateViewConfig` is removed from `@object-ui/types`, and `ObjectStackAdapter.updateViewConfig` is removed from `@object-ui/data-objectstack`. The capability gate that only that method read goes with it: `ObjectStackAdapter.setSystemCapabilities`, the `systemCapabilities` option of the adapter constructor and of `createObjectStackAdapter`, `VIEW_CONFIG_CAPABILITY`, `ViewConfigPermissionDeniedError` and `isViewConfigPermissionDeniedError`. A class that implements `DataSource` and keeps an `updateViewConfig` method still compiles, but nothing calls the method any more; an object literal annotated as `DataSource` that carries it is refused as an unknown property. Code that calls `updateViewConfig` through a `DataSource` or an `ObjectStackAdapter`, or imports one of the gate names, no longer compiles. To change a view for everyone, save the view: `createView` / `updateView` on the data source, or the console's view editor, which writes through the runtime metadata seam.
|
|
115
|
+
|
|
116
|
+
Since objectui#12418 and objectui#12474, the toolbar's sort, column layout, density and Hide Fields are each user's own list state, so the toolbar write had no caller. The view editor's explicit save is now the one write path to the shared view, and it is unchanged.
|
|
117
|
+
|
|
118
|
+
Personalization rows that earlier consoles wrote into `/meta/view` stay readable: the overlay marker, `narrowPersonalizationOverlay` and `listViews()` exclusion still apply to them. Nothing new writes such a row.
|
|
119
|
+
|
|
120
|
+
`@object-ui/i18n` drops `console.objectView.viewConfigPermissionDenied` from all ten packs. It was the toast the retired write raised when the gate refused it. The other packages change only in comments and tests that named the retired path.
|
|
121
|
+
- 3bcf314: Studio marks the settings the AI copilot drafted: a Draft chip and a Source line ("Added as a draft by the AI copilot 2 min ago") on a field or a list-view setting the copilot changed, read from the platform's per-key draft provenance (objectui#12725, item 3; objectstack#24298 and #24395).
|
|
122
|
+
|
|
123
|
+
- **`@object-ui/data-objectstack` reads and writes the provenance.** The strict draft read (`getDraft`) serves `draftChanges` beside `item`: one `{ path, by, actor, at }` per changed key path. New exports beside `extractDraftBody`: `readDraftChanges(resp)` (the envelope's entries, or `null` when the read recorded none, never `[]`), `governingDraftChange(changes, path)` (the entry that answers for a key: the longest prefix), `draftChangesUnder(changes, path)`, and the `DraftChange` type, derived from `@objectstack/spec`'s `GetMetaItemResponse`. `MetadataClientSaveOptions` gains `surface?: 'studio' | 'api'`, sent once as `?surface=`, derived from the spec's `MetadataAuthoringSurface` minus `copilot` (the HTTP doors refuse it). `MetadataDeleteOptions` no longer inherits it: the delete door reads no such parameter.
|
|
124
|
+
- **Studio's own draft saves declare `surface: 'studio'`,** so a person's edit there is recorded as `studio`, not `api`.
|
|
125
|
+
- **The marks.** The Data pillar's field properties show the chip and the Source line when the field's entry, or an entry under it, is the copilot's. An `object` leaf's list-view properties show the chip on each setting the copilot changed (card fields, title, click to open, group by, columns, filter, sort), mark the elements it added to an array setting by comparing the draft's array with the published one, and end with a Source line naming them. A `studio` or `api` entry draws nothing, and so does a draft whose read recorded no provenance. To compare arrays, the list-view panel reads the published layer beside a draft when, and only when, the draft carries a copilot entry.
|
|
126
|
+
|
|
127
|
+
The new copy lives in the metadata-admin designer's own string tables (en and zh).
|
|
128
|
+
- 6058bf8: Studio's Changes drawer can now fill a new field's default into the records an object already holds, when you publish (objectui#12929).
|
|
129
|
+
|
|
130
|
+
- **The choice.** In the drawer, under a field that a pending draft adds to an already-published object, an unchecked "Backfill default (N existing records)" choice appears when the field declares a literal `defaultValue`. N is the object's record count, read as you. If the count read gives no total, the choice shows no number. A field whose default is a runtime token (`NOW()`, `current_user`) or an expression, a field with no default, and the fields of an object that has never been published get no choice. The publish door refuses those with a 400, and there are no existing records to fill.
|
|
131
|
+
- **The publish.** Publish all sends the checked fields as the protocol's opt-in `backfill` (objectstack#24253). The server writes each field's default into the existing records that have no value, as you, with permissions and record hooks applied. The publish toast then shows the rows the server says it wrote for each field. If a field stopped short, the toast is a warning that gives the server's reason, and the publish still stands. If nothing is checked, the publish request is `{}`, the same as before, and no existing record is written.
|
|
132
|
+
- **`MetadataClient.publishPackageDrafts(packageId, options?)`** (`@object-ui/data-objectstack`) takes `options.backfill`, an array of `{ object, field }`. Its type is picked from `@objectstack/client`'s `packages.publishDrafts`. The method sends `backfill` only when it is given. The receipt is `backfillApplied` on the result. A call without options sends the `{}` body it always sent.
|
|
133
|
+
- **New strings:** `preview.changes.backfillOption` (a count family) and `preview.changes.backfillOptionNoCount` in all ten `@object-ui/i18n` packs. The Studio table gains `engine.studio.backfillWritten` and `engine.studio.backfillStopped` (en and zh).
|
|
134
|
+
- 088e0f7: A record-form field can name one item of a metadata registry by its machine name: a `text` field that declares `widget: 'position-ref'` lists the position registry, and one that declares `widget: 'permission-ref'` lists the permission-set registry. It stores the name only (objectui#7611, ruling B2 on objectstack#22865). This is the mechanism the metadata forms' `ref-multi:permission` picker brought in (objectui#12126), now reachable from an ObjectQL object field. A grant table's name field (`sys_user_position`'s position, `sys_user_permission_set.permission_set`) can now offer the declared items instead of a box for a typed name.
|
|
135
|
+
|
|
136
|
+
- **The widget (`@object-ui/fields`).** `field:position-ref` and `field:permission-ref`, one key per registry type, with the type in the key, as `object-ref` carries `object`. Like every `widget` value, the name is looked up as `field:<widget>`. Each item reads as its label beside its name, and a pick writes the name: no label, no id. A stored name the loaded registry does not list stays on screen as its own option, flagged *not found*. The four catalog arms follow the metadata forms' `ref:dataset` / `ref-multi:<type>` pickers. With no data source that can list a registry, the field is a text box for the typed name. While the list loads, the stored name sits in a disabled box. When the load fails, a notice names the failure and its cause, and the text box stays enabled. A loaded but empty registry gives the same text box. Neither key is a declarable field type: both are widget hints, like `object-ref`.
|
|
137
|
+
- **The catalog (`@object-ui/types`, `@object-ui/data-objectstack`).** `DataSource` gains an optional `listMetadataItems(type)`, answering `{ name, label }` headers for one registry type. An empty array is authoritative ("this registry has no item of this type"), so a failed load must reject, never resolve `[]`. `ObjectStackAdapter` implements it over `GET /api/v1/meta/:type` (the client's `meta.getItems`). It rejects with the transport's error, and refuses an answer that carries no `items` list. It is uncached, so an item authored in Studio shows on the next form.
|
|
138
|
+
- **Locale keys (`@object-ui/i18n`).** `fields.registryRef.loadFailed` and `fields.registryRef.notFound`, in all ten packs.
|
|
139
|
+
- **Wiring.** The form renderer threads `dataSource` to both keys, as it does for the other widget-hint pickers (`@object-ui/components`). The inline-grid exclusion set and the action-param value-shape table list both keys as widget-hint-only (`@object-ui/fields`, `@object-ui/app-shell`). The CLI's generated `KNOWN_SCHEMA_TYPES` gains `field:position-ref` and `field:permission-ref` (`@object-ui/cli`). A comment in `@object-ui/core` stops counting the picker names.
|
|
140
|
+
|
|
141
|
+
**Clause-②: yes (widening)**: one optional `DataSource` member, two registered `field:` keys (the `RegisteredFieldWidgetType` union and `FORM_FIELD_TYPES` gain two members), and two locale keys. Nothing narrows: a field without the hint renders exactly as before.
|
|
142
|
+
- 78b07f4: The object-page special cases for the retired permission-set catalog object are removed (objectui#7611, after objectstack's ADR-0131 stage 8 retired `sys_position`, `sys_permission_set`, `sys_position_permission_set` and `sys_capability`). A permission set is edited on its Setup metadata page, which reads the registry; none of the removed code ran anywhere else.
|
|
143
|
+
|
|
144
|
+
**Breaking (narrowing; scored `minor` per the fixed group's version policy).** Each removal below shrinks a published face:
|
|
145
|
+
|
|
146
|
+
- **`@object-ui/core`: `recordDelete.confirmText`.** FROM `recordDelete.confirmText({ objectName, t }, record)` TO `recordDelete.confirmText({ t })`. A package-owned `sys_permission_set` row no longer gets the reset question or the "reset" toast: every record gets the delete question and the delete toast. Fix: drop `objectName` and the second argument.
|
|
147
|
+
- **`@object-ui/i18n`: pack keys.** FROM `perm.facet.*` (`none`, `more`, `objects`, `fields`, `rls`, `tabs` with their plural slots, `adminScope`, `designInStudio`, `designInStudioHint`) and `objectActions.resetPackageSetConfirm` / `objectActions.resetPackageSetSuccess` in all ten packs TO absent. Fix: a consumer that still asks for one keeps its own copy (inline `defaultValue` or its own bundle).
|
|
148
|
+
- **`@object-ui/plugin-detail`: the `field:permission-facet-link` registry key.** FROM a registered field widget (the read-only facet summary with a "Design in Studio" link, also special-cased in the detail section and inline edit) TO unregistered. A field still carrying `widget: 'permission-facet-link'` renders with its declared type, like any unregistered widget. Fix: drop the `widget` hint, or register an own widget under that key.
|
|
149
|
+
- **`@object-ui/cli`: `objectui check`'s known schema types.** FROM `field:permission-facet-link` known TO unknown, so `objectui check` reports a node of that type. Fix: as above.
|
|
150
|
+
|
|
151
|
+
**Behaviour changes, not narrowings:**
|
|
152
|
+
|
|
153
|
+
- **`@object-ui/data-objectstack`:** `getObjectSchema` no longer stamps `widget: 'permission-facet-link'` onto the six `sys_permission_set` facet fields (that object is no longer served).
|
|
154
|
+
- **`@object-ui/app-shell`:** the `sys_permission_set` record page no longer appends the assigned-users panel. The panel itself, `AssignedUsersSection`, stays on the permission-set metadata page.
|
|
155
|
+
- **`@object-ui/app-shell`: a permission set created from the Setup catalog is created live.** "New" on the permission-set page used to save a draft, while the set's editor edits the live set and reads no draft: the editor opened an empty set without the label, its Save wrote the grants live, and the draft stayed pending, so publishing it from Home replaced the grants with the empty create body. Outside a package binding the create now writes live (with the same first-write pin); under `?package=` it stays a package draft.
|
|
156
|
+
|
|
157
|
+
**Widenings:**
|
|
158
|
+
|
|
159
|
+
- **`@object-ui/core`:** the data source `recordDelete.run` is injected with needs only `delete` (it no longer calls `findOne`).
|
|
160
|
+
- **`@object-ui/app-shell`:** the metadata-resource registry entry gains an optional `createLive`, which a type sets when its edit page writes the live item.
|
|
161
|
+
|
|
162
|
+
Internal, not reachable from a published entry: the `PermissionFacetLink` component (never exported from `@object-ui/plugin-detail`), `RecordPermissionAssignmentsRenderer` (never exported from `@object-ui/app-shell`), and `ObjectStackAdapter`'s private `applyFieldWidgetOverrides`.
|
|
163
|
+
|
|
164
|
+
### Patch Changes
|
|
165
|
+
|
|
166
|
+
- 4590363: A record open no longer sends the same record read twice at once, and asks each explain question once, with concurrent callers sharing the request (objectui#11699).
|
|
167
|
+
|
|
168
|
+
A record page open sent `GET /api/v1/data/OBJ/ID` twice at the same moment and `POST /api/v1/security/explain` four times: the `update` and the `delete` question, each asked twice. Both reads came from the details block, whose load effect runs again while its first read is still on the wire. Each question was asked by the page header and by the details block, which mounts while the header's answer is still pending. The record page's own `$expand` read of the record, which it sends again when the object definition changes identity, is not changed here.
|
|
169
|
+
|
|
170
|
+
- **`ObjectStackAdapter.findOne` shares an in-flight read**, the way `find` already did. Calls for the same resource, id and params that arrive while a read is pending get that read's answer. The entry is dropped when the read settles, so a later call reads again: this is not a response cache. A failed read reaches every caller that shared it and is not remembered. A write through the adapter (`create`, `update`, `delete`, the bulk and batch writes) drops the pending `findOne` reads of the resource it wrote, so a record read asked after a save never gets an answer sent before it.
|
|
171
|
+
- **The record-level edit and delete probe (`useRecordEditable`) shares an unanswered question.** Mounts asking the same question (same principal, object, record and operation) while it is pending wait on one request. A question whose record changes while it is pending is not shared, so the re-ask is a fresh request. No pending question is shared across a change of the signed-in principal.
|
|
172
|
+
|
|
173
|
+
Nothing is added to either package entry: no export, prop, type member or language-pack key. `findOne` keeps its signature.
|
|
174
|
+
- 4a9fe31: Studio shows a refused save as a sentence the author can act on, with the technical detail behind a "Details" disclosure (objectui#11785).
|
|
175
|
+
|
|
176
|
+
- **Studio's save strips.** The Data, Automations and Interfaces pages used to print the refusal verbatim: the write guard's developer prose, or the server's raw issue paths such as `nodes.2.config.title — …`. Each strip now shows one sentence that names the input the refusal is about, in the terms the editor shows. On the Data page that is the field's label. On the Automations page it is the step's label and the inspector input, so a refusal at `nodes.2.config.title` reads "Check Title on the step “Notify approver” — …". On the Interfaces page it is the navigation item and its input. A "Show me" button opens that field, step or navigation item. The raw text stays under a closed "Details" disclosure, unchanged. A path Studio cannot place on the open page keeps its line there, and the sentence says the draft was refused.
|
|
177
|
+
- **A Picklist with no options.** Switching a field to Picklist before it has an option now says "Changes not saved: the field “Status” needs at least one option…", instead of the guard's message.
|
|
178
|
+
- **The object write guard's message names no code.** `assertObjectMetadataWritable` no longer prints the door's class name, a package name or tracker ids. Its message is the same at every door, so the other surfaces that show it as their banner read the field, its type and the remedy. Its signature, error type and exports are unchanged.
|
|
179
|
+
|
|
180
|
+
`formatMetadataError` is unchanged, so every other surface that calls it prints what it printed before. Nothing is added to either package entry: no export, prop, type member or language-pack key. The new sentences are rows in the metadata-admin designer's own string tables (en and zh).
|
|
181
|
+
- 6d8bf0c: Opening a Studio package no longer sends the same read several times at once (objectui#11797).
|
|
182
|
+
|
|
183
|
+
Several readers mount together on Studio entry and each one used to send its own request for the same answer. A caller that arrives while an identical request is still pending now waits for that request instead of sending another:
|
|
184
|
+
|
|
185
|
+
- **`@object-ui/app-shell`:** the package list (`GET /api/v1/packages`, read by the Studio package switcher, the read-only gate and the object-name namespace lookup) and the pending-drafts count (`GET /api/v1/meta/_drafts`, read by the Studio top bar and the chat bar).
|
|
186
|
+
- **`@object-ui/data-objectstack`:** `MetadataClient.listTypes`, `list`, `listDrafts`, `get` and `getDraft`. Requests are shared between clients built on the same `fetch`, so the per-component clients the console creates share them too. Each caller, the first one included, still gets an answer object that no other caller holds.
|
|
187
|
+
|
|
188
|
+
This is not a response cache. Once a request settles, the next call goes to the server again. A failed request is shared only by the callers already waiting, and the next call retries. A write the reader can see drops any read still pending, so a read made after the write never gets the answer of a request sent before it. For the metadata client that is any save, publish or reset through a client on the same `fetch`. For the app-shell reads it is a package duplicate, the `objectui:packages-changed` announcement and the publish refresh pulse.
|
|
189
|
+
|
|
190
|
+
No exported function, method signature, option or type changes.
|
|
191
|
+
- 5dff027: Studio stops asking questions whose expected answer is an error, so the browser console no longer fills with red lines that hide real failures (objectui#11799).
|
|
192
|
+
|
|
193
|
+
- **`MetadataClient.getDraft()` reads the drafts ledger first.** It reads `GET /meta/_drafts` through the client's shared read, and sends `GET /meta/:type/:name?state=draft` only when the ledger lists the name. An item with no draft no longer logs a 404. The method still resolves `null` when there is no draft and the draft envelope when there is one. A draft saved a moment ago is found, because a write drops the ledger read that was pending when it landed. A ledger the caller cannot read (403, 501, a network fault) counts as unknown, and the item read is sent as before.
|
|
194
|
+
- **Studio reads an item's published baseline only when it uses it.** The Interfaces pillar's leaves and the Automations pillar's flows read `GET /meta/:type/:name/layers` only for an item with no pending draft, and the Interfaces pillar reads none for an app found only in the drafts ledger. An item that was never saved always has a draft, so it no longer logs a 404 for `/layers`. Opening a leaf or a flow now waits for the ledger before it reads the item, one round trip more than the parallel reads it replaces. The Data pillar still reads `/layers` for an object that was never published: that answer is how it tells it has no records table yet.
|
|
195
|
+
- **The AI usage indicator asks nothing while AI is off.** It reads `GET /ai/usage` only while the agent catalog at its base lists an agent, the signal the console gates every AI entry point on. On an open-edition server, Studio's copilot dock no longer logs a 501 for it each time it mounts.
|
|
196
|
+
|
|
197
|
+
No REST answer, export, prop or type member changes.
|
|
198
|
+
- d48071e: A console page load reads `sys_user_preference` once, not once per preference key (objectui#12187).
|
|
199
|
+
|
|
200
|
+
- **The user-state adapters share one read per load.** `createObjectStackUserStateAdapter`'s `load()` now joins an open read for its data source, resource and user. Every load of that scope issued before the next microtask adds its key, the read is sent once, and each adapter takes its own row from the answer. The console's favorites, recent items and flow palette recents used to make three requests, one per key; they now make one, `user_id = USER and key in (the three keys)`, with `$top` set to the number of keys. The object's unique index on `(user_id, key)` bounds the answer to one row per key.
|
|
201
|
+
- **A read that collects one key is unchanged.** It sends the same per-key query, `$top: 1`, as before.
|
|
202
|
+
- **Nothing is kept past the request.** The read closes when it is sent. A later `load()`, such as a new adapter after a language or account switch, asks the server again. A different user or data source never joins another's read.
|
|
203
|
+
- **Writes are unchanged.** `save()` still finds and writes its own row, and the stored rows and their shape are the same.
|
|
204
|
+
|
|
205
|
+
No export, option or type changes.
|
|
206
|
+
- 40cb13b: The connection state follows the requests the console already makes, so the header chip now shows a mid-session outage (objectui#12295).
|
|
207
|
+
|
|
208
|
+
Until now the adapter set its connection state only while making the first connection. Once that had succeeded, no failed request could change it, so a backend that stopped mid-session left stale rows on screen and the header chip hidden.
|
|
209
|
+
|
|
210
|
+
- **A request with no HTTP answer** (refused, reset, unreachable) moves the state to `reconnecting`. Once the adapter's own retry window is spent, it moves to `error`. The window is the same `autoReconnect` / `maxReconnectAttempts` budget a failed first connection gets, counted in unanswered requests. The state stays there until a request is answered, and the header chip stays visible the whole time.
|
|
211
|
+
- **The next answered request** moves it back to `connected`, and the chip hides again after its brief "Connected" flash.
|
|
212
|
+
- **An answered HTTP error does not move it.** A 4xx or 5xx means the server is reachable. A request its caller aborted does not move it either.
|
|
213
|
+
|
|
214
|
+
The adapter adds no timer, no heartbeat and no request of its own: the state changes when a request the host makes settles. Every request goes through the adapter's one fetch, including those made through `getClient()`, except `uploadFile` / `uploadFiles`: those two call the global `fetch`, so an upload's outcome does not move the state. The fetch hands its response or rejection to the caller unchanged. Nothing is added to the package entry: no export, option, type member or language-pack key.
|
|
215
|
+
- 068a540: The console's Setup › Apps page reports what the server did with a write (objectui#12300). Before, a Delete and a Disable could report success for a write that changed nothing.
|
|
216
|
+
|
|
217
|
+
- **Delete.** When the reset door answers `reset: false` (there was no customization row to remove, so the app already is what its package ships), the page shows a neutral notice with the server's own sentence and never says "deleted". The row stays, because the server still serves it.
|
|
218
|
+
- **Disable, Set as default and the bulk Enable / Disable.** A save of an app that a package owns now names that package (`?package=`, read off the served `_packageId`), so it lands on the row the server serves. A package-less app is saved with no scope, as before.
|
|
219
|
+
- After every write, whatever it answered, the page re-reads the app list and shows the served state.
|
|
220
|
+
|
|
221
|
+
`ObjectStackAdapter` forwards every argument of `getClient().meta.saveItem`. Its save-advisory wrapper used to pass on only `type`, `name` and `item`, so the SDK's options bag (`packageId`, `force`, `mode`, `ifMatch`) was dropped before it reached the wire, for every caller of the adapter's client.
|
|
222
|
+
|
|
223
|
+
Two new `appManagement.toast` keys, `nothingToDelete` and `alreadyAtDefault`, ship in all ten language packs.
|
|
224
|
+
- 866763b: A failed first `connect()` with `autoReconnect` now retries discovery and settles (objectui#12318). The retry used to call `connect()` from inside the attempt it was retrying. `connect()` hands every caller the attempt in flight, so the retry waited on itself: one discovery read, a state stuck at `reconnecting`, and a promise that never settled. A console opened while its backend was down never finished starting, even after the backend came back.
|
|
225
|
+
|
|
226
|
+
- **Each retry is a new discovery read**, after the existing backoff (`reconnectDelay` × 2^(n-1)), up to `maxReconnectAttempts` times.
|
|
227
|
+
- **`connect()` settles.** It resolves once discovery answers, and the state ends `connected`. Once the retries are spent it rejects with the last attempt's `ConnectionError`, and the state ends `error`.
|
|
228
|
+
- **Concurrent callers still share one attempt**, including a caller that arrives during the backoff.
|
|
229
|
+
|
|
230
|
+
Nothing is added to the package entry: no export, option or type member. The state sequence of a failed attempt is unchanged: `error`, then `reconnecting` while retries remain.
|
|
231
|
+
- d12bc5f: A console reset, delete or draft discard names the package whose row it removes (objectui#12335, the consumer half of objectstack#23165).
|
|
232
|
+
|
|
233
|
+
Since `@objectstack/rest` 18.0.0-next.1 the metadata delete door reads `?package=`. A delete that names no package addresses the one package that holds the item; when two or more packages hold it the door refuses `409 RESOURCE_CONFLICT`, deletes nothing, and names `?package=` as the remedy. The console could not send that remedy.
|
|
234
|
+
|
|
235
|
+
- **`MetadataClient.reset()`** now sends `?package=` when `options.packageId` is set, beside `?state=draft` for a draft discard, with the spelling `save()` and `publish()` use. Without a binding the parameter is omitted, never sent empty. Its options type already declared `packageId` (inherited by `MetadataDeleteOptions`); `reset()` used to drop it. No exported type changes.
|
|
236
|
+
- **The metadata editor** (`MetadataResourceEditPage`): Reset/Delete and "Discard draft" state the package the editor's save and publish already state, read from `?package=` on the editor URL (the `all` scope names none). An editor with no package sends none, and a refusal is shown on the page's error line in the server's words.
|
|
237
|
+
- **The Objects page** (`MetadataObjectsPage`, `@object-ui/plugin-designer`): deleting an object states the package the listed row is served from (its `_packageId`). A row no package owns, including one served with the `sys_metadata` sentinel, sends none, and a refusal is shown in the page's error panel.
|
|
238
|
+
- **Unchanged:** the runtime draft bar's Discard and the adapter's `deleteView` hold no package binding, so they keep the package-less delete. Their refusals already reach the caller: the draft bar shows the server's message in its error toast, and `deleteView` throws.
|
|
239
|
+
- cfbca7f: `deleteView` now shows the server's reason when the published half of a view delete is refused (objectui#12416).
|
|
240
|
+
|
|
241
|
+
`ObjectStackAdapter.deleteView` deletes a view's draft home, then its published home. When the published delete failed, the adapter threw its own sentence ("… is NOT fully removed … retry the delete") and kept the server's error only on `cause`. `formatMetadataError(err)` reads the error's issues or its own message, so the console's Delete view toast showed the adapter's sentence and hid the server's reason. For the `409 RESOURCE_CONFLICT` a package-less delete of a view two packages hold gets since `@objectstack/rest` 18.0.0-next.1, that reason is the `?package=` remedy, and the toast told the user to retry a delete that cannot succeed.
|
|
242
|
+
|
|
243
|
+
The thrown error's message is now the adapter's sentence followed, on the next line, by the server's reason as `formatMetadataError` renders it (its issue lines, or else the served message unchanged). The sentence still says the view is not fully removed and the published row still serves it. It says "retry the delete" only when the failure is not a 4xx refusal. `cause` and `outcome` are unchanged. A refusal of the draft half is thrown as served, as before.
|
|
244
|
+
- 35ef750: `aggregate()` no longer reads a served `null` measure as a missing one. The analytics answer's `null` (the max or min over rows whose field is all NULL) now reaches the caller as `null` from the single analytics request; only a measure key that is absent from the answer falls back to re-reading the rows through `find()`. This removes a second, row-reading request from every widget bound to such an aggregate.
|
|
245
|
+
- 65fe4c2: One page open asks the server for one object document once (objectui#13044). The metadata provider's by-name object read now goes through the adapter's `getObjectSchema`, so the page's object scope and the record form, `useSettledSchema` and the field widgets share one `GET /meta/object/NAME`. The published view list (`GET /meta/view`) is shared while a request is on the wire: the provider's eager view read, `listViews` and `listViewOverrides` join it instead of each sending its own, and nothing is stored, so a later read, a refresh or a view write still asks the server. An organization or session switch clears the adapter's metadata cache. Each reader still gets its own copy, in the shape it got before; the draft-preview path is unchanged.
|
|
246
|
+
- cd4623d: One object-page open reads the published view list (`GET /meta/view`) once (objectui#13057). The adapter now stores the list its client read and answers `listViews` and `listViewOverrides` from it, so the provider's eager view read serves the page's two readers. The client read itself still always asks the server. Every path that should see a fresh list drops it first: a refresh of the object page, every view write (through the adapter, its client, or the console's `MetadataClient`, whose view saves, publishes, resets, rollbacks and package publishes now drop it), and an organization or session switch. The package sheet's *Publish app* now announces the publish like every other publish path, so open views, forms and navigation re-read what went live. The draft-preview list read is unchanged and never fills the stored list.
|
|
247
|
+
- 12ff256: `ConnectionState` is re-exported from `@object-ui/types` instead of being declared a second time here (objectui#6349, batch 10). `@object-ui/collaboration` declared the same five states for `useRealtimeSubscription`, so both packages now publish the one declaration under the same name. The union is unchanged, and so are `getConnectionState()`, `ConnectionStateEvent` and every import of `ConnectionState` from this package.
|
|
248
|
+
|
|
249
|
+
No runtime behaviour changes.
|
|
250
|
+
- 1b2d016: Doc comments only. `UserDataAdapter` now carries the contract notes `@object-ui/app-shell` kept on its own copy of the interface: implementations must be safe to call concurrently and should never throw, so the hosting provider can degrade to localStorage when the backend fails. `@object-ui/app-shell` now re-exports this declaration instead of declaring a second one (objectui#6349, batch 9). The type and its members are unchanged.
|
|
251
|
+
|
|
252
|
+
No runtime behaviour changes.
|
|
253
|
+
- Updated dependencies [18d7b48]
|
|
254
|
+
- Updated dependencies [172acc3]
|
|
255
|
+
- Updated dependencies [f0496bd]
|
|
256
|
+
- Updated dependencies [c4c506b]
|
|
257
|
+
- Updated dependencies [9db9ff3]
|
|
258
|
+
- Updated dependencies [b10c68e]
|
|
259
|
+
- Updated dependencies [bdc9049]
|
|
260
|
+
- Updated dependencies [2e818d0]
|
|
261
|
+
- Updated dependencies [8b14aec]
|
|
262
|
+
- Updated dependencies [2abec3a]
|
|
263
|
+
- Updated dependencies [9dfaca6]
|
|
264
|
+
- Updated dependencies [e6dcd85]
|
|
265
|
+
- Updated dependencies [96fcf30]
|
|
266
|
+
- Updated dependencies [d50f724]
|
|
267
|
+
- Updated dependencies [17acfbb]
|
|
268
|
+
- Updated dependencies [89cc738]
|
|
269
|
+
- Updated dependencies [c0862c1]
|
|
270
|
+
- Updated dependencies [c0862c1]
|
|
271
|
+
- Updated dependencies [c0862c1]
|
|
272
|
+
- Updated dependencies [c0862c1]
|
|
273
|
+
- Updated dependencies [4c0de52]
|
|
274
|
+
- Updated dependencies [a80fef7]
|
|
275
|
+
- Updated dependencies [f34e877]
|
|
276
|
+
- Updated dependencies [f34e877]
|
|
277
|
+
- Updated dependencies [aaba865]
|
|
278
|
+
- Updated dependencies [45d5853]
|
|
279
|
+
- Updated dependencies [2312bd9]
|
|
280
|
+
- Updated dependencies [c5406c2]
|
|
281
|
+
- Updated dependencies [d99b731]
|
|
282
|
+
- Updated dependencies [2a48bd4]
|
|
283
|
+
- Updated dependencies [c7b30bd]
|
|
284
|
+
- Updated dependencies [69bca0a]
|
|
285
|
+
- Updated dependencies [023f00d]
|
|
286
|
+
- Updated dependencies [158c75d]
|
|
287
|
+
- Updated dependencies [7525045]
|
|
288
|
+
- Updated dependencies [5af42db]
|
|
289
|
+
- Updated dependencies [5f75cfa]
|
|
290
|
+
- Updated dependencies [c0c2f79]
|
|
291
|
+
- Updated dependencies [0665c3b]
|
|
292
|
+
- Updated dependencies [d2500d8]
|
|
293
|
+
- Updated dependencies [b231607]
|
|
294
|
+
- Updated dependencies [466a219]
|
|
295
|
+
- Updated dependencies [6fb281d]
|
|
296
|
+
- Updated dependencies [334f0cf]
|
|
297
|
+
- Updated dependencies [c08c933]
|
|
298
|
+
- Updated dependencies [c4dd707]
|
|
299
|
+
- Updated dependencies [923504f]
|
|
300
|
+
- Updated dependencies [10aba37]
|
|
301
|
+
- Updated dependencies [5d3f437]
|
|
302
|
+
- Updated dependencies [7af0ab3]
|
|
303
|
+
- Updated dependencies [fd4872f]
|
|
304
|
+
- Updated dependencies [f0532d8]
|
|
305
|
+
- Updated dependencies [3092aca]
|
|
306
|
+
- Updated dependencies [82e5f53]
|
|
307
|
+
- Updated dependencies [d81e460]
|
|
308
|
+
- Updated dependencies [8106db1]
|
|
309
|
+
- Updated dependencies [093d55b]
|
|
310
|
+
- Updated dependencies [a8051d6]
|
|
311
|
+
- Updated dependencies [524136e]
|
|
312
|
+
- Updated dependencies [6f3299f]
|
|
313
|
+
- Updated dependencies [f2b17c6]
|
|
314
|
+
- Updated dependencies [52d6194]
|
|
315
|
+
- Updated dependencies [23bf859]
|
|
316
|
+
- Updated dependencies [1c72d77]
|
|
317
|
+
- Updated dependencies [2b4d3f1]
|
|
318
|
+
- Updated dependencies [d75f212]
|
|
319
|
+
- Updated dependencies [ff9df88]
|
|
320
|
+
- Updated dependencies [a2225f9]
|
|
321
|
+
- Updated dependencies [21f9577]
|
|
322
|
+
- Updated dependencies [3eb3ec1]
|
|
323
|
+
- Updated dependencies [619e3f7]
|
|
324
|
+
- Updated dependencies [f62b6ff]
|
|
325
|
+
- Updated dependencies [d66d7e8]
|
|
326
|
+
- Updated dependencies [15d8ca6]
|
|
327
|
+
- Updated dependencies [378896d]
|
|
328
|
+
- Updated dependencies [4839f5c]
|
|
329
|
+
- Updated dependencies [c999d4c]
|
|
330
|
+
- Updated dependencies [260ef71]
|
|
331
|
+
- Updated dependencies [26323c4]
|
|
332
|
+
- Updated dependencies [c219165]
|
|
333
|
+
- Updated dependencies [fb501bc]
|
|
334
|
+
- Updated dependencies [30083da]
|
|
335
|
+
- Updated dependencies [025130c]
|
|
336
|
+
- Updated dependencies [8aa8e3e]
|
|
337
|
+
- Updated dependencies [2493e85]
|
|
338
|
+
- Updated dependencies [a928aaa]
|
|
339
|
+
- Updated dependencies [61ed5ad]
|
|
340
|
+
- Updated dependencies [1b94d50]
|
|
341
|
+
- Updated dependencies [fb85637]
|
|
342
|
+
- Updated dependencies [9f1dd79]
|
|
343
|
+
- Updated dependencies [df71568]
|
|
344
|
+
- Updated dependencies [cbac9d7]
|
|
345
|
+
- Updated dependencies [616df65]
|
|
346
|
+
- Updated dependencies [daeb77f]
|
|
347
|
+
- Updated dependencies [eb4552e]
|
|
348
|
+
- Updated dependencies [3c3115e]
|
|
349
|
+
- Updated dependencies [55e90fd]
|
|
350
|
+
- Updated dependencies [9b75509]
|
|
351
|
+
- Updated dependencies [b13ea3c]
|
|
352
|
+
- Updated dependencies [d7e9e9a]
|
|
353
|
+
- Updated dependencies [cf62edf]
|
|
354
|
+
- Updated dependencies [0253416]
|
|
355
|
+
- Updated dependencies [5d77c09]
|
|
356
|
+
- Updated dependencies [3fd8625]
|
|
357
|
+
- Updated dependencies [1473757]
|
|
358
|
+
- Updated dependencies [d73d987]
|
|
359
|
+
- Updated dependencies [12ff256]
|
|
360
|
+
- Updated dependencies [26b0bd2]
|
|
361
|
+
- Updated dependencies [d328698]
|
|
362
|
+
- Updated dependencies [d328698]
|
|
363
|
+
- Updated dependencies [b4e0787]
|
|
364
|
+
- Updated dependencies [088e0f7]
|
|
365
|
+
- Updated dependencies [78b07f4]
|
|
366
|
+
- Updated dependencies [b403bb3]
|
|
367
|
+
- Updated dependencies [12dc7b4]
|
|
368
|
+
- @object-ui/types@18.0.0
|
|
369
|
+
- @object-ui/core@18.0.0
|
|
370
|
+
|
|
3
371
|
## 17.7.0
|
|
4
372
|
|
|
5
373
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ const dataSource = createObjectStackAdapter({
|
|
|
67
67
|
## Features
|
|
68
68
|
|
|
69
69
|
- ✅ **CRUD Operations**: Implements `find`, `findOne`, `create`, `update`, `delete`.
|
|
70
|
+
- ✅ **Shared In-Flight Reads**: Concurrent `find` calls with the same resource and params, and concurrent `findOne` calls with the same resource, id and params, share one request. The entry is dropped when the read settles, so a later call reads again (this is not a response cache), and a failed read is not remembered. A write through the adapter drops the pending `findOne` reads of the resource it wrote, so a read asked after a save never gets an answer sent before it. `MetadataClient` shares its `listTypes`, `list`, `listDrafts`, `get` and `getDraft` reads the same way. Calls with the same method, URL and headers on clients built with the same `fetch` share one request, and every caller gets an answer no other caller holds. A save, publish or reset through any of those clients drops the reads still pending.
|
|
70
71
|
- ✅ **Metadata Caching**: Automatic LRU caching of schema metadata with TTL expiration.
|
|
71
72
|
- ✅ **Metadata Fetching**: Implements `getObjectSchema` to power auto-generated forms and grids.
|
|
72
73
|
- ✅ **Query Translation**: Converts Object UI's OData-like query parameters to ObjectStack's native query format.
|
|
@@ -499,6 +500,26 @@ unsubscribe();
|
|
|
499
500
|
- `reconnecting` - Attempting to reconnect after failure
|
|
500
501
|
- `error` - Connection failed (check event.error for details)
|
|
501
502
|
|
|
503
|
+
### After the First Connect
|
|
504
|
+
|
|
505
|
+
Once the first connection has succeeded, the state follows the outcome of the
|
|
506
|
+
requests your app already makes. The adapter adds no timer, heartbeat or
|
|
507
|
+
request of its own:
|
|
508
|
+
|
|
509
|
+
- **A request with no HTTP answer** (refused, reset, unreachable) moves the state
|
|
510
|
+
to `reconnecting`. When the retry window is spent, it moves to `error`. The
|
|
511
|
+
window is the same `autoReconnect` / `maxReconnectAttempts` budget as below,
|
|
512
|
+
counted in unanswered requests. With `autoReconnect: false` the first
|
|
513
|
+
unanswered request moves the state straight to `error`.
|
|
514
|
+
- **The next answered request** moves it back to `connected`.
|
|
515
|
+
- **An answered HTTP error does not move it.** A 4xx or 5xx means the server is
|
|
516
|
+
reachable. A request its caller aborted does not move it either.
|
|
517
|
+
|
|
518
|
+
Every request goes through the adapter's one fetch, including those made
|
|
519
|
+
through `getClient()`, except `uploadFile` / `uploadFiles`. Those two call the
|
|
520
|
+
global `fetch`, so an upload's outcome does not move the state. The response or
|
|
521
|
+
rejection reaches your code unchanged.
|
|
522
|
+
|
|
502
523
|
### Auto-Reconnect
|
|
503
524
|
|
|
504
525
|
The adapter automatically attempts to reconnect on connection failures:
|
|
@@ -780,7 +801,7 @@ await client.save('object', 'account', {
|
|
|
780
801
|
name: 'account',
|
|
781
802
|
fields: { owner: { type: 'lookup', label: 'Owner' } },
|
|
782
803
|
});
|
|
783
|
-
// throws:
|
|
804
|
+
// throws: The object was not saved: the field
|
|
784
805
|
// `owner` is a `lookup` and carries no `reference` key at all ...
|
|
785
806
|
```
|
|
786
807
|
|
|
@@ -807,7 +828,7 @@ await client.save('object', 'deal', {
|
|
|
807
828
|
name: 'deal',
|
|
808
829
|
fields: { stage: { type: 'select', label: 'Stage', options: [] } },
|
|
809
830
|
});
|
|
810
|
-
// throws:
|
|
831
|
+
// throws: The object was not saved: the field
|
|
811
832
|
// `stage` is a `select` with no options ...
|
|
812
833
|
```
|
|
813
834
|
|
|
@@ -870,6 +891,123 @@ newlines (`whitespace-pre-line`). `formatMetadataIssue(issue)` is the same
|
|
|
870
891
|
one-line format for a single issue, for code that lists several failures, such
|
|
871
892
|
as a publish response's `failed[]`.
|
|
872
893
|
|
|
894
|
+
## Asking Why a Metadata Item Is Not Listed
|
|
895
|
+
|
|
896
|
+
The server filters metadata lists per caller. An app, a dashboard or a list view
|
|
897
|
+
the caller may not open is left out of their list, exactly like a name that does
|
|
898
|
+
not exist. `probeMetaItemAccess(type, name)` asks the by-name route which one it
|
|
899
|
+
is:
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
import { ObjectStackAdapter, type MetaItemAccess } from '@object-ui/data-objectstack';
|
|
903
|
+
|
|
904
|
+
const adapter = new ObjectStackAdapter({ baseUrl: '/api/v1' });
|
|
905
|
+
|
|
906
|
+
const access: MetaItemAccess = await adapter.probeMetaItemAccess('dashboard', 'legal_board');
|
|
907
|
+
// { verdict: 'denied', message: "You do not have permission to open the 'legal_board' dashboard." }
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
`verdict` is `granted`, `denied`, `not_found`, `unreachable` (no answer was
|
|
911
|
+
obtained) or `unknown` (nothing was asked). Only `denied`, read from the
|
|
912
|
+
`PERMISSION_DENIED` code and never from the status, is a permission refusal,
|
|
913
|
+
and only then does `message` carry the server's sentence. The probe never
|
|
914
|
+
throws and caches nothing. `probeAppAccess(name)` is the same probe for an app,
|
|
915
|
+
returning the verdict alone.
|
|
916
|
+
|
|
917
|
+
## Saving an Object You Read From the Server
|
|
918
|
+
|
|
919
|
+
A select field can name a shared picklist (`picklist: 'industry'`) instead of
|
|
920
|
+
carrying its own options. The server *serves* such a field with both keys:
|
|
921
|
+
`picklist`, and the `options` it resolved from the list. The server's authoring
|
|
922
|
+
check refuses the two together, with `422 INVALID_METADATA` at
|
|
923
|
+
`fields.FIELD.options`, and that refuses the whole object. So code that reads an
|
|
924
|
+
object, edits it, and saves the whole document back must drop those resolved
|
|
925
|
+
options first. `dropServedPicklistOptions` does that:
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
import { MetadataClient, dropServedPicklistOptions } from '@object-ui/data-objectstack';
|
|
929
|
+
|
|
930
|
+
const client = new MetadataClient({ baseUrl: '/api/v1' });
|
|
931
|
+
|
|
932
|
+
const account = await client.get<Record<string, unknown>>('object', 'account');
|
|
933
|
+
const edited = { ...account, label: 'Customer' };
|
|
934
|
+
await client.save('object', 'account', dropServedPicklistOptions(edited));
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
It removes `options` from every field that names a picklist, and touches
|
|
938
|
+
nothing else: a field without `picklist` keeps its inline `options`, and every
|
|
939
|
+
other key is kept. It never mutates its input, and it returns the same object
|
|
940
|
+
when there is nothing to remove.
|
|
941
|
+
|
|
942
|
+
Apply it only to a body built from a served read. `MetadataClient.save` does
|
|
943
|
+
not apply it for you: the client cannot tell a served copy from an author who
|
|
944
|
+
wrote `picklist` and `options` together, and that second case should stay the
|
|
945
|
+
server's refusal, which says which key to delete.
|
|
946
|
+
|
|
947
|
+
## Saving Only Over the Version You Read
|
|
948
|
+
|
|
949
|
+
Two editors of one metadata item overwrite each other unless each save says
|
|
950
|
+
which stored row it was built on. `MetadataClient.save` takes that precondition
|
|
951
|
+
as one of two options, never both:
|
|
952
|
+
|
|
953
|
+
- `ifMatch: version`, sent as `If-Match`: save only over that row. The token is
|
|
954
|
+
the `version` the `/meta` door served for the same address (type, name,
|
|
955
|
+
`packageId`, and the draft or active lifecycle): from the item read
|
|
956
|
+
(`getDraft` hands back the envelope, `version` included), from a save
|
|
957
|
+
receipt, or as `currentVersion` on a `409 METADATA_CONFLICT` refusal.
|
|
958
|
+
- `ifNoneMatch: '*'`, sent as `If-None-Match: *`: save only where no row of that
|
|
959
|
+
lifecycle exists yet. A create sends it, and so does the first draft save of
|
|
960
|
+
an item with no pending draft, or one whose read served `version: null`.
|
|
961
|
+
|
|
962
|
+
```ts
|
|
963
|
+
import { MetadataClient } from '@object-ui/data-objectstack';
|
|
964
|
+
|
|
965
|
+
const client = new MetadataClient({ baseUrl: '/api/v1' });
|
|
966
|
+
|
|
967
|
+
const read = await client.getDraft<{ item: Record<string, unknown>; version?: string | null }>(
|
|
968
|
+
'object',
|
|
969
|
+
'account',
|
|
970
|
+
{ packageId: 'com.acme.crm' },
|
|
971
|
+
);
|
|
972
|
+
// No pending draft, or none at this address: the save creates the draft row.
|
|
973
|
+
// A server that serves no `version` at all leaves the save unpinned.
|
|
974
|
+
const pin =
|
|
975
|
+
read === null || read.version === null
|
|
976
|
+
? { ifNoneMatch: '*' as const }
|
|
977
|
+
: read.version
|
|
978
|
+
? { ifMatch: read.version }
|
|
979
|
+
: {};
|
|
980
|
+
await client.save('object', 'account', { name: 'account', label: 'Customer' }, {
|
|
981
|
+
mode: 'draft',
|
|
982
|
+
packageId: 'com.acme.crm',
|
|
983
|
+
...pin,
|
|
984
|
+
});
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
Read at the package the save writes: a read that names no package serves a
|
|
988
|
+
package-bound draft with `version: null`, which says nothing about a save that
|
|
989
|
+
names the package. A save that sets both options throws before any request;
|
|
990
|
+
the door refuses the pair with a `400`. A save whose pin no longer holds is
|
|
991
|
+
refused `409 METADATA_CONFLICT`, and nothing is written.
|
|
992
|
+
|
|
993
|
+
## Reading a Draft and Who Changed It
|
|
994
|
+
|
|
995
|
+
`MetadataClient.getDraft(type, name, { packageId })` answers the draft read's
|
|
996
|
+
wire envelope (`{ type, name, item, version }`, plus `draftChanges` when the
|
|
997
|
+
server recorded them), or `null` when nothing is pending. Read it through the
|
|
998
|
+
package's readers rather than by hand: `extractDraftBody` answers the body
|
|
999
|
+
with the read decorations removed; `readDraftChanges` answers the per-key
|
|
1000
|
+
provenance, one `{ path, by, actor, at }` per changed key path, or `null` when
|
|
1001
|
+
the key is absent, which means not recorded, never "nothing changed";
|
|
1002
|
+
`governingDraftChange(changes, path)` answers the entry that governs a key, the
|
|
1003
|
+
longest path that is the key or one of its ancestors, with `[]` standing for
|
|
1004
|
+
the whole body and segments compared whole; and `draftChangesUnder(changes,
|
|
1005
|
+
path)` answers the entries strictly under the key. A save names the surface it
|
|
1006
|
+
is made through with the `surface` option, `'studio' | 'api'`, sent once as
|
|
1007
|
+
`?surface=` and recorded as `by` on each key path a draft save changes; a save
|
|
1008
|
+
that omits it is recorded as `api`, and `copilot` is the server's own (its
|
|
1009
|
+
HTTP door refuses it), so this client cannot send it.
|
|
1010
|
+
|
|
873
1011
|
## User-Scoped State Adapter
|
|
874
1012
|
|
|
875
1013
|
In addition to the main `DataSource` adapter, this package ships
|
|
@@ -921,6 +1059,38 @@ If the backend doesn't yet expose `sys_user_preference`, every call
|
|
|
921
1059
|
[User-Scoped State Persistence](../../content/docs/guide/user-state-persistence.md)
|
|
922
1060
|
for the full design.
|
|
923
1061
|
|
|
1062
|
+
### Key families
|
|
1063
|
+
|
|
1064
|
+
Some state is one row per subject, and which subjects exist is only known
|
|
1065
|
+
from the rows: the console keeps each object's list state under
|
|
1066
|
+
`ui.list.<object>`. `createObjectStackUserPreferenceFamily` loads every row
|
|
1067
|
+
whose key starts with a prefix, and writes one key's row at a time:
|
|
1068
|
+
|
|
1069
|
+
```typescript
|
|
1070
|
+
import { createObjectStackUserPreferenceFamily, type ObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
1071
|
+
|
|
1072
|
+
declare const dataSource: ObjectStackAdapter;
|
|
1073
|
+
declare const user: { id: string };
|
|
1074
|
+
|
|
1075
|
+
const lists = createObjectStackUserPreferenceFamily<{ viewOrder?: string[] }>({
|
|
1076
|
+
dataSource,
|
|
1077
|
+
userId: user.id,
|
|
1078
|
+
prefix: 'ui.list.',
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
const rows = await lists.load(); // { 'ui.list.account': { viewOrder: [...] }, ... }
|
|
1082
|
+
await lists.save('ui.list.account', { viewOrder: ['account.mine', 'account.all'] });
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
A family's `load()` joins the same shared read as the per-key adapters' loads
|
|
1086
|
+
issued in the same tick, so the console's slots and its families are still
|
|
1087
|
+
ONE request per load. That read asks for its keys or the prefix, and its
|
|
1088
|
+
`$top` is the key count plus `PREFERENCE_FAMILY_ROW_LIMIT` rows per prefix.
|
|
1089
|
+
When an answer fills that `$top`, a key whose row is missing from it reads
|
|
1090
|
+
its own row. Only rows whose value is a JSON object are returned. A failed
|
|
1091
|
+
read loads as `{}`, and a failed write is reported to `onError` and
|
|
1092
|
+
swallowed. A key outside the family is refused and nothing is written.
|
|
1093
|
+
|
|
924
1094
|
## API Reference
|
|
925
1095
|
|
|
926
1096
|
### ObjectStackAdapter
|