> **See also:** [ROADMAP.md](../ROADMAP.md) ## v1.2.0 — PGlite Proof of Concept > **Release Theme** > This release validates whether PGlite users want real incremental view > maintenance by shipping a lightweight TypeScript plugin with zero core > changes. The plugin (`@pgtrickle/pglite-lite`) intercepts DML via > statement-level AFTER triggers and applies pre-computed delta SQL for > simple patterns — single-table aggregates, two-table inner joins, and > filtered scans. It deliberately limits scope to 3–5 SQL patterns to > keep effort low while generating a concrete demand signal. If adoption > materialises, the full core extraction (v0.30.0) and WASM build (v0.28.0) > proceed. The main pg_trickle PostgreSQL extension ships no functional > changes in this release — only version bumps and upgrade migration > plumbing. See [PLAN_PGLITE.md](plans/ecosystem/PLAN_PGLITE.md) for the full feasibility report. ### PGlite JS Plugin PoC (Strategy C — Phase 0) > **In plain terms:** PGlite's built-in `live.incrementalQuery()` re-runs > the full query on every change and diffs at the JavaScript layer. This > proof of concept ships a PGlite plugin (`@pgtrickle/pglite-lite`) that > intercepts DML via statement-level AFTER triggers and applies pre-computed > delta SQL for simple cases — single-table aggregates and two-table inner > joins. It validates whether PGlite users want real IVM and whether the > trigger infrastructure works correctly in PGlite's single-user WASM mode. > No WASM compilation, no pgrx changes, no core refactoring required. | Item | Description | Effort | Ref | |------|-------------|--------|-----| | PGL-0-1 | **PGlite trigger infrastructure validation.** Empirically verify that statement-level triggers with `REFERENCING NEW TABLE AS ... OLD TABLE AS ...` work in PGlite's single-user mode. Document any limitations. | 4–8h | [PLAN_PGLITE.md](plans/ecosystem/PLAN_PGLITE.md) §8 Q1 | | PGL-0-2 | **Delta SQL templates for simple patterns.** Implement delta SQL generation in TypeScript for: (a) single-table `GROUP BY` with `COUNT`/`SUM`/`AVG`, (b) two-table `INNER JOIN`, (c) simple `WHERE` filter. Pre-compute at `createStreamTable()` time. | 2–3d | [PLAN_PGLITE.md](plans/ecosystem/PLAN_PGLITE.md) §5 Strategy C | | PGL-0-3 | **PGlite plugin skeleton.** TypeScript plugin implementing `createStreamTable()`, `dropStreamTable()`, trigger registration, and delta application via PGlite's plugin API. | 2–3d | [PLAN_PGLITE.md](plans/ecosystem/PLAN_PGLITE.md) §5 Strategy C | | PGL-0-4 | **npm package `@pgtrickle/pglite-lite`.** Package, publish, README with usage examples, and 3–5 supported SQL patterns documented. | 1–2d | — | | PGL-0-5 | **Benchmark vs `live.incrementalQuery()`.** Compare latency and throughput for a 10K-row table with single-row inserts. Quantify the IVM advantage. | 1d | [PLAN_PGLITE.md](plans/ecosystem/PLAN_PGLITE.md) §4.2 | > **Phase 0 subtotal: ~2–3 weeks** ### Correctness | ID | Title | Effort | Priority | |----|-------|--------|----------| | CORR-1 | Delta SQL equivalence for supported patterns | M | P0 | | CORR-2 | NULL-key aggregate correctness in JS delta | S | P0 | | CORR-3 | Multi-DML transaction atomicity | S | P1 | **CORR-1 — Delta SQL equivalence for supported patterns** > **In plain terms:** The TypeScript delta SQL templates must produce the > exact same stream table state as a full query re-evaluation, for every > combination of INSERT, UPDATE, and DELETE on the supported patterns > (single-table GROUP BY + COUNT/SUM/AVG, two-table INNER JOIN, simple > WHERE filter). Correctness is proven by running each DML operation, > comparing the delta-maintained result against a fresh `SELECT`, and > asserting row-for-row equivalence. Verify: automated test suite runs 100+ randomised DML sequences per pattern; zero divergence from full re-evaluation. Dependencies: PGL-0-2, PGL-0-3. Schema change: No. **CORR-2 — NULL-key aggregate correctness in JS delta** > **In plain terms:** When a GROUP BY key is NULL, SQL three-valued logic > means `GROUP BY NULL` forms its own group. The TypeScript delta templates > must handle NULL group keys correctly — insertions into the NULL group, > deletions that empty it, and updates that move rows in/out of the NULL > group. This is the most common correctness pitfall in hand-rolled IVM. Verify: E2E test with nullable GROUP BY column; assert NULL group appears, grows, shrinks, and disappears correctly. Dependencies: CORR-1. Schema change: No. **CORR-3 — Multi-DML transaction atomicity** > **In plain terms:** PGlite runs in single-connection mode, so a > `BEGIN; INSERT ...; DELETE ...; COMMIT` sequence fires two separate > statement-level triggers. The plugin must ensure the stream table reflects > the net effect of the entire transaction, not an intermediate state. If > trigger ordering produces incorrect intermediate results, a > post-transaction reconciliation pass is needed. Verify: test with `BEGIN; INSERT; UPDATE; DELETE; COMMIT` on a single base table; stream table matches full re-evaluation after commit. Dependencies: PGL-0-3. Schema change: No. ### Stability | ID | Title | Effort | Priority | |----|-------|--------|----------| | STAB-1 | Trigger cleanup on dropStreamTable | S | P0 | | STAB-2 | Graceful error on unsupported SQL | S | P0 | | STAB-3 | Plugin idempotency (create-drop-create cycle) | S | P1 | **STAB-1 — Trigger cleanup on dropStreamTable** > **In plain terms:** When a user calls `dropStreamTable()`, all statement- > level AFTER triggers registered on source tables must be removed. Orphaned > triggers would fire on every subsequent DML and attempt to write to a > non-existent stream table, causing errors. Verify: after `dropStreamTable()`, no pg_trickle-related triggers remain in `pg_trigger` for the source tables. Dependencies: PGL-0-3. Schema change: No. **STAB-2 — Graceful error on unsupported SQL** > **In plain terms:** The PoC supports only 3–5 SQL patterns. If a user > passes an unsupported query (e.g., a LEFT JOIN, window function, or > recursive CTE), the plugin must throw a clear, actionable error message > listing what is supported — not silently produce wrong results or crash. Verify: `createStreamTable()` with an unsupported query throws an error whose message names the unsupported feature and lists supported alternatives. Dependencies: PGL-0-2. Schema change: No. **STAB-3 — Plugin idempotency (create-drop-create cycle)** > **In plain terms:** Creating a stream table, dropping it, and creating it > again with the same name must work without leftover state. Leftover > catalog rows, triggers, or temp tables from the first creation must not > interfere with the second. Verify: create-drop-create cycle produces correct results; no duplicate triggers or stale catalog entries. Dependencies: STAB-1. Schema change: No. ### Performance | ID | Title | Effort | Priority | |----|-------|--------|----------| | PERF-1 | Benchmark vs live.incrementalQuery() | M | P0 | | PERF-2 | Delta overhead profiling per DML | S | P1 | | PERF-3 | Large result set scalability (10K/100K rows) | S | P1 | **PERF-1 — Benchmark vs `live.incrementalQuery()`** (= PGL-0-5) > **In plain terms:** The entire value proposition of this PoC depends on > being faster than PGlite's built-in `live.incrementalQuery()` for the > supported patterns. Produce a public benchmark comparing latency and > throughput for single-row inserts into a 10K-row base table across all > three supported patterns (aggregate, join, filter). Verify: delta-maintained stream table refresh latency < 50% of `live.incrementalQuery()` latency for all supported patterns at 10K rows. Dependencies: PGL-0-3, PGL-0-4. Schema change: No. **PERF-2 — Delta overhead profiling per DML** > **In plain terms:** Measure the per-DML overhead added by the statement- > level triggers. INSERT-heavy workloads should not suffer more than 2x > latency increase compared to the same INSERT without pg_trickle triggers > installed. Profile trigger function execution time, temp table creation, > and delta DML. Verify: microbenchmark shows per-DML overhead < 2 ms for aggregate pattern; < 5 ms for join pattern at 10K source rows. Dependencies: PGL-0-3. Schema change: No. **PERF-3 — Large result set scalability (10K/100K rows)** > **In plain terms:** Verify that the delta approach maintains its advantage > over full re-evaluation as base table size grows. At 100K rows, the delta > path should be significantly faster than full re-evaluation for single-row > changes. Verify: at 100K base table rows, single-row insert refresh latency is < 10% of full query re-evaluation latency. Dependencies: PERF-1. Schema change: No. ### Scalability | ID | Title | Effort | Priority | |----|-------|--------|----------| | SCAL-1 | Multiple stream tables on same source | S | P1 | | SCAL-2 | Cascading stream table triggers | M | P2 | | SCAL-3 | Concurrent DML with multiple stream tables | S | P2 | **SCAL-1 — Multiple stream tables on same source** > **In plain terms:** Verify that 3+ stream tables can be maintained from > the same base table simultaneously. Each DML fires one trigger per stream > table; ensure triggers do not interfere with each other. Verify: 3 stream tables on the same source; INSERT + UPDATE + DELETE cycle; all 3 produce correct results. Dependencies: PGL-0-3. Schema change: No. **SCAL-2 — Cascading stream table triggers** > **In plain terms:** If stream table B reads from stream table A's > underlying storage, an INSERT into A's source should propagate through > A's trigger, update A, and then fire B's trigger to update B — all > within the same PGlite transaction. Verify this works in PGlite's > single-connection environment without deadlocks or infinite trigger loops. Verify: A->B cascade produces correct results for INSERT/DELETE on A's source. No infinite loops detected. Dependencies: SCAL-1. Schema change: No. **SCAL-3 — Concurrent DML with multiple stream tables** > **In plain terms:** PGlite is single-connection, but a user could issue > rapid sequential DML (`INSERT; INSERT; INSERT`) without explicit > transactions. Verify all stream tables converge to the correct state. Verify: 100 sequential INSERTs with 3 stream tables; final state matches full re-evaluation. Dependencies: SCAL-1. Schema change: No. ### Ease of Use | ID | Title | Effort | Priority | |----|-------|--------|----------| | UX-1 | Getting-started README with copy-paste examples | S | P0 | | UX-2 | Supported patterns decision table | XS | P0 | | UX-3 | Error messages include remediation hints | S | P1 | | UX-4 | TypeScript type definitions | S | P1 | | UX-5 | ElectricSQL outreach and collaboration | S | P1 | **UX-1 — Getting-started README with copy-paste examples** > **In plain terms:** The npm package README must include 3 complete, > copy-pasteable examples — one per supported pattern — that a developer > can run in under 2 minutes. Include Node.js and browser (Vite) examples. Verify: all README examples execute without modification on a fresh PGlite instance. Dependencies: PGL-0-4. Schema change: No. **UX-2 — Supported patterns decision table** > **In plain terms:** A clear table showing which SQL patterns are and are > not supported, what error you get for unsupported patterns, and when full > support is expected (v0.30.0). This prevents user frustration and sets > expectations. Verify: decision table in README and npm page lists all tested patterns with status (supported / unsupported / planned). Dependencies: None. Schema change: No. **UX-3 — Error messages include remediation hints** > **In plain terms:** Every error thrown by the plugin must include the > table name, the failing operation, and a one-sentence hint. Example: > `"LEFT JOIN is not supported in pglite-lite. Use @pgtrickle/pglite > (v0.30.0+) for full SQL support, or rewrite as INNER JOIN."` Verify: all error paths tested; every error message includes a remediation sentence. Dependencies: STAB-2. Schema change: No. **UX-4 — TypeScript type definitions** > **In plain terms:** Ship `.d.ts` type definitions so TypeScript users > get autocomplete and type checking for `createStreamTable()`, > `dropStreamTable()`, and configuration options. Verify: TypeScript project consumes the plugin with strict mode; no `any` types leaked. Dependencies: PGL-0-4. Schema change: No. **UX-5 — ElectricSQL outreach and collaboration** > **In plain terms:** PGlite is developed by ElectricSQL. Their cooperation > is essential for Phase 2 (WASM build). Initiate contact before shipping > Phase 0 to gauge interest, validate assumptions about PGlite's trigger > infrastructure, and explore potential co-marketing. Verify: documented exchange with ElectricSQL team (GitHub issue, email, or meeting notes). Dependencies: None. Schema change: No. ### Test Coverage | ID | Title | Effort | Priority | |----|-------|--------|----------| | TEST-1 | Automated correctness suite (all patterns x DML types) | M | P0 | | TEST-2 | PGlite version compatibility matrix | S | P1 | | TEST-3 | Regression test: trigger firing order | S | P1 | | TEST-4 | Bundle size monitoring | XS | P2 | | TEST-5 | Extension upgrade path (0.18 to 0.19) | S | P0 | **TEST-1 — Automated correctness suite (all patterns x DML types)** > **In plain terms:** For each supported pattern (aggregate, join, filter), > run every DML type (INSERT, UPDATE, DELETE, multi-row, TRUNCATE) and > assert the stream table matches a fresh full evaluation. This is the > primary quality gate. Verify: Jest/Vitest test suite with > 50 test cases; all pass on PGlite latest. Dependencies: PGL-0-2, PGL-0-3. Schema change: No. **TEST-2 — PGlite version compatibility matrix** > **In plain terms:** PGlite updates frequently. Test the plugin against > the last 3 PGlite releases to ensure trigger behavior hasn't changed. > Document the minimum supported PGlite version. Verify: CI matrix runs tests against PGlite N, N-1, N-2. Dependencies: TEST-1. Schema change: No. **TEST-3 — Regression test: trigger firing order** > **In plain terms:** When multiple triggers exist on the same table, > PostgreSQL fires them in alphabetical order by trigger name. Verify that > trigger naming conventions prevent ordering conflicts with user-defined > triggers. Verify: test with a user-defined AFTER trigger alongside the plugin's trigger; both fire correctly; stream table produces correct results. Dependencies: PGL-0-3. Schema change: No. **TEST-4 — Bundle size monitoring** > **In plain terms:** The npm package should be small (< 50 KB minified + > gzipped) since this is a pure-JS plugin with no WASM. Add a CI check > that fails if bundle size exceeds the threshold. Verify: `npm pack --dry-run` reports < 50 KB gzipped. Dependencies: PGL-0-4. Schema change: No. **TEST-5 — Extension upgrade path (0.18 to 0.19)** > **In plain terms:** The main pg_trickle PostgreSQL extension ships no > functional changes in v0.29.0, but the upgrade migration path must still > be tested. `ALTER EXTENSION pg_trickle UPDATE` from 0.26.0 to 0.27.0 > must leave existing stream tables intact. Verify: upgrade E2E test confirms all existing stream tables survive and refresh correctly after `0.26.0 -> 0.27.0` upgrade. Dependencies: None. Schema change: No (PG extension unchanged). ### Conflicts & Risks 1. **Demand uncertainty is the primary risk.** This entire milestone is a bet that PGlite users want IVM beyond what pg_ivm provides. If Phase 0 generates no adoption signal, v0.30.0–v0.31.0 should be deprioritised and v1.0.0 proceeds without PGlite. Define a concrete adoption threshold (e.g., > 100 npm weekly downloads within 60 days of publication) as a go/no-go gate for v0.29.0. 2. **PGlite trigger infrastructure is unverified.** PGL-0-1 (trigger validation) is a hard prerequisite for everything else. If statement-level triggers with transition tables do not work in PGlite's single-user mode, the entire Strategy C approach fails and the PoC must pivot to a pure JS diff approach (lower value). 3. **PGlite version mismatch.** PGlite tracks PostgreSQL 17; pg_trickle targets PG 18. The PoC operates at the SQL level and should be unaffected, but if PGlite upgrades to PG 18 mid-cycle, trigger behavior may change. Pin the minimum PGlite version in `package.json`. 4. **No core Rust changes, but version bump required.** The main pg_trickle extension needs a v0.27.0 version bump, upgrade migration SQL, and passing CI even though no functional code changes. This is low-risk but must not be forgotten. 5. **ElectricSQL collaboration timing.** UX-5 (outreach) should happen early — before v0.27.0 ships — to avoid building something ElectricSQL is already working on or would actively resist. If they signal interest in co-development, Phase 2 scope and timeline may shift. 6. **TypeScript delta SQL correctness is harder to prove than Rust.** The main extension uses property-based testing and SQLancer for correctness. The TS plugin lacks these tools. TEST-1 must be rigorously designed to compensate — consider porting the proptest approach to a JS property- testing library (e.g., fast-check). > **v1.2.0 total: ~2–3 weeks (PGlite plugin) + ~1–2 days (PG extension version bump)** **Exit criteria:** - [ ] PGL-0-1: Statement-level triggers with transition tables confirmed working in PGlite - [ ] PGL-0-2: Delta SQL correct for single-table aggregate, two-table join, and filtered query - [ ] PGL-0-3: `@pgtrickle/pglite-lite` plugin creates and maintains stream tables in PGlite - [ ] PGL-0-4: npm package published with README and usage examples - [ ] PGL-0-5: Benchmark shows measurable latency improvement over `live.incrementalQuery()` for supported patterns - [ ] CORR-1: Automated delta SQL equivalence tests pass (100+ DML sequences per pattern) - [ ] CORR-2: NULL-key aggregate groups correctly created, updated, and removed - [ ] CORR-3: Multi-DML transaction produces correct net result - [ ] STAB-1: No orphaned triggers after `dropStreamTable()` - [ ] STAB-2: Unsupported SQL patterns produce clear, actionable errors - [ ] STAB-3: Create-drop-create cycle produces correct results - [ ] PERF-1: Delta refresh latency < 50% of `live.incrementalQuery()` at 10K rows - [ ] PERF-3: Delta advantage holds at 100K rows (< 10% of full re-evaluation latency) - [ ] SCAL-1: 3+ stream tables on same source produce correct results - [ ] UX-1: README examples run unmodified on fresh PGlite instance - [ ] UX-2: Supported patterns decision table published - [ ] UX-4: TypeScript type definitions ship with strict-mode compatibility - [ ] TEST-1: > 50 correctness test cases pass on PGlite latest - [ ] TEST-2: CI tests pass against PGlite N, N-1, N-2 - [ ] TEST-5: Extension upgrade path tested (`1.1.0 → 1.2.0`) - [ ] `just check-version-sync` passes ---