@abloatai/ablo 0.30.2 → 0.31.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.
@@ -1,31 +1,49 @@
1
1
  # Connect Your Database
2
2
 
3
- **Connect your database = Postgres logical replication.** That is the one way.
4
- Ablo reads your write-ahead log (WAL) and **never runs DDL, never owns your
5
- schema, and never migrates it**. Your application keeps writing to its own
6
- Postgres through its own backend, exactly as it does today; Ablo only tails the
7
- changes and fans the confirmed rows out to every connected human and agent. This
8
- is the same model ElectricSQL, PowerSync, and Zero use — a publication plus a
9
- replication slot. Ablo consumes the logical-replication stream; your application
10
- continues to own the write path.
3
+ You write through Ablo, and Ablo writes to your Postgres. A call to
4
+ `ablo.<model>.create / update / delete` enters Ablo's commit chokepoint where
5
+ claims, ordering, and idempotency are enforced and Ablo applies the change to
6
+ your database through a scoped role. Your rows live in your database, which stays
7
+ the system of record. Ablo reads your write-ahead log (WAL) to confirm each write
8
+ landed and to keep every connected human and agent current.
9
+
10
+ Ablo writes your **rows**; it never touches your **schema**. It runs no DDL and no
11
+ migrations — your migration tool stays in charge of the shape of your database.
12
+ Ablo only writes rows into tables you already have, through a role scoped to
13
+ exactly that.
14
+
15
+ > **Just trying Ablo?** You don't need a database to start. Pass an `apiKey` only,
16
+ > and Ablo keeps your rows in its own log so you can build the whole app today —
17
+ > like Stripe test mode. For a sandbox you can throw away, point Ablo at a separate
18
+ > or local Postgres. Connect your production database (below) when you're ready for
19
+ > it to be the system of record.
20
+
21
+ Connecting sets up two capabilities on your Postgres: **logical replication**, so
22
+ Ablo can read and confirm, and a **scoped DML role**, so Ablo can write. `ablo
23
+ connect` prints the exact SQL. `ablo connect --apply` runs it for you.
24
+
25
+ ## Connect in one command
11
26
 
12
- > **Just trying Ablo?** You don't need a database at all to start. The hosted
13
- > **sandbox** can host rows in Ablo's test plane — pass an `apiKey` only and omit
14
- > any database setup, like Stripe test mode. Connect your Postgres with logical
15
- > replication (below) when you're ready for it to be the system of record.
27
+ ```bash
28
+ npx ablo connect --apply --url postgres://admin:...@host:5432/db
29
+ ```
16
30
 
17
- Your database stays the system of record. Ablo never becomes a second source of
18
- truth and never takes over operating your Postgres.
31
+ Pass an admin connection string with `--url` and it creates the publication, the
32
+ two scoped roles, and the grants, turns on logical decoding where it can, registers
33
+ both scoped roles with Ablo, and proves the setup by reconnecting and reading back.
34
+ The admin credential is used on this machine only and never persisted — nothing is
35
+ written to your `.env`, which keeps holding only `ABLO_API_KEY`. Pass `--show-sql`
36
+ to see every statement first, or drop `--apply` to print the SQL and run it
37
+ yourself. Rotate the scoped passwords any time with `ablo connect --rotate`.
19
38
 
20
- ## The five steps (mirrors Zero's install flow)
39
+ The rest of this page is what that command sets up, step by step, for when you want
40
+ to run it by hand or review exactly what changes.
21
41
 
22
- You run the setup once against your own database, then point Ablo at it. The CLI
23
- prints the exact SQL and validates it for you — you never hand-craft replication
24
- internals.
42
+ ## The setup, step by step
25
43
 
26
44
  ### 1. Enable logical decoding
27
45
 
28
- Turn on logical WAL so Ablo can decode row changes:
46
+ Turn on logical WAL so Ablo can decode row changes and confirm writes:
29
47
 
30
48
  ```sql
31
49
  ALTER SYSTEM SET wal_level = 'logical';
@@ -34,21 +52,20 @@ ALTER SYSTEM SET wal_level = 'logical';
34
52
  `wal_level` is **not reloadable** — you must **restart Postgres** for it to take
35
53
  effect. On Amazon RDS / Aurora you can't `ALTER SYSTEM`; set
36
54
  `rds.logical_replication = 1` in the instance's parameter group instead, then
37
- reboot.
55
+ reboot. (`ablo connect --apply` attempts this for you and, where a managed
56
+ provider refuses, hands you the one remaining step.)
38
57
 
39
- ### 2. Run `ablo connect` to get the publication / slot / role SQL
58
+ ### 2. Run `ablo connect` for the publication and roles
40
59
 
41
60
  ```bash
42
61
  npx ablo connect
43
62
  ```
44
63
 
45
- `ablo connect` prints the exact, copy-pasteable setup SQL for **your** Postgres
46
- and nothing else it does not ask for a connection-string flavor, an adapter, or
47
- a driver, because logical replication is how you connect. Run the printed SQL
48
- against your database (as a superuser / the DB owner). It does three things:
64
+ `ablo connect` prints the exact, copy-pasteable setup SQL for **your** Postgres.
65
+ Run it against your database as a superuser or the DB owner. It creates:
49
66
 
50
- - **A publication** naming the tables Ablo should read (`ablo_publication`, the
51
- single canonical name the runtime subscribes to):
67
+ - **A publication** naming the tables Ablo reads and confirms against
68
+ (`ablo_publication`, the single canonical name the runtime subscribes to):
52
69
 
53
70
  ```sql
54
71
  CREATE PUBLICATION "ablo_publication" FOR ALL TABLES;
@@ -56,22 +73,33 @@ against your database (as a superuser / the DB owner). It does three things:
56
73
 
57
74
  Scope it to a subset with `npx ablo connect --tables a,b,c`.
58
75
 
59
- - **A least-privilege replication role** — it can stream replication and `SELECT`,
60
- nothing more. You choose the password; it never passes through Ablo's CLI or
61
- servers:
76
+ - **A replication role** — it streams the WAL and `SELECT`s, nothing more. This is
77
+ the role Ablo reads and confirms through. You choose the password; it never
78
+ passes through Ablo's CLI or servers:
62
79
 
63
80
  ```sql
64
81
  CREATE ROLE "ablo_replicator" WITH REPLICATION LOGIN PASSWORD '<password>';
65
82
  GRANT SELECT ON ALL TABLES IN SCHEMA public TO "ablo_replicator";
66
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "ablo_replicator";
67
83
  ```
68
84
 
69
- Rename it with `--role <name>`. On Amazon RDS the `REPLICATION` attribute is
70
- granted, not set directly: `GRANT rds_replication TO "ablo_replicator";`.
85
+ On Amazon RDS the `REPLICATION` attribute is granted, not set directly:
86
+ `GRANT rds_replication TO "ablo_replicator";`.
87
+
88
+ - **A scoped writer role** — the role Ablo writes your rows through. It gets row
89
+ DML (`SELECT, INSERT, UPDATE, DELETE`) and the sync ledger, and nothing else: no
90
+ `REPLICATION`, no schema `CREATE`, `NOSUPERUSER NOBYPASSRLS`, row security on. It
91
+ can change rows in your tables; it cannot change your database:
92
+
93
+ ```sql
94
+ CREATE ROLE "ablo_writer" WITH LOGIN PASSWORD '<write-password>'
95
+ NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
96
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO "ablo_writer";
97
+ ```
98
+
99
+ Rename either role with `--role <name>` / `--write-role <name>`.
71
100
 
72
101
  The **replication slot** is created and owned by Ablo's runtime when it first
73
- subscribes with this role — you don't pre-create it. The publication and the role
74
- are the only objects the recipe asks you to create.
102
+ subscribes — you don't pre-create it.
75
103
 
