@object-ui/data-objectstack 16.0.0 → 16.1.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 CHANGED
@@ -1,5 +1,204 @@
1
1
  # @object-ui/data-objectstack
2
2
 
3
+ ## 16.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8c1e415: feat(data-objectstack): gate the non-atomic batch fallback on the discovery `transactionalBatch` capability (#2693)
8
+
9
+ `ObjectStackAdapter.batchTransaction` now negotiates atomic cross-object batch
10
+ **declaratively** instead of only probing at runtime. At `connect()` the adapter
11
+ reads `capabilities.transactionalBatch` from `GET /api/v1/discovery`
12
+ (framework #3298 — `declared === enforced`; the server advertises `true` only
13
+ when the `/batch` route is mounted _and_ the runtime engine can honour a
14
+ transaction):
15
+
16
+ - **Declared `true`** — the adapter TRUSTS server atomicity: it calls `/batch`
17
+ and surfaces any failure (including `404`/`405`/`501`) as a real error. No
18
+ runtime probe, no non-atomic client-side compensation.
19
+ - **Declared `false`, or absent** (backend predates #3298) — the legacy path is
20
+ unchanged: probe `/batch` and, on `404`/`405`/`501`, fall back to the
21
+ non-atomic `emulateBatchTransaction`. Keeping this avoids regressing older
22
+ backends from "saves, less safe" to "no save path" (#2679 compat constraint).
23
+
24
+ Both the hierarchical wire shape (`{ transactionalBatch: { enabled: true } }`)
25
+ and the flat form the client SDK normalizes to (`{ transactionalBatch: true }`)
26
+ are accepted. `@object-ui/core`'s generic `emulateBatchTransaction` /
27
+ `runBatchTransaction` are untouched and remain the fallback for adapters with no
28
+ server-side transaction (`ValueDataSource`, `MockDataSource`, …).
29
+
30
+ Docs: the adapter README and the data-source guide now document the capability
31
+ table and the minimum-backend note — atomic cross-object saves are guaranteed
32
+ only against backends advertising the capability (framework #3298 / #1604).
33
+
34
+ Picks up #2679 acceptance item 4; unblocked by framework#3298 (merged).
35
+
36
+ - 62b9ab5: feat(data): unify master-detail saves behind `DataSource.batchTransaction`, isolate the non-atomic fallback in the adapter (#2679)
37
+
38
+ Master-detail saves (`MasterDetailForm`, `LineItemsPanel`) now always persist
39
+ through `dataSource.batchTransaction(operations)` — one ordered cross-object
40
+ operation list, with `{ $ref: <op index> }` linking a child to a parent created
41
+ in the same batch. The form no longer contains any client-side orchestration or
42
+ best-effort compensation-delete; that atomicity anti-pattern is gone from the UI
43
+ layer (framework #1604 / framework ADR-0034 item 4).
44
+
45
+ - **`@object-ui/types`** — `batchTransaction?` is now a first-class (optional)
46
+ method on the `DataSource` contract, typed via `BatchTransactionOperation` /
47
+ `BatchRef`. Replaces the previous `(dataSource as any).batchTransaction`
48
+ method-sniffing.
49
+ - **`@object-ui/core`** — new `emulateBatchTransaction(dataSource, operations)`
50
+ (sequential writes, `$ref` resolution, best-effort reverse-order compensation)
51
+ and `runBatchTransaction(dataSource, operations)` (prefers the adapter's method,
52
+ emulates otherwise). `ApiDataSource` / `ValueDataSource` implement
53
+ `batchTransaction` via the emulation.
54
+ - **`@object-ui/data-objectstack`** — `ObjectStackAdapter.batchTransaction` uses
55
+ the server's atomic `POST /api/v1/batch`, prefers the typed
56
+ `client.data.batchTransaction` SDK method when the installed client exposes it,
57
+ and degrades to the client-side emulation ONLY when the endpoint is missing
58
+ (404/405) or the runtime can't do transactions (501). Real errors (400/401/403/
59
+ 409/500) still surface. This is the isolated, tested home of the non-atomic
60
+ fallback.
61
+ - **`@object-ui/plugin-form`** — removed `applyDetail` / `createMany` /
62
+ `ApplyDetailResult` from `masterDetailTx.ts`; `MasterDetailForm` and
63
+ `LineItemsPanel` build ops and call `runBatchTransaction`. `LineItemsPanel`
64
+ saves are now atomic on a capable backend, with the rollup folded into the same
65
+ batch.
66
+
67
+ No behavior change on a current ObjectStack backend (it has `/api/v1/batch`);
68
+ older/limited backends keep a working — now clearly non-atomic — save path.
69
+
70
+ ### Patch Changes
71
+
72
+ - 8b8b744: chore(deps): align `@objectstack/formula` / `lint` / `client` to `^15.1.1`
73
+
74
+ These three were still pinned to `^14.6.0` while `@objectstack/spec` was already
75
+ `^15.1.1` — a version skew from the v15 upgrade (formula/lint/client publish in
76
+ lockstep with spec, and their own 15.0.0 entries are pure dependency bumps, so
77
+ this is alignment, not a behavioral migration).
78
+
79
+ Practical effect: the client-side field-rule evaluation
80
+ (`visibleWhen`/`readonlyWhen`/`requiredWhen` via `fieldRules.ts`, which delegates
81
+ to `@objectstack/formula`'s `ExpressionEngine`) now tracks the 15.x engine — and
82
+ will pick up the framework's `dateField == today()` equality fix
83
+ (objectstack-ai/framework#3205) automatically at the next 15.x release via the
84
+ caret range. Renderer/action `visible`/`disabled` predicates are unaffected (they
85
+ use the home-grown JS evaluator — tracked separately in #2661).
86
+
87
+ - 7cf4051: chore(deps): align every `@objectstack/*` dependency to `^16.0.0-rc.0`
88
+
89
+ Bumps `@objectstack/spec` / `client` / `formula` / `lint` from `^15.1.1` to the
90
+ `16.0.0-rc.0` pre-release across the workspace (root + `apps/console` +
91
+ `apps/site` + all consuming packages). ObjectUI's own packages are already on
92
+ major 16, so this closes the 15↔16 skew between ObjectUI and the `@objectstack`
93
+ contract libraries (which publish in lockstep with `spec`).
94
+
95
+ This is a dependency alignment, not a behavioral migration: the full workspace
96
+ build (43/43) and the `@objectstack`-consuming package test suites
97
+ (`core` / `app-shell` / `data-objectstack` / `plugin-form` / `types`) are green
98
+ against `16.0.0-rc.0` with no source changes required.
99
+
100
+ Practical effect: `@objectstack/client@16.0.0-rc.0` now ships
101
+ `data.batchTransaction` (framework #3271), so `ObjectStackAdapter`'s feature
102
+ detect (`typeof client.data.batchTransaction === 'function'`) routes
103
+ master-detail cross-object saves through the typed SDK method instead of the
104
+ raw `fetch('/api/v1/batch')` fallback — realizing the "verify SDK path" half of
105
+ #2694. The raw-fetch branch stays as a defensive fallback (removal tracked in
106
+ #2694).
107
+
108
+ - 0ea5036: refactor(data-objectstack): route `batchTransaction` through the client SDK only, drop the raw-fetch branch
109
+
110
+ `@objectstack/client@^16` (framework #3271, the current ObjectUI dependency
111
+ floor) ships `data.batchTransaction`, so `ObjectStackAdapter.batchTransaction`
112
+ now calls the typed SDK method directly. The transitional hand-rolled
113
+ `fetch('/api/v1/batch')` branch — a feature-detect shim kept while the SDK
114
+ method was unreleased — is removed (#2694). Per AGENTS.md §7, adapter data
115
+ always flows through `@objectstack/client`, never a raw `fetch`.
116
+
117
+ No behavior change: the SDK still drives the server's atomic `POST /api/v1/batch`,
118
+ one `MutationEvent` is emitted per committed op (no double-fire), and the adapter
119
+ still degrades to the non-atomic `emulateBatchTransaction` when this backend lacks
120
+ the endpoint (404/405) or its runtime can't do transactions (501). Every other
121
+ status still surfaces to the caller.
122
+
123
+ - 549c67d: chore(lint): clear the mechanical baseline lint errors so these packages' lint gates protect them again
124
+
125
+ Extends the fields/core cleanup from #2709 (objectui#2713). These eight package
126
+ lints were red at baseline on `main`, so their per-package `lint` gate could not
127
+ catch new violations of the same class. Cleared every **error** (no behavior
128
+ change; warnings are out of scope):
129
+
130
+ - **`no-useless-catch`** (`data-objectstack`) — unwrapped five try/catch blocks
131
+ whose `catch` only re-threw; errors still propagate identically.
132
+ - **`preserve-caught-error`** (`cli`, `data-objectstack`, `react`) — the caught
133
+ error's message is inlined into the thrown `Error`; a scoped disable with a
134
+ justifying comment carries each one, because these packages target ES2020
135
+ whose lib types the 1-arg `Error` constructor only (so `{ cause }` won't
136
+ compile) — same reasoning as the core case in #2709.
137
+ - **`prefer-const`** (`plugin-calendar`, `plugin-map`) — `let`→`const` for
138
+ never-reassigned bindings.
139
+ - **`no-empty-object-type`** (`plugin-designer`) — empty extend-only interfaces
140
+ → equivalent `type` aliases.
141
+ - **`no-useless-assignment`** (`react`) — dropped a dead initializer that both
142
+ branches overwrite before it is read.
143
+ - **`no-require-imports`** (`plugin-calendar`, `plugin-timeline` tests) —
144
+ hoisted `vi.mock` factories now use an `async` factory with
145
+ `await import('react')` instead of `require('react')`.
146
+ - **stale `eslint-disable` directive** (`plugin-markdown`) — removed a
147
+ `react/no-danger` disable whose plugin is not loaded in the flat config (an
148
+ unknown-rule reference that ESLint v10 reports as an error); the rationale is
149
+ kept as a plain comment.
150
+
151
+ - 29c6040: fix(app-shell): redo the record-list "Add View" create flow — empty-name 405, invisible drafts, canonical naming
152
+
153
+ Rebuilds the record-list "Add View" / "Save as view" create path so a
154
+ runtime-created view has one canonical identity and is actually verifiable
155
+ before publish (supersedes #2754; fixes #2767).
156
+
157
+ - **Unified identity (P1).** New `viewEnvelope(objectName, spec, { name, label })`
158
+ seam in `runtime-metadata-persistence.ts` emits the canonical ViewItem
159
+ (`{ name: '<object>.<key>', object, viewKind: 'list', label, config }` with
160
+ `config.data = { provider: 'object', object }`), mirroring the Studio
161
+ `anchors.ts:createBuildBody`. The **qualified** name is passed as BOTH the
162
+ `PUT /meta/view/:name` URL segment and `body.name`, so the `sys_metadata`
163
+ row key, the ViewTabBar tab id, and the body identity all agree and the
164
+ draft → read → publish loop resolves. `ObjectView` and `ObjectDataPage` both
165
+ call the single helper — the duplicated envelope block is gone (P6).
166
+ - **Empty-name guards (405).** `MetadataClient.save()` and
167
+ `createRuntimeMetadata()` throw a clear contextual error instead of emitting
168
+ `PUT /meta/view/` (empty `:name`, server 405).
169
+ - **Draft visibility (P2/P3/P4).** `DataSource.listViews(objectName, { previewDrafts })`:
170
+ in draft-preview mode the `ObjectStackAdapter` makes a **single**
171
+ `MetadataClient.withPreviewDrafts(true).list('view')` request and uses the
172
+ server's already-overlaid list (draft wins by name, `_draft` tagged) —
173
+ replacing, not appending, so a draft that edits a published view can't
174
+ double-tab. No hand-rolled `fetch` of metadata routes at the adapter layer.
175
+ After a create in normal mode the console navigates to the new view with
176
+ `?preview=draft`, so the DraftPreviewBar is visible and Publish is one click.
177
+ - **CJK-aware naming (P5).** `CreateViewDialog` gains an editable machine-name
178
+ field, prefilled via `slugify(label)` for Latin labels and required (submit
179
+ disabled) when slugify yields empty for non-Latin labels — no more silent
180
+ random `task_grid_mrsyt56j` names. New `console.objectView.viewName*` keys
181
+ (en/zh).
182
+
183
+ - Updated dependencies [1c8935a]
184
+ - Updated dependencies [8b8b744]
185
+ - Updated dependencies [7cf4051]
186
+ - Updated dependencies [2e7d7f0]
187
+ - Updated dependencies [94d4876]
188
+ - Updated dependencies [2b17339]
189
+ - Updated dependencies [31b77d4]
190
+ - Updated dependencies [6d4fbe6]
191
+ - Updated dependencies [0a3710b]
192
+ - Updated dependencies [62b9ab5]
193
+ - Updated dependencies [1629313]
194
+ - Updated dependencies [29c6040]
195
+ - Updated dependencies [faebac3]
196
+ - Updated dependencies [2331ac9]
197
+ - Updated dependencies [199fa83]
198
+ - Updated dependencies [eee4ded]
199
+ - @object-ui/core@16.1.0
200
+ - @object-ui/types@16.1.0
201
+
3
202
  ## 16.0.0
4
203
 
5
204
  ### Patch Changes
package/README.md CHANGED
@@ -272,6 +272,68 @@ await dataSource.bulk('users', 'delete', [
272
272
  - Provides partial success reporting for resilient error handling
273
273
  - Atomic operations where supported by the backend
274
274
 
275
+ ## Cross-Object Atomic Batch (`batchTransaction`)
276
+
277
+ `bulk()` above operates on **one** object. To persist a set of **cross-object**
278
+ writes as a single all-or-nothing unit — the master-detail case, where a parent
279
+ and its children must commit or roll back together — use `batchTransaction`:
280
+
281
+ ```typescript
282
+ // Create a parent and a child that references it, atomically.
283
+ // `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
284
+ await dataSource.batchTransaction([
285
+ { object: 'invoice', action: 'create', data: { no: 'INV-1' } },
286
+ { object: 'invoice_line', action: 'create', data: { invoice: { $ref: 0 }, amount: 10 } },
287
+ ]);
288
+ ```
289
+
290
+ On a supporting backend this is one `POST /api/v1/batch` that commits or rolls
291
+ back the whole set in a single server transaction — no orphaned parent if a
292
+ child write fails.
293
+
294
+ ### Declarative capability negotiation (`transactionalBatch`)
295
+
296
+ Whether the adapter can rely on server atomicity is decided **at connect time**,
297
+ not by firing a batch and reading the failure. `connect()` reads the
298
+ `capabilities.transactionalBatch` flag from `GET /api/v1/discovery`
299
+ ([framework #3298](https://github.com/objectstack-ai/framework/issues/3298)),
300
+ which the server sets to `true` only when the `/batch` route is mounted **and**
301
+ the runtime engine can honour a transaction (`declared === enforced`):
302
+
303
+ | Discovery `capabilities.transactionalBatch` | `batchTransaction` behaviour |
304
+ | --- | --- |
305
+ | `true` | **Trusts server atomicity.** Calls `/batch`; any failure — including `404`/`405`/`501` — surfaces as a real error. No non-atomic client-side fallback. |
306
+ | `false` | Backend can't do an atomic batch (route absent, or a runtime without transactions) → falls back to the non-atomic client-side emulation below. |
307
+ | *absent* | Backend predates #3298 and advertises nothing → the legacy runtime probe stays: try `/batch`, and on `404`/`405`/`501` fall back to emulation. |
308
+
309
+ The hierarchical wire shape (`{ transactionalBatch: { enabled: true } }`) and the
310
+ flat form the client SDK normalizes to (`{ transactionalBatch: true }`) are both
311
+ accepted.
312
+
313
+ ### Non-atomic fallback
314
+
315
+ When the capability is `false` or absent, the adapter degrades to a client-side
316
+ emulation (`@object-ui/core`'s `emulateBatchTransaction`): the operations run in
317
+ order and, on failure, it best-effort deletes the records it created (children
318
+ before parent) before rethrowing. This is **not** a transaction — a create's
319
+ side effects (hooks, rollups, webhooks) are not undone by a later delete, and a
320
+ mid-batch network drop leaves no chance to compensate. It exists only so a save
321
+ is still possible against a backend that lacks server atomicity; removing it
322
+ would turn "saves, less safe" into "no save path" on older backends
323
+ ([objectui #2679](https://github.com/objectstack-ai/objectui/issues/2679)).
324
+
325
+ ### Minimum supported backend
326
+
327
+ Atomic cross-object saves are **guaranteed only against ObjectStack backends on
328
+ the 16.x line that advertise `capabilities.transactionalBatch: true`** — the
329
+ endpoint landed in [framework #1604](https://github.com/objectstack-ai/framework/issues/1604)
330
+ and its discovery capability in
331
+ [framework #3298](https://github.com/objectstack-ai/framework/issues/3298).
332
+ ObjectUI does not hard-require it: against an older backend a master-detail save
333
+ still succeeds, but non-atomically via the fallback above. Treat the advertised
334
+ capability as the floor for the atomicity guarantee, not as a connection
335
+ prerequisite.
336
+
275
337
  ## User-Scoped State Adapter
276
338
 
277
339
  In addition to the main `DataSource` adapter, this package ships
@@ -348,6 +410,7 @@ new ObjectStackAdapter(config: {
348
410
  - `update(resource, id, data)` - Update an existing record
349
411
  - `delete(resource, id)` - Delete a record
350
412
  - `bulk(resource, operation, data)` - Batch operations (create/update/delete)
413
+ - `batchTransaction(operations)` - Cross-object atomic batch (master-detail); atomic when the backend advertises `transactionalBatch`, else non-atomic client-side fallback
351
414
  - `getObjectSchema(objectName)` - Get schema metadata (cached)
352
415
  - `getCacheStats()` - Get cache statistics
353
416
  - `invalidateCache(key?)` - Invalidate cache entries