Skip to content

feat(core): server-action dispatcher factory createServerActionHandler (#2904) - #3324

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-2904-dispatcher-factory
Aug 4, 2026
Merged

feat(core): server-action dispatcher factory createServerActionHandler (#2904)#3324
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-2904-dispatcher-factory

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #2904

按维护者裁决(issue 评论 2026-08-03)实施选项 1@object-ui/core 导出服务端 action 派发工厂,消费者自行注册;顺手把 useConsoleActionRuntimeRecordDetailView 两处手写 POST 收敛为一处。客户端执行 body 依旧永久出界(#2896 论证不变)。

变更内容

@object-ui/core(新公共 API,minor)

  • createServerActionHandler({ fetch, baseUrl, resolveObject, resolveRecordId, onRefresh }) — 服务端 action 派发工厂(packages/core/src/actions/serverActionHandler.ts)。core 对 issue 点名的三件事保持无立场、全部注入:auth(fetch 传认证包装)、base URL(字符串或 thunk,不继承 VITE_SERVER_URL 这类打包器约定)、对象归属(resolveObject 兜底)。协议性的东西全部收进工厂、只此一份:
  • executeScript 的报错与 ActionDef.body 文档指向工厂;独立消费者从此有受支持的接法:<ActionProvider handlers={{ script: createServerActionHandler({...}) }}>

@object-ui/app-shell(收敛,patch)

  • 新增 utils/consoleServerAction.tscreateConsoleServerActionHandler:在 core 工厂之上叠 console 独有的 DOM 编排(popup 预开 spinner tab、newTabUrl 零往返快路径、redirectUrl 约定 + 弹窗被拦 toast)。
  • useConsoleActionRuntimeRecordDetailView 都改为由该包装构造 handler,两份手写 POST(各 ~160 行)删除。
  • actions-envelope.ratchet.test.ts 收紧:app-shell 任何文件不得在代码中出现 /api/v1/actions/ 路由(marketplaceApi 豁免不变),并钉住两个派发面确实经过共享包装。
  • utils/actionResponse.ts / utils/actionErrorDetail.ts 删除(迁 core),flowResponse.ts 改从 @object-ui/core 导入。

行为收敛点(RecordDetailView 侧,属修偏而非破坏)

  1. target || name → name-only(ADR-0110 D1):#3935 当时只修了共享 runtime,record 页漂移未修 —— 旧行为对 target-bound action POST 注册键,服务端解析不到声明、静默跳过 ADR-0066 D4 能力门与 ADR-0104 参数契约;
  2. _rowRecord 不再漏进请求体(客户端暂存,从不属于服务端契约);
  3. 快路径 recordId 现在同样尊重 action.recordId 覆盖(旧代码在 related-list 行 action 场景会错用父记录 id);
  4. 无名 action 的报错文案统一为共享 runtime 的版本。

一处已知微差(wrapper 注释里写明):防重入守卫随派发进了 core 工厂,位于 popup 预开之后 —— opensInNewTab action 被双击时第二次点击会预开一个存活一个微任务的空 tab 随即关闭;守卫防的双重 POST 依然不可能发生。

文档 / changeset

  • packages/core/README.md 新增 “Server Action Dispatch” 一节;content/docs/guide/architecture-overview.md 修正 script = Execute inline JavaScript 的陈旧表述并补工厂用法。
  • changeset:@object-ui/core minor + @object-ui/app-shell patch(fixed 组,无 major)。
  • ⛔ 未触碰 content/docs/releases/

验证(全部在本提交 cfb7c1f 上本地跑完,2026-08-03 19:27–19:51;容器其后重启,交由 PR CI 复核)

命令 结果
聚焦基线(core 工厂 22 例 + 信封随迁用例 + console 包装 12 例 + 既有 runtime 行为钉 + 两个 ratchet) pnpm exec vitest run <6 个文件> ✅ 6 文件 / 91 用例全绿
turbo type-check pnpm exec turbo run type-check --concurrency=2 ✅ 78/78 tasks
turbo lint pnpm exec turbo run lint --concurrency=2 ✅ 45/45 tasks
turbo build pnpm exec turbo run build --concurrency=2 ✅ 44/44 tasks
仓根全量 vitest pnpm exec vitest run --maxWorkers=2 ✅ 874 passed | 1 skipped(10356 用例 passed | 25 skipped)

破坏验证实录(故意改坏 → 红 → 还原 → 绿;三处同轮注入,标记核验 1/0/1)

断言 破坏方式 红掉的用例
ADR-0110 name-only 身份 工厂 URL 改回 target || name core dispatches by declarative name, never by target;hook posts the action NAME, not its target
信封 inner-failure 规则(#3913) interpretActionResponse 去掉 innerFailed 分支 core catches a LEGACY business rejection hiding under HTTP 200;工厂 treats an INNER success:false under HTTP 200 as a failure;hook #3913 no green toast
redirectUrl 双层信封读取 wrapper 直接读 result.data wrapper drives the pre-opened tab to a handler-returned redirectUrl + opens lazily…toast fallback;hook read through BOTH envelopes

破坏轮:4 文件 / 8 用例红(exit 1);git restore 还原后 0 脏文件,同套命令 6 文件 / 91 用例全绿(exit 0)。

清单外发现(只记录未顺手修)

与在飞工单的文件交叠

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa


Generated by Claude Code

#2904)

ActionSchema.body executes server-side; the client dispatches, it never
interprets. Until now only the console could dispatch: useConsoleActionRuntime
and RecordDetailView each hand-rolled the same POST /api/v1/actions/{object}/
{action} and drifted twice (objectstack#3913 envelope, framework#3935
identity). Standalone @object-ui/core consumers had no supported way to run a
body at all — #2895's improved error was a dead end.

- core: createServerActionHandler({ fetch, baseUrl, resolveObject,
  resolveRecordId, onRefresh }) owns the protocol once — name-only identity
  (ADR-0110 D1), the record-id dance (also exported as
  resolveServerActionRecordId, plus isRecordScopedAction), a re-entrancy
  guard, the POST body contract, and the /actions envelope rule.
  interpretActionResponse / readActionPayload / actionErrorDetail move from
  app-shell internals into core as public exports.
- app-shell: both console copies collapse into
  utils/consoleServerAction.createConsoleServerActionHandler — the DOM
  choreography (popup pre-open, newTabUrl zero-roundtrip fast path,
  redirectUrl convention) layered over the core factory. RecordDetailView
  thereby adopts name-only identity and stops leaking _rowRecord to the
  server.
- ratchet: actions-envelope.ratchet.test.ts is now stronger — NO app-shell
  file may name the /actions route in code (marketplaceApi excepted).
- docs: core README + guide/architecture-overview.md document the factory;
  the stale 'script = inline JavaScript' row corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
@vercel

vercel Bot commented Aug 4, 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 Aug 4, 2026 12:01am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation package: core tests labels Aug 4, 2026
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 00:02
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 5781fb1 Aug 4, 2026
16 of 17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-2904-dispatcher-factory branch August 4, 2026 00:02
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-B_5jXsIb.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
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) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
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) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
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) 476.66KB 104.69KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 224.93KB 55.15KB
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) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.53KB 10.71KB
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) 6.96KB 1.98KB
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) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
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.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 60.54KB 17.13KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.01KB 28.86KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 230.56KB 56.80KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.54KB 26.97KB
plugin-gantt (index.js) 162.25KB 39.55KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 104.94KB 25.32KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.55KB 10.59KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
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) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
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) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.46KB 1.21KB
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.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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

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

Labels

documentation Improvements or additions to documentation package: core tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@object-ui/core has no default server dispatcher, so standalone consumers cannot run action.body at all

2 participants