76
104
  ### 3. Validate with `ablo connect --check`
77
105
 
@@ -82,27 +110,40 @@ database is replication-ready:
82
110
  npx ablo connect --check
83
111
  ```
84
112
 
85
- This connects and checks the four invariants, printing a green checklist or the
86
- precise per-item fix:
113
+ It checks the four invariants and prints a green checklist or the precise per-item
114
+ fix:
87
115
 
88
116
  - `wal_level` is `logical`
89
117
  - the `ablo_publication` publication exists
90
- - the `DATABASE_URL` role has the `REPLICATION` attribute
118
+ - the replication role has the `REPLICATION` attribute
91
119
  - every published table has a usable `REPLICA IDENTITY` (a primary key, or
92
120
  `REPLICA IDENTITY FULL`) so `UPDATE`/`DELETE` can replicate
93
121
 
122
+ `--check` reads the two scoped roles from `ABLO_REPLICATION_DATABASE_URL` and
123
+ `ABLO_WRITE_DATABASE_URL` (the `ABLO_`-namespaced names keep a replication-only
124
+ credential from ever landing in the generic `DATABASE_URL` your ORM reads).
94
125
  Re-run it until every item is green.
95
126
 
96
- ### 4. Point Ablo at the database with the replication role
127
+ ### 4. Register the two roles with Ablo
128
+
129
+ `ablo connect --apply` already did this. If you ran the SQL by hand instead, hand
130
+ Ablo both connection strings — the replication role it reads and confirms through,
131
+ and the writer role it lands your rows through:
132
+
133
+ ```bash
134
+ npx ablo connect --register
135
+ ```
136
+
137
+ This is out of band and happens once. `--register` reads the two scoped connection
138
+ strings from `ABLO_REPLICATION_DATABASE_URL` and `ABLO_WRITE_DATABASE_URL` and
139
+ posts them to Ablo, which holds them encrypted and uses them to read and write your
140
+ database. The role passwords are generated for you and never printed — rotate them
141
+ any time with `ablo connect --rotate`. After this, Ablo does all the connecting.
97
142
 
98
- Give Ablo the connection string for the **replication role** you created — a
99
- `REPLICATION`-attributed role that streams the WAL and `SELECT`s, nothing more
100
- (not a read-only account; see the privilege note below). The same value `--check`
101
- validated:
143
+ Your **app** holds only the API key never a connection string:
102
144
 
103
145
  ```bash
104
146
  # .env — server runtime only, never the browser
105
- DATABASE_URL=postgres://ablo_replicator:<password>@host:5432/db?sslmode=require
106
147
  ABLO_API_KEY=sk_live_...
107
148
  ```
108
149
 
@@ -116,28 +157,33 @@ export const ablo = Ablo({
116
157
  });
117
158
  ```
118
159
 
119
- You define an Ablo schema with `defineSchema`, `model`, and Zod. The Ablo schema
120
- describes **only your synced, collaborative models** the rows Ablo coordinates
121
- and fans out in realtime. It is *not* your whole-database schema and does *not*
122
- replace your `schema.prisma` (or your Drizzle schema). Your auth, billing, and
123
- any other tables stay in your own ORM schema, owned by your own migrations.
124
- `ablo check` reflects this — it reports tables you didn't declare as "ignored /
125
- owned by you," which is exactly right.
160
+ The Ablo schema describes **only your synced, collaborative models** the rows
161
+ Ablo coordinates and fans out in realtime. It is *not* your whole-database schema
162
+ and does *not* replace your `schema.prisma` (or Drizzle schema). Your auth,
163
+ billing, and other tables stay in your own ORM schema, owned by your own
164
+ migrations. `ablo check` reflects this it reports tables you didn't declare as
165
+ "ignored / owned by you," which is exactly right.
126
166
 
127
- ### 5. Writes go through your own backend
167
+ ### 5. Write through `ablo.<model>`
128
168
 
