git mv rename (history preserved); install.sh + README updated. The skill is consumed via user-level install (~/.zcode/skills), so in-repo .agents discovery was unnecessary.
407 lines
18 KiB
Markdown
407 lines
18 KiB
Markdown
# Tables, Fields, Records & Views
|
|
|
|
Complete guide for building table structures and working with data via the `inkreach-teable` CLI.
|
|
|
|
## Quick Start
|
|
|
|
Minimal walkthrough — create a table with fields in one shot, then insert a record:
|
|
|
|
```bash
|
|
# 1. Create a "Tasks" table with 4 fields
|
|
inkreach-teable table create --name "Tasks" --base-id bseXXX \
|
|
--fields '[
|
|
{"name":"Title","type":"singleLineText"},
|
|
{"name":"Status","type":"singleSelect","options":{"choices":["Todo","Doing","Done"]}},
|
|
{"name":"Due Date","type":"date"},
|
|
{"name":"Est Hours","type":"number","options":{"formatting":{"precision":1}}}
|
|
]'
|
|
|
|
# 2. Insert a record
|
|
inkreach-teable record create --table-id tblXXX \
|
|
--fields '{"Title":"Ship sign-up flow","Status":"Doing","Due Date":"2025-06-15","Est Hours":3.5}'
|
|
|
|
# 3. Verify
|
|
inkreach-teable record get --table-id tblXXX
|
|
```
|
|
|
|
> **Tip**: `table create --fields` creates the table AND all fields atomically. For existing tables, use `field create`.
|
|
|
|
---
|
|
|
|
## Tables
|
|
|
|
### Create
|
|
|
|
```bash
|
|
inkreach-teable table create --name "<name>" [--base-id <id>] \
|
|
[--fields '<json>'] [--description <desc>]
|
|
```
|
|
|
|
| Flag | Required | Description |
|
|
|------|----------|-------------|
|
|
| `--name` | yes | Display name (unique within base) |
|
|
| `--base-id` | no | Defaults to stored config |
|
|
| `--fields` | no | Array of field objects (see below) |
|
|
| `--description` | no | Optional description |
|
|
|
|
**`--fields` format**: JSON array of `{name, type, [options], [description]}` objects. Types accept full values or aliases (`sel` → `singleSelect`); per-type option shapes and inference rules: [field-types.md](../api-reference/field-types.md).
|
|
|
|
**Compact shorthand** (in `--fields` only): `"name:type"` string format:
|
|
|
|
```bash
|
|
inkreach-teable table create --name "Projects" --fields '["Name:text","Budget:num","Active:check"]'
|
|
```
|
|
|
|
With select options: `"Status:sel:Todo,Doing,Done"`, `"Tags:mul:Bug,Feature,UI"`
|
|
|
|
### Update & Delete
|
|
|
|
```bash
|
|
inkreach-teable table update <tableId> --name "<newName>" [--description <d>]
|
|
inkreach-teable table delete <tableId>
|
|
```
|
|
|
|
| Operation | Idempotent? | Notes |
|
|
|-----------|-------------|-------|
|
|
| `update` | yes | Only changes name/description |
|
|
| `delete` | **DESTRUCTIVE** | Deletes all records + fields; irreversible |
|
|
|
|
### List & Get
|
|
|
|
```bash
|
|
inkreach-teable table list [--base-id <id>]
|
|
inkreach-teable table get <tableId>
|
|
```
|
|
|
|
`table get` returns `dbTableName` — the internal SQL table name used in `sql` queries (e.g., `SELECT * FROM "bseXXX"."tbl_abc"`).
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
### Create
|
|
|
|
```bash
|
|
inkreach-teable field create --table-id <tid> --name "<name>" \
|
|
--type <type> [--options '<json>'] [--description <d>]
|
|
```
|
|
|
|
| Flag | Required | Notes |
|
|
|------|----------|-------|
|
|
| `--table-id` | yes | Target table |
|
|
| `--name` | yes | Field display name |
|
|
| `--type` | no | Full type name or alias; **omitted → inferred** from `--options` shape or `--name` pattern (rules: [field-types.md § Smart Inference](../api-reference/field-types.md)) |
|
|
| `--options` | depends on type | JSON object; select `choices` accept plain strings — see [field-types.md § Option Shapes](../api-reference/field-types.md) |
|
|
| `--description` | no | Optional |
|
|
|
|
**Name resolution**: for `link` fields, use `foreignTableName` instead of `foreignTableId` — the CLI resolves it automatically:
|
|
|
|
```bash
|
|
inkreach-teable field create --table-id tblXXX --name "Project" --type link \
|
|
--options '{"foreignTableName":"Projects","relationship":"manyOne"}'
|
|
```
|
|
|
|
### Update & Delete
|
|
|
|
```bash
|
|
inkreach-teable field update <tableId> <fieldIdOrName> [--name "<n>"] [--options '<json>']
|
|
inkreach-teable field delete <tableId> <fieldId>
|
|
```
|
|
|
|
| Detail | Rule |
|
|
|--------|------|
|
|
| `--name` only | Uses the PATCH update API (name/description only) |
|
|
| `--options` | Rewrites options via the **convert** API (`PUT …/field/:fid/convert`) — the PATCH update API strips `options` by design. CLI auto-fetches the field's current type; omitted keys are **replaced whole**, not merged |
|
|
| `--name` + `--options` | Both applied via one convert call |
|
|
| `field delete` | Irreversible — all cell data in that field is lost |
|
|
|
|
### Inspecting fields & constraints
|
|
|
|
`field get` and `field visible-choices` accept **fieldId or field name** (the CLI resolves names automatically). Human mode renders select choices with their conditions — no `--json` post-processing needed:
|
|
|
|
```bash
|
|
# One-field overview: which fields carry conditional choices
|
|
inkreach-teable field list tblXXX --with-options
|
|
# ID NAME TYPE OPTIONS
|
|
# ... 处理反馈 singleSelect 21 choices · 19 conditional
|
|
# followed by per-choice listings: name [color] + "when: <condition summary>"
|
|
|
|
# Single field by name — choices expanded with human-readable conditions
|
|
inkreach-teable field get tblXXX 处理反馈
|
|
# choices (21 · 19 conditional):
|
|
# 待工厂补发 [orange]
|
|
# when: 问题类型 is 物流问题-超时未揽收 OR 问题类型 is 物流问题-丢件 …
|
|
|
|
# What a specific record may currently select
|
|
inkreach-teable field visible-choices tblXXX recXXX 处理反馈
|
|
```
|
|
|
|
### Select-choice dependency (visibilityFilter) — 2026-08
|
|
|
|
Single/multiple-select choices may carry a `visibilityFilter` making them visible only when another select field matches:
|
|
|
|
```json
|
|
{"choices":[{"id":"choA","name":"墨盒","visibilityFilter":{
|
|
"filterSet":[{"fieldId":"fldDeviceType","operator":"is","value":"choPrinter"}]}}]}
|
|
```
|
|
|
|
- **Read config**: `field get <tableId> <fieldIdOrName>` → `options.choices[].visibilityFilter` (rendered human-readable in default mode; raw JSON with `--json`)
|
|
- **Survey a whole table**: `field list <tableId> --with-options` shows a constraint count per field plus per-choice conditions
|
|
- **Write config**: `field update --options` (convert API). Backend rejects self-reference (`Self-reference in visibilityFilter is not allowed`) and dependency cycles (`Dependency cycle detected…`) with 400 — enforced on both V1 and V2 paths since fix `26ed1f732`
|
|
- **Evaluate for a record**: `field visible-choices <tableId> <recordId> <fieldIdOrName>` → choices currently selectable
|
|
- **Write-time enforcement**: writing a record value that fails its visibility condition → 400 `Option "X" does not meet the current row conditions and cannot be selected.`
|
|
|
|
---
|
|
|
|
## Records
|
|
|
|
### Get (read)
|
|
|
|
```bash
|
|
# All records (paginated)
|
|
inkreach-teable record get --table-id <tid> [--filter '<json>'] [--projection '<json>'] \
|
|
[--page-size <n>] [--page <n>] [--search <kw>]
|
|
|
|
# Single record by ID
|
|
inkreach-teable record get-one <recordId>
|
|
```
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `--filter` | Filter expression: `{"fieldId":{"operator":"value"}}` |
|
|
| `--projection` | JSON array of field IDs to return: `'["fldXXX","fldYYY"]'` |
|
|
| `--page-size` | Records per page (default 100) |
|
|
| `--search` | Keyword search: `"keyword"` (auto-wraps to `{"value":"keyword"}`) |
|
|
| `--view-id` | Fetch in view order with view's filter/sort/group applied |
|
|
|
|
**Filter operators by field type**:
|
|
|
|
| Field Type | Supported Operators |
|
|
|-----------|-------------------|
|
|
| `singleLineText` `longText` `url` `email` `phone` | `is` `isNot` `contains` `doesNotContain` `isEmpty` `isNotEmpty` |
|
|
| `number` `currency` `percent` `duration` `rating` | `is` `isNot` `isGreater` `isGreaterEqual` `isLess` `isLessEqual` `isEmpty` `isNotEmpty` |
|
|
| `checkbox` | `is` |
|
|
| `date` `createdTime` `lastModifiedTime` | `is` `isNot` `isWithIn` `isBefore` `isAfter` `isOnOrBefore` `isOnOrAfter` `isEmpty` `isNotEmpty` |
|
|
| `singleSelect` (single) | `is` `isNot` `isAnyOf` `isNoneOf` `isEmpty` `isNotEmpty` |
|
|
| `singleSelect` (multi) | `hasAnyOf` `hasAllOf` `isExactly` `isNotExactly` `hasNoneOf` `isEmpty` `isNotEmpty` |
|
|
| `multipleSelect` | `hasAnyOf` `hasAllOf` `isExactly` `isNotExactly` `hasNoneOf` `isEmpty` `isNotEmpty` |
|
|
| `user` `createdBy` `lastModifiedBy` (single) | `is` `isNot` `isAnyOf` `isNoneOf` `isEmpty` `isNotEmpty` |
|
|
| `user` `createdBy` `lastModifiedBy` (multi) | `hasAnyOf` `hasAllOf` `isExactly` `hasNoneOf` `isNotExactly` `isEmpty` `isNotEmpty` |
|
|
| `link` (single) | `is` `isNot` `isAnyOf` `isNoneOf` `contains` `doesNotContain` `isEmpty` `isNotEmpty` |
|
|
| `link` (multi) | `hasAnyOf` `hasAllOf` `isExactly` `hasNoneOf` `isNotExactly` `contains` `doesNotContain` `isEmpty` `isNotEmpty` |
|
|
| `attachment` | `isEmpty` `isNotEmpty` |
|
|
|
|
> **⚠️ `isAnyOf` only works for select/link/user fields, NOT for text/number fields.** For text fields with multiple values, use `or` conjunction with multiple `is` rules:
|
|
> ```json
|
|
> {"conjunction":"or","filterSet":[
|
|
> {"fieldId":"fldX","operator":"is","value":"val1"},
|
|
> {"fieldId":"fldX","operator":"is","value":"val2"}
|
|
> ]}
|
|
> ```
|
|
|
|
**Date sub-operators** (with `is` / `isNot` / `isWithIn` / `isBefore` etc.):
|
|
|
|
| Operator | Description |
|
|
|----------|-------------|
|
|
| `today` `tomorrow` `yesterday` | Relative day |
|
|
| `currentWeek` `lastWeek` `nextWeekPeriod` | Relative week |
|
|
| `currentMonth` `lastMonth` `nextMonthPeriod` | Relative month |
|
|
| `currentYear` `lastYear` `nextYearPeriod` | Relative year |
|
|
| `oneWeekAgo` `oneWeekFromNow` | ±1 week |
|
|
| `oneMonthAgo` `oneMonthFromNow` | ±1 month |
|
|
| `daysAgo` `daysFromNow` | ±N days (set `value`) |
|
|
| `exactDate` | Specific date: `"2025-06-15"` |
|
|
| `exactDateTime` | Specific datetime: `"2025-06-15T09:00:00.000Z"` |
|
|
| `pastWeek` `pastMonth` `pastYear` | Last week/month/year (for `isWithIn`) |
|
|
| `nextWeek` `nextMonth` `nextYear` | Next week/month/year (for `isWithIn`) |
|
|
| `pastNumberOfDays` `nextNumberOfDays` | ±N days (for `isWithIn`, set `value`) |
|
|
|
|
**Conjunction**: `"and"` (all must match) or `"or"` (any matches). Filter sets can nest for complex logic.
|
|
|
|
### Create
|
|
|
|
```bash
|
|
# Single record
|
|
inkreach-teable record create --table-id <tid> --fields '<json>'
|
|
|
|
# Batch (up to 2000)
|
|
inkreach-teable record create --table-id <tid> --records '<json>'
|
|
```
|
|
|
|
**`--fields` format** (single): `{"FieldName":"value","Another":"value"}` — field names resolve automatically.
|
|
|
|
**Value semantics**:
|
|
|
|
| Value | Meaning |
|
|
|-------|---------|
|
|
| `"string"` | Normal value |
|
|
| `null` | **Clear** the cell |
|
|
| `""` (empty string) | **Skip** (no change; update only) |
|
|
|
|
> **Important**: `""` is skip, not clear. Use `null` to empty a cell. For checkbox: `true` = checked, `null` = unchecked (NOT `false`).
|
|
|
|
**`--typecast`**: auto-converts human-readable values to internal IDs. Essential for `user` and `link` fields:
|
|
|
|
```bash
|
|
# Without typecast — must use IDs
|
|
inkreach-teable record create --table-id tblXXX --fields '{"Assignee":{"id":"usrXXX","title":"Alice"}}'
|
|
|
|
# With typecast — use display names
|
|
inkreach-teable record create --table-id tblXXX --fields '{"Assignee":"Alice"}' --typecast
|
|
```
|
|
|
|
**`--records` format** (batch): `{"header":["Field1","Field2"],"records":[["val1","val2"],["val3","val4"]]}`
|
|
|
|
### Update
|
|
|
|
```bash
|
|
inkreach-teable record update --table-id <tid> --records '<json>'
|
|
```
|
|
|
|
**`--records` format**: `[{"id":"recXXX","fields":{"FieldName":"newValue"}}]` — array of `{id, fields}` objects. Batch up to 2000.
|
|
|
|
**Record reordering**: pass `--order` with `viewId`, `anchorId`, `position`:
|
|
|
|
```bash
|
|
inkreach-teable record update --table-id tblXXX \
|
|
--records '[{"id":"recXXX","fields":{}}]' \
|
|
--order '{"viewId":"viwXXX","anchorId":"recYYY","position":"before"}'
|
|
```
|
|
|
|
### Delete
|
|
|
|
```bash
|
|
inkreach-teable record delete --table-id <tid> --record-ids "recA,recB,recC"
|
|
```
|
|
|
|
Max 1000 IDs per call. Irreversible.
|
|
|
|
---
|
|
|
|
## Views
|
|
|
|
### List & Get
|
|
|
|
```bash
|
|
inkreach-teable view list <tableId>
|
|
inkreach-teable view get <viewId>
|
|
```
|
|
|
|
### Create
|
|
|
|
```bash
|
|
inkreach-teable view create --table-id <tid> --name "<name>" [--type <t>]
|
|
```
|
|
|
|
| `--type` | Description |
|
|
|----------|-------------|
|
|
| `grid` | Default spreadsheet view |
|
|
| `kanban` | Board (requires a singleSelect/link grouping field) |
|
|
| `form` | Form input view |
|
|
| `gallery` | Card gallery |
|
|
| `calendar` | Calendar (requires a date field for grouping) |
|
|
|
|
### Update (configure filters, sorts, grouping)
|
|
|
|
```bash
|
|
inkreach-teable view update <viewId> \
|
|
[--name "<n>"] \
|
|
[--filter '<json>'] \
|
|
[--sort '<json>'] \
|
|
[--group '<json>'] \
|
|
[--column-meta '<json>']
|
|
```
|
|
|
|
**Filters**: `{"conjunction":"and","filterSet":[{"fieldId":"fldXXX","operator":"is","value":"Done"}]}`
|
|
|
|
**Sorts**: `[{"fieldId":"fldXXX","order":"asc"}]`
|
|
|
|
**Group**: `[{"fieldId":"fldXXX","order":"asc"}]`
|
|
|
|
**Column meta**: controls visibility, width, order of fields in the view.
|
|
|
|
### Delete
|
|
|
|
```bash
|
|
inkreach-teable view delete <viewId>
|
|
```
|
|
|
|
Deleting a view does NOT delete the table or its data — only the view configuration.
|
|
|
|
---
|
|
|
|
## View Presets
|
|
|
|
Presets are **view-scoped, named bundles of view configuration** (filter/sort/group, optionally options/columnMeta), stored server-side and shared with all collaborators — each view owns its own preset list. Users apply them from the toolbar preset dropdown (labeled by current state: 公共 / 个人 / active preset name; the old "个人" toggle button was merged into this dropdown). Applying seeds the user's personal-view overlay (persisted server-side per user per view), so operators can work in different presets simultaneously without touching the collaborative view. Managing presets requires table-level view-update permission.
|
|
|
|
### Typical use
|
|
|
|
One-click screening configs for operators, e.g. "pending orders" vs "in-progress orders" on an order table, without granting view-edit permission.
|
|
|
|
### Commands
|
|
|
|
```bash
|
|
# preset screening pending orders (bound to one view)
|
|
inkreach-teable preset create --table-id tblXxx --view-id viwXxx --name "待处理订单" \
|
|
--filter '{"conjunction":"and","filterSet":[{"fieldId":"fldStatus","operator":"is","value":"待处理"}]}'
|
|
|
|
# preset for in-progress orders, newest first
|
|
inkreach-teable preset create --table-id tblXxx --view-id viwXxx --name "正在处理的订单" \
|
|
--filter '{"conjunction":"and","filterSet":[{"fieldId":"fldStatus","operator":"is","value":"处理中"}]}' \
|
|
--sort '{"sortObjs":[{"fieldId":"fldCreated","order":"desc"}]}'
|
|
|
|
inkreach-teable preset list tblXxx --view-id viwXxx
|
|
inkreach-teable preset update <presetId> --table-id tblXxx --name "新名字" # or replace --filter/--sort/...
|
|
inkreach-teable preset delete <presetId> --table-id tblXxx
|
|
```
|
|
|
|
### Semantics
|
|
|
|
- Config keys you do NOT pass stay **unset**: on apply, the preset keeps the view's current value for those keys (a filter-only preset does not touch the sort).
|
|
- Keys you DO pass **replace** that aspect on apply.
|
|
- `--column-meta` snapshots field visibility/order — avoid unless needed: fields added later stay hidden while that preset is active (stale snapshot).
|
|
- Deleting a preset does not touch views or records; users applying it fall back to the public config on next switch.
|
|
- REST equivalents: `GET/POST /api/table/:tableId/view-preset` (list accepts `?viewId=`; create requires `viewId`), `PUT/DELETE /api/table/:tableId/view-preset/:presetId` (writes need view-update permission).
|
|
- Per-user personal config: `GET/PUT/DELETE /api/table/:tableId/view-preset/personal?viewId=` (view-read permission; PUT body `{config, enabled}`) — this is what the UI "个人配置" entry persists.
|
|
|
|
---
|
|
|
|
## Value Formats Quick Reference
|
|
|
|
| Field Type | Value format | Notes |
|
|
|-----------|-------------|-------|
|
|
| `singleLineText` / `longText` | `"string"` | |
|
|
| `number` | `42` or `3.14` | Integer or float |
|
|
| `checkbox` | `true` / `null` | NOT `false` for unchecked |
|
|
| `date` | `"2025-06-15"` or `"2025-06-15T09:00:00.000Z"` | ISO 8601; `"now"` maps to current time |
|
|
| `singleSelect` | `"Option Name"` | (string) |
|
|
| `multipleSelect` | `["Tag A","Tag B"]` | (array of strings) |
|
|
| `rating` | `3` | Integer 1..max |
|
|
| `attachment` | `[{"name":"f.png","token":"xxx"}]` | Always array of objects; update **replaces all** |
|
|
| `user` | `{"id":"usrXXX","title":"Alice"}` | With `--typecast`: just `"Alice"` or `"alice@example.com"` |
|
|
| `link` | `[{"id":"recXXX"}]` | With `--typecast`: just `"Linked Record Name"` |
|
|
| `formula` / `rollup` / `lookup` | Read-only | Cannot be set via record create/update |
|
|
| `autoNumber` / `createdBy` / `createdTime` / `lastModifiedTime` / `lastModifiedBy` | Auto-populated | Cannot be set manually |
|
|
| `button` | `{"count":0}` | Read-only; incremented by button clicks |
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
| User intent | Correct approach | Wrong approach |
|
|
|-------------|-----------------|----------------|
|
|
| Create a table with fields | `table create --fields '[...]'` — one call | Multiple `field create` calls (slower, more error-prone) |
|
|
| Use display names for linked records | `record create --fields '{"Project":"Acme"}' --typecast` | Manually looking up `recXXX` IDs |
|
|
| Use email to assign user | `record create --typecast --fields '{"Assignee":"alice@co.com"}'` | Looking up `usrXXX` IDs |
|
|
| Clear a cell | `null` in the fields object | `""` (which means skip!) |
|
|
| Update one field, leave others | `--records '[{"id":"recXXX","fields":{"Status":"Done"}}]'` | Re-sending all fields (unnecessary, slower) |
|
|
| Fetch only 2 columns | `record get --projection '["fldXXX","fldYYY"]'` | Fetching all fields (wastes bandwidth) |
|
|
| Find records by keyword | `record get --search "keyword"` | `record get` then filtering manually |
|
|
| Add a select field with colored options | `--options '{"choices":["A","B"]}'` — colors auto | Manually specifying color for every choice |
|
|
| Make a link field to another table | `--type link --options '{"foreignTableName":"Target","relationship":"manyOne"}'` | Using IDs + manual lookup |
|
|
| Reorder records in a view | `record update --order '{"viewId":...,"anchorId":...,"position":"before"}'` | Deleting and re-creating records |
|
|
| View data in kanban board | `view create --type kanban` then `view update --group` | Creating special columns |
|
|
| Set datetime in a date field | `"2025-06-15T14:30:00.000Z"` | `"2025-06-15 14:30"` (non-ISO; may be rejected) |
|
|
| Update attachments | Always pass the full array (replaces all) | Trying to add/remove individual attachments |
|
|
| Batch insert 500 records | One `record create --records` call | 500 separate `record create` calls |
|