Files
inkreach-teable-cli/.agents/skills/inkreach-teable/guides/development.md
T
yeuimu d3790ab856 refactor: rename skill 'inkreach-teable-ops' -> 'inkreach-teable'
Rename the skill directory and update all references (SKILL.md frontmatter
name, AGENTS.md, design doc, install.sh). Git tracks this as a rename so
history is preserved.
2026-08-05 10:35:04 +08:00

5.3 KiB

Development & Deployment

How to build, restart, and debug this inkreach-teable instance. Covers the dual-process architecture, known gotchas, and test infrastructure. For daily operations (tables/automation), use the inkreach-teable CLI instead — this guide is for changing the source code itself.

Architecture

Two separate production processes managed by systemd user services. Do NOT use pnpm dev as a long-running service — the embedded Next.js dev server inside NestJS is unstable and will hang or OOM-crash.

Service Command Port Mode
teable-backend.service node dist/index.js :3000 NODE_ENV=production, BACKEND_SKIP_NEXT_START=true
teable-frontend.service next start -p 3002 -H 0.0.0.0 :3002 NODE_ENV=production

Users access :3002; the backend :3000 serves the API only.

Build & Restart

Backend (no hot reload)

After editing anything under apps/nestjs-backend/src/:

cd apps/nestjs-backend && pnpm build          # webpack, ~18s
systemctl --user restart teable-backend.service

dist/ from pnpm dev:swc is hot-update fragments, NOT a valid artifact — always nest build.

Frontend

pnpm -r -F './packages/**' build               # build dependent packages first
pnpm --filter @teable/app build-fast
systemctl --user restart teable-frontend.service

Browser cache is aggressive — hard-refresh (Ctrl+Shift+R) after a rebuild.

Recover from OOM crash

If :3000 is unreachable but systemd says active:

systemctl --user stop teable-backend.service
pkill -9 -f 'dist/index.js'
systemctl --user start teable-backend.service

Known Gotchas (from real debugging)

Node v24 fetch drops the Authorization header

The global fetch() on Node v24 (undici) drops the Authorization request header in certain cases (redirects, proxies). Use node:http/node:https directly for any backend API call, or the axios-style clients. This is why the inkreach-teable CLI uses node:http instead of fetch.

Script sandbox has no process global

In automation script actions, typeof process === 'undefined'. Access secrets via process.env.NAME (this is allowed — only process.env is exposed, not process itself). Use triggerInput._secrets, triggerInput._accessToken, triggerInput._apiBaseUrl for back-channel API calls. No fs, eval, Function, or child_process.

scheduledTime backend is a simplified implementation

The backend scheduled-time.trigger.ts only honors timing.type + timing.interval. It ignores triggerTime, weekdays, daysOfMonth, starting, ending, tz. weeks type is hardcoded to every Sunday. OneTime falls back to every 60s. The scheduler is in-process setInterval — multi-instance deployments fire duplicates. See trigger-types.md § scheduledTime.

Webhook route mismatch

Frontend UI shows /api/webhook/base/{baseId}/workflow/{workflowId} but the backend's real route is /api/webhook/{baseId}/{workflowId} (no /base/ or /workflow/ segments). The CLI uses the real route. Webhook authorization config is UI-only — backend does NOT validate it.

Button field options capture stale isActive

A Button field's options.workflow.isActive is a snapshot from creation time. After deactivating a workflow, the API validates the DB is_active instead — a click returns 400 "not active". Re-activate the workflow, or recreate the field. Always verify DB state, not field options.

base_node pollution (sidebar perf)

Workflows create entries in the base_node table, which feeds the sidebar navigation tree. Hundreds of test workflows → thousands of DOM nodes → jank. The sidebar is now virtualized (@tanstack/react-virtual), but avoid generating throwaway workflows; deactivate instead of delete to keep them queryable.

Key-name differences from official teable

The self-built automation uses conditions (not filter), fieldIds (not watchFieldIds), category recordCondition (not recordMatchesConditions). The inkreach-teable CLI handles these correctly; never use the official teable CLI for automations. See SKILL.md § Key Constraints.

Test Infrastructure

Automation E2E tests live in scripts/automation-test/:

node scripts/automation-test/run-all.js     # 59 cases: 8 triggers + 6 actions + cross-features
node scripts/stress-test/stress-test.js     # concurrency stress test

Lib framework: scripts/automation-test/lib.js. Uses Node http (not fetch) due to the auth-header bug. Test workflows are deactivated, not deleted (retention policy) — the base has ~600 historical test workflows.

Useful DB Queries

# Count workflows by active state
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
  "SELECT is_active, count(*) FROM workflow WHERE deleted_time IS NULL GROUP BY is_active;"

# Find button fields and their bound workflows
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
  "SELECT id, name, options->'workflow'->>'id' AS wf_id, options->'workflow'->>'isActive' AS active
   FROM field WHERE type='button';"

# Recent executions
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
  "SELECT id, workflow_id, status, started_at FROM workflow_execution
   ORDER BY started_at DESC LIMIT 10;"