@abloatai/ablo 0.57.0 → 0.59.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.
Files changed (90) hide show
  1. package/AGENTS.md +12 -5
  2. package/CHANGELOG.md +269 -13
  3. package/README.md +6 -5
  4. package/dist/ai-sdk.d.ts +1 -1
  5. package/dist/ai-sdk.d.ts.map +1 -1
  6. package/dist/context/evidence.d.ts +6 -8
  7. package/dist/context/evidence.d.ts.map +1 -1
  8. package/dist/context/evidence.js +6 -20
  9. package/dist/context/evidence.js.map +1 -1
  10. package/dist/context/index.d.ts +23 -0
  11. package/dist/context/index.d.ts.map +1 -0
  12. package/dist/context/index.js +26 -0
  13. package/dist/context/index.js.map +1 -0
  14. package/dist/context/onChange.d.ts +9 -0
  15. package/dist/context/onChange.d.ts.map +1 -0
  16. package/dist/context/onChange.js +37 -0
  17. package/dist/context/onChange.js.map +1 -0
  18. package/dist/source-conformance.d.ts +1 -1
  19. package/dist/source-conformance.d.ts.map +1 -1
  20. package/dist/source-conformance.js +1 -1
  21. package/dist/source-conformance.js.map +1 -1
  22. package/dist/source-drizzle.d.ts +1 -1
  23. package/dist/source-drizzle.d.ts.map +1 -1
  24. package/dist/source-drizzle.js +1 -1
  25. package/dist/source-drizzle.js.map +1 -1
  26. package/dist/source-kysely.d.ts +1 -1
  27. package/dist/source-kysely.d.ts.map +1 -1
  28. package/dist/source-kysely.js +1 -1
  29. package/dist/source-kysely.js.map +1 -1
  30. package/dist/source-next.d.ts +1 -1
  31. package/dist/source-next.d.ts.map +1 -1
  32. package/dist/source-next.js +1 -1
  33. package/dist/source-next.js.map +1 -1
  34. package/docs/agents.md +38 -15
  35. package/docs/api-keys.md +6 -6
  36. package/docs/api.md +107 -34
  37. package/docs/basic-usage.md +84 -0
  38. package/docs/branch-development.md +23 -4
  39. package/docs/cli.md +16 -9
  40. package/docs/client-behavior.md +27 -29
  41. package/docs/comparison.md +63 -0
  42. package/docs/concurrency-convention.md +92 -75
  43. package/docs/context.md +76 -31
  44. package/docs/coordinate-existing-work.md +104 -0
  45. package/docs/coordination.md +93 -38
  46. package/docs/data-sources.md +12 -6
  47. package/docs/debugging.md +1 -1
  48. package/docs/deployment.md +19 -1
  49. package/docs/examples/agent-human.md +6 -18
  50. package/docs/examples/coordination-conformance.md +69 -0
  51. package/docs/examples/evidence-backed-document-pipeline.md +488 -0
  52. package/docs/examples/existing-python-backend.md +10 -13
  53. package/docs/examples/nextjs.md +2 -2
  54. package/docs/examples/scoped-agent.md +18 -1
  55. package/docs/examples/server-agent.md +2 -2
  56. package/docs/faq.md +75 -0
  57. package/docs/groups.md +19 -139
  58. package/docs/guarantees.md +8 -8
  59. package/docs/idempotency.md +3 -0
  60. package/docs/identity.md +2 -1
  61. package/docs/implement.md +61 -0
  62. package/docs/implementation-index.md +20 -0
  63. package/docs/index.md +59 -173
  64. package/docs/installation.md +77 -0
  65. package/docs/instrumentation.md +52 -0
  66. package/docs/integration-guide.md +20 -19
  67. package/docs/integrations/sandbox-runtime.md +157 -0
  68. package/docs/integrations.md +9 -0
  69. package/docs/migration.md +12 -7
  70. package/docs/operating-on-your-database.md +7 -0
  71. package/docs/options.md +172 -0
  72. package/docs/quickstart.md +25 -14
  73. package/docs/react.md +9 -9
  74. package/docs/schema-contract.md +14 -13
  75. package/docs/security.md +64 -0
  76. package/docs/sessions.md +1 -1
  77. package/examples/README.md +8 -2
  78. package/examples/agent-turn.ts +1 -1
  79. package/examples/expensive-agent-turn.ts +1 -1
  80. package/examples/stale-context-agent-turn.ts +106 -0
  81. package/llms.txt +23 -12
  82. package/package.json +7 -6
  83. package/dist/context/sources.d.ts +0 -21
  84. package/dist/context/sources.d.ts.map +0 -1
  85. package/dist/context/sources.js +0 -36
  86. package/dist/context/sources.js.map +0 -1
  87. package/dist/context.d.ts +0 -22
  88. package/dist/context.d.ts.map +0 -1
  89. package/dist/context.js +0 -33
  90. package/dist/context.js.map +0 -1
