@abloatai/ablo 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +10 -2
- package/dist/Model.d.ts +39 -0
- package/dist/Model.js +68 -0
- package/dist/ai-sdk/claim-broadcast.d.ts +4 -3
- package/dist/ai-sdk/claim-broadcast.js +2 -2
- package/dist/ai-sdk/wrap.d.ts +5 -4
- package/dist/ai-sdk/wrap.js +3 -3
- package/dist/auth/credentialPolicy.d.ts +145 -0
- package/dist/auth/credentialPolicy.js +130 -0
- package/dist/cli.cjs +42 -7
- package/dist/client/Ablo.d.ts +64 -91
- package/dist/client/Ablo.js +43 -103
- package/dist/client/ApiClient.d.ts +10 -1
- package/dist/client/ApiClient.js +45 -22
- package/dist/client/auth.d.ts +12 -5
- package/dist/client/auth.js +2 -1
- package/dist/client/createModelProxy.d.ts +64 -17
- package/dist/client/createModelProxy.js +18 -12
- package/dist/client/httpClient.d.ts +17 -3
- package/dist/client/httpClient.js +1 -0
- package/dist/client/identity.js +134 -122
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sessionMint.d.ts +15 -0
- package/dist/client/sessionMint.js +86 -0
- package/dist/coordination/schema.d.ts +1 -1
- package/dist/coordination/schema.js +3 -1
- package/dist/errorCodes.d.ts +2 -0
- package/dist/errorCodes.js +2 -0
- package/dist/errors.d.ts +6 -3
- package/dist/errors.js +9 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -7
- package/dist/mutators/RecordingTransaction.js +14 -42
- package/dist/react/AbloProvider.d.ts +12 -13
- package/dist/react/AbloProvider.js +10 -10
- package/dist/react/context.d.ts +10 -45
- package/dist/react/context.js +12 -17
- package/dist/react/index.d.ts +8 -10
- package/dist/react/index.js +8 -11
- package/dist/react/useMutators.js +3 -2
- package/dist/react/useSyncStatus.d.ts +1 -1
- package/dist/react/useUndoScope.js +3 -2
- package/dist/realtime/index.d.ts +1 -1
- package/dist/schema/generate.js +1 -2
- package/dist/schema/model.d.ts +10 -3
- package/dist/schema/schema.d.ts +13 -2
- package/dist/schema/schema.js +26 -0
- package/dist/surface.d.ts +29 -0
- package/dist/surface.js +60 -0
- package/dist/sync/ConnectionManager.d.ts +16 -5
- package/dist/sync/ConnectionManager.js +42 -7
- package/dist/sync/createClaimStream.js +5 -4
- package/dist/sync/participants.js +1 -1
- package/dist/transactions/TransactionQueue.d.ts +0 -11
- package/dist/transactions/TransactionQueue.js +12 -56
- package/dist/types/global.d.ts +3 -0
- package/dist/types/streams.d.ts +17 -29
- package/dist/utils/mobx-setup.js +1 -0
- package/docs/api-keys.md +49 -0
- package/docs/api.md +3 -2
- package/docs/client-behavior.md +1 -0
- package/docs/coordination.md +75 -21
- package/docs/examples/existing-python-backend.md +9 -5
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/guarantees.md +4 -3
- package/docs/identity.md +89 -82
- package/docs/integration-guide.md +19 -10
- package/docs/migration.md +11 -3
- package/docs/quickstart.md +6 -2
- package/docs/react.md +3 -3
- package/docs/schema-contract.md +23 -5
- package/llms-full.txt +18 -16
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.js +0 -9
- package/dist/principal.d.ts +0 -44
- package/dist/principal.js +0 -49
- package/dist/react/SyncGroupProvider.d.ts +0 -19
- package/dist/react/SyncGroupProvider.js +0 -44
- package/dist/react/useClaim.d.ts +0 -29
- package/dist/react/useClaim.js +0 -42
- package/dist/react/usePresence.d.ts +0 -32
- package/dist/react/usePresence.js +0 -41
|
@@ -44,13 +44,18 @@ objects by hand.
|
|
|
44
44
|
|
|
45
45
|
## Your Database
|
|
46
46
|
|
|
47
|
-
Every schema model is backed by **your own database**.
|
|
48
|
-
|
|
49
|
-
Postgres. The SDK call shape is the same everywhere.
|
|
47
|
+
Every schema model is backed by **your own database**. The SDK call shape is the
|
|
48
|
+
same everywhere.
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
|
|
50
|
+
In this guide — an app that already owns its backend and database — keep
|
|
51
|
+
`DATABASE_URL` inside your app and expose a signed Data Source endpoint: Ablo
|
|
52
|
+
coordinates each write and your app commits it to your Postgres. Do not pass
|
|
53
|
+
`databaseUrl` to `Ablo(...)` here; application and agent code use `ABLO_API_KEY`.
|
|
54
|
+
|
|
55
|
+
If instead you want Ablo to connect to your Postgres directly, pass `databaseUrl`
|
|
56
|
+
(a live, server-only option) to `Ablo(...)`. That and the sandbox-only `apiKey`
|
|
57
|
+
shape are the other two start states — [Connect Your Database](./data-sources.md)
|
|
58
|
+
is the single source of truth for all three.
|
|
54
59
|
|
|
55
60
|
## Test With Sandboxes
|
|
56
61
|
|
|
@@ -94,12 +99,13 @@ import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
|
94
99
|
export const schema = defineSchema(
|
|
95
100
|
{
|
|
96
101
|
weatherReports: model({
|
|
97
|
-
id
|
|
102
|
+
// Reserved fields (id, createdAt, updatedAt, organizationId, createdBy)
|
|
103
|
+
// are SDK-provided automatically — never declare them. Declare only your
|
|
104
|
+
// own fields.
|
|
98
105
|
projectId: z.string(),
|
|
99
106
|
location: z.string(),
|
|
100
107
|
status: z.enum(['pending', 'ready']),
|
|
101
108
|
assigneeId: z.string().nullable(),
|
|
102
|
-
updatedAt: z.string(),
|
|
103
109
|
}),
|
|
104
110
|
},
|
|
105
111
|
{
|
|
@@ -169,7 +175,7 @@ export const ablo = Ablo({
|
|
|
169
175
|
Browser apps should use the React provider or a scoped session token, not a
|
|
170
176
|
server API key in the bundle. Build the client first, then hand it to the
|
|
171
177
|
provider — `AbloProvider` takes `{ client, userId?, onError?, fallback? }`, and
|
|
172
|
-
nothing else (`schema`, `teamIds`,
|
|
178
|
+
nothing else (`schema`, `teamIds`, and `apiKey` all live on the
|
|
173
179
|
client now).
|
|
174
180
|
|
|
175
181
|
```tsx
|
|
@@ -179,7 +185,10 @@ import { schema } from '@/ablo/schema';
|
|
|
179
185
|
|
|
180
186
|
// The browser never holds the API key. The client mints a short-lived token
|
|
181
187
|
// from your session route (see below) and refreshes it before expiry.
|
|
182
|
-
export const ablo = Ablo({
|
|
188
|
+
export const ablo = Ablo({
|
|
189
|
+
schema,
|
|
190
|
+
apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
|
|
191
|
+
});
|
|
183
192
|
```
|
|
184
193
|
|
|
185
194
|
```tsx
|
package/docs/migration.md
CHANGED
|
@@ -113,6 +113,11 @@ const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: 'http'
|
|
|
113
113
|
await ablo.tasks.update({ id, data: { status: 'done' } });
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
> **Minting still needs the stateful client.** `sessions.create(...)` is not on
|
|
117
|
+
> the `transport: 'http'` surface. Keep a default-transport `server` client for
|
|
118
|
+
> minting short-lived credentials (see the 0.9.2 example below), and use the
|
|
119
|
+
> http client for the per-request reads and writes.
|
|
120
|
+
|
|
116
121
|
---
|
|
117
122
|
|
|
118
123
|
## 0.9.2 — `turn` / agent-`tasks` removed; `intents` deprecated
|
|
@@ -135,8 +140,10 @@ helper, and the agent/task type family (`Agent`, `AgentOptions`,
|
|
|
135
140
|
```diff
|
|
136
141
|
- const turn = await engine.beginTurn();
|
|
137
142
|
- await Ablo({ apiKey }).agent(agentId, opts).run(prompt, handler);
|
|
138
|
-
+ // Mint a scoped credential
|
|
139
|
-
+
|
|
143
|
+
+ // Mint a scoped credential from a stateful (default-transport) server client —
|
|
144
|
+
+ // sessions.create lives on the stateful client, not on transport: 'http'.
|
|
145
|
+
+ const server = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
146
|
+
+ const { token } = await server.sessions.create({ agent: { id: agentId } });
|
|
140
147
|
+ const agent = Ablo({ schema, apiKey: token });
|
|
141
148
|
+ await using claim = await agent.tasks.claim({ id });
|
|
142
149
|
+ await agent.tasks.update({ id, data: { status: 'done' }, wait: 'confirmed' });
|
|
@@ -277,7 +284,8 @@ One provider component now owns the full React lifecycle. `<SyncProvider>`,
|
|
|
277
284
|
- <SyncProvider store={sync._store} organizationId={orgId}>
|
|
278
285
|
- <AbloProvider ablo={ablo}>{children}</AbloProvider>
|
|
279
286
|
- </SyncProvider>
|
|
280
|
-
+
|
|
287
|
+
+ const ablo = Ablo({ schema, apiKey });
|
|
288
|
+
+ <AbloProvider client={ablo}>
|
|
281
289
|
+ {children}
|
|
282
290
|
+ </AbloProvider>
|
|
283
291
|
```
|
package/docs/quickstart.md
CHANGED
|
@@ -57,6 +57,9 @@ export const schema = defineSchema({
|
|
|
57
57
|
});
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
**Reserved fields** — `id`, `createdAt`, `updatedAt`, `organizationId`, and
|
|
61
|
+
`createdBy` are provided by the SDK automatically. Don't declare them in your
|
|
62
|
+
`model(...)` fields; declare only your own.
|
|
60
63
|
|
|
61
64
|
The schema is registered once (init scaffolds `ablo/register.ts` for you), and
|
|
62
65
|
every type is one parameter away — no `typeof schema` re-stating, anywhere:
|
|
@@ -210,8 +213,9 @@ and you write the usual way with `ablo.<model>.update({ id, data })`.
|
|
|
210
213
|
Claims don't lock. If another writer holds the row, `claim` waits for them,
|
|
211
214
|
re-reads the fresh row, then hands it to you — so two writers serialize instead
|
|
212
215
|
of clobbering. Normal reads still work while the claim is held. If a server read
|
|
213
|
-
should not return a row while someone else is mid-edit, pass `ifClaimed: '
|
|
214
|
-
to
|
|
216
|
+
should not return a row while someone else is mid-edit, pass `ifClaimed: 'fail'`
|
|
217
|
+
to error out instead. Reads never block on a claim — to wait for a row to free
|
|
218
|
+
up, `claim({ id })` it (the claim queues fairly behind the holder).
|
|
215
219
|
Call `handle.release()` when your work is done.
|
|
216
220
|
|
|
217
221
|
```ts
|
package/docs/react.md
CHANGED
|
@@ -31,7 +31,7 @@ import { schema } from '@/ablo/schema';
|
|
|
31
31
|
// from your own server route (see Identity below).
|
|
32
32
|
export const ablo = Ablo({
|
|
33
33
|
schema,
|
|
34
|
-
|
|
34
|
+
apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
|
|
35
35
|
});
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -65,13 +65,13 @@ export function Providers({
|
|
|
65
65
|
|
|
66
66
|
| Prop | Default | Purpose |
|
|
67
67
|
| ----------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
|
|
68
|
-
| `client` | — | **Required.** The `Ablo({ schema,
|
|
68
|
+
| `client` | — | **Required.** The `Ablo({ schema, apiKey })` instance. It carries the schema and connection config. |
|
|
69
69
|
| `userId` | resolved from auth | App participant id for app-owned fields and your `identityRoles`. Not the security boundary. |
|
|
70
70
|
| `fallback` | neutral spinner | Rendered during the *first* bootstrap only. Pass a branded skeleton, `null`, or `'passthrough'`. |
|
|
71
71
|
| `onError` | — | Engine / WebSocket / bootstrap errors. Wire to Sentry / Datadog. |
|
|
72
72
|
|
|
73
73
|
Everything that used to be a provider prop — `schema`, `url`, `apiKey`,
|
|
74
|
-
`teamIds`, `syncGroups
|
|
74
|
+
`teamIds`, `syncGroups`, `persistence`, `bootstrapMode` — now lives on
|
|
75
75
|
the `Ablo({ ... })` client you build before mounting the provider. Where the
|
|
76
76
|
identity comes from, and why the API key never reaches the browser, is the whole
|
|
77
77
|
of [Identity & Sync Groups](./identity.md) — read that if it isn't obvious how
|
package/docs/schema-contract.md
CHANGED
|
@@ -49,6 +49,20 @@ The model key (`weatherReports`) becomes the client namespace
|
|
|
49
49
|
contract. You should not create a parallel string-keyed write path for the same
|
|
50
50
|
data.
|
|
51
51
|
|
|
52
|
+
### Reserved fields
|
|
53
|
+
|
|
54
|
+
The SDK provides these on every row automatically — do **not** declare them in
|
|
55
|
+
your `model(...)` fields:
|
|
56
|
+
|
|
57
|
+
- `id`
|
|
58
|
+
- `createdAt`
|
|
59
|
+
- `updatedAt`
|
|
60
|
+
- `organizationId`
|
|
61
|
+
- `createdBy`
|
|
62
|
+
|
|
63
|
+
Declare only your own fields; the reserved ones are still present on the row and
|
|
64
|
+
readable, you just don't author them.
|
|
65
|
+
|
|
52
66
|
## Reads and writes
|
|
53
67
|
|
|
54
68
|
Use async reads when the row may not be local:
|
|
@@ -88,12 +102,16 @@ the fresh row. Reads stay open; only acting on the row serializes.
|
|
|
88
102
|
|
|
89
103
|
## Storage boundary
|
|
90
104
|
|
|
91
|
-
Every schema model is backed by your own database
|
|
92
|
-
|
|
105
|
+
Every schema model is backed by your own database. There are three start states,
|
|
106
|
+
all covered in [Connect Your Database](./data-sources.md) (the single source of
|
|
107
|
+
truth): the sandbox (`apiKey` only, no database), a direct connection string
|
|
108
|
+
(`databaseUrl` passed to `Ablo(...)`, a live, server-only option), or a signed
|
|
109
|
+
Data Source endpoint where your app keeps the database credential and commits
|
|
110
|
+
each write itself.
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
API key.
|
|
112
|
+
If your database stays canonical behind a Data Source endpoint, do not pass
|
|
113
|
+
`databaseUrl` to `Ablo(...)` — trusted runtimes use `ABLO_API_KEY`. Browser code
|
|
114
|
+
goes through `<AbloProvider>` or a scoped session route, never a raw API key.
|
|
97
115
|
|
|
98
116
|
## Rules of thumb
|
|
99
117
|
|
package/llms-full.txt
CHANGED
|
@@ -39,9 +39,9 @@ Use `snapshot(...)` and `readAt` when an agent write depends on state it already
|
|
|
39
39
|
read. `onStale: 'reject'` prevents lost updates by rejecting if the target
|
|
40
40
|
changed after the snapshot.
|
|
41
41
|
|
|
42
|
-
Claims are live coordination signals, not database locks.
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
Claims are live coordination signals, not database locks. Reads never block on a
|
|
43
|
+
claim — to wait for a row to free up, `claim({ id })` it and the claim queues
|
|
44
|
+
fairly behind the current holder.
|
|
45
45
|
|
|
46
46
|
Agent run bookkeeping is internal. Most users should not create run ledger
|
|
47
47
|
records or scoped access credentials manually.
|
|
@@ -307,7 +307,10 @@ common agent path.
|
|
|
307
307
|
|
|
308
308
|
## Model
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
The read shape depends on whether the client has a local graph:
|
|
311
|
+
|
|
312
|
+
- The stateful `ablo.<model>.retrieve({ id })` returns the BARE row (`T | undefined`), and `list({ where })` returns `T[]`. Read coordination metadata (state watermark, active claims) separately via `ablo.snapshot(...)` and `ablo.<model>.claim.state({ id })`.
|
|
313
|
+
- The stateless HTTP / `.model(name)` `retrieve({ id })` returns a `ModelRead<T>` envelope because it has no local graph to carry the watermark; `list({ where })` still returns `T[]`.
|
|
311
314
|
|
|
312
315
|
```ts
|
|
313
316
|
type ModelRead<T> = {
|
|
@@ -317,32 +320,31 @@ type ModelRead<T> = {
|
|
|
317
320
|
};
|
|
318
321
|
```
|
|
319
322
|
|
|
320
|
-
`stamp` is the state watermark. Pass it to writes as `readAt
|
|
323
|
+
`stamp` is the state watermark. Pass it to writes as `readAt` (from `ablo.snapshot(...)` on the stateful client, or from the envelope on the HTTP client).
|
|
321
324
|
|
|
322
325
|
`claims` lists active work on the target. Reads are allowed while another participant is working. The caller decides whether to return, fail, or wait.
|
|
323
326
|
|
|
324
327
|
## Claimed Behavior
|
|
325
328
|
|
|
326
|
-
Claimed behavior is explicit:
|
|
329
|
+
Claimed behavior is explicit, and there are only two policies:
|
|
327
330
|
|
|
328
|
-
- `ifClaimed: 'return'` returns the current claims with the read.
|
|
331
|
+
- `ifClaimed: 'return'` (the default) returns the row plus the current claims with the read.
|
|
329
332
|
- `ifClaimed: 'fail'` throws `AbloClaimedError`.
|
|
330
|
-
- `ifClaimed: 'wait'` waits for matching claims to clear.
|
|
331
|
-
|
|
332
|
-
Schema clients use the realtime claim stream for waits.
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
Reads never block on a claim. To wait for a row to free up, `claim({ id })` it —
|
|
335
|
+
the claim queues fairly behind the current holder and is granted when the row
|
|
336
|
+
frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
|
|
335
337
|
|
|
336
338
|
```ts
|
|
339
|
+
// Read a claimed row without blocking, surfacing who holds it:
|
|
337
340
|
await api.model('reports').retrieve({
|
|
338
341
|
id: 'report_stockholm',
|
|
339
|
-
ifClaimed: '
|
|
340
|
-
claimedPollInterval: 1_000,
|
|
341
|
-
claimedTimeout: 30_000,
|
|
342
|
+
ifClaimed: 'return',
|
|
342
343
|
});
|
|
343
|
-
```
|
|
344
344
|
|
|
345
|
-
|
|
345
|
+
// Or wait for it to free up by queueing your own claim:
|
|
346
|
+
await api.model('reports').claim({ id: 'report_stockholm' });
|
|
347
|
+
```
|
|
346
348
|
|
|
347
349
|
## Write
|
|
348
350
|
|
package/llms.txt
CHANGED
|
@@ -99,13 +99,13 @@ coordination until the app reports it through Data Source events.
|
|
|
99
99
|
## Claimed Behavior
|
|
100
100
|
|
|
101
101
|
Reads never silently block. Schema reads stay open while a row is claimed.
|
|
102
|
-
Server reads through `ablo.model(name)` can pass `ifClaimed: 'return'`
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
Server reads through `ablo.model(name)` can pass `ifClaimed: 'return'` (the
|
|
103
|
+
default — returns the row plus active claim metadata) or `ifClaimed: 'fail'`
|
|
104
|
+
to throw `AbloClaimedError`.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Use `
|
|
106
|
+
Reads never block on a claim. To wait for a row to free up, `claim({ id })` it —
|
|
107
|
+
the claim queues fairly behind the current holder and is granted when the row
|
|
108
|
+
frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
|
|
109
109
|
|
|
110
110
|
## Guarantees
|
|
111
111
|
|
package/package.json
CHANGED
package/dist/api/index.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal compatibility entrypoint for the stateless hosted protocol client.
|
|
3
|
-
*
|
|
4
|
-
* Use this build for serverless functions, scripts, and backends that want
|
|
5
|
-
* model reads/writes and commits over HTTP without the realtime sync runtime.
|
|
6
|
-
*/
|
|
7
|
-
export { createProtocolClient, createProtocolClient as Ablo, type AbloApi, type AbloApiClientOptions, type AbloApiClaims, type Capability, type CapabilityCreateOptions, type CapabilityParticipantKind, type CapabilityRecord, type CapabilityResource, type CapabilityRevocation, type CapabilityScope, } from '../client/ApiClient.js';
|
|
8
|
-
export type { CommitCreateOptions, CommitOperationInput, CommitReceipt, CommitWait, ClaimCreateOptions, ClaimHandle, ClaimWaitOptions, ClaimedOptions, IfClaimedPolicy, ModelClient, ModelClaim, ModelMutationOptions, ModelReadOptions, ModelRead, ModelTarget, } from '../client/Ablo.js';
|
|
9
|
-
import { createProtocolClient } from '../client/ApiClient.js';
|
|
10
|
-
export default createProtocolClient;
|
package/dist/api/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal compatibility entrypoint for the stateless hosted protocol client.
|
|
3
|
-
*
|
|
4
|
-
* Use this build for serverless functions, scripts, and backends that want
|
|
5
|
-
* model reads/writes and commits over HTTP without the realtime sync runtime.
|
|
6
|
-
*/
|
|
7
|
-
export { createProtocolClient, createProtocolClient as Ablo, } from '../client/ApiClient.js';
|
|
8
|
-
import { createProtocolClient } from '../client/ApiClient.js';
|
|
9
|
-
export default createProtocolClient;
|
package/dist/principal.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Principal constructors — a thin typed façade over the raw
|
|
3
|
-
* `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
|
|
4
|
-
* the discriminated-union tags.
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* import Ablo, { session } from '@abloatai/ablo';
|
|
8
|
-
*
|
|
9
|
-
* const ablo = Ablo({ schema, apiKey });
|
|
10
|
-
* const participant = await ablo.participants.join({
|
|
11
|
-
* type: 'Matter',
|
|
12
|
-
* id: 'deal-1',
|
|
13
|
-
* });
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* Browser-human flows use `session(...)`. Agent-spawn-agent flows use
|
|
17
|
-
* `agent(...)`, but those rarely appear in customer code because the
|
|
18
|
-
* participant layer handles attenuation.
|
|
19
|
-
*
|
|
20
|
-
* These are pure — no I/O, no hidden state. If the shape ever grows a
|
|
21
|
-
* required field (say, a scope hint for the restricted key), the helper
|
|
22
|
-
* is the one place to flag migrations.
|
|
23
|
-
*/
|
|
24
|
-
import type { AgentRef, SessionRef } from './types/streams.js';
|
|
25
|
-
/**
|
|
26
|
-
* Build a `SessionRef` from the identifiers your auth system already
|
|
27
|
-
* holds. Typical inputs: the Better Auth session id, the user id, and
|
|
28
|
-
* the organization the session is scoped to.
|
|
29
|
-
*/
|
|
30
|
-
export declare function session(params: {
|
|
31
|
-
id: string;
|
|
32
|
-
userId: string;
|
|
33
|
-
organizationId: string;
|
|
34
|
-
}): SessionRef;
|
|
35
|
-
/**
|
|
36
|
-
* Build an `AgentRef` from an agent id + the capability token that
|
|
37
|
-
* authenticates it. Rare in application code — the common path is
|
|
38
|
-
* `participant.join(child)` where the parent's token is attenuated
|
|
39
|
-
* automatically.
|
|
40
|
-
*/
|
|
41
|
-
export declare function agent(params: {
|
|
42
|
-
id: string;
|
|
43
|
-
capabilityToken: string;
|
|
44
|
-
}): AgentRef;
|
package/dist/principal.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Principal constructors — a thin typed façade over the raw
|
|
3
|
-
* `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
|
|
4
|
-
* the discriminated-union tags.
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* import Ablo, { session } from '@abloatai/ablo';
|
|
8
|
-
*
|
|
9
|
-
* const ablo = Ablo({ schema, apiKey });
|
|
10
|
-
* const participant = await ablo.participants.join({
|
|
11
|
-
* type: 'Matter',
|
|
12
|
-
* id: 'deal-1',
|
|
13
|
-
* });
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* Browser-human flows use `session(...)`. Agent-spawn-agent flows use
|
|
17
|
-
* `agent(...)`, but those rarely appear in customer code because the
|
|
18
|
-
* participant layer handles attenuation.
|
|
19
|
-
*
|
|
20
|
-
* These are pure — no I/O, no hidden state. If the shape ever grows a
|
|
21
|
-
* required field (say, a scope hint for the restricted key), the helper
|
|
22
|
-
* is the one place to flag migrations.
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Build a `SessionRef` from the identifiers your auth system already
|
|
26
|
-
* holds. Typical inputs: the Better Auth session id, the user id, and
|
|
27
|
-
* the organization the session is scoped to.
|
|
28
|
-
*/
|
|
29
|
-
export function session(params) {
|
|
30
|
-
return {
|
|
31
|
-
kind: 'session',
|
|
32
|
-
id: params.id,
|
|
33
|
-
userId: params.userId,
|
|
34
|
-
organizationId: params.organizationId,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Build an `AgentRef` from an agent id + the capability token that
|
|
39
|
-
* authenticates it. Rare in application code — the common path is
|
|
40
|
-
* `participant.join(child)` where the parent's token is attenuated
|
|
41
|
-
* automatically.
|
|
42
|
-
*/
|
|
43
|
-
export function agent(params) {
|
|
44
|
-
return {
|
|
45
|
-
kind: 'agent',
|
|
46
|
-
id: params.id,
|
|
47
|
-
capabilityToken: params.capabilityToken,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
export interface SyncGroupProviderProps {
|
|
3
|
-
/** The sync-group identifier — e.g., `matter:abc-123`, `deck:xyz`. */
|
|
4
|
-
id: string;
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
}
|
|
7
|
-
export declare function SyncGroupProvider({ id, children }: SyncGroupProviderProps): import("react").JSX.Element;
|
|
8
|
-
/**
|
|
9
|
-
* Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
|
|
10
|
-
* called outside one — sync-group awareness is mandatory by design,
|
|
11
|
-
* so the error points the consumer at the provider instead of
|
|
12
|
-
* returning undefined and letting downstream code silently miss scope.
|
|
13
|
-
*
|
|
14
|
-
* If a component legitimately renders both inside and outside a
|
|
15
|
-
* group, structure the tree so the hook is only called on the
|
|
16
|
-
* inside path (e.g., split into two components). Silent nulls are
|
|
17
|
-
* never the right answer.
|
|
18
|
-
*/
|
|
19
|
-
export declare function useSyncGroup(): string;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useMemo } from 'react';
|
|
4
|
-
import { AbloValidationError } from '../errors.js';
|
|
5
|
-
/**
|
|
6
|
-
* Narrow context for a per-entity sync-group scope. Maps directly onto
|
|
7
|
-
* Liveblocks' `<RoomProvider id="...">`: wrap a subtree, and any hooks
|
|
8
|
-
* inside can read `useSyncGroup()` to discover "which entity am I
|
|
9
|
-
* scoped to?" without threading the ID through props.
|
|
10
|
-
*
|
|
11
|
-
* Typical IDs follow the multiplayer sync-group convention: `matter:<id>`,
|
|
12
|
-
* `deck:<id>`, `project:<id>`. The ID is an opaque string — the
|
|
13
|
-
* provider doesn't parse it.
|
|
14
|
-
*
|
|
15
|
-
* v0.3.0 scope: this is a thin passthrough. Future versions will
|
|
16
|
-
* scope `useQuery` / `useOne` results to the group automatically.
|
|
17
|
-
*/
|
|
18
|
-
const SyncGroupContext = createContext(null);
|
|
19
|
-
export function SyncGroupProvider({ id, children }) {
|
|
20
|
-
// Stabilize the context value so consumers memoized on it don't
|
|
21
|
-
// re-render when the provider re-renders for unrelated reasons.
|
|
22
|
-
const value = useMemo(() => id, [id]);
|
|
23
|
-
return _jsx(SyncGroupContext.Provider, { value: value, children: children });
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
|
|
27
|
-
* called outside one — sync-group awareness is mandatory by design,
|
|
28
|
-
* so the error points the consumer at the provider instead of
|
|
29
|
-
* returning undefined and letting downstream code silently miss scope.
|
|
30
|
-
*
|
|
31
|
-
* If a component legitimately renders both inside and outside a
|
|
32
|
-
* group, structure the tree so the hook is only called on the
|
|
33
|
-
* inside path (e.g., split into two components). Silent nulls are
|
|
34
|
-
* never the right answer.
|
|
35
|
-
*/
|
|
36
|
-
export function useSyncGroup() {
|
|
37
|
-
const id = useContext(SyncGroupContext);
|
|
38
|
-
if (!id) {
|
|
39
|
-
throw new AbloValidationError('useSyncGroup: no <SyncGroupProvider> mounted above this component. ' +
|
|
40
|
-
'Wrap your tree with <SyncGroupProvider id="matter:..."> from ' +
|
|
41
|
-
'@abloatai/ablo/react.', { code: 'no_sync_group_provider' });
|
|
42
|
-
}
|
|
43
|
-
return id;
|
|
44
|
-
}
|
package/dist/react/useClaim.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ResolveClaims } from '../types/global.js';
|
|
2
|
-
/**
|
|
3
|
-
* Named-claim invoker, typed via `ResolveClaims[ClaimName]`.
|
|
4
|
-
*
|
|
5
|
-
* The consumer declares their claim vocabulary in the global:
|
|
6
|
-
*
|
|
7
|
-
* ```ts
|
|
8
|
-
* declare module '@abloatai/ablo' {
|
|
9
|
-
* interface Register {
|
|
10
|
-
* Claims: {
|
|
11
|
-
* editLayer: { slideId: string; layerId: string };
|
|
12
|
-
* generateWithAI: { entityId: string; tool: string };
|
|
13
|
-
* };
|
|
14
|
-
* }
|
|
15
|
-
* }
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* Then `useClaim('editLayer')` returns a function whose sole argument
|
|
19
|
-
* is the `editLayer` claim shape — no runtime checks, purely compile-
|
|
20
|
-
* time narrowing.
|
|
21
|
-
*
|
|
22
|
-
* The SDK doesn't own what happens next: the `beginClaim` function on
|
|
23
|
-
* the React context (supplied via `SyncProvider`) is where the claim
|
|
24
|
-
* claim turns into a network effect. A Node-backed consumer wires it
|
|
25
|
-
* through `SyncAgent.beginClaim`; a browser-backed consumer may
|
|
26
|
-
* broadcast it through their own WebSocket. This hook is pure sugar
|
|
27
|
-
* that adds the typed name + claim narrowing.
|
|
28
|
-
*/
|
|
29
|
-
export declare function useClaim<Name extends keyof ResolveClaims & string>(claimName: Name): (claim: ResolveClaims[Name]) => unknown;
|
package/dist/react/useClaim.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useCallback } from 'react';
|
|
3
|
-
import { useSyncContext } from './context.js';
|
|
4
|
-
import { AbloValidationError } from '../errors.js';
|
|
5
|
-
/**
|
|
6
|
-
* Named-claim invoker, typed via `ResolveClaims[ClaimName]`.
|
|
7
|
-
*
|
|
8
|
-
* The consumer declares their claim vocabulary in the global:
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* declare module '@abloatai/ablo' {
|
|
12
|
-
* interface Register {
|
|
13
|
-
* Claims: {
|
|
14
|
-
* editLayer: { slideId: string; layerId: string };
|
|
15
|
-
* generateWithAI: { entityId: string; tool: string };
|
|
16
|
-
* };
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* Then `useClaim('editLayer')` returns a function whose sole argument
|
|
22
|
-
* is the `editLayer` claim shape — no runtime checks, purely compile-
|
|
23
|
-
* time narrowing.
|
|
24
|
-
*
|
|
25
|
-
* The SDK doesn't own what happens next: the `beginClaim` function on
|
|
26
|
-
* the React context (supplied via `SyncProvider`) is where the claim
|
|
27
|
-
* claim turns into a network effect. A Node-backed consumer wires it
|
|
28
|
-
* through `SyncAgent.beginClaim`; a browser-backed consumer may
|
|
29
|
-
* broadcast it through their own WebSocket. This hook is pure sugar
|
|
30
|
-
* that adds the typed name + claim narrowing.
|
|
31
|
-
*/
|
|
32
|
-
export function useClaim(claimName) {
|
|
33
|
-
const { beginClaim } = useSyncContext();
|
|
34
|
-
return useCallback((claim) => {
|
|
35
|
-
if (!beginClaim) {
|
|
36
|
-
throw new AbloValidationError(`useClaim: no \`beginClaim\` wired into SyncProvider. Pass ` +
|
|
37
|
-
`a \`beginClaim\` prop (typically bound to your transport) ` +
|
|
38
|
-
`to enable claim invocations.`, { code: 'claim_not_wired' });
|
|
39
|
-
}
|
|
40
|
-
return beginClaim(claimName, claim);
|
|
41
|
-
}, [beginClaim, claimName]);
|
|
42
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ResolvePresence } from '../types/global.js';
|
|
2
|
-
/**
|
|
3
|
-
* Read the consumer-supplied presence state with `ResolvePresence`d
|
|
4
|
-
* typing — the shape the consumer declared in
|
|
5
|
-
* `declare module '@abloatai/ablo' { interface Register { Presence: ... } }`.
|
|
6
|
-
*
|
|
7
|
-
* The SDK doesn't own a presence wire format. Consumers plug whatever
|
|
8
|
-
* backs their cursors, status, or activity (a MobX store, a custom
|
|
9
|
-
* WebSocket channel, `SyncAgent` in Node, a Zustand slice) via the
|
|
10
|
-
* `presence` prop on `SyncProvider`. This hook returns it typed.
|
|
11
|
-
*
|
|
12
|
-
* ```ts
|
|
13
|
-
* // apps/your-app/src/ablo-sync.d.ts
|
|
14
|
-
* declare module '@abloatai/ablo' {
|
|
15
|
-
* interface Register {
|
|
16
|
-
* Presence: { cursor: { x: number; y: number } | null; status: 'away' | 'online' };
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
*
|
|
20
|
-
* // consumer's <SyncProvider> wiring
|
|
21
|
-
* <SyncProvider store={store} organizationId={orgId} presence={presenceStore}>
|
|
22
|
-
*
|
|
23
|
-
* // any component
|
|
24
|
-
* const presence = usePresence();
|
|
25
|
-
* presence?.cursor?.x; // fully typed
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* Returns `undefined` when no provider-level presence source is wired —
|
|
29
|
-
* consumers can narrow with a guard or configure a default in their
|
|
30
|
-
* provider.
|
|
31
|
-
*/
|
|
32
|
-
export declare function usePresence(): ResolvePresence | undefined;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useSyncContext } from './context.js';
|
|
3
|
-
/**
|
|
4
|
-
* Read the consumer-supplied presence state with `ResolvePresence`d
|
|
5
|
-
* typing — the shape the consumer declared in
|
|
6
|
-
* `declare module '@abloatai/ablo' { interface Register { Presence: ... } }`.
|
|
7
|
-
*
|
|
8
|
-
* The SDK doesn't own a presence wire format. Consumers plug whatever
|
|
9
|
-
* backs their cursors, status, or activity (a MobX store, a custom
|
|
10
|
-
* WebSocket channel, `SyncAgent` in Node, a Zustand slice) via the
|
|
11
|
-
* `presence` prop on `SyncProvider`. This hook returns it typed.
|
|
12
|
-
*
|
|
13
|
-
* ```ts
|
|
14
|
-
* // apps/your-app/src/ablo-sync.d.ts
|
|
15
|
-
* declare module '@abloatai/ablo' {
|
|
16
|
-
* interface Register {
|
|
17
|
-
* Presence: { cursor: { x: number; y: number } | null; status: 'away' | 'online' };
|
|
18
|
-
* }
|
|
19
|
-
* }
|
|
20
|
-
*
|
|
21
|
-
* // consumer's <SyncProvider> wiring
|
|
22
|
-
* <SyncProvider store={store} organizationId={orgId} presence={presenceStore}>
|
|
23
|
-
*
|
|
24
|
-
* // any component
|
|
25
|
-
* const presence = usePresence();
|
|
26
|
-
* presence?.cursor?.x; // fully typed
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* Returns `undefined` when no provider-level presence source is wired —
|
|
30
|
-
* consumers can narrow with a guard or configure a default in their
|
|
31
|
-
* provider.
|
|
32
|
-
*/
|
|
33
|
-
export function usePresence() {
|
|
34
|
-
const ctx = useSyncContext();
|
|
35
|
-
// The runtime value is whatever the consumer passed to `SyncProvider`.
|
|
36
|
-
// The type assertion reflects the consumer's declared global, which
|
|
37
|
-
// the hook can't verify at runtime — but the consumer controls both
|
|
38
|
-
// ends (the registration and the provider prop) so this is a
|
|
39
|
-
// single-source-of-truth contract, not blind trust.
|
|
40
|
-
return ctx.presence;
|
|
41
|
-
}
|