@abloatai/ablo 0.29.3 → 0.30.1
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 +31 -0
- package/README.md +3 -2
- package/dist/BaseSyncedStore.js +3 -1
- package/dist/Database.js +29 -2
- package/dist/SyncClient.d.ts +5 -3
- package/dist/SyncClient.js +8 -4
- package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
- package/dist/ai-sdk/coordinatedTool.js +0 -1
- package/dist/ai-sdk/coordinationContext.js +1 -5
- package/dist/cli.cjs +377 -84
- package/dist/client/Ablo.d.ts +1 -1
- package/dist/client/Ablo.js +24 -36
- package/dist/client/auth.d.ts +8 -18
- package/dist/client/auth.js +16 -96
- package/dist/client/createModelProxy.d.ts +16 -11
- package/dist/client/createModelProxy.js +162 -20
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/httpTransport.js +263 -89
- package/dist/client/options.d.ts +0 -15
- package/dist/client/resourceTypes.d.ts +13 -26
- package/dist/client/schemaConfig.js +5 -0
- package/dist/client/writeOptionsSchema.d.ts +1 -0
- package/dist/client/writeOptionsSchema.js +11 -2
- package/dist/client/wsMutationExecutor.d.ts +3 -5
- package/dist/coordination/schema.d.ts +17 -13
- package/dist/coordination/schema.js +36 -10
- package/dist/errorCodes.d.ts +9 -4
- package/dist/errorCodes.js +10 -5
- package/dist/errors.js +4 -9
- package/dist/index.d.ts +4 -0
- package/dist/interfaces/index.d.ts +31 -19
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/schema.d.ts +13 -0
- package/dist/schema/select.js +7 -0
- package/dist/schema/syncDeltaRow.d.ts +31 -13
- package/dist/schema/syncDeltaRow.js +33 -15
- package/dist/schema/tenancy.d.ts +52 -16
- package/dist/schema/tenancy.js +86 -25
- package/dist/server/commit.d.ts +8 -24
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/readConfig.d.ts +10 -0
- package/dist/source/adapter.d.ts +26 -15
- package/dist/source/adapter.js +5 -3
- package/dist/source/adapters/drizzle.js +35 -9
- package/dist/source/adapters/kysely.d.ts +33 -61
- package/dist/source/adapters/kysely.js +170 -177
- package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
- package/dist/source/adapters/kyselyMutationCore.js +125 -0
- package/dist/source/adapters/memory.d.ts +1 -1
- package/dist/source/adapters/memory.js +24 -10
- package/dist/source/adapters/prisma.js +35 -8
- package/dist/source/conformance.d.ts +26 -28
- package/dist/source/conformance.js +142 -69
- package/dist/source/contract.d.ts +46 -4
- package/dist/source/contract.js +66 -5
- package/dist/source/factory.d.ts +3 -3
- package/dist/source/factory.js +24 -3
- package/dist/source/idempotency.d.ts +46 -0
- package/dist/source/idempotency.js +129 -0
- package/dist/source/index.d.ts +6 -4
- package/dist/source/index.js +5 -3
- package/dist/source/migrations.d.ts +9 -5
- package/dist/source/migrations.js +39 -6
- package/dist/source/types.d.ts +88 -14
- package/dist/source/types.js +16 -0
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +0 -1
- package/dist/sync/BootstrapFetcher.js +7 -0
- package/dist/sync/SyncWebSocket.d.ts +5 -27
- package/dist/sync/awaitClaimGrant.d.ts +6 -0
- package/dist/sync/awaitClaimGrant.js +12 -2
- package/dist/sync/commitFrames.d.ts +2 -4
- package/dist/sync/commitFrames.js +1 -0
- package/dist/sync/createClaimStream.d.ts +1 -1
- package/dist/sync/createClaimStream.js +13 -15
- package/dist/sync/deltaPipeline.d.ts +1 -1
- package/dist/sync/deltaPipeline.js +1 -1
- package/dist/sync/participants.d.ts +2 -3
- package/dist/sync/participants.js +2 -2
- package/dist/sync/wsFrameHandlers.js +35 -15
- package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
- package/dist/testing/mocks/MockMutationExecutor.js +53 -1
- package/dist/transactions/TransactionQueue.d.ts +61 -9
- package/dist/transactions/TransactionQueue.js +502 -114
- package/dist/transactions/commitEnvelope.d.ts +4 -0
- package/dist/transactions/commitEnvelope.js +25 -0
- package/dist/transactions/commitPayload.d.ts +23 -5
- package/dist/transactions/commitPayload.js +3 -0
- package/dist/transactions/deltaConfirmation.d.ts +2 -1
- package/dist/transactions/deltaConfirmation.js +22 -4
- package/dist/transactions/durableWriteStore.d.ts +8 -1
- package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
- package/dist/transactions/httpCommitEnvelope.js +30 -0
- package/dist/transactions/replayValidation.d.ts +4 -0
- package/dist/transactions/replayValidation.js +6 -0
- package/dist/types/streams.d.ts +29 -17
- package/dist/wire/commit.d.ts +671 -0
- package/dist/wire/commit.js +300 -0
- package/dist/wire/delta.d.ts +4 -0
- package/dist/wire/delta.js +9 -0
- package/dist/wire/frames.d.ts +17 -45
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/index.js +4 -0
- package/docs/agent-messaging.md +4 -5
- package/docs/agents.md +1 -2
- package/docs/api-keys.md +1 -2
- package/docs/client-behavior.md +4 -6
- package/docs/coordination.md +64 -9
- package/docs/data-sources.md +4 -7
- package/docs/integration-guide.md +6 -8
- package/docs/migration.md +0 -3
- package/docs/quickstart.md +7 -8
- package/docs/schema-contract.md +7 -8
- package/package.json +4 -4
- package/dist/client/registerDataSource.d.ts +0 -19
- package/dist/client/registerDataSource.js +0 -66
package/docs/coordination.md
CHANGED
|
@@ -197,7 +197,7 @@ a model row. It's what `claim.state()` returns and what observers render.
|
|
|
197
197
|
| `id` | `string` | The claim id (distinct from the target row id). |
|
|
198
198
|
| `status` | `ClaimStatus` | `'active' \| 'queued' \| 'committed' \| 'expired' \| 'canceled'`. `active` = the holder; `queued` = waiting in line behind it. The other three are terminal states you only see on a claim you just finished — `committed` (released after a successful write), `expired` (TTL lapsed), `canceled` (released early). |
|
|
199
199
|
| `target` | `EntityRef` | What is being coordinated (`{ model, id, field? }`). |
|
|
200
|
-
| `
|
|
200
|
+
| `description` | `string` | Peer-visible description of the work — the sentence another participant reads to decide whether to wait or move on (`'rewriting the risk section'`). Defaults to `'editing'`. |
|
|
201
201
|
| `heldBy` | `string` | Participant holding (or waiting on) it (e.g. `'agent:forecaster'`). |
|
|
202
202
|
| `participantKind` | `'user' \| 'agent' \| 'system'` | Who's behind it — a human (`user`), an AI (`agent`), or automated infrastructure (`system`). |
|
|
203
203
|
| `position` | `number?` | 0-based place in the FIFO line — present only when `status: 'queued'` (`0` = next behind the holder). |
|
|
@@ -209,7 +209,7 @@ a model row. It's what `claim.state()` returns and what observers render.
|
|
|
209
209
|
"id": "claim_8fJ2",
|
|
210
210
|
"status": "active",
|
|
211
211
|
"target": { "model": "weatherReports", "id": "report_stockholm" },
|
|
212
|
-
"
|
|
212
|
+
"description": "editing",
|
|
213
213
|
"heldBy": "agent:forecaster",
|
|
214
214
|
"participantKind": "agent",
|
|
215
215
|
"createdAt": 1748160000000,
|
|
@@ -254,7 +254,7 @@ so two claimers can't both think they won.
|
|
|
254
254
|
| name | type | required | description |
|
|
255
255
|
|---|---|---|---|
|
|
256
256
|
| `id` | `string` | yes | The row id — same id as `retrieve` / `update`. |
|
|
257
|
-
| `options.
|
|
257
|
+
| `options.description` | `string` | no | Peer-visible description of the work, shown to observers (default `'editing'`). |
|
|
258
258
|
| `options.field` | `string` | no | Field-level target, for fine-grained claimed-state badges. |
|
|
259
259
|
| `options.queue` | `boolean` | no | `true` (default) queues and waits for the lease. `false` is fail-fast — if another participant holds the row, reject immediately with `AbloClaimedError('entity_claimed')` instead of queuing (claim-or-skip, for work dedup where waiting would double-process). |
|
|
260
260
|
| `options.maxQueueDepth` | `number` | no | Backpressure: reject with `AbloClaimedError('queue_too_deep')` instead of joining a line already `>= maxQueueDepth` deep. Omit to wait however deep the queue is. |
|
|
@@ -278,7 +278,17 @@ const weather = await weatherAgent.getWeather(report.location);
|
|
|
278
278
|
await ablo.weatherReports.update({ id: report.id, data: { forecast: weather } });
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
-
The claim releases when the `await using` scope exits — on return
|
|
281
|
+
The claim releases when the `await using` scope exits — **on return and on
|
|
282
|
+
throw.** The "on throw" is the whole reason to bind it with `await using`: if the
|
|
283
|
+
work between the claim and the write fails — the agent call errors, validation
|
|
284
|
+
rejects, you decide not to write — the scope unwinds, the lease is released, and
|
|
285
|
+
the next waiter is promoted, with no `finally` to remember. And nothing reaches
|
|
286
|
+
the server until `update`, so a failure *before* the write leaves the row exactly
|
|
287
|
+
as it was: claiming and committing are separate steps, so a failure between them
|
|
288
|
+
has nothing to roll back. (A failure *after* a successful write leaves that write
|
|
289
|
+
committed — pass an idempotency key on the write if you replay the block.) The
|
|
290
|
+
lower-level [`claim.release`](#claimrelease) shows the manual `try/finally`
|
|
291
|
+
equivalent for when you hold a claim without `await using`.
|
|
282
292
|
|
|
283
293
|
### Claim-gated reads
|
|
284
294
|
|
|
@@ -334,7 +344,7 @@ is free.
|
|
|
334
344
|
|
|
335
345
|
```ts
|
|
336
346
|
const who = ablo.weatherReports.claim.state({ id: 'report_stockholm' });
|
|
337
|
-
if (who) console.log(`${who.heldBy} is ${who.
|
|
347
|
+
if (who) console.log(`${who.heldBy} is ${who.description}`);
|
|
338
348
|
```
|
|
339
349
|
|
|
340
350
|
Returns the active claim state when the row is held, or `null` when it's free:
|
|
@@ -344,7 +354,7 @@ Returns the active claim state when the row is held, or `null` when it's free:
|
|
|
344
354
|
"id": "claim_8fJ2",
|
|
345
355
|
"status": "active",
|
|
346
356
|
"target": { "model": "weatherReports", "id": "report_stockholm" },
|
|
347
|
-
"
|
|
357
|
+
"description": "editing",
|
|
348
358
|
"heldBy": "agent:forecaster",
|
|
349
359
|
"participantKind": "agent",
|
|
350
360
|
"expiresAt": 1748160030000
|
|
@@ -405,7 +415,7 @@ Releasing **promotes the head of the queue**: the next waiter receives the claim
|
|
|
405
415
|
**Example**
|
|
406
416
|
|
|
407
417
|
```ts
|
|
408
|
-
const claim = await ablo.weatherReports.claim({ id: 'report_stockholm',
|
|
418
|
+
const claim = await ablo.weatherReports.claim({ id: 'report_stockholm', description: 'reviewing' });
|
|
409
419
|
const report = claim.data;
|
|
410
420
|
try {
|
|
411
421
|
const ok = await reviewExternally(report);
|
|
@@ -435,7 +445,7 @@ third of the TTL until release:
|
|
|
435
445
|
```ts
|
|
436
446
|
await using claim = await ablo.reports.claim({
|
|
437
447
|
id: 'report_q3',
|
|
438
|
-
|
|
448
|
+
description: 'generating',
|
|
439
449
|
ttl: '5m',
|
|
440
450
|
heartbeat: true, // or an explicit cadence: heartbeat: '2m'
|
|
441
451
|
onHeartbeatLost: () => abortWork(),
|
|
@@ -475,6 +485,51 @@ A stateless worker holding **many** rows beats them all in one round trip:
|
|
|
475
485
|
entry per extended lease. This is the socketless twin of the realtime
|
|
476
486
|
keepalive, which already renews every held lease on each ping.
|
|
477
487
|
|
|
488
|
+
### durability — what a claim survives
|
|
489
|
+
|
|
490
|
+
A lease belongs to your **identity** — the participant behind the credential —
|
|
491
|
+
not to the socket it was claimed on; the server keys each lease by participant
|
|
492
|
+
and `claimId`. That one fact decides what a claim lives through.
|
|
493
|
+
|
|
494
|
+
**A brief blip is transparent.** The realtime client reconnects on its own
|
|
495
|
+
(exponential backoff), and on each reconnect it re-announces every claim it
|
|
496
|
+
still holds, so the server renews those leases and peers never see them flicker.
|
|
497
|
+
A heartbeat that would land while the socket is momentarily down is skipped
|
|
498
|
+
rather than failed — the next tick retries once the connection is back. Nothing
|
|
499
|
+
to write: hold the claim and keep working.
|
|
500
|
+
|
|
501
|
+
**A crashed holder frees the claim quickly — and it is the keepalive, not the
|
|
502
|
+
TTL, that does it.** A dead holder is caught whichever way fires first: a clean
|
|
503
|
+
socket close releases immediately, and a silent socket that never sent a close
|
|
504
|
+
frame (a crashed tab, a dropped NAT) is reaped on the keepalive cycle (a ~30s
|
|
505
|
+
ping / 10s pong window) and released then. Either way the next waiter is
|
|
506
|
+
promoted within tens of seconds. This reclaim is **per-connection**, and it runs
|
|
507
|
+
whether or not the TTL is anywhere near lapsing. Release fires only when your
|
|
508
|
+
**last** connection goes, so a second connection under the same identity keeps
|
|
509
|
+
the claim held.
|
|
510
|
+
|
|
511
|
+
**The TTL is the deeper floor — for when the server itself restarts.** The live
|
|
512
|
+
claim roster is held in memory, so a server restart would lose it; the durable
|
|
513
|
+
lease in the coordination store carries the TTL, and a reconnecting client
|
|
514
|
+
re-announces its claims before that TTL lapses. So size `ttl` to cover a
|
|
515
|
+
deploy or restart window, not your work duration — beating covers the work
|
|
516
|
+
duration.
|
|
517
|
+
|
|
518
|
+
**To hold a claim across a holder crash, give it a durable identity.** A claim
|
|
519
|
+
that must outlive a single failure belongs to a process that stays up — a
|
|
520
|
+
backend worker or agent with its own credential — rather than one ephemeral
|
|
521
|
+
browser tab. On reconnect the SDK re-announces it; if the row was granted onward
|
|
522
|
+
while you were gone, that re-announce comes back as `AbloClaimedError`
|
|
523
|
+
(`claim_lost`) — re-claim (you rejoin the line fairly) and retry from the fresh
|
|
524
|
+
snapshot.
|
|
525
|
+
|
|
526
|
+
| the holder… | what happens to the claim |
|
|
527
|
+
| --- | --- |
|
|
528
|
+
| blips, then reconnects within the window | renewed automatically on reconnect — no interruption |
|
|
529
|
+
| crashes or drops for good | released within one keepalive cycle; the queue advances |
|
|
530
|
+
| still has a second live connection | survives — release fires only on the last connection |
|
|
531
|
+
| loses the server to a restart | rides the TTL in the coordination store; re-announced on reconnect |
|
|
532
|
+
|
|
478
533
|
### `watch` — presence for a set of rows
|
|
479
534
|
|
|
480
535
|
Reading or claiming a row auto-enrolls you in its sync group, which is enough for
|
|
@@ -548,7 +603,7 @@ inspect the `code`.
|
|
|
548
603
|
|
|
549
604
|
| error | `code` | thrown when | carries |
|
|
550
605
|
|---|---|---|---|
|
|
551
|
-
| `AbloClaimedError` | `claim_lost` | A held/queued claim was taken away
|
|
606
|
+
| `AbloClaimedError` | `claim_lost` | A held/queued claim was taken away — the holder disconnected (reaped on the keepalive cycle), went silent past its TTL, or was revoked — while you were holding or waiting. | `claims?` |
|
|
552
607
|
| `AbloClaimedError` | `claim_queued` | **HTTP transport only.** A contended `claim` (default `queue: true`) could not block-wait for the lease (no socket), so it rejected immediately instead of queueing. Retryable — re-attempt the claim. | `claims?` |
|
|
553
608
|
| `AbloClaimedError` | `grant_timeout` | The optional `timeoutMs` elapsed while you were still queued for a grant. | `claims?` |
|
|
554
609
|
| `AbloClaimedError` | `queue_too_deep` | `claim` was passed `maxQueueDepth` and the wait line was already that deep when you tried to join — fail-fast instead of waiting. | `claims?` |
|
package/docs/data-sources.md
CHANGED
|
@@ -205,12 +205,9 @@ rows.
|
|
|
205
205
|
> connecting a new database. They are the seams that caused painful onboarding,
|
|
206
206
|
> and `ablo connect` exists precisely to replace them.
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
**connection string** to operate directly (`databaseUrl` on the client, committing
|
|
210
|
-
writes itself behind row-level security), or by exposing a **signed Data Source
|
|
208
|
+
This older shape connected Ablo to a database by exposing a **signed Data Source
|
|
211
209
|
endpoint** built from an ORM adapter (`prismaDataSource` / `drizzleDataSource`,
|
|
212
210
|
with `ablo_outbox` / `ablo_idempotency` bookkeeping and a reverse-channel
|
|
213
|
-
connector for VPCs).
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
`ablo connect` at the next opportunity.
|
|
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.
|
|
@@ -48,14 +48,12 @@ Every schema model is backed by **your own database**. The SDK call shape is the
|
|
|
48
48
|
same everywhere.
|
|
49
49
|
|
|
50
50
|
In this guide — an app that already owns its backend and database — keep
|
|
51
|
-
`DATABASE_URL` inside your app and
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
shape are the other two start states — [Connect Your Database](./data-sources.md)
|
|
58
|
-
is the single source of truth for all three.
|
|
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
|
|
55
|
+
client never sees a connection string. [Connect Your Database](./data-sources.md)
|
|
56
|
+
is the single source of truth for both paths.
|
|
59
57
|
|
|
60
58
|
## Test With Sandboxes
|
|
61
59
|
|
package/docs/migration.md
CHANGED
|
@@ -234,9 +234,6 @@ The flat coordination methods are gone; everything lives under `claim`.
|
|
|
234
234
|
+ await ablo.task.claim.release(id)
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
This release also added the `databaseUrl` option for the direct Postgres
|
|
238
|
-
connector — additive, no migration required.
|
|
239
|
-
|
|
240
237
|
---
|
|
241
238
|
|
|
242
239
|
## 0.7.0 — legacy React hooks removed
|
package/docs/quickstart.md
CHANGED
|
@@ -215,11 +215,13 @@ of clobbering. Normal reads still work while the claim is held. If a server read
|
|
|
215
215
|
should not return a row while someone else is mid-edit, pass `ifClaimed: 'fail'`
|
|
216
216
|
to error out instead. Reads never block on a claim — to wait for a row to free
|
|
217
217
|
up, `claim({ id })` it (the claim queues fairly behind the holder).
|
|
218
|
-
|
|
218
|
+
Bind the handle with `await using` and the claim releases itself when the scope
|
|
219
|
+
exits — on success or on a throw, so a failing agent call never leaves the row
|
|
220
|
+
locked.
|
|
219
221
|
|
|
220
222
|
```ts
|
|
221
223
|
// Claim the row so other participants serialize behind us while we work.
|
|
222
|
-
|
|
224
|
+
await using handle = await ablo.weatherReports.claim({
|
|
223
225
|
id: 'weather_stockholm',
|
|
224
226
|
reason: 'checking_weather',
|
|
225
227
|
ttl: '2m',
|
|
@@ -236,15 +238,13 @@ await ablo.weatherReports.update({
|
|
|
236
238
|
forecast: weather.summary,
|
|
237
239
|
},
|
|
238
240
|
});
|
|
239
|
-
|
|
240
|
-
await handle.release();
|
|
241
|
+
// scope exit releases the claim — no manual release, even if the work threw
|
|
241
242
|
```
|
|
242
243
|
|
|
243
244
|
Ablo does not fetch the weather. If another participant already holds the row,
|
|
244
245
|
`claim` waits for them to finish, re-reads, and then hands you the fresh row.
|
|
245
246
|
While you hold the claim, `update({ id, data })` rejects with `AbloStaleContextError`
|
|
246
|
-
if someone else changed the row first — so you never overwrite work you didn't
|
|
247
|
-
see. Call `handle.release()` once your work is done.
|
|
247
|
+
if someone else changed the row first — so you never overwrite work you didn't see.
|
|
248
248
|
|
|
249
249
|
## Multiplayer and claimed work
|
|
250
250
|
|
|
@@ -262,9 +262,8 @@ if (active) {
|
|
|
262
262
|
console.log(`${active.heldBy} is ${active.reason}`);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
await using handle = await ablo.weatherReports.claim({ id: 'weather_stockholm' });
|
|
266
266
|
await ablo.weatherReports.update({ id: handle.data.id, data: { status: 'ready' } });
|
|
267
|
-
await handle.release();
|
|
268
267
|
```
|
|
269
268
|
|
|
270
269
|
Use `{ queue: false }` on `claim` when work should be skipped instead of queued
|
package/docs/schema-contract.md
CHANGED
|
@@ -104,14 +104,13 @@ the fresh row. Reads stay open; only acting on the row serializes.
|
|
|
104
104
|
|
|
105
105
|
Every schema model is backed by your own database. There are three start states,
|
|
106
106
|
all covered in [Connect Your Database](./data-sources.md) (the single source of
|
|
107
|
-
truth): the sandbox (`apiKey` only, no database),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
goes through `<AbloProvider>` or a scoped session route, never a raw API key.
|
|
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.
|
|
110
|
+
|
|
111
|
+
Your database connects out of band, so the client holds only `ABLO_API_KEY` —
|
|
112
|
+
never a connection string. Browser code goes through `<AbloProvider>` or a scoped
|
|
113
|
+
session route, never a raw API key.
|
|
115
114
|
|
|
116
115
|
## Rules of thumb
|
|
117
116
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/ablo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "The Collaboration Layer For AI Agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -136,9 +136,9 @@
|
|
|
136
136
|
"prepack": "node scripts/strip-source-condition.mjs",
|
|
137
137
|
"postpack": "node scripts/restore-source-condition.mjs",
|
|
138
138
|
"pack:check": "node scripts/pack-check.mjs",
|
|
139
|
-
"lint": "npm run lint:imports && npm run lint:errors && npm run lint:docs && npm run lint:
|
|
140
|
-
"build:docs": "node scripts/build-
|
|
141
|
-
"lint:
|
|
139
|
+
"lint": "npm run lint:imports && npm run lint:errors && npm run lint:docs && npm run lint:docs-site",
|
|
140
|
+
"build:docs": "node scripts/build-blume-docs.mjs",
|
|
141
|
+
"lint:docs-site": "node scripts/build-blume-docs.mjs --check",
|
|
142
142
|
"lint:imports": "node scripts/check-js-extensions.mjs",
|
|
143
143
|
"generate:errors": "tsx scripts/generate-error-docs.mts",
|
|
144
144
|
"lint:errors": "tsx scripts/check-error-docs.mts",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface RegisterDataSourceInput {
|
|
2
|
-
/** The HTTP API base, for example `https://api.abloatai.com/api`. */
|
|
3
|
-
readonly baseUrl: string;
|
|
4
|
-
/** The secret key (`sk_…`) used to authenticate the call and derive the organization. */
|
|
5
|
-
readonly apiKey: string | null;
|
|
6
|
-
/** The Postgres connection string to register. */
|
|
7
|
-
readonly databaseUrl: string;
|
|
8
|
-
/** An optional Postgres schema; the server defaults to `public`. */
|
|
9
|
-
readonly schema?: string;
|
|
10
|
-
/** A custom fetch implementation for tests, proxies, or unusual runtimes. */
|
|
11
|
-
readonly fetchImpl?: typeof fetch;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Posts the connection string to the data-source registration route. Resolves once
|
|
15
|
-
* the organization's data plane points at this database; otherwise throws an
|
|
16
|
-
* {@link AbloError} with code `datasource_registration_failed`, so `ready()`
|
|
17
|
-
* surfaces the failure instead of quietly bootstrapping against the wrong store.
|
|
18
|
-
*/
|
|
19
|
-
export declare function registerDataSource(input: RegisterDataSourceInput): Promise<void>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registers a direct database connection as an organization's data source.
|
|
3
|
-
*
|
|
4
|
-
* When a client is constructed with `databaseUrl`, the SDK calls this before
|
|
5
|
-
* bootstrap so the server points the organization's data plane at that connection.
|
|
6
|
-
*
|
|
7
|
-
* It posts to `POST /v1/datasources`, falling back to the older
|
|
8
|
-
* `POST /v1/datasource` route on a 404 so registration still works against an
|
|
9
|
-
* earlier server.
|
|
10
|
-
*
|
|
11
|
-
* The organization is derived on the server from the API key; the caller never
|
|
12
|
-
* sends an organization id. The connection string is sent once over TLS and is
|
|
13
|
-
* never echoed back — the server stores it as a secret and returns only a safe
|
|
14
|
-
* projection of the data source (host, database, schema).
|
|
15
|
-
*/
|
|
16
|
-
import { AbloError } from '../errors.js';
|
|
17
|
-
/**
|
|
18
|
-
* Posts the connection string to the data-source registration route. Resolves once
|
|
19
|
-
* the organization's data plane points at this database; otherwise throws an
|
|
20
|
-
* {@link AbloError} with code `datasource_registration_failed`, so `ready()`
|
|
21
|
-
* surfaces the failure instead of quietly bootstrapping against the wrong store.
|
|
22
|
-
*/
|
|
23
|
-
export async function registerDataSource(input) {
|
|
24
|
-
if (!input.apiKey) {
|
|
25
|
-
throw new AbloError('databaseUrl requires an apiKey to register the database connection (the org is derived from the key).', { code: 'datasource_registration_failed' });
|
|
26
|
-
}
|
|
27
|
-
const doFetch = input.fetchImpl ?? fetch;
|
|
28
|
-
const base = input.baseUrl.replace(/\/+$/, '');
|
|
29
|
-
const body = JSON.stringify({
|
|
30
|
-
connectionString: input.databaseUrl,
|
|
31
|
-
...(input.schema ? { schema: input.schema } : {}),
|
|
32
|
-
});
|
|
33
|
-
const post = async (endpoint) => {
|
|
34
|
-
try {
|
|
35
|
-
return await doFetch(endpoint, {
|
|
36
|
-
method: 'POST',
|
|
37
|
-
headers: {
|
|
38
|
-
'content-type': 'application/json',
|
|
39
|
-
authorization: `Bearer ${input.apiKey}`,
|
|
40
|
-
},
|
|
41
|
-
body,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
catch (cause) {
|
|
45
|
-
throw new AbloError('Could not reach the Ablo API to register the database connection.', {
|
|
46
|
-
code: 'datasource_registration_failed',
|
|
47
|
-
cause,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
let response = await post(`${base}/v1/datasources`);
|
|
52
|
-
if (response.status === 404) {
|
|
53
|
-
// The newer route is absent on an older server; use the earlier one.
|
|
54
|
-
response = await post(`${base}/v1/datasource`);
|
|
55
|
-
}
|
|
56
|
-
if (!response.ok) {
|
|
57
|
-
let detail = '';
|
|
58
|
-
try {
|
|
59
|
-
detail = (await response.text()).slice(0, 500);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// ignore body read failures — the status alone is enough to fail loud
|
|
63
|
-
}
|
|
64
|
-
throw new AbloError(`Database connection registration failed (HTTP ${response.status}). ${detail}`, { code: 'datasource_registration_failed', httpStatus: response.status });
|
|
65
|
-
}
|
|
66
|
-
}
|