Skip to content

Commit 92a5e68

Browse files
authored
docs(automation): hooks-vs-flows 决策指引 — flow 是应用默认面,hook 是系统兜底 (#4272)
在 hooks.mdx 新增 Hooks vs flows 一节:任务→层的决策表,以及两条结构性理由 —— flow 的写是受 os validate 检查的结构化元数据、以数据形态可 diff 可审,而 hook body 的写集静态不可见(hook-body.zod.ts 已记录的 accepted gap)。收尾指出需要代码也不必是 hook:flow script 节点 + defineStack({ functions }) 让编排留在受检元数据里。flows.mdx Related 加反向链接。 纯文档,空 changeset 不发版。关联 #4271 / #4001
1 parent 6e357ed commit 92a5e68

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
docs(automation): add a "Hooks vs flows" decision section to `hooks.mdx` — flow as the default application surface, hook as the system backstop — with a task→layer table and the two structural reasons (a flow's writes are lint-checked metadata while a hook body's write set is statically opaque, per the accepted gap in `hook-body.zod.ts`; a flow reviews as data). Reverse link from `flows.mdx` Related. The framing settled in the #4271 discussion; documentation only, releases nothing.

content/docs/automation/flows.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,7 @@ To branch on **which** event fired, test `previous` — it is empty on create
11231123

11241124
## Related
11251125

1126+
- [Hooks](/docs/automation/hooks) — data-layer interception below the node graph; [Hooks vs flows](/docs/automation/hooks#hooks-vs-flows) covers when to drop down to one
11261127
- [Workflow Metadata](/docs/automation/workflows) — why there is no standalone Workflow Rule type, and what replaces it
11271128
- [Object Metadata](/docs/data-modeling/objects) — Objects that flows operate on
11281129
- [Validation Metadata](/docs/data-modeling/validation) — Data validation rules

content/docs/automation/hooks.mdx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,40 @@ A hook targets one or more `object`s and subscribes to one or more lifecycle
1414
`beforeInsert`, `beforeUpdate`, `afterUpdate`, `afterDelete` (read-side events
1515
such as `beforeFind`/`afterFind` are also available).
1616

17+
## Hooks vs flows
18+
19+
Hooks and [flows](/docs/automation/flows) overlap on record-triggered logic —
20+
a flow's `record_change` trigger fires on the same lifecycle events — so pick
21+
the layer before writing either. The working rule: **a flow is the default
22+
surface for application logic; a hook is the backstop** for what a node graph
23+
cannot express.
24+
25+
| You need… | Reach for |
26+
| :--- | :--- |
27+
| Side effects after a save — create records, notify, call HTTP, request approval | **Flow** (`record_change`) |
28+
| Anything that pauses: approvals, screens, timers, signals | **Flow** — a hook runs inline and cannot pause |
29+
| Scheduled or date-relative sweeps ("30 days before `end_date`") | **Flow** (`schedule` / `timeRelative`) |
30+
| Mutating the pending record in the same write, before it is saved | **Before hook** |
31+
| An invariant enforced on every write path, across objects, no matter who writes | **Hook** — the backstop duty itself |
32+
| Read-side interception (`beforeFind` / `afterFind`) | **Hook** — flows have no read events |
33+
34+
Two structural reasons to prefer the flow when either could work:
35+
36+
- **A flow's writes are validatable metadata; a hook body is opaque code.** An
37+
`update_record` node's `fields` is structural config that `os validate`
38+
checks — readonly targets, template dialects, declared expression slots. A
39+
hook body's write set is **not** statically checked (see
40+
[Hook & Action Bodies](/docs/automation/hook-bodies#not-statically-checked-the-write-set)):
41+
a misspelled field name runs green and writes nothing.
42+
- **A flow reviews as data.** The node graph diffs field-by-field and renders
43+
in the Console designer with per-node run history; a hook body reviews as
44+
code only.
45+
46+
Logic that genuinely needs code still doesn't have to be a hook: a flow
47+
`script` node calling a function registered via `defineStack({ functions })`
48+
keeps the orchestration in checked metadata and the code in one named,
49+
testable unit.
50+
1751
## Before Hook
1852

1953
Mutate the incoming record before it is saved. The engine exposes the pending

0 commit comments

Comments
 (0)