package/AGENTS.md CHANGED
@@ -1,11 +1,17 @@
1
1
  # AGENTS.md
2
2
 
3
- Ablo lets AI agents and humans safely edit the same typed data without clobbering each other. When two of them touch the same row, a "claim" makes one wait for the other instead of overwriting it. This file shows a coding assistant the one safe pattern: read a row, claim it, then write.
3
+ Ablo lets AI agents and humans safely edit the same typed data without clobbering each other. When two of them touch the same row, a "claim" makes one wait for the other instead of overwriting it. For an existing application, first preserve its named operation and persistence boundary; use the schema-backed row pattern below only when that route owns the write.
4
4
 
5
5
  Claims don't lock. If another writer holds the row, `claim` waits for them and re-reads the fresh row before handing it to you — so two writers serialize instead of clobbering.
6
6
 
7
7
  ## Start here — scaffold with `ablo init`
8
8
 
9
+ Before choosing among identifier claims, row claims, captured reads, atomic
10
+ commits, existing database writes, and Ablo-routed writes, use the
11
+ [coordinate existing work guide](./docs/coordinate-existing-work.md).
12
+ It routes existing applications to the smallest relevant example and names the
13
+ test layer that proves each guarantee.
14
+
9
15
  Don't hand-write the integration. Run the CLI; it generates the current-API schema, client, the database connection (logical replication by default, or a signed Data Source endpoint as the fallback), and (for Next.js) the browser provider + session route:
10
16
 
11
17
  - **Read the docs for THIS version:** `npx ablo docs` lists every page, `npx ablo docs <page>` prints one. They ship inside the installed package, so they describe the code in `node_modules` and work with no network. Read them instead of a docs URL — a website describes the newest release, so against a pinned version it will hand you a call your package doesn't have (`retrieve`/`list` replaced `get`/`getAll`/`getCount` in 0.35.0).
@@ -21,7 +27,7 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
21
27
 
22
28
  When you use the signed-endpoint fallback, the generated `ablo/data-source.ts` is the whole endpoint and needs no hand-editing: `dataSourceNext({ schema, apiKey, adapter: prismaDataSource(prisma, schema) })` (or `drizzleDataSource(db, schema)`). The adapter owns commit / idempotency / outbox.
23
29
 
24
- **Working on a real database?** Plain model writes are last-write-wins when no active claim applies. Use a functional update, a held claim, or `readAt` when a result depends on an earlier value. Reads are safe to inspect; raw application DDL (`ALTER TABLE …`) and a `--yes` connection cutover belong to a human. When you're unsure whether a write fits, `npx ablo check` reports the live column-by-column fit read-only, before anything runs. Full sorting rule: [Operating on Your Database](./docs/operating-on-your-database.md).
30
+ **Working on a real database?** Plain model writes are last-write-wins when no active claim applies. Use a functional update, a held claim, or `read` plus `reads` when a result depends on an earlier value. Reads are safe to inspect; raw application DDL (`ALTER TABLE …`) and a `--yes` connection cutover belong to a human. When you're unsure whether a write fits, `npx ablo check` reports the live column-by-column fit read-only, before anything runs. Full sorting rule: [Operating on Your Database](./docs/operating-on-your-database.md).
25
31
 
26
32
  ## Rule
27
33
 
@@ -35,10 +41,10 @@ The schema is the integration contract — it drives typed model clients, React
35
41
 
36
42
  Every model verb takes ONE options object. The common loop:
37
43
 
38
- 1. **Read** the row — `await ablo.<model>.get({ id })` (async; from the server) or `await ablo.<model>.list({ where })` for many. In React render, read synchronously with `useAblo((a) => a.<model>.local.get(id))`.
44
+ 1. **Get or read** the row — `get({ id })` observes; `read({ id })` declares that a later mutation depends on this exact version. `list({ where })` is observational. In React render, use `local.get(id)`.
39
45
  2. **See who's active** (optional) — `ablo.<model>.claim.state({ id })` (synchronous; never blocks).
40
46
  3. **Claim** the row before changing it — `await using claim = await ablo.<model>.claim({ id, description?, ttl? })`. If someone else holds it, this waits for them, then gives you the fresh row on `claim.data`. The claim auto-releases when it goes out of scope (`await using`).
41
- 4. **Write** — `await ablo.<model>.update({ id: claim.data.id, data })`. Because you hold the claim, the write is rejected if the row changed underneath you.
47
+ 4. **Write** — pass `reads: [row]` when the decision used a row returned by `read`, or pass `claim` when writing through a held claim. If the declared read moved or the claim was lost, the mutation does not land.
42
48
 
43
49
  Keep coding assistants on this schema-backed path.
44
50
 
