@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,44 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Holds the resume state of one WebSocket sync session: the `lastSyncId`
|
|
3
|
+
* watermark, which marks the highest delta the client has seen, and an opaque
|
|
4
|
+
* server cursor used for incremental sync. The transport carries both across
|
|
5
|
+
* reconnects so the session can resume where it left off.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* leaf instead of fields + accessors spread through the transport
|
|
8
|
-
* class. The advance DISCIPLINE (only move `lastSyncId` on a
|
|
9
|
-
* persistence-gated ack) is documented at the call sites in
|
|
10
|
-
* SyncWebSocket (`sendAck` / `handleDelta`).
|
|
11
|
-
*
|
|
12
|
-
* (The per-entity version vector that used to live here was a Go-era
|
|
13
|
-
* ghost — zero decision reads client- or server-side; one total-ordered
|
|
14
|
-
* log per plane makes the scalar `sync_id` watermark the causality
|
|
15
|
-
* token. Removed in W4a.)
|
|
7
|
+
* The watermark advances under a strict rule — it moves forward only on an
|
|
8
|
+
* acknowledgement that is gated on durable persistence — which the transport
|
|
9
|
+
* enforces at its acknowledgement and delta-handling call sites.
|
|
16
10
|
*/
|
|
17
11
|
export declare class SyncCursor {
|
|
18
12
|
lastSyncId: number;
|
|
19
13
|
syncCursor: string | null;
|
|
20
14
|
constructor(lastSyncId: number);
|
|
21
15
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
16
|
+
* Advances the watermark in response to an acknowledgement. This becomes the
|
|
17
|
+
* value the next incremental-sync request and the connect handshake send, and
|
|
18
|
+
* the value {@link SyncCursor.getLastSyncId} reports when persisting on a
|
|
19
|
+
* clean shutdown. The move is monotonic: a stale, lower acknowledgement never
|
|
20
|
+
* pulls the watermark backward.
|
|
26
21
|
*/
|
|
27
22
|
ackAdvance(syncId: number): void;
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Sets the watermark outright, used when restoring persisted state.
|
|
30
25
|
*/
|
|
31
26
|
setLastSyncId(syncId: number): void;
|
|
32
27
|
/**
|
|
33
|
-
*
|
|
28
|
+
* Sets the opaque server cursor used for incremental sync.
|
|
34
29
|
*/
|
|
35
30
|
setSyncCursor(cursor: string | null): void;
|
|
36
31
|
/**
|
|
37
|
-
*
|
|
32
|
+
* Returns the current opaque server cursor, or null if none is set.
|
|
38
33
|
*/
|
|
39
34
|
getSyncCursor(): string | null;
|
|
40
35
|
/**
|
|
41
|
-
*
|
|
36
|
+
* Returns the highest delta id seen this session, for persistence on a clean
|
|
37
|
+
* shutdown.
|
|
42
38
|
*/
|
|
43
39
|
getLastSyncId(): number;
|
|
44
40
|
}
|
package/dist/sync/syncCursor.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Holds the resume state of one WebSocket sync session: the `lastSyncId`
|
|
3
|
+
* watermark, which marks the highest delta the client has seen, and an opaque
|
|
4
|
+
* server cursor used for incremental sync. The transport carries both across
|
|
5
|
+
* reconnects so the session can resume where it left off.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* leaf instead of fields + accessors spread through the transport
|
|
8
|
-
* class. The advance DISCIPLINE (only move `lastSyncId` on a
|
|
9
|
-
* persistence-gated ack) is documented at the call sites in
|
|
10
|
-
* SyncWebSocket (`sendAck` / `handleDelta`).
|
|
11
|
-
*
|
|
12
|
-
* (The per-entity version vector that used to live here was a Go-era
|
|
13
|
-
* ghost — zero decision reads client- or server-side; one total-ordered
|
|
14
|
-
* log per plane makes the scalar `sync_id` watermark the causality
|
|
15
|
-
* token. Removed in W4a.)
|
|
7
|
+
* The watermark advances under a strict rule — it moves forward only on an
|
|
8
|
+
* acknowledgement that is gated on durable persistence — which the transport
|
|
9
|
+
* enforces at its acknowledgement and delta-handling call sites.
|
|
16
10
|
*/
|
|
17
11
|
export class SyncCursor {
|
|
18
12
|
lastSyncId;
|
|
@@ -22,10 +16,11 @@ export class SyncCursor {
|
|
|
22
16
|
this.syncCursor = null;
|
|
23
17
|
}
|
|
24
18
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
19
|
+
* Advances the watermark in response to an acknowledgement. This becomes the
|
|
20
|
+
* value the next incremental-sync request and the connect handshake send, and
|
|
21
|
+
* the value {@link SyncCursor.getLastSyncId} reports when persisting on a
|
|
22
|
+
* clean shutdown. The move is monotonic: a stale, lower acknowledgement never
|
|
23
|
+
* pulls the watermark backward.
|
|
29
24
|
*/
|
|
30
25
|
ackAdvance(syncId) {
|
|
31
26
|
if (syncId > this.lastSyncId) {
|
|
@@ -33,25 +28,26 @@ export class SyncCursor {
|
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
30
|
/**
|
|
36
|
-
*
|
|
31
|
+
* Sets the watermark outright, used when restoring persisted state.
|
|
37
32
|
*/
|
|
38
33
|
setLastSyncId(syncId) {
|
|
39
34
|
this.lastSyncId = syncId;
|
|
40
35
|
}
|
|
41
36
|
/**
|
|
42
|
-
*
|
|
37
|
+
* Sets the opaque server cursor used for incremental sync.
|
|
43
38
|
*/
|
|
44
39
|
setSyncCursor(cursor) {
|
|
45
40
|
this.syncCursor = cursor;
|
|
46
41
|
}
|
|
47
42
|
/**
|
|
48
|
-
*
|
|
43
|
+
* Returns the current opaque server cursor, or null if none is set.
|
|
49
44
|
*/
|
|
50
45
|
getSyncCursor() {
|
|
51
46
|
return this.syncCursor;
|
|
52
47
|
}
|
|
53
48
|
/**
|
|
54
|
-
*
|
|
49
|
+
* Returns the highest delta id seen this session, for persistence on a clean
|
|
50
|
+
* shutdown.
|
|
55
51
|
*/
|
|
56
52
|
getLastSyncId() {
|
|
57
53
|
return this.lastSyncId || 0;
|
package/dist/sync/syncPlan.d.ts
CHANGED
|
@@ -1,60 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* re-exports everything here so importers are unchanged.
|
|
2
|
+
* Derives a client's sync plan from its {@link Schema}. Walking the schema's
|
|
3
|
+
* models and relations, it produces two declarative arrays consumed when the
|
|
4
|
+
* store is constructed: the foreign-key indexes to register on the in-memory
|
|
5
|
+
* object pool, and the enrichment rules that attach related parents to
|
|
6
|
+
* incoming rows. See {@link deriveSyncPlanFromSchema}.
|
|
8
7
|
*/
|
|
9
8
|
import type { Schema } from '../schema/schema.js';
|
|
10
|
-
/** A foreign-key index to register on the
|
|
9
|
+
/** A foreign-key index to register on the in-memory object pool when the store is constructed. */
|
|
11
10
|
export interface ForeignKeyIndexSpec {
|
|
12
11
|
/**
|
|
13
|
-
* The child model
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Use the wire `__typename` casing (e.g., `'SlideLayer'`, not
|
|
18
|
-
* `'slideLayer'`) — that's the value `createFromData` stamps onto
|
|
19
|
-
* models and the pool indexes by.
|
|
12
|
+
* The name of the child model, where the foreign-key field lives, and the
|
|
13
|
+
* name the object pool indexes by. Use the wire type-name casing (for
|
|
14
|
+
* example `'SlideLayer'`, not `'slideLayer'`), since that is the value
|
|
15
|
+
* stamped onto reconstructed models and the key the pool looks up.
|
|
20
16
|
*/
|
|
21
17
|
readonly modelName: string;
|
|
22
|
-
/** The
|
|
18
|
+
/** The foreign-key field name on the child model, for example `'slideId'`. */
|
|
23
19
|
readonly fieldName: string;
|
|
24
20
|
}
|
|
25
21
|
/**
|
|
26
|
-
* A declarative
|
|
27
|
-
*
|
|
28
|
-
* When a delta for `modelName` arrives, after the model is constructed
|
|
29
|
-
* the base store reads `data[foreignKey]` from the payload, looks up
|
|
30
|
-
* the matching parent in the ObjectPool, and attaches it as
|
|
31
|
-
* `data[relationKey]`. Best-effort: if the parent isn't yet in the
|
|
32
|
-
* pool (e.g., arrived later in the same bootstrap batch), enrichment
|
|
33
|
-
* silently no-ops.
|
|
22
|
+
* A declarative rule for enriching an incoming row with its related parent.
|
|
34
23
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
24
|
+
* When a delta for `modelName` arrives and its row has been constructed, the
|
|
25
|
+
* store reads the row's `foreignKey` value, looks up the matching parent in
|
|
26
|
+
* the object pool, and attaches it under `relationKey`. Enrichment is
|
|
27
|
+
* best-effort: if the parent is not in the pool yet — for example, it arrives
|
|
28
|
+
* later in the same bootstrap batch — the step is skipped without error.
|
|
37
29
|
*/
|
|
38
30
|
export interface EnrichmentPlanEntry {
|
|
39
31
|
/** The child model whose incoming deltas should be enriched. */
|
|
40
32
|
readonly modelName: string;
|
|
41
|
-
/** The
|
|
33
|
+
/** The foreign-key field on the child that points at the parent's id. */
|
|
42
34
|
readonly foreignKey: string;
|
|
43
35
|
/** The property name under which to attach the parent model. */
|
|
44
36
|
readonly relationKey: string;
|
|
45
37
|
}
|
|
46
38
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* FK indexes and enrichment entries are pulled from each `belongsTo`
|
|
52
|
-
* relation where `options.index` / `options.enrich` is set. Relations
|
|
53
|
-
* without those options are skipped — this is an opt-in mechanism so
|
|
54
|
-
* adding a `belongsTo` never silently changes delta or lookup semantics.
|
|
39
|
+
* Walks a schema and derives the two sync-plan arrays used when the store is
|
|
40
|
+
* constructed: the foreign-key indexes to register on the object pool and the
|
|
41
|
+
* enrichment plan. See {@link ForeignKeyIndexSpec} and
|
|
42
|
+
* {@link EnrichmentPlanEntry}.
|
|
55
43
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
44
|
+
* Both are drawn from each `belongsTo` relation that sets `options.index` or
|
|
45
|
+
* `options.enrich`; relations without those options are skipped. Enabling them
|
|
46
|
+
* is opt-in, so adding a `belongsTo` relation never silently changes how deltas
|
|
47
|
+
* apply or how lookups resolve. A `hasMany` or `hasOne` relation registers its
|
|
48
|
+
* index on the target model, since that is where the foreign key lives. The
|
|
49
|
+
* function has no side effects and is called once at construction.
|
|
58
50
|
*/
|
|
59
51
|
export declare function deriveSyncPlanFromSchema(schema: Schema): {
|
|
60
52
|
enrichmentPlan: EnrichmentPlanEntry[];
|
package/dist/sync/syncPlan.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* re-exports everything here so importers are unchanged.
|
|
2
|
+
* Derives a client's sync plan from its {@link Schema}. Walking the schema's
|
|
3
|
+
* models and relations, it produces two declarative arrays consumed when the
|
|
4
|
+
* store is constructed: the foreign-key indexes to register on the in-memory
|
|
5
|
+
* object pool, and the enrichment rules that attach related parents to
|
|
6
|
+
* incoming rows. See {@link deriveSyncPlanFromSchema}.
|
|
8
7
|
*/
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* FK indexes and enrichment entries are pulled from each `belongsTo`
|
|
15
|
-
* relation where `options.index` / `options.enrich` is set. Relations
|
|
16
|
-
* without those options are skipped — this is an opt-in mechanism so
|
|
17
|
-
* adding a `belongsTo` never silently changes delta or lookup semantics.
|
|
9
|
+
* Walks a schema and derives the two sync-plan arrays used when the store is
|
|
10
|
+
* constructed: the foreign-key indexes to register on the object pool and the
|
|
11
|
+
* enrichment plan. See {@link ForeignKeyIndexSpec} and
|
|
12
|
+
* {@link EnrichmentPlanEntry}.
|
|
18
13
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* Both are drawn from each `belongsTo` relation that sets `options.index` or
|
|
15
|
+
* `options.enrich`; relations without those options are skipped. Enabling them
|
|
16
|
+
* is opt-in, so adding a `belongsTo` relation never silently changes how deltas
|
|
17
|
+
* apply or how lookups resolve. A `hasMany` or `hasOne` relation registers its
|
|
18
|
+
* index on the target model, since that is where the foreign key lives. The
|
|
19
|
+
* function has no side effects and is called once at construction.
|
|
21
20
|
*/
|
|
22
21
|
export function deriveSyncPlanFromSchema(schema) {
|
|
23
22
|
const enrichmentPlan = [];
|
|
@@ -38,9 +37,9 @@ export function deriveSyncPlanFromSchema(schema) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
else if (rel.type === 'hasMany' || rel.type === 'hasOne') {
|
|
41
|
-
// hasMany
|
|
42
|
-
//
|
|
43
|
-
//
|
|
40
|
+
// For hasMany and hasOne, the foreign key lives on the target model,
|
|
41
|
+
// not the current one, so register the index on the target. Its wire
|
|
42
|
+
// type name is resolved from the schema here.
|
|
44
43
|
const targetDef = schema.models[rel.target];
|
|
45
44
|
const targetTypename = targetDef?.typename ?? rel.target;
|
|
46
45
|
foreignKeyIndexes.push({ modelName: targetTypename, fieldName: rel.foreignKey });
|
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Records where this client stands in the global delta order. It is a single
|
|
3
|
+
* typed object holding three related but distinct positions, each with its own
|
|
4
|
+
* rule for when it may advance. Keeping them separate is deliberate: collapsing
|
|
5
|
+
* them into one counter is a classic source of sync bugs.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* - `persisted` — the resume cursor. It advances only after deltas have
|
|
8
|
+
* committed to durable local storage. This is the value reconnect catch-up
|
|
9
|
+
* sends to the server, so it must never run ahead of what actually landed
|
|
10
|
+
* on disk; otherwise the server would skip deltas the client never stored.
|
|
9
11
|
*
|
|
10
|
-
* - `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - `applied` — the in-memory cursor: the last delta applied to the object
|
|
13
|
+
* pool. It drives the guards that deduplicate and reject replayed deltas.
|
|
14
|
+
* It may run ahead of `persisted`, because the pool is updated before the
|
|
15
|
+
* flush to disk, and behind what has merely been received, because
|
|
16
|
+
* bootstrap-queued deltas arrive before they are applied.
|
|
15
17
|
*
|
|
16
|
-
* - `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* - `acked` — the highest server position acknowledged for this client's own
|
|
19
|
+
* commits. An acknowledgement at N means the server applied our write at N;
|
|
20
|
+
* the optimistic pool already reflects it, so for the entities we wrote we
|
|
21
|
+
* have effectively read through N even before the echo returns on the
|
|
22
|
+
* stream.
|
|
20
23
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* One value is derived: `readFloor` is the greater of `applied` and `acked`,
|
|
25
|
+
* and is the only position a snapshot or claim should stamp as its read point.
|
|
26
|
+
* Using the raw stream cursor alone would make a claim taken right after a
|
|
27
|
+
* confirmed write look stale against that write's own delta; using the raw
|
|
28
|
+
* acknowledgement alone would be wrong for read-only clients. The maximum is
|
|
29
|
+
* correct per entity, because a competing change to an entity we just wrote
|
|
30
|
+
* necessarily lands above our acknowledgement and still rejects as stale.
|
|
25
31
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* ABOVE our ack and still stale-rejects.
|
|
32
|
-
*
|
|
33
|
-
* The Zod schema IS the state shape — the class holds exactly one
|
|
34
|
-
* `SyncPositionSnapshot` and applies monotonic merges to it, so
|
|
35
|
-
* snapshot/restore are identity-shaped and the schema is the single gate
|
|
36
|
-
* for anything loaded from disk (`parseSyncPosition`; a corrupted stored
|
|
37
|
-
* cursor "ahead of reality" is an existing, known failure mode).
|
|
32
|
+
* The validation schema is the state shape: the class holds exactly one
|
|
33
|
+
* {@link SyncPositionSnapshot} and merges monotonically into it, so snapshot
|
|
34
|
+
* and restore share that shape and {@link parseSyncPosition} is the single gate
|
|
35
|
+
* for anything loaded from disk — a corrupted cursor stored "ahead of reality"
|
|
36
|
+
* being a known failure mode.
|
|
38
37
|
*/
|
|
39
38
|
import { z } from 'zod';
|
|
40
39
|
export declare const syncPositionSchema: z.ZodObject<{
|
|
@@ -44,35 +43,41 @@ export declare const syncPositionSchema: z.ZodObject<{
|
|
|
44
43
|
}, z.core.$strip>;
|
|
45
44
|
export type SyncPositionSnapshot = z.infer<typeof syncPositionSchema>;
|
|
46
45
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
* Only the `persisted` cursor is stored durably; `applied` and `acked` are
|
|
47
|
+
* not. On resume the object pool is rebuilt from the persisted state, so the
|
|
48
|
+
* correct restore is simply to advance `persisted` to the stored value, which
|
|
49
|
+
* also implies `applied`. `acked` starts at zero, because a past session's
|
|
50
|
+
* acknowledgements carry no read authority and the offline queue
|
|
51
|
+
* re-acknowledges its own replays.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Validates an untrusted value, such as one loaded from disk, into a position
|
|
55
|
+
* snapshot, or returns null when it does not match the schema.
|
|
55
56
|
*/
|
|
56
|
-
/** Validate a persisted/foreign value into a position snapshot. */
|
|
57
57
|
export declare function parseSyncPosition(value: unknown): SyncPositionSnapshot | null;
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
58
|
+
/**
|
|
59
|
+
* The live sync position: one instance per client. Three producers each
|
|
60
|
+
* advance their own cursor, and consumers read the result.
|
|
61
|
+
*/
|
|
60
62
|
export declare class SyncPosition {
|
|
61
63
|
#private;
|
|
62
|
-
/**
|
|
64
|
+
/** Returns a copy of the current state in the schema's shape. */
|
|
63
65
|
snapshot(): SyncPositionSnapshot;
|
|
64
66
|
get persisted(): number;
|
|
65
67
|
get applied(): number;
|
|
66
68
|
get acked(): number;
|
|
67
|
-
/**
|
|
69
|
+
/** The position a snapshot or claim stamps as its read point: the greater of `applied` and `acked`. */
|
|
68
70
|
get readFloor(): number;
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
+
/**
|
|
72
|
+
* Records that deltas through `syncId` have committed to durable local
|
|
73
|
+
* storage. This also advances `applied`, since the flush applies each delta
|
|
74
|
+
* before or as it persists.
|
|
75
|
+
*/
|
|
71
76
|
advancePersisted(syncId: number): void;
|
|
72
|
-
/**
|
|
77
|
+
/** Records that a delta was applied to the in-memory object pool. */
|
|
73
78
|
advanceApplied(syncId: number): void;
|
|
74
|
-
/**
|
|
79
|
+
/** Records that the server acknowledged one of this client's commits at the given position. */
|
|
75
80
|
noteAck(lastSyncId: number | undefined): void;
|
|
76
|
-
/**
|
|
81
|
+
/** Restores from an already-validated snapshot, for example on resume from disk. The merge is monotonic. */
|
|
77
82
|
restore(snapshot: SyncPositionSnapshot): void;
|
|
78
83
|
}
|
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Records where this client stands in the global delta order. It is a single
|
|
3
|
+
* typed object holding three related but distinct positions, each with its own
|
|
4
|
+
* rule for when it may advance. Keeping them separate is deliberate: collapsing
|
|
5
|
+
* them into one counter is a classic source of sync bugs.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* - `persisted` — the resume cursor. It advances only after deltas have
|
|
8
|
+
* committed to durable local storage. This is the value reconnect catch-up
|
|
9
|
+
* sends to the server, so it must never run ahead of what actually landed
|
|
10
|
+
* on disk; otherwise the server would skip deltas the client never stored.
|
|
9
11
|
*
|
|
10
|
-
* - `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - `applied` — the in-memory cursor: the last delta applied to the object
|
|
13
|
+
* pool. It drives the guards that deduplicate and reject replayed deltas.
|
|
14
|
+
* It may run ahead of `persisted`, because the pool is updated before the
|
|
15
|
+
* flush to disk, and behind what has merely been received, because
|
|
16
|
+
* bootstrap-queued deltas arrive before they are applied.
|
|
15
17
|
*
|
|
16
|
-
* - `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* - `acked` — the highest server position acknowledged for this client's own
|
|
19
|
+
* commits. An acknowledgement at N means the server applied our write at N;
|
|
20
|
+
* the optimistic pool already reflects it, so for the entities we wrote we
|
|
21
|
+
* have effectively read through N even before the echo returns on the
|
|
22
|
+
* stream.
|
|
20
23
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* One value is derived: `readFloor` is the greater of `applied` and `acked`,
|
|
25
|
+
* and is the only position a snapshot or claim should stamp as its read point.
|
|
26
|
+
* Using the raw stream cursor alone would make a claim taken right after a
|
|
27
|
+
* confirmed write look stale against that write's own delta; using the raw
|
|
28
|
+
* acknowledgement alone would be wrong for read-only clients. The maximum is
|
|
29
|
+
* correct per entity, because a competing change to an entity we just wrote
|
|
30
|
+
* necessarily lands above our acknowledgement and still rejects as stale.
|
|
25
31
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* ABOVE our ack and still stale-rejects.
|
|
32
|
-
*
|
|
33
|
-
* The Zod schema IS the state shape — the class holds exactly one
|
|
34
|
-
* `SyncPositionSnapshot` and applies monotonic merges to it, so
|
|
35
|
-
* snapshot/restore are identity-shaped and the schema is the single gate
|
|
36
|
-
* for anything loaded from disk (`parseSyncPosition`; a corrupted stored
|
|
37
|
-
* cursor "ahead of reality" is an existing, known failure mode).
|
|
32
|
+
* The validation schema is the state shape: the class holds exactly one
|
|
33
|
+
* {@link SyncPositionSnapshot} and merges monotonically into it, so snapshot
|
|
34
|
+
* and restore share that shape and {@link parseSyncPosition} is the single gate
|
|
35
|
+
* for anything loaded from disk — a corrupted cursor stored "ahead of reality"
|
|
36
|
+
* being a known failure mode.
|
|
38
37
|
*/
|
|
39
38
|
import { z } from 'zod';
|
|
40
39
|
export const syncPositionSchema = z.object({
|
|
41
|
-
/**
|
|
40
|
+
/** The resume cursor; advances only after deltas persist to durable local storage. */
|
|
42
41
|
persisted: z.number().int().nonnegative(),
|
|
43
|
-
/**
|
|
42
|
+
/** The in-memory cursor: the last delta applied to the object pool. */
|
|
44
43
|
applied: z.number().int().nonnegative(),
|
|
45
|
-
/**
|
|
44
|
+
/** The highest server position acknowledged for this client's own commits. */
|
|
46
45
|
acked: z.number().int().nonnegative(),
|
|
47
46
|
});
|
|
48
47
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
* Only the `persisted` cursor is stored durably; `applied` and `acked` are
|
|
49
|
+
* not. On resume the object pool is rebuilt from the persisted state, so the
|
|
50
|
+
* correct restore is simply to advance `persisted` to the stored value, which
|
|
51
|
+
* also implies `applied`. `acked` starts at zero, because a past session's
|
|
52
|
+
* acknowledgements carry no read authority and the offline queue
|
|
53
|
+
* re-acknowledges its own replays.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Validates an untrusted value, such as one loaded from disk, into a position
|
|
57
|
+
* snapshot, or returns null when it does not match the schema.
|
|
57
58
|
*/
|
|
58
|
-
/** Validate a persisted/foreign value into a position snapshot. */
|
|
59
59
|
export function parseSyncPosition(value) {
|
|
60
60
|
const result = syncPositionSchema.safeParse(value);
|
|
61
61
|
return result.success ? result.data : null;
|
|
@@ -69,11 +69,13 @@ function advance(state, next) {
|
|
|
69
69
|
acked: Math.max(state.acked, next.acked ?? 0),
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
*
|
|
72
|
+
/**
|
|
73
|
+
* The live sync position: one instance per client. Three producers each
|
|
74
|
+
* advance their own cursor, and consumers read the result.
|
|
75
|
+
*/
|
|
74
76
|
export class SyncPosition {
|
|
75
77
|
#state = ZERO;
|
|
76
|
-
/**
|
|
78
|
+
/** Returns a copy of the current state in the schema's shape. */
|
|
77
79
|
snapshot() {
|
|
78
80
|
return { ...this.#state };
|
|
79
81
|
}
|
|
@@ -86,25 +88,28 @@ export class SyncPosition {
|
|
|
86
88
|
get acked() {
|
|
87
89
|
return this.#state.acked;
|
|
88
90
|
}
|
|
89
|
-
/**
|
|
91
|
+
/** The position a snapshot or claim stamps as its read point: the greater of `applied` and `acked`. */
|
|
90
92
|
get readFloor() {
|
|
91
93
|
return Math.max(this.#state.applied, this.#state.acked);
|
|
92
94
|
}
|
|
93
|
-
/**
|
|
94
|
-
*
|
|
95
|
+
/**
|
|
96
|
+
* Records that deltas through `syncId` have committed to durable local
|
|
97
|
+
* storage. This also advances `applied`, since the flush applies each delta
|
|
98
|
+
* before or as it persists.
|
|
99
|
+
*/
|
|
95
100
|
advancePersisted(syncId) {
|
|
96
101
|
this.#state = advance(this.#state, { persisted: syncId, applied: syncId });
|
|
97
102
|
}
|
|
98
|
-
/**
|
|
103
|
+
/** Records that a delta was applied to the in-memory object pool. */
|
|
99
104
|
advanceApplied(syncId) {
|
|
100
105
|
this.#state = advance(this.#state, { applied: syncId });
|
|
101
106
|
}
|
|
102
|
-
/**
|
|
107
|
+
/** Records that the server acknowledged one of this client's commits at the given position. */
|
|
103
108
|
noteAck(lastSyncId) {
|
|
104
109
|
if (lastSyncId !== undefined)
|
|
105
110
|
this.#state = advance(this.#state, { acked: lastSyncId });
|
|
106
111
|
}
|
|
107
|
-
/**
|
|
112
|
+
/** Restores from an already-validated snapshot, for example on resume from disk. The merge is monotonic. */
|
|
108
113
|
restore(snapshot) {
|
|
109
114
|
this.#state = advance(this.#state, snapshot);
|
|
110
115
|
}
|