@abloatai/ablo 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +40 -0
- package/README.md +53 -27
- package/dist/BaseSyncedStore.d.ts +2 -36
- package/dist/BaseSyncedStore.js +11 -55
- package/dist/NetworkMonitor.js +4 -1
- package/dist/SyncClient.d.ts +22 -5
- package/dist/SyncClient.js +77 -0
- package/dist/SyncEngineContext.js +5 -1
- package/dist/agent/index.js +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/auth/index.js +3 -1
- package/dist/cli.cjs +302645 -0
- package/dist/client/Ablo.d.ts +19 -52
- package/dist/client/Ablo.js +30 -106
- package/dist/client/ApiClient.d.ts +1 -113
- package/dist/client/ApiClient.js +39 -238
- package/dist/client/auth.js +32 -2
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.d.ts +9 -0
- package/dist/client/createModelProxy.js +34 -10
- package/dist/client/httpClient.d.ts +5 -6
- package/dist/client/httpClient.js +2 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/client/persistence.d.ts +6 -1
- package/dist/client/persistence.js +1 -1
- package/dist/client/registerDataSource.d.ts +4 -4
- package/dist/client/registerDataSource.js +39 -31
- package/dist/client/writeOptionsSchema.d.ts +50 -0
- package/dist/client/writeOptionsSchema.js +57 -0
- package/dist/core/index.d.ts +18 -26
- package/dist/core/index.js +22 -46
- package/dist/errorCodes.d.ts +13 -0
- package/dist/errorCodes.js +19 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -1
- package/dist/interfaces/index.d.ts +14 -4
- package/dist/mutators/UndoManager.d.ts +48 -5
- package/dist/mutators/UndoManager.js +166 -1
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/useUndoScope.js +7 -0
- package/dist/schema/ddl.js +2 -1
- package/dist/schema/field.js +2 -1
- package/dist/schema/serialize.js +2 -1
- package/dist/server/commit.d.ts +4 -5
- package/dist/server/storage-mode.d.ts +7 -0
- package/dist/server/storage-mode.js +6 -0
- package/dist/source/adapters/drizzle.js +3 -2
- package/dist/source/adapters/kysely.d.ts +68 -0
- package/dist/source/adapters/kysely.js +210 -0
- package/dist/source/adapters/memory.js +2 -1
- package/dist/source/adapters/prisma.js +3 -2
- package/dist/source/index.js +2 -1
- package/dist/transactions/TransactionQueue.d.ts +6 -7
- package/dist/transactions/TransactionQueue.js +33 -9
- package/dist/types/streams.d.ts +2 -1
- package/dist/utils/duration.js +3 -2
- package/dist/wire/frames.d.ts +6 -8
- package/docs/api.md +1 -1
- package/docs/cli.md +17 -4
- package/docs/client-behavior.md +1 -1
- package/docs/data-sources.md +129 -125
- package/docs/examples/ai-sdk-tool.md +11 -5
- package/docs/examples/existing-python-backend.md +26 -4
- package/docs/examples/nextjs.md +3 -2
- package/docs/examples/scoped-agent.md +38 -11
- package/docs/guarantees.md +2 -2
- package/docs/identity.md +86 -59
- package/docs/index.md +2 -2
- package/docs/integration-guide.md +89 -61
- package/docs/mcp.md +1 -1
- package/docs/quickstart.md +84 -37
- package/docs/react.md +39 -28
- package/docs/schema-contract.md +2 -4
- package/llms-full.txt +360 -0
- package/llms.txt +30 -18
- package/package.json +23 -3
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Self-serve direct-
|
|
3
|
-
*
|
|
4
|
-
* Historical note: this module name says "DataSource", but this path registers
|
|
5
|
-
* a direct database URL. It is not the signed `dataSource(...)` endpoint path.
|
|
2
|
+
* Self-serve direct-kind datasource registration.
|
|
6
3
|
*
|
|
7
4
|
* When a client is constructed with `databaseUrl`, the SDK registers that
|
|
8
5
|
* connection string BEFORE bootstrap so the server resolves the org's data plane
|
|
9
|
-
* to that direct
|
|
6
|
+
* to that direct connection.
|
|
7
|
+
*
|
|
8
|
+
* Targets the unified `POST /v1/datasources` resource; on a 404 (an older
|
|
9
|
+
* server without the unified route) it falls back to the legacy
|
|
10
|
+
* `POST /v1/datasource` alias so an SDK upgrade never strands registration.
|
|
10
11
|
*
|
|
11
12
|
* The org is derived server-side from the API key — the caller never sends an
|
|
12
13
|
* organization id. The connection string is sent once over TLS and is never
|
|
@@ -15,36 +16,43 @@
|
|
|
15
16
|
*/
|
|
16
17
|
import { AbloError } from '../errors.js';
|
|
17
18
|
/**
|
|
18
|
-
* POST the connection string to the self-serve
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* POST the connection string to the self-serve datasource route. Resolves on
|
|
20
|
+
* success (the org's data plane now points at this DB); throws an `AbloError`
|
|
21
|
+
* with `datasource_registration_failed` otherwise so `ready()` surfaces it
|
|
22
|
+
* instead of silently bootstrapping against the wrong store.
|
|
22
23
|
*/
|
|
23
24
|
export async function registerDataSource(input) {
|
|
24
25
|
if (!input.apiKey) {
|
|
25
|
-
throw new AbloError('databaseUrl requires an apiKey to register the
|
|
26
|
+
throw new AbloError('databaseUrl requires an apiKey to register the database connection (the org is derived from the key).', { code: 'datasource_registration_failed' });
|
|
26
27
|
}
|
|
27
28
|
const doFetch = input.fetchImpl ?? fetch;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
29
|
+
const base = input.baseUrl.replace(/\/+$/, '');
|
|
30
|
+
const body = JSON.stringify({
|
|
31
|
+
connectionString: input.databaseUrl,
|
|
32
|
+
...(input.schema ? { schema: input.schema } : {}),
|
|
33
|
+
});
|
|
34
|
+
const post = async (endpoint) => {
|
|
35
|
+
try {
|
|
36
|
+
return await doFetch(endpoint, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'content-type': 'application/json',
|
|
40
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
41
|
+
},
|
|
42
|
+
body,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (cause) {
|
|
46
|
+
throw new AbloError('Could not reach the Ablo API to register the database connection.', {
|
|
47
|
+
code: 'datasource_registration_failed',
|
|
48
|
+
cause,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
let response = await post(`${base}/v1/datasources`);
|
|
53
|
+
if (response.status === 404) {
|
|
54
|
+
// Older server without the unified resource — use the legacy alias.
|
|
55
|
+
response = await post(`${base}/v1/datasource`);
|
|
48
56
|
}
|
|
49
57
|
if (!response.ok) {
|
|
50
58
|
let detail = '';
|
|
@@ -54,6 +62,6 @@ export async function registerDataSource(input) {
|
|
|
54
62
|
catch {
|
|
55
63
|
// ignore body read failures — the status alone is enough to fail loud
|
|
56
64
|
}
|
|
57
|
-
throw new AbloError(`
|
|
65
|
+
throw new AbloError(`Database connection registration failed (HTTP ${response.status}). ${detail}`, { code: 'datasource_registration_failed', httpStatus: response.status });
|
|
58
66
|
}
|
|
59
67
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE write-options schema — one Zod schema for the write dialect every
|
|
3
|
+
* door speaks (`ablo.<model>.create/update/delete`, `commits.create`, the
|
|
4
|
+
* HTTP model routes). Validated once at each public boundary so a plain-JS
|
|
5
|
+
* caller passing `onStale: 'rejct'` fails loudly at the call site with a
|
|
6
|
+
* typed `AbloValidationError`, not silently (or 400) at the server.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors `source/contract.ts`: the schema is the runtime twin of the
|
|
9
|
+
* `MutationOptions` interface, with a compile-time drift guard at the
|
|
10
|
+
* bottom so the two can never silently diverge.
|
|
11
|
+
*
|
|
12
|
+
* Validation-only by design: callers keep their ORIGINAL options object.
|
|
13
|
+
* Zod's parse output strips unknown keys, and the `intent` slot legally
|
|
14
|
+
* carries live handles (`IntentLeaseHandle` / claim leases) whose
|
|
15
|
+
* `release`/`revoke` functions must survive — so we assert, never replace.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
export declare const onStaleModeSchema: z.ZodEnum<{
|
|
19
|
+
reject: "reject";
|
|
20
|
+
force: "force";
|
|
21
|
+
flag: "flag";
|
|
22
|
+
merge: "merge";
|
|
23
|
+
}>;
|
|
24
|
+
export declare const writeOptionsSchema: z.ZodObject<{
|
|
25
|
+
idempotencyKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
26
|
+
label: z.ZodOptional<z.ZodString>;
|
|
27
|
+
wait: z.ZodOptional<z.ZodEnum<{
|
|
28
|
+
confirmed: "confirmed";
|
|
29
|
+
queued: "queued";
|
|
30
|
+
}>>;
|
|
31
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
32
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
33
|
+
reject: "reject";
|
|
34
|
+
force: "force";
|
|
35
|
+
flag: "flag";
|
|
36
|
+
merge: "merge";
|
|
37
|
+
}>>>;
|
|
38
|
+
intent: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
39
|
+
id: z.ZodString;
|
|
40
|
+
}, z.core.$loose>]>>>;
|
|
41
|
+
causedByTaskId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type WriteOptionsInput = z.infer<typeof writeOptionsSchema>;
|
|
44
|
+
/**
|
|
45
|
+
* Assert a write-options bag against THE schema. Throws a typed
|
|
46
|
+
* `AbloValidationError` (`code: 'write_options_invalid'`, Stripe-style
|
|
47
|
+
* `param` pointing at the offending field) and returns nothing — the
|
|
48
|
+
* caller keeps its original object.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertWriteOptions(value: unknown, context?: string): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE write-options schema — one Zod schema for the write dialect every
|
|
3
|
+
* door speaks (`ablo.<model>.create/update/delete`, `commits.create`, the
|
|
4
|
+
* HTTP model routes). Validated once at each public boundary so a plain-JS
|
|
5
|
+
* caller passing `onStale: 'rejct'` fails loudly at the call site with a
|
|
6
|
+
* typed `AbloValidationError`, not silently (or 400) at the server.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors `source/contract.ts`: the schema is the runtime twin of the
|
|
9
|
+
* `MutationOptions` interface, with a compile-time drift guard at the
|
|
10
|
+
* bottom so the two can never silently diverge.
|
|
11
|
+
*
|
|
12
|
+
* Validation-only by design: callers keep their ORIGINAL options object.
|
|
13
|
+
* Zod's parse output strips unknown keys, and the `intent` slot legally
|
|
14
|
+
* carries live handles (`IntentLeaseHandle` / claim leases) whose
|
|
15
|
+
* `release`/`revoke` functions must survive — so we assert, never replace.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { AbloValidationError } from '../errors.js';
|
|
19
|
+
export const onStaleModeSchema = z.enum(['reject', 'force', 'flag', 'merge']);
|
|
20
|
+
export const writeOptionsSchema = z.object({
|
|
21
|
+
/** Server-side mutation_log cache key; `null` opts out of retry-safety. */
|
|
22
|
+
idempotencyKey: z.string().min(1).max(255).nullish(),
|
|
23
|
+
/** Human-readable audit tag, persisted to `mutation_log.label`. */
|
|
24
|
+
label: z.string().max(255).optional(),
|
|
25
|
+
/** Resolve when queued locally (default) or once the server confirms. */
|
|
26
|
+
wait: z.enum(['queued', 'confirmed']).optional(),
|
|
27
|
+
/** Stale guard: the sync watermark the caller's reasoning was based on. */
|
|
28
|
+
readAt: z.number().int().nonnegative().nullish(),
|
|
29
|
+
/** What the server does when the target moved past `readAt`. */
|
|
30
|
+
onStale: onStaleModeSchema.nullish(),
|
|
31
|
+
/** Claim/intent attribution — an id, or a live lease handle (loose: the
|
|
32
|
+
* handle's `release`/`revoke` functions ride along untouched). */
|
|
33
|
+
intent: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
|
|
34
|
+
/** Dormant wire-compat field; always `null` from current clients. */
|
|
35
|
+
causedByTaskId: z.string().nullish(),
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Assert a write-options bag against THE schema. Throws a typed
|
|
39
|
+
* `AbloValidationError` (`code: 'write_options_invalid'`, Stripe-style
|
|
40
|
+
* `param` pointing at the offending field) and returns nothing — the
|
|
41
|
+
* caller keeps its original object.
|
|
42
|
+
*/
|
|
43
|
+
export function assertWriteOptions(value, context) {
|
|
44
|
+
if (value == null)
|
|
45
|
+
return;
|
|
46
|
+
const result = writeOptionsSchema.safeParse(value);
|
|
47
|
+
if (result.success)
|
|
48
|
+
return;
|
|
49
|
+
const issue = result.error.issues[0];
|
|
50
|
+
const path = issue?.path.map(String).join('.') ?? '';
|
|
51
|
+
throw new AbloValidationError(`Invalid write options${context ? ` on \`${context}\`` : ''}${path ? ` at \`${path}\`` : ''}: ${issue?.message ?? 'failed validation'}.`, {
|
|
52
|
+
code: 'write_options_invalid',
|
|
53
|
+
...(path ? { param: path } : {}),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const _writeOptionsContractInSync = [true, true];
|
|
57
|
+
void _writeOptionsContractInSync;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @abloatai/ablo/core — Framework extension
|
|
3
3
|
*
|
|
4
|
-
* Only imported by
|
|
5
|
-
* the
|
|
6
|
-
* Regular model files and components should NOT import from
|
|
4
|
+
* Only imported by the handful of files that extend or orchestrate the
|
|
5
|
+
* sync engine (the app-shell store/provider stack, sync adapters, demo
|
|
6
|
+
* harnesses). Regular model files and components should NOT import from
|
|
7
|
+
* here — the consumer surface is `Ablo({ schema })` on the root.
|
|
8
|
+
*
|
|
9
|
+
* TRIMMED to what framework-level consumers actually import (verified by
|
|
10
|
+
* a monorepo-wide import scan). Everything else the engine defines stays
|
|
11
|
+
* module-private: if a new framework concern genuinely needs another
|
|
12
|
+
* primitive, add the export deliberately — don't re-widen the barrel.
|
|
7
13
|
*/
|
|
8
|
-
export { BaseSyncedStore, type
|
|
9
|
-
export { SyncClient
|
|
10
|
-
export { Database
|
|
14
|
+
export { BaseSyncedStore, type ModelConstructor, type ConcreteModelConstructor, } from '../BaseSyncedStore.js';
|
|
15
|
+
export { SyncClient } from '../SyncClient.js';
|
|
16
|
+
export { Database } from '../Database.js';
|
|
11
17
|
export { ObjectPool, ModelScope } from '../ObjectPool.js';
|
|
12
18
|
export { Model } from '../Model.js';
|
|
13
|
-
export { LazyReferenceCollection, type LazyCollectionOptions } from '../LazyReferenceCollection.js';
|
|
19
|
+
export { LazyReferenceCollection, type LazyCollectionOptions, } from '../LazyReferenceCollection.js';
|
|
20
|
+
export { ModelRegistry, getActiveRegistry, } from '../ModelRegistry.js';
|
|
14
21
|
export { postQuery, type PostQueryOptions } from '../query/client.js';
|
|
15
|
-
export { probeNetwork, type ProbeResult } from '../sync/NetworkProbe.js';
|
|
16
|
-
export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '../sync/ConnectionManager.js';
|
|
17
|
-
export { ModelRegistry, getActiveRegistry, type ExtendedReferenceMetadata, type BackReferenceMetadata } from '../ModelRegistry.js';
|
|
18
22
|
export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo.js';
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export type { SyncEngineConfig, SyncLogger, SyncObservabilityProvider, SyncAnalytics, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, BreadcrumbLevel, SyncBreadcrumbCategory, TransactionFailureDetails, BootstrapFailureDetails, WebSocketErrorDetails, RollbackDetails, SpanAttributes, } from '../interfaces/index.js';
|
|
23
|
-
export { SyncSessionError } from '../errors.js';
|
|
24
|
-
export { QueryProcessor } from './QueryProcessor.js';
|
|
25
|
-
export { QueryView, type QueryViewOptions } from './QueryView.js';
|
|
26
|
-
export { ViewRegistry } from './ViewRegistry.js';
|
|
27
|
-
export { ObjectStore } from '../stores/ObjectStore.js';
|
|
28
|
-
export { NetworkMonitor } from '../NetworkMonitor.js';
|
|
29
|
-
export { SyncWebSocket, type SyncDelta, type VersionVector, type BootstrapHint, type SyncGroupChangePayload, type BootstrapDataEvent, type PresenceUpdateEvent, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
|
|
30
|
-
export { BootstrapHelper, type BootstrapData, type BootstrapOptions, type BootstrapFetchResult } from '../sync/BootstrapHelper.js';
|
|
23
|
+
export type { SyncLogger, SyncObservabilityProvider, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, } from '../interfaces/index.js';
|
|
24
|
+
export { SyncWebSocket, type SyncDelta, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
|
|
25
|
+
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
31
26
|
export { createIntentStream, type AttachableIntentStream, type IntentStreamConfig, } from '../sync/createIntentStream.js';
|
|
32
27
|
export { awaitIntentGrant, type GrantTransport, } from '../sync/awaitIntentGrant.js';
|
|
33
|
-
export {
|
|
34
|
-
export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
|
|
35
|
-
export type { PropertyMetadata, ReferenceMetadata, ModelMetadata, SyncAction, DeltaPacket, BootstrapMetadata, DatabaseMetadata, } from '../types/index.js';
|
|
36
|
-
export type { ModelData } from '../BaseSyncedStore.js';
|
|
28
|
+
export { LoadStrategy } from '../types/index.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,52 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @abloatai/ablo/core — Framework extension
|
|
3
3
|
*
|
|
4
|
-
* Only imported by
|
|
5
|
-
* the
|
|
6
|
-
* Regular model files and components should NOT import from
|
|
4
|
+
* Only imported by the handful of files that extend or orchestrate the
|
|
5
|
+
* sync engine (the app-shell store/provider stack, sync adapters, demo
|
|
6
|
+
* harnesses). Regular model files and components should NOT import from
|
|
7
|
+
* here — the consumer surface is `Ablo({ schema })` on the root.
|
|
8
|
+
*
|
|
9
|
+
* TRIMMED to what framework-level consumers actually import (verified by
|
|
10
|
+
* a monorepo-wide import scan). Everything else the engine defines stays
|
|
11
|
+
* module-private: if a new framework concern genuinely needs another
|
|
12
|
+
* primitive, add the export deliberately — don't re-widen the barrel.
|
|
7
13
|
*/
|
|
8
|
-
// Base store class
|
|
9
|
-
export { BaseSyncedStore,
|
|
10
|
-
// Core infrastructure
|
|
14
|
+
// Base store class + the constructor shapes subclasses reference
|
|
15
|
+
export { BaseSyncedStore, } from '../BaseSyncedStore.js';
|
|
16
|
+
// Core infrastructure classes
|
|
11
17
|
export { SyncClient } from '../SyncClient.js';
|
|
12
18
|
export { Database } from '../Database.js';
|
|
13
19
|
export { ObjectPool, ModelScope } from '../ObjectPool.js';
|
|
14
20
|
export { Model } from '../Model.js';
|
|
15
|
-
export { LazyReferenceCollection } from '../LazyReferenceCollection.js';
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// namespace. Direct class access (tests, non-React hosts) imports via
|
|
20
|
-
// the package's internal subpath.
|
|
21
|
-
// Lower-level network primitives — exposed here for the per-app demand
|
|
22
|
-
// loaders. The main barrel hides these so consumer code converges on
|
|
23
|
-
// `ablo.<model>.fetch(...)` for hydration. Loaders that haven't been
|
|
24
|
-
// migrated yet can keep importing from `/core`.
|
|
21
|
+
export { LazyReferenceCollection, } from '../LazyReferenceCollection.js';
|
|
22
|
+
export { ModelRegistry, getActiveRegistry, } from '../ModelRegistry.js';
|
|
23
|
+
// Lower-level network read — for per-app demand loaders that haven't
|
|
24
|
+
// migrated to `ablo.<model>.list(...)` yet.
|
|
25
25
|
export { postQuery } from '../query/client.js';
|
|
26
|
-
export { probeNetwork } from '../sync/NetworkProbe.js';
|
|
27
|
-
export { ConnectionManager, } from '../sync/ConnectionManager.js';
|
|
28
|
-
export { ModelRegistry, getActiveRegistry } from '../ModelRegistry.js';
|
|
29
26
|
// FK-cycle / dependency-order helper — used by schema-aware test
|
|
30
|
-
// fixtures and scaffolding tools to compute commit ordering.
|
|
31
|
-
// here because it traverses model relations and isn't part of the
|
|
32
|
-
// consumer-facing API.
|
|
27
|
+
// fixtures and scaffolding tools to compute commit ordering.
|
|
33
28
|
export { computeFKDepthPriority } from '../client/Ablo.js';
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
// Adapters that the consumer wires into `<AbloProvider>` props
|
|
37
|
-
// (logger, observability, mutation executor, session-error
|
|
38
|
-
// detector, etc.) implement these interfaces. Lives on `/core`
|
|
39
|
-
// because most consumers don't need them; only apps that wrap the
|
|
40
|
-
// provider with custom adapters reach for them.
|
|
41
|
-
export { initSyncEngine, resetSyncEngine, isSyncEngineInitialized } from '../context.js';
|
|
42
|
-
export { noopLogger, noopObservability, noopAnalytics, browserOnlineStatus, defaultSessionErrorDetector, emptyConfig, } from '../SyncEngineContext.js';
|
|
43
|
-
export { SyncSessionError } from '../errors.js';
|
|
44
|
-
export { QueryProcessor } from './QueryProcessor.js';
|
|
45
|
-
export { QueryView } from './QueryView.js';
|
|
46
|
-
export { ViewRegistry } from './ViewRegistry.js';
|
|
47
|
-
export { ObjectStore } from '../stores/ObjectStore.js';
|
|
48
|
-
export { NetworkMonitor } from '../NetworkMonitor.js';
|
|
49
|
-
// Sync layer
|
|
29
|
+
// Sync layer — the wire socket + delta shape, for sync adapters and the
|
|
30
|
+
// multi-agent demo harnesses.
|
|
50
31
|
export { SyncWebSocket, } from '../sync/SyncWebSocket.js';
|
|
51
32
|
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
52
33
|
// Intent coordination primitives (the lower-level pieces behind the
|
|
@@ -56,11 +37,6 @@ export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
|
56
37
|
// orchestration and e2e harnesses — NOT on the consumer `.` root.
|
|
57
38
|
export { createIntentStream, } from '../sync/createIntentStream.js';
|
|
58
39
|
export { awaitIntentGrant, } from '../sync/awaitIntentGrant.js';
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
// headless-clean import path. Framework-level consumers (the few files
|
|
63
|
-
// that orchestrate sync) import from /core explicitly.
|
|
64
|
-
export { OfflineTransactionStore, offlineTxStore, Priority } from '../sync/OfflineTransactionStore.js';
|
|
65
|
-
// Types used by framework-level code
|
|
66
|
-
export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
|
|
40
|
+
// Schema/model load strategy enum — referenced by model registration in
|
|
41
|
+
// framework code.
|
|
42
|
+
export { LoadStrategy } from '../types/index.js';
|
package/dist/errorCodes.d.ts
CHANGED
|
@@ -134,9 +134,15 @@ export declare const ERROR_CODES: {
|
|
|
134
134
|
readonly browser_apikey_blocked: ErrorCodeSpec;
|
|
135
135
|
readonly browser_database_url_blocked: ErrorCodeSpec;
|
|
136
136
|
readonly datasource_registration_failed: ErrorCodeSpec;
|
|
137
|
+
readonly datasource_connection_unsupported: ErrorCodeSpec;
|
|
137
138
|
readonly capability_scope_denied: ErrorCodeSpec;
|
|
138
139
|
readonly issuer_register_forbidden: ErrorCodeSpec;
|
|
139
140
|
readonly capability_invalid: ErrorCodeSpec;
|
|
141
|
+
readonly test_database_not_registered: ErrorCodeSpec;
|
|
142
|
+
readonly database_role_cannot_enforce_rls: ErrorCodeSpec;
|
|
143
|
+
readonly database_role_unreadable: ErrorCodeSpec;
|
|
144
|
+
readonly database_tables_unforced_rls: ErrorCodeSpec;
|
|
145
|
+
readonly database_host_not_allowed: ErrorCodeSpec;
|
|
140
146
|
readonly byo_role_cannot_enforce_rls: ErrorCodeSpec;
|
|
141
147
|
readonly byo_role_unreadable: ErrorCodeSpec;
|
|
142
148
|
readonly byo_tenant_tables_unforced_rls: ErrorCodeSpec;
|
|
@@ -152,6 +158,13 @@ export declare const ERROR_CODES: {
|
|
|
152
158
|
readonly stale_context: ErrorCodeSpec;
|
|
153
159
|
readonly idempotency_conflict: ErrorCodeSpec;
|
|
154
160
|
readonly idempotency_key_too_long: ErrorCodeSpec;
|
|
161
|
+
readonly write_options_invalid: ErrorCodeSpec;
|
|
162
|
+
readonly source_operation_id_required: ErrorCodeSpec;
|
|
163
|
+
readonly source_adapter_misconfigured: ErrorCodeSpec;
|
|
164
|
+
readonly source_event_invalid: ErrorCodeSpec;
|
|
165
|
+
readonly duration_invalid: ErrorCodeSpec;
|
|
166
|
+
readonly schema_definition_invalid: ErrorCodeSpec;
|
|
167
|
+
readonly cli_invalid_arguments: ErrorCodeSpec;
|
|
155
168
|
readonly turn_validation_failed: ErrorCodeSpec;
|
|
156
169
|
readonly commit_operation_required: ErrorCodeSpec;
|
|
157
170
|
readonly commit_operation_model_required: ErrorCodeSpec;
|
package/dist/errorCodes.js
CHANGED
|
@@ -123,11 +123,19 @@ export const ERROR_CODES = {
|
|
|
123
123
|
file_upload_auth_required: wire('auth', 401, false, 'File upload requires an authenticated session.'),
|
|
124
124
|
browser_apikey_blocked: client('auth', 'Raw API keys must not be used from a browser context.'),
|
|
125
125
|
browser_database_url_blocked: client('auth', 'A database connection string must not be used from a browser context — it carries DB credentials.'),
|
|
126
|
-
datasource_registration_failed: client('auth', 'Failed to register the provided databaseUrl
|
|
126
|
+
datasource_registration_failed: client('auth', 'Failed to register the provided databaseUrl as a datasource.'),
|
|
127
|
+
datasource_connection_unsupported: wire('validation', 400, false, 'This deployment cannot register a direct (connection string) datasource — use the signed endpoint kind.'),
|
|
127
128
|
// ── permission / capability (403) ──────────────────────────────────
|
|
128
129
|
capability_scope_denied: wire('capability', 403, false, "The connection's resolved scope does not cover the attempted action."),
|
|
129
130
|
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (sk_) API key.'),
|
|
130
131
|
capability_invalid: wire('capability', 403, false, 'The capability is unknown, revoked, or expired.'),
|
|
132
|
+
test_database_not_registered: wire('permission', 403, false, 'Test mode requires a registered dev database for this org — run `npx ablo init`, or construct the client with `databaseUrl` using your test key.'),
|
|
133
|
+
database_role_cannot_enforce_rls: wire('permission', 403, false, 'The connected database role cannot enforce row-level security (superuser or BYPASSRLS).'),
|
|
134
|
+
database_role_unreadable: wire('permission', 403, false, 'The connected database role could not be introspected.'),
|
|
135
|
+
database_tables_unforced_rls: wire('permission', 403, false, 'Synced tables in the connected database do not have FORCE ROW LEVEL SECURITY applied.'),
|
|
136
|
+
database_host_not_allowed: wire('permission', 403, false, 'The connected database host resolves to a private, loopback, or link-local address and cannot be used.'),
|
|
137
|
+
// Deprecated spellings of the `database_*` codes above — still emitted by
|
|
138
|
+
// older servers; kept so they classify identically. Do not use in new code.
|
|
131
139
|
byo_role_cannot_enforce_rls: wire('permission', 403, false, 'The direct Postgres connector role cannot enforce row-level security.'),
|
|
132
140
|
byo_role_unreadable: wire('permission', 403, false, 'The direct Postgres connector role could not be introspected.'),
|
|
133
141
|
byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the direct Postgres connector role.'),
|
|
@@ -146,6 +154,13 @@ export const ERROR_CODES = {
|
|
|
146
154
|
idempotency_conflict: wire('conflict', 409, false, 'The same Idempotency-Key was reused with a different request body.'),
|
|
147
155
|
idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
|
|
148
156
|
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
157
|
+
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `intent`) failed validation against the write-options schema.'),
|
|
158
|
+
source_operation_id_required: client('validation', 'A data-source operation arrived without the entity `id` it targets.'),
|
|
159
|
+
source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client (missing delegate or model).'),
|
|
160
|
+
source_event_invalid: client('validation', 'A data-source outbox event could not be built — the operation carries no entity id and none was supplied.'),
|
|
161
|
+
duration_invalid: client('validation', 'A duration value was not a number of seconds or a "500ms" | "30s" | "3m" | "24h" string.'),
|
|
162
|
+
schema_definition_invalid: client('validation', 'A schema definition value was invalid (bad column identifier, non-finite backfill, or unsupported schema-JSON version).'),
|
|
163
|
+
cli_invalid_arguments: client('validation', 'The CLI was invoked with an unknown flag or a malformed flag value.'),
|
|
149
164
|
turn_validation_failed: wire('validation', 422, false, 'The agent turn failed server-side validation.'),
|
|
150
165
|
commit_operation_required: wire('validation', 400, false, 'A commit must carry `operation` or `operations`.'),
|
|
151
166
|
commit_operation_model_required: wire('validation', 400, false, 'A commit operation is missing its `model`.'),
|
|
@@ -176,8 +191,8 @@ export const ERROR_CODES = {
|
|
|
176
191
|
check_violation: wire('validation', 400, false, 'A value violates a database check constraint.'),
|
|
177
192
|
constraint_violation: wire('validation', 400, false, 'A database integrity constraint was violated.'),
|
|
178
193
|
// ── tenant / unknown model (400) ───────────────────────────────────
|
|
179
|
-
server_execute_unknown_model: wire('tenant', 400, false, 'The server
|
|
180
|
-
mutate_create_unknown_model: wire('tenant', 400, false, '
|
|
194
|
+
server_execute_unknown_model: wire('tenant', 400, false, 'Wrote to a model the server does not know. The server keeps its own copy of the schema — run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` before writing to new or changed models.'),
|
|
195
|
+
mutate_create_unknown_model: wire('tenant', 400, false, 'Created a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` first — the server keeps its own copy of the schema.'),
|
|
181
196
|
tenant_model_columns_unknown: wire('tenant', 400, false, "The tenant model's columns could not be resolved."),
|
|
182
197
|
tenant_model_missing_organization_id: wire('tenant', 400, false, 'The tenant model is missing the organization_id column required for isolation.'),
|
|
183
198
|
// ── schema migration / declaration (validation) ────────────────────
|
|
@@ -286,7 +301,7 @@ export const ERROR_CODES = {
|
|
|
286
301
|
provisioner_unavailable: wire('server', 503, false, 'No database provisioner is configured.'),
|
|
287
302
|
invalid_model: wire('validation', 400, false, 'The request named an invalid model.'),
|
|
288
303
|
invalid_id: wire('validation', 400, false, 'The request carried an invalid id.'),
|
|
289
|
-
unknown_model: wire('tenant', 400, false, '
|
|
304
|
+
unknown_model: wire('tenant', 400, false, 'Named a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` — the server keeps its own copy of the schema.'),
|
|
290
305
|
model_not_tenant_scoped: wire('tenant', 400, false, 'The model is not tenant-scoped and cannot be queried this way.'),
|
|
291
306
|
schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
|
|
292
307
|
schema_scope_invalid: wire('schema', 500, false, "The model's scope predicate could not be built."),
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,9 @@ export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionErr
|
|
|
64
64
|
export type { CommitReceipt, RequiredCapability } from './errors.js';
|
|
65
65
|
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errors.js';
|
|
66
66
|
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
67
|
+
export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './client/writeOptionsSchema.js';
|
|
68
|
+
export type { WriteOptionsInput } from './client/writeOptionsSchema.js';
|
|
69
|
+
export type { WriteOptions, MutationOptions } from './interfaces/index.js';
|
|
67
70
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
68
71
|
export type { Register, DefaultSyncShape } from './types/global.js';
|
|
69
72
|
export { defineMutators } from './mutators/defineMutators.js';
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_UR
|
|
|
61
61
|
// Participant types live under `Ablo.Participant.*` —
|
|
62
62
|
// `Ablo.Participant.Joined`, `Ablo.Participant.Manager`,
|
|
63
63
|
// `Ablo.Participant.JoinOptions`, etc. Same dot-access shape as
|
|
64
|
-
// `Ablo.Peer`, `Ablo.Claim
|
|
64
|
+
// `Ablo.Peer`, `Ablo.Claim`. No flat re-exports.
|
|
65
65
|
// Advanced — most apps never import this. Principal constructors for
|
|
66
66
|
// delegated agent paths (`Ablo({ kind: 'agent', as: session({...}) })`).
|
|
67
67
|
// The default `Ablo({ schema, apiKey })` resolves identity from the key;
|
|
@@ -89,6 +89,13 @@ export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
|
89
89
|
// `e.type === 'AbloX'`) plus the HTTP-response translator.
|
|
90
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
91
|
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
92
|
+
// THE write-options contract — the one Zod schema for the option bag every
|
|
93
|
+
// write door accepts (`ablo.<model>.create/update/delete`, `commits.create`,
|
|
94
|
+
// the HTTP model routes). The SDK validates against it at each boundary;
|
|
95
|
+
// it's exported so consumers can validate/compose options ahead of a call
|
|
96
|
+
// (e.g. an agent tool's input schema). Runtime twin of `MutationOptions`,
|
|
97
|
+
// drift-guarded at compile time.
|
|
98
|
+
export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './client/writeOptionsSchema.js';
|
|
92
99
|
// Storage-wedge detection — lets app shells render a recovery screen when the
|
|
93
100
|
// IndexedDB backing store is stuck (see core/openIDBWithTimeout.ts).
|
|
94
101
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
@@ -164,13 +164,23 @@ export interface MutationOptions {
|
|
|
164
164
|
readonly id: string;
|
|
165
165
|
} | null;
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
167
|
+
* Dormant agent-task lineage field, forwarded as the wire-level
|
|
168
|
+
* `causedByTaskId`. Turns/tasks were removed from the SDK; nothing
|
|
169
|
+
* populates this anymore (write attribution rides on the claim/intent
|
|
170
|
+
* id). Kept optional for wire-compat; always `null` from the client.
|
|
171
171
|
*/
|
|
172
172
|
causedByTaskId?: string | null;
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* The `MutationOptions` subset carried per-write through the offline
|
|
176
|
+
* transaction lane (SyncClient → TransactionQueue → wire operation).
|
|
177
|
+
* ONE shared type so the proxy's public params, the queue, and the wire
|
|
178
|
+
* can never narrow each other silently again — `wait` and `intent` are
|
|
179
|
+
* deliberately absent because they resolve client-side before staging
|
|
180
|
+
* (`wait` at the proxy's confirmation await, `intent` server-side via
|
|
181
|
+
* the active lease on the entity).
|
|
182
|
+
*/
|
|
183
|
+
export type WriteOptions = Pick<MutationOptions, 'readAt' | 'onStale' | 'idempotencyKey' | 'label'>;
|
|
174
184
|
/** A single mutation operation in a batch. `options` rides along so the
|
|
175
185
|
* server can cache+replay via `mutation_log`. */
|
|
176
186
|
export interface MutationOperation {
|
|
@@ -52,11 +52,6 @@ export interface UndoScopeOptions {
|
|
|
52
52
|
*/
|
|
53
53
|
recordFromStream?: boolean;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
* A single undo stack for one surface. Access via `UndoManager.getScope(name)`.
|
|
57
|
-
* Consumers call `record(entry)` after each mutator; `undo()` / `redo()` to
|
|
58
|
-
* traverse the stacks.
|
|
59
|
-
*/
|
|
60
55
|
export declare class UndoScope<S extends Schema> {
|
|
61
56
|
private readonly schema;
|
|
62
57
|
private readonly store;
|
|
@@ -75,6 +70,15 @@ export declare class UndoScope<S extends Schema> {
|
|
|
75
70
|
* observer can never wedge the editor's recording path.
|
|
76
71
|
*/
|
|
77
72
|
private readonly recordListeners;
|
|
73
|
+
/**
|
|
74
|
+
* Observers notified after ANY stack change — record, undo, redo, or clear.
|
|
75
|
+
* Distinct from {@link recordListeners} (forward actions only): this fires on
|
|
76
|
+
* reversals too, so React consumers can keep `canUndo`/`canRedo` live. The
|
|
77
|
+
* stream-recording path pushes entries WITHOUT a React render, so without this
|
|
78
|
+
* a freshly-recorded entry leaves `canUndo` stale (snapshot from last render)
|
|
79
|
+
* and a Cmd+Z handler gated on `canUndo !== false` silently no-ops.
|
|
80
|
+
*/
|
|
81
|
+
private readonly changeListeners;
|
|
78
82
|
/**
|
|
79
83
|
* Serialization tail. Recording, undo, and redo all chain off this single
|
|
80
84
|
* promise so they run strictly in the order they were *invoked* — never
|
|
@@ -112,6 +116,23 @@ export declare class UndoScope<S extends Schema> {
|
|
|
112
116
|
* response) collapses into ONE Cmd+Z. `endGroup()` flushes it.
|
|
113
117
|
*/
|
|
114
118
|
private group;
|
|
119
|
+
/**
|
|
120
|
+
* ASYNC replay-echo suppression, keyed by `${modelKey}:${id}`.
|
|
121
|
+
*
|
|
122
|
+
* The synchronous {@link replaying} flag only catches echoes delivered INLINE
|
|
123
|
+
* during `applyOps`. The real engine doesn't emit `transaction:created`
|
|
124
|
+
* synchronously: `SyncClient` defers the commit behind `scheduleSync()` +
|
|
125
|
+
* `await persistMutationQueue()` (an IndexedDB write), so a replayed write's
|
|
126
|
+
* echo lands on the stream AFTER `undo()`/`redo()` has already reset
|
|
127
|
+
* `replaying` and pushed the entry. That late echo would be recorded as a
|
|
128
|
+
* NEW edit — and `record()` clears the redo stack, so every undo silently
|
|
129
|
+
* destroyed its own redo. We mark the (modelKey,id) of every op we're about
|
|
130
|
+
* to replay here (synchronously, before the write), and consume one mark when
|
|
131
|
+
* the matching mutation arrives — independent of WHEN it arrives. Entries
|
|
132
|
+
* carry a TTL so a never-arriving echo (offline: the commit is skipped) can't
|
|
133
|
+
* leak and wrongly suppress a much-later genuine edit to the same row.
|
|
134
|
+
*/
|
|
135
|
+
private readonly pendingReplayEchoes;
|
|
115
136
|
constructor(schema: S, store: SyncStoreContract, organizationId: string, options?: UndoScopeOptions);
|
|
116
137
|
/**
|
|
117
138
|
* Open a grouping session: every stream-recorded op until {@link endGroup}
|
|
@@ -122,6 +143,20 @@ export declare class UndoScope<S extends Schema> {
|
|
|
122
143
|
beginGroup(label?: string): void;
|
|
123
144
|
/** Close the grouping session and record the accumulated ops as one entry. */
|
|
124
145
|
endGroup(label?: string): void;
|
|
146
|
+
/** Every `${modelKey}:${id}` a set of ops will touch (all op kinds). */
|
|
147
|
+
private replayEchoKeys;
|
|
148
|
+
/**
|
|
149
|
+
* Arm async-echo suppression for the rows a replay is about to write. Called
|
|
150
|
+
* synchronously, before `applyOps`, so the marks exist no matter how long the
|
|
151
|
+
* engine takes to surface the echo on the stream. See {@link pendingReplayEchoes}.
|
|
152
|
+
*/
|
|
153
|
+
private markReplayEchoes;
|
|
154
|
+
/**
|
|
155
|
+
* If `${schemaKey}:${modelId}` has an armed echo mark, consume one and report
|
|
156
|
+
* that this mutation is our own replay echo (caller drops it). Prunes expired
|
|
157
|
+
* marks opportunistically so a skipped/never-arriving echo can't leak.
|
|
158
|
+
*/
|
|
159
|
+
private consumeReplayEcho;
|
|
125
160
|
/** Resolve a stream mutation's registered name to its schema key, or null. */
|
|
126
161
|
private resolveSchemaKey;
|
|
127
162
|
/**
|
|
@@ -175,6 +210,14 @@ export declare class UndoScope<S extends Schema> {
|
|
|
175
210
|
*/
|
|
176
211
|
onRecord(listener: (entry: UndoEntry) => void): () => void;
|
|
177
212
|
private emitRecord;
|
|
213
|
+
/**
|
|
214
|
+
* Subscribe to ANY stack change (record/undo/redo/clear). Used by
|
|
215
|
+
* `useUndoScope` to re-render so `canUndo`/`canRedo` stay live across every
|
|
216
|
+
* consumer — not just the component that invoked undo/redo. Returns an
|
|
217
|
+
* unsubscribe function.
|
|
218
|
+
*/
|
|
219
|
+
onChange(listener: () => void): () => void;
|
|
220
|
+
private emitChange;
|
|
178
221
|
canUndo(): boolean;
|
|
179
222
|
canRedo(): boolean;
|
|
180
223
|
/**
|