## 概述 字段 `defaultValue`(含 `Field.user({defaultValue:'current_user'})`)由运行时在用户 `beforeInsert` hook **之后**才解析。于是「从被默认的字段派生第二个字段」的 hook,读到的是**还未填充的 `null`**。 ## 定位 - `packages/objectql/src/engine.ts:2143` —— 执行 beforeInsert hook。 - `packages/objectql/src/engine.ts:2181` —— `applyFieldDefaults`(默认值解析)**在其后**。 → hook 读到的目标字段仍为 null,依赖它派生的字段算错。 ## 复现(概要) `sales_person: Field.user({ defaultValue: 'current_user' })`,POST 不带 `sales_person`:结果行 `sales_person` 被正确回填,但 beforeInsert 里派生的 `current_status` 因当时读到 `sales_person == null` 而算成 `unassigned`。显式传 `sales_person` → hook 看得到 → 正确。 ## 期望 把 `defaultValue`(尤其 `current_user`)解析**提前到 beforeInsert hook 之前**,让派生 hook 能看到默认值;或至少文档明确当前顺序。 --- 来源(项目 dogfood):steedos-labs/os-tianshun-mtc#29 · 与 #31 同族(#31 为 defaultValue 三问汇总)
Activity
baozhoutao commentedon Jul 10, 2026
处理方式与依据
已修复,PR #2748。
根因确认
engine.ts的insert()里执行顺序颠倒:triggerHooks('beforeInsert')先跑,applyFieldDefaults()后跑 —— 派生 hook 读到未回填的null。修法
把
applyFieldDefaults(单条 + 批量)提前到beforeInserthook 之前,回填后数据喂给hookContext.input.data。Hook 仍在默认值之后运行、可覆盖;applyAutonumbers/加密/校验维持在 hook 之后不变。为什么这是平台级正确的顺序(而非仅仅"能跑")
关键区分:声明式默认值 ≠ 引擎生成值。成熟平台把二者插在 before-hook 两侧:
$User.Id,对应本仓库current_user)在记录实例化时就填好,before trigger 的Trigger.new已带默认值;而 Auto-Number 在 save 阶段生成、before trigger 里取不到。→ 本 PR 的新顺序(默认值在前、autonumber 在后)与 Salesforce 完全一致。initialize()时套用,beforebusiness rule 天然看得到默认值。before_create/pre_save看得到;只有 DB 级 default 才滞后。多数派都是「声明式默认值先于 before 逻辑」。之前 ObjectQL「默认值晚于 before hook」才是异类,本 PR 把它挪回标准分层:
默认值 → before hook → autonumber/加密 → 校验 → 持久化。一个预期内的语义翻转(breaking note)
改后
beforeInserthook 无法再区分「客户端没传字段」与「默认值填的字段」(对声明defaultValue的字段,总是看到已解析的默认值)。这与 Salesforce/ServiceNow 行为一致(before 逻辑看到的是已初始化记录),是本次修复的预期语义;依赖「字段 undefined = 客户端未传」分支的下游 hook 需改判。测试
新增回归测试复刻 steedos-labs/os-tianshun-mtc#29;
@objectstack/objectql全量 63 文件 / 805 tests 全通过。后续(未包含在本 PR)
update路径默认值处理是否遵循同样「声明式在前」原则,避免 insert/update 两套顺序。fix(data): resolve field defaultValues before beforeInsert hook (#2703)…
chore: bump objectui to 94d4876df090 (#3319)
chore: bump objectui to 94d4876df090