Skip to content

feat(projectors): project runs into a Postgres event log without blocking append - #41

Merged
dylnslck merged 4 commits into
mainfrom
feat/event-log
Oct 24, 2026
Merged

dylnslck merged 4 commits into
mainfrom
feat/event-log

Conversation

@dylnslck

Copy link
Copy Markdown
Contributor

Problem and change

withProjectors previously ran delivery inside append, adding projector latency to every run step. On Durable Objects, projector delivery is now scheduled with waitUntil after the append commits, so append returns when the primary log commits. The durable cursor still tracks delivery and retries failures on the next alarm or request.

postgresEventLog adds a generic sink for all non-ephemeral events, keyed on (run_id, seq), and a looms_run_index table containing run status, timestamps, parent and launch effect links, and indexed tenant/project/org columns for row-level security. rebuild(source) replays a run or the whole store from seq 1.

Review notes

This branch branches from main. postgresEventLog works over Hyperdrive or direct Postgres connections. waitUntil execution is covered by the @looms/projectors scheduled delivery test and typechecked with Cloudflare Workers types.

Checked with bun x tsc -p packages/{projectors,cloudflare}/tsconfig.build.json --noEmit, bun x oxlint --deny-warnings on changed files, and bun test src in packages/projectors and packages/cloudflare. bun run verify was not run.

Existing contributor checklist

  • bun run verify passes
  • Changeset added if @swirls/looms behavior changes (bun run changeset)
  • Docs updated for API / architecture changes

Made with Cursor

dylnslck and others added 4 commits October 23, 2026 12:21
…king append

Durable Object projectors are scheduled with waitUntil after the log commits. postgresEventLog stores each non-ephemeral envelope and a run index with status, launch links, and tenant attributes, and can rebuild that sink from the log.

Co-authored-by: Cursor <cursoragent@cursor.com>
…tements

postgresEventLog issued one statement per event plus one per run-index change. A batch is now one multi-row event insert per 1,000 rows, one upsert for runs that started or finished, and one unnest update for runs that only gained events, all in one transaction. Index changes are folded per run first, with the same rules as the memory sink.

New tests run the Postgres sink on PGlite through its socket server. They check that it matches the memory sink row for row, that it writes a fixed number of statements, and that a touch-only batch ignores unknown runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@dylnslck
dylnslck merged commit a1fa2c3 into main Oct 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant