@abloatai/ablo 0.26.0 → 0.27.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 +14 -0
- package/README.md +101 -85
- package/dist/BaseSyncedStore.d.ts +85 -88
- package/dist/BaseSyncedStore.js +131 -147
- package/dist/Database.d.ts +54 -68
- package/dist/Database.js +97 -113
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +12 -16
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +46 -61
- package/dist/ModelRegistry.d.ts +21 -19
- package/dist/ModelRegistry.js +23 -27
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +5 -6
- package/dist/SyncClient.d.ts +112 -112
- package/dist/SyncClient.js +165 -172
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +27 -32
- package/dist/agent/Agent.js +18 -19
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +6 -7
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +69 -74
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +6 -5
- package/dist/auth/credentialSource.js +9 -10
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +31 -37
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +167 -119
- package/dist/client/Ablo.d.ts +73 -73
- package/dist/client/Ablo.js +125 -160
- package/dist/client/ApiClient.d.ts +30 -19
- package/dist/client/ApiClient.js +133 -38
- package/dist/client/auth.d.ts +47 -47
- package/dist/client/auth.js +108 -117
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +5 -6
- package/dist/client/consoleLogger.js +5 -6
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +25 -30
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +152 -122
- package/dist/client/credentialEndpoint.d.ts +40 -42
- package/dist/client/credentialEndpoint.js +35 -36
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +9 -12
- package/dist/client/hostedEndpoints.js +9 -12
- package/dist/client/httpClient.d.ts +57 -53
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +47 -58
- package/dist/client/modelRegistration.d.ts +5 -9
- package/dist/client/modelRegistration.js +67 -87
- package/dist/client/options.d.ts +134 -157
- package/dist/client/options.js +3 -7
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +64 -75
- package/dist/client/resourceTypes.js +4 -10
- package/dist/client/schemaConfig.d.ts +31 -43
- package/dist/client/schemaConfig.js +38 -50
- package/dist/client/sessionMint.d.ts +16 -12
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +8 -9
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +15 -20
- package/dist/client/wsMutationExecutor.js +17 -23
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -4
- package/dist/coordination/index.d.ts +10 -8
- package/dist/coordination/index.js +14 -12
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -10
- package/dist/coordination/trace.js +13 -14
- package/dist/core/DatabaseManager.d.ts +5 -7
- package/dist/core/DatabaseManager.js +15 -19
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +22 -28
- package/dist/core/QueryView.d.ts +8 -8
- package/dist/core/QueryView.js +2 -2
- package/dist/core/StoreManager.d.ts +12 -14
- package/dist/core/StoreManager.js +21 -24
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +17 -12
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +42 -43
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +63 -61
- package/dist/core/storeContract.js +8 -12
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +107 -99
- package/dist/errorCodes.js +131 -132
- package/dist/errors.d.ts +160 -166
- package/dist/errors.js +155 -158
- package/dist/index.d.ts +30 -27
- package/dist/index.js +89 -86
- package/dist/interfaces/index.d.ts +102 -113
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +41 -40
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +145 -156
- package/dist/mutators/defineMutators.d.ts +23 -34
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +104 -100
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +28 -23
- package/dist/query/client.js +45 -43
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +2 -2
- package/dist/react/context.d.ts +25 -28
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +16 -14
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +10 -11
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +8 -8
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useReactive.js +2 -2
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +20 -24
- package/dist/schema/ddlLock.js +18 -23
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +35 -48
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +21 -23
- package/dist/schema/model.d.ts +118 -143
- package/dist/schema/model.js +22 -33
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +5 -3
- package/dist/schema/queries.d.ts +29 -31
- package/dist/schema/queries.js +23 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +16 -13
- package/dist/schema/residency.js +16 -13
- package/dist/schema/roles.d.ts +36 -43
- package/dist/schema/roles.js +31 -37
- package/dist/schema/schema.d.ts +33 -42
- package/dist/schema/schema.js +31 -32
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +28 -31
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +30 -25
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +30 -25
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +24 -23
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +9 -8
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +22 -25
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +17 -11
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +28 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +46 -58
- package/dist/source/factory.js +22 -27
- package/dist/source/index.d.ts +7 -9
- package/dist/source/index.js +12 -14
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +9 -10
- package/dist/source/next.js +6 -7
- package/dist/source/pushQueue.d.ts +69 -47
- package/dist/source/pushQueue.js +32 -28
- package/dist/source/signing.d.ts +46 -17
- package/dist/source/signing.js +28 -11
- package/dist/source/types.d.ts +121 -104
- package/dist/source/types.js +13 -14
- package/dist/stores/ObjectStore.d.ts +10 -11
- package/dist/stores/ObjectStore.js +11 -12
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +7 -11
- package/dist/stores/SyncActionStore.js +13 -17
- package/dist/surface.d.ts +27 -20
- package/dist/surface.js +27 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
- package/dist/sync/ConnectionManager.d.ts +39 -50
- package/dist/sync/ConnectionManager.js +55 -66
- package/dist/sync/NetworkProbe.d.ts +24 -29
- package/dist/sync/NetworkProbe.js +63 -69
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
- package/dist/sync/SyncWebSocket.d.ts +139 -165
- package/dist/sync/SyncWebSocket.js +191 -223
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +11 -11
- package/dist/sync/bootstrapApply.d.ts +34 -24
- package/dist/sync/bootstrapApply.js +27 -19
- package/dist/sync/commitFrames.d.ts +21 -20
- package/dist/sync/commitFrames.js +18 -18
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +105 -23
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +12 -14
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +104 -104
- package/dist/sync/credentialLifecycle.js +140 -147
- package/dist/sync/deltaPipeline.d.ts +36 -34
- package/dist/sync/deltaPipeline.js +64 -65
- package/dist/sync/groupChange.d.ts +63 -61
- package/dist/sync/groupChange.js +74 -78
- package/dist/sync/heartbeat.d.ts +34 -33
- package/dist/sync/heartbeat.js +31 -31
- package/dist/sync/participants.d.ts +19 -19
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +17 -21
- package/dist/sync/syncCursor.js +17 -21
- package/dist/sync/syncPlan.d.ts +28 -36
- package/dist/sync/syncPlan.js +18 -19
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +35 -36
- package/dist/sync/wsFrameHandlers.js +63 -67
- package/dist/testing/fixtures/bootstrap.d.ts +12 -6
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +30 -33
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +11 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +2 -2
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
- package/dist/testing/mocks/MockSyncContext.js +15 -13
- package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +181 -176
- package/dist/transactions/TransactionQueue.js +338 -350
- package/dist/transactions/TransactionStore.d.ts +6 -4
- package/dist/transactions/TransactionStore.js +6 -4
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +41 -17
- package/dist/transactions/coalesceRules.js +40 -17
- package/dist/transactions/commitPayload.d.ts +48 -52
- package/dist/transactions/commitPayload.js +48 -57
- package/dist/transactions/deltaConfirmation.d.ts +20 -22
- package/dist/transactions/deltaConfirmation.js +37 -45
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
- package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +71 -77
- package/dist/types/index.js +22 -22
- package/dist/types/modelData.d.ts +6 -8
- package/dist/types/modelData.js +5 -7
- package/dist/types/participant.d.ts +10 -11
- package/dist/types/participant.js +6 -8
- package/dist/types/streams.d.ts +208 -195
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +30 -31
- package/dist/wire/errorEnvelope.js +34 -40
- package/dist/wire/frames.d.ts +79 -86
- package/dist/wire/frames.js +26 -33
- package/dist/wire/index.d.ts +14 -12
- package/dist/wire/index.js +30 -26
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +25 -32
- package/dist/wire/protocol.js +25 -32
- package/dist/wire/protocolVersion.d.ts +44 -40
- package/dist/wire/protocolVersion.js +44 -40
- package/docs/coordination.md +59 -0
- package/package.json +11 -10
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -8
- package/dist/server/storage-mode.js +0 -28
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/transactions/optimistic.d.ts +0 -24
- package/dist/transactions/optimistic.js +0 -45
- package/dist/utils/mobx-setup.d.ts +0 -42
package/dist/SyncClient.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Applies model mutations and manages the offline write queue. The
|
|
3
|
+
* SyncClient turns local create, update, delete, and archive calls into
|
|
4
|
+
* optimistic changes, holds them while the client is offline, sends them to
|
|
5
|
+
* the server when connectivity returns, and resolves conflicts when the
|
|
6
|
+
* server's version of a row disagrees with the local one. It sits between the
|
|
7
|
+
* reactive object pool and the {@link TransactionQueue} that delivers writes
|
|
8
|
+
* over the network.
|
|
9
9
|
*/
|
|
10
10
|
import { runInAction } from 'mobx';
|
|
11
|
-
import {
|
|
11
|
+
import { InstanceCache, ModelScope } from './InstanceCache.js';
|
|
12
12
|
import { Model } from './Model.js';
|
|
13
13
|
// ModelRegistry instance accessed via this.objectPool.registry
|
|
14
14
|
import { LoadStrategy } from './types/index.js';
|
|
@@ -17,17 +17,18 @@ import { AbloAuthenticationError, AbloError, AbloValidationError } from './error
|
|
|
17
17
|
import { EventEmitter } from 'events';
|
|
18
18
|
import { NetworkMonitor } from './NetworkMonitor.js';
|
|
19
19
|
import { TransactionQueue } from './transactions/TransactionQueue.js';
|
|
20
|
-
import { persistedMutationSchema } from './transactions/
|
|
21
|
-
import {
|
|
20
|
+
import { persistedMutationSchema } from './transactions/replayValidation.js';
|
|
21
|
+
import { UnconfirmedWrites, } from './transactions/UnconfirmedWrites.js';
|
|
22
22
|
import { SyncPosition } from './sync/syncPosition.js';
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
24
|
+
* Reports whether an incoming snapshot record is strictly newer than the
|
|
25
|
+
* model already in the pool. The comparison uses the server-stamped
|
|
26
|
+
* `updatedAt` timestamp, since rows carry no numeric version and the delta
|
|
27
|
+
* pipeline resolves order by arrival (last write wins). An undefined incoming
|
|
28
|
+
* timestamp counts as not newer, so a known row is never clobbered; an
|
|
29
|
+
* undefined existing timestamp means the pooled row is unversioned, so the
|
|
30
|
+
* incoming record wins. The scoped hydrate-on-enter path uses this to drop
|
|
31
|
+
* snapshot rows that a live delta has already advanced past.
|
|
31
32
|
*/
|
|
32
33
|
function rawRecordIsNewer(data, existing) {
|
|
33
34
|
const raw = data.updatedAt;
|
|
@@ -46,10 +47,10 @@ function rawRecordIsNewer(data, existing) {
|
|
|
46
47
|
return inMs > exMs;
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* non-date values
|
|
52
|
-
*
|
|
50
|
+
* Converts an untyped server `updatedAt` value — an ISO string, epoch number,
|
|
51
|
+
* or Date read off an untyped row — into epoch milliseconds for
|
|
52
|
+
* last-write-wins comparison. Falsy or non-date values become 0, matching the
|
|
53
|
+
* conflict resolver's rule that a missing timestamp sorts as the epoch.
|
|
53
54
|
*/
|
|
54
55
|
function toEpochMs(value) {
|
|
55
56
|
if (!value)
|
|
@@ -74,32 +75,28 @@ export class SyncClient extends EventEmitter {
|
|
|
74
75
|
// Pending mutations queue
|
|
75
76
|
pendingMutations = [];
|
|
76
77
|
/**
|
|
77
|
-
* Tracks
|
|
78
|
-
* the server has not yet confirmed.
|
|
79
|
-
* to recognize
|
|
80
|
-
*
|
|
81
|
-
* because the delta is the authoritative version of the row.
|
|
78
|
+
* Tracks the ids of transactions the client has applied optimistically but
|
|
79
|
+
* the server has not yet confirmed. When a delta arrives, the receive path
|
|
80
|
+
* consults this set to recognize the echo of the client's own mutation and
|
|
81
|
+
* skip the now-redundant pool update; the IndexedDB write still runs,
|
|
82
|
+
* because the delta is the authoritative version of the row. Without this
|
|
83
|
+
* discriminator, an optimistically applied delete followed by a
|
|
84
|
+
* server-confirmed create echo would resurrect the row for the window
|
|
85
|
+
* between the two confirmations.
|
|
82
86
|
*
|
|
83
|
-
* The
|
|
84
|
-
*
|
|
85
|
-
* it, an optimistically-applied DELETE followed by a
|
|
86
|
-
* server-confirming CREATE echo resurrects the row for the window
|
|
87
|
-
* between the two confirmations (the chart-delete flicker).
|
|
88
|
-
*
|
|
89
|
-
* Bounded with FIFO eviction; observability via `getEchoMetrics()`.
|
|
87
|
+
* The set is bounded with first-in-first-out eviction, and
|
|
88
|
+
* {@link SyncClient.getEchoMetrics} exposes its counters.
|
|
90
89
|
*/
|
|
91
|
-
echoTracker = new
|
|
90
|
+
echoTracker = new UnconfirmedWrites();
|
|
92
91
|
// Connection state
|
|
93
92
|
connectionState = 'disconnected';
|
|
94
|
-
offlineSince;
|
|
95
93
|
// Configuration
|
|
96
|
-
maxRetries = 3;
|
|
97
94
|
isDisposed = false;
|
|
98
95
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* `
|
|
102
|
-
*
|
|
96
|
+
* The client's position in the global delta order, held as the single
|
|
97
|
+
* canonical {@link SyncPosition} instance. The store advances `applied` and
|
|
98
|
+
* `persisted` as deltas land, the queue advances `acked` on commit
|
|
99
|
+
* responses, and snapshots and claims read `readFloor`.
|
|
103
100
|
*/
|
|
104
101
|
position = new SyncPosition();
|
|
105
102
|
constructor(objectPool, database) {
|
|
@@ -110,8 +107,8 @@ export class SyncClient extends EventEmitter {
|
|
|
110
107
|
// Initialize TransactionQueue with proper configuration
|
|
111
108
|
this.transactionQueue = new TransactionQueue({
|
|
112
109
|
position: this.position,
|
|
113
|
-
maxBatchSize: 50, //
|
|
114
|
-
//
|
|
110
|
+
maxBatchSize: 50, // Larger batches keep the batch count low for bulk operations
|
|
111
|
+
// A short delay keeps writes responsive; coalescing still groups them
|
|
115
112
|
batchDelay: 150,
|
|
116
113
|
maxRetries: 3,
|
|
117
114
|
enableOptimistic: true,
|
|
@@ -122,16 +119,17 @@ export class SyncClient extends EventEmitter {
|
|
|
122
119
|
});
|
|
123
120
|
// Provide connection state to TransactionQueue - prevents rollbacks during disconnection
|
|
124
121
|
this.transactionQueue.setConnectionChecker(() => this.connectionState === 'connected');
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
122
|
+
// Restore object-pool state when a transaction is rolled back. If the
|
|
123
|
+
// server rejects a write or it times out, the model's previous state is
|
|
124
|
+
// put back. Because writes are no longer applied to IndexedDB
|
|
125
|
+
// optimistically, that store already holds the correct state.
|
|
128
126
|
this.setupTransactionRollbackHandling();
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
127
|
+
// Forward reconciliation requests from the transaction queue. When delta
|
|
128
|
+
// confirmation times out, the client cycles the WebSocket connection to
|
|
129
|
+
// trigger a catch-up from the server rather than rolling the write back.
|
|
132
130
|
this.setupReconciliationForwarding();
|
|
133
|
-
//
|
|
134
|
-
//
|
|
131
|
+
// Persist unconfirmed transactions to IndexedDB. When delta retries are
|
|
132
|
+
// exhausted, the write is cached so it survives a tab close.
|
|
135
133
|
this.setupAwaitingTransactionPersistence();
|
|
136
134
|
// Setup network monitoring
|
|
137
135
|
this.setupNetworkMonitoring();
|
|
@@ -289,10 +287,10 @@ export class SyncClient extends EventEmitter {
|
|
|
289
287
|
});
|
|
290
288
|
}
|
|
291
289
|
/**
|
|
292
|
-
* Forward reconciliation requests from TransactionQueue to the
|
|
293
|
-
* When delta confirmation times out,
|
|
294
|
-
* instead of rolling back
|
|
295
|
-
*
|
|
290
|
+
* Forward reconciliation requests from the {@link TransactionQueue} to the
|
|
291
|
+
* sync layer. When delta confirmation times out, the queue emits
|
|
292
|
+
* `reconciliation:needed` instead of rolling back, so optimistic state the
|
|
293
|
+
* server may already have committed is never destroyed.
|
|
296
294
|
*/
|
|
297
295
|
setupReconciliationForwarding() {
|
|
298
296
|
this.transactionQueue.on('reconciliation:needed', (event) => {
|
|
@@ -310,10 +308,10 @@ export class SyncClient extends EventEmitter {
|
|
|
310
308
|
});
|
|
311
309
|
}
|
|
312
310
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
311
|
+
* Persist unconfirmed transactions to IndexedDB. When delta-confirmation
|
|
312
|
+
* retries are exhausted, the transaction is cached so it survives a tab
|
|
313
|
+
* close. On the next session, a WebSocket reconnect and delta catch-up
|
|
314
|
+
* deliver the missing deltas and confirm the transaction.
|
|
317
315
|
*/
|
|
318
316
|
setupAwaitingTransactionPersistence() {
|
|
319
317
|
this.transactionQueue.on('transaction:persist_awaiting', (event) => {
|
|
@@ -341,7 +339,7 @@ export class SyncClient extends EventEmitter {
|
|
|
341
339
|
this.echoTracker.drainOnRollback(event.transaction.id);
|
|
342
340
|
});
|
|
343
341
|
}
|
|
344
|
-
/** Persist an unconfirmed transaction to
|
|
342
|
+
/** Persist an unconfirmed transaction to IndexedDB (never rejects — failures are captured). */
|
|
345
343
|
async persistAwaitingTransaction(event) {
|
|
346
344
|
if (!this.database)
|
|
347
345
|
return;
|
|
@@ -392,19 +390,17 @@ export class SyncClient extends EventEmitter {
|
|
|
392
390
|
getContext().observability.setContext(userId, organizationId);
|
|
393
391
|
// Restore queued mutations from previous session
|
|
394
392
|
await this.restoreMutationQueue();
|
|
395
|
-
//
|
|
396
|
-
// In the browser this
|
|
397
|
-
//
|
|
398
|
-
// online
|
|
399
|
-
//
|
|
400
|
-
// status snapshot at registerUser() time.
|
|
393
|
+
// Read the initial network status from the injected OnlineStatusProvider.
|
|
394
|
+
// In the browser this reflects the host's connectivity signal; in Node it
|
|
395
|
+
// reports online by default. NetworkMonitor drives the ongoing
|
|
396
|
+
// online/offline transitions below — this read is only the initial
|
|
397
|
+
// snapshot taken when identity is set.
|
|
401
398
|
if (getContext().onlineStatus.isOnline()) {
|
|
402
399
|
this.setConnectionState('connected');
|
|
403
400
|
}
|
|
404
401
|
else {
|
|
405
402
|
// Offline - start in offline mode
|
|
406
403
|
this.setConnectionState('disconnected');
|
|
407
|
-
this.offlineSince = new Date();
|
|
408
404
|
this.emit('sync:offline');
|
|
409
405
|
}
|
|
410
406
|
}
|
|
@@ -420,7 +416,7 @@ export class SyncClient extends EventEmitter {
|
|
|
420
416
|
* Self-healing helper for individual model records.
|
|
421
417
|
*
|
|
422
418
|
* Two registry-driven repair passes run on every row hydrated from
|
|
423
|
-
*
|
|
419
|
+
* IndexedDB or merged from a delta:
|
|
424
420
|
*
|
|
425
421
|
* 1. **Auto-fill** — for each `autoFill` rule the consumer's schema
|
|
426
422
|
* declares on this model, copy the corresponding identity value
|
|
@@ -476,7 +472,7 @@ export class SyncClient extends EventEmitter {
|
|
|
476
472
|
return { data: result, healed };
|
|
477
473
|
}
|
|
478
474
|
/**
|
|
479
|
-
* Hydrate
|
|
475
|
+
* Hydrate InstanceCache with data from Database
|
|
480
476
|
* Called after bootstrap is complete
|
|
481
477
|
*/
|
|
482
478
|
async hydrateFromDatabase() {
|
|
@@ -591,7 +587,7 @@ export class SyncClient extends EventEmitter {
|
|
|
591
587
|
catch { }
|
|
592
588
|
}
|
|
593
589
|
/**
|
|
594
|
-
* Re-hydrate
|
|
590
|
+
* Re-hydrate InstanceCache from IndexedDB when the pool already has data.
|
|
595
591
|
*
|
|
596
592
|
* Unlike hydrateFromDatabase() (which uses addBatch and skips existing IDs),
|
|
597
593
|
* this method properly:
|
|
@@ -726,13 +722,14 @@ export class SyncClient extends EventEmitter {
|
|
|
726
722
|
return stats;
|
|
727
723
|
}
|
|
728
724
|
/**
|
|
729
|
-
*
|
|
730
|
-
* IndexedDB is only
|
|
731
|
-
*
|
|
732
|
-
* CRITICAL: Changes are captured BEFORE poolAction to prevent data loss.
|
|
733
|
-
* The captured changes are frozen and passed to queueMutation.
|
|
725
|
+
* Apply a mutation to a model optimistically and queue it for server sync.
|
|
726
|
+
* IndexedDB is updated only once the server confirms the change with a delta
|
|
727
|
+
* packet.
|
|
734
728
|
*
|
|
735
|
-
*
|
|
729
|
+
* A model's changes are captured before the pool action runs, because a pool
|
|
730
|
+
* operation such as an upsert can clear the model's local change set;
|
|
731
|
+
* capturing first ensures those changes are never lost. The captured set is
|
|
732
|
+
* frozen and handed to {@link queueMutation}.
|
|
736
733
|
*/
|
|
737
734
|
mutate(type, model, poolAction, writeOptions) {
|
|
738
735
|
// No-op UPDATE guard (O(1)). An update with no dirty fields would travel
|
|
@@ -749,9 +746,9 @@ export class SyncClient extends EventEmitter {
|
|
|
749
746
|
// real write. Only a genuine Model with an empty dirty-set is skipped.
|
|
750
747
|
if (type === 'update' && model.hasChanges === false)
|
|
751
748
|
return;
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
//
|
|
749
|
+
// Capture changes before the pool action runs. Pool operations —
|
|
750
|
+
// upsert in particular — can clear the model's local changes, so
|
|
751
|
+
// capturing first ensures they are never lost.
|
|
755
752
|
const capturedChanges = type === 'update' || type === 'create' ? this.captureModelChanges(model) : undefined;
|
|
756
753
|
poolAction();
|
|
757
754
|
this.queueMutation({ type, model, timestamp: new Date(), capturedChanges, writeOptions });
|
|
@@ -862,8 +859,9 @@ export class SyncClient extends EventEmitter {
|
|
|
862
859
|
}
|
|
863
860
|
}
|
|
864
861
|
/**
|
|
865
|
-
* Upload file and create attachment
|
|
866
|
-
*
|
|
862
|
+
* Upload a file and create its attachment record. The upload runs through
|
|
863
|
+
* the {@link TransactionQueue}, and a model is built from the server's
|
|
864
|
+
* response and added to the pool.
|
|
867
865
|
*/
|
|
868
866
|
async uploadFile(file, options) {
|
|
869
867
|
if (!this.userId || !this.organizationId) {
|
|
@@ -955,16 +953,17 @@ export class SyncClient extends EventEmitter {
|
|
|
955
953
|
this.mutate('archive', model, () => { this.objectPool.updateScope(model.id, ModelScope.archived); });
|
|
956
954
|
}
|
|
957
955
|
/**
|
|
958
|
-
* Append a mutation and schedule its sync work.
|
|
956
|
+
* Append a mutation to the pending queue and schedule its sync work.
|
|
959
957
|
*
|
|
960
|
-
*
|
|
961
|
-
* pushes
|
|
962
|
-
* process call. Without the deferral, queueing
|
|
963
|
-
*
|
|
964
|
-
* growing queue
|
|
958
|
+
* IndexedDB persistence and the server push are deferred to a microtask, so
|
|
959
|
+
* many pushes within the same tick collapse into a single serialization and
|
|
960
|
+
* a single process call. Without the deferral, queueing a hundred mutations
|
|
961
|
+
* at once — a large paste, a document import, bulk layer creation — would
|
|
962
|
+
* reserialize the whole growing queue a hundred times, an O(N²) cost in
|
|
963
|
+
* `model.toJSON()`.
|
|
965
964
|
*
|
|
966
|
-
* @param mutation.capturedChanges - Pre-captured changes
|
|
967
|
-
*
|
|
965
|
+
* @param mutation.capturedChanges - Pre-captured, frozen changes, used to
|
|
966
|
+
* avoid re-reading a model after pool operations that might clear them.
|
|
968
967
|
*/
|
|
969
968
|
queueMutation(mutation) {
|
|
970
969
|
this.pendingMutations.push(mutation);
|
|
@@ -1018,12 +1017,13 @@ export class SyncClient extends EventEmitter {
|
|
|
1018
1017
|
}
|
|
1019
1018
|
}
|
|
1020
1019
|
/**
|
|
1021
|
-
* Restore mutation queue from IndexedDB.
|
|
1020
|
+
* Restore the mutation queue from IndexedDB.
|
|
1022
1021
|
*
|
|
1023
|
-
* The persisted record was written by
|
|
1024
|
-
*
|
|
1025
|
-
* corrupt entries are dropped
|
|
1026
|
-
*
|
|
1022
|
+
* The persisted record was written by an earlier session, possibly by an
|
|
1023
|
+
* older build of the SDK, so each entry is validated as it is replayed:
|
|
1024
|
+
* corrupt entries are dropped and logged at debug level, and a failure is
|
|
1025
|
+
* never swallowed silently, because the survival of offline writes must be
|
|
1026
|
+
* observable.
|
|
1027
1027
|
*/
|
|
1028
1028
|
async restoreMutationQueue() {
|
|
1029
1029
|
if (!this.database || !this.userId)
|
|
@@ -1106,8 +1106,8 @@ export class SyncClient extends EventEmitter {
|
|
|
1106
1106
|
this.pendingMutations = [];
|
|
1107
1107
|
// Clear persisted queue before processing
|
|
1108
1108
|
await this.persistMutationQueue();
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1109
|
+
// Stage every mutation synchronously within the same event-loop tick;
|
|
1110
|
+
// the transaction queue's microtask batches and sends them together.
|
|
1111
1111
|
for (const mutation of mutations) {
|
|
1112
1112
|
// Skip mutations for deleted models (prevents "not found" errors)
|
|
1113
1113
|
if (mutation.type !== 'delete' && !this.objectPool.get(mutation.model.id)) {
|
|
@@ -1149,11 +1149,10 @@ export class SyncClient extends EventEmitter {
|
|
|
1149
1149
|
}
|
|
1150
1150
|
}
|
|
1151
1151
|
/**
|
|
1152
|
-
* Resolve
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* even when there are local changes, to maintain data consistency.
|
|
1152
|
+
* Resolve a conflict between the local model and incoming server data,
|
|
1153
|
+
* called while processing deltas from the WebSocket. Certain server states,
|
|
1154
|
+
* such as deletions and deactivations, always take precedence even when the
|
|
1155
|
+
* local model has unsynced changes, so the two sides stay consistent.
|
|
1157
1156
|
*/
|
|
1158
1157
|
resolveConflicts(localModel, serverData) {
|
|
1159
1158
|
const hasLocalChanges = localModel.hasChanges;
|
|
@@ -1218,9 +1217,9 @@ export class SyncClient extends EventEmitter {
|
|
|
1218
1217
|
return localModel;
|
|
1219
1218
|
}
|
|
1220
1219
|
/**
|
|
1221
|
-
* Extract critical state fields from server data
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1220
|
+
* Extract the critical state fields from server data. These are the states
|
|
1221
|
+
* that must be honored even when the local model has unsynced changes. The
|
|
1222
|
+
* conflict resolver reads exactly these fields and no others.
|
|
1224
1223
|
*/
|
|
1225
1224
|
extractCriticalState(serverData) {
|
|
1226
1225
|
const critical = {};
|
|
@@ -1267,8 +1266,6 @@ export class SyncClient extends EventEmitter {
|
|
|
1267
1266
|
await this.processPendingMutations();
|
|
1268
1267
|
this.setConnectionState('connected');
|
|
1269
1268
|
this.emit('sync:reconnected');
|
|
1270
|
-
// Clear offline timestamp
|
|
1271
|
-
this.offlineSince = undefined;
|
|
1272
1269
|
}
|
|
1273
1270
|
catch (error) {
|
|
1274
1271
|
getContext().observability.captureTransactionFailure({
|
|
@@ -1284,7 +1281,6 @@ export class SyncClient extends EventEmitter {
|
|
|
1284
1281
|
async handleDisconnection() {
|
|
1285
1282
|
getContext().observability.breadcrumb('Network disconnected', 'sync.offline');
|
|
1286
1283
|
this.setConnectionState('disconnected');
|
|
1287
|
-
this.offlineSince = new Date();
|
|
1288
1284
|
this.emit('sync:offline');
|
|
1289
1285
|
}
|
|
1290
1286
|
/**
|
|
@@ -1381,9 +1377,10 @@ export class SyncClient extends EventEmitter {
|
|
|
1381
1377
|
this.removeAllListeners();
|
|
1382
1378
|
}
|
|
1383
1379
|
/**
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1380
|
+
* Notify the {@link TransactionQueue} of an incoming delta so it can confirm
|
|
1381
|
+
* transactions by sync-id threshold. A transaction is confirmed once any
|
|
1382
|
+
* delta with an id at or beyond its `lastSyncId` threshold arrives.
|
|
1383
|
+
* @param syncId - The sync id of the received delta.
|
|
1387
1384
|
*/
|
|
1388
1385
|
onDeltaReceived(syncId) {
|
|
1389
1386
|
try {
|
|
@@ -1396,22 +1393,21 @@ export class SyncClient extends EventEmitter {
|
|
|
1396
1393
|
}
|
|
1397
1394
|
}
|
|
1398
1395
|
/**
|
|
1399
|
-
*
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
* Cancels pending transactions for children that reference the deleted parent.
|
|
1396
|
+
* Cancel pending transactions for child entities orphaned by a parent's
|
|
1397
|
+
* deletion. The store calls this when a delete delta arrives for a parent,
|
|
1398
|
+
* cancelling any queued writes on children that reference it.
|
|
1403
1399
|
*
|
|
1404
|
-
* @param childModelName - The child model type (
|
|
1405
|
-
* @param foreignKey - The
|
|
1406
|
-
* @param parentId - The deleted parent
|
|
1407
|
-
* @returns
|
|
1400
|
+
* @param childModelName - The child model type (for example, `SlideLayer`).
|
|
1401
|
+
* @param foreignKey - The foreign-key property name (for example, `slideId`).
|
|
1402
|
+
* @param parentId - The id of the deleted parent.
|
|
1403
|
+
* @returns The number of transactions cancelled.
|
|
1408
1404
|
*/
|
|
1409
1405
|
cancelTransactionsByForeignKey(childModelName, foreignKey, parentId) {
|
|
1410
1406
|
return this.transactionQueue.cancelTransactionsByForeignKey(childModelName, foreignKey, parentId);
|
|
1411
1407
|
}
|
|
1412
1408
|
/**
|
|
1413
|
-
* Wait for a transaction to be confirmed
|
|
1414
|
-
*
|
|
1409
|
+
* Wait for a transaction to be confirmed by its delta echo. Delegates to the
|
|
1410
|
+
* {@link TransactionQueue}, which handles the confirmation timeout.
|
|
1415
1411
|
*/
|
|
1416
1412
|
waitForDeltaConfirmation(transactionId) {
|
|
1417
1413
|
return this.transactionQueue.waitForConfirmation(transactionId);
|
|
@@ -1425,7 +1421,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1425
1421
|
/**
|
|
1426
1422
|
* Get sync statistics. Return type is inferred from the literal so
|
|
1427
1423
|
* the call site sees the actual shape — `connectionState` narrowed
|
|
1428
|
-
* to its three states, `objectPoolStats` typed by `
|
|
1424
|
+
* to its three states, `objectPoolStats` typed by `InstanceCache.getStats`.
|
|
1429
1425
|
*/
|
|
1430
1426
|
getSyncStats() {
|
|
1431
1427
|
return {
|
|
@@ -1460,25 +1456,26 @@ export class SyncClient extends EventEmitter {
|
|
|
1460
1456
|
* can render typed UI (toast keyed by `AbloError.type`, route-level
|
|
1461
1457
|
* "this entity reverted" boundaries, telemetry).
|
|
1462
1458
|
*
|
|
1463
|
-
* Distinct from `onTransactionEvent('failed', cb)`, which
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1459
|
+
* Distinct from `onTransactionEvent('failed', cb)`, which serves the
|
|
1460
|
+
* parameterless `pendingChanges` counter and intentionally drops the
|
|
1461
|
+
* payload. The two coexist: the counter callback stays lightweight, while
|
|
1462
|
+
* this typed listener drives user-visible surfaces.
|
|
1467
1463
|
*/
|
|
1468
1464
|
onMutationFailure(listener) {
|
|
1469
1465
|
this.transactionQueue.on('transaction:failed', listener);
|
|
1470
1466
|
return () => this.transactionQueue.off('transaction:failed', listener);
|
|
1471
1467
|
}
|
|
1472
1468
|
/**
|
|
1473
|
-
* Subscribe to
|
|
1469
|
+
* Subscribe to local transaction creation with the full {@link Transaction}
|
|
1474
1470
|
* payload (`type`, `modelName`, `modelId`, `data`, `previousData`). This is
|
|
1475
|
-
* the feed
|
|
1471
|
+
* the feed the store's local-mutation subscription taps for undo recording.
|
|
1476
1472
|
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* (reached
|
|
1480
|
-
* `subscribe('transaction:created')`
|
|
1481
|
-
*
|
|
1473
|
+
* It subscribes to the {@link TransactionQueue}'s emitter directly, since
|
|
1474
|
+
* that is the only emitter that fires `transaction:created`. The SyncClient's
|
|
1475
|
+
* own emitter (reached through {@link subscribe}) never rebroadcasts that
|
|
1476
|
+
* event, so routing undo through `subscribe('transaction:created')` would
|
|
1477
|
+
* record nothing. {@link onMutationFailure} taps the queue for the same
|
|
1478
|
+
* reason.
|
|
1482
1479
|
*/
|
|
1483
1480
|
onLocalTransaction(listener) {
|
|
1484
1481
|
this.transactionQueue.on('transaction:created', listener);
|
|
@@ -1574,11 +1571,11 @@ export class SyncClient extends EventEmitter {
|
|
|
1574
1571
|
assigneeId,
|
|
1575
1572
|
});
|
|
1576
1573
|
}
|
|
1577
|
-
// ── Delta + Bootstrap application (owns
|
|
1574
|
+
// ── Delta + Bootstrap application (owns InstanceCache writes) ──────────────
|
|
1578
1575
|
/**
|
|
1579
|
-
* Apply a batch of delta results from Database to the
|
|
1576
|
+
* Apply a batch of delta results from Database to the InstanceCache.
|
|
1580
1577
|
* Owns: model creation, upsert, remove, archive, conflict resolution.
|
|
1581
|
-
* Returns: nothing —
|
|
1578
|
+
* Returns: nothing — InstanceCache is updated in place.
|
|
1582
1579
|
*/
|
|
1583
1580
|
/**
|
|
1584
1581
|
* Mark a local transaction as optimistically applied. The matching
|
|
@@ -1600,12 +1597,12 @@ export class SyncClient extends EventEmitter {
|
|
|
1600
1597
|
return this.echoTracker.getMetrics();
|
|
1601
1598
|
}
|
|
1602
1599
|
/**
|
|
1603
|
-
* Package-internal accessor for the TransactionQueue. Used by
|
|
1604
|
-
* `Ablo.commits.create()` to route raw multi-
|
|
1605
|
-
* same retry-on-reconnect lane as the
|
|
1606
|
-
*
|
|
1607
|
-
* instance the SyncClient subscribes to.
|
|
1608
|
-
* consumers
|
|
1600
|
+
* Package-internal accessor for the {@link TransactionQueue}. Used by
|
|
1601
|
+
* `Ablo.commits.create()` to route raw multi-operation envelopes through the
|
|
1602
|
+
* same retry-on-reconnect lane as the model proxy path, and by tests to
|
|
1603
|
+
* exercise the queue's interaction with {@link markTransactionPending} on the
|
|
1604
|
+
* real instance the SyncClient subscribes to. It is not re-exported to SDK
|
|
1605
|
+
* consumers; `Ablo` is the public surface.
|
|
1609
1606
|
*/
|
|
1610
1607
|
getTransactionQueue() {
|
|
1611
1608
|
return this.transactionQueue;
|
|
@@ -1633,16 +1630,14 @@ export class SyncClient extends EventEmitter {
|
|
|
1633
1630
|
}
|
|
1634
1631
|
for (const result of dbResults) {
|
|
1635
1632
|
const { modelName, modelId, action, transactionId } = result;
|
|
1636
|
-
//
|
|
1637
|
-
//
|
|
1638
|
-
//
|
|
1639
|
-
//
|
|
1640
|
-
//
|
|
1641
|
-
// the
|
|
1642
|
-
//
|
|
1643
|
-
//
|
|
1644
|
-
// for the ~2s window before the matching DELETE confirmation
|
|
1645
|
-
// lands. See `OPTIMISTIC_RECONCILIATION.md` for the framing.
|
|
1633
|
+
// Echo detection: if this delta carries a transaction id that matches
|
|
1634
|
+
// one already applied optimistically, the pool already reflects the
|
|
1635
|
+
// mutation, so the pool operation is skipped. The IndexedDB write in
|
|
1636
|
+
// Database.processDeltaBatch still runs; only the in-memory pool update
|
|
1637
|
+
// is suppressed. This prevents a resurrection flicker: a server-confirmed
|
|
1638
|
+
// create arriving after the user has optimistically deleted the row would
|
|
1639
|
+
// otherwise re-add it for the brief window before the matching delete
|
|
1640
|
+
// confirmation lands.
|
|
1646
1641
|
if (this.echoTracker.consumeEcho(transactionId)) {
|
|
1647
1642
|
continue;
|
|
1648
1643
|
}
|
|
@@ -1708,17 +1703,15 @@ export class SyncClient extends EventEmitter {
|
|
|
1708
1703
|
break;
|
|
1709
1704
|
}
|
|
1710
1705
|
}
|
|
1711
|
-
// Reveal the whole frame in
|
|
1712
|
-
// `removeBatch
|
|
1713
|
-
// so calling them
|
|
1714
|
-
// boundary — a catch-up frame that adds
|
|
1715
|
-
// every dependent reaction
|
|
1716
|
-
//
|
|
1717
|
-
//
|
|
1718
|
-
//
|
|
1719
|
-
//
|
|
1720
|
-
// equivalent of Replicache's "atomically reveal the new state" — the
|
|
1721
|
-
// app never observes a partially-applied frame.
|
|
1706
|
+
// Reveal the whole frame in a single MobX action. `addBatch`,
|
|
1707
|
+
// `upsertBatch`, `removeBatch`, and `updateScope` are each individually
|
|
1708
|
+
// wrapped in an action, so calling them in sequence flushes reactions at
|
|
1709
|
+
// every action boundary — a catch-up frame that adds, updates, and removes
|
|
1710
|
+
// would fire every dependent reaction several times in a row, re-rendering
|
|
1711
|
+
// and re-sorting on each. Wrapping them in one outer `runInAction` defers
|
|
1712
|
+
// all reaction flushes to a single boundary, so dependents recompute
|
|
1713
|
+
// exactly once regardless of how many models or operation kinds the frame
|
|
1714
|
+
// touched. The app therefore never observes a partially applied frame.
|
|
1722
1715
|
runInAction(() => {
|
|
1723
1716
|
if (modelsToAdd.length > 0)
|
|
1724
1717
|
this.objectPool.addBatch(modelsToAdd, ModelScope.live);
|
|
@@ -1737,7 +1730,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1737
1730
|
});
|
|
1738
1731
|
}
|
|
1739
1732
|
/**
|
|
1740
|
-
* Apply bootstrap data to the
|
|
1733
|
+
* Apply bootstrap data to the InstanceCache with ghost removal.
|
|
1741
1734
|
* Owns: model creation, batch upsert, ghost detection + removal.
|
|
1742
1735
|
*/
|
|
1743
1736
|
applyBootstrapDataToPool(bootstrapData, protectedIds, options) {
|
|
@@ -1775,12 +1768,12 @@ export class SyncClient extends EventEmitter {
|
|
|
1775
1768
|
const recordId = data.id;
|
|
1776
1769
|
if (recordId)
|
|
1777
1770
|
idsForType.add(recordId);
|
|
1778
|
-
// Scoped backfill
|
|
1779
|
-
// a server watermark. If a concurrent live delta already
|
|
1780
|
-
// row past the snapshot, skip it
|
|
1781
|
-
// model
|
|
1782
|
-
// version guard
|
|
1783
|
-
// late, the row would already be clobbered.
|
|
1771
|
+
// Scoped backfill for the hydrate-on-enter path: a subset snapshot is
|
|
1772
|
+
// taken at a server watermark. If a concurrent live delta already
|
|
1773
|
+
// advanced this row past the snapshot, skip it. `createFromData`
|
|
1774
|
+
// mutates the pooled model in place to keep instances alive, so this
|
|
1775
|
+
// version guard has to run before it; a guard at the upsert layer would
|
|
1776
|
+
// be too late, because the row would already be clobbered.
|
|
1784
1777
|
if (options?.scoped && recordId) {
|
|
1785
1778
|
const existing = this.objectPool.get(recordId);
|
|
1786
1779
|
if (existing && !rawRecordIsNewer(data, existing)) {
|
|
@@ -1804,10 +1797,10 @@ export class SyncClient extends EventEmitter {
|
|
|
1804
1797
|
this.objectPool.upsertBatch(allModels, ModelScope.live);
|
|
1805
1798
|
const addedCount = this.objectPool.size - beforeSize;
|
|
1806
1799
|
const updatedCount = allModels.length - addedCount;
|
|
1807
|
-
// Ghost removal
|
|
1808
|
-
// valid for a
|
|
1809
|
-
// returned type. A
|
|
1810
|
-
// type that belong to other
|
|
1800
|
+
// Ghost removal: drop pool entities absent from the server snapshot. This
|
|
1801
|
+
// is valid only for a full bootstrap, where the snapshot is authoritative
|
|
1802
|
+
// for each returned type. A scoped subset snapshot must not remove rows of
|
|
1803
|
+
// the same type that belong to other, unhydrated groups.
|
|
1811
1804
|
let removedCount = 0;
|
|
1812
1805
|
if (!options?.scoped) {
|
|
1813
1806
|
const ghostIds = [];
|