@abloatai/ablo 0.25.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/AGENTS.md +5 -3
- package/CHANGELOG.md +34 -0
- package/README.md +104 -88
- package/dist/BaseSyncedStore.d.ts +140 -266
- package/dist/BaseSyncedStore.js +338 -739
- package/dist/Database.d.ts +62 -77
- package/dist/Database.js +106 -127
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +16 -15
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +52 -69
- package/dist/ModelRegistry.d.ts +46 -25
- package/dist/ModelRegistry.js +32 -30
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +6 -7
- package/dist/SyncClient.d.ts +119 -109
- package/dist/SyncClient.js +303 -224
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- 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 +39 -31
- package/dist/agent/Agent.js +35 -23
- 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} +30 -31
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +7 -8
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/credentialPolicy.d.ts +74 -71
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +7 -18
- package/dist/auth/credentialSource.js +10 -18
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +34 -40
- 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 +483 -369
- package/dist/client/Ablo.d.ts +107 -836
- package/dist/client/Ablo.js +174 -833
- package/dist/client/ApiClient.d.ts +44 -20
- package/dist/client/ApiClient.js +193 -44
- package/dist/client/auth.d.ts +51 -60
- package/dist/client/auth.js +137 -110
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +35 -0
- package/dist/client/consoleLogger.js +44 -0
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +26 -31
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +158 -124
- package/dist/client/credentialEndpoint.d.ts +61 -0
- package/dist/client/credentialEndpoint.js +86 -0
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +21 -0
- package/dist/client/hostedEndpoints.js +21 -0
- package/dist/client/httpClient.d.ts +58 -54
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +49 -59
- package/dist/client/modelRegistration.d.ts +10 -0
- package/dist/client/modelRegistration.js +301 -0
- package/dist/client/options.d.ts +373 -0
- package/dist/client/options.js +6 -0
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +333 -0
- package/dist/client/resourceTypes.js +7 -0
- package/dist/client/schemaConfig.d.ts +44 -0
- package/dist/client/schemaConfig.js +176 -0
- package/dist/client/sessionMint.d.ts +17 -13
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +9 -10
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +28 -0
- package/dist/client/wsMutationExecutor.js +71 -0
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -7
- package/dist/coordination/index.d.ts +13 -4
- package/dist/coordination/index.js +29 -4
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -11
- package/dist/coordination/trace.js +13 -15
- package/dist/core/DatabaseManager.d.ts +5 -8
- package/dist/core/DatabaseManager.js +38 -40
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +27 -34
- package/dist/core/QueryView.d.ts +17 -5
- package/dist/core/QueryView.js +6 -7
- package/dist/core/StoreManager.d.ts +14 -16
- package/dist/core/StoreManager.js +26 -25
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +18 -13
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +57 -54
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +145 -0
- package/dist/core/storeContract.js +12 -0
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +118 -101
- package/dist/errorCodes.js +277 -260
- package/dist/errors.d.ts +170 -165
- package/dist/errors.js +161 -151
- package/dist/index.d.ts +30 -27
- package/dist/index.js +90 -82
- package/dist/interfaces/index.d.ts +108 -133
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +59 -49
- 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 +149 -155
- package/dist/mutators/defineMutators.d.ts +24 -37
- 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 +105 -101
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +32 -16
- package/dist/query/client.js +103 -72
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +7 -11
- package/dist/react/AbloProvider.js +24 -17
- package/dist/react/context.d.ts +27 -146
- 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 +17 -15
- 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 +11 -12
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +9 -9
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +10 -4
- package/dist/react/useReactive.js +2 -3
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +3 -3
- 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 +35 -0
- package/dist/schema/ddlLock.js +46 -0
- 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 +36 -49
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +5 -4
- package/dist/schema/index.js +29 -21
- package/dist/schema/model.d.ts +121 -146
- package/dist/schema/model.js +24 -35
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +7 -1
- package/dist/schema/queries.d.ts +30 -32
- package/dist/schema/queries.js +24 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +38 -0
- package/dist/schema/residency.js +30 -0
- package/dist/schema/roles.d.ts +45 -27
- package/dist/schema/roles.js +52 -21
- package/dist/schema/schema.d.ts +36 -45
- package/dist/schema/schema.js +42 -39
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +36 -39
- 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} +27 -50
- 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 +31 -26
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +34 -28
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +28 -26
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +10 -9
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +27 -29
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +27 -19
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +30 -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 +94 -0
- package/dist/source/factory.js +268 -0
- package/dist/source/index.d.ts +10 -462
- package/dist/source/index.js +17 -421
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +10 -11
- package/dist/source/next.js +7 -8
- package/dist/source/pushQueue.d.ts +70 -48
- package/dist/source/pushQueue.js +36 -29
- package/dist/source/signing.d.ts +88 -0
- package/dist/source/signing.js +159 -0
- package/dist/source/types.d.ts +351 -0
- package/dist/source/types.js +43 -0
- package/dist/stores/ObjectStore.d.ts +11 -12
- package/dist/stores/ObjectStore.js +34 -35
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +8 -12
- package/dist/stores/SyncActionStore.js +77 -46
- package/dist/surface.d.ts +28 -21
- package/dist/surface.js +28 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
- package/dist/sync/ConnectionManager.d.ts +47 -50
- package/dist/sync/ConnectionManager.js +74 -70
- package/dist/sync/NetworkProbe.d.ts +27 -31
- package/dist/sync/NetworkProbe.js +67 -72
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
- package/dist/sync/SyncWebSocket.d.ts +175 -250
- package/dist/sync/SyncWebSocket.js +431 -769
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +38 -30
- package/dist/sync/bootstrapApply.d.ts +70 -0
- package/dist/sync/bootstrapApply.js +73 -0
- package/dist/sync/commitFrames.d.ts +44 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +108 -25
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +13 -17
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +322 -0
- package/dist/sync/deltaPipeline.d.ts +113 -0
- package/dist/sync/deltaPipeline.js +261 -0
- package/dist/sync/groupChange.d.ts +113 -0
- package/dist/sync/groupChange.js +242 -0
- package/dist/sync/heartbeat.d.ts +63 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +27 -27
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +40 -0
- package/dist/sync/syncCursor.js +55 -0
- package/dist/sync/syncPlan.d.ts +54 -0
- package/dist/sync/syncPlan.js +50 -0
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +116 -0
- package/dist/sync/wsFrameHandlers.js +374 -0
- package/dist/testing/fixtures/bootstrap.d.ts +21 -17
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +31 -34
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +12 -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 -18
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +3 -3
- 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 +21 -34
- package/dist/testing/mocks/MockSyncContext.js +16 -45
- package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +190 -221
- package/dist/transactions/TransactionQueue.js +424 -822
- package/dist/transactions/TransactionStore.d.ts +20 -0
- package/dist/transactions/TransactionStore.js +53 -0
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +58 -0
- package/dist/transactions/coalesceRules.js +140 -0
- package/dist/transactions/commitPayload.d.ts +130 -0
- package/dist/transactions/commitPayload.js +143 -0
- package/dist/transactions/deltaConfirmation.d.ts +58 -0
- package/dist/transactions/deltaConfirmation.js +215 -0
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/replayValidation.d.ts +99 -0
- package/dist/transactions/replayValidation.js +111 -0
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +74 -80
- package/dist/types/index.js +22 -27
- package/dist/types/modelData.d.ts +10 -0
- package/dist/types/modelData.js +9 -0
- package/dist/types/participant.d.ts +20 -0
- package/dist/types/participant.js +10 -0
- package/dist/types/streams.d.ts +216 -209
- 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} +44 -100
- 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 +35 -27
- package/dist/wire/errorEnvelope.js +38 -32
- package/dist/wire/frames.d.ts +150 -67
- package/dist/wire/frames.js +48 -1
- package/dist/wire/index.d.ts +18 -13
- package/dist/wire/index.js +36 -13
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +38 -0
- package/dist/wire/protocol.js +38 -0
- package/dist/wire/protocolVersion.d.ts +60 -0
- package/dist/wire/protocolVersion.js +67 -0
- package/docs/api-keys.md +4 -3
- package/docs/coordination.md +59 -0
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +24 -21
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- 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 -1
- package/dist/server/storage-mode.js +0 -18
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Loads model rows on demand — the lazy-load path of the sync engine. When
|
|
3
|
+
* something needs an entity that the initial bootstrap did not fetch,
|
|
4
|
+
* {@link OnDemandLoader.fetch | fetch} finds it and populates the
|
|
5
|
+
* in-memory {@link InstanceCache} so the rest of the engine can read it normally.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* A fetch resolves against three tiers in order, stopping at the first that can
|
|
8
|
+
* answer:
|
|
9
|
+
* 1. The object pool — if rows already in memory match the query, return them.
|
|
10
|
+
* 2. Local storage — if matching rows exist there, hydrate the pool and return.
|
|
11
|
+
* 3. The network — post the query to `/sync/query`, then hydrate both the pool
|
|
12
|
+
* and local storage.
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 3. Network — `postQuery` against `/sync/query`, hydrate pool + IDB.
|
|
14
|
+
* Concurrent calls with the same query key share one in-flight promise, so a
|
|
15
|
+
* burst of components mounting and asking for the same data on first paint
|
|
16
|
+
* triggers a single fetch rather than one each.
|
|
13
17
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* The coordinator does NOT replace bootstrap (full sync of `instant`
|
|
19
|
-
* models) or live deltas (WS push). It only fills the gap for `lazy`
|
|
20
|
-
* models accessed by id/where after the engine is ready.
|
|
18
|
+
* The coordinator does not replace the bootstrap (which fully syncs instantly
|
|
19
|
+
* loaded models) or the live delta stream (pushed over the WebSocket). It only
|
|
20
|
+
* fills the gap for lazily loaded models read by id or filter after the engine
|
|
21
|
+
* is ready.
|
|
21
22
|
*/
|
|
22
|
-
import type {
|
|
23
|
+
import type { InstanceCache } from '../InstanceCache.js';
|
|
23
24
|
import type { Database } from '../Database.js';
|
|
24
25
|
import type { Model } from '../Model.js';
|
|
25
26
|
import type { ModelRegistry } from '../ModelRegistry.js';
|
|
27
|
+
import type { RecoveryClass } from '../errorCodes.js';
|
|
26
28
|
import type { LoadWhere, WhereClause } from '../query/types.js';
|
|
27
29
|
import type { Schema } from '../schema/schema.js';
|
|
28
|
-
export interface
|
|
29
|
-
readonly objectPool:
|
|
30
|
+
export interface OnDemandLoaderOptions {
|
|
31
|
+
readonly objectPool: InstanceCache;
|
|
30
32
|
readonly database: Database;
|
|
31
33
|
readonly registry: ModelRegistry;
|
|
32
34
|
readonly schema: Schema;
|
|
@@ -42,11 +44,11 @@ export interface HydrationCoordinatorOptions {
|
|
|
42
44
|
}
|
|
43
45
|
export interface FetchOptions<T> {
|
|
44
46
|
/**
|
|
45
|
-
* Filter clauses for the lookup. Accepts either the equality-object
|
|
46
|
-
*
|
|
47
|
-
* or the explicit tuple form (`[['name', 'ILIKE', '%
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* Filter clauses for the lookup. Accepts either the equality-object form
|
|
48
|
+
* (`{ id: 'abc' }` becomes `WHERE id = 'abc'`, and an array value becomes an
|
|
49
|
+
* `IN`) or the explicit tuple form (`[['name', 'ILIKE', '%Acme%']]`), which
|
|
50
|
+
* mirrors the wire `WhereClause[]` exactly. Multiple entries combine with AND.
|
|
51
|
+
* See {@link LoadWhere} for the full shape.
|
|
50
52
|
*/
|
|
51
53
|
readonly where?: LoadWhere<T>;
|
|
52
54
|
readonly orderBy?: {
|
|
@@ -71,7 +73,7 @@ export interface FetchOptions<T> {
|
|
|
71
73
|
*/
|
|
72
74
|
readonly expand?: readonly string[];
|
|
73
75
|
}
|
|
74
|
-
export declare class
|
|
76
|
+
export declare class OnDemandLoader {
|
|
75
77
|
private readonly opts;
|
|
76
78
|
private readonly inFlight;
|
|
77
79
|
/**
|
|
@@ -85,22 +87,33 @@ export declare class HydrationCoordinator {
|
|
|
85
87
|
/**
|
|
86
88
|
* Query keys that have been satisfied from the server at least once this
|
|
87
89
|
* session. Once a key is here, repeat reads serve purely from the pool with
|
|
88
|
-
*
|
|
89
|
-
* re-running the HTTP query would be redundant polling. This
|
|
90
|
-
*
|
|
90
|
+
* no network round-trip: the WebSocket delta stream keeps those pool rows
|
|
91
|
+
* fresh, so re-running the HTTP query would be redundant polling. This ledger
|
|
92
|
+
* is what stops an already-open view from re-querying on every navigation.
|
|
91
93
|
*
|
|
92
94
|
* Cleared on reconnect (see {@link invalidate}) so that, after a connection
|
|
93
95
|
* drop where deltas may have been missed, the next read re-confirms once.
|
|
94
96
|
*/
|
|
95
97
|
private readonly hydratedKeys;
|
|
96
98
|
private authTokenProvider;
|
|
97
|
-
|
|
99
|
+
/**
|
|
100
|
+
* The credential-recovery hook (the store's `recoverFromAuthRejection`),
|
|
101
|
+
* late-bound like {@link setAuthTokenProvider} because the store does not
|
|
102
|
+
* exist yet when the coordinator is constructed. Handed to `postQuery` so a
|
|
103
|
+
* 401 on the lazy lane re-mints through the same single-flight path the
|
|
104
|
+
* WebSocket probe uses, then replays the query once — instead of silently
|
|
105
|
+
* returning empty rows against an expired key.
|
|
106
|
+
*/
|
|
107
|
+
private credentialRecovery;
|
|
108
|
+
constructor(opts: OnDemandLoaderOptions);
|
|
98
109
|
/**
|
|
99
110
|
* Late-bind the auth token getter. Browser cookie consumers can omit this;
|
|
100
111
|
* bearer consumers need it so lazy HTTP queries use the same credential as
|
|
101
112
|
* bootstrap and the WebSocket.
|
|
102
113
|
*/
|
|
103
114
|
setAuthTokenProvider(provider: () => string | null): void;
|
|
115
|
+
/** Late-bind the auth-recovery backbone. See {@link credentialRecovery}. */
|
|
116
|
+
setCredentialRecovery(recover: (recovery: RecoveryClass) => Promise<'retry' | 'stop'>): void;
|
|
104
117
|
/** @deprecated Use `setAuthTokenProvider`. */
|
|
105
118
|
setCapabilityTokenProvider(provider: () => string | null): void;
|
|
106
119
|
/**
|
|
@@ -132,13 +145,13 @@ export declare class HydrationCoordinator {
|
|
|
132
145
|
*/
|
|
133
146
|
private fetchFromNetwork;
|
|
134
147
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
148
|
+
* Fires the single background confirm for a query that was just served from
|
|
149
|
+
* local cache but is not hydrated yet. On success the key is marked hydrated,
|
|
150
|
+
* so every later read serves purely from local with no network until a
|
|
138
151
|
* reconnect invalidates the ledger. Deduped per query key so a render burst
|
|
139
|
-
*
|
|
140
|
-
* local snapshot, and a failed confirm leaves the key un-hydrated so the
|
|
141
|
-
*
|
|
152
|
+
* does not stampede. Errors are swallowed — the caller already has a usable
|
|
153
|
+
* local snapshot, and a failed confirm leaves the key un-hydrated so the next
|
|
154
|
+
* read simply tries again.
|
|
142
155
|
*/
|
|
143
156
|
private scheduleHydratingFetch;
|
|
144
157
|
/**
|
|
@@ -173,7 +186,7 @@ export declare class HydrationCoordinator {
|
|
|
173
186
|
* `postgres.camel` driver leaves behind when the server's SQL
|
|
174
187
|
* bakes `__typename` into a JSONB literal — the driver's
|
|
175
188
|
* snake↔camel transform misreads `__typename` as `_typename` with
|
|
176
|
-
* a leading underscore and produces `_Typename`.
|
|
189
|
+
* a leading underscore and produces `_Typename`. InstanceCache only
|
|
177
190
|
* recognises `__typename`, so without this step nested rows fall
|
|
178
191
|
* through to the 'Unknown' branch and never instantiate.
|
|
179
192
|
*/
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Loads model rows on demand — the lazy-load path of the sync engine. When
|
|
3
|
+
* something needs an entity that the initial bootstrap did not fetch,
|
|
4
|
+
* {@link OnDemandLoader.fetch | fetch} finds it and populates the
|
|
5
|
+
* in-memory {@link InstanceCache} so the rest of the engine can read it normally.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* A fetch resolves against three tiers in order, stopping at the first that can
|
|
8
|
+
* answer:
|
|
9
|
+
* 1. The object pool — if rows already in memory match the query, return them.
|
|
10
|
+
* 2. Local storage — if matching rows exist there, hydrate the pool and return.
|
|
11
|
+
* 3. The network — post the query to `/sync/query`, then hydrate both the pool
|
|
12
|
+
* and local storage.
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 3. Network — `postQuery` against `/sync/query`, hydrate pool + IDB.
|
|
14
|
+
* Concurrent calls with the same query key share one in-flight promise, so a
|
|
15
|
+
* burst of components mounting and asking for the same data on first paint
|
|
16
|
+
* triggers a single fetch rather than one each.
|
|
13
17
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* The coordinator does NOT replace bootstrap (full sync of `instant`
|
|
19
|
-
* models) or live deltas (WS push). It only fills the gap for `lazy`
|
|
20
|
-
* models accessed by id/where after the engine is ready.
|
|
18
|
+
* The coordinator does not replace the bootstrap (which fully syncs instantly
|
|
19
|
+
* loaded models) or the live delta stream (pushed over the WebSocket). It only
|
|
20
|
+
* fills the gap for lazily loaded models read by id or filter after the engine
|
|
21
|
+
* is ready.
|
|
21
22
|
*/
|
|
22
|
-
import { ModelScope } from '../
|
|
23
|
+
import { ModelScope } from '../InstanceCache.js';
|
|
23
24
|
import { AbloValidationError } from '../errors.js';
|
|
24
25
|
import { postQuery } from '../query/client.js';
|
|
25
|
-
export class
|
|
26
|
+
export class OnDemandLoader {
|
|
26
27
|
opts;
|
|
27
28
|
inFlight = new Map();
|
|
28
29
|
/**
|
|
@@ -36,17 +37,30 @@ export class HydrationCoordinator {
|
|
|
36
37
|
/**
|
|
37
38
|
* Query keys that have been satisfied from the server at least once this
|
|
38
39
|
* session. Once a key is here, repeat reads serve purely from the pool with
|
|
39
|
-
*
|
|
40
|
-
* re-running the HTTP query would be redundant polling. This
|
|
41
|
-
*
|
|
40
|
+
* no network round-trip: the WebSocket delta stream keeps those pool rows
|
|
41
|
+
* fresh, so re-running the HTTP query would be redundant polling. This ledger
|
|
42
|
+
* is what stops an already-open view from re-querying on every navigation.
|
|
42
43
|
*
|
|
43
44
|
* Cleared on reconnect (see {@link invalidate}) so that, after a connection
|
|
44
45
|
* drop where deltas may have been missed, the next read re-confirms once.
|
|
45
46
|
*/
|
|
46
47
|
hydratedKeys = new Set();
|
|
47
48
|
authTokenProvider = null;
|
|
49
|
+
/**
|
|
50
|
+
* The credential-recovery hook (the store's `recoverFromAuthRejection`),
|
|
51
|
+
* late-bound like {@link setAuthTokenProvider} because the store does not
|
|
52
|
+
* exist yet when the coordinator is constructed. Handed to `postQuery` so a
|
|
53
|
+
* 401 on the lazy lane re-mints through the same single-flight path the
|
|
54
|
+
* WebSocket probe uses, then replays the query once — instead of silently
|
|
55
|
+
* returning empty rows against an expired key.
|
|
56
|
+
*/
|
|
57
|
+
credentialRecovery = null;
|
|
48
58
|
constructor(opts) {
|
|
49
59
|
this.opts = opts;
|
|
60
|
+
// Reading the deprecated `getCapabilityToken` is deliberate: it's the
|
|
61
|
+
// back-compat shim that keeps older callers who still pass it working
|
|
62
|
+
// until they migrate to `getAuthToken`.
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
50
64
|
this.authTokenProvider = opts.getAuthToken ?? opts.getCapabilityToken ?? null;
|
|
51
65
|
}
|
|
52
66
|
/**
|
|
@@ -57,6 +71,10 @@ export class HydrationCoordinator {
|
|
|
57
71
|
setAuthTokenProvider(provider) {
|
|
58
72
|
this.authTokenProvider = provider;
|
|
59
73
|
}
|
|
74
|
+
/** Late-bind the auth-recovery backbone. See {@link credentialRecovery}. */
|
|
75
|
+
setCredentialRecovery(recover) {
|
|
76
|
+
this.credentialRecovery = recover;
|
|
77
|
+
}
|
|
60
78
|
/** @deprecated Use `setAuthTokenProvider`. */
|
|
61
79
|
setCapabilityTokenProvider(provider) {
|
|
62
80
|
this.setAuthTokenProvider(provider);
|
|
@@ -71,7 +89,7 @@ export class HydrationCoordinator {
|
|
|
71
89
|
const ModelClass = this.opts.registry.getModelByName(typename)
|
|
72
90
|
?? this.opts.registry.getModelByName(modelName);
|
|
73
91
|
if (!ModelClass) {
|
|
74
|
-
throw new AbloValidationError(`
|
|
92
|
+
throw new AbloValidationError(`OnDemandLoader.fetch: unknown model "${modelName}" — ` +
|
|
75
93
|
`not registered in the schema.`, { code: 'model_not_registered' });
|
|
76
94
|
}
|
|
77
95
|
const clauses = normalizeWhere(options?.where);
|
|
@@ -82,9 +100,15 @@ export class HydrationCoordinator {
|
|
|
82
100
|
return inFlight;
|
|
83
101
|
const work = this.runFetch(modelName, typename, ModelClass, clauses, options, queryKey);
|
|
84
102
|
this.inFlight.set(queryKey, work);
|
|
85
|
-
|
|
103
|
+
// The rejection (if any) reaches callers via the returned `work`; this
|
|
104
|
+
// side-chain only clears the single-flight slot. Without the trailing
|
|
105
|
+
// catch, `.finally()` mirrors the rejection into a second, unhandled
|
|
106
|
+
// promise even when every caller handles theirs.
|
|
107
|
+
void work
|
|
108
|
+
.finally(() => {
|
|
86
109
|
this.inFlight.delete(queryKey);
|
|
87
|
-
})
|
|
110
|
+
})
|
|
111
|
+
.catch(() => undefined);
|
|
88
112
|
return work;
|
|
89
113
|
}
|
|
90
114
|
async runFetch(modelName, typename, ModelClass, clauses, options, queryKey) {
|
|
@@ -95,24 +119,24 @@ export class HydrationCoordinator {
|
|
|
95
119
|
const hasExpand = !!(expand && expand.length > 0);
|
|
96
120
|
// Fast path — this exact query was already satisfied from the server this
|
|
97
121
|
// session. The WebSocket delta stream has kept the pool fresh since, so a
|
|
98
|
-
// repeat read needs
|
|
99
|
-
// stops an already-open
|
|
122
|
+
// repeat read needs no network: serve straight from local. This is what
|
|
123
|
+
// stops an already-open view from re-querying on every navigation when no
|
|
100
124
|
// new deltas have arrived.
|
|
101
125
|
if (!explicitComplete && this.hydratedKeys.has(queryKey)) {
|
|
102
126
|
return applyLimit(await this.readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand), options?.limit);
|
|
103
127
|
}
|
|
104
128
|
// Not yet hydrated (or an explicit complete read). For a non-complete read
|
|
105
|
-
//
|
|
106
|
-
// after a reload), hand it back immediately and confirm with the
|
|
107
|
-
//
|
|
108
|
-
// are
|
|
129
|
+
// without expand, if there is anything local to show (a warm pool, or local
|
|
130
|
+
// storage after a reload), hand it back immediately and confirm with the
|
|
131
|
+
// server once in the background — then mark the key hydrated so subsequent
|
|
132
|
+
// reads are purely local. First paint never blocks on the network.
|
|
109
133
|
//
|
|
110
|
-
// Expand queries are deliberately excluded here: a
|
|
111
|
-
// nothing about whether its relations are loaded. Returning the
|
|
112
|
-
// would surface it with empty children
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
134
|
+
// Expand queries are deliberately excluded here: the presence of a primary
|
|
135
|
+
// row says nothing about whether its relations are loaded. Returning the
|
|
136
|
+
// parent now would surface it with empty children, letting a readiness flag
|
|
137
|
+
// flip before the children exist. So an un-hydrated expand query falls
|
|
138
|
+
// through to the blocking fetch that brings parent and children together;
|
|
139
|
+
// the second open is served by the fast path.
|
|
116
140
|
if (!explicitComplete && !hasExpand) {
|
|
117
141
|
const local = await this.readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand);
|
|
118
142
|
if (local.length > 0) {
|
|
@@ -171,10 +195,10 @@ export class HydrationCoordinator {
|
|
|
171
195
|
async fetchFromNetwork(modelName, typename, clauses, options) {
|
|
172
196
|
const networkRows = await this.queryNetwork(modelName, clauses, options);
|
|
173
197
|
const networkModels = networkRows
|
|
174
|
-
// Strict: a row the
|
|
175
|
-
// registered is a genuine schema collision (the
|
|
176
|
-
//
|
|
177
|
-
//
|
|
198
|
+
// Strict: a row the server returned whose type name this client never
|
|
199
|
+
// registered is a genuine schema collision (the pushed schema differs
|
|
200
|
+
// from the local one). Throw here, naming the cause, rather than silently
|
|
201
|
+
// dropping the row and failing downstream as `entity_not_found`.
|
|
178
202
|
.map((raw) => this.hydrateOne(raw, typename, { strict: true }))
|
|
179
203
|
.filter((m) => m !== null);
|
|
180
204
|
if (networkModels.length > 0) {
|
|
@@ -186,13 +210,13 @@ export class HydrationCoordinator {
|
|
|
186
210
|
return networkModels;
|
|
187
211
|
}
|
|
188
212
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
213
|
+
* Fires the single background confirm for a query that was just served from
|
|
214
|
+
* local cache but is not hydrated yet. On success the key is marked hydrated,
|
|
215
|
+
* so every later read serves purely from local with no network until a
|
|
192
216
|
* reconnect invalidates the ledger. Deduped per query key so a render burst
|
|
193
|
-
*
|
|
194
|
-
* local snapshot, and a failed confirm leaves the key un-hydrated so the
|
|
195
|
-
*
|
|
217
|
+
* does not stampede. Errors are swallowed — the caller already has a usable
|
|
218
|
+
* local snapshot, and a failed confirm leaves the key un-hydrated so the next
|
|
219
|
+
* read simply tries again.
|
|
196
220
|
*/
|
|
197
221
|
scheduleHydratingFetch(queryKey, modelName, typename, clauses, options) {
|
|
198
222
|
if (this.revalidating.has(queryKey))
|
|
@@ -282,7 +306,7 @@ export class HydrationCoordinator {
|
|
|
282
306
|
if (!Array.isArray(all))
|
|
283
307
|
return [];
|
|
284
308
|
const idSet = new Set(parentIds);
|
|
285
|
-
return all.filter((r) => idSet.has(r[foreignKey]));
|
|
309
|
+
return all.filter((r) => idSet.has((r)[foreignKey]));
|
|
286
310
|
}
|
|
287
311
|
catch {
|
|
288
312
|
return [];
|
|
@@ -306,7 +330,7 @@ export class HydrationCoordinator {
|
|
|
306
330
|
// after a missed delta (WS dropped, tab slept, redeploy) silently
|
|
307
331
|
// discards the fresh state and the consumer keeps seeing the
|
|
308
332
|
// birth-time snapshot forever. `updateFromData` is the same
|
|
309
|
-
// primitive `
|
|
333
|
+
// primitive `InstanceCache.upsert()` uses for delta application,
|
|
310
334
|
// so the behaviour matches "delta-applied" semantics exactly.
|
|
311
335
|
const existing = this.opts.objectPool.get(obj.id);
|
|
312
336
|
if (existing) {
|
|
@@ -318,9 +342,9 @@ export class HydrationCoordinator {
|
|
|
318
342
|
}
|
|
319
343
|
// Stamp the known relation typename onto the row when the source
|
|
320
344
|
// (IndexedDB rows, sometimes network rows) didn't carry one. Without
|
|
321
|
-
// this,
|
|
345
|
+
// this, InstanceCache.createFromData falls through to the 'Unknown'
|
|
322
346
|
// model-name branch and emits the
|
|
323
|
-
// "
|
|
347
|
+
// "InstanceCache.createFromData: No model identifier found" warning,
|
|
324
348
|
// failing to hydrate the entity from cache (network path then has to
|
|
325
349
|
// re-populate it). The typename comes from the schema relation
|
|
326
350
|
// (`'SlideLayer'`, `'SlideLayoutLayer'`, etc.) so no guessing involved.
|
|
@@ -333,7 +357,7 @@ export class HydrationCoordinator {
|
|
|
333
357
|
* `postgres.camel` driver leaves behind when the server's SQL
|
|
334
358
|
* bakes `__typename` into a JSONB literal — the driver's
|
|
335
359
|
* snake↔camel transform misreads `__typename` as `_typename` with
|
|
336
|
-
* a leading underscore and produces `_Typename`.
|
|
360
|
+
* a leading underscore and produces `_Typename`. InstanceCache only
|
|
337
361
|
* recognises `__typename`, so without this step nested rows fall
|
|
338
362
|
* through to the 'Unknown' branch and never instantiate.
|
|
339
363
|
*/
|
|
@@ -368,6 +392,7 @@ export class HydrationCoordinator {
|
|
|
368
392
|
const result = await postQuery({
|
|
369
393
|
baseUrl: this.opts.baseUrl,
|
|
370
394
|
getAuthToken: this.authTokenProvider ?? undefined,
|
|
395
|
+
recoverCredential: this.credentialRecovery ?? undefined,
|
|
371
396
|
}, { queries: [query] });
|
|
372
397
|
const rows = Array.isArray(result.results[0]) ? result.results[0] : [];
|
|
373
398
|
// Normalize: wire rows lack `__typename` when the server elides it.
|
|
@@ -451,7 +476,7 @@ export class HydrationCoordinator {
|
|
|
451
476
|
resolveTypename(modelName) {
|
|
452
477
|
// Schema is the source of truth for wire typenames. The model proxy
|
|
453
478
|
// is keyed by camelCase plural (`slideLayers`) but the wire query +
|
|
454
|
-
//
|
|
479
|
+
// InstanceCache typeIndex use the typename (`SlideLayer`).
|
|
455
480
|
const def = this.opts.schema
|
|
456
481
|
.models?.[modelName];
|
|
457
482
|
return def?.typename ?? modelName;
|
|
@@ -523,8 +548,8 @@ async function scanIdb(database, modelName, clauses) {
|
|
|
523
548
|
// through to full-scan + filter.
|
|
524
549
|
if (clausesAreAllEquality(clauses)) {
|
|
525
550
|
const indexedKeys = Object.keys(eqClauses).filter((k) => k !== 'id' && typeof eqClauses[k] === 'string');
|
|
526
|
-
|
|
527
|
-
|
|
551
|
+
const idxKey = indexedKeys.length === 1 ? indexedKeys[0] : undefined;
|
|
552
|
+
if (idxKey !== undefined) {
|
|
528
553
|
try {
|
|
529
554
|
const rows = await store.getAllFromIndex(idxKey, eqClauses[idxKey]);
|
|
530
555
|
if (Array.isArray(rows)) {
|
|
@@ -1,75 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* the
|
|
2
|
+
* Decides which sync groups a connection subscribes to as the user navigates,
|
|
3
|
+
* and pushes each change through the {@link SubscriptionTransport}'s
|
|
4
|
+
* `update_subscription` call. It smooths two kinds of churn so that opening and
|
|
5
|
+
* closing entities does not turn into a storm of subscription changes.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The first is hysteresis. Calling {@link SubscriptionManager.leave | leave}
|
|
8
|
+
* on a group does not unsubscribe it right away; the group stays subscribed for
|
|
9
|
+
* a grace period — its warm window — and drops only once that window lapses.
|
|
10
|
+
* Re-entering within the window costs nothing, since the group was never
|
|
11
|
+
* dropped, so rapid back-and-forth navigation becomes a cache hit rather than a
|
|
12
|
+
* repeated bootstrap.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (already subscribed → no bootstrap), and only when the warm TTL
|
|
15
|
-
* lapses does the group actually drop. This is the boundary hysteresis
|
|
16
|
-
* that turns deck-tab flipping from a re-bootstrap storm into a
|
|
17
|
-
* cache hit.
|
|
14
|
+
* The second is prominence. A group that holds an active write claim is pinned
|
|
15
|
+
* (see {@link SubscriptionManager.pin | pin}) and stays subscribed regardless
|
|
16
|
+
* of navigation, so a row someone is actively editing never loses its live
|
|
17
|
+
* updates. The `baseGroups` are permanent scopes that are always subscribed.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `user:<id>`) that are always in the effective set.
|
|
27
|
-
*
|
|
28
|
-
* The effective set is recomputed and diffed against what was last sent;
|
|
29
|
-
* the transport's `update_subscription` is only called when it actually
|
|
30
|
-
* changes, so hysteresis genuinely suppresses network churn rather than
|
|
31
|
-
* just deferring it.
|
|
32
|
-
*
|
|
33
|
-
* Transport-agnostic: it depends only on {@link SubscriptionTransport},
|
|
34
|
-
* which `SyncWebSocket` satisfies structurally. `now` and the sweep timer
|
|
35
|
-
* are injectable so the policy is deterministic under test.
|
|
19
|
+
* The manager recomputes the full desired set on every change, diffs it against
|
|
20
|
+
* the set the transport last confirmed, and calls `update_subscription` only
|
|
21
|
+
* when the set actually changes — so the smoothing suppresses network traffic
|
|
22
|
+
* rather than merely deferring it. It depends only on
|
|
23
|
+
* {@link SubscriptionTransport}, which {@link SyncWebSocket} satisfies. The
|
|
24
|
+
* clock and the sweep timer are injectable so the policy is deterministic under
|
|
25
|
+
* test.
|
|
36
26
|
*/
|
|
37
27
|
/** The single capability this manager needs from the connection. */
|
|
38
28
|
export interface SubscriptionTransport {
|
|
39
29
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
30
|
+
* Replaces the connection's read interest with the complete group set. This
|
|
31
|
+
* is a full replace, not an incremental add or remove. Resolves with the
|
|
32
|
+
* effective set the server applied, which the manager treats as authoritative
|
|
33
|
+
* for its next diff.
|
|
43
34
|
*/
|
|
44
|
-
updateSubscription(syncGroups:
|
|
35
|
+
updateSubscription(syncGroups: readonly string[]): Promise<{
|
|
45
36
|
syncGroups: string[];
|
|
46
37
|
}>;
|
|
47
38
|
}
|
|
48
|
-
export interface
|
|
39
|
+
export interface SubscriptionManagerOptions {
|
|
49
40
|
/** Connection to drive. `SyncWebSocket` satisfies this structurally. */
|
|
50
41
|
transport: SubscriptionTransport;
|
|
51
42
|
/**
|
|
52
43
|
* Groups always present in the effective set (e.g. `org:<id>`,
|
|
53
44
|
* `user:<id>`). Never warm, never expired.
|
|
54
45
|
*/
|
|
55
|
-
baseGroups?:
|
|
46
|
+
baseGroups?: readonly string[];
|
|
56
47
|
/**
|
|
57
48
|
* How long a `leave`-ed group stays subscribed before it actually drops.
|
|
58
49
|
* This is the hysteresis margin. Default 30s.
|
|
59
50
|
*/
|
|
60
51
|
warmTtlMs?: number;
|
|
61
52
|
/**
|
|
62
|
-
*
|
|
63
|
-
* navigation
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* This is the bounded relevant-set discipline from game netcode. Default 16.
|
|
53
|
+
* The maximum number of warm (left but still subscribed) groups. Under heavy
|
|
54
|
+
* navigation, warm groups would otherwise pile up until each one's window
|
|
55
|
+
* lapses, inflating the connection's subscription set. When the cap is
|
|
56
|
+
* exceeded, the least-recently-warmed group is dropped immediately instead of
|
|
57
|
+
* waiting for its window. Default 16.
|
|
68
58
|
*/
|
|
69
59
|
maxWarm?: number;
|
|
70
60
|
/**
|
|
71
61
|
* Auto-run the warm-expiry sweep on this cadence. Set `0` to disable and
|
|
72
|
-
* drive {@link
|
|
62
|
+
* drive {@link SubscriptionManager.sweep} yourself (tests do this).
|
|
73
63
|
* Default = `warmTtlMs` (checks about once per margin).
|
|
74
64
|
*/
|
|
75
65
|
sweepIntervalMs?: number;
|
|
@@ -81,7 +71,7 @@ export interface AreaOfInterestOptions {
|
|
|
81
71
|
*/
|
|
82
72
|
scheduler?: (fn: () => void, intervalMs: number) => () => void;
|
|
83
73
|
}
|
|
84
|
-
export declare class
|
|
74
|
+
export declare class SubscriptionManager {
|
|
85
75
|
private readonly transport;
|
|
86
76
|
private readonly baseGroups;
|
|
87
77
|
private readonly warmTtlMs;
|
|
@@ -99,7 +89,7 @@ export declare class AreaOfInterestManager {
|
|
|
99
89
|
private inFlight;
|
|
100
90
|
private dirty;
|
|
101
91
|
private readonly cancelSweep;
|
|
102
|
-
constructor(options:
|
|
92
|
+
constructor(options: SubscriptionManagerOptions);
|
|
103
93
|
/**
|
|
104
94
|
* Move a group into the warm set with a fresh TTL, maintaining LRU order
|
|
105
95
|
* and the `maxWarm` cap. JS `Map` preserves insertion order, so deleting
|
|
@@ -134,19 +124,15 @@ export declare class AreaOfInterestManager {
|
|
|
134
124
|
/** The set the manager believes is subscribed (post-confirmation). */
|
|
135
125
|
effectiveGroups(): string[];
|
|
136
126
|
/**
|
|
137
|
-
* Re-
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the manager's
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* socket's server-side index matches local interest, even if warm/pinned
|
|
147
|
-
* groups drifted across the disconnect window. The connect-time URL
|
|
148
|
-
* already carries the last-acked set, so this is a correction frame, not
|
|
149
|
-
* the primary mechanism.
|
|
127
|
+
* Re-asserts the full desired set against the transport, forgetting what was
|
|
128
|
+
* previously confirmed. Call this after a reconnect: a fresh
|
|
129
|
+
* {@link SyncWebSocket} starts from the sync groups named in the connect-time
|
|
130
|
+
* URL, so the manager's diff baseline no longer reflects the new socket.
|
|
131
|
+
* Clearing that baseline makes the next reconcile push one
|
|
132
|
+
* `update_subscription` frame that re-establishes the current interest —
|
|
133
|
+
* including any warm or pinned groups that drifted while the connection was
|
|
134
|
+
* down. The connect-time URL already carries the last-acknowledged set, so
|
|
135
|
+
* this is a correction, not the primary mechanism.
|
|
150
136
|
*/
|
|
151
137
|
resync(): Promise<void>;
|
|
152
138
|
/** Stop the sweep timer. The connection is unaffected. */
|