Skip to content

feat(data-objectstack): gate non-atomic batch fallback on discovery transactionalBatch capability (#2693)#2755

Merged
os-zhuang merged 1 commit into
mainfrom
claude/client-batch-hard-delete-rollback-gc5a9n
Jul 20, 2026
Merged

feat(data-objectstack): gate non-atomic batch fallback on discovery transactionalBatch capability (#2693)#2755
os-zhuang merged 1 commit into
mainfrom
claude/client-batch-hard-delete-rollback-gc5a9n

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2693. Picks up #2679 acceptance item 4. Unblocked by framework#3298 (the discovery capability bit), which merged today.

Background

Until now ObjectStackAdapter.batchTransaction learned whether a backend could do an atomic cross-object /batch only by firing one and reading the status (404/405 → no route, 501 → runtime without transactions), then degrading to the non-atomic client-side emulation in @object-ui/core. framework#3298 added a declarative signal so the client can decide at connect time instead.

The contract (from framework#3298, merged)

Discovery now carries a capability bit. All three server producers (rest-server, plugin-hono-server, metadata-protocol) emit the hierarchical shape and the client SDK normalizes it:

  • Wire: discovery.capabilities.transactionalBatch.enabled: boolean
  • Client SDK (@objectstack/client ≥16.0.0-rc.1): flattened to client.capabilities.transactionalBatch: boolean
  • Semantics: true = route mounted and the runtime can honour a transaction (declared === enforced); false = neither; absent = backend predates #3298.

Change

connect() reads capabilities.transactionalBatch into a tri-state; batchTransaction gates its fallback on it:

capabilities.transactionalBatch batchTransaction behaviour
true Trusts server atomicity. Calls /batch; any failure — incl. 404/405/501 — is a real error. No probe, no client-side compensation.
false Legacy: probe /batch, degrade to non-atomic emulation on 404/405/501.
absent Legacy path unchanged (can't tell without trying).

The reader accepts both the hierarchical { enabled } shape and the flat boolean the SDK normalizes to.

Why the fallback is not hard-removed: #2679's compatibility note is explicit that deleting it against backends without /batch turns "saves, less safe" into "no save path". So the compensation branch is gated off for declared-support backends (the modern path) but retained as the fallback for false/absent backends. @object-ui/core's generic emulateBatchTransaction / runBatchTransaction are untouched and remain the fallback for adapters with no server transaction (ValueDataSource, MockDataSource, …).

Acceptance mapping (#2693)

Tests

  • Declared true + 404 / 501 → hard error, no client-side compensation.
  • Flat-boolean true normalized identically.
  • Declared support + committed /batch → still emits one mutation event per op.
  • Declared support + real error (400) → surfaces unchanged.
  • Declared false / absent + 404 → still degrades to emulation (back-compat).
  • Unit test for readTransactionalBatchCapability across hierarchical / flat / absent / malformed shapes.

Full unit project green (252 files, 3454 tests); data-objectstack 187/187; package type-check and eslint clean (only pre-existing any warnings).

Notes

  • Changeset: @object-ui/data-objectstack minor.
  • No @objectstack/client dependency bump needed — the capability is read from the discovery document the adapter already fetches, so the strict path works regardless of the installed client patch.

🤖 Generated with Claude Code


Generated by Claude Code

…ransactionalBatch capability (#2693)

ObjectStackAdapter.batchTransaction now negotiates atomic cross-object batch
declaratively via the discovery capability bit (framework #3298) instead of
only probing at runtime.

- connect() reads capabilities.transactionalBatch from GET /api/v1/discovery
  and records it as a tri-state (readTransactionalBatchCapability accepts both
  the hierarchical `{ enabled }` wire shape and the flat boolean the client SDK
  normalizes to).
- When the backend DECLARES support (true), batchTransaction trusts server
  atomicity: it calls /batch and surfaces any failure — including 404/405/501 —
  as a real error. No runtime probe, no non-atomic client-side compensation.
- When the capability is false or ABSENT (backend predates #3298), the legacy
  runtime-probe + emulation fallback stays active so a save is still possible;
  hard-removing it would regress older backends from "saves, less safe" to
  "no save path" (#2679 compatibility constraint).

core's generic emulateBatchTransaction / runBatchTransaction are untouched and
remain the fallback for adapters with no server-side transaction.

Docs: adapter README + data-source guide document the capability table and the
minimum-backend note (atomicity guaranteed only against backends advertising
the capability — framework #3298 / #1604). Adds tests for each capability state
and a unit test for the shape reader.

Picks up #2679 acceptance item 4; unblocked by framework#3298 (merged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1UuBuaurza8a2XWSUaDQr
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 20, 2026 12:32pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation data-adapter tests labels Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.0 KB 350 KB
Entry file index-Dy-AYV9l.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.19KB 2.96KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 21.70KB 4.21KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 33.74KB 8.53KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 1.83KB 0.79KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.86KB 0.85KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 449.99KB 97.82KB
core (index.js) 1.86KB 0.63KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 122.27KB 30.66KB
fields (index.js) 211.66KB 51.78KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 24.31KB 5.34KB
i18n (useSafeTranslation.js) 2.87KB 1.28KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 4.42KB 1.27KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 5.90KB 2.15KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.46KB 1.03KB
permissions (evaluator.js) 4.00KB 1.23KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.50KB 0.70KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.37KB 12.48KB
plugin-charts (index.js) 46.75KB 13.23KB
plugin-chatbot (index.js) 179.53KB 42.79KB
plugin-dashboard (index.js) 108.56KB 27.97KB
plugin-designer (index.js) 210.92KB 42.69KB
plugin-detail (index.js) 214.78KB 52.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 102.82KB 24.94KB
plugin-gantt (index.js) 162.33KB 39.53KB
plugin-grid (index.js) 174.13KB 45.90KB
plugin-kanban (index.js) 47.45KB 12.99KB
plugin-list (index.js) 98.18KB 23.20KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.81KB 9.98KB
plugin-timeline (index.js) 25.37KB 7.20KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.64KB 20.85KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 2.02KB 0.95KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.26KB 1.96KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang marked this pull request as ready for review July 20, 2026 12:39
@os-zhuang
os-zhuang merged commit 8c1e415 into main Jul 20, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the claude/client-batch-hard-delete-rollback-gc5a9n branch July 20, 2026 12:39
os-zhuang added a commit that referenced this pull request Jul 20, 2026
… SDK only (#2694)

ObjectStackAdapter.batchTransaction now calls the typed SDK method
client.data.batchTransaction(operations) directly; the transitional
hand-rolled fetch('/api/v1/batch') branch is removed. Guaranteed present by
the @objectstack/client@^16 dependency floor (framework #3271). Composes with
the #2755 capability gate: a declared-atomic backend treats any failure as a
real error; otherwise the SDK's status-decorated throw (404/405/501) still
degrades to the non-atomic emulateBatchTransaction. Fallback tests reworked to
drive via the SDK method. Docs + changeset updated.

Closes #2694.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapter documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

硬删除客户端非原子 batch 模拟回退(待后端经 discovery 声明支持 /batch)——承接 #2679 验收第 4 条

2 participants