@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/sync/groupChange.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Handles the delta types that change which sync groups a session can see. A
|
|
3
|
+
* sync group is a fan-out scope the server uses to decide which entities a
|
|
4
|
+
* client receives. When a session's membership changes, these handlers update
|
|
5
|
+
* the client's subscription list; when access is revoked, they clear cached
|
|
6
|
+
* data and trigger a full re-bootstrap so revoked rows cannot linger on the
|
|
7
|
+
* device.
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* group-set math, the force-re-bootstrap trigger, and the security-critical
|
|
7
|
-
* shrinkage check. The store keeps thin protected delegates with unchanged
|
|
8
|
-
* signatures — subclass override points stay overridable, and the leaf
|
|
9
|
-
* routes every cross-handler call back through the minimal
|
|
10
|
-
* {@link GroupChangeContext} so dynamic dispatch is preserved.
|
|
9
|
+
* Every handler takes a {@link GroupChangeContext}, the narrow facade through
|
|
10
|
+
* which it reaches the client's local storage and connection lifecycle hooks.
|
|
11
11
|
*/
|
|
12
12
|
import { getContext } from '../context.js';
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
13
|
+
/**
|
|
14
|
+
* Marker returned when a group-change payload cannot be parsed, kept distinct
|
|
15
|
+
* from a valid null or absent payload, which the handlers accept normally.
|
|
16
|
+
*/
|
|
15
17
|
const MALFORMED_PAYLOAD = Symbol('malformed-group-change-payload');
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* carried
|
|
19
|
+
* Parses a group-change delta payload without ever throwing. The server sends
|
|
20
|
+
* these as JSON strings. If a frame is corrupt, this returns
|
|
21
|
+
* {@link MALFORMED_PAYLOAD} rather than raising, because an error escaping here
|
|
22
|
+
* would leave the delta pipeline after the watermark has already advanced. The
|
|
23
|
+
* delta is never re-delivered, so the security clear it carried would be lost.
|
|
22
24
|
*/
|
|
23
25
|
function parseGroupChangePayload(delta) {
|
|
24
26
|
if (typeof delta.data !== 'string')
|
|
@@ -36,41 +38,38 @@ function parseGroupChangePayload(delta) {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
40
|
-
* access changed but not how,
|
|
41
|
-
* data
|
|
41
|
+
* Fallback for a group-change delta that could not be read. Because we know
|
|
42
|
+
* access changed but not how, this treats it as a revocation: it clears cached
|
|
43
|
+
* data from both local storage and the in-memory pool, then forces a full
|
|
44
|
+
* re-bootstrap from the server.
|
|
42
45
|
*/
|
|
43
46
|
async function clearForUnknownGroupChange(ctx, delta, kind) {
|
|
44
47
|
getContext().logger.debug(`[BaseSyncedStore] Unreadable ${kind} payload — clearing cached data and re-bootstrapping`, { syncId: delta.id });
|
|
45
|
-
//
|
|
46
|
-
//
|
|
48
|
+
// Revoked data must not persist if the device goes offline before the
|
|
49
|
+
// re-bootstrap, the same reasoning as the explicit removed-groups path.
|
|
47
50
|
await ctx.database.clear();
|
|
48
51
|
ctx.objectPool.clear();
|
|
49
52
|
ctx.forceFullRebootstrap();
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* The server emits 'G' via two distinct pathways, distinguished by payload
|
|
55
|
-
* shape:
|
|
55
|
+
* Handles a 'G' (group-change) delta. The server sends two shapes of this
|
|
56
|
+
* delta, told apart by the payload:
|
|
56
57
|
*
|
|
57
|
-
* Incremental
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* - No re-bootstrap — entities arrive via the normal insert path.
|
|
58
|
+
* Incremental — `{ group, userId }`: the recipient was added to a single
|
|
59
|
+
* sync group. No re-bootstrap follows; the newly visible entities arrive as
|
|
60
|
+
* ordinary 'C' (covering) deltas through the normal insert path.
|
|
61
61
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* - Deprecated on the server; kept here for wire-level backward compat.
|
|
62
|
+
* Full diff — `{ addedGroups, removedGroups }`: one delta carrying the whole
|
|
63
|
+
* membership change. This forces a full re-bootstrap (disconnect, reconnect,
|
|
64
|
+
* and refetch), clearing cached data first if any group was removed.
|
|
66
65
|
*/
|
|
67
66
|
export async function handleSyncGroupChange(ctx, delta) {
|
|
68
67
|
const raw = parseGroupChangePayload(delta);
|
|
69
68
|
if (raw === MALFORMED_PAYLOAD) {
|
|
70
|
-
//
|
|
71
|
-
// delta will never be re-delivered
|
|
72
|
-
//
|
|
73
|
-
// data, and rebuild from server
|
|
69
|
+
// The payload is unreadable, so we cannot tell which groups changed, and
|
|
70
|
+
// this delta will never be re-delivered because the watermark has already
|
|
71
|
+
// advanced. Fall back to the safe direction: assume a revocation, clear
|
|
72
|
+
// cached data, and rebuild from the server rather than throwing.
|
|
74
73
|
await clearForUnknownGroupChange(ctx, delta, 'sync-group change');
|
|
75
74
|
return;
|
|
76
75
|
}
|
|
@@ -84,7 +83,7 @@ export async function handleSyncGroupChange(ctx, delta) {
|
|
|
84
83
|
await ctx.handleGroupAdded(incremental, delta.id);
|
|
85
84
|
return;
|
|
86
85
|
}
|
|
87
|
-
//
|
|
86
|
+
// Full-diff payload: { addedGroups, removedGroups }
|
|
88
87
|
const payload = {
|
|
89
88
|
removedGroups: rawObj.removedGroups ?? [],
|
|
90
89
|
addedGroups: rawObj.addedGroups ?? [],
|
|
@@ -94,9 +93,9 @@ export async function handleSyncGroupChange(ctx, delta) {
|
|
|
94
93
|
addedGroups: payload.addedGroups,
|
|
95
94
|
syncId: delta.id,
|
|
96
95
|
});
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
96
|
+
// If any groups were removed, clear cached data immediately so revoked data
|
|
97
|
+
// cannot persist should the device go offline before the re-bootstrap
|
|
98
|
+
// completes.
|
|
100
99
|
if (payload.removedGroups.length > 0) {
|
|
101
100
|
await ctx.database.clear();
|
|
102
101
|
ctx.objectPool.clear();
|
|
@@ -109,11 +108,10 @@ export async function handleSyncGroupChange(ctx, delta) {
|
|
|
109
108
|
ctx.forceFullRebootstrap();
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* each newly-visible entity, which flow through the normal insert path.
|
|
111
|
+
* Handles an incremental group-added delta. It records the new sync group in
|
|
112
|
+
* the subscription metadata without forcing a re-bootstrap; the server then
|
|
113
|
+
* sends a 'C' (covering) delta for each newly visible entity, which flows
|
|
114
|
+
* through the normal insert path.
|
|
117
115
|
*/
|
|
118
116
|
export async function handleGroupAdded(ctx, payload, syncId) {
|
|
119
117
|
getContext().logger.info('[BaseSyncedStore] Group added (incremental)', {
|
|
@@ -123,26 +121,20 @@ export async function handleGroupAdded(ctx, payload, syncId) {
|
|
|
123
121
|
const current = new Set(ctx.getSubscribedSyncGroups());
|
|
124
122
|
current.add(payload.group);
|
|
125
123
|
await ctx.database.updateWorkspaceMetadata({ subscribedSyncGroups: Array.from(current) });
|
|
126
|
-
//
|
|
124
|
+
// No forceFullRebootstrap() here; the covering deltas will bring the entities.
|
|
127
125
|
}
|
|
128
126
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* selectively purge entities belonging to that group. The safe fallback
|
|
134
|
-
* is the legacy behavior: clear local state and force a re-bootstrap
|
|
135
|
-
* with the updated group list.
|
|
136
|
-
*
|
|
137
|
-
* Future optimization: track group membership in the ObjectPool so 'S'
|
|
138
|
-
* can do a targeted purge instead of a full re-bootstrap.
|
|
127
|
+
* Handles an 'S' (group-removed) delta, which signals the recipient has lost
|
|
128
|
+
* access to a sync group. The client does not track which entities belong to
|
|
129
|
+
* which group, so it cannot purge only the affected rows; instead it clears
|
|
130
|
+
* local state and forces a re-bootstrap with the updated group list.
|
|
139
131
|
*/
|
|
140
132
|
export async function handleGroupRemoved(ctx, delta) {
|
|
141
133
|
const raw = parseGroupChangePayload(delta);
|
|
142
134
|
if (raw === MALFORMED_PAYLOAD) {
|
|
143
|
-
//
|
|
144
|
-
// group.
|
|
145
|
-
//
|
|
135
|
+
// The payload is unreadable: access was revoked but we cannot tell which
|
|
136
|
+
// group. Fall back to a full clear, the safe direction for an
|
|
137
|
+
// access-revocation delta, rather than throwing.
|
|
146
138
|
await clearForUnknownGroupChange(ctx, delta, 'group-removed');
|
|
147
139
|
return;
|
|
148
140
|
}
|
|
@@ -158,9 +150,9 @@ export async function handleGroupRemoved(ctx, delta) {
|
|
|
158
150
|
group: groupKey,
|
|
159
151
|
syncId: delta.id,
|
|
160
152
|
});
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
153
|
+
// Clear cached data before the re-bootstrap so revoked-group data cannot
|
|
154
|
+
// persist if the device goes offline between receiving this delta and
|
|
155
|
+
// completing the re-bootstrap.
|
|
164
156
|
await ctx.database.clear();
|
|
165
157
|
ctx.objectPool.clear();
|
|
166
158
|
// Update subscription metadata so the re-bootstrap fetches the
|
|
@@ -170,7 +162,7 @@ export async function handleGroupRemoved(ctx, delta) {
|
|
|
170
162
|
await ctx.database.updateWorkspaceMetadata({ subscribedSyncGroups: Array.from(current) });
|
|
171
163
|
ctx.forceFullRebootstrap();
|
|
172
164
|
}
|
|
173
|
-
/**
|
|
165
|
+
/** Computes the new sync-group set after applying the additions and removals in a diff. */
|
|
174
166
|
export function computeUpdatedSyncGroups(ctx, payload) {
|
|
175
167
|
const current = new Set(ctx.getSubscribedSyncGroups());
|
|
176
168
|
for (const g of payload.removedGroups)
|
|
@@ -179,12 +171,12 @@ export function computeUpdatedSyncGroups(ctx, payload) {
|
|
|
179
171
|
current.add(g);
|
|
180
172
|
return Array.from(current);
|
|
181
173
|
}
|
|
182
|
-
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
174
|
+
/**
|
|
175
|
+
* Forces a full re-bootstrap by marking local storage as needing one,
|
|
176
|
+
* disconnecting, and emitting a connection lifecycle event that the reconnect
|
|
177
|
+
* path acts on. Does nothing for participants whose bootstrap mode is 'none':
|
|
178
|
+
* they never pull a baseline, so after a trigger such as a sync-group shrink or
|
|
179
|
+
* an access revocation they rely on covering deltas to repopulate the data they
|
|
188
180
|
* subscribe to.
|
|
189
181
|
*/
|
|
190
182
|
export function forceFullRebootstrap(ctx) {
|
|
@@ -197,12 +189,12 @@ export function forceFullRebootstrap(ctx) {
|
|
|
197
189
|
ctx.emitConnectionEvent('WS_DISCONNECTED');
|
|
198
190
|
}
|
|
199
191
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* here so the
|
|
205
|
-
*
|
|
192
|
+
* Resolves the sync-group list this session subscribes to, and is the single
|
|
193
|
+
* place that decision is made. The server-issued `context.syncGroups` is
|
|
194
|
+
* authoritative; when it is absent, the session subscribes to no explicit
|
|
195
|
+
* groups. {@link checkSyncGroupShrinkage} and connection setup both read
|
|
196
|
+
* through here, so the live subscription and the access-revocation check can
|
|
197
|
+
* never disagree.
|
|
206
198
|
*/
|
|
207
199
|
export function resolveSyncGroups(context) {
|
|
208
200
|
if (context.syncGroups && context.syncGroups.length > 0) {
|
|
@@ -210,7 +202,11 @@ export function resolveSyncGroups(context) {
|
|
|
210
202
|
}
|
|
211
203
|
return [];
|
|
212
204
|
}
|
|
213
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Compares the session's current sync groups against the set stored from the
|
|
207
|
+
* last session. If any group is now missing, access has narrowed, so this
|
|
208
|
+
* clears cached data and forces a full bootstrap before recording the new set.
|
|
209
|
+
*/
|
|
214
210
|
export async function checkSyncGroupShrinkage(ctx) {
|
|
215
211
|
const currentSyncGroups = ctx.getCurrentSyncGroups();
|
|
216
212
|
if (!currentSyncGroups)
|
|
@@ -228,8 +224,8 @@ export async function checkSyncGroupShrinkage(ctx) {
|
|
|
228
224
|
storedCount: stored.length,
|
|
229
225
|
currentCount: currentGroups.size,
|
|
230
226
|
});
|
|
231
|
-
//
|
|
232
|
-
//
|
|
227
|
+
// Clear cached data before the re-bootstrap so revoked-group data cannot
|
|
228
|
+
// persist if the device goes offline first.
|
|
233
229
|
await ctx.database.clear();
|
|
234
230
|
ctx.objectPool.clear();
|
|
235
231
|
ctx.database.markRequiresFullBootstrap();
|
package/dist/sync/heartbeat.d.ts
CHANGED
|
@@ -1,55 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Application-level heartbeat for the sync WebSocket.
|
|
3
3
|
*
|
|
4
|
-
* The browser WebSocket API hides RFC 6455 protocol-level ping
|
|
5
|
-
* JavaScript, so the server's
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* NAT timeout, mobile handoff).
|
|
9
|
-
* `{ type: 'ping' }` every
|
|
10
|
-
*
|
|
11
|
-
* proof
|
|
12
|
-
*
|
|
4
|
+
* The browser WebSocket API hides RFC 6455 protocol-level ping and pong frames
|
|
5
|
+
* from JavaScript, so the server's keepalive cannot be observed by client
|
|
6
|
+
* code. That leaves the client unable to tell a healthy idle connection apart
|
|
7
|
+
* from a "zombie" socket whose underlying TCP connection has silently broken
|
|
8
|
+
* (laptop sleep, NAT timeout, mobile handoff). To close the gap, the client
|
|
9
|
+
* sends an application-level `{ type: 'ping' }` every 30 seconds and
|
|
10
|
+
* force-closes the socket if no inbound traffic arrives within 10 seconds. Any
|
|
11
|
+
* inbound message counts as proof the connection is alive; the explicit `pong`
|
|
12
|
+
* merely guarantees that something arrives even on an otherwise idle stream.
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* lease window is derived from
|
|
15
|
+
* The interval between application-level pings, shared with both sides of the
|
|
16
|
+
* connection: the client pings at the same {@link PING_INTERVAL_MS} the server
|
|
17
|
+
* uses for its own keepalive, and the claim lease window is derived from the
|
|
18
|
+
* same constant.
|
|
18
19
|
*/
|
|
19
20
|
export declare const HEARTBEAT_INTERVAL_MS = 30000;
|
|
20
21
|
export declare const HEARTBEAT_TIMEOUT_MS = 10000;
|
|
21
22
|
/**
|
|
22
|
-
* The slice of the socket the heartbeat
|
|
23
|
-
*
|
|
23
|
+
* The narrow slice of the socket the heartbeat depends on. Keeping it small
|
|
24
|
+
* lets the {@link HeartbeatController} work without depending on the full
|
|
25
|
+
* WebSocket transport.
|
|
24
26
|
*/
|
|
25
27
|
export interface HeartbeatTransport {
|
|
26
|
-
/** True only while the underlying socket is
|
|
28
|
+
/** True only while the underlying socket is open. */
|
|
27
29
|
isSocketOpen(): boolean;
|
|
28
|
-
/**
|
|
30
|
+
/** Sends the `{ type: 'ping' }` frame; throws when the socket is already dead. */
|
|
29
31
|
sendPing(): void;
|
|
30
32
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* Closes the socket from the client side with a private 4xxx code, so the
|
|
34
|
+
* socket's close event fires and the owner's reconnect or handshake-failure
|
|
35
|
+
* handling runs.
|
|
34
36
|
*/
|
|
35
37
|
forceClose(reason: string): void;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* path
|
|
40
|
+
* Runs the application-level heartbeat for one socket. While the socket is
|
|
41
|
+
* open, it sends a `{ type: 'ping' }` frame every {@link HEARTBEAT_INTERVAL_MS}
|
|
42
|
+
* and arms a {@link HEARTBEAT_TIMEOUT_MS} watchdog. Any inbound frame clears
|
|
43
|
+
* the watchdog — the owner calls {@link HeartbeatController.clearHeartbeatTimeout}
|
|
44
|
+
* from its message handler. If the watchdog fires first, the connection is
|
|
45
|
+
* treated as a zombie and force-closed, which lets the owner's reconnect path
|
|
46
|
+
* run.
|
|
44
47
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* the OS surfaces the broken connection. App-level traffic is
|
|
52
|
-
* the only signal we can observe.
|
|
48
|
+
* The heartbeat exists because the client cannot see the protocol-level
|
|
49
|
+
* keepalive: browsers answer the server's pings automatically but never expose
|
|
50
|
+
* those frames to JavaScript. On a half-open connection (laptop wake, NAT
|
|
51
|
+
* timeout, mobile handoff) the socket can report itself open for minutes
|
|
52
|
+
* before the operating system surfaces the break, so observable application
|
|
53
|
+
* traffic is the only reliable signal.
|
|
53
54
|
*/
|
|
54
55
|
export declare class HeartbeatController {
|
|
55
56
|
private readonly transport;
|
package/dist/sync/heartbeat.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Application-level heartbeat for the sync WebSocket.
|
|
3
3
|
*
|
|
4
|
-
* The browser WebSocket API hides RFC 6455 protocol-level ping
|
|
5
|
-
* JavaScript, so the server's
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* NAT timeout, mobile handoff).
|
|
9
|
-
* `{ type: 'ping' }` every
|
|
10
|
-
*
|
|
11
|
-
* proof
|
|
12
|
-
*
|
|
4
|
+
* The browser WebSocket API hides RFC 6455 protocol-level ping and pong frames
|
|
5
|
+
* from JavaScript, so the server's keepalive cannot be observed by client
|
|
6
|
+
* code. That leaves the client unable to tell a healthy idle connection apart
|
|
7
|
+
* from a "zombie" socket whose underlying TCP connection has silently broken
|
|
8
|
+
* (laptop sleep, NAT timeout, mobile handoff). To close the gap, the client
|
|
9
|
+
* sends an application-level `{ type: 'ping' }` every 30 seconds and
|
|
10
|
+
* force-closes the socket if no inbound traffic arrives within 10 seconds. Any
|
|
11
|
+
* inbound message counts as proof the connection is alive; the explicit `pong`
|
|
12
|
+
* merely guarantees that something arrives even on an otherwise idle stream.
|
|
13
13
|
*/
|
|
14
14
|
import { getContext } from '../context.js';
|
|
15
15
|
import { PING_INTERVAL_MS } from '../wire/protocol.js';
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* lease window is derived from
|
|
17
|
+
* The interval between application-level pings, shared with both sides of the
|
|
18
|
+
* connection: the client pings at the same {@link PING_INTERVAL_MS} the server
|
|
19
|
+
* uses for its own keepalive, and the claim lease window is derived from the
|
|
20
|
+
* same constant.
|
|
20
21
|
*/
|
|
21
22
|
export const HEARTBEAT_INTERVAL_MS = PING_INTERVAL_MS;
|
|
22
23
|
export const HEARTBEAT_TIMEOUT_MS = 10_000;
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* path
|
|
25
|
+
* Runs the application-level heartbeat for one socket. While the socket is
|
|
26
|
+
* open, it sends a `{ type: 'ping' }` frame every {@link HEARTBEAT_INTERVAL_MS}
|
|
27
|
+
* and arms a {@link HEARTBEAT_TIMEOUT_MS} watchdog. Any inbound frame clears
|
|
28
|
+
* the watchdog — the owner calls {@link HeartbeatController.clearHeartbeatTimeout}
|
|
29
|
+
* from its message handler. If the watchdog fires first, the connection is
|
|
30
|
+
* treated as a zombie and force-closed, which lets the owner's reconnect path
|
|
31
|
+
* run.
|
|
30
32
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* the OS surfaces the broken connection. App-level traffic is
|
|
38
|
-
* the only signal we can observe.
|
|
33
|
+
* The heartbeat exists because the client cannot see the protocol-level
|
|
34
|
+
* keepalive: browsers answer the server's pings automatically but never expose
|
|
35
|
+
* those frames to JavaScript. On a half-open connection (laptop wake, NAT
|
|
36
|
+
* timeout, mobile handoff) the socket can report itself open for minutes
|
|
37
|
+
* before the operating system surfaces the break, so observable application
|
|
38
|
+
* traffic is the only reliable signal.
|
|
39
39
|
*/
|
|
40
40
|
export class HeartbeatController {
|
|
41
41
|
transport;
|
|
@@ -49,8 +49,8 @@ export class HeartbeatController {
|
|
|
49
49
|
this.heartbeatTimer = setInterval(() => {
|
|
50
50
|
if (!this.transport.isSocketOpen())
|
|
51
51
|
return;
|
|
52
|
-
// Send the ping. If
|
|
53
|
-
// force-close
|
|
52
|
+
// Send the ping. If it throws, the socket is already dead, so
|
|
53
|
+
// force-close it to let the close event drive the reconnect cycle.
|
|
54
54
|
try {
|
|
55
55
|
this.transport.sendPing();
|
|
56
56
|
}
|
|
@@ -62,9 +62,9 @@ export class HeartbeatController {
|
|
|
62
62
|
this.transport.forceClose('heartbeat-send-failed');
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
// Arm the timeout.
|
|
66
|
-
//
|
|
67
|
-
//
|
|
65
|
+
// Arm the timeout. Any inbound message clears it; an explicit `pong` is
|
|
66
|
+
// not required, since a delta or any other frame is equally good proof
|
|
67
|
+
// that the connection is alive.
|
|
68
68
|
if (this.heartbeatTimeoutTimer)
|
|
69
69
|
clearTimeout(this.heartbeatTimeoutTimer);
|
|
70
70
|
this.heartbeatTimeoutTimer = setTimeout(() => {
|
|
@@ -3,9 +3,9 @@ import type { Schema } from '../schema/schema.js';
|
|
|
3
3
|
import type { Claim, Activity, ClaimTarget, ClaimStream, Peer, PresenceStream, PresenceTarget } from '../types/streams.js';
|
|
4
4
|
import type { AttachableClaimStream } from './createClaimStream.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The scope a participant can be joined to. The usual form is an entity target
|
|
7
|
+
* (`{ type, id }`); raw sync-group strings are an advanced escape hatch for
|
|
8
|
+
* addressing a transport scope directly.
|
|
9
9
|
*/
|
|
10
10
|
export type ParticipantScope = ClaimTarget | readonly ClaimTarget[] | string | readonly string[] | {
|
|
11
11
|
readonly syncGroup: string;
|
|
@@ -19,26 +19,27 @@ export interface EngineParticipant {
|
|
|
19
19
|
}
|
|
20
20
|
export interface ParticipantJoinOptions {
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* narrowed to a path, field, or range. When `scope` is omitted,
|
|
24
|
-
*
|
|
22
|
+
* The initial focus target, named in your schema's vocabulary and optionally
|
|
23
|
+
* narrowed to a path, field, or range. When `scope` is omitted, this target
|
|
24
|
+
* also becomes the routing scope.
|
|
25
25
|
*/
|
|
26
26
|
readonly target?: PresenceTarget;
|
|
27
27
|
/** Alias for `target` when the participant is joined to a broader scope. */
|
|
28
28
|
readonly focus?: PresenceTarget;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* The routing scope: one entity, many entities, or a raw sync-group escape
|
|
31
|
+
* hatch. Use it for "joined to the folder, focused on one file" shapes,
|
|
32
|
+
* where the participant listens more broadly than its focus target.
|
|
33
33
|
*/
|
|
34
34
|
readonly scope?: ParticipantScope;
|
|
35
35
|
/** Present a narrower capability for this logical participant. */
|
|
36
36
|
readonly capabilityToken?: string;
|
|
37
|
-
/**
|
|
37
|
+
/** How long the claim lives, in seconds or a compact duration string (`30s`, `5m`). */
|
|
38
38
|
readonly ttlSeconds?: number | string | null;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* `reading` when `target` is present. Pass false to join
|
|
40
|
+
* The activity to announce as soon as the claim is acknowledged. Defaults to
|
|
41
|
+
* `reading` when a `target` is present. Pass `false` to join without
|
|
42
|
+
* announcing anything.
|
|
42
43
|
*/
|
|
43
44
|
readonly activity?: 'reading' | 'viewing' | 'editing' | false;
|
|
44
45
|
readonly detail?: string;
|
|
@@ -63,17 +64,16 @@ export interface ScopedClaimOptions {
|
|
|
63
64
|
/** Free-form reason. Defaults to `'editing'`. Common: `'editing'`,
|
|
64
65
|
* `'writing'`, `'reviewing'`, app-specific phases. */
|
|
65
66
|
readonly reason?: string;
|
|
66
|
-
/**
|
|
67
|
+
/** How long the claim lives; the server expires it automatically after this. */
|
|
67
68
|
readonly ttl?: import('../types/streams.js').Duration;
|
|
68
69
|
}
|
|
69
70
|
export interface ScopedClaims {
|
|
70
71
|
readonly focus: ClaimTarget | null;
|
|
71
72
|
readonly others: readonly Claim[];
|
|
72
73
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* collapsed into this one method.
|
|
74
|
+
* Takes an exclusive claim on the participant's focus target, or on an
|
|
75
|
+
* explicit target passed via `opts.target`. While the claim is held, other
|
|
76
|
+
* participants that request an overlapping target are rejected.
|
|
77
77
|
*/
|
|
78
78
|
claim(opts?: ScopedClaimOptions): Claim;
|
|
79
79
|
onRejected(listener: Parameters<ClaimStream['onRejected']>[0]): () => void;
|
|
@@ -84,10 +84,10 @@ export interface ParticipantFocusOptions {
|
|
|
84
84
|
readonly detail?: string;
|
|
85
85
|
}
|
|
86
86
|
export interface JoinedParticipant {
|
|
87
|
-
/**
|
|
87
|
+
/** The exact entity this participant is currently reading or editing. */
|
|
88
88
|
readonly target: ClaimTarget | null;
|
|
89
89
|
readonly focusTarget: ClaimTarget | null;
|
|
90
|
-
/**
|
|
90
|
+
/** The transport scopes this participant is joined to, which govern what it sees and receives. */
|
|
91
91
|
readonly syncGroups: readonly string[];
|
|
92
92
|
readonly presence: ScopedPresence;
|
|
93
93
|
readonly claims: ScopedClaims;
|
package/dist/sync/schemas.d.ts
CHANGED
|
@@ -74,7 +74,8 @@ export declare const BootstrapResponseSchema: z.ZodObject<{
|
|
|
74
74
|
}, z.core.$loose>;
|
|
75
75
|
export type ValidatedBootstrapResponse = z.infer<typeof BootstrapResponseSchema>;
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
77
|
+
* Validates a raw bootstrap response from the server and returns the typed
|
|
78
|
+
* result. On failure it records a diagnostic breadcrumb and throws an
|
|
79
|
+
* {@link AbloValidationError} describing which fields were invalid.
|
|
79
80
|
*/
|
|
80
81
|
export declare function parseBootstrapResponse(raw: unknown): ValidatedBootstrapResponse;
|
package/dist/sync/schemas.js
CHANGED
|
@@ -8,7 +8,8 @@ import { z } from 'zod';
|
|
|
8
8
|
import { getContext } from "../context.js";
|
|
9
9
|
import { AbloValidationError } from "../errors.js";
|
|
10
10
|
// ─── Sync Action Types ───────────────────────────────────────────────────────
|
|
11
|
-
//
|
|
11
|
+
// The action codes a server delta can carry, matching the wire protocol's
|
|
12
|
+
// action-type set.
|
|
12
13
|
const SYNC_ACTION_VALUES = ['I', 'U', 'D', 'A', 'C', 'G', 'S', 'V'];
|
|
13
14
|
// ─── Server Delta Schema ─────────────────────────────────────────────────────
|
|
14
15
|
export const ServerDeltaSchema = z
|
|
@@ -23,11 +24,12 @@ export const ServerDeltaSchema = z
|
|
|
23
24
|
})
|
|
24
25
|
.passthrough();
|
|
25
26
|
// ─── Model Value Schema ─────────────────────────────────────────────────────
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// -
|
|
29
|
-
// -
|
|
30
|
-
//
|
|
27
|
+
// A model's values can arrive in more than one shape depending on how the
|
|
28
|
+
// server serialized them:
|
|
29
|
+
// - Array: an already-parsed JSON array (the common case)
|
|
30
|
+
// - String: a JSON array still encoded as a string, which must be parsed
|
|
31
|
+
// - null: no matching rows
|
|
32
|
+
// This schema normalizes every variant into an array before downstream use.
|
|
31
33
|
const ModelValueSchema = z
|
|
32
34
|
.union([z.array(z.unknown()), z.string(), z.null()])
|
|
33
35
|
.transform((val) => {
|
|
@@ -54,15 +56,17 @@ export const BootstrapResponseSchema = z
|
|
|
54
56
|
deltaCount: z.number().optional(),
|
|
55
57
|
failedModels: z.array(z.string()).optional(),
|
|
56
58
|
timestamp: z.number().default(() => Date.now()),
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
+
// The server's active schema hash, used to detect schema drift. Optional:
|
|
60
|
+
// absent when the server predates this field or the tenant has never
|
|
61
|
+
// pushed a schema.
|
|
59
62
|
schemaHash: z.string().optional(),
|
|
60
63
|
})
|
|
61
64
|
.passthrough();
|
|
62
65
|
// ─── Parse Helpers ───────────────────────────────────────────────────────────
|
|
63
66
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
67
|
+
* Validates a raw bootstrap response from the server and returns the typed
|
|
68
|
+
* result. On failure it records a diagnostic breadcrumb and throws an
|
|
69
|
+
* {@link AbloValidationError} describing which fields were invalid.
|
|
66
70
|
*/
|
|
67
71
|
export function parseBootstrapResponse(raw) {
|
|
68
72
|
const result = BootstrapResponseSchema.safeParse(raw);
|