@@ -58,7 +64,7 @@ const schema = defineSchema({
58
64
 
59
65
  const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
60
66
 
61
- const report = await ablo.weatherReports.get({ id: 'report_stockholm' });
67
+ const report = await ablo.weatherReports.read({ id: 'report_stockholm' });
62
68
  if (!report) throw new Error('Report not found');
63
69
 
64
70
  // If someone else holds the row, claim waits for them and re-reads the fresh
@@ -74,6 +80,7 @@ const claimed = claim.data;
74
80
  await ablo.weatherReports.update({
75
81
  id: claimed.id,
76
82
  data: { status: 'ready', forecast: await getForecast(claimed.location) },
83
+ claim,
77
84
  });
78
85
  ```
79
86
 
package/CHANGELOG.md CHANGED
@@ -1,23 +1,279 @@
1
1
  # Changelog
2
2
 
3
- ## 0.57.0
3
+ ## 0.59.0
4
+
5
+ ### Schema changes now have one ordered deployment plan
6
+
7
+ `ablo plan` compares the source schema, the active Ablo schema artifact, and the
8
+ connected PostgreSQL shape without changing any of them. It produces one
9
+ fingerprinted expand, dual-write, backfill, verify, switch, and contract
10
+ sequence, with explicit owners, blockers, and a rollback target:
11
+
12
+ ```sh
13
+ npx ablo plan
14
+ npx ablo plan --json
15
+ ```
16
+
17
+ `ablo check` is now the database-compatibility view of that same plan. `ablo
18
+ push` and `ablo migrate` consume it instead of maintaining separate migration
19
+ judgments, and `ablo rollback` plans or applies a reviewed reactivation of an
20
+ earlier schema artifact. The shared deployment contracts are available through
21
+ `@abloatai/ablo/schema` and `@abloatai/transaction/schema`.
22
+
23
+ Planning must observe all three states. `ablo plan`, `ablo check`, and `ablo
24
+ migrate` therefore require `ABLO_API_KEY` plus
25
+ `DATABASE_ADMIN_URL`/`DATABASE_URL`; a migration dry run is no longer a
26
+ source-only operation. `ablo push` now refuses a blocked plan and can accept an
27
+ explicit lifecycle manifest with `--manifest <path>`.
28
+
29
+ Runtime schema-drift warnings now name the affected fields and distinguish
30
+ client-only fields, active-only fields, and changes to type or optionality.
31
+
32
+ ### Declared timestamps no longer recurse during local edits
33
+
34
+ Schemas can declare `createdAt` and `updatedAt` for typed reads and ordering
35
+ without turning Ablo's automatic timestamp bookkeeping into another model
36
+ edit. Updating an observable field now advances `updatedAt` once, keeps the
37
+ timestamp observable, and excludes system-managed timestamps from the
38
+ user-authored change payload.
39
+
40
+ ### The public client configuration boundary is explicit
41
+
42
+ The supported `Ablo({ ... })` options are now machine-checked against the
43
+ published reference. The internal `onCommitReceipt` transport callback is no
44
+ longer accepted by the public factory type.
45
+
46
+ ### Connection capacity is selectable from the public pricing model
47
+
48
+ The pricing API can now select the first tier that accommodates a requested
49
+ connection count. The published Pro allowance increases from 1,000 to 5,000
50
+ concurrent connections.
51
+
52
+ ### CLI telemetry can reach authenticated ingestion
53
+
54
+ When an Ablo runtime key is available, the CLI uses it only in memory to
55
+ authenticate product-analytics delivery. The key is not written to the local
56
+ telemetry state, and existing telemetry opt-outs continue to apply.
57
+
58
+ ### Version-matched integration guidance is easier to enter
59
+
60
+ The documentation bundled with `@abloatai/ablo` now starts from installation,
61
+ the operation being coordinated, and whether an existing write boundary must
62
+ be preserved. New focused pages cover basic usage, implementation choices,
63
+ existing-operation coordination, every client option, security,
64
+ instrumentation, comparisons, common questions, and GraphQL.js. `npx ablo
65
+ docs` continues to read this package-local documentation, so the guidance
66
+ matches the installed version.
67
+
68
+ A new stale-context agent-turn example shows the complete long-running policy:
69
+ abort cancellable work when an exact read moves, rebuild context for a bounded
70
+ retry, retain the authoritative write guard, and reconcile rather than blindly
71
+ replay an irreversible external side effect.
72
+
73
+ ## 0.58.0
74
+
75
+ ### Reads now distinguish observation from decision input
76
+
77
+ `get({ id })` returns the current row for display or inspection. `read({ id })`
78
+ returns the same row while privately retaining its model, id, and watermark so a
79
+ later write can prove exactly which state it depended on:
80
+
81
+ ```ts
82
+ const report = await ablo.reports.read({ id: reportId });
83
+
84
+ await ablo.reports.update({
85
+ id: report.id,
86
+ data: { summary },
87
+ reads: [report],
88
+ });
89
+ ```
90
+
91
+ If `report` changed in between, the update does not land and rejects with
92
+ `AbloStaleContextError`. `get()` and `list()` remain observational, while
93
+ `onChange()` remains the live notification surface.
94
+
95
+ This replaces the older `retrieve()` and durable `track()` model surfaces. It is
96
+ a public-surface break with no compatibility aliases:
97
+
98
+ - replace `retrieve({ id })` with `get({ id })` for observation, or `read({ id })`
99
+ when a later mutation depends on the row;
100
+ - replace `track(...)`, `CommitContext.track`, and mutation `track` / `onStale`
101
+ options with captured rows passed through the mutation's `reads` option; and
102
+ - replace AI SDK `ToolModel.get` with `ToolModel.read`.
103
+
104
+ Schema conflict-policy configuration and the `agents*`, `humans*`, and
105
+ `system*` policy constants are also removed, together with the supporting
106
+ conflict, stale-notification, persisted-read-set, and internal read-set exports.
107
+ Use an active claim when work must exclude another participant. Use `read()` and
108
+ `reads` when work may run concurrently but must not commit from a stale premise.
4
109
 
5
- ### Before you upgrade: drain the endpoint outbox
110
+ The removals announced by earlier releases now take effect too:
111
+ `SourceRequestContext.requiredSyncGroups` is gone in favor of `syncGroups`, and
112
+ `DeltaPosition`, `deltaPositionSchema`, `ReadSetWatermark`, and
113
+ `readSetWatermarkSchema` are gone in favor of `LogPosition` and
114
+ `logPositionSchema`.
115
+
116
+ ### Context follows exact reads and can stop stale work early
117
+
118
+ `context()` now assembles ordinary application values and exact Ablo reads into
119
+ one result with `data`, `reads`, and `onChange`. It no longer publishes a
120
+ context-level cursor or source classification. `ContextResult.cursor`,
121
+ `ContextResult.sources`, `ContextChange`, `ContextSource`, and
122
+ `contextSourceSchema` leave with that older model.
123
+
124
+ The reactive client's cache-based `snapshot()` operation and `Snapshot` type are
125
+ removed. Call `read()` for every row an action depends on, assemble those values
126
+ with `context()`, and pass `ctx.reads` to the final write:
127
+
128
+ ```ts
129
+ const ctx = await context({
130
+ ablo,
131
+ data: {
132
+ report: ablo.reports.read({ id: reportId }),
133
+ documents: searchDocuments(reportId),
134
+ },
135
+ });
136
+
137
+ const stop = ctx.onChange((error) => controller.abort(error));
138
+
139
+ try {
140
+ const summary = await generateSummary(ctx.data, controller.signal);
141
+ await ablo.reports.update({
142
+ id: reportId,
143
+ data: { summary },
144
+ reads: ctx.reads,
145
+ });
146
+ } finally {
147
+ stop();
148
+ }
149
+ ```
6
150
 
7
- This release adds a `sync_groups` column to the source outbox, and the migration
8
- refuses to run while legacy rows are still sitting in it. That refusal is
9
- deliberate. A legacy row has no routes recorded, and assigning it one would
10
- either invent an audience or give it none, so the migration stops and tells you
11
- rather than guessing.
151
+ `onChange` calls its listeners once when any captured row moves, which lets an
152
+ expensive model call stop early. The final write must still receive
153
+ `reads: ctx.reads`; that server-side check stays authoritative if notification
154
+ races the write or the connection drops.
12
155
 
13
- If you run a `dataSource()` endpoint, do this on the previous release, in order:
156
+ Atomic `commits.create()` batches accept the same captured rows on both the
157
+ stateless HTTP and reactive WebSocket clients. Typed model claim handles can be
158
+ passed directly as a batch's `claim`, without losing their row type. Disposing a
159
+ reactive client now also disposes its mutation queue and commit-lane timers.
14
160
 
15
- 1. Let Ablo poll until every event already in `ablo_outbox` has been consumed.
16
- 2. Confirm the polling cursor has advanced past the last of them.
17
- 3. Delete those consumed rows.
161
+ ### Claims are available from the shell
162
+
163
+ The CLI can now acquire, queue for, inspect, heartbeat, and release a row lease:
164
+
165
+ ```bash
166
+ npx ablo claims acquire reports report_123 --queue -- npm run reconcile
167
+ npx ablo claims list reports report_123
168
+ npx ablo claims release reports report_123
169
+ ```
170
+
171
+ The `-- <command>` form keeps the lease alive only while the child process runs
172
+ and releases it on success, failure, or interruption. This lets an operator or
173
+ coding agent participate in the same coordination boundary as SDK clients
174
+ without first adding application code.
175
+
176
+ ### An agent run can carry the person who started it
177
+
178
+ An agent session can now name the person it acts for. Pass `onBehalfOf` when you
179
+ mint, and every write that agent makes records both principals: the agent as the
180
+ actor, that user as the delegator.
181
+
182
+ ```ts
183
+ const { token } = await server.sessions.create({
184
+ agent: { id: agentId },
185
+ onBehalfOf: { user: { id: requestingUserId } },
186
+ can: { records: ['read', 'update'] },
187
+ syncGroups: [syncGroup('workspace', workspaceId)],
188
+ });
189
+ ```
190
+
191
+ Until now the delegation chain root came from whoever called the mint. A browser
192
+ session minting for its own user recorded that person; a backend minting with a
193
+ secret key recorded nobody. Background work is the second case, so a queued
194
+ job's writes arrived attributed to the agent alone, or to `system` where the
195
+ worker wrote around Ablo entirely. Ablo never sees your user directory, so it
196
+ cannot recover that identity afterwards.
197
+
198
+ The rule is about who may attest, not who may ask. A secret key already carries
199
+ organization authority, so it may name any user id, which is what lets a worker
200
+ resume a job somebody else started. A human session may name only its own user,
201
+ and naming another is refused. `onBehalfOf` on a capability that is not an agent
202
+ is refused as well.
203
+
204
+ Two habits make this hold in practice. Persist the user id on the job before you
205
+ enqueue it, because request context and in-memory arguments do not survive a
206
+ retry or a process boundary, and a durable job without its delegator can only
207
+ produce agent-only attribution. And do not quietly fall back to writing straight
208
+ to the database when minting is unavailable. Those writes are still observed,
209
+ but they carry no trusted correlation and are recorded as `system`.
210
+
211
+ Omit the field and sessions mint exactly as they did before.
212
+
213
+ ### The CLI has one credential input and a clearer project boundary
214
+
215
+ `ABLO_API_KEY` is now the CLI's single explicit credential input. Management,
216
+ branch-runtime, and restricted-agent authority follow from the credential kind
217
+ and its server-side grant; `ABLO_MANAGEMENT_KEY` is no longer a separate input.
218
+ Keep management credentials at the control-plane boundary and pass only a
219
+ delegated, per-run `rk_` credential into a sandbox or agent runtime.
220
+
221
+ After browser approval, `ablo login` now lets an organization with multiple
222
+ projects choose the project in the terminal. `ablo login --project <slug>` skips
223
+ the picker. Credentials remain fixed to the project that minted them, and the
224
+ CLI refuses to silently use another project's stored key.
225
+
226
+ Database connection setup now handles more provider-specific PostgreSQL role
227
+ constraints, repairs required role inheritance, registers local connectors
228
+ through the control-plane boundary, and reports a missing Data Source API key as
229
+ an authentication problem with a concrete fix. A branch still needs a database
230
+ connected before its schema can be pushed.
231
+
232
+ ### Hosted test branches are explicit and temporary
233
+
234
+ Live integration fixtures can create an expiring `test` branch backed by Ablo's
235
+ hosted log storage. Hosted storage must be requested explicitly and the branch
236
+ must expire within 24 hours. Ordinary customer branches remain unbound until
237
+ their own database is connected, so a test convenience cannot silently choose
238
+ where customer data lives.
239
+
240
+ The documentation now includes a coordination-conformance fixture, an
241
+ existing-document evidence pipeline, an existing Python backend path, a
242
+ GraphQL.js approach, and a sandbox-runtime integration guide. These examples
243
+ separate the guarantees Ablo enforces from application, provider, database, and
244
+ deployment responsibilities.
245
+
246
+ ### Pricing and plan limits moved
247
+
248
+ The rate card, the included allowances, and the plan ceilings all changed in
249
+ this release. The canonical page is published at
250
+ https://docs.abloatai.com/pricing and needs no sign-in.
251
+
252
+ ## 0.57.0
18
253
 
19
- Then upgrade and run the migration. If you connect your database over
20
- replication rather than an endpoint, there is no outbox and nothing to do.
254
+ ### The endpoint outbox upgrades without a drain
255
+
256
+ Endpoint events now have an explicit envelope version. Existing rows and writes
257
+ from an older endpoint are version 1 and pass through the preserved pre-subject
258
+ routing decoder. New adapters write version 2, capturing `sync_groups` in the
259
+ same transaction as the row change. The database constraint requires every
260
+ version-2 event to carry those immutable routes.
261
+
262
+ Old and new endpoint versions may run together during rollout without stalling
263
+ the feed. A page served by an old reader necessarily uses version-1 semantics,
264
+ even when a new writer created the row, because that reader does not select the
265
+ new routing columns. Version-2 routing is therefore universal once every
266
+ endpoint reader has upgraded. There is still no pre-upgrade drain, write pause,
267
+ cursor inspection, or manual deletion.
268
+
269
+ Ablo sends `cursor` as the read position and `acknowledgedThrough` separately
270
+ after the event and consumer position are durable. Built-in adapters use that
271
+ explicit acknowledgement for bounded cleanup; custom event handlers may do the
272
+ same.
273
+
274
+ Version 1 remains a deliberately named compatibility decoder, not a
275
+ NULL value silently interpreted as an empty audience. Replication connections
276
+ have no endpoint outbox and require no action.
21
277
 
22
278
  ### A row is authorized by the subject its schema declares
23
279
 
package/README.md CHANGED
@@ -3,12 +3,12 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>Collaboration infrastructure for AI agents.</strong>
6
+ <strong>Coordination infrastructure for agents, applications, services, and people working on shared state.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://docs.abloatai.com">Docs</a> &nbsp;|&nbsp;
11
- <a href="https://docs.abloatai.com/quickstart">Quickstart</a> &nbsp;|&nbsp;
11
+ <a href="https://docs.abloatai.com/installation">Installation</a> &nbsp;|&nbsp;
12
12
  <a href="https://docs.abloatai.com/api">API</a> &nbsp;|&nbsp;
13
13
  <a href="https://github.com/Abloatai/ablo">GitHub</a>
14
14
  </p>
@@ -22,8 +22,8 @@
22
22
 
23
23
  ---
24
24
 
25
- Ablo is collaboration infrastructure for AI agents: one API for agents, apps,
26
- and services to claim, change, and confirm the same rows.
25
+ Ablo is coordination infrastructure for agents, applications, services, and
26
+ people working on shared state.
27
27
 
28
28
  Every write goes through it, so authority, idempotency, conflicts, ordering,
29
29
  and confirmation are enforced in one place. Your Postgres remains the source of
@@ -56,12 +56,13 @@ for changes.
56
56
  Read and write through one typed API:
57
57
 
58
58
  ```ts
59
- const order = await ablo.orders.get({ id: orderId });
59
+ const order = await ablo.orders.read({ id: orderId });
60
60
 
61
61
  if (!order) throw new Error('Order not found');
62
62
  await ablo.orders.update({
63
63
  id: order.id,
64
64
  data: { status: 'approved' },
65
+ reads: [order],
65
66
  });
66
67
  ```
67
68
 
package/dist/ai-sdk.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from '@abloatai/transaction/ai-sdk';
2
- import type { ContextResult } from './context.js';
2
+ import type { ContextResult } from './context/index.js';
3
3
  export interface ContextMessageOptions<TData extends Readonly<Record<string, unknown>>> {
4
4
  /** Top-level context keys to render. The default is every selected key. */
5
5
  readonly include?: readonly (keyof TData & string)[];
@@ -1 +1 @@
1
- {"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAG7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAMlD,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpF,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC5E,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAC3B,OAAO,GAAE,qBAAqB,CAAC,KAAK,CAAM,GACzC,cAAc,CAehB"}
1
+ {"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAG7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpF,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC5E,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAC3B,OAAO,GAAE,qBAAqB,CAAC,KAAK,CAAM,GACzC,cAAc,CAehB"}
@@ -1,11 +1,9 @@
1
1
  import { type CapturedReadEvidence } from '@abloatai/transaction/internal/read-set';
2
- export interface ContextEvidenceSlice {
3
- readonly reads: readonly CapturedReadEvidence[];
4
- readonly includesInformational: boolean;
2
+ import type { ReadDependency } from '@abloatai/transaction/coordination';
3
+ import type { AbloStaleContextError } from '@abloatai/transaction';
4
+ export interface BoundContextEvidence {
5
+ readonly collect: (data: unknown) => readonly CapturedReadEvidence[];
6
+ readonly onChange?: (reads: readonly ReadDependency[], listener: (error: AbloStaleContextError) => void) => () => void;
5
7
  }
6
- export interface ContextEvidence {
7
- readonly all: readonly CapturedReadEvidence[];
8
- readonly inspect: (value: unknown) => ContextEvidenceSlice;
9
- }
10
- export declare function bindContextEvidence(client: object): (data: unknown) => ContextEvidence;
8
+ export declare function bindContextEvidence(client: object): BoundContextEvidence;
11
9
  //# sourceMappingURL=evidence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/context/evidence.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,yCAAyC,CAAC;AAQjD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAChD,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;CACzC;AAsCD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,oBAAoB,CAAC;CAC5D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,eAAe,CAWtF"}
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/context/evidence.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAiCnE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,oBAAoB,EAAE,CAAC;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAClB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,KAC7C,MAAM,IAAI,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CAQxE"}
@@ -8,12 +8,9 @@ function isTraversable(value) {
8
8
  function inspectValue(binding, value) {
9
9
  const found = [];
10
10
  const seen = new WeakSet();
11
- let includesInformational = false;
12
11
  const visit = (current) => {
13
- if (typeof current !== 'object' || current === null) {
14
- includesInformational = true;
12
+ if (typeof current !== 'object' || current === null)
15
13
  return;
16
- }
17
14
  if (seen.has(current))
18
15
  return;
19
16
  seen.add(current);
@@ -23,31 +20,20 @@ function inspectValue(binding, value) {
23
20
  return;
24
21
  }
25
22
  if (isTraversable(current)) {
26
- const children = Object.values(current);
27
- if (children.length === 0)
28
- includesInformational = true;
29
- for (const child of children)
23
+ for (const child of Object.values(current))
30
24
  visit(child);
31
- return;
32
25
  }
33
- includesInformational = true;
34
26
  };
35
27
  visit(value);
36
- return {
37
- reads: [...new Map(found.map((item) => [item.row, item])).values()],
38
- includesInformational,
39
- };
28
+ return [...new Map(found.map((item) => [item.row, item])).values()];
40
29
  }
41
30
  export function bindContextEvidence(client) {
42
31
  const binding = readEvidenceBinding(client);
43
32
  if (!binding)
44
33
  throw new TypeError('context() requires an Ablo client in `ablo`.');
45
- return (data) => {
46
- const inspect = (value) => inspectValue(binding, value);
47
- return {
48
- all: inspect(data).reads,
49
- inspect,
50
- };
34
+ return {
35
+ collect: (data) => inspectValue(binding, data),
36
+ ...(binding.onChange ? { onChange: binding.onChange } : {}),
51
37
  };
52
38
  }
53
39
  //# sourceMappingURL=evidence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/context/evidence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,mBAAmB,GAGpB,MAAM,yCAAyC,CAAC;AAEjD,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC;AAOD,SAAS,YAAY,CACnB,OAA4B,EAC5B,KAAc;IAEd,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,OAAO,EAAU,CAAC;IACnC,IAAI,qBAAqB,GAAG,KAAK,CAAC;IAElC,MAAM,KAAK,GAAG,CAAC,OAAgB,EAAQ,EAAE;QACvC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACpD,qBAAqB,GAAG,IAAI,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClB,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClD,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,qBAAqB,GAAG,IAAI,CAAC;YACxD,KAAK,MAAM,KAAK,IAAI,QAAQ;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,qBAAqB,GAAG,IAAI,CAAC;IAC/B,CAAC,CAAC;IAEF,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACnE,qBAAqB;KACtB,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;IAElF,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACjE,OAAO;YACL,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK;YACxB,OAAO;SACR,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/context/evidence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,mBAAmB,GAGpB,MAAM,yCAAyC,CAAC;AAIjD,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,YAAY,CACnB,OAA4B,EAC5B,KAAc;IAEd,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,OAAO,EAAU,CAAC;IAEnC,MAAM,KAAK,GAAG,CAAC,OAAgB,EAAQ,EAAE;QACvC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO;QAC5D,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClB,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClD,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACtE,CAAC;AAUD,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;IAElF,OAAO;QACL,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC;QAC9C,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { CapturedRow } from '@abloatai/transaction';
2
+ import { type AwaitedDeep } from './await.js';
3
+ import { type ContextOnChange } from './onChange.js';
4
+ export type { AwaitedDeep } from './await.js';
5
+ export type { ContextChangeListener, ContextOnChange } from './onChange.js';
6
+ export interface ContextOptions<TData extends Readonly<Record<string, unknown>>> {
7
+ /** The client whose read evidence may guard a later create, update, or delete. */
8
+ readonly ablo: object;
9
+ /** Values selected by the application. Nested promises are accepted. */
10
+ readonly data: TData;
11
+ }
12
+ export interface ContextResult<TData extends Readonly<Record<string, unknown>>> {
13
+ readonly data: AwaitedDeep<TData>;
14
+ /**
15
+ * Exact returned Ablo rows, ready to pass to create, update, or delete
16
+ * through `reads`.
17
+ */
18
+ readonly reads: readonly CapturedRow[];
19
+ /** Called once when any exact read in this context becomes stale. */
20
+ readonly onChange: ContextOnChange;
21
+ }
22
+ export declare function context<const TData extends Readonly<Record<string, unknown>>>(options: ContextOptions<TData>): Promise<ContextResult<TData>>;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5E,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAI5E,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7E,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5E,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED,wBAAsB,OAAO,CAAC,KAAK,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACjF,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,GAC7B,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAgB/B"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Assemble the values selected for one action and retain the exact Ablo reads
3
+ * they contain. Each read keeps its own `readAt`; context does not collapse
4
+ * those independent premises into another watermark.
5
+ */
6
+ import { z } from 'zod';
7
+ import { awaitDeep } from './await.js';
8
+ import { bindContextEvidence } from './evidence.js';
9
+ import { createContextOnChange } from './onChange.js';
10
+ const contextDataSchema = z.record(z.string(), z.unknown());
11
+ export async function context(options) {
12
+ const evidenceBinding = bindContextEvidence(options.ablo);
13
+ const data = await awaitDeep(options.data);
14
+ const parsed = contextDataSchema.safeParse(data);
15
+ if (!parsed.success) {
16
+ throw new TypeError('context() requires `data` to be an object.', { cause: parsed.error });
17
+ }
18
+ const evidence = evidenceBinding.collect(data);
19
+ const dependencies = evidence.map((item) => item.entry);
20
+ return {
21
+ data: data,
22
+ reads: evidence.map((item) => item.row),
23
+ onChange: createContextOnChange(dependencies, evidenceBinding.onChange),
24
+ };
25
+ }
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAoB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAwB,MAAM,eAAe,CAAC;AAK5E,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAoB5D,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,OAA8B;IAE9B,MAAM,eAAe,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,4CAA4C,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAExD,OAAO;QACL,IAAI,EAAE,IAA0B;QAChC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAkB,CAAC;QACtD,QAAQ,EAAE,qBAAqB,CAAC,YAAY,EAAE,eAAe,CAAC,QAAQ,CAAC;KACxE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ReadDependency } from '@abloatai/transaction/coordination';
2
+ import type { AbloStaleContextError } from '@abloatai/transaction';
3
+ export type ContextChangeListener = (error: AbloStaleContextError) => void;
4
+ export type ContextOnChange = (listener: ContextChangeListener) => () => void;
5
+ type StartOnChange = (reads: readonly ReadDependency[], listener: ContextChangeListener) => () => void;
6
+ /** Share one transport subscription for every listener on one context. */
7
+ export declare function createContextOnChange(reads: readonly ReadDependency[], start: StartOnChange | undefined): ContextOnChange;
8
+ export {};
9
+ //# sourceMappingURL=onChange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onChange.d.ts","sourceRoot":"","sources":["../../src/context/onChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAC3E,MAAM,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,qBAAqB,KAAK,MAAM,IAAI,CAAC;AAE9E,KAAK,aAAa,GAAG,CACnB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,EAAE,qBAAqB,KAC5B,MAAM,IAAI,CAAC;AAEhB,0EAA0E;AAC1E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,eAAe,CAoCjB"}
@@ -0,0 +1,37 @@
1
+ /** Share one transport subscription for every listener on one context. */
2
+ export function createContextOnChange(reads, start) {
3
+ const listeners = new Set();
4
+ let stop;
5
+ let stale;
6
+ const changed = (error) => {
7
+ if (stale)
8
+ return;
9
+ stale = error;
10
+ stop?.();
11
+ stop = undefined;
12
+ for (const listener of [...listeners])
13
+ listener(error);
14
+ };
15
+ return (listener) => {
16
+ if (stale) {
17
+ listener(stale);
18
+ return () => undefined;
19
+ }
20
+ listeners.add(listener);
21
+ if (reads.length > 0 && !stop) {
22
+ if (!start) {
23
+ listeners.delete(listener);
24
+ throw new TypeError('This Ablo client does not support context().onChange.');
25
+ }
26
+ stop = start(reads, changed);
27
+ }
28
+ return () => {
29
+ listeners.delete(listener);
30
+ if (listeners.size === 0) {
31
+ stop?.();
32
+ stop = undefined;
33
+ }
34
+ };
35
+ };
36
+ }
37
+ //# sourceMappingURL=onChange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onChange.js","sourceRoot":"","sources":["../../src/context/onChange.ts"],"names":[],"mappings":"AAWA,0EAA0E;AAC1E,MAAM,UAAU,qBAAqB,CACnC,KAAgC,EAChC,KAAgC;IAEhC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;IACnD,IAAI,IAA8B,CAAC;IACnC,IAAI,KAAwC,CAAC;IAE7C,MAAM,OAAO,GAAG,CAAC,KAA4B,EAAQ,EAAE;QACrD,IAAI,KAAK;YAAE,OAAO;QAClB,KAAK,GAAG,KAAK,CAAC;QACd,IAAI,EAAE,EAAE,CAAC;QACT,IAAI,GAAG,SAAS,CAAC;QACjB,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC;IAEF,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,IAAI,KAAK,EAAE,CAAC;YACV,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;QACzB,CAAC;QAED,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC3B,MAAM,IAAI,SAAS,CAAC,uDAAuD,CAAC,CAAC;YAC/E,CAAC;YACD,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,EAAE,EAAE,CAAC;gBACT,IAAI,GAAG,SAAS,CAAC;YACnB,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -1,2 +1,2 @@
1
- export * from '@abloatai/transaction/source/conformance';
1
+ export * from '@abloatai/transaction/source/adapters';
2
2
  //# sourceMappingURL=source-conformance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-conformance.d.ts","sourceRoot":"","sources":["../src/source-conformance.ts"],"names":[],"mappings":"AAAA,cAAc,0CAA0C,CAAC"}
1
+ {"version":3,"file":"source-conformance.d.ts","sourceRoot":"","sources":["../src/source-conformance.ts"],"names":[],"mappings":"AAAA,cAAc,uCAAuC,CAAC"}
@@ -1,2 +1,2 @@
1
- export * from '@abloatai/transaction/source/conformance';
1
+ export * from '@abloatai/transaction/source/adapters';
2
2
  //# sourceMappingURL=source-conformance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-conformance.js","sourceRoot":"","sources":["../src/source-conformance.ts"],"names":[],"mappings":"AAAA,cAAc,0CAA0C,CAAC"}
1
+ {"version":3,"file":"source-conformance.js","sourceRoot":"","sources":["../src/source-conformance.ts"],"names":[],"mappings":"AAAA,cAAc,uCAAuC,CAAC"}