@abloatai/ablo 0.56.0 → 0.58.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 +10 -4
- package/CHANGELOG.md +428 -10
- package/LICENSE +1 -1
- package/NOTICE +3 -3
- package/README.md +2 -1
- 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/agent-integration-decision-guide.md +123 -0
- package/docs/agents.md +74 -13
- package/docs/api-keys.md +6 -6
- package/docs/api.md +117 -43
- package/docs/branch-development.md +23 -4
- package/docs/cli.md +16 -9
- package/docs/client-behavior.md +21 -15
- package/docs/concurrency-convention.md +67 -77
- package/docs/context.md +56 -31
- package/docs/coordination.md +115 -36
- package/docs/customer-organizations.md +49 -31
- package/docs/data-sources.md +12 -6
- package/docs/debugging.md +1 -1
- package/docs/examples/agent-human.md +6 -18
- package/docs/examples/coordination-conformance.md +69 -0
- package/docs/examples/existing-document-pipeline.md +488 -0
- package/docs/examples/existing-python-backend.md +10 -13
- package/docs/examples/nextjs.md +49 -6
- package/docs/examples/scoped-agent.md +18 -1
- package/docs/examples/server-agent.md +2 -2
- package/docs/groups.md +19 -139
- package/docs/guarantees.md +5 -6
- package/docs/identity.md +2 -1
- package/docs/index.md +5 -0
- package/docs/integration-guide.md +46 -19
- package/docs/integrations/sandbox-runtime.md +148 -0
- package/docs/integrations.md +9 -0
- package/docs/operating-on-your-database.md +7 -0
- package/docs/quickstart.md +19 -13
- package/docs/react.md +9 -9
- package/docs/schema-contract.md +14 -13
- package/docs/session-settings.md +9 -0
- package/docs/sessions.md +1 -1
- package/examples/README.md +2 -2
- package/examples/agent-turn.ts +1 -1
- package/examples/data-source/customer-server.ts +12 -5
- package/examples/expensive-agent-turn.ts +1 -1
- package/llms.txt +72 -10
- package/package.json +6 -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
|
@@ -39,6 +39,13 @@ Ablo prevents a non-holder from writing through another participant's claim by
|
|
|
39
39
|
default. It does not infer that every plain update is a read-modify-write
|
|
40
40
|
operation; use the guarded forms when lost-update protection matters.
|
|
41
41
|
|
|
42
|
+
Direct SQL remains outside that enforcement path. If an existing application
|
|
43
|
+
service writes the same row directly, keep its database constraints and locks
|
|
44
|
+
as the final integrity boundary and route every participating caller through
|
|
45
|
+
one named operation. Ablo can select one worker before expensive work begins;
|
|
46
|
+
the existing service must still re-read, validate, and commit authoritatively.
|
|
47
|
+
See [Coordinate an existing database operation](./coordination.md#coordinate-an-existing-database-operation).
|
|
48
|
+
|
|
42
49
|
## Three kinds of action
|
|
43
50
|
|
|
44
51
|
Sort any action you're about to take into one of these, and the right move
|
package/docs/quickstart.md
CHANGED
|
@@ -10,10 +10,11 @@ confirms it by tailing your write-ahead log (WAL). Your rows live in your databa
|
|
|
10
10
|
which stays the system of record. Ablo writes rows but **runs no DDL and owns no
|
|
11
11
|
schema** — your migration tool stays in charge of the shape of your database.
|
|
12
12
|
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
> database when you're ready
|
|
13
|
+
> **Which database?** A branch needs one connected before a schema will push, so
|
|
14
|
+
> start on a throwaway rather than your production Postgres. `ablo dev` gives
|
|
15
|
+
> each Git branch its own isolated plane: point a development branch at a local
|
|
16
|
+
> or separate database, and connect your production one when you're ready for it
|
|
17
|
+
> to be the system of record.
|
|
17
18
|
|
|
18
19
|
## 1. Install and initialize
|
|
19
20
|
|
|
@@ -26,14 +27,16 @@ npx ablo init
|
|
|
26
27
|
by signing you in — one browser click, and a project-scoped `mk_` management
|
|
27
28
|
credential is saved locally. Later, `npx ablo dev` (step 4) prepares an isolated branch and writes
|
|
28
29
|
its temporary `ABLO_API_KEY` into `.env.local`, so the SDK follows your Git
|
|
29
|
-
branch with no manual copy-paste. `npx ablo login` also exists standalone.
|
|
30
|
-
CI
|
|
30
|
+
branch with no manual copy-paste. `npx ablo login` also exists standalone.
|
|
31
|
+
Headless CI uses the same credential input as every other Ablo process; during branch
|
|
32
|
+
preparation it contains an `mk_` credential:
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
export
|
|
35
|
+
export ABLO_API_KEY=mk_...
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
Do not pass that value to application code. Every runtime call needs a
|
|
39
|
+
branch-bound API key. `ablo dev` exchanges the
|
|
37
40
|
stored management credential for
|
|
38
41
|
an expiring `sk_*` key bound to the current development branch.
|
|
39
42
|
Production runtimes use `sk_*`. In production a key points at the database
|
|
@@ -67,9 +70,12 @@ export const schema = defineSchema({
|
|
|
67
70
|
});
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
`
|
|
72
|
-
`
|
|
73
|
+
**`id` is the only automatic field.** Every row carries one, so leave it out of
|
|
74
|
+
`model(...)`. Everything else is yours, audit fields included: declare
|
|
75
|
+
`createdAt`, `updatedAt` or `createdBy` and `ablo migrate` gives each a column,
|
|
76
|
+
which your write or a database default then fills. Ablo does not write them for
|
|
77
|
+
you. A model that omits them still reads and writes, it just orders and
|
|
78
|
+
attributes its history less precisely.
|
|
73
79
|
|
|
74
80
|
The schema is registered once (init scaffolds `ablo/register.ts` for you), and
|
|
75
81
|
every type is one parameter away — no `typeof schema` re-stating, anywhere:
|
|
@@ -229,12 +235,12 @@ const updated = await ablo.weatherReports.update({
|
|
|
229
235
|
console.log({ id: updated.id, status: updated.status }); // { id: '...', status: 'ready' }
|
|
230
236
|
```
|
|
231
237
|
|
|
232
|
-
Read a single row back with `
|
|
238
|
+
Read a single row back with `read({ id })`. It resolves to the row, or to
|
|
233
239
|
`undefined` when no row has that id — so narrow it once, then the fields are
|
|
234
240
|
fully typed:
|
|
235
241
|
|
|
236
242
|
```ts
|
|
237
|
-
const report = await ablo.weatherReports.
|
|
243
|
+
const report = await ablo.weatherReports.read({ id: created.id });
|
|
238
244
|
if (!report) throw new Error(`weatherReports ${created.id} not found`);
|
|
239
245
|
|
|
240
246
|
console.log(report.status); // 'ready'
|
package/docs/react.md
CHANGED
|
@@ -25,7 +25,7 @@ as props. Construct the client once, then pass that instance to the provider.
|
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
// lib/ablo.ts
|
|
28
|
-
import Ablo from '@abloatai/ablo';
|
|
28
|
+
import { Ablo } from '@abloatai/ablo/react';
|
|
29
29
|
import { createAbloReact } from '@abloatai/ablo/react';
|
|
30
30
|
import { schema } from '@/ablo/schema';
|
|
31
31
|
|
|
@@ -140,12 +140,12 @@ const reports = useAblo((ablo) =>
|
|
|
140
140
|
## Server Load
|
|
141
141
|
|
|
142
142
|
```tsx
|
|
143
|
-
const report = await ablo.weatherReports.
|
|
143
|
+
const report = await ablo.weatherReports.read({ id });
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
Use `get` in Server Components when the row may not be in the local pool
|
|
147
147
|
yet — it hydrates from the local store and the server, and returns a Promise, so
|
|
148
|
-
`await` it. (Server reads come in two shapes: `
|
|
148
|
+
`await` it. (Server reads come in two shapes: `read({ id })` for one row and
|
|
149
149
|
`list({ where })` for many; both are async. The synchronous local reads are
|
|
150
150
|
the `local` reads, used in render below.)
|
|
151
151
|
|
|
@@ -156,12 +156,12 @@ For Server Actions and route handlers, call the SDK directly:
|
|
|
156
156
|
```ts
|
|
157
157
|
import { ablo } from '@/lib/ablo';
|
|
158
158
|
|
|
159
|
-
const
|
|
159
|
+
const report = await ablo.weatherReports.read({ id });
|
|
160
|
+
if (!report) throw new Error('report not found');
|
|
160
161
|
await ablo.weatherReports.update({
|
|
161
162
|
id,
|
|
162
163
|
data: patch,
|
|
163
|
-
|
|
164
|
-
onStale: 'reject',
|
|
164
|
+
reads: [report],
|
|
165
165
|
});
|
|
166
166
|
```
|
|
167
167
|
|
|
@@ -173,12 +173,12 @@ const ablo = useAblo();
|
|
|
173
173
|
|
|
174
174
|
async function markReady() {
|
|
175
175
|
if (!ablo) return;
|
|
176
|
-
const
|
|
176
|
+
const report = await ablo.weatherReports.read({ id });
|
|
177
|
+
if (!report) return;
|
|
177
178
|
await ablo.weatherReports.update({
|
|
178
179
|
id,
|
|
179
180
|
data: { status: 'ready' },
|
|
180
|
-
|
|
181
|
-
onStale: 'reject',
|
|
181
|
+
reads: [report],
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
184
|
```
|
package/docs/schema-contract.md
CHANGED
|
@@ -51,26 +51,27 @@ The model key (`weatherReports`) becomes the client namespace
|
|
|
51
51
|
contract. You should not create a parallel string-keyed write path for the same
|
|
52
52
|
data.
|
|
53
53
|
|
|
54
|
-
###
|
|
54
|
+
### The one field you don't declare
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
`id` is supplied on every row, so leave it out of your `model(...)` fields. That
|
|
57
|
+
is the whole list.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
Two things look like framework territory and are not. **Audit fields are yours to
|
|
60
|
+
declare and yours to fill.** Add `createdAt`, `updatedAt` or `createdBy` and
|
|
61
|
+
`ablo migrate` gives each a column, which your own write or a database default
|
|
62
|
+
then populates; Ablo records who changed what in its transaction log and does not
|
|
63
|
+
write these columns for you. Omit them and no column is created at all; the model
|
|
64
|
+
still reads and writes, it just orders and attributes its history less precisely.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
**The tenancy column** (`organizationId` by default) comes from the model's
|
|
67
|
+
`policy` rather than its field list, so you neither declare it nor lose it.
|
|
67
68
|
|
|
68
69
|
## Reads and writes
|
|
69
70
|
|
|
70
71
|
Use async reads when the row may not be local:
|
|
71
72
|
|
|
72
73
|
```ts
|
|
73
|
-
const report = await ablo.weatherReports.
|
|
74
|
+
const report = await ablo.weatherReports.read({ id: reportId });
|
|
74
75
|
const ready = await ablo.weatherReports.list({ where: { status: 'ready' } });
|
|
75
76
|
```
|
|
76
77
|
|
|
@@ -123,8 +124,8 @@ session route, never a raw API key.
|
|
|
123
124
|
- Keep direct database writes out of the coordinated path unless they are reported
|
|
124
125
|
back through Data Source events.
|
|
125
126
|
- Use `claim` for slow read -> think -> write spans.
|
|
126
|
-
- Use `
|
|
127
|
-
after it was read.
|
|
127
|
+
- Use `read` and pass its exact row in `reads` when a write must fail if the row
|
|
128
|
+
changed after it was read.
|
|
128
129
|
|
|
129
130
|
For the shortest runnable path, start with [Quickstart](./quickstart.md). For a
|
|
130
131
|
production app, continue with [Integration Guide](./integration-guide.md).
|
package/docs/session-settings.md
CHANGED
|
@@ -50,6 +50,7 @@ context, whether or not you map anything:
|
|
|
50
50
|
| `app.current_participant_id` | The participant making the write |
|
|
51
51
|
| `app.current_participant_kind` | Whether that participant is a person, an agent, or the system |
|
|
52
52
|
| `app.current_user_id` | The person on whose behalf the write is made |
|
|
53
|
+
| `app.current_subject_groups` | The subject groups the caller belongs to, as a JSON array of `group:value` strings |
|
|
53
54
|
|
|
54
55
|
If your policies read these names directly, you need no mapping at all — this
|
|
55
56
|
page is for the case where they read different ones.
|
|
@@ -62,6 +63,14 @@ from `app.current_org_id` can be useful as defense in depth, but it is not a
|
|
|
62
63
|
substitute for the tenant policy and you should never loosen RLS to make an
|
|
63
64
|
Ablo write pass.
|
|
64
65
|
|
|
66
|
+
`app.current_subject_groups` is the one a model with a `subject` rule reads. A
|
|
67
|
+
subject rule names a field and a group — `subject: { field: 'teamId', group:
|
|
68
|
+
'team' }` — and Ablo provisions a policy asking whether the array contains
|
|
69
|
+
`team:` followed by that row's value. The setting is always present, and it is
|
|
70
|
+
`[]` when the caller belongs to no group, so a policy on a pooled connection
|
|
71
|
+
reads an empty membership as an empty membership rather than inheriting what the
|
|
72
|
+
previous transaction left behind.
|
|
73
|
+
|
|
65
74
|
`app.current_user_id` is worth reading twice, because it has three states rather
|
|
66
75
|
than two. It carries a person's id when a person is behind the write. It carries
|
|
67
76
|
`*` when a backend credential is acting as the organization itself, which is the
|
package/docs/sessions.md
CHANGED
package/examples/README.md
CHANGED
|
@@ -30,8 +30,8 @@ For read-reason-write work, pass the exact returned rows that informed the
|
|
|
30
30
|
decision. Their watermarks stay opaque:
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
|
-
const record = await ablo.records.
|
|
34
|
-
const policy = await ablo.policies.
|
|
33
|
+
const record = await ablo.records.read({ id: recordId });
|
|
34
|
+
const policy = await ablo.policies.read({ id: policyId });
|
|
35
35
|
const result = await model({ record, policy });
|
|
36
36
|
await ablo.records.update({
|
|
37
37
|
id: record.id,
|
package/examples/agent-turn.ts
CHANGED
|
@@ -21,7 +21,7 @@ if (!recordId) throw new Error('RECORD_ID is required');
|
|
|
21
21
|
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
22
22
|
try {
|
|
23
23
|
await ablo.ready();
|
|
24
|
-
const record = await ablo.records.
|
|
24
|
+
const record = await ablo.records.read({ id: recordId });
|
|
25
25
|
if (!record) throw new Error(`Record ${recordId} was not found`);
|
|
26
26
|
const commitId = `record:${recordId}:cheap`;
|
|
27
27
|
await ablo.records.update({
|
|
@@ -114,10 +114,14 @@ export const handleAbloSource = dataSource({
|
|
|
114
114
|
// own transaction. The example uses a synchronous in-memory
|
|
115
115
|
// update; the surrounding `apply` helper shows where you would
|
|
116
116
|
// open `db.transaction(async (tx) => { ... })`.
|
|
117
|
-
commit({ operations, clientTxId }) {
|
|
117
|
+
commit({ operations, clientTxId, context }) {
|
|
118
|
+
// The routes an outbox event carries come from the trusted scope Ablo
|
|
119
|
+
// signed into the request, never from the row itself. Ablo adds the
|
|
120
|
+
// organization group on its side; these are the finer ones.
|
|
121
|
+
const syncGroups = context.scope?.syncGroups ?? [];
|
|
118
122
|
const rows: RecordRow[] = [];
|
|
119
123
|
for (const op of operations) {
|
|
120
|
-
const row = applyOperation(op, clientTxId);
|
|
124
|
+
const row = applyOperation(op, clientTxId, syncGroups);
|
|
121
125
|
if (row) rows.push(row);
|
|
122
126
|
}
|
|
123
127
|
return { rows };
|
|
@@ -145,6 +149,7 @@ export const handleAbloSource = dataSource({
|
|
|
145
149
|
function applyOperation(
|
|
146
150
|
op: SourceOperation,
|
|
147
151
|
clientTxId: string | undefined,
|
|
152
|
+
syncGroups: readonly string[],
|
|
148
153
|
): RecordRow | null {
|
|
149
154
|
if (op.model !== 'records') return null;
|
|
150
155
|
const id = op.id ?? `record_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -160,7 +165,7 @@ function applyOperation(
|
|
|
160
165
|
: {}),
|
|
161
166
|
};
|
|
162
167
|
recordStore.set(id, row);
|
|
163
|
-
appendOutbox({ operation: op, entityId: id, data: row, clientTxId });
|
|
168
|
+
appendOutbox({ operation: op, entityId: id, data: row, clientTxId, syncGroups });
|
|
164
169
|
return row;
|
|
165
170
|
}
|
|
166
171
|
|
|
@@ -169,7 +174,7 @@ function applyOperation(
|
|
|
169
174
|
if (!existing) return null;
|
|
170
175
|
const next: RecordRow = { ...existing, ...(op.input as Partial<RecordRow>) };
|
|
171
176
|
recordStore.set(id, next);
|
|
172
|
-
appendOutbox({ operation: op, entityId: id, data: next, clientTxId });
|
|
177
|
+
appendOutbox({ operation: op, entityId: id, data: next, clientTxId, syncGroups });
|
|
173
178
|
return next;
|
|
174
179
|
}
|
|
175
180
|
|
|
@@ -177,7 +182,7 @@ function applyOperation(
|
|
|
177
182
|
const existing = recordStore.get(id);
|
|
178
183
|
if (!existing) return null;
|
|
179
184
|
recordStore.delete(id);
|
|
180
|
-
appendOutbox({ operation: op, entityId: id, data: null, clientTxId });
|
|
185
|
+
appendOutbox({ operation: op, entityId: id, data: null, clientTxId, syncGroups });
|
|
181
186
|
return existing;
|
|
182
187
|
}
|
|
183
188
|
|
|
@@ -189,6 +194,7 @@ function appendOutbox(input: {
|
|
|
189
194
|
entityId: string;
|
|
190
195
|
data: RecordRow | null;
|
|
191
196
|
clientTxId: string | undefined;
|
|
197
|
+
syncGroups: readonly string[];
|
|
192
198
|
}): void {
|
|
193
199
|
outboxSequence += 1;
|
|
194
200
|
outbox.push(
|
|
@@ -197,6 +203,7 @@ function appendOutbox(input: {
|
|
|
197
203
|
operation: input.operation,
|
|
198
204
|
entityId: input.entityId,
|
|
199
205
|
data: input.data,
|
|
206
|
+
syncGroups: input.syncGroups,
|
|
200
207
|
...(input.clientTxId ? { clientTxId: input.clientTxId } : {}),
|
|
201
208
|
}),
|
|
202
209
|
);
|
|
@@ -49,7 +49,7 @@ try {
|
|
|
49
49
|
if (!durable) throw new Error(`Commit ${commitId} was not retained`);
|
|
50
50
|
console.log({
|
|
51
51
|
identity: ablo.identity,
|
|
52
|
-
|
|
52
|
+
reads: durable.reads,
|
|
53
53
|
attempts: durable.attempts,
|
|
54
54
|
claims: durable.claims,
|
|
55
55
|
authority: durable.authority,
|
package/llms.txt
CHANGED
|
@@ -6,6 +6,62 @@ Here is the problem it solves. Two agents reach for `report_stockholm` at once.
|
|
|
6
6
|
|
|
7
7
|
Use AI SDK for the agent loop. Use Ablo when agent reads and writes must persist, coordinate with concurrent work, and leave an audit trail.
|
|
8
8
|
|
|
9
|
+
## When to use Ablo
|
|
10
|
+
|
|
11
|
+
Reach for Ablo when MORE THAN ONE writer changes the same rows and the second one must not clobber the first. That is the whole judgement. If a single process owns the data, an ORM against your database is simpler and you should use it.
|
|
12
|
+
|
|
13
|
+
These are the jobs it is right for:
|
|
14
|
+
|
|
15
|
+
- **Two or more agents on one workload.** A planner and three workers editing the same records; a swarm assigned overlapping tickets; a retry that restarts while the first attempt is still running. Each takes `claim({ id })` before its slow step, so the second waits and is handed the row as the first left it instead of overwriting.
|
|
16
|
+
- **A read → LLM call → write gap.** The row can change during the seconds your model is thinking. Pass the `readAt` stamp from your read back into the write and a stale write is refused (`AbloStaleContextError`) rather than silently applied on top of someone else's.
|
|
17
|
+
- **A human and an agent in the same record.** A person editing in your app is just another holder of the claim. The agent queues behind them; nobody's edit is lost, and neither side needs to know the other exists.
|
|
18
|
+
- **Work that must survive the process.** Commits are durable and receipts are idempotent, so a crashed or re-scheduled agent replays its write with the same `idempotencyKey` and gets the original result instead of a duplicate.
|
|
19
|
+
- **Anything you will later have to explain.** Every accepted change lands in an append-only log with who wrote it, when, and against which read. `logs` answers "what did the agents actually do" without you having instrumented for the question in advance.
|
|
20
|
+
- **Data you are not allowed to move.** Ablo runs on the customer's own Postgres. It tails the replication stream and writes back through a scoped role; your rows never live in Ablo. If a data-residency or procurement rule forbids a vendor holding the records, this is the shape that passes.
|
|
21
|
+
|
|
22
|
+
These are the jobs it is WRONG for, and saying so saves you a wasted integration:
|
|
23
|
+
|
|
24
|
+
- **One writer, no concurrency.** Use Prisma, Drizzle, or SQL. Ablo's coordination costs a round trip and buys you nothing here.
|
|
25
|
+
- **Chat history, traces, or evals.** Append-only telemetry has no contention to resolve. Write it wherever you write logs.
|
|
26
|
+
- **A cache, a queue, or a pub/sub bus.** Ablo is a commit chokepoint over a system of record, not a message broker. Emit your events as usual and use Ablo for the state they are about.
|
|
27
|
+
- **Vector search, embeddings, or retrieval.** Different problem. Ablo coordinates the rows; your vector store indexes them.
|
|
28
|
+
- **A file, blob, or artifact store.** Coordinate the ROW that points at the object; put the bytes in object storage.
|
|
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 })`. 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
|
+
|
|
32
|
+
## Machine-readable surfaces
|
|
33
|
+
|
|
34
|
+
Everything below is public, needs no credential, and is served from `https://www.abloatai.com`:
|
|
35
|
+
|
|
36
|
+
- `/llms.txt` — this file.
|
|
37
|
+
- `/llms-full.txt` — the entire published documentation corpus in one fetch.
|
|
38
|
+
- `/developers` — every developer surface named on one page: the SDK, the API reference, the OpenAPI document, the MCP server, the CLI.
|
|
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
|
+
- `/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.
|
|
42
|
+
- `/.well-known/mcp.json` — that server's manifest, in the MCP registry's `server.json` format.
|
|
43
|
+
- `/api/docs/<page>` — any documentation page as plain Markdown, for a client that fetches URLs rather than speaking MCP.
|
|
44
|
+
- `/sitemap.xml` — every indexable page on the domain.
|
|
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
|
+
|
|
50
|
+
Every page on that host serves a Markdown representation from its own URL: send
|
|
51
|
+
`Accept: text/markdown` (q-values are honoured), or append `.md` to the path if
|
|
52
|
+
your client cannot set the header. Responses carry `Vary: Accept`, a client that
|
|
53
|
+
will accept neither `text/html` nor `text/markdown` gets a `406` listing what is
|
|
54
|
+
available, and a path that does not exist answers a real `404` — never a `200`
|
|
55
|
+
carrying a sign-in page.
|
|
56
|
+
|
|
57
|
+
## Versioning and deprecation
|
|
58
|
+
|
|
59
|
+
Every route lives under `/v1`, and that segment is part of the address you call. A change that would break you arrives as a new segment beside it, never as a change to this one. Additive changes do land in `/v1` — a new response field, a new optional parameter, a new error code — so ignore what you do not recognize.
|
|
60
|
+
|
|
61
|
+
Every response carries `Ablo-Version`, a date stamp for the contract being served. A route being withdrawn says so on itself for at least 180 days first: `Deprecation` (RFC 9745) carries when the deprecation took effect and the route keeps answering; `Sunset` (RFC 8594) carries when it stops. The same operations are marked `deprecated: true` in `/openapi.json`.
|
|
62
|
+
|
|
63
|
+
Responses also carry `RateLimit-Policy` (the standing allowance, e.g. `"secret";q=600;w=12`) and, once your request is attributed to a key, `RateLimit` (what is left and when it refills). A 429 adds `Retry-After` in whole seconds. Pace against these rather than retrying blind.
|
|
64
|
+
|
|
9
65
|
## Surfaces: pick by who is calling
|
|
10
66
|
|
|
11
67
|
Every surface reaches the same coordinated state. They are not interchangeable.
|
|
@@ -14,6 +70,7 @@ Every surface reaches the same coordinated state. They are not interchangeable.
|
|
|
14
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.
|
|
15
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.
|
|
16
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.
|
|
17
74
|
- REST, `/api/v1` — runtimes with no SDK.
|
|
18
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.
|
|
19
76
|
|
|
@@ -52,7 +109,7 @@ const schema = defineSchema({
|
|
|
52
109
|
|
|
53
110
|
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
54
111
|
|
|
55
|
-
const report = await ablo.weatherReports.
|
|
112
|
+
const report = await ablo.weatherReports.read({ id: 'report_stockholm' });
|
|
56
113
|
if (!report) throw new Error('Row not found');
|
|
57
114
|
|
|
58
115
|
// Claim the row (waits if someone else holds it), read the fresh copy off
|
|
@@ -64,7 +121,7 @@ const updated = await ablo.weatherReports.update({
|
|
|
64
121
|
});
|
|
65
122
|
```
|
|
66
123
|
|
|
67
|
-
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.
|
|
68
125
|
|
|
69
126
|
Treat the schema as the integration contract. It drives typed model clients,
|
|
70
127
|
React selectors, server and agent writes, Data Source request/response shape,
|
|
@@ -75,12 +132,12 @@ For full integrations, use `integration-guide` as the canonical doc. It covers
|
|
|
75
132
|
the same model API across your own Data Source-backed app databases,
|
|
76
133
|
React selectors, multiplayer, and future agent workers.
|
|
77
134
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)`,
|
|
81
139
|
`local.list({ where })`, and `local.count({ where })` are the same reads narrowed
|
|
82
|
-
to what has already synced: nothing to await, reactive in render.
|
|
83
|
-
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
|
|
84
141
|
`where`, `filter`, `orderBy`, `limit`, `offset`,
|
|
85
142
|
and `state`; state defaults to `'live'`, with `'archived'` and `'all'` to include
|
|
86
143
|
retired rows.
|
|
@@ -117,7 +174,7 @@ coordination until the app reports it through Data Source events.
|
|
|
117
174
|
|
|
118
175
|
## Change propagation
|
|
119
176
|
|
|
120
|
-
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.
|
|
121
178
|
|
|
122
179
|
## Nouns
|
|
123
180
|
|
|
@@ -129,7 +186,7 @@ A change to one row reaches other rows three ways. ROUTING: a write fans out to
|
|
|
129
186
|
## Claimed Behavior
|
|
130
187
|
|
|
131
188
|
Reads never silently block. Schema reads stay open while a row is claimed.
|
|
132
|
-
Typed reads through `ablo.<model>.
|
|
189
|
+
Typed reads through `ablo.<model>.read({ id })` return the row by default,
|
|
133
190
|
even while it is claimed. Pass `ifClaimed: 'fail'` to throw
|
|
134
191
|
`AbloClaimedError`, and inspect active coordination separately through
|
|
135
192
|
`ablo.<model>.claim.state({ id })`.
|
|
@@ -147,7 +204,7 @@ frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
|
|
|
147
204
|
|
|
148
205
|
## Guarantees
|
|
149
206
|
|
|
150
|
-
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.
|
|
151
208
|
|
|
152
209
|
Claims coordinate writers; they do not block readers. Most users should stay on
|
|
153
210
|
schema-backed reads/writes and `claim(...)`; manual protocol bookkeeping is not
|
|
@@ -231,6 +288,11 @@ Import from these public paths only:
|
|
|
231
288
|
|
|
232
289
|
Do not teach `/api`, `/agent`, `/core`, `/realtime`, or internal subpaths. (`/source` and `/ai-sdk` are public.)
|
|
233
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
|
+
|
|
234
296
|
## CLI: agents run it NON-INTERACTIVELY
|
|
235
297
|
|
|
236
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.58.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",
|
|
@@ -139,8 +139,8 @@
|
|
|
139
139
|
"directory": "packages/ablo"
|
|
140
140
|
},
|
|
141
141
|
"dependencies": {
|
|
142
|
-
"@abloatai/humans": "^0.
|
|
143
|
-
"@abloatai/transaction": "^0.
|
|
142
|
+
"@abloatai/humans": "^0.58.0",
|
|
143
|
+
"@abloatai/transaction": "^0.58.0",
|
|
144
144
|
"zod": "^4.4.3"
|
|
145
145
|
},
|
|
146
146
|
"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"}
|