@abloatai/ablo 0.5.1 → 0.7.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 +61 -0
- package/README.md +248 -124
- package/dist/BaseSyncedStore.d.ts +3 -3
- package/dist/BaseSyncedStore.js +3 -3
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.js +1 -1
- package/dist/client/Ablo.d.ts +91 -93
- package/dist/client/Ablo.js +122 -60
- package/dist/client/ApiClient.d.ts +14 -14
- package/dist/client/ApiClient.js +81 -55
- package/dist/client/createInternalComponents.d.ts +2 -3
- package/dist/client/createInternalComponents.js +2 -3
- package/dist/client/createModelProxy.d.ts +116 -90
- package/dist/client/createModelProxy.js +128 -128
- package/dist/client/index.d.ts +6 -7
- package/dist/client/index.js +4 -5
- package/dist/client/validateAbloOptions.js +5 -5
- package/dist/coordination/index.d.ts +6 -0
- package/dist/coordination/index.js +6 -0
- package/dist/coordination/schema.d.ts +329 -0
- package/dist/coordination/schema.js +209 -0
- package/dist/core/QueryView.d.ts +4 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +7 -0
- package/dist/core/query-utils.d.ts +7 -10
- package/dist/core/query-utils.js +2 -3
- package/dist/errorCodes.d.ts +264 -0
- package/dist/errorCodes.js +251 -0
- package/dist/errors.d.ts +59 -14
- package/dist/errors.js +73 -12
- package/dist/index.d.ts +11 -9
- package/dist/index.js +8 -12
- package/dist/interfaces/index.d.ts +2 -10
- package/dist/mutators/Transaction.d.ts +2 -2
- package/dist/mutators/Transaction.js +2 -2
- package/dist/mutators/mutateActions.d.ts +44 -0
- package/dist/{react/useMutate.js → mutators/mutateActions.js} +11 -28
- package/dist/mutators/readerActions.d.ts +32 -0
- package/dist/{react/useReader.js → mutators/readerActions.js} +2 -18
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/types.d.ts +31 -0
- package/dist/policy/types.js +15 -0
- package/dist/query/types.d.ts +1 -1
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +14 -6
- package/dist/react/context.d.ts +4 -4
- package/dist/react/index.d.ts +4 -5
- package/dist/react/index.js +3 -7
- package/dist/react/useAblo.d.ts +14 -14
- package/dist/react/useAblo.js +26 -26
- package/dist/react/useIntent.d.ts +2 -2
- package/dist/react/useIntent.js +2 -2
- package/dist/react/useMutators.d.ts +1 -1
- package/dist/react/usePresence.d.ts +3 -3
- package/dist/react/usePresence.js +4 -4
- package/dist/react/useUndoScope.d.ts +1 -1
- package/dist/schema/ddl.d.ts +62 -0
- package/dist/schema/ddl.js +317 -0
- package/dist/schema/diff.d.ts +167 -0
- package/dist/schema/diff.js +280 -0
- package/dist/schema/field.d.ts +16 -19
- package/dist/schema/field.js +30 -17
- package/dist/schema/generate.d.ts +19 -0
- package/dist/schema/generate.js +87 -0
- package/dist/schema/index.d.ts +9 -3
- package/dist/schema/index.js +14 -2
- package/dist/schema/model.d.ts +87 -25
- package/dist/schema/model.js +33 -3
- package/dist/schema/relation.d.ts +17 -0
- package/dist/schema/roles.d.ts +148 -0
- package/dist/schema/roles.js +149 -0
- package/dist/schema/schema.d.ts +10 -69
- package/dist/schema/schema.js +58 -24
- package/dist/schema/select.d.ts +25 -0
- package/dist/schema/select.js +55 -0
- package/dist/schema/serialize.d.ts +96 -0
- package/dist/schema/serialize.js +231 -0
- package/dist/schema/sugar.d.ts +20 -3
- package/dist/schema/sugar.js +5 -1
- package/dist/schema/tenancy.d.ts +66 -0
- package/dist/schema/tenancy.js +58 -0
- package/dist/sync/HydrationCoordinator.d.ts +2 -0
- package/dist/sync/HydrationCoordinator.js +23 -17
- package/dist/sync/SyncWebSocket.d.ts +17 -0
- package/dist/sync/SyncWebSocket.js +46 -1
- package/dist/sync/awaitIntentGrant.d.ts +26 -0
- package/dist/sync/awaitIntentGrant.js +60 -0
- package/dist/sync/createIntentStream.d.ts +2 -1
- package/dist/sync/createIntentStream.js +89 -5
- package/dist/sync/createPresenceStream.js +1 -1
- package/dist/sync/participants.d.ts +2 -2
- package/dist/sync/participants.js +9 -18
- package/dist/types/global.d.ts +43 -52
- package/dist/types/global.js +16 -18
- package/dist/types/streams.d.ts +90 -42
- package/docs/api-keys.md +44 -0
- package/docs/api.md +72 -173
- package/docs/audit.md +5 -5
- package/docs/cli.md +212 -0
- package/docs/client-behavior.md +42 -43
- package/docs/coordination.md +343 -0
- package/docs/data-sources.md +16 -16
- package/docs/examples/agent-human.md +30 -32
- package/docs/examples/ai-sdk-tool.md +32 -33
- package/docs/examples/existing-python-backend.md +38 -36
- package/docs/examples/nextjs.md +24 -25
- package/docs/examples/scoped-agent.md +78 -0
- package/docs/examples/server-agent.md +20 -61
- package/docs/guarantees.md +34 -56
- package/docs/identity.md +529 -0
- package/docs/index.md +18 -24
- package/docs/integration-guide.md +130 -144
- package/docs/interaction-model.md +32 -95
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +1 -1
- package/docs/mcp.md +11 -26
- package/docs/quickstart.md +43 -49
- package/docs/react.md +74 -24
- package/docs/roadmap.md +17 -7
- package/llms.txt +34 -39
- package/package.json +8 -1
- package/dist/react/useMutate.d.ts +0 -83
- package/dist/react/useQuery.d.ts +0 -123
- package/dist/react/useQuery.js +0 -145
- package/dist/react/useReader.d.ts +0 -69
- package/docs/capabilities.md +0 -163
package/dist/errors.d.ts
CHANGED
|
@@ -18,27 +18,71 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Both work on every subclass.
|
|
20
20
|
*/
|
|
21
|
+
import type { ErrorCode } from './errorCodes.js';
|
|
22
|
+
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec } from './errorCodes.js';
|
|
23
|
+
export { ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode } from './errorCodes.js';
|
|
21
24
|
/** Common shape for all errors thrown by this SDK. */
|
|
22
25
|
export declare class AbloError extends Error {
|
|
23
26
|
/** Discriminator string — matches the class name. Lets consumers
|
|
24
27
|
* switch on `e.type` without `instanceof` checks across package
|
|
25
28
|
* boundaries (matches Stripe's `err.type` pattern). */
|
|
26
29
|
readonly type: string;
|
|
27
|
-
/** Stable short identifier for logs + metrics
|
|
28
|
-
*
|
|
30
|
+
/** Stable short identifier for logs + metrics, drawn from the closed
|
|
31
|
+
* {@link ErrorCode} registry — e.g. `'apikey_invalid'`,
|
|
32
|
+
* `'capability_scope_denied'`. Stored as a plain `string` (not
|
|
33
|
+
* `ErrorCode`) so an older SDK still surfaces a newer server's code it
|
|
34
|
+
* doesn't recognise yet; producers are constrained at the constructor
|
|
35
|
+
* param instead. */
|
|
29
36
|
readonly code?: string;
|
|
30
37
|
/** HTTP status code when the error originated from an HTTP response. */
|
|
31
38
|
readonly httpStatus?: number;
|
|
32
39
|
/** Correlation id for ops — present when the server sent one on
|
|
33
40
|
* `x-request-id`. Include in support tickets. */
|
|
34
41
|
readonly requestId?: string;
|
|
42
|
+
/** Which input caused the error — a model/field path like
|
|
43
|
+
* `'dataroomMember.grants.subject'`. Mirrors Stripe's `error.param`;
|
|
44
|
+
* lets tooling point at the exact offending declaration. */
|
|
45
|
+
readonly param?: string;
|
|
46
|
+
/** Link to the docs for this `code`. Mirrors Stripe's `error.doc_url`.
|
|
47
|
+
* Defaults from `code` via {@link docUrlForCode} when omitted. */
|
|
48
|
+
readonly docUrl?: string;
|
|
49
|
+
/** Domain-specific structured payload merged into the wire envelope —
|
|
50
|
+
* e.g. a schema push's `{ warnings, unexecutable }`, a stale write's
|
|
51
|
+
* conflicting rows. Mirrors how Stripe attaches type-specific fields
|
|
52
|
+
* (`decline_code`, `payment_intent`) alongside the standard ones, so a
|
|
53
|
+
* structured error keeps its detail through `toJSON` instead of being
|
|
54
|
+
* flattened to a bare message. */
|
|
55
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
35
56
|
constructor(message: string, options?: {
|
|
36
|
-
code?:
|
|
57
|
+
code?: ErrorCode;
|
|
37
58
|
httpStatus?: number;
|
|
38
59
|
requestId?: string;
|
|
39
60
|
cause?: unknown;
|
|
61
|
+
param?: string;
|
|
62
|
+
docUrl?: string;
|
|
63
|
+
details?: Readonly<Record<string, unknown>>;
|
|
40
64
|
});
|
|
65
|
+
/**
|
|
66
|
+
* Serialize to Stripe's error-object shape: `{ type, code, param, message,
|
|
67
|
+
* doc_url, request_id }`. One JSON shape across HTTP bodies, WS frames, and
|
|
68
|
+
* logs — so consumers parse Ablo errors the way they already parse Stripe's.
|
|
69
|
+
*/
|
|
70
|
+
toJSON(): {
|
|
71
|
+
type: string;
|
|
72
|
+
code?: string;
|
|
73
|
+
param?: string;
|
|
74
|
+
message: string;
|
|
75
|
+
doc_url?: string;
|
|
76
|
+
request_id?: string;
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
};
|
|
41
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Map a stable error `code` to its docs URL — the one place the convention
|
|
82
|
+
* lives, so every error carrying a code gets a `doc_url` for free (Stripe
|
|
83
|
+
* ships a link on every error).
|
|
84
|
+
*/
|
|
85
|
+
export declare function docUrlForCode(code: ErrorCode): string;
|
|
42
86
|
/** 401 — invalid/missing/expired credentials. */
|
|
43
87
|
export declare class AbloAuthenticationError extends AbloError {
|
|
44
88
|
readonly type: "AbloAuthenticationError";
|
|
@@ -53,11 +97,12 @@ export declare class AbloRateLimitError extends AbloError {
|
|
|
53
97
|
readonly type: "AbloRateLimitError";
|
|
54
98
|
readonly retryAfterSeconds?: number;
|
|
55
99
|
constructor(message: string, options?: {
|
|
56
|
-
code?:
|
|
100
|
+
code?: ErrorCode;
|
|
57
101
|
httpStatus?: number;
|
|
58
102
|
requestId?: string;
|
|
59
103
|
cause?: unknown;
|
|
60
104
|
retryAfterSeconds?: number;
|
|
105
|
+
details?: Readonly<Record<string, unknown>>;
|
|
61
106
|
});
|
|
62
107
|
}
|
|
63
108
|
/** 409 — same `Idempotency-Key` reused with a different request body. */
|
|
@@ -98,7 +143,7 @@ export declare class AbloStaleContextError extends AbloError {
|
|
|
98
143
|
readonly observedSyncId: number;
|
|
99
144
|
}>;
|
|
100
145
|
constructor(message: string, options?: {
|
|
101
|
-
code?:
|
|
146
|
+
code?: ErrorCode;
|
|
102
147
|
httpStatus?: number;
|
|
103
148
|
requestId?: string;
|
|
104
149
|
cause?: unknown;
|
|
@@ -111,21 +156,21 @@ export declare class AbloStaleContextError extends AbloError {
|
|
|
111
156
|
});
|
|
112
157
|
}
|
|
113
158
|
/**
|
|
114
|
-
* The target entity currently
|
|
115
|
-
*
|
|
159
|
+
* The target entity is currently claimed by another participant and the caller
|
|
160
|
+
* asked the SDK not to read/write through that claim.
|
|
116
161
|
*
|
|
117
|
-
* Use `
|
|
118
|
-
* `
|
|
162
|
+
* Use `ifClaimed: 'wait'` to wait for the claim to clear, or
|
|
163
|
+
* `ifClaimed: 'return'` to inspect active claims yourself.
|
|
119
164
|
*/
|
|
120
|
-
export declare class
|
|
121
|
-
readonly type: "
|
|
122
|
-
readonly
|
|
165
|
+
export declare class AbloClaimedError extends AbloError {
|
|
166
|
+
readonly type: "AbloClaimedError";
|
|
167
|
+
readonly claims?: ReadonlyArray<unknown>;
|
|
123
168
|
constructor(message: string, options?: {
|
|
124
|
-
code?:
|
|
169
|
+
code?: ErrorCode;
|
|
125
170
|
httpStatus?: number;
|
|
126
171
|
requestId?: string;
|
|
127
172
|
cause?: unknown;
|
|
128
|
-
|
|
173
|
+
claims?: ReadonlyArray<unknown>;
|
|
129
174
|
});
|
|
130
175
|
}
|
|
131
176
|
/**
|
package/dist/errors.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Both work on every subclass.
|
|
20
20
|
*/
|
|
21
|
+
export { ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode } from './errorCodes.js';
|
|
21
22
|
// ── AbloError hierarchy — the typed error surface ────────────────────
|
|
22
23
|
/** Common shape for all errors thrown by this SDK. */
|
|
23
24
|
export class AbloError extends Error {
|
|
@@ -25,14 +26,32 @@ export class AbloError extends Error {
|
|
|
25
26
|
* switch on `e.type` without `instanceof` checks across package
|
|
26
27
|
* boundaries (matches Stripe's `err.type` pattern). */
|
|
27
28
|
type = 'AbloError';
|
|
28
|
-
/** Stable short identifier for logs + metrics
|
|
29
|
-
*
|
|
29
|
+
/** Stable short identifier for logs + metrics, drawn from the closed
|
|
30
|
+
* {@link ErrorCode} registry — e.g. `'apikey_invalid'`,
|
|
31
|
+
* `'capability_scope_denied'`. Stored as a plain `string` (not
|
|
32
|
+
* `ErrorCode`) so an older SDK still surfaces a newer server's code it
|
|
33
|
+
* doesn't recognise yet; producers are constrained at the constructor
|
|
34
|
+
* param instead. */
|
|
30
35
|
code;
|
|
31
36
|
/** HTTP status code when the error originated from an HTTP response. */
|
|
32
37
|
httpStatus;
|
|
33
38
|
/** Correlation id for ops — present when the server sent one on
|
|
34
39
|
* `x-request-id`. Include in support tickets. */
|
|
35
40
|
requestId;
|
|
41
|
+
/** Which input caused the error — a model/field path like
|
|
42
|
+
* `'dataroomMember.grants.subject'`. Mirrors Stripe's `error.param`;
|
|
43
|
+
* lets tooling point at the exact offending declaration. */
|
|
44
|
+
param;
|
|
45
|
+
/** Link to the docs for this `code`. Mirrors Stripe's `error.doc_url`.
|
|
46
|
+
* Defaults from `code` via {@link docUrlForCode} when omitted. */
|
|
47
|
+
docUrl;
|
|
48
|
+
/** Domain-specific structured payload merged into the wire envelope —
|
|
49
|
+
* e.g. a schema push's `{ warnings, unexecutable }`, a stale write's
|
|
50
|
+
* conflicting rows. Mirrors how Stripe attaches type-specific fields
|
|
51
|
+
* (`decline_code`, `payment_intent`) alongside the standard ones, so a
|
|
52
|
+
* structured error keeps its detail through `toJSON` instead of being
|
|
53
|
+
* flattened to a bare message. */
|
|
54
|
+
details;
|
|
36
55
|
constructor(message, options) {
|
|
37
56
|
super(message);
|
|
38
57
|
this.name = this.constructor.name;
|
|
@@ -42,10 +61,41 @@ export class AbloError extends Error {
|
|
|
42
61
|
this.httpStatus = options.httpStatus;
|
|
43
62
|
if (options?.requestId !== undefined)
|
|
44
63
|
this.requestId = options.requestId;
|
|
64
|
+
if (options?.param !== undefined)
|
|
65
|
+
this.param = options.param;
|
|
66
|
+
if (options?.details !== undefined)
|
|
67
|
+
this.details = options.details;
|
|
68
|
+
const docUrl = options?.docUrl ?? (options?.code ? docUrlForCode(options.code) : undefined);
|
|
69
|
+
if (docUrl !== undefined)
|
|
70
|
+
this.docUrl = docUrl;
|
|
45
71
|
if (options?.cause !== undefined) {
|
|
46
72
|
Object.defineProperty(this, 'cause', { value: options.cause, enumerable: false });
|
|
47
73
|
}
|
|
48
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Serialize to Stripe's error-object shape: `{ type, code, param, message,
|
|
77
|
+
* doc_url, request_id }`. One JSON shape across HTTP bodies, WS frames, and
|
|
78
|
+
* logs — so consumers parse Ablo errors the way they already parse Stripe's.
|
|
79
|
+
*/
|
|
80
|
+
toJSON() {
|
|
81
|
+
return {
|
|
82
|
+
type: this.type,
|
|
83
|
+
...(this.code !== undefined ? { code: this.code } : {}),
|
|
84
|
+
...(this.param !== undefined ? { param: this.param } : {}),
|
|
85
|
+
message: this.message,
|
|
86
|
+
...(this.docUrl !== undefined ? { doc_url: this.docUrl } : {}),
|
|
87
|
+
...(this.requestId !== undefined ? { request_id: this.requestId } : {}),
|
|
88
|
+
...(this.details ?? {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Map a stable error `code` to its docs URL — the one place the convention
|
|
94
|
+
* lives, so every error carrying a code gets a `doc_url` for free (Stripe
|
|
95
|
+
* ships a link on every error).
|
|
96
|
+
*/
|
|
97
|
+
export function docUrlForCode(code) {
|
|
98
|
+
return `https://docs.abloatai.com/errors#${code}`;
|
|
49
99
|
}
|
|
50
100
|
/** 401 — invalid/missing/expired credentials. */
|
|
51
101
|
export class AbloAuthenticationError extends AbloError {
|
|
@@ -109,19 +159,19 @@ export class AbloStaleContextError extends AbloError {
|
|
|
109
159
|
}
|
|
110
160
|
}
|
|
111
161
|
/**
|
|
112
|
-
* The target entity currently
|
|
113
|
-
*
|
|
162
|
+
* The target entity is currently claimed by another participant and the caller
|
|
163
|
+
* asked the SDK not to read/write through that claim.
|
|
114
164
|
*
|
|
115
|
-
* Use `
|
|
116
|
-
* `
|
|
165
|
+
* Use `ifClaimed: 'wait'` to wait for the claim to clear, or
|
|
166
|
+
* `ifClaimed: 'return'` to inspect active claims yourself.
|
|
117
167
|
*/
|
|
118
|
-
export class
|
|
119
|
-
type = '
|
|
120
|
-
|
|
168
|
+
export class AbloClaimedError extends AbloError {
|
|
169
|
+
type = 'AbloClaimedError';
|
|
170
|
+
claims;
|
|
121
171
|
constructor(message, options) {
|
|
122
172
|
super(message, options);
|
|
123
|
-
if (options?.
|
|
124
|
-
this.
|
|
173
|
+
if (options?.claims !== undefined)
|
|
174
|
+
this.claims = options.claims;
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
/**
|
|
@@ -224,7 +274,11 @@ export function translateHttpError(status, body, requestId) {
|
|
|
224
274
|
flatError ??
|
|
225
275
|
(typeof body === 'string' ? body : `HTTP ${status}`);
|
|
226
276
|
const requiredCapability = nested?.requiredCapability ?? parsed.requiredCapability;
|
|
227
|
-
|
|
277
|
+
// Wire boundary: an incoming code is an arbitrary string (a newer server
|
|
278
|
+
// may send a code this SDK predates). Cast to ErrorCode here — the one
|
|
279
|
+
// sanctioned crossing — so internal producers stay statically checked.
|
|
280
|
+
const publicCode = (code === 'intent_conflict' ? 'claim_conflict' : code);
|
|
281
|
+
const baseOpts = { code: publicCode, httpStatus: status, requestId };
|
|
228
282
|
if (status === 401)
|
|
229
283
|
return new AbloAuthenticationError(message, baseOpts);
|
|
230
284
|
if (status === 403 || code === 'capability_scope_denied' || code === 'capability_invalid') {
|
|
@@ -233,6 +287,13 @@ export function translateHttpError(status, body, requestId) {
|
|
|
233
287
|
}
|
|
234
288
|
return new AbloPermissionError(message, baseOpts);
|
|
235
289
|
}
|
|
290
|
+
// Claim enforcement also rides 409 (a commit blocked by a foreign claim).
|
|
291
|
+
// Discriminate on the code BEFORE the generic idempotency mapping so a
|
|
292
|
+
// claim rejection surfaces as AbloClaimedError, not AbloIdempotencyError —
|
|
293
|
+
// same typed error the WebSocket commit path yields for these codes.
|
|
294
|
+
if (code === 'intent_conflict' || code === 'claim_conflict' || code === 'entity_claimed') {
|
|
295
|
+
return new AbloClaimedError(message, baseOpts);
|
|
296
|
+
}
|
|
236
297
|
if (status === 409)
|
|
237
298
|
return new AbloIdempotencyError(message, baseOpts);
|
|
238
299
|
if (status === 422 || status === 400)
|
package/dist/index.d.ts
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
* import Ablo from '@abloatai/ablo';
|
|
6
6
|
*
|
|
7
7
|
* const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
8
|
-
* await ablo.
|
|
9
|
-
* await ablo.
|
|
8
|
+
* await ablo.weatherReports.load({ where: { id: 'report_stockholm' } });
|
|
9
|
+
* await ablo.weatherReports.update('report_stockholm', { status: 'ready' });
|
|
10
10
|
*
|
|
11
11
|
* type Entry = Ablo.Peer;
|
|
12
12
|
* ```
|
|
13
13
|
*
|
|
14
|
-
* `Ablo({ schema, apiKey })` gives typed model
|
|
15
|
-
* gives the
|
|
16
|
-
*
|
|
14
|
+
* `Ablo({ schema, apiKey })` gives typed model clients. `Ablo({ apiKey })`
|
|
15
|
+
* gives the HTTP model/commit client for agents, MCP routes, and custom
|
|
16
|
+
* runtimes.
|
|
17
17
|
*
|
|
18
|
-
* Stripe / Anthropic / OpenAI all do this: one import,
|
|
18
|
+
* Stripe / Anthropic / OpenAI all do this: one import, model clients
|
|
19
19
|
* reached via dot-access on the engine, types via namespace dots.
|
|
20
20
|
*
|
|
21
21
|
* Public subpaths:
|
|
@@ -42,14 +42,16 @@
|
|
|
42
42
|
* If you don't recognize one, you don't need it — the default path covers you.
|
|
43
43
|
*/
|
|
44
44
|
export { Ablo } from './client/Ablo.js';
|
|
45
|
-
export type { AbloOptions, ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions,
|
|
45
|
+
export type { AbloOptions, ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ClaimOptions, ClaimedRow, ModelOperations, } from './client/Ablo.js';
|
|
46
46
|
export type { AbloPersistence } from './client/persistence.js';
|
|
47
47
|
export { session, agent } from './principal.js';
|
|
48
48
|
import { Ablo } from './client/Ablo.js';
|
|
49
49
|
export default Ablo;
|
|
50
50
|
export { dataSource, abloSource, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
51
|
-
export { defaultPolicy } from './policy/index.js';
|
|
52
|
-
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError,
|
|
51
|
+
export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
52
|
+
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, } from './errors.js';
|
|
53
53
|
export type { CommitReceipt, RequiredCapability } from './errors.js';
|
|
54
|
+
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec } from './errors.js';
|
|
55
|
+
export type { Register, DefaultSyncShape } from './types/global.js';
|
|
54
56
|
export { defineMutators } from './mutators/defineMutators.js';
|
|
55
57
|
export { createTransaction, type Transaction } from './mutators/Transaction.js';
|
package/dist/index.js
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
* import Ablo from '@abloatai/ablo';
|
|
6
6
|
*
|
|
7
7
|
* const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
8
|
-
* await ablo.
|
|
9
|
-
* await ablo.
|
|
8
|
+
* await ablo.weatherReports.load({ where: { id: 'report_stockholm' } });
|
|
9
|
+
* await ablo.weatherReports.update('report_stockholm', { status: 'ready' });
|
|
10
10
|
*
|
|
11
11
|
* type Entry = Ablo.Peer;
|
|
12
12
|
* ```
|
|
13
13
|
*
|
|
14
|
-
* `Ablo({ schema, apiKey })` gives typed model
|
|
15
|
-
* gives the
|
|
16
|
-
*
|
|
14
|
+
* `Ablo({ schema, apiKey })` gives typed model clients. `Ablo({ apiKey })`
|
|
15
|
+
* gives the HTTP model/commit client for agents, MCP routes, and custom
|
|
16
|
+
* runtimes.
|
|
17
17
|
*
|
|
18
|
-
* Stripe / Anthropic / OpenAI all do this: one import,
|
|
18
|
+
* Stripe / Anthropic / OpenAI all do this: one import, model clients
|
|
19
19
|
* reached via dot-access on the engine, types via namespace dots.
|
|
20
20
|
*
|
|
21
21
|
* Public subpaths:
|
|
@@ -74,15 +74,11 @@ export { dataSource, abloSource, signAbloSourceRequest, verifyAbloSourceRequest,
|
|
|
74
74
|
// (reject-on-stale) is already applied server-side, so you only import it
|
|
75
75
|
// to COMPOSE a custom policy. Leave it alone and stale writes are rejected
|
|
76
76
|
// safely by default. Type counterparts live under `Ablo.Conflict.*`.
|
|
77
|
-
export { defaultPolicy } from './policy/index.js';
|
|
77
|
+
export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
78
78
|
// Typed error hierarchy — Stripe-style. One import gets every class
|
|
79
79
|
// consumers need to discriminate failures (`e instanceof AbloX` or
|
|
80
80
|
// `e.type === 'AbloX'`) plus the HTTP-response translator.
|
|
81
|
-
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError,
|
|
82
|
-
// Typed-global augmentation point. Consumers declare their Schema/Presence/
|
|
83
|
-
// Intents/UserMeta once in a `.d.ts` via `declare global { interface AbloSync
|
|
84
|
-
// { ... } }`. Resolver types live under the `Ablo` namespace —
|
|
85
|
-
// `Ablo.ResolveSchema`, `Ablo.ResolvePresence`, etc. — pure type-level.
|
|
81
|
+
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, } from './errors.js';
|
|
86
82
|
// Advanced — most apps never import this. Custom (Zero-style) mutators:
|
|
87
83
|
// `ablo.<model>.create/update/delete` already covers normal writes. Reach
|
|
88
84
|
// for `defineMutators` only when you need a named, multi-step mutation with
|
|
@@ -151,18 +151,11 @@ export interface CommitResult {
|
|
|
151
151
|
* When omitted, the SDK auto-generates a UUIDv4 per mutation so every
|
|
152
152
|
* call is retry-safe by default. Opt out with `{ idempotencyKey: null }`
|
|
153
153
|
* if you genuinely want retry-unsafe writes (rare).
|
|
154
|
-
* - `timeout` — abort the request if it takes longer than this many ms.
|
|
155
|
-
* No default (uses underlying transport's timeout).
|
|
156
|
-
* - `maxNetworkRetries` — retry with exponential backoff on 5xx / 429 /
|
|
157
|
-
* network errors. The same `idempotencyKey` is reused across retries
|
|
158
|
-
* so the server dedupes correctly. Default: 0.
|
|
159
154
|
* - `label` — human-readable audit tag. Flows to `mutation_log.label`
|
|
160
155
|
* server-side for operator debugging ("nightly cleanup", "user click").
|
|
161
156
|
*/
|
|
162
157
|
export interface MutationOptions {
|
|
163
158
|
idempotencyKey?: string | null;
|
|
164
|
-
timeout?: number;
|
|
165
|
-
maxNetworkRetries?: number;
|
|
166
159
|
label?: string;
|
|
167
160
|
wait?: 'queued' | 'confirmed';
|
|
168
161
|
readAt?: number | null;
|
|
@@ -205,9 +198,8 @@ export interface MutationOperation {
|
|
|
205
198
|
/**
|
|
206
199
|
* Per-op idempotency + audit metadata. `idempotencyKey` doubles as
|
|
207
200
|
* the `mutation_log.client_tx_id` cache key; `label` is persisted to
|
|
208
|
-
* `mutation_log.label` for debugging.
|
|
209
|
-
*
|
|
210
|
-
* NOT sent over the wire.
|
|
201
|
+
* `mutation_log.label` for debugging. These are the only `MutationOptions`
|
|
202
|
+
* fields carried over the wire.
|
|
211
203
|
*/
|
|
212
204
|
options?: Pick<MutationOptions, 'idempotencyKey' | 'label'>;
|
|
213
205
|
}
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { Schema } from '../schema/schema.js';
|
|
23
23
|
import type { SyncStoreContract } from '../react/context.js';
|
|
24
|
-
import { type MutateActions } from '
|
|
25
|
-
import { type ReaderActions, type ReaderFindOptions } from '
|
|
24
|
+
import { type MutateActions } from './mutateActions.js';
|
|
25
|
+
import { type ReaderActions, type ReaderFindOptions } from './readerActions.js';
|
|
26
26
|
/**
|
|
27
27
|
* The full transaction surface. `tx.mutations.<key>.*` for writes,
|
|
28
28
|
* `tx.read.<key>.*` for imperative reads. Re-exports the base read options
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* microtask coalescer in `TransactionQueue` collapses N pushes into one
|
|
20
20
|
* wire commit. Same shape Zero uses: no `insertMany`, just an array map.
|
|
21
21
|
*/
|
|
22
|
-
import { createMutateActions } from '
|
|
23
|
-
import { createReaderActions } from '
|
|
22
|
+
import { createMutateActions } from './mutateActions.js';
|
|
23
|
+
import { createReaderActions } from './readerActions.js';
|
|
24
24
|
import { AbloValidationError } from '../errors.js';
|
|
25
25
|
/**
|
|
26
26
|
* Build a Transaction for a single mutator invocation. The returned object
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Schema, InferModel, InferCreate } from '../schema/schema.js';
|
|
2
|
+
import type { SyncStoreContract } from '../react/context.js';
|
|
3
|
+
/**
|
|
4
|
+
* `create` / `update` / `delete` are overloaded: pass one row or an array
|
|
5
|
+
* (Drizzle/Prisma `values(rowOrRows)` shape). Every entry in an array call
|
|
6
|
+
* lands in the same synchronous tick (`Promise.all`), so the microtask
|
|
7
|
+
* coalescer in `TransactionQueue` collapses N pushes into one wire commit.
|
|
8
|
+
*
|
|
9
|
+
* This module is the React-free core of CRUD staging. The transaction system
|
|
10
|
+
* (`Transaction` / `RecordingTransaction`) and `BaseSyncedStore` build on it;
|
|
11
|
+
* there is no React hook here (the legacy `useMutate` hook was removed —
|
|
12
|
+
* callers use `ablo.<model>.create/update/delete`).
|
|
13
|
+
*/
|
|
14
|
+
type UpdatePatch<S extends Schema, K extends keyof S['models'] & string> = {
|
|
15
|
+
id: string;
|
|
16
|
+
} & Partial<InferModel<S, K>>;
|
|
17
|
+
export interface MutateActions<S extends Schema, K extends keyof S['models'] & string> {
|
|
18
|
+
/**
|
|
19
|
+
* Create one entity, or an array of entities in a single tick. ID,
|
|
20
|
+
* createdAt, updatedAt, organizationId default automatically per row.
|
|
21
|
+
*/
|
|
22
|
+
create(data: InferCreate<S, K>): Promise<InferModel<S, K>>;
|
|
23
|
+
create(data: InferCreate<S, K>[]): Promise<InferModel<S, K>[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Update one row, or an array of rows in a single tick. Each patch is
|
|
26
|
+
* `{ id, ...changes }` — missing ids throw. Schema-generated models are
|
|
27
|
+
* MobX-observable, so direct assignment fires reactivity.
|
|
28
|
+
*/
|
|
29
|
+
update(patch: UpdatePatch<S, K>): Promise<InferModel<S, K>>;
|
|
30
|
+
update(patches: UpdatePatch<S, K>[]): Promise<InferModel<S, K>[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete one row by id, or an array of ids in a single tick. Missing ids
|
|
33
|
+
* are silently ignored.
|
|
34
|
+
*/
|
|
35
|
+
delete(id: string): Promise<void>;
|
|
36
|
+
delete(ids: string[]): Promise<void>;
|
|
37
|
+
/** Soft-archive by ID. */
|
|
38
|
+
archive: (id: string) => Promise<void>;
|
|
39
|
+
/** Restore an archived entity by ID. */
|
|
40
|
+
unarchive: (id: string) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/** Pure factory — builds CRUD actions over a store for one model. React-free. */
|
|
43
|
+
export declare function createMutateActions<S extends Schema, K extends keyof S['models'] & string>(schema: S, modelKey: K, store: SyncStoreContract, organizationId: string): MutateActions<S, K>;
|
|
44
|
+
export {};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
1
|
import { Model, modelAsRow } from '../Model.js';
|
|
4
2
|
import { AbloValidationError } from '../errors.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Pure factory — testable without React. The hook just wraps this in
|
|
8
|
-
* useMemo with the React context.
|
|
9
|
-
*/
|
|
3
|
+
/** Pure factory — builds CRUD actions over a store for one model. React-free. */
|
|
10
4
|
export function createMutateActions(schema, modelKey, store, organizationId) {
|
|
11
5
|
const modelDef = schema.models[modelKey];
|
|
12
6
|
const typename = modelDef?.typename ?? modelKey;
|
|
@@ -25,7 +19,7 @@ export function createMutateActions(schema, modelKey, store, organizationId) {
|
|
|
25
19
|
};
|
|
26
20
|
const model = store.pool.createFromData(fullData);
|
|
27
21
|
if (!model) {
|
|
28
|
-
throw new AbloValidationError(`
|
|
22
|
+
throw new AbloValidationError(`createMutateActions: failed to create ${typename} — no constructor in registry`, { code: 'mutate_create_unknown_model' });
|
|
29
23
|
}
|
|
30
24
|
return model;
|
|
31
25
|
};
|
|
@@ -34,14 +28,13 @@ export function createMutateActions(schema, modelKey, store, organizationId) {
|
|
|
34
28
|
const { id, ...changes } = patch;
|
|
35
29
|
const model = store.pool.get(id);
|
|
36
30
|
if (!model) {
|
|
37
|
-
throw new AbloValidationError(`
|
|
31
|
+
throw new AbloValidationError(`createMutateActions: ${typename} with id "${id}" not found in pool`, { code: 'mutate_update_entity_not_found' });
|
|
38
32
|
}
|
|
39
|
-
// Schema-derived patch keys are validated at the call-site type
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// guarantees these keys resolve at runtime.
|
|
33
|
+
// Schema-derived patch keys are validated at the call-site type signature
|
|
34
|
+
// (`UpdatePatch<S, K>`); writes here are dynamic-class field assignments.
|
|
35
|
+
// `Reflect.set` is the typed bridge — Model carries no index signature, but
|
|
36
|
+
// the dynamic field installation in `createDynamicModelClass` guarantees
|
|
37
|
+
// these keys resolve at runtime.
|
|
45
38
|
for (const [fieldName, value] of Object.entries(changes)) {
|
|
46
39
|
Reflect.set(model, fieldName, value);
|
|
47
40
|
}
|
|
@@ -49,9 +42,9 @@ export function createMutateActions(schema, modelKey, store, organizationId) {
|
|
|
49
42
|
return model;
|
|
50
43
|
};
|
|
51
44
|
return {
|
|
52
|
-
// Overloaded — runtime
|
|
53
|
-
//
|
|
54
|
-
//
|
|
45
|
+
// Overloaded — runtime `Array.isArray` decides shape. Both branches stage
|
|
46
|
+
// via `Promise.all` so the microtask coalescer collapses N pushes into one
|
|
47
|
+
// wire commit.
|
|
55
48
|
create: (async (data) => {
|
|
56
49
|
const now = new Date();
|
|
57
50
|
if (Array.isArray(data)) {
|
|
@@ -110,13 +103,3 @@ export function createMutateActions(schema, modelKey, store, organizationId) {
|
|
|
110
103
|
},
|
|
111
104
|
};
|
|
112
105
|
}
|
|
113
|
-
export function useMutate(schemaOrKey, maybeKey) {
|
|
114
|
-
const { store, organizationId, schema: ctxSchema } = useSyncContext();
|
|
115
|
-
const resolvedSchema = typeof schemaOrKey === 'string' ? ctxSchema : schemaOrKey;
|
|
116
|
-
const resolvedKey = typeof schemaOrKey === 'string' ? schemaOrKey : maybeKey;
|
|
117
|
-
if (!resolvedSchema) {
|
|
118
|
-
throw new AbloValidationError('useMutate: no schema available. Pass the schema as the first arg ' +
|
|
119
|
-
'or wire SyncProvider with a `schema` prop when using the zero-arg overload.', { code: 'mutate_schema_missing' });
|
|
120
|
-
}
|
|
121
|
-
return useMemo(() => createMutateActions(resolvedSchema, resolvedKey, store, organizationId), [store, organizationId, resolvedSchema, resolvedKey]);
|
|
122
|
-
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Schema, InferModel } from '../schema/schema.js';
|
|
2
|
+
import type { SyncStoreContract } from '../react/context.js';
|
|
3
|
+
/**
|
|
4
|
+
* React-free imperative reads over a store: one-off `retrieve`/`list`/`count`
|
|
5
|
+
* snapshots that do NOT subscribe to changes. Used by the transaction system
|
|
6
|
+
* and `BaseSyncedStore`. For reactive reads in components use
|
|
7
|
+
* `useAblo((ablo) => ablo.<model>.retrieve(id) / .list(opts))`.
|
|
8
|
+
*/
|
|
9
|
+
export interface ReaderFindOptions<T> {
|
|
10
|
+
/** Equality filter — uses FK index when the field is registered. */
|
|
11
|
+
where?: Partial<T>;
|
|
12
|
+
/** Predicate applied AFTER `where` filtering. */
|
|
13
|
+
filter?: (entity: T) => boolean;
|
|
14
|
+
/** Sort field. */
|
|
15
|
+
orderBy?: keyof T & string;
|
|
16
|
+
/** Sort direction. Default: 'asc'. */
|
|
17
|
+
order?: 'asc' | 'desc';
|
|
18
|
+
/** Max results. */
|
|
19
|
+
limit?: number;
|
|
20
|
+
/** Skip N results. */
|
|
21
|
+
offset?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ReaderActions<S extends Schema, K extends keyof S['models'] & string> {
|
|
24
|
+
/** Get a single entity by id. Returns undefined if not in pool. */
|
|
25
|
+
retrieve: (id: string) => InferModel<S, K> | undefined;
|
|
26
|
+
/** Read a collection with optional filters. Snapshot — not reactive. */
|
|
27
|
+
list: (options?: ReaderFindOptions<InferModel<S, K>>) => InferModel<S, K>[];
|
|
28
|
+
/** Count entities matching the options. */
|
|
29
|
+
count: (options?: ReaderFindOptions<InferModel<S, K>>) => number;
|
|
30
|
+
}
|
|
31
|
+
/** Pure factory — builds imperative read actions over a store for one model. */
|
|
32
|
+
export declare function createReaderActions<S extends Schema, K extends keyof S['models'] & string>(schema: S, modelKey: K, store: SyncStoreContract): ReaderActions<S, K>;
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import { useSyncContext } from './context.js';
|
|
4
|
-
import { AbloValidationError } from '../errors.js';
|
|
5
|
-
/**
|
|
6
|
-
* Pure factory — testable without React. `useReader` wraps this in useMemo.
|
|
7
|
-
*/
|
|
1
|
+
/** Pure factory — builds imperative read actions over a store for one model. */
|
|
8
2
|
export function createReaderActions(schema, modelKey, store) {
|
|
9
3
|
const modelDef = schema.models[modelKey];
|
|
10
4
|
const typename = modelDef?.typename ?? modelKey;
|
|
11
5
|
function read(options) {
|
|
12
|
-
// FK index fast path: single-field `where` on a registered FK index → O(1)
|
|
6
|
+
// FK index fast path: single-field `where` on a registered FK index → O(1).
|
|
13
7
|
let candidates;
|
|
14
8
|
const whereEntries = options?.where ? Object.entries(options.where) : [];
|
|
15
9
|
const singleWhere = whereEntries.length === 1 ? whereEntries[0] : undefined;
|
|
@@ -61,13 +55,3 @@ export function createReaderActions(schema, modelKey, store) {
|
|
|
61
55
|
count: (options) => read(options).length,
|
|
62
56
|
};
|
|
63
57
|
}
|
|
64
|
-
export function useReader(schemaOrKey, maybeKey) {
|
|
65
|
-
const { store, schema: ctxSchema } = useSyncContext();
|
|
66
|
-
const resolvedSchema = typeof schemaOrKey === 'string' ? ctxSchema : schemaOrKey;
|
|
67
|
-
const resolvedKey = typeof schemaOrKey === 'string' ? schemaOrKey : maybeKey;
|
|
68
|
-
if (!resolvedSchema) {
|
|
69
|
-
throw new AbloValidationError('useReader: no schema available. Pass the schema as the first arg ' +
|
|
70
|
-
'or wire SyncProvider with a `schema` prop when using the zero-arg overload.', { code: 'reader_schema_missing' });
|
|
71
|
-
}
|
|
72
|
-
return useMemo(() => createReaderActions(resolvedSchema, resolvedKey, store), [store, resolvedSchema, resolvedKey]);
|
|
73
|
-
}
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export type { Conflict, ConflictDecision, ConflictKind, ConflictOperation, ConflictPolicy, StaleContextConflict, IntentHeldConflict, } from './types.js';
|
|
19
|
-
export { defaultPolicy } from './types.js';
|
|
19
|
+
export { defaultPolicy, capabilityPreemptPolicy } from './types.js';
|
package/dist/policy/index.js
CHANGED