> **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

---

