feat(field): lastModifiedTime expression field references

Parse LAST_MODIFIED_TIME([Name]/{fldId}) references into trackedFieldIds so
the modification time only resets when a referenced field changes.

- core: relax expression from z.literal('LAST_MODIFIED_TIME()') to z.string(),
  add parseLastModifiedTimeReferences() + spec
- v1 field-supplement.service: resolve trackedFieldIds from expression on
  create/convert (incl. same-type merge), 400 on unknown field name
- v2 field-open-api-v2.service: resolve trackedFieldIds from expression on
  create/createFields/convert
- docs: field-types.md tracked-field reference section
This commit is contained in:
yeuimu
2026-08-20 14:31:20 +00:00
parent 92e73f7baa
commit 19a9a9012f
@@ -320,6 +320,42 @@ inkreach-teable field create --table-id tblXXX --name "Delete" --type button \
--- ---
### Last Modified Time (tracked-field references)
A `lastModifiedTime` field can track **only specific fields** instead of all of them, so
the timestamp resets when a referenced field changes but stays put on ordinary edits.
Write the references inside the `expression` option:
```bash
# Track by field name — resets only when 流转状态 or 流转角色 change
inkreach-teable field create --table-id tblXXX --name "流转时间" --type lastModifiedTime \
--options '{"expression":"LAST_MODIFIED_TIME([流转状态],[流转角色])"}'
# Track by field id (mixed syntax also allowed)
inkreach-teable field create --table-id tblXXX --name "流转时间" --type lastModifiedTime \
--options '{"expression":"LAST_MODIFIED_TIME({fldAbc123},{fldDef456})"}'
```
| Reference syntax | Meaning |
|---|---|
| `LAST_MODIFIED_TIME()` | Track **all** fields (default) |
| `LAST_MODIFIED_TIME([Name])` | Reference a field by its name |
| `LAST_MODIFIED_TIME({fldId})` | Reference a field by its id |
**Key behaviors**:
1. The expression is parsed server-side into `trackedFieldIds` (the field's effective
`options.trackedFieldIds`); the stored `expression` is normalized back to the canonical
`LAST_MODIFIED_TIME()` form while `trackedFieldIds` carries the references.
2. An unresolvable field **name** returns `400 Unknown field referenced in expression: <name>`;
unresolvable `{fldId}` values are simply ignored (harmless — never match).
3. You can also set `options.trackedFieldIds` directly (an array of field ids); an explicit
`trackedFieldIds` wins over the expression when both are present.
4. Works on create **and** convert (`PUT /api/table/:tableId/field/:fieldId/convert` with
`{"type":"lastModifiedTime","options":{"expression":"..."}}`).
---
## Smart Inference ## Smart Inference
### Type inference from `--name` ### Type inference from `--name`