129
- Your application writes to its Postgres the way it always has — its own ORM, its
130
- own backend, its own transactions. Ablo does not intercept or proxy those writes.
131
- It observes them on the WAL and fans the confirmed rows out to connected clients.
132
- The read, claim, and coordination surface (`ablo.<model>`) layers on top:
169
+ Every change goes through Ablo. The write enters the commit chokepoint, Ablo
170
+ applies it to your Postgres through the writer role, and the WAL echo confirms it
171
+ landed:
133
172
 
134
173
  ```ts
174
+ // Enters the chokepoint (claims, ordering, idempotency), lands in your Postgres.
175
+ await ablo.weatherReports.update('report_stockholm', { high: 21 });
176
+
177
+ // Block until your database has it and the WAL echo confirms.
178
+ await ablo.weatherReports.update('report_stockholm', { high: 21 }, { wait: 'confirmed' });
179
+
180
+ // Reads are live off the same stream.
135
181
  const report = ablo.weatherReports.get('report_stockholm');
136
- const active = ablo.weatherReports.claim.state({ id: 'report_stockholm' });
137
182
  ```
138
183
 
139
- For the typed read/claim/write surface itself, see
140
- [Quickstart](./quickstart.md) and [Schema Contract](./schema-contract.md).
184
+ A commit is accepted the moment Ablo takes it (`queued`); it becomes `confirmed`
185
+ once the row appears on your WAL. See [Guarantees](./guarantees.md) for what each
186
+ state means and when to wait.
141
187
 
142
188
  ## What Ablo touches in your database — the honest footprint
143
189
 
@@ -145,9 +191,10 @@ This is the complete list. Nothing else.
145
191
 
146
192
  | Object | What it is | Owned by |
147
193
  |---|---|---|
148
- | `ablo_publication` | A Postgres publication naming the tables Ablo reads. | You create it (step 2). |
194
+ | `ablo_publication` | A publication naming the tables Ablo reads and confirms against. | You create it (step 2). |
195
+ | `ablo_replicator` role | A `REPLICATION` + `SELECT` role Ablo reads and confirms through. | You create it (step 2). |
196
+ | `ablo_writer` role | A scoped DML role Ablo writes your rows through — row DML + ledger, nothing more. | You create it (step 2). |
149
197
  | Replication slot | A logical slot Ablo subscribes through to track its WAL position. | Ablo's runtime creates it on first connect. |
150
- | `ablo_replicator` role | A least-privilege `REPLICATION` + `SELECT` role. | You create it (step 2). |
151
198
  | `wal_level = logical` | A server setting that **requires a restart**. | You set it (step 1). |
152
199
 
153
200
  Operational reality you should know up front:
@@ -155,30 +202,26 @@ Operational reality you should know up front:
155
202
  - **`wal_level = logical` needs a restart.** It is a one-time, server-wide change
156
203
  and is not reloadable.
157
204
  - **A replication slot retains WAL.** While Ablo is connected, the slot holds the
158
- WAL it hasn't yet acknowledged. If Ablo is disconnected for a long time, that
159
- WAL accumulates and consumes disk. **Ablo monitors slot lag and WAL retention**
160
- and surfaces it so you're never surprised by disk pressure; an abandoned slot is
161
- dropped rather than left to grow unbounded.
162
- - **The role's privilege footprint is narrow and precise not a "read-only"
163
- account.** It carries the `REPLICATION` attribute, which lets it stream the WAL
164
- and `SELECT`; it cannot `INSERT`/`UPDATE`/`DELETE`, run DDL, or own objects, and
165
- the recipe never grants it more. (For a security review, state it that way — a
166
- logical-replication role is a real privilege, just a tightly-scoped one — rather
167
- than calling it "read-only", which a reviewer will correctly push back on.)
168
-
169
- What Ablo explicitly does **not** do:
170
-
171
- - It **never runs DDL** against your database.
172
- - It **never owns or migrates your schema** — your migration tool stays in charge.
173
- - It **never writes your rows** — writes are yours, through your backend.
205
+ WAL it hasn't yet acknowledged. If Ablo is disconnected for a long time, that WAL
206
+ accumulates and consumes disk. **Ablo monitors slot lag and WAL retention** and
207
+ surfaces it, so disk pressure never surprises you; an abandoned slot is dropped
208
+ rather than left to grow unbounded.
209
+ - **The writer role changes rows, not your database.** It carries row DML plus the
210
+ sync ledger and nothing more — no `REPLICATION`, no DDL, no object ownership, and
211
+ it runs with row security on and `NOBYPASSRLS`. It is a real, tightly-scoped
212
+ privilege describe it that way in a security review.
213
+
214
+ Ablo runs **no DDL** and **owns no schema**: your migration tool stays in charge of
215
+ the shape of your database, and Ablo writes only rows, only into tables you already
216
+ have.
174
217
 
