@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
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* runtime, the HTTP commit path can share this projection too.
|
|
2
|
+
* The vocabulary a local transaction uses on the wire. This module defines the
|
|
3
|
+
* {@link Transaction} record and the helpers that turn a transaction into a
|
|
4
|
+
* wire-safe commit operation: schema-field projection
|
|
5
|
+
* ({@link projectCommitPayload}), write-option projection
|
|
6
|
+
* ({@link applyWriteOptions}), foreign-key-aware priority scoring
|
|
7
|
+
* ({@link computePriorityScore}), and the structural checks used to classify
|
|
8
|
+
* transport errors. Nothing here holds queue state or timers, so the batched
|
|
9
|
+
* queue path and the HTTP commit path share the same projection.
|
|
11
10
|
*/
|
|
12
11
|
import { getContext } from '../context.js';
|
|
13
12
|
import { getActiveRegistry } from '../ModelRegistry.js';
|
|
@@ -19,24 +18,22 @@ import { MutationOperationType } from '../types/index.js';
|
|
|
19
18
|
*/
|
|
20
19
|
const FRAMEWORK_KEYS = new Set(['__class', '__typename', 'clientId', 'syncStatus']);
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Projects a model's serialized data onto the fields declared in its schema and
|
|
22
|
+
* returns a wire-safe commit payload. It does two things:
|
|
24
23
|
*
|
|
25
|
-
* 1.
|
|
24
|
+
* 1. Drops framework-internal keys (`__class`, `__typename`, `clientId`,
|
|
26
25
|
* `syncStatus`) and anything not declared on the model's schema.
|
|
27
|
-
* 2.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* 2. Passes each declared field's value through unchanged, including
|
|
27
|
+
* JSON-typed fields, which are sent as objects rather than pre-serialized
|
|
28
|
+
* strings (see the note in the body for why).
|
|
30
29
|
*
|
|
31
|
-
* For updates (`dropUndefined: true`), `undefined` values are also
|
|
32
|
-
*
|
|
33
|
-
* server side.
|
|
30
|
+
* For updates (`dropUndefined: true`), `undefined` values are also removed so
|
|
31
|
+
* they are not written as `SET column = NULL` on the server.
|
|
34
32
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the caller gets whatever the Model serialized.
|
|
33
|
+
* Field metadata comes from the model registry, populated when the schema is
|
|
34
|
+
* registered at initialization. If a model has no registered field metadata —
|
|
35
|
+
* for example a manually registered model — projection passes the serialized
|
|
36
|
+
* data through unchanged apart from dropping the framework keys.
|
|
40
37
|
*/
|
|
41
38
|
export function projectCommitPayload(modelName, source, opts) {
|
|
42
39
|
const metadata = getActiveRegistry().getMetadata(modelName);
|
|
@@ -53,31 +50,27 @@ export function projectCommitPayload(modelName, source, opts) {
|
|
|
53
50
|
}
|
|
54
51
|
return out;
|
|
55
52
|
}
|
|
56
|
-
for (const
|
|
53
|
+
for (const key of Object.keys(fields)) {
|
|
57
54
|
if (!(key in source))
|
|
58
55
|
continue;
|
|
59
56
|
const value = source[key];
|
|
60
57
|
if (opts.dropUndefined && value === undefined)
|
|
61
58
|
continue;
|
|
62
|
-
// JSON-typed fields (
|
|
63
|
-
//
|
|
64
|
-
// here, which round-tripped incorrectly:
|
|
59
|
+
// JSON-typed fields (stored as jsonb on the server) are sent as objects,
|
|
60
|
+
// not pre-serialized strings. Pre-stringifying here corrupts a round trip:
|
|
65
61
|
//
|
|
66
|
-
// 1.
|
|
67
|
-
// 2.
|
|
68
|
-
// 3.
|
|
69
|
-
//
|
|
70
|
-
// 4.
|
|
71
|
-
// 5.
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// 6. That corrupt object lands in the next commit, stored in jsonb.
|
|
62
|
+
// 1. The client stringifies `position: {x, y}` to `'{"x":...}'`.
|
|
63
|
+
// 2. The server writes it to the jsonb column, parsing the string, fine.
|
|
64
|
+
// 3. The server's delta echoes the input, where `position` is still the
|
|
65
|
+
// string from step 1.
|
|
66
|
+
// 4. The client merges the delta and sets `model.position` to that string.
|
|
67
|
+
// 5. The next edit spreads the string character by character, producing a
|
|
68
|
+
// corrupted, index-keyed object.
|
|
69
|
+
// 6. That corrupt object lands in the next commit and is stored in jsonb.
|
|
75
70
|
//
|
|
76
|
-
// Sending objects avoids the
|
|
77
|
-
// the
|
|
78
|
-
//
|
|
79
|
-
// `json.serialize` (triggered by Postgres's ParameterDescription
|
|
80
|
-
// response identifying the column as type 3802 / jsonb).
|
|
71
|
+
// Sending objects avoids the mismatch: the value travels through the delta
|
|
72
|
+
// and the commit unchanged, and the Postgres driver serializes a JS object
|
|
73
|
+
// to jsonb correctly once the column is identified as jsonb.
|
|
81
74
|
out[key] = value;
|
|
82
75
|
}
|
|
83
76
|
return out;
|
|
@@ -85,19 +78,17 @@ export function projectCommitPayload(modelName, source, opts) {
|
|
|
85
78
|
export const normalizeModelKey = (modelName) => modelName.replace('Model', '').toLowerCase();
|
|
86
79
|
export const stripModelSuffix = (modelName) => modelName.replace('Model', '');
|
|
87
80
|
/**
|
|
88
|
-
*
|
|
81
|
+
* Returns the priority score used to order create operations by foreign-key
|
|
82
|
+
* depth, so a parent row commits before its children.
|
|
89
83
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* `
|
|
94
|
-
* names appear here, and consumer applications can override specific
|
|
95
|
-
* priorities via `configOverrides.modelCreatePriority` without touching the
|
|
96
|
-
* SDK.
|
|
84
|
+
* The score comes from a priority map on the runtime configuration, built once
|
|
85
|
+
* at initialization by walking the schema's `belongsTo` graph. No model names
|
|
86
|
+
* are hard-coded here, and an application can override specific priorities
|
|
87
|
+
* through `configOverrides.modelCreatePriority`.
|
|
97
88
|
*
|
|
98
|
-
* Non-create
|
|
99
|
-
* because the row already exists, so they all share
|
|
100
|
-
*
|
|
89
|
+
* Non-create operations (update, delete, archive, unarchive) need no
|
|
90
|
+
* foreign-key ordering because the row already exists, so they all share the
|
|
91
|
+
* configured default non-create priority.
|
|
101
92
|
*/
|
|
102
93
|
export const computePriorityScore = (type, modelName) => {
|
|
103
94
|
const { modelCreatePriority, defaultCreatePriority, defaultNonCreatePriority } = getContext().config;
|
|
@@ -117,11 +108,11 @@ export function hasStaleWriteOptions(options) {
|
|
|
117
108
|
options?.onStale !== undefined);
|
|
118
109
|
}
|
|
119
110
|
/**
|
|
120
|
-
*
|
|
121
|
-
* guards (`readAt
|
|
122
|
-
*
|
|
123
|
-
* mutation_log cache key
|
|
124
|
-
*
|
|
111
|
+
* Copies a transaction's `writeOptions` onto the wire operation. The
|
|
112
|
+
* stale-context guards (`readAt` and `onStale`) sit at the operation's root,
|
|
113
|
+
* while `idempotencyKey` and `label` go in its `options` slot — the
|
|
114
|
+
* `mutation_log` cache key and audit tag. This is the one place caller-supplied
|
|
115
|
+
* write options cross onto the wire.
|
|
125
116
|
*/
|
|
126
117
|
export function applyWriteOptions(op, transaction) {
|
|
127
118
|
const operation = op;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* so the leaf stays cycle-free and testable in isolation.
|
|
2
|
+
* Tracks whether the confirming delta for a write has arrived. It holds the
|
|
3
|
+
* acknowledgement watermark (through the shared {@link SyncPosition}), the
|
|
4
|
+
* per-transaction confirmation timeouts, and the retry-with-backoff and
|
|
5
|
+
* reconciliation policy for transactions in the `awaiting_delta` status. It
|
|
6
|
+
* reaches back to {@link TransactionQueue} only through the small
|
|
7
|
+
* {@link DeltaConfirmationContext} interface, not the queue class itself, so it
|
|
8
|
+
* has no cyclic dependency and can be tested on its own.
|
|
10
9
|
*/
|
|
11
10
|
import type { SyncPosition } from '../sync/syncPosition.js';
|
|
12
11
|
import type { Transaction } from './commitPayload.js';
|
|
13
12
|
/**
|
|
14
|
-
* The
|
|
15
|
-
* status
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* offline), and the shared client
|
|
19
|
-
* diagnostics read
|
|
13
|
+
* The subset of {@link TransactionQueue} that the confirmation tracker needs:
|
|
14
|
+
* store lookups and status changes, removing optimistic entries once a write
|
|
15
|
+
* confirms, the queue's event emitter (`transaction:completed`,
|
|
16
|
+
* `reconciliation:needed`, and so on), a connection check (so timeouts
|
|
17
|
+
* re-schedule instead of escalating while offline), and the shared client
|
|
18
|
+
* position (`noteAck` advances the acknowledgement cursor; diagnostics read the
|
|
19
|
+
* applied cursor).
|
|
20
20
|
*/
|
|
21
21
|
export interface DeltaConfirmationContext {
|
|
22
22
|
store: {
|
|
@@ -34,7 +34,6 @@ export interface DeltaConfirmationContext {
|
|
|
34
34
|
export declare class DeltaConfirmationTracker {
|
|
35
35
|
private readonly ctx;
|
|
36
36
|
private static readonly DELTA_MAX_RETRIES;
|
|
37
|
-
private static readonly DELTA_INITIAL_TIMEOUT_MS;
|
|
38
37
|
private static readonly DELTA_MAX_TIMEOUT_MS;
|
|
39
38
|
private deltaConfirmationTimeouts;
|
|
40
39
|
private deltaConfirmationRetries;
|
|
@@ -43,18 +42,17 @@ export declare class DeltaConfirmationTracker {
|
|
|
43
42
|
private get lastSeenSyncId();
|
|
44
43
|
noteAck(lastSyncId: number | undefined): void;
|
|
45
44
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @param syncId - The sync
|
|
45
|
+
* Confirms every awaiting transaction whose sync-id threshold this delta
|
|
46
|
+
* meets or exceeds.
|
|
47
|
+
* @param syncId - The sync id of the received delta.
|
|
49
48
|
*/
|
|
50
49
|
onDeltaReceived(syncId: number): void;
|
|
51
50
|
scheduleDeltaConfirmationTimeout(tx: Transaction, timeoutMs: number): void;
|
|
52
51
|
private cancelDeltaConfirmationTimeout;
|
|
53
52
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* callbacks against an already-cleared store (T1.19).
|
|
53
|
+
* Clears every armed confirmation timer, one per in-flight transaction.
|
|
54
|
+
* {@link TransactionQueue.dispose} calls this; without it a disposed queue
|
|
55
|
+
* would keep the process alive and fire callbacks against a cleared store.
|
|
58
56
|
*/
|
|
59
57
|
dispose(): void;
|
|
60
58
|
}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* so the leaf stays cycle-free and testable in isolation.
|
|
2
|
+
* Tracks whether the confirming delta for a write has arrived. It holds the
|
|
3
|
+
* acknowledgement watermark (through the shared {@link SyncPosition}), the
|
|
4
|
+
* per-transaction confirmation timeouts, and the retry-with-backoff and
|
|
5
|
+
* reconciliation policy for transactions in the `awaiting_delta` status. It
|
|
6
|
+
* reaches back to {@link TransactionQueue} only through the small
|
|
7
|
+
* {@link DeltaConfirmationContext} interface, not the queue class itself, so it
|
|
8
|
+
* has no cyclic dependency and can be tested on its own.
|
|
10
9
|
*/
|
|
11
10
|
import { getContext } from '../context.js';
|
|
12
11
|
export class DeltaConfirmationTracker {
|
|
13
12
|
ctx;
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// Retry configuration for delta confirmation, using exponential backoff.
|
|
14
|
+
// Maximum retries before requesting a full reconciliation.
|
|
16
15
|
static DELTA_MAX_RETRIES = 5;
|
|
17
|
-
//
|
|
18
|
-
static DELTA_INITIAL_TIMEOUT_MS = 30_000;
|
|
19
|
-
// Max timeout cap (like Replicache's maxDelayMs of 60s)
|
|
16
|
+
// Upper bound on the backoff timeout.
|
|
20
17
|
static DELTA_MAX_TIMEOUT_MS = 120_000;
|
|
21
|
-
//
|
|
22
|
-
//
|
|
18
|
+
// Pending confirmation timeouts for transactions awaiting their delta. On
|
|
19
|
+
// timeout the tracker retries with backoff rather than rolling back.
|
|
23
20
|
deltaConfirmationTimeouts = new Map();
|
|
24
21
|
// Track retry attempts per transaction for exponential backoff
|
|
25
22
|
deltaConfirmationRetries = new Map();
|
|
@@ -34,12 +31,12 @@ export class DeltaConfirmationTracker {
|
|
|
34
31
|
this.ctx.position.noteAck(lastSyncId);
|
|
35
32
|
}
|
|
36
33
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param syncId - The sync
|
|
34
|
+
* Confirms every awaiting transaction whose sync-id threshold this delta
|
|
35
|
+
* meets or exceeds.
|
|
36
|
+
* @param syncId - The sync id of the received delta.
|
|
40
37
|
*/
|
|
41
38
|
onDeltaReceived(syncId) {
|
|
42
|
-
//
|
|
39
|
+
// The cursor advances where the delta is applied (the store calls
|
|
43
40
|
// position.advanceApplied / advancePersisted); this hook only resolves
|
|
44
41
|
// confirmation thresholds against the incoming id.
|
|
45
42
|
const awaitingTxs = this.ctx.store.getByStatus('awaiting_delta');
|
|
@@ -82,7 +79,7 @@ export class DeltaConfirmationTracker {
|
|
|
82
79
|
}
|
|
83
80
|
// Log batch summary only if we confirmed something
|
|
84
81
|
if (confirmedCount > 0) {
|
|
85
|
-
//
|
|
82
|
+
// Leave a breadcrumb when transactions confirm.
|
|
86
83
|
getContext().observability.breadcrumb('Transactions confirmed via delta', 'sync.transaction', 'info', {
|
|
87
84
|
count: confirmedCount,
|
|
88
85
|
syncId,
|
|
@@ -90,15 +87,16 @@ export class DeltaConfirmationTracker {
|
|
|
90
87
|
});
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
90
|
+
// Schedule the confirmation wait for a transaction. On timeout the tracker
|
|
91
|
+
// retries with exponential backoff and requests reconciliation to catch up on
|
|
92
|
+
// missed deltas, rather than rolling back, which would discard state the
|
|
93
|
+
// server has already confirmed. A rollback happens only on an explicit server
|
|
94
|
+
// rejection, never on a timeout.
|
|
97
95
|
scheduleDeltaConfirmationTimeout(tx, timeoutMs) {
|
|
98
96
|
// Cancel any existing timeout for this transaction
|
|
99
97
|
this.cancelDeltaConfirmationTimeout(tx.id);
|
|
100
|
-
//
|
|
101
|
-
//
|
|
98
|
+
// Deliberately not an async callback: the body is fully synchronous, and
|
|
99
|
+
// `setTimeout(async …)` would turn any throw into an unhandled promise
|
|
102
100
|
// rejection instead of a catchable synchronous error.
|
|
103
101
|
const timeoutHandle = setTimeout(() => {
|
|
104
102
|
const currentTx = this.ctx.store.get(tx.id);
|
|
@@ -119,12 +117,6 @@ export class DeltaConfirmationTracker {
|
|
|
119
117
|
return;
|
|
120
118
|
}
|
|
121
119
|
const retryCount = this.deltaConfirmationRetries.get(tx.id) ?? 0;
|
|
122
|
-
const diagnosis = this.lastSeenSyncId === 0
|
|
123
|
-
? 'No deltas received - delta pipeline may be broken'
|
|
124
|
-
: currentTx.syncIdNeededForCompletion &&
|
|
125
|
-
this.lastSeenSyncId < currentTx.syncIdNeededForCompletion
|
|
126
|
-
? 'Delta not yet received - may be lost or delayed'
|
|
127
|
-
: 'Delta should have confirmed - possible race condition';
|
|
128
120
|
getContext().observability.captureReconciliation({
|
|
129
121
|
reason: 'delta_timeout',
|
|
130
122
|
model: tx.modelName,
|
|
@@ -135,14 +127,15 @@ export class DeltaConfirmationTracker {
|
|
|
135
127
|
connectionState: this.ctx.isConnected() ? 'connected' : 'disconnected',
|
|
136
128
|
});
|
|
137
129
|
if (retryCount < DeltaConfirmationTracker.DELTA_MAX_RETRIES) {
|
|
138
|
-
//
|
|
139
|
-
// The server already committed
|
|
130
|
+
// Retry: request reconciliation and re-schedule with exponential
|
|
131
|
+
// backoff. The server has already committed the mutation; only the
|
|
132
|
+
// delta is outstanding.
|
|
140
133
|
this.deltaConfirmationRetries.set(tx.id, retryCount + 1);
|
|
141
134
|
this.deltaConfirmationTimeouts.delete(tx.id);
|
|
142
135
|
// Exponential backoff: 30s → 60s → 120s → 120s → 120s (capped)
|
|
143
136
|
const nextTimeout = Math.min(timeoutMs * 2, DeltaConfirmationTracker.DELTA_MAX_TIMEOUT_MS);
|
|
144
|
-
//
|
|
145
|
-
//
|
|
137
|
+
// Request reconciliation so the client can cycle the connection and
|
|
138
|
+
// catch up on missed deltas from the server.
|
|
146
139
|
this.ctx.emit('reconciliation:needed', {
|
|
147
140
|
reason: 'delta_confirmation_timeout',
|
|
148
141
|
txId: tx.id,
|
|
@@ -163,10 +156,10 @@ export class DeltaConfirmationTracker {
|
|
|
163
156
|
this.scheduleDeltaConfirmationTimeout(tx, nextTimeout);
|
|
164
157
|
}
|
|
165
158
|
else {
|
|
166
|
-
//
|
|
167
|
-
// The
|
|
168
|
-
//
|
|
169
|
-
//
|
|
159
|
+
// Retries exhausted: persist the awaiting state instead of rolling back.
|
|
160
|
+
// The commit succeeded on the server, so the data exists there. Saving
|
|
161
|
+
// the awaiting state lets it survive the page closing; on the next
|
|
162
|
+
// session, reconnecting and catching up on deltas will confirm it.
|
|
170
163
|
this.deltaConfirmationRetries.delete(tx.id);
|
|
171
164
|
this.deltaConfirmationTimeouts.delete(tx.id);
|
|
172
165
|
getContext().observability.captureDeltaRetryExhausted({
|
|
@@ -176,7 +169,7 @@ export class DeltaConfirmationTracker {
|
|
|
176
169
|
retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
|
|
177
170
|
syncIdNeeded: currentTx.syncIdNeededForCompletion,
|
|
178
171
|
});
|
|
179
|
-
// Emit persist event
|
|
172
|
+
// Emit the persist event; the client performs the write to local storage.
|
|
180
173
|
this.ctx.emit('transaction:persist_awaiting', {
|
|
181
174
|
txId: tx.id,
|
|
182
175
|
model: tx.modelName,
|
|
@@ -208,10 +201,9 @@ export class DeltaConfirmationTracker {
|
|
|
208
201
|
this.deltaConfirmationRetries.delete(id);
|
|
209
202
|
}
|
|
210
203
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* callbacks against an already-cleared store (T1.19).
|
|
204
|
+
* Clears every armed confirmation timer, one per in-flight transaction.
|
|
205
|
+
* {@link TransactionQueue.dispose} calls this; without it a disposed queue
|
|
206
|
+
* would keep the process alive and fire callbacks against a cleared store.
|
|
215
207
|
*/
|
|
216
208
|
dispose() {
|
|
217
209
|
for (const timeoutHandle of this.deltaConfirmationTimeouts.values()) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-first apply and rollback bookkeeping for pending mutations.
|
|
3
|
+
*
|
|
4
|
+
* When a change is written before the server confirms it, these functions
|
|
5
|
+
* record it in a ledger and emit an `optimistic:*` event; separate listeners
|
|
6
|
+
* apply the change to the in-memory data and, on rollback, restore the value
|
|
7
|
+
* it replaced. Each function takes the ledger and a small event emitter rather
|
|
8
|
+
* than any larger object, so the rules have no dependencies of their own and
|
|
9
|
+
* can be tested in isolation.
|
|
10
|
+
*/
|
|
11
|
+
import type { Model } from '../Model.js';
|
|
12
|
+
import type { MutationInput, Transaction } from './commitPayload.js';
|
|
13
|
+
/**
|
|
14
|
+
* One tracked optimistic mutation: the live model plus the value it held
|
|
15
|
+
* before the change, kept so a rollback can restore it.
|
|
16
|
+
*/
|
|
17
|
+
export interface OptimisticUpdateEntry {
|
|
18
|
+
model: Model;
|
|
19
|
+
previousState: MutationInput | null | undefined;
|
|
20
|
+
transaction: Transaction;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The event emitter these functions use to announce each optimistic change and
|
|
24
|
+
* its rollback. You supply the implementation.
|
|
25
|
+
*/
|
|
26
|
+
export interface OptimisticEmitter {
|
|
27
|
+
emit(event: string, payload: unknown): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Record an optimistic create and announce it. There is no prior value to
|
|
31
|
+
* restore, so the rollback pre-image is `null`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function applyOptimisticCreate(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
|
|
34
|
+
/**
|
|
35
|
+
* Record an optimistic update and announce it, keeping the row's prior value so
|
|
36
|
+
* a rollback can put it back.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyOptimisticUpdate(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
|
|
39
|
+
/**
|
|
40
|
+
* Record an optimistic delete and announce it, keeping the deleted row so a
|
|
41
|
+
* rollback can restore it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyOptimisticDelete(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
|
|
44
|
+
/**
|
|
45
|
+
* Undo an optimistic mutation by its transaction id: emit `optimistic:rollback`
|
|
46
|
+
* with the saved pre-image so listeners can restore the prior value, then drop
|
|
47
|
+
* the ledger entry. Does nothing if the transaction was never tracked.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rollbackOptimistic(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, transaction: Transaction, reason?: string, error?: Error): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-first apply and rollback bookkeeping for pending mutations.
|
|
3
|
+
*
|
|
4
|
+
* When a change is written before the server confirms it, these functions
|
|
5
|
+
* record it in a ledger and emit an `optimistic:*` event; separate listeners
|
|
6
|
+
* apply the change to the in-memory data and, on rollback, restore the value
|
|
7
|
+
* it replaced. Each function takes the ledger and a small event emitter rather
|
|
8
|
+
* than any larger object, so the rules have no dependencies of their own and
|
|
9
|
+
* can be tested in isolation.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Record an optimistic create and announce it. There is no prior value to
|
|
13
|
+
* restore, so the rollback pre-image is `null`.
|
|
14
|
+
*/
|
|
15
|
+
export function applyOptimisticCreate(optimisticUpdates, emitter, model, transaction) {
|
|
16
|
+
optimisticUpdates.set(transaction.id, {
|
|
17
|
+
model,
|
|
18
|
+
previousState: null,
|
|
19
|
+
transaction,
|
|
20
|
+
});
|
|
21
|
+
emitter.emit('optimistic:create', { model, transaction });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Record an optimistic update and announce it, keeping the row's prior value so
|
|
25
|
+
* a rollback can put it back.
|
|
26
|
+
*/
|
|
27
|
+
export function applyOptimisticUpdate(optimisticUpdates, emitter, model, transaction) {
|
|
28
|
+
optimisticUpdates.set(transaction.id, {
|
|
29
|
+
model,
|
|
30
|
+
previousState: transaction.previousData,
|
|
31
|
+
transaction,
|
|
32
|
+
});
|
|
33
|
+
emitter.emit('optimistic:update', { model, transaction });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Record an optimistic delete and announce it, keeping the deleted row so a
|
|
37
|
+
* rollback can restore it.
|
|
38
|
+
*/
|
|
39
|
+
export function applyOptimisticDelete(optimisticUpdates, emitter, model, transaction) {
|
|
40
|
+
optimisticUpdates.set(transaction.id, {
|
|
41
|
+
model,
|
|
42
|
+
previousState: transaction.previousData,
|
|
43
|
+
transaction,
|
|
44
|
+
});
|
|
45
|
+
emitter.emit('optimistic:delete', { model, transaction });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Undo an optimistic mutation by its transaction id: emit `optimistic:rollback`
|
|
49
|
+
* with the saved pre-image so listeners can restore the prior value, then drop
|
|
50
|
+
* the ledger entry. Does nothing if the transaction was never tracked.
|
|
51
|
+
*/
|
|
52
|
+
export function rollbackOptimistic(optimisticUpdates, emitter, transaction, reason, error) {
|
|
53
|
+
const optimistic = optimisticUpdates.get(transaction.id);
|
|
54
|
+
if (!optimistic)
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
emitter.emit('optimistic:rollback', {
|
|
57
|
+
model: optimistic.model,
|
|
58
|
+
previousState: optimistic.previousState,
|
|
59
|
+
transaction,
|
|
60
|
+
reason: reason ?? 'unknown',
|
|
61
|
+
error,
|
|
62
|
+
});
|
|
63
|
+
optimisticUpdates.delete(transaction.id);
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The validation boundary for replaying persisted transactions after a restart.
|
|
3
3
|
*
|
|
4
|
-
* Rows read back from the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* replayed as garbage commits.
|
|
4
|
+
* Rows read back from the on-disk transaction store may have been written by an
|
|
5
|
+
* earlier run — possibly by an older version of this package, possibly
|
|
6
|
+
* corrupted. Rather than trust them, the schemas here validate exactly the
|
|
7
|
+
* fields the transaction queue and the offline-mutation restore read during
|
|
8
|
+
* replay. A row that fails to parse is dropped and reported, never replayed as
|
|
9
|
+
* a malformed commit.
|
|
11
10
|
*
|
|
12
|
-
* The same
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The same store also holds two kinds of rows that are not replayable
|
|
12
|
+
* transactions — the offline mutation queue (`type: 'queue'`) and delta-await
|
|
13
|
+
* markers (`type: 'awaiting_delta'`), each owned by another part of the client.
|
|
14
|
+
* {@link isNonReplayablePersistedRow} recognizes them so they are skipped
|
|
15
|
+
* quietly rather than flagged as corruption.
|
|
17
16
|
*/
|
|
18
17
|
import { z } from 'zod';
|
|
19
18
|
import type { Transaction } from './commitPayload.js';
|
|
20
19
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* identity context
|
|
20
|
+
* The shape of a persisted transaction that can be replayed: the fields the
|
|
21
|
+
* transaction queue reads when it re-enqueues the row — its id, operation type,
|
|
22
|
+
* model addressing, payload, and identity context. Any remaining bookkeeping is
|
|
23
|
+
* filled in with defaults when the row is rehydrated by
|
|
24
|
+
* {@link deserializePersistedTransaction}.
|
|
24
25
|
*/
|
|
25
26
|
export declare const persistedTransactionSchema: z.ZodObject<{
|
|
26
27
|
id: z.ZodString;
|
|
@@ -57,17 +58,22 @@ export declare const persistedTransactionSchema: z.ZodObject<{
|
|
|
57
58
|
localOnly: z.ZodOptional<z.ZodBoolean>;
|
|
58
59
|
}, z.core.$loose>;
|
|
59
60
|
export type PersistedReplayableTransaction = z.infer<typeof persistedTransactionSchema>;
|
|
60
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Reports whether a stored row is one of the non-transaction kinds, so callers
|
|
63
|
+
* skip it instead of treating it as a corrupt transaction.
|
|
64
|
+
*/
|
|
61
65
|
export declare function isNonReplayablePersistedRow(row: unknown): boolean;
|
|
62
66
|
/**
|
|
63
|
-
*
|
|
64
|
-
* or `null` when the row
|
|
65
|
-
*
|
|
67
|
+
* Validates one stored row and rehydrates it into a {@link Transaction} ready
|
|
68
|
+
* to replay, or returns `null` when the row fails validation. Bookkeeping
|
|
69
|
+
* fields the stored row lacks — status, attempts, priority, timestamp — are
|
|
70
|
+
* re-derived the same way a freshly staged transaction derives them.
|
|
66
71
|
*/
|
|
67
72
|
export declare function deserializePersistedTransaction(row: unknown): Transaction | null;
|
|
68
73
|
/**
|
|
69
|
-
*
|
|
70
|
-
* `mutations` array
|
|
74
|
+
* The shape of one entry in the persisted offline mutation queue — an item of
|
|
75
|
+
* the `'queue'` row's `mutations` array, carrying the fields read when the
|
|
76
|
+
* queue is restored on reconnect.
|
|
71
77
|
*/
|
|
72
78
|
export declare const persistedMutationSchema: z.ZodObject<{
|
|
73
79
|
type: z.ZodEnum<{
|