@abloatai/ablo 0.8.0 → 0.9.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 +40 -1
- package/README.md +32 -27
- package/dist/BaseSyncedStore.d.ts +73 -0
- package/dist/BaseSyncedStore.js +172 -2
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/agent/session.js +3 -3
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +4 -42
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +160 -42
- package/dist/client/Ablo.js +145 -75
- package/dist/client/ApiClient.d.ts +20 -4
- package/dist/client/ApiClient.js +166 -28
- package/dist/client/auth.d.ts +14 -5
- package/dist/client/auth.js +60 -7
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +130 -66
- package/dist/client/createModelProxy.js +152 -49
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +49 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +3 -3
- package/dist/client/registerDataSource.js +11 -9
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +70 -1
- package/dist/errorCodes.js +108 -9
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +72 -22
- package/dist/index.d.ts +17 -8
- package/dist/index.js +15 -6
- package/dist/keys/index.d.ts +16 -1
- package/dist/keys/index.js +26 -6
- package/dist/mutators/UndoManager.d.ts +86 -50
- package/dist/mutators/UndoManager.js +129 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +3 -6
- package/dist/react/AbloProvider.d.ts +23 -126
- package/dist/react/AbloProvider.js +62 -199
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/schema/ddl.d.ts +26 -3
- package/dist/schema/ddl.js +152 -4
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.js +12 -0
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +4 -0
- package/dist/schema/serialize.js +4 -0
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +59 -0
- package/dist/source/adapter.js +19 -0
- package/dist/source/adapters/drizzle.d.ts +34 -0
- package/dist/source/adapters/drizzle.js +147 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +199 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +143 -0
- package/dist/source/contract.js +98 -0
- package/dist/source/index.d.ts +61 -10
- package/dist/source/index.js +98 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +10 -15
- package/dist/sync/ConnectionManager.d.ts +55 -1
- package/dist/sync/ConnectionManager.js +155 -16
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +238 -39
- package/dist/sync/NetworkProbe.d.ts +58 -24
- package/dist/sync/NetworkProbe.js +118 -42
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +70 -36
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api.md +47 -44
- package/docs/cli.md +44 -44
- package/docs/client-behavior.md +30 -30
- package/docs/coordination.md +33 -36
- package/docs/data-sources.md +35 -15
- package/docs/examples/agent-human.md +45 -43
- package/docs/examples/ai-sdk-tool.md +20 -16
- package/docs/examples/existing-python-backend.md +16 -12
- package/docs/examples/nextjs.md +14 -12
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/examples/server-agent.md +24 -21
- package/docs/guarantees.md +15 -13
- package/docs/index.md +1 -1
- package/docs/integration-guide.md +30 -30
- package/docs/interaction-model.md +19 -23
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +2 -2
- package/docs/mcp.md +6 -6
- package/docs/quickstart.md +41 -31
- package/docs/react.md +13 -9
- package/docs/schema-contract.md +12 -10
- package/docs/the-loop.md +21 -0
- package/examples/data-source/README.md +4 -5
- package/examples/data-source/customer-server.ts +27 -25
- package/llms.txt +28 -5
- package/package.json +43 -3
package/dist/client/auth.d.ts
CHANGED
|
@@ -43,13 +43,22 @@ export declare function readProcessEnv(): Record<string, string | undefined>;
|
|
|
43
43
|
export declare function resolveApiKey(input: AuthResolveInput): string | ApiKeySetter | null;
|
|
44
44
|
export declare function resolveAuthToken(input: AuthResolveInput): string | null;
|
|
45
45
|
/**
|
|
46
|
-
* Resolve the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
46
|
+
* Resolve the direct-URL connector's Postgres connection string.
|
|
47
|
+
*
|
|
48
|
+
* The default Data Source path should not call this: the customer keeps
|
|
49
|
+
* `DATABASE_URL` in their app and exposes `dataSource(...)`. This helper exists
|
|
50
|
+
* only for the opt-in direct connector where Ablo registers a dedicated tenant
|
|
51
|
+
* database. Returns null for Ablo-managed storage.
|
|
50
52
|
*/
|
|
51
53
|
export declare function resolveDatabaseUrl(input: AuthResolveInput): string | null;
|
|
52
|
-
export declare const
|
|
54
|
+
export declare const ABLO_HOSTED_API_DOMAIN = "api.abloatai.com";
|
|
55
|
+
export declare const ABLO_HOSTED_HTTP_BASE_URL = "https://api.abloatai.com";
|
|
56
|
+
export declare const ABLO_DEFAULT_BASE_URL = "wss://api.abloatai.com";
|
|
57
|
+
/**
|
|
58
|
+
* Normalize old hosted aliases to the public API domain. Self-hosted/custom
|
|
59
|
+
* URLs pass through unchanged; only first-party legacy hosts are rewritten.
|
|
60
|
+
*/
|
|
61
|
+
export declare function normalizeAbloHostedBaseUrl(rawUrl: string): string;
|
|
53
62
|
export declare function resolveBaseURL(input: AuthResolveInput): string;
|
|
54
63
|
/**
|
|
55
64
|
* Browser guard — apiKey is server-side-only by default. Same check
|
package/dist/client/auth.js
CHANGED
|
@@ -28,17 +28,58 @@ export function resolveAuthToken(input) {
|
|
|
28
28
|
return input.options.authToken ?? null;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Resolve the
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Resolve the direct-URL connector's Postgres connection string.
|
|
32
|
+
*
|
|
33
|
+
* The default Data Source path should not call this: the customer keeps
|
|
34
|
+
* `DATABASE_URL` in their app and exposes `dataSource(...)`. This helper exists
|
|
35
|
+
* only for the opt-in direct connector where Ablo registers a dedicated tenant
|
|
36
|
+
* database. Returns null for Ablo-managed storage.
|
|
35
37
|
*/
|
|
36
38
|
export function resolveDatabaseUrl(input) {
|
|
37
39
|
return input.options.databaseUrl ?? input.env.DATABASE_URL ?? null;
|
|
38
40
|
}
|
|
39
|
-
export const
|
|
41
|
+
export const ABLO_HOSTED_API_DOMAIN = 'api.abloatai.com';
|
|
42
|
+
export const ABLO_HOSTED_HTTP_BASE_URL = `https://${ABLO_HOSTED_API_DOMAIN}`;
|
|
43
|
+
export const ABLO_DEFAULT_BASE_URL = `wss://${ABLO_HOSTED_API_DOMAIN}`;
|
|
44
|
+
const LEGACY_HOSTED_API_HOSTS = new Set([
|
|
45
|
+
'mesh.ablo.finance',
|
|
46
|
+
'mesh-staging.ablo.finance',
|
|
47
|
+
'api.ablo.finance',
|
|
48
|
+
'sync-staging.ablo.finance',
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Normalize old hosted aliases to the public API domain. Self-hosted/custom
|
|
52
|
+
* URLs pass through unchanged; only first-party legacy hosts are rewritten.
|
|
53
|
+
*/
|
|
54
|
+
export function normalizeAbloHostedBaseUrl(rawUrl) {
|
|
55
|
+
const trimmed = rawUrl.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
return trimmed;
|
|
58
|
+
// A scheme-less value (e.g. `api-staging.abloatai.com`) is a RELATIVE URL:
|
|
59
|
+
// `new URL()` throws on it, and downstream `fetch` then resolves it against
|
|
60
|
+
// the current page — producing `https://<app-host>/<route>/api-staging…/api/
|
|
61
|
+
// auth/identity`, a 404 from the app's own origin. Prepend a scheme so the
|
|
62
|
+
// base is absolute. `https` mirrors `ABLO_HOSTED_HTTP_BASE_URL`; the socket
|
|
63
|
+
// layer derives `wss` from it. An existing scheme (ws/wss/http/https) is
|
|
64
|
+
// preserved untouched.
|
|
65
|
+
const schemed = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(schemed);
|
|
68
|
+
if (!LEGACY_HOSTED_API_HOSTS.has(url.hostname))
|
|
69
|
+
return schemed.replace(/\/+$/, '');
|
|
70
|
+
url.hostname = ABLO_HOSTED_API_DOMAIN;
|
|
71
|
+
if (url.protocol === 'http:')
|
|
72
|
+
url.protocol = 'https:';
|
|
73
|
+
if (url.protocol === 'ws:')
|
|
74
|
+
url.protocol = 'wss:';
|
|
75
|
+
return url.toString().replace(/\/+$/, '');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return schemed;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
40
81
|
export function resolveBaseURL(input) {
|
|
41
|
-
return input.options.baseURL ?? ABLO_DEFAULT_BASE_URL;
|
|
82
|
+
return normalizeAbloHostedBaseUrl(input.options.baseURL ?? ABLO_DEFAULT_BASE_URL);
|
|
42
83
|
}
|
|
43
84
|
/**
|
|
44
85
|
* Browser guard — apiKey is server-side-only by default. Same check
|
|
@@ -96,5 +137,17 @@ export async function resolveApiKeyValue(apiKey) {
|
|
|
96
137
|
* preserves the protocol family (ws → http, wss → https).
|
|
97
138
|
*/
|
|
98
139
|
export function resolveBootstrapBaseUrl(input) {
|
|
99
|
-
|
|
140
|
+
if (input.bootstrapBaseUrl) {
|
|
141
|
+
// Coerce ws/wss → http/https on the override path too. This base URL is
|
|
142
|
+
// used for HTTP fetches (identity resolve, apiKey exchange, bootstrap) and
|
|
143
|
+
// the browser `fetch` rejects ws/wss schemes outright ("URL scheme \"wss\"
|
|
144
|
+
// is not supported"). apps/web derives this override as `${baseUrl}/api`
|
|
145
|
+
// where `baseUrl` may carry a WebSocket scheme, so the override can
|
|
146
|
+
// legitimately arrive as `wss://…` — normalize it here rather than
|
|
147
|
+
// faceplanting at fetch time. The derive branch below already does this;
|
|
148
|
+
// the override branch silently skipped it.
|
|
149
|
+
return normalizeAbloHostedBaseUrl(input.bootstrapBaseUrl).replace(/^ws/, 'http');
|
|
150
|
+
}
|
|
151
|
+
const url = normalizeAbloHostedBaseUrl(input.url);
|
|
152
|
+
return `${url.replace(/^ws/, 'http')}/api`;
|
|
100
153
|
}
|
|
@@ -16,6 +16,7 @@ import { ObjectPool } from '../ObjectPool.js';
|
|
|
16
16
|
import { SyncClient } from '../SyncClient.js';
|
|
17
17
|
import { HydrationCoordinator } from '../sync/HydrationCoordinator.js';
|
|
18
18
|
import { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
19
|
+
import type { AuthCredentialSource } from '../auth/credentialSource.js';
|
|
19
20
|
import type { Schema, SchemaRecord } from '../schema/schema.js';
|
|
20
21
|
import { type AbloPersistence } from './persistence.js';
|
|
21
22
|
export interface InternalComponentsInput<S extends SchemaRecord> {
|
|
@@ -31,6 +32,7 @@ export interface InternalComponentsInput<S extends SchemaRecord> {
|
|
|
31
32
|
readonly offline?: boolean;
|
|
32
33
|
readonly inMemory?: boolean;
|
|
33
34
|
};
|
|
35
|
+
readonly auth?: AuthCredentialSource;
|
|
34
36
|
}
|
|
35
37
|
export interface InternalComponents {
|
|
36
38
|
readonly modelRegistry: ModelRegistry;
|
|
@@ -19,7 +19,7 @@ import { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
|
19
19
|
import { resolveBootstrapBaseUrl } from './auth.js';
|
|
20
20
|
import { shouldUseInMemoryPersistence } from './persistence.js';
|
|
21
21
|
export function createInternalComponents(input) {
|
|
22
|
-
const { schema, url, options } = input;
|
|
22
|
+
const { schema, url, options, auth } = input;
|
|
23
23
|
// The registry is created here but model registration happens in
|
|
24
24
|
// the caller (Ablo.ts owns `registerModelsFromSchema` since the
|
|
25
25
|
// schema-to-class translation depends on private helpers there).
|
|
@@ -37,6 +37,7 @@ export function createInternalComponents(input) {
|
|
|
37
37
|
baseUrl: bootstrapBaseUrl,
|
|
38
38
|
syncGroups: options.syncGroups,
|
|
39
39
|
instantModels: deriveInstantModels(schema),
|
|
40
|
+
getAuthToken: auth?.getAuthToken,
|
|
40
41
|
});
|
|
41
42
|
const database = new Database(modelRegistry, bootstrapHelper, {
|
|
42
43
|
// Point-solution default: no browser-local durable store unless the
|
|
@@ -55,7 +56,13 @@ export function createInternalComponents(input) {
|
|
|
55
56
|
registry: modelRegistry,
|
|
56
57
|
schema,
|
|
57
58
|
baseUrl: bootstrapBaseUrl,
|
|
59
|
+
getAuthToken: auth?.getAuthToken,
|
|
58
60
|
});
|
|
61
|
+
// Drop the lazy-lane hydration ledger on reconnect. While connected, the
|
|
62
|
+
// WebSocket delta stream keeps hydrated rows fresh so repeat reads serve
|
|
63
|
+
// pure-local with no network; after a drop, deltas may have been missed, so
|
|
64
|
+
// the next read of each query must re-confirm with the server once.
|
|
65
|
+
syncClient.on('sync:reconnecting', () => hydration.invalidate());
|
|
59
66
|
return {
|
|
60
67
|
modelRegistry,
|
|
61
68
|
objectPool,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* exposes the async server reads `retrieve` / `list`, the synchronous
|
|
11
11
|
* local-graph snapshots `get` / `getAll` / `getCount`, the writes
|
|
12
12
|
* `create` / `update` / `delete`, the coordination namespace `claim`
|
|
13
|
-
* (`claim(id
|
|
13
|
+
* (`claim({ id })` plus `claim.state` / `claim.queue` / `claim.release` /
|
|
14
14
|
* `claim.reorder`), and `onChange`. The factory returns a plain object; the
|
|
15
15
|
* client assembles the `ablo.<model>` lookup table from these.
|
|
16
16
|
*/
|
|
@@ -21,7 +21,7 @@ import type { SyncClient } from '../SyncClient.js';
|
|
|
21
21
|
import type { HydrationCoordinator } from '../sync/HydrationCoordinator.js';
|
|
22
22
|
import type { LoadWhere } from '../query/types.js';
|
|
23
23
|
import { ModelScope } from '../types/index.js';
|
|
24
|
-
import type { Duration, Intent, IntentWaitOptions, Snapshot } from '../types/streams.js';
|
|
24
|
+
import type { Duration, Intent, IntentWaitOptions, Snapshot, TargetRange } from '../types/streams.js';
|
|
25
25
|
export interface ModelClientMeta {
|
|
26
26
|
readonly key: string;
|
|
27
27
|
readonly typename: string;
|
|
@@ -70,7 +70,7 @@ export interface ModelLoadOptions<T> {
|
|
|
70
70
|
*/
|
|
71
71
|
expand?: readonly string[];
|
|
72
72
|
}
|
|
73
|
-
/** Options for the single-row async server read `retrieve(id)`. A subset of
|
|
73
|
+
/** Options for the single-row async server read `retrieve({ id })`. A subset of
|
|
74
74
|
* {@link ModelLoadOptions} — `where`/`limit`/`orderBy` are fixed by the id. */
|
|
75
75
|
export type ModelRetrieveOptions = Pick<ModelLoadOptions<unknown>, 'type' | 'expand'>;
|
|
76
76
|
export interface IntentLeaseHandle {
|
|
@@ -84,6 +84,9 @@ export interface ModelCollaboration<T> {
|
|
|
84
84
|
model: string;
|
|
85
85
|
id: string;
|
|
86
86
|
field?: string;
|
|
87
|
+
path?: string;
|
|
88
|
+
range?: TargetRange;
|
|
89
|
+
meta?: Record<string, unknown>;
|
|
87
90
|
};
|
|
88
91
|
action: string;
|
|
89
92
|
ttl?: Duration;
|
|
@@ -139,12 +142,19 @@ export interface ModelCollaboration<T> {
|
|
|
139
142
|
*/
|
|
140
143
|
readonly selfParticipantId: string;
|
|
141
144
|
}
|
|
142
|
-
|
|
143
|
-
export interface ClaimOptions {
|
|
145
|
+
export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
144
146
|
/** Phase shown to observers while held. Defaults to `'editing'`. */
|
|
145
147
|
action?: string;
|
|
148
|
+
/** Peer-visible explanation of the work being performed. */
|
|
149
|
+
description?: string;
|
|
146
150
|
/** Field-level target, for fine-grained claimed-state badges. */
|
|
147
151
|
field?: string;
|
|
152
|
+
/** Optional path for document/file-like targets. */
|
|
153
|
+
path?: string;
|
|
154
|
+
/** Optional range for document/file-like targets. */
|
|
155
|
+
range?: TargetRange;
|
|
156
|
+
/** App-defined structured metadata. */
|
|
157
|
+
meta?: Record<string, unknown>;
|
|
148
158
|
/** Crash-cleanup TTL — the claim auto-releases if the holder dies. */
|
|
149
159
|
ttl?: Duration;
|
|
150
160
|
/**
|
|
@@ -164,73 +174,120 @@ export interface ClaimOptions {
|
|
|
164
174
|
*/
|
|
165
175
|
maxQueueDepth?: number;
|
|
166
176
|
}
|
|
177
|
+
/** Options for `claim({ id, ... })`. */
|
|
178
|
+
export interface ClaimParams<T = Record<string, unknown>> extends ClaimTargetOptions<T> {
|
|
179
|
+
readonly id: string;
|
|
180
|
+
}
|
|
181
|
+
export interface ClaimLookupParams<T = Record<string, unknown>> {
|
|
182
|
+
readonly id: string;
|
|
183
|
+
readonly field?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface ClaimReorderParams<T = Record<string, unknown>> extends ClaimLookupParams<T> {
|
|
186
|
+
readonly order: readonly Intent[];
|
|
187
|
+
}
|
|
167
188
|
/**
|
|
168
|
-
* A
|
|
189
|
+
* A claim handle: the held entity data plus an explicit release hook, so
|
|
169
190
|
*
|
|
170
191
|
* ```ts
|
|
171
|
-
* await ablo.weatherReports.claim(
|
|
172
|
-
*
|
|
192
|
+
* const claim = await ablo.weatherReports.claim({
|
|
193
|
+
* id: 'report_stockholm',
|
|
194
|
+
* action: 'forecasting',
|
|
195
|
+
* description: 'Fetching current weather before writing the forecast.',
|
|
173
196
|
* });
|
|
197
|
+
* try {
|
|
198
|
+
* await ablo.weatherReports.update({
|
|
199
|
+
* id: claim.target.id,
|
|
200
|
+
* data: { status: 'ready' },
|
|
201
|
+
* claim,
|
|
202
|
+
* });
|
|
203
|
+
* } finally {
|
|
204
|
+
* await claim.release();
|
|
205
|
+
* }
|
|
174
206
|
* ```
|
|
175
207
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
208
|
+
* `data` is a snapshot taken after the lease is held. Write through the flat
|
|
209
|
+
* `ablo.<model>.update({ id, data, claim })` verb — the handle carries the
|
|
210
|
+
* lease id and snapshot watermark for attribution + stale protection.
|
|
179
211
|
*/
|
|
180
|
-
export
|
|
212
|
+
export interface ClaimHandle<T = Record<string, unknown>> extends AsyncDisposable {
|
|
213
|
+
readonly object: 'claim';
|
|
214
|
+
readonly claimId: string;
|
|
215
|
+
readonly target: {
|
|
216
|
+
readonly model: string;
|
|
217
|
+
readonly id: string;
|
|
218
|
+
readonly field?: string;
|
|
219
|
+
readonly path?: string;
|
|
220
|
+
readonly range?: TargetRange;
|
|
221
|
+
readonly meta?: Record<string, unknown>;
|
|
222
|
+
};
|
|
223
|
+
readonly action: string;
|
|
224
|
+
readonly description?: string;
|
|
225
|
+
readonly data: T;
|
|
226
|
+
release(): Promise<void>;
|
|
227
|
+
revoke(): void;
|
|
228
|
+
}
|
|
229
|
+
export type ClaimOptions<T = Record<string, unknown>> = ClaimTargetOptions<T>;
|
|
181
230
|
/**
|
|
182
231
|
* The coordination surface for a model, exposed as a callable namespace.
|
|
183
232
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
233
|
+
* Most callers do not need this namespace directly. Put `claim: { ... }` on a
|
|
234
|
+
* write and the SDK acquires/releases around that one mutation:
|
|
235
|
+
*
|
|
236
|
+
* ```ts
|
|
237
|
+
* await ablo.tasks.update({
|
|
238
|
+
* id,
|
|
239
|
+
* data: { title },
|
|
240
|
+
* claim: {
|
|
241
|
+
* field: 'title',
|
|
242
|
+
* action: 'renaming',
|
|
243
|
+
* description: 'Renaming the task to match the project brief.',
|
|
244
|
+
* },
|
|
245
|
+
* });
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* Use `claim({ id, ... })` when a tool spans multiple writes and needs one
|
|
249
|
+
* handle. `state`, `queue`, and `reorder` are coordination reads/scheduler
|
|
250
|
+
* controls for UI and operators.
|
|
190
251
|
*/
|
|
191
252
|
export interface ClaimApi<T> {
|
|
192
|
-
/** Take a claim and get
|
|
193
|
-
(
|
|
194
|
-
/**
|
|
195
|
-
* Take a claim, run `work` with the held row, release when it settles. The
|
|
196
|
-
* preferred form for ordinary held work.
|
|
197
|
-
*/
|
|
198
|
-
<R>(id: string, work: (row: ClaimedRow<T>) => Promise<R> | R, options?: ClaimOptions): Promise<R>;
|
|
253
|
+
/** Take a claim and get an explicit held-work handle back. */
|
|
254
|
+
(params: ClaimParams<T>): Promise<ClaimHandle<T>>;
|
|
199
255
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* blocks.
|
|
256
|
+
* Current holder for a row, or `null` when free. Use this for UI badges and
|
|
257
|
+
* preflight checks, not for the normal write path.
|
|
203
258
|
*/
|
|
204
|
-
state(
|
|
259
|
+
state(params: ClaimLookupParams<T>): Intent | null;
|
|
205
260
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* returns a Stripe-style list envelope, FIFO order, empty when no one waits.
|
|
209
|
-
*
|
|
210
|
-
* ```ts
|
|
211
|
-
* const { data } = ablo.decks.claim.queue('deck_1');
|
|
212
|
-
* // → [{ heldBy: 'agent:summarizer', action: 'editing', position: 0 }, …]
|
|
213
|
-
* ```
|
|
261
|
+
* FIFO wait line behind the current holder. Advanced: useful for operator
|
|
262
|
+
* UIs and schedulers.
|
|
214
263
|
*/
|
|
215
|
-
queue(
|
|
264
|
+
queue(params: ClaimLookupParams<T>): {
|
|
216
265
|
readonly object: 'list';
|
|
217
266
|
readonly data: readonly Intent[];
|
|
218
267
|
};
|
|
219
268
|
/**
|
|
220
|
-
* Re-rank the wait
|
|
221
|
-
* order. Pass the `Intent[]` from `claim.queue(id).data`, reordered. A
|
|
222
|
-
* privileged operation: the server gates it (the caller needs the
|
|
223
|
-
* `intent.reorder` capability), so it's fire-and-forget — the new order
|
|
224
|
-
* arrives reactively through `claim.queue(id)`.
|
|
225
|
-
*
|
|
226
|
-
* ```ts
|
|
227
|
-
* const { data } = ablo.decks.claim.queue('deck_1');
|
|
228
|
-
* ablo.decks.claim.reorder('deck_1', [data[2], data[0], data[1]]); // promote #2
|
|
229
|
-
* ```
|
|
269
|
+
* Re-rank the wait line. Advanced and permission-gated.
|
|
230
270
|
*/
|
|
231
|
-
reorder(
|
|
232
|
-
/** Release a claim
|
|
233
|
-
release(
|
|
271
|
+
reorder(params: ClaimReorderParams<T>): void;
|
|
272
|
+
/** Release a manual claim handle early. Single-write claims auto-release. */
|
|
273
|
+
release(params: ClaimLookupParams<T> | ClaimHandle<T>): Promise<void>;
|
|
274
|
+
}
|
|
275
|
+
export interface ModelRetrieveParams extends ModelRetrieveOptions {
|
|
276
|
+
readonly id: string;
|
|
277
|
+
}
|
|
278
|
+
export interface ModelCreateParams<T, CreateInput> extends MutationOptions {
|
|
279
|
+
readonly data: CreateInput;
|
|
280
|
+
readonly id?: string | null;
|
|
281
|
+
readonly claim?: ClaimHandle<T> | ClaimTargetOptions<T> | null;
|
|
282
|
+
}
|
|
283
|
+
export interface ModelUpdateParams<T> extends MutationOptions {
|
|
284
|
+
readonly id: string;
|
|
285
|
+
readonly data: Partial<T>;
|
|
286
|
+
readonly claim?: ClaimHandle<T> | ClaimTargetOptions<T> | null;
|
|
287
|
+
}
|
|
288
|
+
export interface ModelDeleteParams<T> extends MutationOptions {
|
|
289
|
+
readonly id: string;
|
|
290
|
+
readonly claim?: ClaimHandle<T> | ClaimTargetOptions<T> | null;
|
|
234
291
|
}
|
|
235
292
|
export interface ModelOperations<T, CreateInput> {
|
|
236
293
|
/**
|
|
@@ -244,9 +301,9 @@ export interface ModelOperations<T, CreateInput> {
|
|
|
244
301
|
* synchronous read of an already-warm graph (a React selector) use
|
|
245
302
|
* `get(id)`.
|
|
246
303
|
*
|
|
247
|
-
* Mirrors `stripe.customers.retrieve(id)` — network-backed.
|
|
304
|
+
* Mirrors `stripe.customers.retrieve({ id })` — network-backed.
|
|
248
305
|
*/
|
|
249
|
-
retrieve(
|
|
306
|
+
retrieve(params: ModelRetrieveParams): Promise<T | undefined>;
|
|
250
307
|
/**
|
|
251
308
|
* List entities matching a filter from the **server** — async. Same 3-tier
|
|
252
309
|
* lookup + graph hydration as `retrieve`; single-flight deduped. Returns the
|
|
@@ -276,29 +333,36 @@ export interface ModelOperations<T, CreateInput> {
|
|
|
276
333
|
* the mutation is queued locally, not when the server confirms.
|
|
277
334
|
* Server rejection rolls back automatically; watch `sync.syncStatus`.
|
|
278
335
|
*/
|
|
279
|
-
create(
|
|
336
|
+
create(params: ModelCreateParams<T, CreateInput>): Promise<T>;
|
|
280
337
|
/** Update an entity by id — optimistic, offline-first (see `create`). */
|
|
281
|
-
update(
|
|
338
|
+
update(params: ModelUpdateParams<T>): Promise<T>;
|
|
282
339
|
/** Delete an entity by id — optimistic, offline-first (see `create`). */
|
|
283
|
-
delete(
|
|
340
|
+
delete(params: ModelDeleteParams<T>): Promise<void>;
|
|
284
341
|
/**
|
|
285
342
|
* Claim a row so other writers wait or are rejected until you're done, and
|
|
286
343
|
* inspect or manage that coordination through the same namespace. Call it to
|
|
287
|
-
* take a claim
|
|
288
|
-
* throws); reach for its members to observe and steer the wait line:
|
|
344
|
+
* take a claim handle; reach for its members to observe and steer the wait line:
|
|
289
345
|
*
|
|
290
|
-
* - `claim.state(id)` — who holds the row now, or `null` when free
|
|
291
|
-
* - `claim.queue(id)` — who's lined up behind the holder
|
|
292
|
-
* - `claim.release(id)` — drop a claim early (usually implicit on scope exit)
|
|
293
|
-
* - `claim.reorder(id, order)` — re-rank the wait line
|
|
346
|
+
* - `claim.state({ id })` — who holds the row now, or `null` when free
|
|
347
|
+
* - `claim.queue({ id })` — who's lined up behind the holder
|
|
348
|
+
* - `claim.release({ id })` — drop a claim early (usually implicit on scope exit)
|
|
349
|
+
* - `claim.reorder({ id, order })` — re-rank the wait line
|
|
294
350
|
*
|
|
295
351
|
* ```ts
|
|
296
|
-
* await ablo.weatherReports.claim(
|
|
297
|
-
*
|
|
298
|
-
*
|
|
352
|
+
* const claim = await ablo.weatherReports.claim({
|
|
353
|
+
* id: 'report_stockholm',
|
|
354
|
+
* action: 'forecasting',
|
|
355
|
+
* description: 'Fetching fresh weather before updating the report.',
|
|
356
|
+
* });
|
|
357
|
+
* const weather = await getWeather(claim.data.location);
|
|
358
|
+
* await ablo.weatherReports.update({
|
|
359
|
+
* id: claim.target.id,
|
|
360
|
+
* data: { forecast: weather },
|
|
361
|
+
* claim,
|
|
299
362
|
* });
|
|
363
|
+
* await claim.release();
|
|
300
364
|
*
|
|
301
|
-
* const holder = ablo.weatherReports.claim.state('report_stockholm');
|
|
365
|
+
* const holder = ablo.weatherReports.claim.state({ id: 'report_stockholm' });
|
|
302
366
|
* ```
|
|
303
367
|
*/
|
|
304
368
|
claim: ClaimApi<T>;
|