175
218
  ## What Ablo stores on its side
176
219
 
177
220
  Your schema *definition* (model names, fields, types — pushed with `ablo push`),
178
221
  your hashed API keys, a safe projection of the connection registration (host,
179
222
  database, schema — the connection string itself is sealed and never echoed back),
180
- the replication slot position, and the commit log that drives sync. Never your
181
- rows.
223
+ the replication slot position, and the ordered transaction log that drives sync and
224
+ coordination. Your rows live in your database.
182
225
 
183
226
  > **Postgres replication status: Preview.** Registration, readiness checks, and
184
227
  > the server replication fleet are implemented and boot-wired. Preview describes
@@ -186,6 +229,17 @@ rows.
186
229
  > [internal/postgres-replication.md](./internal/postgres-replication.md) for the
187
230
  > architecture and operational invariants.
188
231
 
232
+ ## When your database can't grant replication
233
+
234
+ Some managed databases won't grant a `REPLICATION` role. For those, Ablo connects
235
+ through a **signed Data Source endpoint** instead: you expose one signed HTTP route
236
+ built from your ORM (`prismaDataSource` / `drizzleDataSource`, with `ablo_outbox` /
237
+ `ablo_idempotency` bookkeeping), and Ablo writes and confirms through it — same
238
+ `ablo.<model>` surface, same commit chokepoint, same `queued` → `confirmed`
239
+ lifecycle. It needs no replication setup, which is exactly why it's the fallback:
240
+ reach for it only when logical replication isn't available, and prefer `ablo
241
+ connect` everywhere else.
242
+
189
243
  ## Next steps
190
244
 
191
245
  - [Quickstart](./quickstart.md) — connect and write through `ablo.<model>`.
@@ -194,20 +248,3 @@ rows.
194
248
  - [Guarantees](./guarantees.md) — what confirmed writes and stale checks mean.
195
249
  - [Integration Guide](./integration-guide.md) — the full app, React, multiplayer,
196
250
  and agent path.
197
-
198
- ---
199
-
200
- ## Legacy / not recommended
201
-
202
- > **Use logical replication instead** (top of this page). The shapes below
203
- > predate the single connect path. They are documented only so existing
204
- > integrations can be read and understood — do **not** reach for them when
205
- > connecting a new database. They are the seams that caused painful onboarding,
206
- > and `ablo connect` exists precisely to replace them.
207
-
208
- This older shape connected Ablo to a database by exposing a **signed Data Source
209
- endpoint** built from an ORM adapter (`prismaDataSource` / `drizzleDataSource`,
210
- with `ablo_outbox` / `ablo_idempotency` bookkeeping and a reverse-channel
211
- connector for VPCs). It required Ablo to proxy every write, and has been superseded
212
- by logical replication, where Ablo only reads your WAL. If you are maintaining one
213
- of these integrations, migrate it to `ablo connect` at the next opportunity.
@@ -47,11 +47,12 @@ objects by hand.
47
47
  Every schema model is backed by **your own database**. The SDK call shape is the
48
48
  same everywhere.
49
49
 
50
- In this guide — an app that already owns its backend and database — keep
51
- `DATABASE_URL` inside your app and connect it out of band: run `npx ablo connect`
52
- for logical replication (Ablo tails your WAL), or expose a signed Data Source
53
- endpoint where Ablo coordinates each write and your app commits it to your
54
- Postgres. Either way, application and agent code hold only `ABLO_API_KEY` the
50
+ In this guide — an app that already owns its backend and database — keep the
51
+ database credentials inside your server runtime and connect out of band: run `npx
52
+ ablo connect` to set up logical replication and a scoped writer role, or expose a
53
+ signed Data Source endpoint when your database can't grant replication. Either way,
54
+ you write through `ablo.<model>`; Ablo lands each change in your Postgres and
55
+ confirms it over the WAL. Application and agent code hold only `ABLO_API_KEY` — the
55
56
  client never sees a connection string. [Connect Your Database](./data-sources.md)
56
57
  is the single source of truth for both paths.
57
58
 
@@ -2,15 +2,16 @@
2
2
 
3
3
  Build with Ablo on **the Postgres you already have**. You declare a small Ablo
4
4
  schema for the models humans and agents edit together, connect Ablo to your
5
- database with **logical replication** (`ablo connect`), and coordinate every read
6
- and claim through `ablo.<model>`. Your database stays the system of record: your
7
- app keeps writing through its own backend, Ablo tails your write-ahead log (WAL),
8
- and the confirmed rows fan out to every connected client. Ablo **never runs DDL,
9
- owns, or migrates your schema**.
5
+ database (`ablo connect`), and read and write every one of those models through
6
+ `ablo.<model>`. You write through Ablo; it lands the change in your Postgres and
7
+ confirms it by tailing your write-ahead log (WAL). Your rows live in your database,
8
+ which stays the system of record. Ablo writes rows but **runs no DDL and owns no
9
+ schema** your migration tool stays in charge of the shape of your database.
10
10
 
11
- > No database yet? The hosted **sandbox** can host rows in Ablo's test plane —
12
- > pass an `apiKey` only and skip the database setup, like Stripe test mode so
13
- > you can try Ablo before connecting your Postgres.
11
+ > No database yet? Pass an `apiKey` only and Ablo keeps your rows in its own log,
12
+ > so you can build the whole app today like Stripe test mode. Point it at a
13
+ > separate or local Postgres for a throwaway sandbox, or at your production
14
+ > database when you're ready.
14
15
 
15
16
  ## 1. Install and initialize
16
17
 
@@ -97,32 +98,38 @@ collapses to the untyped client.
97
98
 
98
99
  ## 3. Connect your database with `ablo connect`
99
100
 
100
- Connecting a real database = Postgres logical replication, and `ablo connect` is
101
- the one way to set it up. Ablo **reads** your WAL and never runs DDL, owns, or
102
- migrates your schema — your app keeps writing through its own backend.
101
+ `ablo connect` sets your database up so Ablo can write your rows (a scoped DML
102
+ role) and read them back to confirm (logical replication). It writes rows through
103
+ that role but runs no DDL and owns no schema — your migration tool stays in charge.
103
104
 
104
- ```bash
105
- # 1. Enable logical decoding (then RESTART Postgres wal_level is not reloadable)
106
- # ALTER SYSTEM SET wal_level = 'logical';
107
- # On RDS/Aurora: set rds.logical_replication = 1 in the parameter group, reboot.
108
-
109
- # 2. Print the exact publication + replication-role SQL for YOUR Postgres, run it:
110
- npx ablo connect
105
+ You run `ablo connect` once, out of band — it provisions the roles and hands them
106
+ to Ablo. From then on Ablo does the connecting; your app never opens a database
107
+ connection.
111
108
 
