@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.
- package/AGENTS.md +12 -5
- package/CHANGELOG.md +269 -13
- package/README.md +6 -5
- package/dist/ai-sdk.d.ts +1 -1
- package/dist/ai-sdk.d.ts.map +1 -1
- package/dist/context/evidence.d.ts +6 -8
- package/dist/context/evidence.d.ts.map +1 -1
- package/dist/context/evidence.js +6 -20
- package/dist/context/evidence.js.map +1 -1
- package/dist/context/index.d.ts +23 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +26 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/onChange.d.ts +9 -0
- package/dist/context/onChange.d.ts.map +1 -0
- package/dist/context/onChange.js +37 -0
- package/dist/context/onChange.js.map +1 -0
- package/dist/source-conformance.d.ts +1 -1
- package/dist/source-conformance.d.ts.map +1 -1
- package/dist/source-conformance.js +1 -1
- package/dist/source-conformance.js.map +1 -1
- package/dist/source-drizzle.d.ts +1 -1
- package/dist/source-drizzle.d.ts.map +1 -1
- package/dist/source-drizzle.js +1 -1
- package/dist/source-drizzle.js.map +1 -1
- package/dist/source-kysely.d.ts +1 -1
- package/dist/source-kysely.d.ts.map +1 -1
- package/dist/source-kysely.js +1 -1
- package/dist/source-kysely.js.map +1 -1
- package/dist/source-next.d.ts +1 -1
- package/dist/source-next.d.ts.map +1 -1
- package/dist/source-next.js +1 -1
- package/dist/source-next.js.map +1 -1
- package/docs/agents.md +38 -15
- package/docs/api-keys.md +6 -6
- package/docs/api.md +107 -34
- package/docs/basic-usage.md +84 -0
- package/docs/branch-development.md +23 -4
- package/docs/cli.md +16 -9
- package/docs/client-behavior.md +27 -29
- package/docs/comparison.md +63 -0
- package/docs/concurrency-convention.md +92 -75
- package/docs/context.md +76 -31
- package/docs/coordinate-existing-work.md +104 -0
- package/docs/coordination.md +93 -38
- package/docs/data-sources.md +12 -6
- package/docs/debugging.md +1 -1
- package/docs/deployment.md +19 -1
- package/docs/examples/agent-human.md +6 -18
- package/docs/examples/coordination-conformance.md +69 -0
- package/docs/examples/evidence-backed-document-pipeline.md +488 -0
- package/docs/examples/existing-python-backend.md +10 -13
- package/docs/examples/nextjs.md +2 -2
- package/docs/examples/scoped-agent.md +18 -1
- package/docs/examples/server-agent.md +2 -2
- package/docs/faq.md +75 -0
- package/docs/groups.md +19 -139
- package/docs/guarantees.md +8 -8
- package/docs/idempotency.md +3 -0
- package/docs/identity.md +2 -1
- package/docs/implement.md +61 -0
- package/docs/implementation-index.md +20 -0
- package/docs/index.md +59 -173
- package/docs/installation.md +77 -0
- package/docs/instrumentation.md +52 -0
- package/docs/integration-guide.md +20 -19
- package/docs/integrations/sandbox-runtime.md +157 -0
- package/docs/integrations.md +9 -0
- package/docs/migration.md +12 -7
- package/docs/operating-on-your-database.md +7 -0
- package/docs/options.md +172 -0
- package/docs/quickstart.md +25 -14
- package/docs/react.md +9 -9
- package/docs/schema-contract.md +14 -13
- package/docs/security.md +64 -0
- package/docs/sessions.md +1 -1
- package/examples/README.md +8 -2
- package/examples/agent-turn.ts +1 -1
- package/examples/expensive-agent-turn.ts +1 -1
- package/examples/stale-context-agent-turn.ts +106 -0
- package/llms.txt +23 -12
- package/package.json +7 -6
- package/dist/context/sources.d.ts +0 -21
- package/dist/context/sources.d.ts.map +0 -1
- package/dist/context/sources.js +0 -36
- package/dist/context/sources.js.map +0 -1
- package/dist/context.d.ts +0 -22
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -33
- package/dist/context.js.map +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical stale-context turn: abort early, retain the authoritative write
|
|
3
|
+
* guard, rebuild context on a bounded retry, and never blindly replay an
|
|
4
|
+
* irreversible tool side effect.
|
|
5
|
+
*
|
|
6
|
+
* The application-owned model and notification functions below are small,
|
|
7
|
+
* deterministic stand-ins. Keep their cancellation/idempotency contracts when
|
|
8
|
+
* replacing them with real providers.
|
|
9
|
+
*
|
|
10
|
+
* Run: ABLO_API_KEY=sk_... RECORD_ID=record_... npx tsx examples/stale-context-agent-turn.ts
|
|
11
|
+
*/
|
|
12
|
+
import { Ablo, AbloStaleContextError } from '@abloatai/ablo';
|
|
13
|
+
import { context } from '@abloatai/ablo/context';
|
|
14
|
+
import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
15
|
+
|
|
16
|
+
const schema = defineSchema({
|
|
17
|
+
records: model({
|
|
18
|
+
title: z.string(),
|
|
19
|
+
status: z.enum(['pending', 'done']),
|
|
20
|
+
result: z.string().optional(),
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const delivered = new Set<string>();
|
|
25
|
+
|
|
26
|
+
async function callModel(title: string, signal: AbortSignal): Promise<string> {
|
|
27
|
+
await new Promise<void>((resolve, reject) => {
|
|
28
|
+
const timer = setTimeout(resolve, 25);
|
|
29
|
+
signal.addEventListener('abort', () => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
reject(signal.reason);
|
|
32
|
+
}, { once: true });
|
|
33
|
+
});
|
|
34
|
+
return `Completed: ${title}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function sendResult(operationKey: string, signal: AbortSignal): Promise<void> {
|
|
38
|
+
signal.throwIfAborted();
|
|
39
|
+
// Replace with a provider call that accepts operationKey as its idempotency key.
|
|
40
|
+
delivered.add(operationKey);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function wasResultSent(operationKey: string): Promise<boolean> {
|
|
44
|
+
// Replace with the provider's outcome lookup using the same key.
|
|
45
|
+
return delivered.has(operationKey);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const recordId = process.env.RECORD_ID;
|
|
49
|
+
if (!recordId) throw new Error('RECORD_ID is required');
|
|
50
|
+
|
|
51
|
+
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
52
|
+
|
|
53
|
+
async function completeRecord(id: string): Promise<void> {
|
|
54
|
+
const operationKey = `complete-record:${id}`;
|
|
55
|
+
const maxAttempts = 3;
|
|
56
|
+
|
|
57
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
58
|
+
const ctx = await context({
|
|
59
|
+
ablo,
|
|
60
|
+
data: { record: ablo.records.read({ id }) },
|
|
61
|
+
});
|
|
62
|
+
if (!ctx.data.record) throw new Error(`Record ${id} was not found`);
|
|
63
|
+
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const stop = ctx.onChange((error) => controller.abort(error));
|
|
66
|
+
let resultMayHaveBeenSent = false;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await callModel(ctx.data.record.title, controller.signal);
|
|
70
|
+
resultMayHaveBeenSent = true;
|
|
71
|
+
await sendResult(operationKey, controller.signal);
|
|
72
|
+
|
|
73
|
+
await ablo.records.update({
|
|
74
|
+
id: ctx.data.record.id,
|
|
75
|
+
data: { status: 'done', result },
|
|
76
|
+
reads: ctx.reads,
|
|
77
|
+
idempotencyKey: operationKey,
|
|
78
|
+
});
|
|
79
|
+
console.log({ attempt, operationKey, status: 'done' });
|
|
80
|
+
return;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const stale = error instanceof AbloStaleContextError ||
|
|
83
|
+
controller.signal.reason instanceof AbloStaleContextError;
|
|
84
|
+
if (!stale) throw error;
|
|
85
|
+
|
|
86
|
+
if (resultMayHaveBeenSent) {
|
|
87
|
+
const sent = await wasResultSent(operationKey);
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Context changed after the external action (sent=${sent}). ` +
|
|
90
|
+
`Reconcile operation ${operationKey}; do not replay it automatically.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (attempt === maxAttempts) throw error;
|
|
94
|
+
// The next iteration assembles new data and new read evidence.
|
|
95
|
+
} finally {
|
|
96
|
+
stop();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await ablo.ready();
|
|
103
|
+
await completeRecord(recordId);
|
|
104
|
+
} finally {
|
|
105
|
+
await ablo.dispose();
|
|
106
|
+
}
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ablo
|
|
2
2
|
|
|
3
|
-
Ablo is
|
|
3
|
+
Ablo is coordination infrastructure for agents, applications, services, and people working on shared state.
|
|
4
4
|
|
|
5
5
|
Here is the problem it solves. Two agents reach for `report_stockholm` at once. One claims the row, does slow work (an LLM call, a fetch, a chain of tools), and commits. The second is neither rejected nor allowed to clobber: it waits in line, is handed the row as it now stands, and proceeds. Claims don't lock. If another writer holds the row, `claim` waits for them, re-reads the fresh row, then hands it to you — so writers serialize instead of colliding. A person editing that row is simply another holder; the agent waits behind them the same way. And a claim is as narrow as its target: name a `path`, a `range`, or a `field` and two claims on non-overlapping parts of the same row are both granted — region locking within one row, with no queueing between regions.
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ These are the jobs it is WRONG for, and saying so saves you a wasted integration
|
|
|
27
27
|
- **Vector search, embeddings, or retrieval.** Different problem. Ablo coordinates the rows; your vector store indexes them.
|
|
28
28
|
- **A file, blob, or artifact store.** Coordinate the ROW that points at the object; put the bytes in object storage.
|
|
29
29
|
|
|
30
|
-
How to call it, shortest path first: install `@abloatai/ablo`, run `npx ablo init --yes --framework <nextjs|vite|remix|vanilla>`, and construct `Ablo({ schema, apiKey: process.env.ABLO_API_KEY })`.
|
|
30
|
+
How to call it, shortest path first: install `@abloatai/ablo`, run `npx ablo init --yes --framework <nextjs|vite|remix|vanilla>`, and construct `Ablo({ schema, apiKey: process.env.ABLO_API_KEY })`. Use `ablo.<model>.get({ id })` to observe a row. Use `const input = await ablo.<model>.read({ id })`, then pass `reads: [input]` to the create/update/delete whose decision used it; if the row moved, that mutation does not land. `list({ where })` is observational. Wrap anything slow and exclusive in `await using claim = await ablo.<model>.claim({ id })`. The sections below cover the rest; "Start here" is the first thing to run.
|
|
31
31
|
|
|
32
32
|
## Machine-readable surfaces
|
|
33
33
|
|
|
@@ -38,10 +38,15 @@ Everything below is public, needs no credential, and is served from `https://www
|
|
|
38
38
|
- `/developers` — every developer surface named on one page: the SDK, the API reference, the OpenAPI document, the MCP server, the CLI.
|
|
39
39
|
- `/openapi.json` — the REST contract as OpenAPI 3.1: every route, a stable `operationId` and description on each, typed responses, and the `ErrorEnvelope` every 4xx and 5xx decodes through. Generate a client from it when no SDK exists for your runtime.
|
|
40
40
|
- `/mcp` — the integration-helper MCP server over Streamable HTTP. POST your JSON-RPC here; a GET returns a descriptor rather than the protocol.
|
|
41
|
+
- `/.well-known/mcp` — the same Streamable HTTP server at its direct discovery endpoint, with no page/transport negotiation.
|
|
41
42
|
- `/.well-known/mcp.json` — that server's manifest, in the MCP registry's `server.json` format.
|
|
42
43
|
- `/api/docs/<page>` — any documentation page as plain Markdown, for a client that fetches URLs rather than speaking MCP.
|
|
43
44
|
- `/sitemap.xml` — every indexable page on the domain.
|
|
44
45
|
|
|
46
|
+
Pricing and plan limits are public at [Ablo pricing](https://docs.abloatai.com/pricing). Do not
|
|
47
|
+
infer prices or follow a dashboard billing link: the documentation page is the canonical rate
|
|
48
|
+
card and needs no sign-in.
|
|
49
|
+
|
|
45
50
|
Every page on that host serves a Markdown representation from its own URL: send
|
|
46
51
|
`Accept: text/markdown` (q-values are honoured), or append `.md` to the path if
|
|
47
52
|
your client cannot set the header. Responses carry `Vary: Accept`, a client that
|
|
@@ -65,6 +70,7 @@ Every surface reaches the same coordinated state. They are not interchangeable.
|
|
|
65
70
|
- Coordination MCP, `@abloatai/mcp` — an agent living inside an MCP host (Claude, Cursor). Two jobs: MANAGING your Ablo the way the CLI does (`get_schema`, `list_projects`, `create_project`, `tail_logs`, `get_usage`), and claim/commit as tools over your rows. Call `get_schema` first — it is the only way to learn the model names every data tool needs. `init`, `push`, `pull`, `generate` have no tools (they touch your repo); run those in a shell.
|
|
66
71
|
- `humans()` with `@abloatai/humans/react` — the interfaces a person watches agent work arrive in. The bare client is the coordination layer (commit, read, observe, claim); `humans()` is the plugin that adds the local watchable copy, live queries, and presence. It `requires: { duplex: true }`, so a `transport: 'http'` agent is rejected at construction rather than left holding a subscription that never delivers. It remains the compatibility default for omitted `plugins`; new code should install it explicitly from `@abloatai/humans`. There is no `agents()` plugin — agents are the default caller, not a special one. A browser NEVER receives the secret key; mint a session token.
|
|
67
72
|
- CLI, `ablo` — scaffolding, schema push, connecting a database. Terminals and CI. Agents must run it non-interactively (see the CLI section below).
|
|
73
|
+
- CLI package, [`@abloatai/cli`](https://www.npmjs.com/package/@abloatai/cli) — the official npm package that installs the `ablo` binary.
|
|
68
74
|
- REST, `/api/v1` — runtimes with no SDK.
|
|
69
75
|
- Integration-helper MCP, hosted `/api/mcp` — teaching a coding assistant the SDK WHILE BUILDING an integration. Docs, schema lint, and scaffolds only; it has NO per-model data tools and cannot touch application rows.
|
|
70
76
|
|
|
@@ -103,7 +109,7 @@ const schema = defineSchema({
|
|
|
103
109
|
|
|
104
110
|
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
105
111
|
|
|
106
|
-
const report = await ablo.weatherReports.
|
|
112
|
+
const report = await ablo.weatherReports.read({ id: 'report_stockholm' });
|
|
107
113
|
if (!report) throw new Error('Row not found');
|
|
108
114
|
|
|
109
115
|
// Claim the row (waits if someone else holds it), read the fresh copy off
|
|
@@ -115,7 +121,7 @@ const updated = await ablo.weatherReports.update({
|
|
|
115
121
|
});
|
|
116
122
|
```
|
|
117
123
|
|
|
118
|
-
That is the normal app path: declare models in a schema, then use `ablo.<model>.
|
|
124
|
+
That is the normal app path: declare models in a schema, then use `ablo.<model>.read({ id })`, `ablo.<model>.create(...)`, `ablo.<model>.update(...)`, and `ablo.<model>.delete(...)`. Every verb takes a single options object.
|
|
119
125
|
|
|
120
126
|
Treat the schema as the integration contract. It drives typed model clients,
|
|
121
127
|
React selectors, server and agent writes, Data Source request/response shape,
|
|
@@ -126,12 +132,12 @@ For full integrations, use `integration-guide` as the canonical doc. It covers
|
|
|
126
132
|
the same model API across your own Data Source-backed app databases,
|
|
127
133
|
React selectors, multiplayer, and future agent workers.
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
Server reads separate observation from decision input. `get({ id })` and
|
|
136
|
+
`list({ where })` return current data without creating stale evidence.
|
|
137
|
+
`read({ id })` returns the same row with private `{ model, id, readAt }` evidence
|
|
138
|
+
for a specific mutation's `reads` array. `local.get(id)`,
|
|
132
139
|
`local.list({ where })`, and `local.count({ where })` are the same reads narrowed
|
|
133
|
-
to what has already synced: nothing to await, reactive in render.
|
|
134
|
-
second verb to learn — `local.` is the only difference. The query reads accept
|
|
140
|
+
to what has already synced: nothing to await, reactive in render. The collection queries accept
|
|
135
141
|
`where`, `filter`, `orderBy`, `limit`, `offset`,
|
|
136
142
|
and `state`; state defaults to `'live'`, with `'archived'` and `'all'` to include
|
|
137
143
|
retired rows.
|
|
@@ -168,7 +174,7 @@ coordination until the app reports it through Data Source events.
|
|
|
168
174
|
|
|
169
175
|
## Change propagation
|
|
170
176
|
|
|
171
|
-
A change to one row reaches other rows three ways. ROUTING: a write fans out to every sync group the row belongs to, INCLUDING its ancestors' groups (editing a block routes to `block:` + `document:` + `workspace:`), so everyone watching the workspace sees it — delivery, not recomputation. DELETE CASCADE: deleting a parent emits explicit tombstone deltas for its descendants, so open clients never silently hold rows that are gone. VALUE: derived values are NOT recomputed server-side — Ablo surfaces that the source moved and the actor decides. To keep dependent work fresh,
|
|
177
|
+
A change to one row reaches other rows three ways. ROUTING: a write fans out to every sync group the row belongs to, INCLUDING its ancestors' groups (editing a block routes to `block:` + `document:` + `workspace:`), so everyone watching the workspace sees it — delivery, not recomputation. DELETE CASCADE: deleting a parent emits explicit tombstone deltas for its descendants, so open clients never silently hold rows that are gone. VALUE: derived values are NOT recomputed server-side — Ablo surfaces that the source moved and the actor decides. To keep dependent work fresh, pass rows returned by `ablo.<model>.read({ id })` in the mutation's `reads` array. Ablo records compact model/id/readAt evidence, not row contents. At commit the server checks whether anything moved past the read watermark; if so, the mutation does not land. Use `get` when no such relationship exists. To chain A→B→C, put A+B in one group and B+C in another: A's change reaches B, and C hears it only once B ITSELF writes. No transitive auto-recompute, no convergence guarantee for cycles.
|
|
172
178
|
|
|
173
179
|
## Nouns
|
|
174
180
|
|
|
@@ -180,7 +186,7 @@ A change to one row reaches other rows three ways. ROUTING: a write fans out to
|
|
|
180
186
|
## Claimed Behavior
|
|
181
187
|
|
|
182
188
|
Reads never silently block. Schema reads stay open while a row is claimed.
|
|
183
|
-
Typed reads through `ablo.<model>.
|
|
189
|
+
Typed reads through `ablo.<model>.read({ id })` return the row by default,
|
|
184
190
|
even while it is claimed. Pass `ifClaimed: 'fail'` to throw
|
|
185
191
|
`AbloClaimedError`, and inspect active coordination separately through
|
|
186
192
|
`ablo.<model>.claim.state({ id })`.
|
|
@@ -198,7 +204,7 @@ frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
|
|
|
198
204
|
|
|
199
205
|
## Guarantees
|
|
200
206
|
|
|
201
|
-
Schema model writes update local state optimistically, while their returned promise always waits for authoritative confirmation. Server rejection rolls back local state. To prevent lost updates,
|
|
207
|
+
Schema model writes update local state optimistically, while their returned promise always waits for authoritative confirmation. Server rejection rolls back local state. To prevent lost updates, fetch the decision input with `read({ id })` and pass that exact row in the mutation's `reads` array. If it changed, the mutation does not land.
|
|
202
208
|
|
|
203
209
|
Claims coordinate writers; they do not block readers. Most users should stay on
|
|
204
210
|
schema-backed reads/writes and `claim(...)`; manual protocol bookkeeping is not
|
|
@@ -282,6 +288,11 @@ Import from these public paths only:
|
|
|
282
288
|
|
|
283
289
|
Do not teach `/api`, `/agent`, `/core`, `/realtime`, or internal subpaths. (`/source` and `/ai-sdk` are public.)
|
|
284
290
|
|
|
291
|
+
`onChange` is WebSocket-only. It exists on the reactive client because that
|
|
292
|
+
client holds an open socket and a local graph. The stateless HTTP client does
|
|
293
|
+
not expose it at the type or runtime level; HTTP agents re-read or inspect
|
|
294
|
+
durable logs instead.
|
|
295
|
+
|
|
285
296
|
## CLI: agents run it NON-INTERACTIVELY
|
|
286
297
|
|
|
287
298
|
`ablo init` and other prompts need a TTY; an agent/CI run has none and will HANG. Always:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/ablo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "The public Ablo SDK for coordinated reads, commits, claims, observation, and reactive applications.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -81,9 +81,9 @@
|
|
|
81
81
|
"default": "./dist/ai-sdk.js"
|
|
82
82
|
},
|
|
83
83
|
"./context": {
|
|
84
|
-
"types": "./dist/context.d.ts",
|
|
85
|
-
"import": "./dist/context.js",
|
|
86
|
-
"default": "./dist/context.js"
|
|
84
|
+
"types": "./dist/context/index.d.ts",
|
|
85
|
+
"import": "./dist/context/index.js",
|
|
86
|
+
"default": "./dist/context/index.js"
|
|
87
87
|
},
|
|
88
88
|
"./wire": {
|
|
89
89
|
"types": "./dist/wire.d.ts",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"prepack": "npm run build && node scripts/strip-source-condition.mjs",
|
|
113
113
|
"postpack": "node scripts/restore-source-condition.mjs",
|
|
114
114
|
"pack:check": "node scripts/pack-check.mjs",
|
|
115
|
+
"verify:context-package": "node scripts/verify-context-package.mjs",
|
|
115
116
|
"typecheck": "tsc --noEmit && tsc -p typetests/tsconfig.json && tsc -p examples/tsconfig.json",
|
|
116
117
|
"test": "vitest run",
|
|
117
118
|
"generate:errors": "tsx scripts/generate-error-docs.mts",
|
|
@@ -139,8 +140,8 @@
|
|
|
139
140
|
"directory": "packages/ablo"
|
|
140
141
|
},
|
|
141
142
|
"dependencies": {
|
|
142
|
-
"@abloatai/humans": "^0.
|
|
143
|
-
"@abloatai/transaction": "^0.
|
|
143
|
+
"@abloatai/humans": "^0.59.0",
|
|
144
|
+
"@abloatai/transaction": "^0.59.0",
|
|
144
145
|
"zod": "^4.4.3"
|
|
145
146
|
},
|
|
146
147
|
"peerDependencies": {
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { ContextEvidenceSlice } from './evidence.js';
|
|
3
|
-
export declare const contextSourceSchema: z.ZodReadonly<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4
|
-
key: z.ZodString;
|
|
5
|
-
kind: z.ZodLiteral<"ablo">;
|
|
6
|
-
guarantee: z.ZodLiteral<"guardable">;
|
|
7
|
-
cursor: z.ZodNumber;
|
|
8
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
9
|
-
key: z.ZodString;
|
|
10
|
-
kind: z.ZodLiteral<"value">;
|
|
11
|
-
guarantee: z.ZodLiteral<"informational">;
|
|
12
|
-
cursor: z.ZodNull;
|
|
13
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
-
key: z.ZodString;
|
|
15
|
-
kind: z.ZodLiteral<"mixed">;
|
|
16
|
-
guarantee: z.ZodLiteral<"partial">;
|
|
17
|
-
cursor: z.ZodNumber;
|
|
18
|
-
}, z.core.$strip>], "kind">>;
|
|
19
|
-
export type ContextSource = z.infer<typeof contextSourceSchema>;
|
|
20
|
-
export declare function sourceFor(key: string, evidence: ContextEvidenceSlice): ContextSource;
|
|
21
|
-
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../src/context/sources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;4BAmBnB,CAAC;AAEd,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,oBAAoB,GAC7B,aAAa,CAaf"}
|
package/dist/context/sources.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export const contextSourceSchema = z.discriminatedUnion('kind', [
|
|
3
|
-
z.object({
|
|
4
|
-
key: z.string(),
|
|
5
|
-
kind: z.literal('ablo'),
|
|
6
|
-
guarantee: z.literal('guardable'),
|
|
7
|
-
cursor: z.number().int().nonnegative(),
|
|
8
|
-
}),
|
|
9
|
-
z.object({
|
|
10
|
-
key: z.string(),
|
|
11
|
-
kind: z.literal('value'),
|
|
12
|
-
guarantee: z.literal('informational'),
|
|
13
|
-
cursor: z.null(),
|
|
14
|
-
}),
|
|
15
|
-
z.object({
|
|
16
|
-
key: z.string(),
|
|
17
|
-
kind: z.literal('mixed'),
|
|
18
|
-
guarantee: z.literal('partial'),
|
|
19
|
-
cursor: z.number().int().nonnegative(),
|
|
20
|
-
}),
|
|
21
|
-
]).readonly();
|
|
22
|
-
export function sourceFor(key, evidence) {
|
|
23
|
-
if (evidence.reads.length === 0) {
|
|
24
|
-
return contextSourceSchema.parse({
|
|
25
|
-
key,
|
|
26
|
-
kind: 'value',
|
|
27
|
-
guarantee: 'informational',
|
|
28
|
-
cursor: null,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
const cursor = Math.max(...evidence.reads.map((item) => item.entry.watermark));
|
|
32
|
-
return evidence.includesInformational
|
|
33
|
-
? contextSourceSchema.parse({ key, kind: 'mixed', guarantee: 'partial', cursor })
|
|
34
|
-
: contextSourceSchema.parse({ key, kind: 'ablo', guarantee: 'guardable', cursor });
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=sources.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/context/sources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC9D,CAAC,CAAC,MAAM,CAAC;QACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACvB,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACjC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;QACrC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE;KACjB,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC/B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;CACH,CAAC,CAAC,QAAQ,EAAE,CAAC;AAId,MAAM,UAAU,SAAS,CACvB,GAAW,EACX,QAA8B;IAE9B,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,mBAAmB,CAAC,KAAK,CAAC;YAC/B,GAAG;YACH,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,eAAe;YAC1B,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;IACL,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/E,OAAO,QAAQ,CAAC,qBAAqB;QACnC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QACjF,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;AACvF,CAAC"}
|
package/dist/context.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { CapturedRow } from '@abloatai/transaction';
|
|
2
|
-
import { type AwaitedDeep } from './context/await.js';
|
|
3
|
-
import { type ContextSource } from './context/sources.js';
|
|
4
|
-
export { contextSourceSchema, type ContextSource } from './context/sources.js';
|
|
5
|
-
export type { AwaitedDeep } from './context/await.js';
|
|
6
|
-
export interface ContextOptions<TData extends Readonly<Record<string, unknown>>> {
|
|
7
|
-
/** The client whose read evidence may guard a later write. */
|
|
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
|
-
/** Exact returned Ablo rows, ready to pass to a write's `reads` option. */
|
|
15
|
-
readonly reads: readonly CapturedRow[];
|
|
16
|
-
/** The greatest watermark among included authoritative reads. */
|
|
17
|
-
readonly cursor: number | null;
|
|
18
|
-
readonly sources: readonly ContextSource[];
|
|
19
|
-
}
|
|
20
|
-
/** Assemble selected application values and the Ablo evidence they retain. */
|
|
21
|
-
export declare function context<const TData extends Readonly<Record<string, unknown>>>(options: ContextOptions<TData>): Promise<ContextResult<TData>>;
|
|
22
|
-
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC/E,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7E,8DAA8D;IAC9D,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,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED,8EAA8E;AAC9E,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,CAsB/B"}
|
package/dist/context.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Additive context assembly for one Ablo client.
|
|
3
|
-
*
|
|
4
|
-
* The caller chooses the data. This module awaits it, reports the exact Ablo
|
|
5
|
-
* rows it contains, and leaves model execution and external retrieval alone.
|
|
6
|
-
*/
|
|
7
|
-
import { z } from 'zod';
|
|
8
|
-
import { awaitDeep } from './context/await.js';
|
|
9
|
-
import { bindContextEvidence } from './context/evidence.js';
|
|
10
|
-
import { sourceFor } from './context/sources.js';
|
|
11
|
-
export { contextSourceSchema } from './context/sources.js';
|
|
12
|
-
const contextDataSchema = z.record(z.string(), z.unknown());
|
|
13
|
-
/** Assemble selected application values and the Ablo evidence they retain. */
|
|
14
|
-
export async function context(options) {
|
|
15
|
-
const collectEvidence = bindContextEvidence(options.ablo);
|
|
16
|
-
const data = await awaitDeep(options.data);
|
|
17
|
-
const parsed = contextDataSchema.safeParse(data);
|
|
18
|
-
if (!parsed.success) {
|
|
19
|
-
throw new TypeError('context() requires `data` to be an object.', { cause: parsed.error });
|
|
20
|
-
}
|
|
21
|
-
const evidence = collectEvidence(data);
|
|
22
|
-
const sources = Object.entries(data).map(([key, value]) => sourceFor(key, evidence.inspect(value)));
|
|
23
|
-
const cursor = evidence.all.length === 0
|
|
24
|
-
? null
|
|
25
|
-
: Math.max(...evidence.all.map((item) => item.entry.watermark));
|
|
26
|
-
return {
|
|
27
|
-
data: data,
|
|
28
|
-
reads: evidence.all.map((item) => item.row),
|
|
29
|
-
cursor,
|
|
30
|
-
sources,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAsB,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAsB,MAAM,sBAAsB,CAAC;AAG/E,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAkB5D,8EAA8E;AAC9E,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,IAAI,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACxD,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACxC,CAAC;IACF,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC;QACtC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAElE,OAAO;QACL,IAAI,EAAE,IAA0B;QAChC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAkB,CAAC;QAC1D,MAAM;QACN,OAAO;KACR,CAAC;AACJ,CAAC"}
|