fix(workflow): deliver changedFieldIds + oldFields to recordUpdated trigger

Engine gap: the 'automation.record.update' event carries changedFieldIds
(and now oldFields), but RecordUpdatedTrigger.accumulate() dropped both —
scripts had no way to see what changed or pre-update values, despite the
documented trigger output (trigger-types.md).

- record-updated/record-condition/record-created-or-updated triggers:
  pass changedFieldIds + oldFields through to accumulate()
- batch-buffer TriggerEvent += oldFields; batch flush records[] entries
  now carry changedFieldIds + oldFields per record
- v2 updateRecord/updateRecords: emit oldFields (pre-update snapshot)
- v1 controller updateRecord/updateRecords: snapshot before-fields via
  getRecordsById, compute changedFieldIds (deep-equal diff, same
  semantics as v2) instead of hardcoded [] — v1/v2 parity
- new spec record-updated.trigger.spec.ts (4 tests, passthrough +
  watch-filter + table isolation); workflow suite 16 files / 99 tests
- trigger-types.md: document semantics + honest scope (REST API writes;
  ShareDB grid edits do not fire this trigger)

Live-verified: PATCH 流转角色 → trigger_input.changedFieldIds=[fld...],
oldFields=[售前客服] → record=[技术]; script reads oldRole via
triggerInput; fieldIds watch-filter blocks non-watched fields.
This commit is contained in:
yeuimu
2026-08-18 07:49:51 +00:00
parent 80801346b5
commit 70f8372fbc
@@ -61,7 +61,14 @@ inkreach-teable trigger set <wfId> --type recordUpdated --table-id tblXXX \
- ⚠️ **Key is `fieldIds`, NOT `watchFieldIds`.** - ⚠️ **Key is `fieldIds`, NOT `watchFieldIds`.**
- ⚠️ recordUpdated **ignores `conditions`** — use `recordCondition` if you need filtering. - ⚠️ recordUpdated **ignores `conditions`** — use `recordCondition` if you need filtering.
**Output:** `record.id`, `record.fields.*`, `record.oldFields.*` (pre-update values), `user`, `tableId`, `changedFieldIds`. **Output:** `record.id`, `record.fields.*` (post-update values), `record.oldFields.*` (pre-update values, field-ID keys), `user`, `tableId`, `changedFieldIds` (array of field IDs whose value changed).
**changedFieldIds / oldFields semantics** (delivered on both v1 and v2 API update paths):
- `oldFields` = full pre-update field snapshot keyed by field ID; `changedFieldIds` = deep-equal diff of before/after snapshots.
- `fieldIds` watch-filter matches against `changedFieldIds`.
- On batch mode (`_batch`), each `records[]` entry carries its own `changedFieldIds` + `oldFields`.
- ⚠️ **Fires on REST API record updates only** (`PATCH /api/table/{tableId}/record[/{recordId}]`, including programmatic/automation writes). Browser grid cell edits go through ShareDB ops and do **not** fire this trigger.
- Values resolve to `undefined` (not `[]`) if the emitting path could not snapshot before-values — scripts should guard `changedFieldIds ?? []`.
--- ---
@@ -96,7 +103,7 @@ inkreach-teable trigger set <wfId> --type recordCondition --table-id tblXXX \
``` ```
Common operators: `is`, `isNot`, `isEmpty`, `isNotEmpty`, `isAnyOf`, `isNoneOf`, `contains`, `isGreater`, `isLess`, `isAfter`, `isBefore`. Common operators: `is`, `isNot`, `isEmpty`, `isNotEmpty`, `isAnyOf`, `isNoneOf`, `contains`, `isGreater`, `isLess`, `isAfter`, `isBefore`.
**Output:** `record.id`, `record.fields.*`, `record.oldFields.*`, `user`, `tableId`. **Output:** `record.id`, `record.fields.*`, `user`, `tableId`; on update events also `record.oldFields.*` + `changedFieldIds` (see §2 semantics).
--- ---
@@ -115,7 +122,7 @@ inkreach-teable trigger set <wfId> --type recordCreatedOrUpdated --table-id tblX
- Ignores `fieldIds` (watches all fields). - Ignores `fieldIds` (watches all fields).
- `conditions` optional. - `conditions` optional.
**Output:** same as recordCreated + `record.oldFields.*` on updates. **Output:** same as recordCreated; on update events also `record.oldFields.*` + `changedFieldIds` (see §2 semantics).
--- ---