@abloatai/ablo 0.35.0 → 0.36.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 +713 -629
- package/README.md +56 -519
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +24 -4
- package/dist/BaseSyncedStore.js +53 -37
- package/dist/Database.d.ts +8 -20
- package/dist/Database.js +61 -59
- package/dist/InstanceCache.d.ts +6 -2
- package/dist/InstanceCache.js +18 -16
- package/dist/Model.d.ts +17 -7
- package/dist/Model.js +17 -7
- package/dist/ModelRegistry.d.ts +4 -0
- package/dist/ModelRegistry.js +17 -15
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/SyncClient.d.ts +5 -15
- package/dist/SyncClient.js +60 -57
- package/dist/client/Ablo.js +30 -19
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +8 -2
- package/dist/client/createModelProxy.d.ts +21 -1
- package/dist/client/createModelProxy.js +123 -57
- package/dist/client/humans.d.ts +30 -9
- package/dist/client/humans.js +45 -19
- package/dist/client/reactiveEngine.d.ts +16 -11
- package/dist/client/reactiveEngine.js +113 -335
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +23 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/docs/catalog.js +6 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/query/client.d.ts +3 -0
- package/dist/query/client.js +6 -5
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +5 -2
- package/dist/react/context.d.ts +2 -2
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAblo.d.ts +9 -2
- package/dist/react/useAblo.js +25 -7
- package/dist/schema/coordination.js +5 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/select.js +3 -0
- package/dist/schema/serialize.js +3 -0
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +13 -12
- package/dist/{core → stores}/StoreManager.d.ts +5 -3
- package/dist/{core → stores}/StoreManager.js +24 -22
- package/dist/stores/SyncActionStore.d.ts +3 -1
- package/dist/stores/SyncActionStore.js +9 -7
- package/dist/sync/BootstrapFetcher.d.ts +4 -0
- package/dist/sync/BootstrapFetcher.js +28 -26
- package/dist/sync/OnDemandLoader.d.ts +3 -0
- package/dist/sync/OnDemandLoader.js +1 -0
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +19 -2
- package/dist/sync/participants.js +3 -1
- package/dist/sync/schemas.d.ts +2 -1
- package/dist/sync/schemas.js +3 -3
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/auth/capability.d.ts +35 -0
- package/dist/transaction/auth/capability.js +25 -0
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/index.d.ts +4 -4
- package/dist/transaction/coordination/index.js +4 -3
- package/dist/transaction/coordination/locator.d.ts +23 -2
- package/dist/transaction/coordination/locator.js +22 -2
- package/dist/transaction/coordination/schema.d.ts +125 -62
- package/dist/transaction/coordination/schema.js +228 -64
- package/dist/transaction/coordination/targetConflict.js +32 -28
- package/dist/transaction/errorCodes.d.ts +2 -2
- package/dist/transaction/errorCodes.js +13 -9
- package/dist/transaction/plugin.d.ts +95 -2
- package/dist/transaction/plugin.js +21 -2
- package/dist/transaction/resources/httpResources.d.ts +57 -2
- package/dist/transaction/resources/modelOperations.d.ts +148 -40
- package/dist/transaction/resources/where.d.ts +16 -0
- package/dist/transaction/resources/where.js +45 -0
- package/dist/transaction/schema/field.d.ts +12 -18
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/openapi.d.ts +16 -15
- package/dist/transaction/schema/openapi.js +186 -25
- package/dist/transaction/schema/relation.d.ts +7 -2
- package/dist/transaction/schema/schema.d.ts +27 -0
- package/dist/transaction/schema/schema.js +20 -0
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
- package/dist/transaction/transport/httpClient.d.ts +9 -1
- package/dist/transaction/transport/httpClient.js +1 -0
- package/dist/transaction/transport/httpTransport.js +156 -44
- package/dist/transaction/transport/wsTransport.d.ts +2 -4
- package/dist/transaction/transport/wsTransport.js +16 -10
- package/dist/transaction/types/streams.d.ts +10 -0
- package/dist/transaction/utils/duration.d.ts +25 -0
- package/dist/transaction/utils/duration.js +32 -0
- package/dist/transaction/wire/accountResponses.d.ts +69 -0
- package/dist/transaction/wire/accountResponses.js +36 -1
- package/dist/transaction/wire/auth.d.ts +9 -2
- package/dist/transaction/wire/auth.js +7 -1
- package/dist/transaction/wire/claims.d.ts +164 -97
- package/dist/transaction/wire/claims.js +126 -28
- package/dist/transaction/wire/commit.d.ts +1 -1
- package/dist/transaction/wire/feedEvent.d.ts +27 -0
- package/dist/transaction/wire/feedEvent.js +27 -1
- package/dist/transaction/wire/frames.d.ts +2 -2
- package/dist/transaction/wire/inboundFrames.d.ts +12 -2
- package/dist/transaction/wire/index.d.ts +10 -6
- package/dist/transaction/wire/index.js +12 -3
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
- package/dist/transactions/mutations/MutationQueue.js +55 -45
- package/dist/transactions/mutations/commitPayload.d.ts +3 -2
- package/dist/transactions/mutations/commitPayload.js +5 -5
- package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
- package/dist/transactions/mutations/deltaConfirmation.js +10 -8
- package/dist/transactions/mutations/replayValidation.d.ts +2 -1
- package/dist/transactions/mutations/replayValidation.js +3 -2
- package/dist/{core → views}/QueryView.d.ts +1 -1
- package/dist/{core → views}/QueryView.js +1 -1
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +6 -6
- package/docs/api.md +5 -43
- package/docs/audit.md +4 -3
- package/docs/cli.md +11 -11
- package/docs/client-behavior.md +4 -4
- package/docs/concurrency-convention.md +28 -42
- package/docs/coordination.md +235 -83
- package/docs/data-sources.md +4 -4
- package/docs/debugging.md +34 -12
- package/docs/deployment.md +8 -8
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/groups.md +57 -3
- package/docs/guarantees.md +37 -10
- package/docs/how-it-works.md +29 -5
- package/docs/idempotency.md +6 -6
- package/docs/identity.md +22 -23
- package/docs/index.md +8 -8
- package/docs/integration-guide.md +14 -3
- package/docs/mcp.md +7 -7
- package/docs/migration.md +34 -15
- package/docs/projects.md +1 -1
- package/docs/react.md +19 -8
- package/docs/sessions.md +1 -1
- package/docs/webhooks.md +9 -9
- package/llms.txt +4 -4
- package/package.json +13 -20
- package/dist/cli.cjs +0 -288600
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/httpResponses.d.ts +0 -70
- package/dist/testing/fixtures/httpResponses.js +0 -90
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -186
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -72
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/docs/interaction-model.md +0 -99
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{source → transaction}/footprint.d.ts +0 -0
- /package/dist/{source → transaction}/footprint.js +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* ensures every view sorts and filters identically. The functions
|
|
6
|
-
* plain arrays and values — they hold no reference to models,
|
|
7
|
-
* reactivity system.
|
|
2
|
+
* The {@link IncrementalView} contract — the interface a live view implements
|
|
3
|
+
* to receive add/update/remove notifications — together with the sorting,
|
|
4
|
+
* matching, and binary-insertion rules every view shares. Keeping the rules in
|
|
5
|
+
* one place ensures every view sorts and filters identically. The functions
|
|
6
|
+
* here work on plain arrays and values — they hold no reference to models,
|
|
7
|
+
* pools, or the reactivity system.
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* Compares two values for sorting, tolerating `null` and `undefined`, which
|
package/docs/agents.md
CHANGED
|
@@ -47,7 +47,7 @@ and `claim`. It does **not** expose the stateful-only surface (`get` /
|
|
|
47
47
|
live connection, so with `transport: 'http'` the return type narrows and they
|
|
48
48
|
are a *compile error*, not a runtime surprise.
|
|
49
49
|
|
|
50
|
-
## Coordination
|
|
50
|
+
## Coordination: claim, queue, reorder
|
|
51
51
|
|
|
52
52
|
The differentiator. A claim is a **durable lease + FIFO wait-line** on a row —
|
|
53
53
|
"who's working on this, who's waiting" — and it's request/response, so an agent
|
package/docs/api-keys.md
CHANGED
|
@@ -21,9 +21,9 @@ Pick your row:
|
|
|
21
21
|
|
|
22
22
|
| Where your code runs | What to pass | Example |
|
|
23
23
|
|---|---|---|
|
|
24
|
-
| **Server / worker / CLI** (can hold a secret) | your secret `sk_
|
|
25
|
-
| **Browser
|
|
26
|
-
| **Browser
|
|
24
|
+
| **Server / worker / CLI** (can hold a secret) | your secret `sk_`: it defaults to `ABLO_API_KEY`, so usually pass **nothing** | `Ablo({ schema })` |
|
|
25
|
+
| **Browser: read-only** | a publishable `pk_` (safe to ship, like a Stripe `pk_`) | `Ablo({ schema, apiKey: process.env.NEXT_PUBLIC_ABLO_PUBLISHABLE_KEY })` |
|
|
26
|
+
| **Browser: writing as the signed-in user** | `authEndpoint`: the route on your own backend that mints a short-lived per-user token | `Ablo({ schema, authEndpoint: '/api/ablo-session' })` |
|
|
27
27
|
|
|
28
28
|
That's the whole story: one knob, filled by audience.
|
|
29
29
|
|
|
@@ -31,8 +31,8 @@ That's the whole story: one knob, filled by audience.
|
|
|
31
31
|
|
|
32
32
|
| Stripe | Ablo | Where it goes |
|
|
33
33
|
|---|---|---|
|
|
34
|
-
| publishable `pk_` (client-safe) | `pk_` | browser
|
|
35
|
-
| secret `sk_` (server, full) | `sk_` | server
|
|
34
|
+
| publishable `pk_` (client-safe) | `pk_` | browser: read-only |
|
|
35
|
+
| secret `sk_` (server, full) | `sk_` | server: full authority |
|
|
36
36
|
| restricted `rk_` (granular) | `rk_` | scoped agent sessions (`sessions.create({ agent, can })`) |
|
|
37
37
|
| ephemeral key (client, customer-scoped) | `ek_` | per-user browser sessions (`sessions.create({ user })`) |
|
|
38
38
|
|
|
@@ -74,7 +74,7 @@ Use API keys from trusted (server-side) runtimes:
|
|
|
74
74
|
|
|
75
75
|
Never ship a secret API key to a browser bundle.
|
|
76
76
|
|
|
77
|
-
## Publishable key (`pk_`)
|
|
77
|
+
## Publishable key (`pk_`): browser-safe, read-only
|
|
78
78
|
|
|
79
79
|
For a read-only browser experience, a publishable key is safe to ship in the
|
|
80
80
|
bundle. Like a Stripe `pk_` or a Supabase anon key, it is long-lived,
|
package/docs/api.md
CHANGED
|
@@ -120,49 +120,11 @@ blocks), and `ablo.<model>.claim.release({ id })` releases it early. The full
|
|
|
120
120
|
coordination surface is `claim.state({ id })` / `claim.queue({ id })` /
|
|
121
121
|
`claim.release({ id })` / `claim.reorder({ id, order })` hanging off `claim`.
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| `id` | string | Unique identifier for the claim. |
|
|
129
|
-
| `status` | `'active' \| 'queued' \| 'committed' \| 'expired' \| 'canceled'` | The whole lifecycle, in one field. `active` is the holder; `queued` is a waiter in the FIFO line behind it. |
|
|
130
|
-
| `target` | `{ type, id, field? }` | What is being coordinated. |
|
|
131
|
-
| `description` | string | Peer-visible phrase for the work in progress — `'editing'`, `'writing'`, `'reviewing the risk section'`. Defaults to `'editing'`, and rides back in the rejection a blocked writer receives. |
|
|
132
|
-
| `heldBy` | string | Participant id holding the claim. |
|
|
133
|
-
| `participantKind` | `'user' \| 'agent' \| 'system'` | Who's behind it — a human (`user`), an AI (`agent`), or automated infrastructure (`system`). |
|
|
134
|
-
| `createdAt` | number? | Ms-epoch the holder opened it. Optional — derived shapes may omit it. |
|
|
135
|
-
| `expiresAt` | number | Ms-epoch at which the server auto-expires it if the holder doesn't finish. |
|
|
136
|
-
|
|
137
|
-
```json
|
|
138
|
-
{
|
|
139
|
-
"object": "claim",
|
|
140
|
-
"id": "claim_3MtwBwLkdIwHu7ix",
|
|
141
|
-
"status": "active",
|
|
142
|
-
"target": { "type": "weatherReports", "id": "report_stockholm", "field": "status" },
|
|
143
|
-
"description": "editing",
|
|
144
|
-
"heldBy": "agent:report-writer",
|
|
145
|
-
"participantKind": "agent",
|
|
146
|
-
"expiresAt": 1716580000000
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Lifecycle
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
claim({ id }) update({ id }) lands
|
|
154
|
-
(free) ───────────▶ active ───────────────────────▶ committed
|
|
155
|
-
│
|
|
156
|
-
┌───────────┴───────────┐
|
|
157
|
-
▼ ▼
|
|
158
|
-
canceled expired
|
|
159
|
-
(release w/o write) (TTL; holder died)
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
A target is free when `ablo.<model>.claim.state({ id })` is `null`. Terminal
|
|
163
|
-
states drop out of the live stream, so a present claim is either `active` (the
|
|
164
|
-
holder) or `queued` (waiting in the FIFO line behind the holder; see
|
|
165
|
-
`claim.queue({ id })`).
|
|
123
|
+
The fields on a claim, its lifecycle diagram, and the full method surface are in
|
|
124
|
+
[Coordination](./coordination.md#the-claim-state-object), which is where that
|
|
125
|
+
object is defined. Note that the entity half of `target` is spelled `model`/`id`
|
|
126
|
+
on the SDK's model surface and `type`/`id` on the claim handle and the wait
|
|
127
|
+
line.
|
|
166
128
|
|
|
167
129
|
### Reading and claiming
|
|
168
130
|
|
package/docs/audit.md
CHANGED
|
@@ -23,11 +23,12 @@ with which key — and the chain columns that make the log tamper-evident:
|
|
|
23
23
|
capabilityId: string | null, // the API key/capability used for the write
|
|
24
24
|
capabilityLabel: string | null, // its human-readable name, for scanning the log
|
|
25
25
|
delegationChainRootUserId: string | null, // always points at a human
|
|
26
|
-
actionType:
|
|
27
|
-
modelName: string,
|
|
26
|
+
actionType: 'I' | 'U' | 'D', // insert, update, delete
|
|
27
|
+
modelName: string, // the model that changed, e.g. 'orders'
|
|
28
|
+
modelId: string, // the row that changed
|
|
28
29
|
confirmationState: 'auto' | 'previewed' | 'approved' | 'required_human_approval' | 'auto_historical',
|
|
29
30
|
diffSummary: unknown,
|
|
30
|
-
// chain columns
|
|
31
|
+
// chain columns, carried on every stored row and checked by verify (below)
|
|
31
32
|
chainSeq: number,
|
|
32
33
|
prevHash: string,
|
|
33
34
|
rowHash: string,
|
package/docs/cli.md
CHANGED
|
@@ -94,18 +94,18 @@ profiles entirely: it acts in whatever project it was minted for.
|
|
|
94
94
|
|
|
95
95
|
| Command | What it does | Flags |
|
|
96
96
|
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
97
|
-
| `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end.
|
|
98
|
-
| `ablo login` / `logout` / `status` | Authentication & status (above).
|
|
99
|
-
| `ablo mode [sandbox\|production]` | Switch active environment.
|
|
97
|
+
| `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end. |: |
|
|
98
|
+
| `ablo login` / `logout` / `status` | Authentication & status (above). |: |
|
|
99
|
+
| `ablo mode [sandbox\|production]` | Switch active environment. |: |
|
|
100
100
|
| `ablo projects list\|create\|use\|rename` | Manage projects and the active one (see [Projects](#projects)). Each project's keys/schema/data are isolated. | `--name "<display>"` (create/rename) |
|
|
101
|
-
| `ablo dev` | **Hosted
|
|
101
|
+
| `ablo dev` | **Hosted**: push the schema to your test sandbox, then watch `ablo/schema.ts` and re-push on save. | `--no-watch`, `--schema <path>`, `--export <name>`, `--url <url>` |
|
|
102
102
|
| `ablo logs` | Tail your scope's commit activity (`stripe logs tail`). Follows by default. | `-n, --tail <N>`, `--since <dur\|ts>`, `--model`, `--op`, `--json`, `--no-follow`, `--mode sandbox\|production` |
|
|
103
|
-
| `ablo push` | **Hosted
|
|
104
|
-
| `ablo migrate` | **Direct Postgres
|
|
105
|
-
| `ablo pull` | **Direct Postgres
|
|
106
|
-
| `ablo check` | **Direct Postgres
|
|
103
|
+
| `ablo push` | **Hosted**: upload the schema to Ablo; the server diffs, migrates, and activates it. | `--force`, `--rename old:new`, `--backfill model.field=value`, `--schema`, `--export`, `--url` |
|
|
104
|
+
| `ablo migrate` | **Direct Postgres**: provision just the synced models (plus the adapter's `ablo_outbox` / `ablo_idempotency`) in your own `DATABASE_URL`. Leaves your other tables alone. | `--dry-run`, `--output <file>`, `--schema`, `--export` |
|
|
105
|
+
| `ablo pull` | **Direct Postgres**: generate `defineSchema(...)` from your existing tables (read-only, like `prisma db pull`). | `--out <path>`, `--app-schema <name>`, `--import <pkg>`, `--force` |
|
|
106
|
+
| `ablo check` | **Direct Postgres**: verify your _existing_ tables fit the schema (read-only, no schema changes). | `--schema <path>`, `--export <name>`, `--app-schema <name>` |
|
|
107
107
|
| `ablo generate` | Emit TypeScript types from the schema. | `--out <path>`, `--schema`, `--export` |
|
|
108
|
-
| `ablo docs` | Read these pages for the version you installed
|
|
108
|
+
| `ablo docs` | Read these pages for the version you installed: offline, no network (see [`ablo docs`](#ablo-docs)). | `--json` |
|
|
109
109
|
|
|
110
110
|
## `ablo docs`
|
|
111
111
|
|
|
@@ -241,7 +241,7 @@ The one type map, shared by both paths (there is no second mapping):
|
|
|
241
241
|
| Zod | Postgres |
|
|
242
242
|
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
243
243
|
| `z.string()` | `TEXT` |
|
|
244
|
-
| `z.number()` | `DOUBLE PRECISION
|
|
244
|
+
| `z.number()` | `DOUBLE PRECISION`: never `INTEGER`; a Zod number may be fractional, and truncating is silent data loss |
|
|
245
245
|
| `z.boolean()` | `BOOLEAN` |
|
|
246
246
|
| `z.date()` | `TIMESTAMPTZ` |
|
|
247
247
|
| `z.enum([...])` | `TEXT` + a `CHECK (col IN (...))` constraint |
|
|
@@ -296,7 +296,7 @@ migration can't leave clients gated against tables that don't match.
|
|
|
296
296
|
|
|
297
297
|
| Variable | Purpose | Default |
|
|
298
298
|
| ------------------------------------- | ------------------------------------------------------------------------ | -------------------------- |
|
|
299
|
-
| `ABLO_API_KEY` | Authenticate without `ablo login` (CI). Always overrides the stored key.
|
|
299
|
+
| `ABLO_API_KEY` | Authenticate without `ablo login` (CI). Always overrides the stored key. |: |
|
|
300
300
|
| `ABLO_API_URL` | Control-plane / API host (`push`, `dev`, `status`). | `https://api.abloatai.com` |
|
|
301
301
|
| `ABLO_AUTH_URL` | Dashboard origin for `ablo login`'s device flow. | `https://abloatai.com` |
|
|
302
302
|
| `ABLO_CONFIG_DIR` / `XDG_CONFIG_HOME` | Where the credential file lives. | `~/.config/ablo` |
|
package/docs/client-behavior.md
CHANGED
|
@@ -34,7 +34,7 @@ Common options:
|
|
|
34
34
|
| `baseURL` | Override the hosted sync endpoint for staging or private deployments. |
|
|
35
35
|
| `persistence` | `memory` by default. Use `indexeddb` for a durable browser cache that survives reloads. |
|
|
36
36
|
| `durableWrites` | Optional crash recovery for unacknowledged agent/worker writes. Independent of the default memory cache; accepts `{ store, namespace? }`. |
|
|
37
|
-
| `transport` | `'websocket'` (default) is the live, stateful client
|
|
37
|
+
| `transport` | `'websocket'` (default) is the live, stateful client: a persistent socket, a local synced pool, and `onChange` subscriptions. `'http'` returns the **stateless** client for server-side actors (agents, workers, serverless): the same `ablo.<model>` read/write/claim surface, but each call is one HTTP round-trip with no socket. Under `'http'` the return type narrows to `AbloHttpClient`, so stateful-only methods (the `local` reads, `onChange`, `join`) are compile errors rather than runtime gaps. |
|
|
38
38
|
| `fetch` | Custom fetch implementation for tests or non-standard runtimes. |
|
|
39
39
|
| `defaultHeaders` | Extra headers attached to every HTTP request. |
|
|
40
40
|
| `defaultQuery` | Extra query parameters attached to every HTTP request. |
|
|
@@ -218,8 +218,8 @@ Only these imports are public SemVer surface:
|
|
|
218
218
|
- `@abloatai/ablo`
|
|
219
219
|
- `@abloatai/ablo/schema`
|
|
220
220
|
- `@abloatai/ablo/react`
|
|
221
|
-
- `@abloatai/ablo/testing`
|
|
222
221
|
|
|
223
222
|
`dataSource(...)` is exported from the root package for customer-owned storage
|
|
224
|
-
adapters. Everything outside the
|
|
225
|
-
apps and infrastructure.
|
|
223
|
+
adapters. Everything outside the three import paths is internal to Ablo-owned
|
|
224
|
+
apps and infrastructure. For adapter authors, `@abloatai/ablo/source/conformance`
|
|
225
|
+
is the suite that proves a storage adapter behaves correctly.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> The governing rule for how Ablo resolves concurrent writes to shared state.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
This page is the contract: the `onStale` dispositions, what a conflict is
|
|
6
|
+
checked against, and where the convention stops. The three-layer mechanics of
|
|
7
|
+
claiming live in [Coordination](./coordination.md).
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -27,8 +27,8 @@ moments in time:
|
|
|
27
27
|
|
|
28
28
|
| form | when | mechanism |
|
|
29
29
|
|---|---|---|
|
|
30
|
-
| **Claim** | *prospective
|
|
31
|
-
| **Notification** | *in-flight
|
|
30
|
+
| **Claim** | *prospective*: before you act | reserve the row; others queue. Coordinate so the conflict never forms. |
|
|
31
|
+
| **Notification** | *in-flight*: after a concurrent change | surface the changed value; the actor resolves and re-issues. |
|
|
32
32
|
|
|
33
33
|
Use a claim when you will hold the row across a slow read→reason→write gap. Use a
|
|
34
34
|
notification when you didn't, and the premise moved under you.
|
|
@@ -42,9 +42,9 @@ when it goes stale. Three modes, split by whether they **force** an outcome:
|
|
|
42
42
|
|
|
43
43
|
| mode | coercive? | what the engine does | who resolves | use when |
|
|
44
44
|
|---|---|---|---|---|
|
|
45
|
-
| `notify` | **No
|
|
46
|
-
| `reject` | **Yes
|
|
47
|
-
| `overwrite` | **Yes
|
|
45
|
+
| `notify` | **No**: surface + delegate | Holds the write (does **not** apply it); returns a `StaleNotification` with the current value. | The actor (agent or human) reconciles and re-issues. | The aligned mode: tell the actor what changed, let it solve. |
|
|
46
|
+
| `reject` | **Yes**: force-abort | Throws `AbloStaleContextError`; the batch is discarded. | The caller retries from scratch. | Hard invariants; legacy/strict callers. The current default. |
|
|
47
|
+
| `overwrite` | **Yes**: force-clobber | Overwrites blindly last-writer-wins; **no** signal. | Nobody. | You genuinely own the field and concurrent values are noise. |
|
|
48
48
|
|
|
49
49
|
> `notify` is the convention. `reject` and `overwrite` are escape hatches for the
|
|
50
50
|
> two ends — "never let this be wrong" and "never bother me." They are not the
|
|
@@ -82,9 +82,9 @@ reads: [
|
|
|
82
82
|
]
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
- **Row
|
|
85
|
+
- **Row:** did this specific row (optionally these fields) change? The literal
|
|
86
86
|
per-object premise.
|
|
87
|
-
- **Group
|
|
87
|
+
- **Group:** did *anything* in this sync group change? `group` is a sync-group
|
|
88
88
|
key (`workspace:abc`, `document:s1`, `org:X`) — the same unit a participant **watches
|
|
89
89
|
and claims**. This is the more Ablo-native granularity.
|
|
90
90
|
|
|
@@ -108,13 +108,13 @@ Shape (canonical in `coordination/schema.ts`):
|
|
|
108
108
|
|
|
109
109
|
| field | meaning |
|
|
110
110
|
|---|---|
|
|
111
|
-
| `object` | Stripe-style type tag
|
|
111
|
+
| `object` | Stripe-style type tag: `'stale_notification'` |
|
|
112
112
|
| `model`, `id` | the conflicting row (for a group dep, both are the group key) |
|
|
113
113
|
| `group?` | set when this is a group-scoped notification |
|
|
114
114
|
| `readAt` | the watermark the committer reasoned against |
|
|
115
|
-
| `observedSyncId` | the newest delta on the premise
|
|
115
|
+
| `observedSyncId` | the newest delta on the premise: re-read at/after this |
|
|
116
116
|
| `conflictingFields` | fields that moved (empty for group / whole-entity) |
|
|
117
|
-
| `currentValues` | the live values of those fields
|
|
117
|
+
| `currentValues` | the live values of those fields: the premise to reconcile against (empty for group) |
|
|
118
118
|
| `writtenBy` | `{ kind, id }` of the concurrent author, reported faithfully |
|
|
119
119
|
|
|
120
120
|
Only `notify` produces a notification (the write was held). `reject` throws and
|
|
@@ -169,7 +169,7 @@ What the convention **guarantees**, and where it **stops**:
|
|
|
169
169
|
or human) owns the resolution. The engine does not distinguish them — it is
|
|
170
170
|
actor-neutral by design.
|
|
171
171
|
|
|
172
|
-
2. **Truthfulness
|
|
172
|
+
2. **Truthfulness:** `currentValues` / `observedSyncId` reflect committed state at
|
|
173
173
|
detection time, inside the same transaction as the write. A notification is
|
|
174
174
|
never speculative.
|
|
175
175
|
|
|
@@ -185,13 +185,11 @@ What the convention **guarantees**, and where it **stops**:
|
|
|
185
185
|
so they must not be gated by `notify`.
|
|
186
186
|
|
|
187
187
|
5. **Defaults.** A plain write (no `readAt`) is last-writer-wins with **no**
|
|
188
|
-
check. A guarded write with `readAt` but no `onStale` defaults to `reject
|
|
189
|
-
(back-compat). *Open decision (§7).*
|
|
188
|
+
check. A guarded write with `readAt` but no `onStale` defaults to `reject`.
|
|
190
189
|
|
|
191
190
|
6. **Policy seam.** Custom `ConflictPolicy` functions see **write-target**
|
|
192
191
|
conflicts (`stale_context` / `claim_held`). **Batch-premise** conflicts are
|
|
193
|
-
|
|
194
|
-
seam. *Open decision (§7).*
|
|
192
|
+
resolved directly via each entry's `onStale`, not through the policy seam.
|
|
195
193
|
|
|
196
194
|
7. **Claims win when held.** A non-holder writing to a claimed row is rejected
|
|
197
195
|
(`AbloClaimedError`) regardless of `readAt` — the prospective form takes
|
|
@@ -200,29 +198,17 @@ What the convention **guarantees**, and where it **stops**:
|
|
|
200
198
|
|
|
201
199
|
---
|
|
202
200
|
|
|
203
|
-
## 7.
|
|
201
|
+
## 7. What this convention does not cover
|
|
204
202
|
|
|
205
|
-
|
|
203
|
+
Three limits worth knowing before you rely on it.
|
|
206
204
|
|
|
207
|
-
- **
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
## 8. Out of scope
|
|
218
|
-
|
|
219
|
-
- Irreversible external side-effects (§6.4) — not gated by this convention.
|
|
220
|
-
- Cross-object *serializability proof*. A batch premise is a sound check, not
|
|
221
|
-
a full precedence-graph guarantee; it catches only what the caller declared.
|
|
222
|
-
A caller that declares nothing gets **no check at all** — not write-target
|
|
223
|
-
checking, which needs a `readAt` to check against. A plain write is
|
|
224
|
-
last-writer-wins, as §6.5 says. The floor is zero, and closing that gap is the
|
|
225
|
-
subject of ADR 0018.
|
|
226
|
-
- Identity → participant-kind mapping. `writtenBy.kind` reports whatever
|
|
227
|
-
authenticated (an `sk_` key resolves to `system`, not `agent`); how identities
|
|
228
|
-
map to kinds is a separate concern.
|
|
205
|
+
- **Irreversible external side-effects.** Emails, payments, and third-party
|
|
206
|
+
calls are not gated by this convention (§6.4). The engine cannot hold or undo
|
|
207
|
+
them, so never place one behind `notify`.
|
|
208
|
+
- **A caller that declares nothing gets no check.** The batch premise catches
|
|
209
|
+
only what you declared. Write-target checking needs a `readAt` to compare
|
|
210
|
+
against, so a plain write with neither is last-writer-wins (§6.5). What you
|
|
211
|
+
declare is what is protected.
|
|
212
|
+
- **`writtenBy.kind` reports what authenticated, not what you meant.** An `sk_`
|
|
213
|
+
key resolves to `system`, not `agent`. How identities map to participant kinds
|
|
214
|
+
is a separate concern from this convention.
|