@abloatai/ablo 0.16.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **Bring-your-own database is now one model.** Ablo connects to your Postgres and
8
+ never operates it. There used to be two confusing BYO paths, and the
9
+ connection-string one would create roles, force row-level security, transfer
10
+ table ownership, and push you to run `ablo migrate` before anything worked. That
11
+ cascade is gone. Ablo now follows the shape every serious "sync over your own
12
+ Postgres" engine uses (ElectricSQL, PowerSync, Zero): it reads your database via
13
+ Postgres logical replication and never runs DDL, creates roles, forces RLS, or
14
+ rewrites your `DATABASE_URL`. You own your schema; Ablo reads it.
15
+ - **New: `ablo connect`.** One command prints the exact, copy-pasteable setup for
16
+ your own Postgres — enable `wal_level=logical`, create the `ablo_publication`
17
+ publication and a least-privilege `ablo_replicator` role — and
18
+ `ablo connect --check` validates readiness (wal level, publication, replication
19
+ grant, replica identity). This is the single supported way to connect a real
20
+ database.
21
+ - **`ablo migrate` left the happy path.** It no longer creates roles, transfers
22
+ ownership, or rewrites your connection string, and `ablo dev` no longer attempts
23
+ a scoped-role creation on every watch loop. `migrate` is now an optional escape
24
+ hatch for generating starter DDL (`--dry-run` prints the SQL).
25
+ - **Clearer failures.** `ablo push` permission errors lead with the server's actual
26
+ reason code and per-code remediation instead of a generic "needs `schema:push`
27
+ scope," and the schema-conflict message names which environment/version a prior
28
+ push came from and when.
29
+ - **Logical-replication runtime is in Preview.** The setup (`ablo connect`) and the
30
+ connection model are live; the server-side WAL consumer that streams your changes
31
+ is implemented and journey-tested but not yet generally available.
32
+
33
+ The previous connection-string-operate and adapter/outbox modes are demoted to a
34
+ clearly-labeled **Legacy / not recommended** section — they still work, but new
35
+ integrations should use logical replication.
36
+
3
37
  ## 0.16.3
4
38
 
5
39
  ### Patch Changes
@@ -702,7 +702,9 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
702
702
  * sync-group mutations). Does NOT schedule a flush — callers decide
703
703
  * whether to debounce (live) or flush atomically (catch-up frame).
704
704
  */
705
- protected enqueueDelta(delta: SyncDelta): boolean;
705
+ protected enqueueDelta(delta: SyncDelta, options?: {
706
+ authoritative?: boolean;
707
+ }): boolean;
706
708
  /** Debounce a flush for live single-delta traffic. */
707
709
  protected scheduleDeltaFlush(): void;
708
710
  /**
@@ -1713,7 +1713,25 @@ export class BaseSyncedStore {
1713
1713
  applyDeltaFrame(deltas) {
1714
1714
  let enqueuedAny = false;
1715
1715
  for (const delta of deltas) {
1716
- if (this.enqueueDelta(delta))
1716
+ // A delta_batch frame is the server's AUTHORITATIVE, ordered answer to
1717
+ // "everything in my stream after cursor C" (reconnect/catch-up replay or
1718
+ // post-bootstrap drain). Apply every delta it carries — do NOT subject it
1719
+ // to the live-traffic watermark dedup (`id <= applied`).
1720
+ //
1721
+ // That watermark is only valid under in-order delivery, and reconnect
1722
+ // breaks the assumption: an in-flight LIVE broadcast for a gap delta can
1723
+ // land out of order BEFORE the catch-up fills the ids below it (e.g. the
1724
+ // server acks a write, then the test/client reconnects, then that write's
1725
+ // pending broadcast arrives on the fresh socket — id 4 live before the
1726
+ // catch-up's [2,3,4]). Applying id 4 advances `applied` to 4, and the
1727
+ // watermark would then drop 2 and 3 from the catch-up as "already seen" —
1728
+ // a poisoned gap and a cursor that lies (applied=4 with rows 2,3 absent).
1729
+ //
1730
+ // Re-applying a delta the live path already applied is safe: the
1731
+ // downstream `Database.processDeltaBatch` + `SyncClient.applyDeltaBatchToPool`
1732
+ // are idempotent (echo detection, no row resurrection, conflict
1733
+ // resolution), so the redundant id 4 is a no-op while 2 and 3 land.
1734
+ if (this.enqueueDelta(delta, { authoritative: true }))
1717
1735
  enqueuedAny = true;
1718
1736
  }
1719
1737
  if (!enqueuedAny)
@@ -1734,10 +1752,15 @@ export class BaseSyncedStore {
1734
1752
  * sync-group mutations). Does NOT schedule a flush — callers decide
1735
1753
  * whether to debounce (live) or flush atomically (catch-up frame).
1736
1754
  */
1737
- enqueueDelta(delta) {
1738
- // Dedup guard — skip already-processed deltas
1739
- if (delta.id > 0 && delta.id <= this.highestProcessedSyncId)
1755
+ enqueueDelta(delta, options = {}) {
1756
+ // Dedup guard — skip already-processed deltas. The `applied` watermark is a
1757
+ // valid skip threshold ONLY for in-order live traffic; an authoritative
1758
+ // catch-up frame bypasses it (see `applyDeltaFrame`) so an out-of-order
1759
+ // live delta that advanced the watermark can't cause the frame's lower ids
1760
+ // to be silently dropped.
1761
+ if (!options.authoritative && delta.id > 0 && delta.id <= this.highestProcessedSyncId) {
1740
1762
  return false;
1763
+ }
1741
1764
  // Confirm awaiting transactions via sync ID threshold (before batching)
1742
1765
  this.syncClient.onDeltaReceived(delta.id);
1743
1766
  // Update version vector