112
- # 3. Put the replication role's connection string in DATABASE_URL, then validate:
113
- npx ablo connect --check
109
+ ```bash
110
+ # Point it at an admin connection once — it does the whole ceremony: creates the
111
+ # roles + publication, turns on logical decoding where it can, registers both
112
+ # scoped roles with Ablo, and proves it by reading back. Nothing lands in your .env.
113
+ npx ablo connect --apply --url postgres://admin:...@host:5432/db
114
+
115
+ # ...or print the SQL and run it yourself, then register:
116
+ # npx ablo connect # prints the publication + two scoped roles
117
+ # npx ablo connect --check # validates the database is ready
118
+ # npx ablo connect --register # hands the two scoped roles to Ablo
114
119
  ```
115
120
 
116
- `ablo connect` prints the copy-pasteable SQL (a publication named
117
- `ablo_publication` and a least-privilege `ablo_replicator` role with
118
- `REPLICATION` + `SELECT`, password you choose). `ablo connect --check` connects to
119
- `DATABASE_URL` and verifies `wal_level=logical`, the publication, the role's
120
- `REPLICATION` attribute, and that every published table has a usable
121
- `REPLICA IDENTITY` a green checklist or the precise fix per item.
121
+ `ablo connect --apply` generates two roles and their passwords — an
122
+ `ablo_replicator` role (`REPLICATION` + `SELECT`, for reads and confirmation) and
123
+ an `ablo_writer` role (scoped row DML, for writes) and registers both connection
124
+ strings with Ablo's control plane, encrypted. Ablo's runtime uses them to read and
125
+ write your database. The admin credential you pass to `--url` is used on this
126
+ machine only and never persisted. The role passwords are generated for you and
127
+ never printed — rotate them any time with `ablo connect --rotate`.
128
+
129
+ Your **app** holds only the API key — never a connection string:
122
130
 
