@abloatai/ablo 0.3.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 +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- package/package.json +147 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncClient - Mutation and offline queue manager
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Handle model mutations (create, update, delete, archive)
|
|
6
|
+
* - Manage offline mutation queue with persistence
|
|
7
|
+
* - Send mutations to server via API client
|
|
8
|
+
* - Handle conflict resolution for local changes
|
|
9
|
+
*/
|
|
10
|
+
import { ObjectPool } from './ObjectPool.js';
|
|
11
|
+
import { Model } from './Model.js';
|
|
12
|
+
import { EventEmitter } from 'events';
|
|
13
|
+
import { TransactionQueue } from './transactions/TransactionQueue.js';
|
|
14
|
+
import { type OptimisticEchoMetrics } from './transactions/OptimisticEchoTracker.js';
|
|
15
|
+
import type { Database } from './Database.js';
|
|
16
|
+
import type { MutationOptions } from './interfaces/index.js';
|
|
17
|
+
interface SyncObserver {
|
|
18
|
+
onSync?: (event: SyncEvent) => void;
|
|
19
|
+
}
|
|
20
|
+
interface SyncEvent {
|
|
21
|
+
type: 'create' | 'update' | 'delete' | 'archive' | 'rollback';
|
|
22
|
+
modelType: string;
|
|
23
|
+
model?: Model;
|
|
24
|
+
modelId?: string;
|
|
25
|
+
transactionType?: string;
|
|
26
|
+
}
|
|
27
|
+
interface SyncState {
|
|
28
|
+
connectionState: 'connected' | 'disconnected' | 'connecting';
|
|
29
|
+
pendingMutations: number;
|
|
30
|
+
lastSyncAt?: Date;
|
|
31
|
+
error?: Error;
|
|
32
|
+
}
|
|
33
|
+
export interface RehydrationStats {
|
|
34
|
+
added: number;
|
|
35
|
+
updated: number;
|
|
36
|
+
removed: number;
|
|
37
|
+
skipped: number;
|
|
38
|
+
healed: number;
|
|
39
|
+
elapsedMs: number;
|
|
40
|
+
}
|
|
41
|
+
type MutationWriteOptions = Pick<MutationOptions, 'readAt' | 'onStale'>;
|
|
42
|
+
export declare class SyncClient extends EventEmitter {
|
|
43
|
+
private objectPool;
|
|
44
|
+
private database;
|
|
45
|
+
private get mutationExecutor();
|
|
46
|
+
private networkMonitor;
|
|
47
|
+
private transactionQueue;
|
|
48
|
+
private observers;
|
|
49
|
+
private userId;
|
|
50
|
+
private organizationId;
|
|
51
|
+
private pendingMutations;
|
|
52
|
+
/**
|
|
53
|
+
* Tracks transaction ids the client has optimistically applied but
|
|
54
|
+
* the server has not yet confirmed. The receive path consults it
|
|
55
|
+
* to recognize delta echoes of own mutations and suppress the
|
|
56
|
+
* (otherwise-redundant) pool mutation — the IDB write still runs
|
|
57
|
+
* because the delta is the authoritative version of the row.
|
|
58
|
+
*
|
|
59
|
+
* The receive-layer discriminator named in
|
|
60
|
+
* `apps/sync-server/docs/OPTIMISTIC_RECONCILIATION.md`. Without
|
|
61
|
+
* it, an optimistically-applied DELETE followed by a
|
|
62
|
+
* server-confirming CREATE echo resurrects the row for the window
|
|
63
|
+
* between the two confirmations (the chart-delete flicker).
|
|
64
|
+
*
|
|
65
|
+
* Bounded with FIFO eviction; observability via `getEchoMetrics()`.
|
|
66
|
+
*/
|
|
67
|
+
private readonly echoTracker;
|
|
68
|
+
private connectionState;
|
|
69
|
+
private offlineSince?;
|
|
70
|
+
private maxRetries;
|
|
71
|
+
private isDisposed;
|
|
72
|
+
constructor(objectPool: ObjectPool, database: Database);
|
|
73
|
+
/**
|
|
74
|
+
* Setup network monitoring handlers
|
|
75
|
+
*/
|
|
76
|
+
private setupNetworkMonitoring;
|
|
77
|
+
/**
|
|
78
|
+
* Handle transaction rollback. Two distinct shapes flow through this
|
|
79
|
+
* event:
|
|
80
|
+
*
|
|
81
|
+
* 1. **Server-rejected rollback** (`reason === 'permanent_error'`,
|
|
82
|
+
* `'max_retries_exhausted'`, `'conflict_server_wins'`) — the
|
|
83
|
+
* optimistic state is wrong, the row exists, restore previous
|
|
84
|
+
* state and notify the UI.
|
|
85
|
+
*
|
|
86
|
+
* 2. **Local-cancellation cleanup** (`reason === 'model_cancelled'`,
|
|
87
|
+
* `'cascade_parent_deleted'`) — the user deleted this model (or
|
|
88
|
+
* its parent), so a pending UPDATE on it gets cancelled. There's
|
|
89
|
+
* nothing to restore (the model is doomed) and no UI notification
|
|
90
|
+
* needed (the delete itself already triggered re-renders). Just
|
|
91
|
+
* discard the optimistic state silently.
|
|
92
|
+
*
|
|
93
|
+
* Treating both paths the same caused the deletion-flicker bug: every
|
|
94
|
+
* cancelled update on a multi-layer chart fired a per-model observer
|
|
95
|
+
* event and a `[SyncClient.rollback]` warn, producing N renders and N
|
|
96
|
+
* spam log lines for one user-initiated delete.
|
|
97
|
+
*/
|
|
98
|
+
private setupTransactionRollbackHandling;
|
|
99
|
+
/**
|
|
100
|
+
* Forward reconciliation requests from TransactionQueue to the sync layer.
|
|
101
|
+
* When delta confirmation times out, TransactionQueue emits 'reconciliation:needed'
|
|
102
|
+
* instead of rolling back — following the Replicache/PowerSync pattern of never
|
|
103
|
+
* destroying optimistic state that the server may have committed.
|
|
104
|
+
*/
|
|
105
|
+
private setupReconciliationForwarding;
|
|
106
|
+
/**
|
|
107
|
+
* LINEAR PATTERN: Persist unconfirmed transactions to IndexedDB.
|
|
108
|
+
* When delta confirmation retries exhaust, the transaction data is cached in IDB
|
|
109
|
+
* so it survives tab close. On next session, WebSocket reconnect + delta catch-up
|
|
110
|
+
* will deliver the missing deltas and naturally confirm the transaction.
|
|
111
|
+
*/
|
|
112
|
+
private setupAwaitingTransactionPersistence;
|
|
113
|
+
/**
|
|
114
|
+
* Initialize sync client with authentication
|
|
115
|
+
*/
|
|
116
|
+
initialize(userId: string, organizationId: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Self-healing helper for individual model records.
|
|
119
|
+
*
|
|
120
|
+
* Two registry-driven repair passes run on every row hydrated from
|
|
121
|
+
* IDB or merged from a delta:
|
|
122
|
+
*
|
|
123
|
+
* 1. **Auto-fill** — for each `autoFill` rule the consumer's schema
|
|
124
|
+
* declares on this model, copy the corresponding identity value
|
|
125
|
+
* (`organizationId` / `userId`) onto the row when it's missing.
|
|
126
|
+
* Repairs rows from a past version that didn't write the field.
|
|
127
|
+
*
|
|
128
|
+
* 2. **Required-field gate** — if the row is missing any field listed
|
|
129
|
+
* in the model's `requiredFields`, return `null` so the caller
|
|
130
|
+
* skips this record. Used for FK columns whose absence renders the
|
|
131
|
+
* row unrecoverable (e.g. a SlideLayer with no slideId).
|
|
132
|
+
*
|
|
133
|
+
* The engine itself is product-neutral: model identity (which fields
|
|
134
|
+
* to back-fill, which absences are fatal) lives entirely in the
|
|
135
|
+
* consumer schema.
|
|
136
|
+
*/
|
|
137
|
+
healModelRecord(modelType: string, data: Record<string, unknown>): {
|
|
138
|
+
data: Record<string, unknown>;
|
|
139
|
+
healed: boolean;
|
|
140
|
+
} | null;
|
|
141
|
+
/**
|
|
142
|
+
* Hydrate ObjectPool with data from Database
|
|
143
|
+
* Called after bootstrap is complete
|
|
144
|
+
*/
|
|
145
|
+
hydrateFromDatabase(): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Re-hydrate ObjectPool from IndexedDB when the pool already has data.
|
|
148
|
+
*
|
|
149
|
+
* Unlike hydrateFromDatabase() (which uses addBatch and skips existing IDs),
|
|
150
|
+
* this method properly:
|
|
151
|
+
* 1. Upserts models — updates existing models in-place, adds new ones
|
|
152
|
+
* 2. Removes ghosts — deletes models from the pool that no longer exist in IndexedDB
|
|
153
|
+
*
|
|
154
|
+
* Used by background bootstrap, network recovery, and server-triggered re-bootstrap.
|
|
155
|
+
*/
|
|
156
|
+
rehydrateFromDatabase(): Promise<RehydrationStats>;
|
|
157
|
+
/**
|
|
158
|
+
* Mutate model optimistically and queue for server sync.
|
|
159
|
+
* IndexedDB is only updated when server confirms via delta packet.
|
|
160
|
+
*
|
|
161
|
+
* CRITICAL: Changes are captured BEFORE poolAction to prevent data loss.
|
|
162
|
+
* The captured changes are frozen and passed to queueMutation.
|
|
163
|
+
*
|
|
164
|
+
* @see src/sync-engine/types/TrackableModel.ts for change capture pattern
|
|
165
|
+
*/
|
|
166
|
+
private mutate;
|
|
167
|
+
private pendingChangedTypes;
|
|
168
|
+
private markModelChanged;
|
|
169
|
+
/**
|
|
170
|
+
* Capture model changes immutably BEFORE any pool operations
|
|
171
|
+
* This prevents the fragile pattern of reading changes after state modification
|
|
172
|
+
*/
|
|
173
|
+
private captureModelChanges;
|
|
174
|
+
/** Add new model (CREATE) - works offline */
|
|
175
|
+
add(model: Model, options?: MutationWriteOptions): void;
|
|
176
|
+
/** Update existing model (UPDATE) - works offline */
|
|
177
|
+
update(model: Model, options?: MutationWriteOptions): void;
|
|
178
|
+
/**
|
|
179
|
+
* Update existing model with pre-computed changes.
|
|
180
|
+
* Used by saveManyOptimized when incoming models have empty change-tracking
|
|
181
|
+
* (e.g. freshly constructed SpreadsheetCellModels from decomposeSpreadsheetDocument).
|
|
182
|
+
*/
|
|
183
|
+
updateWithChanges(model: Model, changes?: Record<string, unknown>): void;
|
|
184
|
+
/** Expose the GraphQL client for atomic mutations (e.g., createSlideWithLayers).
|
|
185
|
+
* Used by SyncedStore for operations that bypass the transaction queue
|
|
186
|
+
* but still need optimistic pool updates at the sync layer. */
|
|
187
|
+
get gql(): import("./interfaces/index.js").MutationExecutor;
|
|
188
|
+
/** Delete model (DELETE) - works offline */
|
|
189
|
+
delete(model: Model, options?: MutationWriteOptions): void;
|
|
190
|
+
/**
|
|
191
|
+
* Clear all pending mutations for a specific model
|
|
192
|
+
* Called before deletion to prevent "layer not found" errors on the server
|
|
193
|
+
*/
|
|
194
|
+
private clearPendingMutationsForModel;
|
|
195
|
+
/**
|
|
196
|
+
* Upload file and create attachment (UPLOAD operation)
|
|
197
|
+
* Uses Linear-style pattern with immediate URL generation
|
|
198
|
+
*/
|
|
199
|
+
uploadFile(file: File, options: {
|
|
200
|
+
id: string;
|
|
201
|
+
attachableType: string;
|
|
202
|
+
attachableId: string;
|
|
203
|
+
metadata?: Record<string, unknown>;
|
|
204
|
+
}): Promise<Model | null>;
|
|
205
|
+
/**
|
|
206
|
+
* Batch upload files — single GraphQL call + parallel S3 PUTs.
|
|
207
|
+
*
|
|
208
|
+
* Returns the raw `Model[]` built by the object pool (typename is
|
|
209
|
+
* determined by the payload the server returns — currently always
|
|
210
|
+
* `Attachment`). The SDK has no knowledge of app-specific model classes,
|
|
211
|
+
* so it cannot honestly claim a narrower return type; consumers that
|
|
212
|
+
* need an `Attachment[]` project through their own typed accessor
|
|
213
|
+
* (e.g. `store.query.attachments.findMany({ where: { id: IN ids } })`)
|
|
214
|
+
* after the upload resolves.
|
|
215
|
+
*/
|
|
216
|
+
batchUploadFiles(files: File[], options: {
|
|
217
|
+
ids: string[];
|
|
218
|
+
attachableType: string;
|
|
219
|
+
attachableId: string;
|
|
220
|
+
metadata?: Record<string, unknown>;
|
|
221
|
+
}): Promise<Model[]>;
|
|
222
|
+
/** Archive model (ARCHIVE) - works offline */
|
|
223
|
+
archive(model: Model): void;
|
|
224
|
+
/**
|
|
225
|
+
* Append a mutation and schedule its sync work.
|
|
226
|
+
*
|
|
227
|
+
* IDB persistence and the server push are deferred to a microtask so N
|
|
228
|
+
* pushes inside the same tick collapse into ONE IDB serialization + ONE
|
|
229
|
+
* process call. Without the deferral, queueing 100 mutations (paste,
|
|
230
|
+
* PPTX import, AI sandbox layer creation) reserializes the entire
|
|
231
|
+
* growing queue 100× — O(N²) `model.toJSON()`.
|
|
232
|
+
*
|
|
233
|
+
* @param mutation.capturedChanges - Pre-captured changes (frozen), used
|
|
234
|
+
* to avoid re-reading changes after pool ops that might clear them.
|
|
235
|
+
*/
|
|
236
|
+
private queueMutation;
|
|
237
|
+
private syncScheduled;
|
|
238
|
+
private scheduleSync;
|
|
239
|
+
/**
|
|
240
|
+
* Persist mutation queue to IndexedDB
|
|
241
|
+
*/
|
|
242
|
+
private persistMutationQueue;
|
|
243
|
+
/**
|
|
244
|
+
* Restore mutation queue from IndexedDB
|
|
245
|
+
*/
|
|
246
|
+
private restoreMutationQueue;
|
|
247
|
+
/**
|
|
248
|
+
* Process pending mutations - can be called by SyncedStore when online
|
|
249
|
+
*
|
|
250
|
+
* Best Practice: Only sync models that still exist locally (local-first principle)
|
|
251
|
+
* - If a model was deleted locally → skip any pending updates/creates for it
|
|
252
|
+
* - This prevents "layer not found" errors from fast copy-paste-delete workflows
|
|
253
|
+
*/
|
|
254
|
+
processPendingMutations(): Promise<void>;
|
|
255
|
+
/**
|
|
256
|
+
* Stage mutation to TransactionQueue - mutations in same tick are batched via microtask
|
|
257
|
+
*
|
|
258
|
+
* @param mutation.capturedChanges - Pre-captured changes to use instead of re-reading from model
|
|
259
|
+
*/
|
|
260
|
+
private stageMutation;
|
|
261
|
+
/**
|
|
262
|
+
* Resolve conflicts between local and server data
|
|
263
|
+
* Used when processing deltas from WebSocket
|
|
264
|
+
*
|
|
265
|
+
* CRITICAL: Always respects certain server states (deletes, deactivations)
|
|
266
|
+
* even when there are local changes, to maintain data consistency.
|
|
267
|
+
*/
|
|
268
|
+
resolveConflicts(localModel: Model, serverData: any): Model;
|
|
269
|
+
/**
|
|
270
|
+
* Extract critical state fields from server data
|
|
271
|
+
* These are states that must always be respected, even with local changes
|
|
272
|
+
*/
|
|
273
|
+
private extractCriticalState;
|
|
274
|
+
/**
|
|
275
|
+
* Check if critical state changes exist that require forcing server state
|
|
276
|
+
*/
|
|
277
|
+
private hasCriticalStateChange;
|
|
278
|
+
/**
|
|
279
|
+
* Handle network reconnection
|
|
280
|
+
*/
|
|
281
|
+
private handleReconnection;
|
|
282
|
+
/**
|
|
283
|
+
* Handle network disconnection
|
|
284
|
+
*/
|
|
285
|
+
private handleDisconnection;
|
|
286
|
+
/**
|
|
287
|
+
* Get current sync state
|
|
288
|
+
*/
|
|
289
|
+
getState(): SyncState;
|
|
290
|
+
/**
|
|
291
|
+
* Set connection state
|
|
292
|
+
*/
|
|
293
|
+
private setConnectionState;
|
|
294
|
+
/**
|
|
295
|
+
* Subscribe to events with disposer pattern
|
|
296
|
+
*/
|
|
297
|
+
subscribe(event: string, handler: (data?: unknown) => void): () => void;
|
|
298
|
+
/**
|
|
299
|
+
* Add observer for sync events
|
|
300
|
+
*/
|
|
301
|
+
addObserver(observer: SyncObserver): void;
|
|
302
|
+
/**
|
|
303
|
+
* Remove observer
|
|
304
|
+
*/
|
|
305
|
+
removeObserver(observer: SyncObserver): void;
|
|
306
|
+
/**
|
|
307
|
+
* Notify all observers
|
|
308
|
+
*/
|
|
309
|
+
private notifyObservers;
|
|
310
|
+
/**
|
|
311
|
+
* Disconnect from sync
|
|
312
|
+
*/
|
|
313
|
+
disconnect(): void;
|
|
314
|
+
/**
|
|
315
|
+
* Mark the sync client as connected
|
|
316
|
+
* Called when WebSocket successfully connects (can happen independently of browser online/offline)
|
|
317
|
+
*/
|
|
318
|
+
markConnected(): void;
|
|
319
|
+
/**
|
|
320
|
+
* Dispose and cleanup
|
|
321
|
+
*/
|
|
322
|
+
dispose(): void;
|
|
323
|
+
/**
|
|
324
|
+
* LINEAR PATTERN: Notify TransactionQueue of incoming delta for sync ID threshold confirmation.
|
|
325
|
+
* Transactions are confirmed when any delta with id >= their lastSyncId threshold arrives.
|
|
326
|
+
* @param syncId - The sync ID of the received delta
|
|
327
|
+
*/
|
|
328
|
+
onDeltaReceived(syncId: number): void;
|
|
329
|
+
/**
|
|
330
|
+
* LINEAR PATTERN: Cancel transactions for orphaned child entities
|
|
331
|
+
*
|
|
332
|
+
* Called by SyncedStore when a DELETE delta arrives for a parent entity.
|
|
333
|
+
* Cancels pending transactions for children that reference the deleted parent.
|
|
334
|
+
*
|
|
335
|
+
* @param childModelName - The child model type (e.g., 'SlideLayer')
|
|
336
|
+
* @param foreignKey - The FK property name (e.g., 'slideId')
|
|
337
|
+
* @param parentId - The deleted parent's ID
|
|
338
|
+
* @returns Number of transactions cancelled
|
|
339
|
+
*/
|
|
340
|
+
cancelTransactionsByForeignKey(childModelName: string, foreignKey: string, parentId: string): number;
|
|
341
|
+
/**
|
|
342
|
+
* Wait for a transaction to be confirmed via delta echo (Linear pattern)
|
|
343
|
+
* Delegates to TransactionQueue which already handles timeouts
|
|
344
|
+
*/
|
|
345
|
+
waitForDeltaConfirmation(transactionId: string): Promise<void>;
|
|
346
|
+
/**
|
|
347
|
+
* Force sync now - process pending mutations
|
|
348
|
+
*/
|
|
349
|
+
syncNow(): Promise<void>;
|
|
350
|
+
/**
|
|
351
|
+
* Get sync statistics. Return type is inferred from the literal so
|
|
352
|
+
* the call site sees the actual shape — `connectionState` narrowed
|
|
353
|
+
* to its three states, `objectPoolStats` typed by `ObjectPool.getStats`.
|
|
354
|
+
*/
|
|
355
|
+
getSyncStats(): {
|
|
356
|
+
connectionState: 'connected' | 'disconnected' | 'connecting';
|
|
357
|
+
pendingMutations: number;
|
|
358
|
+
objectPoolStats: ReturnType<ObjectPool['getStats']>;
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* Get pending transaction count from TransactionQueue
|
|
362
|
+
* Used by SyncedStore to compute hasUnsyncedChanges
|
|
363
|
+
*/
|
|
364
|
+
getPendingTransactionCount(): number;
|
|
365
|
+
/**
|
|
366
|
+
* Subscribe to transaction events for sync status tracking
|
|
367
|
+
* Returns unsubscribe function
|
|
368
|
+
*/
|
|
369
|
+
onTransactionEvent(event: 'created' | 'completed' | 'failed', callback: () => void): () => void;
|
|
370
|
+
/**
|
|
371
|
+
* Subscribe to mutation failures with the full payload. Mirrors the
|
|
372
|
+
* underlying TransactionQueue 'transaction:failed' shape so consumers
|
|
373
|
+
* can render typed UI (toast keyed by `AbloError.type`, route-level
|
|
374
|
+
* "this entity reverted" boundaries, telemetry).
|
|
375
|
+
*
|
|
376
|
+
* Distinct from `onTransactionEvent('failed', cb)`, which exists only
|
|
377
|
+
* for the legacy parameterless `pendingChanges` counter and intentionally
|
|
378
|
+
* drops the payload. The two coexist — keep the counter callback fast
|
|
379
|
+
* and the typed listener for user-visible surfaces.
|
|
380
|
+
*/
|
|
381
|
+
onMutationFailure(listener: (payload: {
|
|
382
|
+
transaction: import('./transactions/TransactionQueue.js').Transaction;
|
|
383
|
+
error: Error;
|
|
384
|
+
permanent?: boolean;
|
|
385
|
+
}) => void): () => void;
|
|
386
|
+
/**
|
|
387
|
+
* Wait for the latest in-flight transaction for (modelName, modelId)
|
|
388
|
+
* to be confirmed by the server, or reject if it's rolled back.
|
|
389
|
+
* Resolves immediately when no transaction is in flight — see
|
|
390
|
+
* `TransactionQueue.confirmationFor` for the lookup contract.
|
|
391
|
+
*
|
|
392
|
+
* Distinct from `waitForDeltaConfirmation(transactionId)` which keys
|
|
393
|
+
* off a known tx id; this variant is for call sites that hold a
|
|
394
|
+
* Model reference but never see the underlying transaction.
|
|
395
|
+
*/
|
|
396
|
+
waitForConfirmation(modelName: string, modelId: string): Promise<void>;
|
|
397
|
+
/**
|
|
398
|
+
* Get detailed debug info for the sync debug page
|
|
399
|
+
*/
|
|
400
|
+
getDebugInfo(): {
|
|
401
|
+
connectionState: "connected" | "disconnected" | "connecting";
|
|
402
|
+
pendingMutationsCount: number;
|
|
403
|
+
transactionQueue: {
|
|
404
|
+
lastSeenSyncId: number;
|
|
405
|
+
awaitingDeltaCount: number;
|
|
406
|
+
awaitingDeltaTransactions: {
|
|
407
|
+
id: string;
|
|
408
|
+
type: "update" | "create" | "delete" | "archive" | "unarchive";
|
|
409
|
+
modelName: string;
|
|
410
|
+
modelId: string;
|
|
411
|
+
syncIdNeeded: number | undefined;
|
|
412
|
+
createdAt: number;
|
|
413
|
+
age: number;
|
|
414
|
+
}[];
|
|
415
|
+
pendingTransactions: {
|
|
416
|
+
id: string;
|
|
417
|
+
type: "update" | "create" | "delete" | "archive" | "unarchive";
|
|
418
|
+
modelName: string;
|
|
419
|
+
modelId: string;
|
|
420
|
+
}[];
|
|
421
|
+
executingTransactions: {
|
|
422
|
+
id: string;
|
|
423
|
+
type: "update" | "create" | "delete" | "archive" | "unarchive";
|
|
424
|
+
modelName: string;
|
|
425
|
+
modelId: string;
|
|
426
|
+
}[];
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
unassignEntity(entityType: string, entityId: string): Promise<void>;
|
|
430
|
+
reassignEntity(entityType: string, entityId: string, assigneeType: string, assigneeId: string, id?: string): Promise<void>;
|
|
431
|
+
/**
|
|
432
|
+
* Apply a batch of delta results from Database to the ObjectPool.
|
|
433
|
+
* Owns: model creation, upsert, remove, archive, conflict resolution.
|
|
434
|
+
* Returns: nothing — ObjectPool is updated in place.
|
|
435
|
+
*/
|
|
436
|
+
/**
|
|
437
|
+
* Mark a local transaction as optimistically applied. The matching
|
|
438
|
+
* server delta (when it arrives with the same `transactionId`) will
|
|
439
|
+
* be recognized as an echo and skip the pool mutation. Called
|
|
440
|
+
* automatically by `TransactionQueue` when a transaction is staged;
|
|
441
|
+
* exposed publicly so tests can drive the API directly.
|
|
442
|
+
*/
|
|
443
|
+
markTransactionPending(transactionId: string): void;
|
|
444
|
+
/**
|
|
445
|
+
* Read echo-detection counters: hits, rollbacks, evictions, and the
|
|
446
|
+
* current pending-set size. Surfaced for production observability
|
|
447
|
+
* — a sustained `evictions > 0` rate or `rollbacks` spike is a
|
|
448
|
+
* health signal worth alerting on.
|
|
449
|
+
*/
|
|
450
|
+
getEchoMetrics(): Readonly<OptimisticEchoMetrics>;
|
|
451
|
+
/**
|
|
452
|
+
* Package-internal accessor for the TransactionQueue. Used by
|
|
453
|
+
* `Ablo.commits.create()` to route raw multi-op envelopes through the
|
|
454
|
+
* same retry-on-reconnect lane as the Model proxy path, and by tests
|
|
455
|
+
* to exercise the queue ↔ markTransactionPending wiring on the real
|
|
456
|
+
* instance the SyncClient subscribes to. NOT re-exported to SDK
|
|
457
|
+
* consumers — `Ablo` itself is the public surface.
|
|
458
|
+
*/
|
|
459
|
+
getTransactionQueue(): TransactionQueue;
|
|
460
|
+
applyDeltaBatchToPool(dbResults: Array<{
|
|
461
|
+
action: string;
|
|
462
|
+
modelName: string;
|
|
463
|
+
modelId: string;
|
|
464
|
+
data?: Record<string, unknown> | null;
|
|
465
|
+
/**
|
|
466
|
+
* Server-stamped transaction id, echoing the client's commit op
|
|
467
|
+
* id. Used by echo detection to recognize "this is the
|
|
468
|
+
* confirmation of a mutation I've already applied locally."
|
|
469
|
+
* Optional because system-emitted deltas (sync_group changes,
|
|
470
|
+
* schema-derived deltas, etc.) don't have a client transaction.
|
|
471
|
+
*/
|
|
472
|
+
transactionId?: string;
|
|
473
|
+
}>, enrichRelations: (modelName: string, data: Record<string, unknown>) => Record<string, unknown>): void;
|
|
474
|
+
/**
|
|
475
|
+
* Apply bootstrap data to the ObjectPool with ghost removal.
|
|
476
|
+
* Owns: model creation, batch upsert, ghost detection + removal.
|
|
477
|
+
*/
|
|
478
|
+
applyBootstrapDataToPool(bootstrapData: {
|
|
479
|
+
models?: Record<string, unknown[]>;
|
|
480
|
+
failedModels?: string[];
|
|
481
|
+
}, protectedIds?: ReadonlySet<string>): {
|
|
482
|
+
added: number;
|
|
483
|
+
updated: number;
|
|
484
|
+
removed: number;
|
|
485
|
+
skipped: number;
|
|
486
|
+
healed: number;
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
export {};
|