@abloatai/ablo 0.8.0 → 0.9.1
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 +46 -1
- package/README.md +33 -28
- package/dist/BaseSyncedStore.d.ts +83 -0
- package/dist/BaseSyncedStore.js +194 -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 +158 -50
- package/dist/mutators/UndoManager.js +345 -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/context.d.ts +31 -0
- 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 +34 -3
- package/dist/schema/ddl.js +162 -4
- package/dist/schema/index.d.ts +5 -1
- package/dist/schema/index.js +13 -1
- 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 +65 -0
- package/dist/source/adapter.js +20 -0
- package/dist/source/adapters/drizzle.d.ts +43 -0
- package/dist/source/adapters/drizzle.js +185 -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 +176 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +144 -0
- package/dist/source/contract.js +99 -0
- package/dist/source/index.d.ts +62 -10
- package/dist/source/index.js +99 -0
- package/dist/source/migrations.d.ts +14 -0
- package/dist/source/migrations.js +39 -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 +2 -2
- 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/errors.js
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Both work on every subclass.
|
|
20
20
|
*/
|
|
21
|
-
import {
|
|
22
|
-
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import { errorCodeSpec, classifyRecovery } from './errorCodes.js';
|
|
23
|
+
export { ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errorCodes.js';
|
|
23
24
|
// ── AbloError hierarchy — the typed error surface ────────────────────
|
|
24
25
|
/** Common shape for all errors thrown by this SDK. */
|
|
25
26
|
export class AbloError extends Error {
|
|
@@ -238,15 +239,22 @@ export class SyncSessionError extends AbloAuthenticationError {
|
|
|
238
239
|
* Check if an HTTP response status indicates a session error
|
|
239
240
|
*/
|
|
240
241
|
static isSessionErrorResponse(status, body) {
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
242
|
+
// "Should this response sign the user out?" — TRUE only for a genuine
|
|
243
|
+
// expiry of the LONG-LIVED login (`recovery: 'session_expiry'`). Decided
|
|
244
|
+
// via the closed recovery taxonomy rather than a hardcoded code list, so
|
|
245
|
+
// the access-vs-session split lives in one place (errorCodes.ts). This is
|
|
246
|
+
// behaviourally identical to the old `session_expired || jwt_expired` list.
|
|
247
|
+
//
|
|
248
|
+
// Deliberately NOT true for `access_credential_expiry` (`apikey_expired` —
|
|
249
|
+
// the Stripe-style ephemeral key): an expired `ek_`/`rk_` is re-mintable
|
|
250
|
+
// from the still-valid login and must NOT log the user out — the connection
|
|
251
|
+
// layer silently re-mints instead. Likewise NOT true for `auth_blocked` /
|
|
252
|
+
// `permission` failures (api_key_required, jwt_issuer_untrusted, 403s):
|
|
253
|
+
// re-auth re-mints the same rejected credential and loops ("flash then
|
|
254
|
+
// bounce to /signin").
|
|
247
255
|
const code = extractWireCode(body);
|
|
248
256
|
if (code) {
|
|
249
|
-
return code === '
|
|
257
|
+
return classifyRecovery(code) === 'session_expiry';
|
|
250
258
|
}
|
|
251
259
|
// No structured code (bare body, non-Ablo proxy response): a 401 is taken as
|
|
252
260
|
// expiry — the historical default that drives re-auth — while a 403 is a
|
|
@@ -254,6 +262,50 @@ export class SyncSessionError extends AbloAuthenticationError {
|
|
|
254
262
|
return status === 401;
|
|
255
263
|
}
|
|
256
264
|
}
|
|
265
|
+
// ── HTTP → class mapping ──────────────────────────────────────────────
|
|
266
|
+
const OptionalWireStringSchema = z.preprocess((value) => (typeof value === 'string' ? value : undefined), z.string().optional());
|
|
267
|
+
const RequiredCapabilityWireSchema = z
|
|
268
|
+
.object({
|
|
269
|
+
scope: z.string(),
|
|
270
|
+
constraints: z
|
|
271
|
+
.record(z.string(), z.union([z.array(z.string()), z.string()]))
|
|
272
|
+
.optional(),
|
|
273
|
+
issuer: OptionalWireStringSchema,
|
|
274
|
+
ttlSeconds: z
|
|
275
|
+
.preprocess((value) => (typeof value === 'number' ? value : undefined), z.number().optional()),
|
|
276
|
+
nonce: OptionalWireStringSchema,
|
|
277
|
+
})
|
|
278
|
+
.passthrough();
|
|
279
|
+
const NestedErrorShapeSchema = z
|
|
280
|
+
.object({
|
|
281
|
+
code: OptionalWireStringSchema,
|
|
282
|
+
message: OptionalWireStringSchema,
|
|
283
|
+
field: OptionalWireStringSchema,
|
|
284
|
+
requiredCapability: RequiredCapabilityWireSchema.optional().catch(undefined),
|
|
285
|
+
})
|
|
286
|
+
.passthrough();
|
|
287
|
+
const ErrorFieldSchema = z
|
|
288
|
+
.preprocess((value) => typeof value === 'string' || (typeof value === 'object' && value !== null)
|
|
289
|
+
? value
|
|
290
|
+
: undefined, z.union([z.string(), NestedErrorShapeSchema]).optional())
|
|
291
|
+
.catch(undefined);
|
|
292
|
+
const ErrorBodyShapeSchema = z
|
|
293
|
+
.object({
|
|
294
|
+
/** Legacy: `error` was a flat code string on older endpoints. Newer
|
|
295
|
+
* endpoints (CommitReceipt) carry `error` as a nested object. */
|
|
296
|
+
error: ErrorFieldSchema,
|
|
297
|
+
code: OptionalWireStringSchema,
|
|
298
|
+
reason: OptionalWireStringSchema,
|
|
299
|
+
message: OptionalWireStringSchema,
|
|
300
|
+
requiredCapability: RequiredCapabilityWireSchema.optional().catch(undefined),
|
|
301
|
+
})
|
|
302
|
+
.passthrough();
|
|
303
|
+
function parseErrorBodyShape(body) {
|
|
304
|
+
if (typeof body !== 'object' || body === null)
|
|
305
|
+
return {};
|
|
306
|
+
const parsed = ErrorBodyShapeSchema.safeParse(body);
|
|
307
|
+
return parsed.success ? parsed.data : {};
|
|
308
|
+
}
|
|
257
309
|
/**
|
|
258
310
|
* Coerce ANY thrown value into an {@link AbloError} — the last-line guarantee
|
|
259
311
|
* that an SDK consumer never catches an untagged error. An already-typed
|
|
@@ -340,7 +392,7 @@ export function errorFromWire(message, opts = {}) {
|
|
|
340
392
|
* frame transports) after extracting code/message from the HTTP body.
|
|
341
393
|
*/
|
|
342
394
|
export function translateHttpError(status, body, requestId) {
|
|
343
|
-
const parsed =
|
|
395
|
+
const parsed = parseErrorBodyShape(body);
|
|
344
396
|
const nested = parsed.error != null && typeof parsed.error === 'object'
|
|
345
397
|
? parsed.error
|
|
346
398
|
: undefined;
|
|
@@ -363,16 +415,14 @@ export function translateHttpError(status, body, requestId) {
|
|
|
363
415
|
* of emitting a code-less error.
|
|
364
416
|
*/
|
|
365
417
|
export function hasWireCode(body) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const b = body;
|
|
369
|
-
if (typeof b.code === 'string')
|
|
418
|
+
const parsed = parseErrorBodyShape(body);
|
|
419
|
+
if (typeof parsed.code === 'string')
|
|
370
420
|
return true;
|
|
371
|
-
if (typeof
|
|
421
|
+
if (typeof parsed.error === 'string')
|
|
372
422
|
return true;
|
|
373
|
-
return (typeof
|
|
374
|
-
|
|
375
|
-
typeof
|
|
423
|
+
return (typeof parsed.error === 'object' &&
|
|
424
|
+
parsed.error !== null &&
|
|
425
|
+
typeof parsed.error.code === 'string');
|
|
376
426
|
}
|
|
377
427
|
/**
|
|
378
428
|
* Extract the canonical error `code` from a raw HTTP error body STRING — the
|
|
@@ -392,12 +442,12 @@ export function extractWireCode(body) {
|
|
|
392
442
|
}
|
|
393
443
|
if (typeof parsed !== 'object' || parsed === null)
|
|
394
444
|
return undefined;
|
|
395
|
-
const b = parsed;
|
|
445
|
+
const b = parseErrorBodyShape(parsed);
|
|
396
446
|
if (typeof b.code === 'string')
|
|
397
447
|
return b.code;
|
|
398
|
-
if (typeof b.error === '
|
|
399
|
-
b.error
|
|
400
|
-
|
|
448
|
+
if (typeof b.error === 'string')
|
|
449
|
+
return b.error;
|
|
450
|
+
if (typeof b.error === 'object' && b.error !== null && typeof b.error.code === 'string') {
|
|
401
451
|
return b.error.code;
|
|
402
452
|
}
|
|
403
453
|
return undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* import Ablo from '@abloatai/ablo';
|
|
6
6
|
*
|
|
7
7
|
* const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
8
|
-
* const report = await ablo.weatherReports.retrieve('report_stockholm');
|
|
9
|
-
* await ablo.weatherReports.update(
|
|
8
|
+
* const report = await ablo.weatherReports.retrieve({ id: 'report_stockholm' });
|
|
9
|
+
* await ablo.weatherReports.update({
|
|
10
|
+
* id: 'report_stockholm',
|
|
11
|
+
* data: { status: 'ready' },
|
|
12
|
+
* });
|
|
10
13
|
*
|
|
11
14
|
* type Entry = Ablo.Peer;
|
|
12
15
|
* ```
|
|
@@ -23,7 +26,7 @@
|
|
|
23
26
|
* @abloatai/ablo/react — <AbloProvider>, useQuery, useMutate
|
|
24
27
|
* @abloatai/ablo/testing — test harnesses + mocks
|
|
25
28
|
*
|
|
26
|
-
* Reads split by where the data comes from. `ablo.<model>.retrieve(id)` and
|
|
29
|
+
* Reads split by where the data comes from. `ablo.<model>.retrieve({ id })` and
|
|
27
30
|
* `.list({ where })` are the async **server** reads (pool → IDB → network via
|
|
28
31
|
* the `HydrationCoordinator`, single-flight deduped); they're the default and
|
|
29
32
|
* what hosted/stateless callers want, since their local graph starts empty.
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
*
|
|
34
37
|
* ── What to import (read this first) ────────────────────────────────
|
|
35
38
|
* Default path — this is all most apps and agents ever need:
|
|
36
|
-
* • `Ablo` (default export) + `AbloOptions` + the `Model*
|
|
39
|
+
* • `Ablo` (default export) + `AbloOptions` + the `Model*Params` bags
|
|
37
40
|
* • the `Ablo*Error` classes, to discriminate failures in catch blocks
|
|
38
41
|
* That's it. If you're reaching past those, you're in advanced territory.
|
|
39
42
|
*
|
|
@@ -46,16 +49,22 @@
|
|
|
46
49
|
* If you don't recognize one, you don't need it — the default path covers you.
|
|
47
50
|
*/
|
|
48
51
|
export { Ablo } from './client/Ablo.js';
|
|
49
|
-
export type {
|
|
52
|
+
export type { MutationExecutor } from './interfaces/index.js';
|
|
53
|
+
export type { HttpClaimApi, InternalAbloOptions } from './client/Ablo.js';
|
|
54
|
+
export { createAbloHttpClient, type AbloHttpClientOptions, type AbloHttpClient, type HttpModelClient, } from './client/httpClient.js';
|
|
55
|
+
export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
|
|
56
|
+
export type { AbloOptions, ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './client/Ablo.js';
|
|
50
57
|
export type { AbloPersistence } from './client/persistence.js';
|
|
51
58
|
export { session, agent } from './principal.js';
|
|
52
59
|
import { Ablo } from './client/Ablo.js';
|
|
53
60
|
export default Ablo;
|
|
54
|
-
export { dataSource, abloSource, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
61
|
+
export { dataSource, abloSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
55
62
|
export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
56
|
-
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, } from './errors.js';
|
|
63
|
+
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errors.js';
|
|
57
64
|
export type { CommitReceipt, RequiredCapability } from './errors.js';
|
|
58
|
-
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec } from './errors.js';
|
|
65
|
+
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errors.js';
|
|
66
|
+
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
67
|
+
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
59
68
|
export type { Register, DefaultSyncShape } from './types/global.js';
|
|
60
69
|
export { defineMutators } from './mutators/defineMutators.js';
|
|
61
70
|
export { createTransaction, type Transaction } from './mutators/Transaction.js';
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* import Ablo from '@abloatai/ablo';
|
|
6
6
|
*
|
|
7
7
|
* const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
8
|
-
* const report = await ablo.weatherReports.retrieve('report_stockholm');
|
|
9
|
-
* await ablo.weatherReports.update(
|
|
8
|
+
* const report = await ablo.weatherReports.retrieve({ id: 'report_stockholm' });
|
|
9
|
+
* await ablo.weatherReports.update({
|
|
10
|
+
* id: 'report_stockholm',
|
|
11
|
+
* data: { status: 'ready' },
|
|
12
|
+
* });
|
|
10
13
|
*
|
|
11
14
|
* type Entry = Ablo.Peer;
|
|
12
15
|
* ```
|
|
@@ -23,7 +26,7 @@
|
|
|
23
26
|
* @abloatai/ablo/react — <AbloProvider>, useQuery, useMutate
|
|
24
27
|
* @abloatai/ablo/testing — test harnesses + mocks
|
|
25
28
|
*
|
|
26
|
-
* Reads split by where the data comes from. `ablo.<model>.retrieve(id)` and
|
|
29
|
+
* Reads split by where the data comes from. `ablo.<model>.retrieve({ id })` and
|
|
27
30
|
* `.list({ where })` are the async **server** reads (pool → IDB → network via
|
|
28
31
|
* the `HydrationCoordinator`, single-flight deduped); they're the default and
|
|
29
32
|
* what hosted/stateless callers want, since their local graph starts empty.
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
*
|
|
34
37
|
* ── What to import (read this first) ────────────────────────────────
|
|
35
38
|
* Default path — this is all most apps and agents ever need:
|
|
36
|
-
* • `Ablo` (default export) + `AbloOptions` + the `Model*
|
|
39
|
+
* • `Ablo` (default export) + `AbloOptions` + the `Model*Params` bags
|
|
37
40
|
* • the `Ablo*Error` classes, to discriminate failures in catch blocks
|
|
38
41
|
* That's it. If you're reaching past those, you're in advanced territory.
|
|
39
42
|
*
|
|
@@ -53,6 +56,8 @@
|
|
|
53
56
|
// `import Ablo from '@abloatai/ablo'` works; named export so
|
|
54
57
|
// `import { Ablo }` also compiles.
|
|
55
58
|
export { Ablo } from './client/Ablo.js';
|
|
59
|
+
export { createAbloHttpClient, } from './client/httpClient.js';
|
|
60
|
+
export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
|
|
56
61
|
// Participant types live under `Ablo.Participant.*` —
|
|
57
62
|
// `Ablo.Participant.Joined`, `Ablo.Participant.Manager`,
|
|
58
63
|
// `Ablo.Participant.JoinOptions`, etc. Same dot-access shape as
|
|
@@ -70,7 +75,7 @@ export default Ablo;
|
|
|
70
75
|
// storage — if you haven't deliberately chosen to keep your own DB
|
|
71
76
|
// canonical, skip this entirely. Type counterparts live under
|
|
72
77
|
// `Ablo.Source.*` (`Ablo.Source.Operation`, `Ablo.Source.Commit.Params`).
|
|
73
|
-
export { dataSource, abloSource, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
78
|
+
export { dataSource, abloSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
74
79
|
// Schema DSL is intentionally published from `@abloatai/ablo/schema`.
|
|
75
80
|
// Keeping it out of the root import preserves one clean runtime surface:
|
|
76
81
|
// `import Ablo from '@abloatai/ablo'`.
|
|
@@ -82,7 +87,11 @@ export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
|
82
87
|
// Typed error hierarchy — Stripe-style. One import gets every class
|
|
83
88
|
// consumers need to discriminate failures (`e instanceof AbloX` or
|
|
84
89
|
// `e.type === 'AbloX'`) plus the HTTP-response translator.
|
|
85
|
-
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, } from './errors.js';
|
|
90
|
+
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errors.js';
|
|
91
|
+
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
92
|
+
// Storage-wedge detection — lets app shells render a recovery screen when the
|
|
93
|
+
// IndexedDB backing store is stuck (see core/openIDBWithTimeout.ts).
|
|
94
|
+
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
86
95
|
// Advanced — most apps never import this. Custom (Zero-style) mutators:
|
|
87
96
|
// `ablo.<model>.create/update/delete` already covers normal writes. Reach
|
|
88
97
|
// for `defineMutators` only when you need a named, multi-step mutation with
|
package/dist/keys/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* still validate by hash — they parse here as `checksummed: false`.
|
|
17
17
|
*/
|
|
18
18
|
import { z } from 'zod';
|
|
19
|
-
export declare const API_KEY_KINDS: readonly ["secret", "restricted", "ephemeral"];
|
|
19
|
+
export declare const API_KEY_KINDS: readonly ["secret", "restricted", "ephemeral", "publishable"];
|
|
20
20
|
export type ApiKeyKind = (typeof API_KEY_KINDS)[number];
|
|
21
21
|
export declare const API_KEY_ENVS: readonly ["live", "test"];
|
|
22
22
|
export type ApiKeyEnv = (typeof API_KEY_ENVS)[number];
|
|
@@ -59,3 +59,18 @@ export declare function generateApiKey(env?: ApiKeyEnv, kind?: ApiKeyKind): {
|
|
|
59
59
|
* defend against. Used at both write (mint) and lookup.
|
|
60
60
|
*/
|
|
61
61
|
export declare function hashApiKey(plaintext: string): string;
|
|
62
|
+
/** `whsec_` label prefix per the Standard Webhooks spec (not part of the key material). */
|
|
63
|
+
export declare const WEBHOOK_SECRET_PREFIX = "whsec_";
|
|
64
|
+
/**
|
|
65
|
+
* Mint a webhook signing secret per the Standard Webhooks spec
|
|
66
|
+
* (https://www.standardwebhooks.com): a base64-encoded random key, 24–64 bytes,
|
|
67
|
+
* labelled with the `whsec_` prefix. We use 32 bytes (256 bits) — comfortably
|
|
68
|
+
* inside the range and matching Stripe/Svix. Unlike an API key this is NOT
|
|
69
|
+
* hashed at rest: signing (`signAbloSourceRequest`) needs the live key, so it is
|
|
70
|
+
* stored by reference via the secret store, returned to the customer once at
|
|
71
|
+
* creation, and never echoed again (Stripe's policy).
|
|
72
|
+
*/
|
|
73
|
+
export declare function generateWebhookSecret(): {
|
|
74
|
+
plaintext: string;
|
|
75
|
+
last4: string;
|
|
76
|
+
};
|
package/dist/keys/index.js
CHANGED
|
@@ -18,24 +18,29 @@
|
|
|
18
18
|
import { createHash, randomBytes } from 'node:crypto';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
// ── Vocabulary ──────────────────────────────────────────────────────────
|
|
21
|
-
// The
|
|
21
|
+
// The Stripe-style key model:
|
|
22
22
|
// secret (sk_) — backend / server-to-server / agents. Full authority. Never in a browser.
|
|
23
23
|
// restricted (rk_) — scoped SERVER key (agent session tokens / capabilities).
|
|
24
24
|
// ephemeral (ek_) — short-lived, backend-minted, USER-scoped BROWSER session credential
|
|
25
25
|
// (Stripe ephemeral keys). Carries participantKind:'user' + baked syncGroups.
|
|
26
|
-
// (
|
|
27
|
-
//
|
|
28
|
-
|
|
26
|
+
// publishable (pk_) — long-lived, browser-safe, org-scoped, READ-ONLY project key
|
|
27
|
+
// (Stripe `pk_` / Supabase anon key). Used DIRECTLY as the bearer
|
|
28
|
+
// (never exchanged, never expires → nothing to refresh). The org owns
|
|
29
|
+
// it; it grants read access to the org's data plane and cannot write
|
|
30
|
+
// or reach any control-plane operation.
|
|
31
|
+
export const API_KEY_KINDS = ['secret', 'restricted', 'ephemeral', 'publishable'];
|
|
29
32
|
export const API_KEY_ENVS = ['live', 'test'];
|
|
30
33
|
const PREFIX_BY_KIND = {
|
|
31
34
|
secret: 'sk',
|
|
32
35
|
restricted: 'rk',
|
|
33
36
|
ephemeral: 'ek',
|
|
37
|
+
publishable: 'pk',
|
|
34
38
|
};
|
|
35
39
|
const KIND_BY_PREFIX = {
|
|
36
40
|
sk: 'secret',
|
|
37
41
|
rk: 'restricted',
|
|
38
42
|
ek: 'ephemeral',
|
|
43
|
+
pk: 'publishable',
|
|
39
44
|
};
|
|
40
45
|
const BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
41
46
|
/** Random base62 chars before the checksum. */
|
|
@@ -44,8 +49,8 @@ const KEY_BODY_LEN = 30;
|
|
|
44
49
|
const CHECKSUM_LEN = 6;
|
|
45
50
|
/** A new checksummed body is exactly this long and pure base62. */
|
|
46
51
|
const CHECKSUMMED_BODY_LEN = KEY_BODY_LEN + CHECKSUM_LEN;
|
|
47
|
-
/** `<sk|rk|ek>_<live|test>_<body>`; body charset covers base62 AND legacy base64url. */
|
|
48
|
-
const KEY_RE = /^(sk|rk|ek)_(live|test)_([0-9A-Za-z\-_]+)$/;
|
|
52
|
+
/** `<sk|rk|ek|pk>_<live|test>_<body>`; body charset covers base62 AND legacy base64url. */
|
|
53
|
+
const KEY_RE = /^(sk|rk|ek|pk)_(live|test)_([0-9A-Za-z\-_]+)$/;
|
|
49
54
|
const BASE62_RE = /^[0-9A-Za-z]+$/;
|
|
50
55
|
// ── Checksum (standard CRC-32, GitHub-compatible) ───────────────────────
|
|
51
56
|
const CRC32_TABLE = (() => {
|
|
@@ -149,3 +154,18 @@ export function generateApiKey(env = 'live', kind = 'secret') {
|
|
|
149
154
|
export function hashApiKey(plaintext) {
|
|
150
155
|
return createHash('sha256').update(plaintext).digest('hex');
|
|
151
156
|
}
|
|
157
|
+
/** `whsec_` label prefix per the Standard Webhooks spec (not part of the key material). */
|
|
158
|
+
export const WEBHOOK_SECRET_PREFIX = 'whsec_';
|
|
159
|
+
/**
|
|
160
|
+
* Mint a webhook signing secret per the Standard Webhooks spec
|
|
161
|
+
* (https://www.standardwebhooks.com): a base64-encoded random key, 24–64 bytes,
|
|
162
|
+
* labelled with the `whsec_` prefix. We use 32 bytes (256 bits) — comfortably
|
|
163
|
+
* inside the range and matching Stripe/Svix. Unlike an API key this is NOT
|
|
164
|
+
* hashed at rest: signing (`signAbloSourceRequest`) needs the live key, so it is
|
|
165
|
+
* stored by reference via the secret store, returned to the customer once at
|
|
166
|
+
* creation, and never echoed again (Stripe's policy).
|
|
167
|
+
*/
|
|
168
|
+
export function generateWebhookSecret() {
|
|
169
|
+
const plaintext = `${WEBHOOK_SECRET_PREFIX}${randomBytes(32).toString('base64')}`;
|
|
170
|
+
return { plaintext, last4: plaintext.slice(-4) };
|
|
171
|
+
}
|
|
@@ -19,56 +19,38 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { Schema } from '../schema/schema.js';
|
|
21
21
|
import type { SyncStoreContract } from '../react/context.js';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* is schema-agnostic — the transaction it replays through is schema-typed.
|
|
27
|
-
*/
|
|
28
|
-
export type InverseOp = {
|
|
29
|
-
kind: 'create';
|
|
30
|
-
modelKey: string;
|
|
31
|
-
data: Record<string, unknown>;
|
|
32
|
-
} | {
|
|
33
|
-
kind: 'update';
|
|
34
|
-
modelKey: string;
|
|
35
|
-
patch: {
|
|
36
|
-
id: string;
|
|
37
|
-
} & Record<string, unknown>;
|
|
38
|
-
} | {
|
|
39
|
-
kind: 'delete';
|
|
40
|
-
modelKey: string;
|
|
41
|
-
id: string;
|
|
42
|
-
} | {
|
|
43
|
-
kind: 'createMany';
|
|
44
|
-
modelKey: string;
|
|
45
|
-
data: Record<string, unknown>[];
|
|
46
|
-
} | {
|
|
47
|
-
kind: 'updateMany';
|
|
48
|
-
modelKey: string;
|
|
49
|
-
patches: Array<{
|
|
50
|
-
id: string;
|
|
51
|
-
} & Record<string, unknown>>;
|
|
52
|
-
} | {
|
|
53
|
-
kind: 'deleteMany';
|
|
54
|
-
modelKey: string;
|
|
55
|
-
ids: string[];
|
|
56
|
-
};
|
|
57
|
-
/** One undo entry = one mutator invocation's set of inverses, in reverse order. */
|
|
58
|
-
export interface UndoEntry {
|
|
59
|
-
/** Optional label for diagnostics / UI ("Move layer", "Delete slide", etc). */
|
|
60
|
-
label?: string;
|
|
61
|
-
inverses: InverseOp[];
|
|
62
|
-
/**
|
|
63
|
-
* Paired forward ops, captured at record time so redo can replay them
|
|
64
|
-
* without re-running the user's mutator (which may have non-idempotent
|
|
65
|
-
* side effects like generating new IDs).
|
|
66
|
-
*/
|
|
67
|
-
forwards: InverseOp[];
|
|
68
|
-
}
|
|
22
|
+
import { type InverseOp, type UndoEntry } from './inverseOp.js';
|
|
23
|
+
import { type UndoConflictPolicy } from './undoApply.js';
|
|
24
|
+
export type { InverseOp, UndoEntry };
|
|
25
|
+
export type { UndoConflictPolicy } from './undoApply.js';
|
|
69
26
|
export interface UndoScopeOptions {
|
|
70
27
|
/** Max number of undo entries. Older entries drop off the bottom. Default: 100. */
|
|
71
28
|
maxHistory?: number;
|
|
29
|
+
/**
|
|
30
|
+
* How undo/redo treats a field a collaborator changed after your op.
|
|
31
|
+
* Default `skip-stale` — your undo reverts your change only where it still
|
|
32
|
+
* stands, never clobbering a concurrent collaborator edit (per-user undo).
|
|
33
|
+
* `last-writer-wins` restores the legacy clobbering behavior. See
|
|
34
|
+
* {@link UndoConflictPolicy}.
|
|
35
|
+
*/
|
|
36
|
+
conflictPolicy?: UndoConflictPolicy;
|
|
37
|
+
/**
|
|
38
|
+
* Which models this surface owns. The scope only records mutations whose
|
|
39
|
+
* resolved schema key passes this predicate, so a spreadsheet edit never
|
|
40
|
+
* lands on the deck editor's stack (the equivalent of Yjs scoping by
|
|
41
|
+
* shared-type set). Omit to track every model — fine for a single-surface
|
|
42
|
+
* app, wrong when two surfaces with independent Cmd+Z share one store.
|
|
43
|
+
*/
|
|
44
|
+
tracksModel?: (schemaKey: string) => boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Opt into recording undo entries by OBSERVING the local-mutation stream
|
|
47
|
+
* (the best-practice model: undo listens where all local writes converge —
|
|
48
|
+
* Yjs/Liveblocks). When false (default), the scope records nothing on its
|
|
49
|
+
* own and relies on legacy manual `record()` calls. Transitional: a scope
|
|
50
|
+
* must not mix the two, or shared writes double-count. Flip a surface to
|
|
51
|
+
* `true` only when its manual-record consumers are removed in the same step.
|
|
52
|
+
*/
|
|
53
|
+
recordFromStream?: boolean;
|
|
72
54
|
}
|
|
73
55
|
/**
|
|
74
56
|
* A single undo stack for one surface. Access via `UndoManager.getScope(name)`.
|
|
@@ -82,14 +64,134 @@ export declare class UndoScope<S extends Schema> {
|
|
|
82
64
|
private undoStack;
|
|
83
65
|
private redoStack;
|
|
84
66
|
private readonly maxHistory;
|
|
67
|
+
private readonly conflictPolicy;
|
|
68
|
+
/**
|
|
69
|
+
* Observers notified after each successful {@link record}. These see FORWARD
|
|
70
|
+
* user actions only — `undo()`/`redo()` replays move entries between stacks
|
|
71
|
+
* without calling `record()`, so a listener never observes a reversal. This
|
|
72
|
+
* is a deliberately domain-agnostic seam: analytics, gamification, and audit
|
|
73
|
+
* can tap the committed-mutation stream without the scope knowing about them.
|
|
74
|
+
* A throwing listener is isolated (see {@link emitRecord}) so a faulty
|
|
75
|
+
* observer can never wedge the editor's recording path.
|
|
76
|
+
*/
|
|
77
|
+
private readonly recordListeners;
|
|
78
|
+
/**
|
|
79
|
+
* Serialization tail. Recording, undo, and redo all chain off this single
|
|
80
|
+
* promise so they run strictly in the order they were *invoked* — never
|
|
81
|
+
* interleaved. This is load-bearing for correctness, not just throughput:
|
|
82
|
+
* - Ordering: callers fire writes un-awaited (`void mutations.x.update`).
|
|
83
|
+
* Without serialization, an entry lands on the stack when its mutator
|
|
84
|
+
* *resolves*, so a fast second write can record before a slow first one
|
|
85
|
+
* → undo replays in the wrong order.
|
|
86
|
+
* - Snapshot integrity: every recording reads/clears the shared models'
|
|
87
|
+
* `modifiedProperties` (the undo "before" baseline). Two recordings
|
|
88
|
+
* interleaving on the same model corrupt each other's inverse snapshot.
|
|
89
|
+
* Serializing the whole scope closes both holes with one mechanism.
|
|
90
|
+
*/
|
|
91
|
+
private tail;
|
|
92
|
+
/** Predicate selecting which models this surface records (see options). */
|
|
93
|
+
private readonly tracksModel?;
|
|
94
|
+
/** registered-name / alias → schema key, built once from the schema. */
|
|
95
|
+
private readonly schemaKeyByAlias;
|
|
96
|
+
/** Unsubscribe from the local-mutation stream. */
|
|
97
|
+
private readonly unsubscribe;
|
|
98
|
+
/**
|
|
99
|
+
* True while `undo()`/`redo()` replays ops. Replays write through the same
|
|
100
|
+
* commit path, so they re-emit on the local-mutation stream; this flag tells
|
|
101
|
+
* our own listener to ignore them (no echo) — the engine equivalent of Yjs's
|
|
102
|
+
* `trackedOrigins` exclusion / Liveblocks pausing history during undo.
|
|
103
|
+
*/
|
|
104
|
+
private replaying;
|
|
105
|
+
/** Ops collected during the current tick, flushed as ONE entry. */
|
|
106
|
+
private batch;
|
|
107
|
+
private flushScheduled;
|
|
108
|
+
/**
|
|
109
|
+
* Open grouping session (Liveblocks `history.pause()` / Yjs `stopCapturing`
|
|
110
|
+
* analogue). While set, stream ops accumulate here ACROSS ticks instead of
|
|
111
|
+
* flushing per-tick, so a multi-tick action (a drag, a whole streaming AI
|
|
112
|
+
* response) collapses into ONE Cmd+Z. `endGroup()` flushes it.
|
|
113
|
+
*/
|
|
114
|
+
private group;
|
|
85
115
|
constructor(schema: S, store: SyncStoreContract, organizationId: string, options?: UndoScopeOptions);
|
|
86
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Open a grouping session: every stream-recorded op until {@link endGroup}
|
|
118
|
+
* collapses into a single undo entry. Mirrors Liveblocks `history.pause()` —
|
|
119
|
+
* call on gesture start (pointerdown) or AI-response start. Idempotent-ish:
|
|
120
|
+
* a second call closes the previous group first.
|
|
121
|
+
*/
|
|
122
|
+
beginGroup(label?: string): void;
|
|
123
|
+
/** Close the grouping session and record the accumulated ops as one entry. */
|
|
124
|
+
endGroup(label?: string): void;
|
|
125
|
+
/** Resolve a stream mutation's registered name to its schema key, or null. */
|
|
126
|
+
private resolveSchemaKey;
|
|
127
|
+
/**
|
|
128
|
+
* Stream listener — the sole place entries are born. Skips replay echoes
|
|
129
|
+
* and out-of-scope models, derives the forward+inverse op from the
|
|
130
|
+
* mutation's `data`/`previousData`, and defers the stack push to a
|
|
131
|
+
* per-tick flush so a burst of writes (e.g. align 5 layers) becomes ONE
|
|
132
|
+
* undo step — riding the same tick boundary the TransactionQueue batches on.
|
|
133
|
+
*/
|
|
134
|
+
private onLocalMutation;
|
|
135
|
+
private scheduleFlush;
|
|
136
|
+
/** Coalesce the tick's collected ops into one entry and record it. */
|
|
137
|
+
private flushBatch;
|
|
138
|
+
/**
|
|
139
|
+
* Run `work` after every previously-enqueued scope operation has settled,
|
|
140
|
+
* in invocation order. The internal `tail` always resolves (failures are
|
|
141
|
+
* swallowed *for the chain only*) so one rejected mutator can't wedge the
|
|
142
|
+
* queue; the original settlement is still surfaced to this call's caller.
|
|
143
|
+
*/
|
|
144
|
+
private enqueue;
|
|
145
|
+
/**
|
|
146
|
+
* Run a recording mutator exclusively on the scope's serialization chain.
|
|
147
|
+
* Used by the legacy manual-record path (`useMutators` + `RecordingTransaction`)
|
|
148
|
+
* so the snapshot → write → `record()` sequence is atomic relative to undo/
|
|
149
|
+
* redo. The stream-recording path doesn't need this (it derives entries from
|
|
150
|
+
* already-committed mutations); kept until all surfaces migrate off manual.
|
|
151
|
+
*/
|
|
152
|
+
runRecorded<T>(work: () => Promise<T>): Promise<T>;
|
|
153
|
+
/**
|
|
154
|
+
* Record one entry onto the undo stack. Clears the redo stack. Fed by
|
|
155
|
+
* {@link flushBatch}/{@link endGroup} from the local-mutation stream, and
|
|
156
|
+
* still called directly by the legacy manual-record consumers
|
|
157
|
+
* (`useMutators`, the AI mutation pipeline) until they migrate. Entries are
|
|
158
|
+
* built internally (trusted), so the schema check is DEV-ONLY: it catches
|
|
159
|
+
* recorder bugs in dev/test (rejecting a malformed op at ingestion, with its
|
|
160
|
+
* path, instead of letting it crash later inside `applyOps`) without paying a
|
|
161
|
+
* Zod parse on every user action in production. The real validation boundary
|
|
162
|
+
* is `parseUndoEntry`, applied when entries are deserialized from persistence
|
|
163
|
+
* (untrusted input). Best practice: validate at trust boundaries, type-check
|
|
164
|
+
* internal calls.
|
|
165
|
+
*/
|
|
87
166
|
record(entry: UndoEntry): void;
|
|
167
|
+
/**
|
|
168
|
+
* Subscribe to every recorded mutation. Fires synchronously at the tail of
|
|
169
|
+
* each {@link record} call, after the entry is on the undo stack. Returns an
|
|
170
|
+
* unsubscribe function — call it on teardown.
|
|
171
|
+
*
|
|
172
|
+
* Listeners receive the full {@link UndoEntry} (its `forwards` carry the
|
|
173
|
+
* `{ kind, modelKey, data }` ops), so a consumer can derive what changed
|
|
174
|
+
* (e.g. "a slideLayers row of type 'chart' was created") without re-querying.
|
|
175
|
+
*/
|
|
176
|
+
onRecord(listener: (entry: UndoEntry) => void): () => void;
|
|
177
|
+
private emitRecord;
|
|
88
178
|
canUndo(): boolean;
|
|
89
179
|
canRedo(): boolean;
|
|
90
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Pop the last mutator and apply its inverses. Pushes to redo.
|
|
182
|
+
*
|
|
183
|
+
* Under the default `skip-stale` policy the inverses are filtered against
|
|
184
|
+
* live state first (paired with the entry's forwards = "what I set"), so a
|
|
185
|
+
* field a collaborator changed after my op is left untouched — undo reverts
|
|
186
|
+
* my change only where it still stands.
|
|
187
|
+
*/
|
|
91
188
|
undo(): Promise<void>;
|
|
92
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Pop the last undone entry and re-apply the forward ops. Pushes to undo.
|
|
191
|
+
* Symmetric to {@link undo}: forwards are filtered against live state
|
|
192
|
+
* (paired with the entry's inverses = "what undo restored"), so redo
|
|
193
|
+
* re-asserts my change only where the undone value still stands.
|
|
194
|
+
*/
|
|
93
195
|
redo(): Promise<void>;
|
|
94
196
|
/** Drop all history. Use after bootstrap / sync group change / sync error. */
|
|
95
197
|
clear(): void;
|
|
@@ -98,6 +200,12 @@ export declare class UndoScope<S extends Schema> {
|
|
|
98
200
|
undo: number;
|
|
99
201
|
redo: number;
|
|
100
202
|
};
|
|
203
|
+
/**
|
|
204
|
+
* Detach from the local-mutation stream and drop listeners. Scopes are
|
|
205
|
+
* cached for the store's lifetime by `UndoManager`, so this is mainly for
|
|
206
|
+
* tests and explicit teardown.
|
|
207
|
+
*/
|
|
208
|
+
dispose(): void;
|
|
101
209
|
}
|
|
102
210
|
/**
|
|
103
211
|
* Central registry of named undo scopes. One per-app instance, created once
|