123
131
  ```bash
124
132
  # .env — server runtime only, never the browser
125
- DATABASE_URL=postgres://ablo_replicator:<password>@host:5432/db?sslmode=require
126
133
  ABLO_API_KEY=sk_test_...
127
134
  ```
128
135
 
@@ -137,9 +144,9 @@ export const ablo = Ablo({
137
144
  });
138
145
  ```
139
146
 
140
- The full setup, the honest footprint (publication + slot + `REPLICATION` role +
141
- the `wal_level` restart + slot/WAL retention Ablo monitors), and the Preview
142
- status of the WAL runtime are in [Connect Your Database](./data-sources.md).
147
+ The full setup, the honest footprint (publication + slot + the `REPLICATION` and
148
+ writer roles + the `wal_level` restart + slot/WAL retention Ablo monitors), and the
149
+ Preview status are in [Connect Your Database](./data-sources.md).
143
150
 
144
151
  ## 4. Push the schema, then map it to tables
145
152
 
@@ -102,11 +102,13 @@ the fresh row. Reads stay open; only acting on the row serializes.
102
102
 
103
103
  ## Storage boundary
104
104
 
105
- Every schema model is backed by your own database. There are three start states,
106
- all covered in [Connect Your Database](./data-sources.md) (the single source of
107
- truth): the sandbox (`apiKey` only, no database), logical replication set up with
108
- `npx ablo connect` (Ablo tails your WAL), or a signed Data Source endpoint where
109
- your app keeps the database credential and commits each write itself.
105
+ Every schema model is backed by your own database, and you write to it through
106
+ `ablo.<model>`. There are three start states, all covered in [Connect Your
107
+ Database](./data-sources.md) (the single source of truth): the sandbox (`apiKey`
108
+ only, no database Ablo keeps your rows in its own log), `npx ablo connect` (a
109
+ scoped writer role plus logical replication, so Ablo writes your rows and confirms
110
+ them over the WAL), or a signed Data Source endpoint when your database can't grant
111
+ replication.
110
112
 
111
113
  Your database connects out of band, so the client holds only `ABLO_API_KEY` —
112
114
  never a connection string. Browser code goes through `<AbloProvider>` or a scoped
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.30.2",
3
+ "version": "0.31.0",
4
4
  "description": "The Collaboration Layer For AI Agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",