Sync Stripe data to PostgreSQL (and other destinations) via a message-based protocol. Sources read from APIs, destinations write to databases, and the engine wires them together through typed async iterable streams. Connectors communicate via NDJSON when running as subprocesses.
pnpm install
pnpm build # required before running CLI or e2e tests
pnpm test # unit tests (no deps needed)
pnpm test:integration # needs local Postgres
pnpm test:e2e # needs Docker + Stripe API keys in .envBefore committing (CI enforces all three):
pnpm format # prettier
pnpm lint
pnpm buildMinimum Node.js version: 24. Dev with auto-rebuild: pnpm --filter sync-engine dev
If you add a migration, register it in packages/state-postgres/src/migrations/index.ts.
Sources and destinations are isolated connectors that only depend on protocol
and approved shared utilities (logger, openapi, util-postgres).
The engine loads connectors (in-process or subprocess), pipes source output through
destination input, and manages state checkpoints. See docs/architecture/packages.md
for the full dependency graph.
| Package | Purpose | Depends on |
|---|---|---|
packages/protocol |
Message types, Source/Destination interfaces, Zod schemas | zod, citty, ix |
packages/openapi |
Stripe OpenAPI spec fetching and parsing | zod |
packages/logger |
Structured logging (pino) + progress UI (ink) | pino, ink; peer: protocol |
packages/source-stripe |
Stripe API source connector | protocol, openapi, logger |
packages/destination-postgres |
Postgres destination connector | protocol, util-postgres, logger |
packages/destination-google-sheets |
Google Sheets destination connector | protocol, logger |
packages/state-postgres |
Postgres state store + migrations | util-postgres, logger |
packages/util-postgres |
Shared Postgres utilities (upsert, rate limiter) | logger, pg |
packages/hono-zod-openapi |
Hono + zod-openapi integration for spec generation | hono, zod, zod-openapi |
packages/test-utils |
Shared test helpers (servers, seeds, fixtures) | destination-postgres, openapi, pg |
packages/ts-cli |
Generic TypeScript module CLI runner | citty |
apps/engine |
Sync engine library + stateless CLI + HTTP API | protocol, connectors, state-postgres |
apps/service |
Pipeline management + Temporal workflows | engine, Temporal SDK |
apps/dashboard |
React web UI for pipeline management | openapi-fetch, radix-ui |
apps/visualizer |
Next.js data visualization tool | next, source-stripe, pglite |
apps/supabase |
Supabase edge functions (Deno runtime) | protocol, engine, connectors |
e2e/ |
Cross-package conformance and layer tests | all packages |
- This file is an index, not a rulebook — before adding anything here, check if it belongs in docs/architecture/principles.md, docs/architecture/decisions.md, or another doc first. Only add to AGENTS.md if no better home exists.
- Connector isolation — sources never import destinations, both depend only on
protocol+ approved shared utilities. Enforced bye2e/layers.test.ts. - State is a message — connectors never access state storage directly. State in =
cursor_in; state out =SourceStateMessage. - Snake_case on the wire — all Zod schemas and JSON wire format use snake_case.
- api_version is required — always mandatory in Stripe source config. Never optional.
- Tests fail loud — no silent skips when dependencies are unavailable.
See docs/architecture/principles.md for the complete list.
- Architecture & layers: docs/architecture/
- Design decisions: docs/architecture/decisions.md
- Engine internals: docs/engine/
- Service internals: docs/service/
- Plans & RFCs: docs/plans/
- Guides (CLI, publishing, tsconfig): docs/guides/
- OpenAPI specs:
apps/{engine,service}/src/__generated__/openapi.json - CI: .github/workflows/ci.yml
- All serializable inputs/outputs (Zod schemas, JSON wire format) must use snake_case field names.
- Source connectors must use
console.errorfor logging (stdout is the NDJSON stream). - Generated OpenAPI specs live in each package's
src/__generated__/openapi.json. Run./scripts/generate-openapi.shand commit the output before pushing when schemas change. Never edit generated files by hand. - Non-trivial PRs should be accompanied by a plan artifact in
docs/plans/YYYY-MM-DD-<slug>.md. Save it before or alongside the first implementation commit.
- Debugging the sync CLI — subprocess log location, pnpm store staleness, dist vs bun condition.
- No build step for local dev — the sync CLI uses
--conditions bun --import tsxso it reads.tssource directly. Edits to workspace packages propagate immediately.pnpm buildis only needed for vitest (which resolvesdist/) and Docker. - Do NOT add
injectWorkspacePackages: truetopnpm-workspace.yaml— it copies files into the pnpm store as hardlinks, which break silently when editors save (write-temp-then-rename). Without it, pnpm uses symlinks and edits always propagate. Docker builds usepnpm deploy --legacyinstead. tsxfails onapps/supabase—?rawimports pull in Deno-only code. Other packages work fine withnpx tsx.packages/sync-engine/src/supabaseis Deno, not Node. Don't run those files with Node/tsx.- E2E tests need Stripe keys with write permissions (they create real objects).
- The npm-bundled
esbuildbinary is blocked by Santa (endpoint security). SetESBUILD_BINARY_PATHto the Homebrew-installed binary:source scripts/prefer-system-esbuild.shor add it to your.envrc.apps/supabase/build.mjsauto-detects the system binary, but tools likeviteandtsxneed the env var set in the shell.
When creating git worktrees, always use .worktrees/ at the repo root — not .claude/worktrees/.
git worktree add .worktrees/<name> <branch>