@abloatai/ablo 0.34.1 → 0.36.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 +2 -1
- package/CHANGELOG.md +758 -5
- package/README.md +56 -502
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +176 -48
- package/dist/BaseSyncedStore.js +346 -214
- package/dist/Database.d.ts +17 -44
- package/dist/Database.js +96 -79
- package/dist/InstanceCache.d.ts +31 -6
- package/dist/InstanceCache.js +65 -30
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +23 -13
- package/dist/Model.js +27 -17
- package/dist/ModelRegistry.d.ts +8 -4
- package/dist/ModelRegistry.js +20 -18
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +47 -47
- package/dist/SyncClient.js +215 -156
- package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
- package/dist/ai-sdk/coordinatedTool.js +1 -1
- package/dist/ai-sdk/coordinationContext.d.ts +2 -2
- package/dist/ai-sdk/coordinationContext.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +3 -3
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/index.d.ts +1 -156
- package/dist/auth/index.js +8 -301
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +129 -963
- package/dist/client/abloClient.d.ts +309 -0
- package/dist/client/abloClient.js +13 -0
- package/dist/client/clientPrelude.d.ts +52 -0
- package/dist/client/clientPrelude.js +60 -0
- package/dist/client/consoleLogger.d.ts +2 -2
- package/dist/client/coreClient.d.ts +60 -0
- package/dist/client/coreClient.js +118 -0
- package/dist/client/createInternalComponents.d.ts +8 -4
- package/dist/client/createInternalComponents.js +17 -10
- package/dist/client/createModelProxy.d.ts +98 -373
- package/dist/client/createModelProxy.js +233 -139
- package/dist/client/humans.d.ts +69 -0
- package/dist/client/humans.js +78 -0
- package/dist/client/modelRegistration.d.ts +1 -1
- package/dist/client/modelRegistration.js +9 -9
- package/dist/client/options.d.ts +73 -17
- package/dist/client/reactiveEngine.d.ts +53 -0
- package/dist/client/reactiveEngine.js +688 -0
- package/dist/client/resourceTypes.d.ts +9 -250
- package/dist/client/resourceTypes.js +8 -5
- package/dist/client/schemaConfig.d.ts +4 -4
- package/dist/client/schemaConfig.js +6 -2
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/client/validateAbloOptions.d.ts +3 -2
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +3 -3
- package/dist/client/wsMutationExecutor.js +3 -3
- package/dist/context.d.ts +22 -9
- package/dist/context.js +33 -9
- package/dist/coordination/ClaimLog.d.ts +26 -0
- package/dist/coordination/ClaimLog.js +32 -0
- package/dist/coordination/index.d.ts +1 -15
- package/dist/coordination/index.js +8 -31
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +230 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +10 -0
- package/dist/environment.d.ts +1 -40
- package/dist/environment.js +8 -37
- package/dist/index.d.ts +44 -36
- package/dist/index.js +30 -22
- package/dist/interfaces/index.d.ts +44 -134
- package/dist/keys/index.d.ts +1 -77
- package/dist/keys/index.js +8 -190
- package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
- package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
- package/dist/mutators/Transaction.d.ts +1 -1
- package/dist/mutators/Transaction.js +1 -1
- package/dist/mutators/UndoManager.d.ts +6 -6
- package/dist/mutators/UndoManager.js +5 -5
- package/dist/mutators/defineMutators.d.ts +3 -3
- package/dist/mutators/defineMutators.js +1 -1
- package/dist/mutators/inverseOp.js +2 -2
- package/dist/mutators/mutateActions.d.ts +3 -3
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +1 -1
- package/dist/mutators/undoApply.js +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/query/client.d.ts +5 -2
- package/dist/query/client.js +10 -9
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/AbloProvider.js +10 -9
- package/dist/react/context.d.ts +3 -3
- package/dist/react/context.js +1 -1
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +6 -5
- package/dist/react/index.js +6 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +12 -5
- package/dist/react/useAblo.js +26 -8
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.d.ts +2 -2
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.d.ts +3 -3
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useUndoScope.d.ts +5 -5
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +69 -10
- package/dist/schema/coordination.js +90 -9
- package/dist/schema/ddl.js +2 -2
- package/dist/schema/diff.d.ts +1 -1
- package/dist/schema/generate.js +1 -1
- package/dist/schema/index.d.ts +11 -10
- package/dist/schema/index.js +22 -18
- package/dist/schema/queries.d.ts +27 -27
- package/dist/schema/queries.js +23 -23
- package/dist/schema/select.d.ts +3 -3
- package/dist/schema/select.js +6 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +20 -3
- package/dist/schema/sugar.d.ts +6 -7
- package/dist/schema/sugar.js +9 -12
- package/dist/schema/syncDeltaRow.d.ts +4 -152
- package/dist/schema/syncDeltaRow.js +4 -105
- package/dist/server/adapter.d.ts +18 -1
- package/dist/server/commit.d.ts +10 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +1 -1
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/adapters/drizzle.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +2 -2
- package/dist/source/adapters/kysely.d.ts +1 -1
- package/dist/source/adapters/kysely.js +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +2 -2
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.d.ts +8 -3
- package/dist/source/adapters/prisma.js +1 -1
- package/dist/source/connector.js +1 -1
- package/dist/source/connectorProtocol.d.ts +2 -8
- package/dist/source/connectorProtocol.js +3 -2
- package/dist/source/contract.d.ts +29 -17
- package/dist/source/contract.js +27 -22
- package/dist/source/factory.d.ts +1 -1
- package/dist/source/idempotency.js +2 -2
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.js +3 -0
- package/dist/source/next.d.ts +1 -1
- package/dist/source/signing.d.ts +9 -2
- package/dist/source/signing.js +4 -1
- package/dist/source/types.d.ts +6 -4
- package/dist/source/types.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +14 -13
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/{core → stores}/StoreManager.d.ts +9 -26
- package/dist/{core → stores}/StoreManager.js +29 -77
- package/dist/stores/SyncActionStore.d.ts +4 -2
- package/dist/stores/SyncActionStore.js +11 -17
- package/dist/stores/syncAction.d.ts +26 -0
- package/dist/stores/syncAction.js +16 -0
- package/dist/surface.d.ts +3 -3
- package/dist/surface.js +6 -4
- package/dist/sync/BootstrapFetcher.d.ts +127 -6
- package/dist/sync/BootstrapFetcher.js +511 -83
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +5 -2
- package/dist/sync/OnDemandLoader.js +61 -21
- package/dist/sync/SubscriptionManager.d.ts +13 -2
- package/dist/sync/SubscriptionManager.js +23 -5
- package/dist/sync/SyncWebSocket.d.ts +27 -510
- package/dist/sync/SyncWebSocket.js +76 -954
- package/dist/sync/awaitClaimGrant.d.ts +4 -44
- package/dist/sync/awaitClaimGrant.js +4 -109
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/commitFrames.d.ts +6 -40
- package/dist/sync/commitFrames.js +6 -97
- package/dist/sync/contextPorts.d.ts +18 -0
- package/dist/sync/contextPorts.js +31 -0
- package/dist/sync/createClaimStream.d.ts +5 -49
- package/dist/sync/createClaimStream.js +5 -469
- package/dist/sync/createPresenceStream.d.ts +26 -4
- package/dist/sync/createPresenceStream.js +28 -20
- package/dist/sync/createSnapshot.d.ts +2 -2
- package/dist/sync/createSnapshot.js +1 -1
- package/dist/sync/credentialLifecycle.d.ts +5 -173
- package/dist/sync/credentialLifecycle.js +5 -320
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +24 -6
- package/dist/sync/participants.js +32 -23
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +23 -33
- package/dist/sync/schemas.js +29 -20
- package/dist/sync/syncPlan.d.ts +3 -3
- package/dist/sync/wsFrameHandlers.d.ts +6 -114
- package/dist/sync/wsFrameHandlers.js +6 -392
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/ablo.d.ts +88 -0
- package/dist/transaction/ablo.js +33 -0
- package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
- package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
- package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
- package/dist/transaction/auth/bootstrapScope.js +1 -0
- package/dist/transaction/auth/capability.d.ts +212 -0
- package/dist/transaction/auth/capability.js +224 -0
- package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
- package/dist/{client → transaction/auth}/identity.d.ts +8 -7
- package/dist/{client → transaction/auth}/identity.js +1 -1
- package/dist/transaction/auth/index.d.ts +162 -0
- package/dist/transaction/auth/index.js +304 -0
- package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
- package/dist/{auth → transaction/auth}/schemas.js +13 -13
- package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
- package/dist/{client → transaction/auth}/sessionMint.js +4 -7
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/claimMeta.d.ts +49 -0
- package/dist/transaction/coordination/claimMeta.js +52 -0
- package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
- package/dist/transaction/coordination/createClaimStream.js +475 -0
- package/dist/transaction/coordination/events.d.ts +74 -0
- package/dist/transaction/coordination/events.js +7 -0
- package/dist/transaction/coordination/index.d.ts +19 -0
- package/dist/transaction/coordination/index.js +45 -0
- package/dist/transaction/coordination/locator.d.ts +104 -0
- package/dist/transaction/coordination/locator.js +102 -0
- package/dist/transaction/coordination/schema.d.ts +1536 -0
- package/dist/transaction/coordination/schema.js +1177 -0
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +107 -0
- package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
- package/dist/{coordination → transaction/coordination}/trace.js +18 -25
- package/dist/transaction/durableWrites.d.ts +62 -0
- package/dist/{client → transaction}/durableWrites.js +28 -3
- package/dist/transaction/environment.d.ts +105 -0
- package/dist/transaction/environment.js +108 -0
- package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
- package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- package/dist/transaction/footprint.d.ts +111 -0
- package/dist/transaction/footprint.js +0 -0
- package/dist/transaction/index.d.ts +20 -0
- package/dist/transaction/index.js +20 -0
- package/dist/transaction/keys/index.d.ts +87 -0
- package/dist/transaction/keys/index.js +207 -0
- package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
- package/dist/transaction/log/syncDeltaRow.js +95 -0
- package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
- package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
- package/dist/transaction/logger.d.ts +16 -0
- package/dist/transaction/logger.js +7 -0
- package/dist/transaction/observability.d.ts +53 -0
- package/dist/transaction/observability.js +19 -0
- package/dist/transaction/plugin.d.ts +285 -0
- package/dist/transaction/plugin.js +106 -0
- package/dist/{policy → transaction/policy}/types.d.ts +3 -3
- package/dist/{policy → transaction/policy}/types.js +2 -0
- package/dist/transaction/resources/httpResources.d.ts +321 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +427 -0
- package/dist/transaction/resources/modelOperations.js +12 -0
- package/dist/transaction/resources/mutationOptions.d.ts +66 -0
- package/dist/transaction/resources/mutationOptions.js +9 -0
- package/dist/transaction/resources/where.d.ts +101 -0
- package/dist/transaction/resources/where.js +115 -0
- package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
- package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
- package/dist/{schema → transaction/schema}/field.d.ts +17 -23
- package/dist/{schema → transaction/schema}/field.js +5 -5
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/loadStrategy.d.ts +45 -0
- package/dist/transaction/schema/loadStrategy.js +46 -0
- package/dist/{schema → transaction/schema}/model.d.ts +50 -35
- package/dist/{schema → transaction/schema}/model.js +30 -20
- package/dist/transaction/schema/openapi.d.ts +58 -0
- package/dist/transaction/schema/openapi.js +501 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
- package/dist/{schema → transaction/schema}/relation.js +7 -7
- package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
- package/dist/{schema → transaction/schema}/residency.js +0 -5
- package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
- package/dist/{schema → transaction/schema}/roles.js +5 -5
- package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
- package/dist/{schema → transaction/schema}/schema.js +24 -3
- package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
- package/dist/{schema → transaction/schema}/tenancy.js +7 -4
- package/dist/transaction/transactionLayer.d.ts +82 -0
- package/dist/transaction/transactionLayer.js +24 -0
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
- package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
- package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
- package/dist/transaction/transport/commitFrames.d.ts +90 -0
- package/dist/transaction/transport/commitFrames.js +134 -0
- package/dist/transaction/transport/connectionManager.d.ts +215 -0
- package/dist/transaction/transport/connectionManager.js +673 -0
- package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
- package/dist/transaction/transport/credentialLifecycle.js +324 -0
- package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
- package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
- package/dist/transaction/transport/httpClient.d.ts +131 -0
- package/dist/{client → transaction/transport}/httpClient.js +6 -5
- package/dist/transaction/transport/httpOptions.d.ts +33 -0
- package/dist/transaction/transport/httpOptions.js +12 -0
- package/dist/{client → transaction/transport}/httpTransport.js +295 -97
- package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
- package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
- package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
- package/dist/transaction/transport/wsFrameHandlers.js +429 -0
- package/dist/transaction/transport/wsTransport.d.ts +574 -0
- package/dist/transaction/transport/wsTransport.js +1023 -0
- package/dist/transaction/types/assertExact.d.ts +17 -0
- package/dist/transaction/types/assertExact.js +1 -0
- package/dist/{types → transaction/types}/global.d.ts +17 -2
- package/dist/{types → transaction/types}/global.js +2 -1
- package/dist/{types → transaction/types}/index.d.ts +14 -46
- package/dist/{types → transaction/types}/index.js +7 -16
- package/dist/{types → transaction/types}/streams.d.ts +73 -45
- package/dist/transaction/utils/duration.d.ts +50 -0
- package/dist/{utils → transaction/utils}/duration.js +32 -0
- package/dist/{utils → transaction/utils}/json.d.ts +18 -0
- package/dist/transaction/utils/json.js +276 -0
- package/dist/transaction/wire/accountResponses.d.ts +420 -0
- package/dist/transaction/wire/accountResponses.js +290 -0
- package/dist/transaction/wire/auth.d.ts +56 -0
- package/dist/transaction/wire/auth.js +63 -0
- package/dist/transaction/wire/claimEvent.d.ts +76 -0
- package/dist/transaction/wire/claimEvent.js +73 -0
- package/dist/transaction/wire/claims.d.ts +530 -0
- package/dist/transaction/wire/claims.js +327 -0
- package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
- package/dist/{wire → transaction/wire}/commit.js +68 -47
- package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
- package/dist/{wire → transaction/wire}/delta.js +37 -13
- package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
- package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
- package/dist/transaction/wire/feedCursor.d.ts +60 -0
- package/dist/transaction/wire/feedCursor.js +82 -0
- package/dist/transaction/wire/feedEvent.d.ts +204 -0
- package/dist/transaction/wire/feedEvent.js +65 -0
- package/dist/transaction/wire/frames.d.ts +194 -0
- package/dist/transaction/wire/frames.js +50 -0
- package/dist/transaction/wire/inboundFrames.d.ts +562 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +54 -0
- package/dist/transaction/wire/index.js +83 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
- package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
- package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
- package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
- package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
- package/dist/transactions/mutations/commitLatency.d.ts +52 -0
- package/dist/transactions/mutations/commitLatency.js +130 -0
- package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
- package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
- package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
- package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
- package/dist/transactions/mutations/durableWriteStore.js +12 -0
- package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
- package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- package/dist/{core → views}/QueryView.d.ts +2 -2
- package/dist/{core → views}/QueryView.js +2 -2
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/dist/webhooks/events.d.ts +2 -2
- package/dist/wire/index.d.ts +1 -34
- package/dist/wire/index.js +8 -49
- package/docs/agent-messaging.md +3 -3
- package/docs/agents.md +20 -13
- package/docs/api-keys.md +14 -10
- package/docs/api.md +27 -61
- package/docs/audit.md +6 -3
- package/docs/cli.md +41 -13
- package/docs/client-behavior.md +11 -9
- package/docs/concurrency-convention.md +49 -57
- package/docs/coordination.md +283 -121
- package/docs/data-sources.md +7 -5
- package/docs/debugging.md +39 -15
- package/docs/deployment.md +267 -0
- package/docs/examples/agent-human.md +49 -42
- package/docs/examples/ai-sdk-tool.md +69 -44
- package/docs/examples/existing-python-backend.md +8 -6
- package/docs/examples/nextjs.md +129 -47
- package/docs/examples/scoped-agent.md +46 -45
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +87 -30
- package/docs/guarantees.md +41 -12
- package/docs/how-it-works.md +38 -12
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +77 -74
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +31 -19
- package/docs/mcp.md +46 -21
- package/docs/migration.md +95 -18
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +3 -1
- package/docs/quickstart.md +22 -5
- package/docs/react.md +31 -18
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +4 -2
- package/docs/webhooks.md +12 -10
- package/llms.txt +48 -18
- package/package.json +21 -26
- package/dist/agent/Agent.d.ts +0 -366
- package/dist/agent/Agent.js +0 -514
- package/dist/agent/index.d.ts +0 -115
- package/dist/agent/index.js +0 -128
- package/dist/agent/session.d.ts +0 -93
- package/dist/agent/session.js +0 -149
- package/dist/agent/types.d.ts +0 -68
- package/dist/agent/types.js +0 -9
- package/dist/cli.cjs +0 -286329
- package/dist/client/durableWrites.d.ts +0 -21
- package/dist/client/httpClient.d.ts +0 -80
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/coordination/schema.js +0 -578
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -192
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -71
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/dist/transactions/durableWriteStore.js +0 -30
- package/dist/utils/duration.d.ts +0 -25
- package/dist/utils/json.js +0 -88
- package/dist/wire/errorEnvelope.d.ts +0 -55
- package/dist/wire/frames.d.ts +0 -197
- package/dist/wire/frames.js +0 -49
- package/dist/wire/listEnvelope.js +0 -18
- package/docs/interaction-model.md +0 -97
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
- /package/dist/{client → transaction}/persistence.d.ts +0 -0
- /package/dist/{client → transaction}/persistence.js +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
- /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.js +0 -0
- /package/dist/{types → transaction/types}/participant.d.ts +0 -0
- /package/dist/{types → transaction/types}/participant.js +0 -0
- /package/dist/{types → transaction/types}/streams.js +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
- /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocol.js +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
- /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
- /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
- /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -1,175 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* client only through the small {@link CredentialLifecycleContext} interface,
|
|
7
|
-
* so the two can reference each other without an import cycle.
|
|
2
|
+
* Moved to the settlement core with the duplex transport (ADR 0016): keeping
|
|
3
|
+
* a long-lived socket's credential fresh is connection plumbing an agent needs
|
|
4
|
+
* as much as a browser does. This path re-exports it so existing importers
|
|
5
|
+
* stay unchanged.
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Tri-state outcome of a credential re-mint, mirroring the `getToken`
|
|
12
|
-
* contract (see {@link CredentialLifecycle.refresh}).
|
|
13
|
-
*/
|
|
14
|
-
export type CredentialRefreshOutcome = 'refreshed' | 'session_error' | 'network_error';
|
|
15
|
-
/**
|
|
16
|
-
* What an auth-rejected transport should do after recovery has run. `'retry'`
|
|
17
|
-
* means a fresh credential is now in place — replay the request once. `'stop'`
|
|
18
|
-
* means don't replay: either the session is gone for good, the rejection is one
|
|
19
|
-
* a re-mint can't cure, or the mint failed transiently and the caller's own
|
|
20
|
-
* retry path will recover later.
|
|
21
|
-
*/
|
|
22
|
-
export type CredentialRecoveryOutcome = 'retry' | 'stop';
|
|
23
|
-
/**
|
|
24
|
-
* What a credential refresher may resolve with. The plain-string form returns
|
|
25
|
-
* just the token. The object form also carries the mint response's `expiresAt`
|
|
26
|
-
* (an ISO string, epoch milliseconds, or a `Date`), which lets the proactive
|
|
27
|
-
* pre-roll schedule against the credential's real lifetime rather than assume
|
|
28
|
-
* the server's default expiry.
|
|
29
|
-
*/
|
|
30
|
-
export type CredentialRefreshResult = string | {
|
|
31
|
-
readonly token: string;
|
|
32
|
-
readonly expiresAt?: string | number | Date;
|
|
33
|
-
} | null;
|
|
34
|
-
/** A credential re-mint hook. `Promise<string | null>` resolvers remain
|
|
35
|
-
* assignable — the widened result type is a superset. */
|
|
36
|
-
export type CredentialRefresher = () => Promise<CredentialRefreshResult>;
|
|
37
|
-
/** The fallback pre-roll interval, and also its ceiling: 10 minutes, which sits
|
|
38
|
-
* comfortably inside the server's 15-minute default credential lifetime. Used
|
|
39
|
-
* as-is when the refresher reports no expiry. */
|
|
40
|
-
export declare const DEFAULT_PREROLL_INTERVAL_MS: number;
|
|
41
|
-
/** Floor so a very short (or already-elapsed) TTL can't hot-loop the mint. */
|
|
42
|
-
export declare const MIN_PREROLL_DELAY_MS: number;
|
|
43
|
-
/**
|
|
44
|
-
* Computes how long to wait before pre-rolling a credential that expires at
|
|
45
|
-
* `expiresAtMs`: about two-thirds of the remaining lifetime (a 15-minute
|
|
46
|
-
* credential yields 10 minutes), clamped to
|
|
47
|
-
* [{@link MIN_PREROLL_DELAY_MS}, {@link DEFAULT_PREROLL_INTERVAL_MS}]. An
|
|
48
|
-
* unknown expiry (`null`) falls back to the 10-minute interval. Exported for
|
|
49
|
-
* unit tests.
|
|
50
|
-
*/
|
|
51
|
-
export declare function computePrerollDelayMs(expiresAtMs: number | null, nowMs: number): number;
|
|
52
|
-
/**
|
|
53
|
-
* The callbacks this lifecycle needs back from the surrounding client. It is
|
|
54
|
-
* deliberately minimal — three callbacks, each resolved lazily at call time,
|
|
55
|
-
* because the connection machinery behind two of them isn't constructed until
|
|
56
|
-
* the sync connection is set up.
|
|
57
|
-
*/
|
|
58
|
-
export interface CredentialLifecycleContext {
|
|
59
|
-
/** Push a freshly-minted access token into the shared credential source
|
|
60
|
-
* (no-op when the deployment wired no credential source). */
|
|
61
|
-
setAuthToken(token: string): void;
|
|
62
|
-
/** Nudge the connection to re-probe using the credential now in place. */
|
|
63
|
-
nudgeReconnect(): void;
|
|
64
|
-
/** Report that the long-lived login is gone, so the connection can move to
|
|
65
|
-
* its signed-out state. */
|
|
66
|
-
reportSessionExpired(): void;
|
|
67
|
-
}
|
|
68
|
-
export declare class CredentialLifecycle {
|
|
69
|
-
private readonly ctx;
|
|
70
|
-
/**
|
|
71
|
-
* The hook that mints a fresh short-lived access credential (the `ek_`/`rk_`
|
|
72
|
-
* key). An integrator wires it from their own token endpoint: this lifecycle
|
|
73
|
-
* decides when to refresh (a stale-credential probe or an external nudge),
|
|
74
|
-
* and the hook decides how to mint. It follows the same contract as a
|
|
75
|
-
* `getToken` function — it resolves a token string on success, `null` when
|
|
76
|
-
* the long-lived login is gone (a terminal state), and throws on a transient
|
|
77
|
-
* or offline failure. Used by {@link refresh}. When it is absent there is no
|
|
78
|
-
* silent re-mint, as with a static `apiKey` whose credential source is
|
|
79
|
-
* refreshed elsewhere.
|
|
80
|
-
*/
|
|
81
|
-
private credentialRefresher;
|
|
82
|
-
/** Single-flight guard so a wake nudge + an in-flight request + a probe don't
|
|
83
|
-
* all mint at once (the classic "token thrash → random logout" bug). */
|
|
84
|
-
private inFlightCredentialRefresh;
|
|
85
|
-
/** Tears down the proactive credential lifecycle (the refresh timer and the
|
|
86
|
-
* OS-wake listener) installed by {@link start}; cleared when the client
|
|
87
|
-
* disconnects. Null when no refresher is wired. */
|
|
88
|
-
private credentialLifecycleTeardown;
|
|
89
|
-
/** Epoch milliseconds at which the current credential expires, when the
|
|
90
|
-
* refresher reports it (the object form of {@link CredentialRefreshResult}).
|
|
91
|
-
* `null` for the string-form resolver, in which case the pre-roll uses its
|
|
92
|
-
* fixed interval. */
|
|
93
|
-
private credentialExpiresAtMs;
|
|
94
|
-
/** Re-arms the proactive pre-roll timer (set by {@link start}). Called after
|
|
95
|
-
* every successful mint, so a refresh triggered reactively (by a probe, an
|
|
96
|
-
* OS wake, or the first mint) re-anchors the schedule to the fresh
|
|
97
|
-
* credential's real expiry instead of a stale fixed delay. */
|
|
98
|
-
private prerollReschedule;
|
|
99
|
-
constructor(ctx: CredentialLifecycleContext);
|
|
100
|
-
/**
|
|
101
|
-
* Registers the re-mint hook for the access credential — a function that
|
|
102
|
-
* mints a fresh `ek_`/`rk_` key, typically the integrator's `getToken`. See
|
|
103
|
-
* {@link credentialRefresher}.
|
|
104
|
-
*/
|
|
105
|
-
setRefresher(refresher: CredentialRefresher | null): void;
|
|
106
|
-
/**
|
|
107
|
-
* Re-mints the short-lived access credential, pushes it into the credential
|
|
108
|
-
* source, and reports a three-way outcome the connection layer acts on:
|
|
109
|
-
* - a token string → `'refreshed'` (the fresh key is in place; re-probe and reconnect)
|
|
110
|
-
* - `null` → `'session_error'` (the login itself is gone — terminal, sign out)
|
|
111
|
-
* - a thrown error → `'network_error'` (the mint endpoint was unreachable — transient)
|
|
112
|
-
*
|
|
113
|
-
* The call is single-flight: concurrent triggers (an OS wake, an in-flight
|
|
114
|
-
* request, a probe) share one in-flight promise, so the credential is never
|
|
115
|
-
* minted twice at once. This avoids the failure where every rejected request
|
|
116
|
-
* mints a new token and the resulting thrash logs the user out.
|
|
117
|
-
*
|
|
118
|
-
* With no refresher wired, it resolves `'refreshed'` as a no-op re-probe: a
|
|
119
|
-
* static-`apiKey` client has no session to mint from and its credential
|
|
120
|
-
* source is refreshed elsewhere, so it simply re-probes with what it holds.
|
|
121
|
-
*/
|
|
122
|
-
refresh(): Promise<CredentialRefreshOutcome>;
|
|
123
|
-
/**
|
|
124
|
-
* Interprets a refresh outcome and drives the connection accordingly. This is
|
|
125
|
-
* the single place the three-way outcome is acted on, shared by the proactive
|
|
126
|
-
* pre-roll, the OS-wake nudge, and the HTTP auth-recovery path, so every
|
|
127
|
-
* trigger converges on the same behavior.
|
|
128
|
-
*/
|
|
129
|
-
private routeRefreshOutcome;
|
|
130
|
-
/**
|
|
131
|
-
* The shared recovery path for a request rejected on authentication, over any
|
|
132
|
-
* transport. The WebSocket probe already routes its own 401s; HTTP callers
|
|
133
|
-
* call this instead of inventing their own handling, so every path shares one
|
|
134
|
-
* single-flight mint, one outcome routing, and one taxonomy. It classifies
|
|
135
|
-
* the same recovery codes the connection probe does:
|
|
136
|
-
* - `access_credential_expiry` — the routine case, an expired `ek_`/`rk_`:
|
|
137
|
-
* silently re-mint through the single-flight {@link refresh} and tell the
|
|
138
|
-
* caller to replay once on success. This never signs out on its own; the
|
|
139
|
-
* only terminal path is the mint resolving `null`.
|
|
140
|
-
* - `session_expiry` — the login is gone: report it (which drives sign-out)
|
|
141
|
-
* and stop, since replaying is pointless.
|
|
142
|
-
* - `auth_blocked`, `permission`, and everything else — re-minting would
|
|
143
|
-
* produce the same rejected credential, so stop and leave the connection
|
|
144
|
-
* alone.
|
|
145
|
-
*/
|
|
146
|
-
recoverFromAuthRejection(recovery: RecoveryClass): Promise<CredentialRecoveryOutcome>;
|
|
147
|
-
/**
|
|
148
|
-
* Installs the credential lifecycle. It has two parts:
|
|
149
|
-
* 1. Reactive — registers `getToken` as the re-mint hook the connection
|
|
150
|
-
* calls when a probe finds the key stale, or on a nudge.
|
|
151
|
-
* 2. Proactive — keeps the short-lived key fresh ahead of expiry with a
|
|
152
|
-
* refresh timer inside the credential's lifetime, plus a re-mint on OS
|
|
153
|
-
* wake. The whole proactive block is browser-gated on `typeof window`,
|
|
154
|
-
* because a server render has no socket to keep warm and the resolver is
|
|
155
|
-
* browser-oriented; arming it under Node would fire a relative-URL fetch
|
|
156
|
-
* and throw. (Agents pass a static `apiKey` with no resolver, so this
|
|
157
|
-
* method is never called for them.)
|
|
158
|
-
*
|
|
159
|
-
* Refreshing is automatic — a consumer never calls a refresh method. The call
|
|
160
|
-
* is idempotent: a second call replaces the first, and it is torn down when
|
|
161
|
-
* the client disconnects.
|
|
162
|
-
*
|
|
163
|
-
* `opts.proactiveInNode` arms the refresh timer on a windowless host as well.
|
|
164
|
-
* Set it for agent or system participants — long-lived server sockets whose
|
|
165
|
-
* `rk_`/`ek_` must renew before the server's keepalive check closes them.
|
|
166
|
-
* Node timers are `unref`ed, so a finishing script is never held alive by the
|
|
167
|
-
* pre-roll. The OS-wake listener stays browser-only regardless, since there
|
|
168
|
-
* is no `window` to listen on.
|
|
169
|
-
*/
|
|
170
|
-
start(getToken: CredentialRefresher, opts?: {
|
|
171
|
-
proactiveInNode?: boolean;
|
|
172
|
-
}): void;
|
|
173
|
-
/** Tear down the proactive credential lifecycle (idempotent). */
|
|
174
|
-
stop(): void;
|
|
175
|
-
}
|
|
7
|
+
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '../transaction/transport/credentialLifecycle.js';
|
|
@@ -1,322 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* client only through the small {@link CredentialLifecycleContext} interface,
|
|
7
|
-
* so the two can reference each other without an import cycle.
|
|
2
|
+
* Moved to the settlement core with the duplex transport (ADR 0016): keeping
|
|
3
|
+
* a long-lived socket's credential fresh is connection plumbing an agent needs
|
|
4
|
+
* as much as a browser does. This path re-exports it so existing importers
|
|
5
|
+
* stay unchanged.
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
/** The fallback pre-roll interval, and also its ceiling: 10 minutes, which sits
|
|
11
|
-
* comfortably inside the server's 15-minute default credential lifetime. Used
|
|
12
|
-
* as-is when the refresher reports no expiry. */
|
|
13
|
-
export const DEFAULT_PREROLL_INTERVAL_MS = 10 * 60 * 1000;
|
|
14
|
-
/** Floor so a very short (or already-elapsed) TTL can't hot-loop the mint. */
|
|
15
|
-
export const MIN_PREROLL_DELAY_MS = 30 * 1000;
|
|
16
|
-
/**
|
|
17
|
-
* Computes how long to wait before pre-rolling a credential that expires at
|
|
18
|
-
* `expiresAtMs`: about two-thirds of the remaining lifetime (a 15-minute
|
|
19
|
-
* credential yields 10 minutes), clamped to
|
|
20
|
-
* [{@link MIN_PREROLL_DELAY_MS}, {@link DEFAULT_PREROLL_INTERVAL_MS}]. An
|
|
21
|
-
* unknown expiry (`null`) falls back to the 10-minute interval. Exported for
|
|
22
|
-
* unit tests.
|
|
23
|
-
*/
|
|
24
|
-
export function computePrerollDelayMs(expiresAtMs, nowMs) {
|
|
25
|
-
if (expiresAtMs === null || !Number.isFinite(expiresAtMs)) {
|
|
26
|
-
return DEFAULT_PREROLL_INTERVAL_MS;
|
|
27
|
-
}
|
|
28
|
-
const remaining = expiresAtMs - nowMs;
|
|
29
|
-
if (remaining <= 0)
|
|
30
|
-
return MIN_PREROLL_DELAY_MS;
|
|
31
|
-
const twoThirds = Math.floor((remaining * 2) / 3);
|
|
32
|
-
return Math.min(Math.max(twoThirds, MIN_PREROLL_DELAY_MS), DEFAULT_PREROLL_INTERVAL_MS);
|
|
33
|
-
}
|
|
34
|
-
/** Narrow a refresher-supplied `expiresAt` to epoch ms, or `null` when
|
|
35
|
-
* absent/unparseable (→ fallback cadence, never a crash). */
|
|
36
|
-
function normalizeExpiresAtMs(expiresAt) {
|
|
37
|
-
if (expiresAt === undefined)
|
|
38
|
-
return null;
|
|
39
|
-
const ms = expiresAt instanceof Date
|
|
40
|
-
? expiresAt.getTime()
|
|
41
|
-
: typeof expiresAt === 'number'
|
|
42
|
-
? expiresAt
|
|
43
|
-
: Date.parse(expiresAt);
|
|
44
|
-
return Number.isFinite(ms) ? ms : null;
|
|
45
|
-
}
|
|
46
|
-
export class CredentialLifecycle {
|
|
47
|
-
ctx;
|
|
48
|
-
/**
|
|
49
|
-
* The hook that mints a fresh short-lived access credential (the `ek_`/`rk_`
|
|
50
|
-
* key). An integrator wires it from their own token endpoint: this lifecycle
|
|
51
|
-
* decides when to refresh (a stale-credential probe or an external nudge),
|
|
52
|
-
* and the hook decides how to mint. It follows the same contract as a
|
|
53
|
-
* `getToken` function — it resolves a token string on success, `null` when
|
|
54
|
-
* the long-lived login is gone (a terminal state), and throws on a transient
|
|
55
|
-
* or offline failure. Used by {@link refresh}. When it is absent there is no
|
|
56
|
-
* silent re-mint, as with a static `apiKey` whose credential source is
|
|
57
|
-
* refreshed elsewhere.
|
|
58
|
-
*/
|
|
59
|
-
credentialRefresher = null;
|
|
60
|
-
/** Single-flight guard so a wake nudge + an in-flight request + a probe don't
|
|
61
|
-
* all mint at once (the classic "token thrash → random logout" bug). */
|
|
62
|
-
inFlightCredentialRefresh = null;
|
|
63
|
-
/** Tears down the proactive credential lifecycle (the refresh timer and the
|
|
64
|
-
* OS-wake listener) installed by {@link start}; cleared when the client
|
|
65
|
-
* disconnects. Null when no refresher is wired. */
|
|
66
|
-
credentialLifecycleTeardown = null;
|
|
67
|
-
/** Epoch milliseconds at which the current credential expires, when the
|
|
68
|
-
* refresher reports it (the object form of {@link CredentialRefreshResult}).
|
|
69
|
-
* `null` for the string-form resolver, in which case the pre-roll uses its
|
|
70
|
-
* fixed interval. */
|
|
71
|
-
credentialExpiresAtMs = null;
|
|
72
|
-
/** Re-arms the proactive pre-roll timer (set by {@link start}). Called after
|
|
73
|
-
* every successful mint, so a refresh triggered reactively (by a probe, an
|
|
74
|
-
* OS wake, or the first mint) re-anchors the schedule to the fresh
|
|
75
|
-
* credential's real expiry instead of a stale fixed delay. */
|
|
76
|
-
prerollReschedule = null;
|
|
77
|
-
constructor(ctx) {
|
|
78
|
-
this.ctx = ctx;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Registers the re-mint hook for the access credential — a function that
|
|
82
|
-
* mints a fresh `ek_`/`rk_` key, typically the integrator's `getToken`. See
|
|
83
|
-
* {@link credentialRefresher}.
|
|
84
|
-
*/
|
|
85
|
-
setRefresher(refresher) {
|
|
86
|
-
this.credentialRefresher = refresher;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Re-mints the short-lived access credential, pushes it into the credential
|
|
90
|
-
* source, and reports a three-way outcome the connection layer acts on:
|
|
91
|
-
* - a token string → `'refreshed'` (the fresh key is in place; re-probe and reconnect)
|
|
92
|
-
* - `null` → `'session_error'` (the login itself is gone — terminal, sign out)
|
|
93
|
-
* - a thrown error → `'network_error'` (the mint endpoint was unreachable — transient)
|
|
94
|
-
*
|
|
95
|
-
* The call is single-flight: concurrent triggers (an OS wake, an in-flight
|
|
96
|
-
* request, a probe) share one in-flight promise, so the credential is never
|
|
97
|
-
* minted twice at once. This avoids the failure where every rejected request
|
|
98
|
-
* mints a new token and the resulting thrash logs the user out.
|
|
99
|
-
*
|
|
100
|
-
* With no refresher wired, it resolves `'refreshed'` as a no-op re-probe: a
|
|
101
|
-
* static-`apiKey` client has no session to mint from and its credential
|
|
102
|
-
* source is refreshed elsewhere, so it simply re-probes with what it holds.
|
|
103
|
-
*/
|
|
104
|
-
async refresh() {
|
|
105
|
-
const refresher = this.credentialRefresher;
|
|
106
|
-
if (!refresher)
|
|
107
|
-
return 'refreshed';
|
|
108
|
-
if (this.inFlightCredentialRefresh)
|
|
109
|
-
return this.inFlightCredentialRefresh;
|
|
110
|
-
const run = (async () => {
|
|
111
|
-
try {
|
|
112
|
-
const result = await refresher();
|
|
113
|
-
const token = typeof result === 'string' ? result : result?.token;
|
|
114
|
-
if (!token) {
|
|
115
|
-
// null = the long-lived login is gone (the mint endpoint answered
|
|
116
|
-
// 401/403). Terminal — this routes to sign-out.
|
|
117
|
-
return 'session_error';
|
|
118
|
-
}
|
|
119
|
-
// Object-form resolvers carry the mint's actual expiry; remember it so
|
|
120
|
-
// the proactive pre-roll schedules off the real TTL (string-form
|
|
121
|
-
// resolvers leave it null → fixed fallback cadence).
|
|
122
|
-
this.credentialExpiresAtMs =
|
|
123
|
-
typeof result === 'object' && result !== null
|
|
124
|
-
? normalizeExpiresAtMs(result.expiresAt)
|
|
125
|
-
: null;
|
|
126
|
-
this.ctx.setAuthToken(token);
|
|
127
|
-
// Re-anchor the proactive pre-roll to the fresh credential's expiry.
|
|
128
|
-
this.prerollReschedule?.();
|
|
129
|
-
return 'refreshed';
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
// A throw = transient (offline / mint endpoint unreachable / 5xx). The
|
|
133
|
-
// login may be perfectly valid; never sign out for this — back off and
|
|
134
|
-
// retry. Mirrors the `getToken` throw-vs-null contract end-to-end.
|
|
135
|
-
const message = error?.message ?? String(error);
|
|
136
|
-
// A relative-URL resolver invoked on the server (Node's fetch has no
|
|
137
|
-
// origin to resolve against) throws the opaque "Failed to parse URL" or
|
|
138
|
-
// "Only absolute URLs are supported". Translate it into something
|
|
139
|
-
// actionable rather than a mystery transient blip: the proactive
|
|
140
|
-
// refresh is browser-only, so reaching here means the resolver fired
|
|
141
|
-
// from a server render or a server route.
|
|
142
|
-
if (typeof window === 'undefined' && /parse URL|absolute URLs?/i.test(message)) {
|
|
143
|
-
getContext().logger.warn('credential resolver ran on the server with a relative URL — Node fetch needs an absolute URL. ' +
|
|
144
|
-
'Refresh the Ablo client in the browser, or build an absolute URL server-side ' +
|
|
145
|
-
"(e.g. new URL('/api/ablo-session', process.env.NEXT_PUBLIC_APP_URL)).", { error: message });
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
getContext().logger.debug('access-credential re-mint failed (transient)', { error: message });
|
|
149
|
-
}
|
|
150
|
-
return 'network_error';
|
|
151
|
-
}
|
|
152
|
-
})();
|
|
153
|
-
this.inFlightCredentialRefresh = run;
|
|
154
|
-
try {
|
|
155
|
-
return await run;
|
|
156
|
-
}
|
|
157
|
-
finally {
|
|
158
|
-
this.inFlightCredentialRefresh = null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Interprets a refresh outcome and drives the connection accordingly. This is
|
|
163
|
-
* the single place the three-way outcome is acted on, shared by the proactive
|
|
164
|
-
* pre-roll, the OS-wake nudge, and the HTTP auth-recovery path, so every
|
|
165
|
-
* trigger converges on the same behavior.
|
|
166
|
-
*/
|
|
167
|
-
routeRefreshOutcome(outcome) {
|
|
168
|
-
if (outcome === 'refreshed') {
|
|
169
|
-
// Fresh key already pushed into the credential source by `refresh`;
|
|
170
|
-
// nudge a parked connection to re-probe with it.
|
|
171
|
-
this.ctx.nudgeReconnect();
|
|
172
|
-
return 'retry';
|
|
173
|
-
}
|
|
174
|
-
if (outcome === 'session_error') {
|
|
175
|
-
// The long-lived login is gone (the mint answered 401/403). Report it;
|
|
176
|
-
// this is harmless in connection states that don't accept the event,
|
|
177
|
-
// which converge on sign-out anyway.
|
|
178
|
-
this.ctx.reportSessionExpired();
|
|
179
|
-
return 'stop';
|
|
180
|
-
}
|
|
181
|
-
// 'network_error' → transient (offline or a mint hiccup); the next proactive
|
|
182
|
-
// tick or the connection's own probe retries. Never sign out, never replay now.
|
|
183
|
-
return 'stop';
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* The shared recovery path for a request rejected on authentication, over any
|
|
187
|
-
* transport. The WebSocket probe already routes its own 401s; HTTP callers
|
|
188
|
-
* call this instead of inventing their own handling, so every path shares one
|
|
189
|
-
* single-flight mint, one outcome routing, and one taxonomy. It classifies
|
|
190
|
-
* the same recovery codes the connection probe does:
|
|
191
|
-
* - `access_credential_expiry` — the routine case, an expired `ek_`/`rk_`:
|
|
192
|
-
* silently re-mint through the single-flight {@link refresh} and tell the
|
|
193
|
-
* caller to replay once on success. This never signs out on its own; the
|
|
194
|
-
* only terminal path is the mint resolving `null`.
|
|
195
|
-
* - `session_expiry` — the login is gone: report it (which drives sign-out)
|
|
196
|
-
* and stop, since replaying is pointless.
|
|
197
|
-
* - `auth_blocked`, `permission`, and everything else — re-minting would
|
|
198
|
-
* produce the same rejected credential, so stop and leave the connection
|
|
199
|
-
* alone.
|
|
200
|
-
*/
|
|
201
|
-
async recoverFromAuthRejection(recovery) {
|
|
202
|
-
switch (recovery) {
|
|
203
|
-
case 'access_credential_expiry':
|
|
204
|
-
return this.routeRefreshOutcome(await this.refresh());
|
|
205
|
-
case 'session_expiry':
|
|
206
|
-
this.ctx.reportSessionExpired();
|
|
207
|
-
return 'stop';
|
|
208
|
-
default:
|
|
209
|
-
return 'stop';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Installs the credential lifecycle. It has two parts:
|
|
214
|
-
* 1. Reactive — registers `getToken` as the re-mint hook the connection
|
|
215
|
-
* calls when a probe finds the key stale, or on a nudge.
|
|
216
|
-
* 2. Proactive — keeps the short-lived key fresh ahead of expiry with a
|
|
217
|
-
* refresh timer inside the credential's lifetime, plus a re-mint on OS
|
|
218
|
-
* wake. The whole proactive block is browser-gated on `typeof window`,
|
|
219
|
-
* because a server render has no socket to keep warm and the resolver is
|
|
220
|
-
* browser-oriented; arming it under Node would fire a relative-URL fetch
|
|
221
|
-
* and throw. (Agents pass a static `apiKey` with no resolver, so this
|
|
222
|
-
* method is never called for them.)
|
|
223
|
-
*
|
|
224
|
-
* Refreshing is automatic — a consumer never calls a refresh method. The call
|
|
225
|
-
* is idempotent: a second call replaces the first, and it is torn down when
|
|
226
|
-
* the client disconnects.
|
|
227
|
-
*
|
|
228
|
-
* `opts.proactiveInNode` arms the refresh timer on a windowless host as well.
|
|
229
|
-
* Set it for agent or system participants — long-lived server sockets whose
|
|
230
|
-
* `rk_`/`ek_` must renew before the server's keepalive check closes them.
|
|
231
|
-
* Node timers are `unref`ed, so a finishing script is never held alive by the
|
|
232
|
-
* pre-roll. The OS-wake listener stays browser-only regardless, since there
|
|
233
|
-
* is no `window` to listen on.
|
|
234
|
-
*/
|
|
235
|
-
start(getToken, opts) {
|
|
236
|
-
this.stop();
|
|
237
|
-
this.setRefresher(getToken);
|
|
238
|
-
// Re-mint through the same single-flight path the reactive probe uses
|
|
239
|
-
// (`refresh`) rather than calling `getToken()` directly. This gives two
|
|
240
|
-
// things: the mint stays single-flight, so an OS wake, an in-flight probe,
|
|
241
|
-
// and this proactive roll share one in-flight promise instead of thrashing;
|
|
242
|
-
// and the three-way outcome is honored, so a `null` result (the login is
|
|
243
|
-
// gone) actually drives expiry instead of being dropped, which would leave a
|
|
244
|
-
// zombie session that re-mints on every tab focus.
|
|
245
|
-
const refresh = async () => {
|
|
246
|
-
// Same outcome routing as every other trigger (see routeRefreshOutcome):
|
|
247
|
-
// refreshed → nudge, session_error → report, network_error → wait for
|
|
248
|
-
// the next tick / the FSM's own probe. Never sign out for a transient.
|
|
249
|
-
this.routeRefreshOutcome(await this.refresh());
|
|
250
|
-
};
|
|
251
|
-
const teardowns = [];
|
|
252
|
-
// The proactive pre-roll arms in the browser, or under Node when the host
|
|
253
|
-
// opts in (`proactiveInNode`, for agent or system participants). The
|
|
254
|
-
// default Node posture stays reactive-only, because a server render can
|
|
255
|
-
// construct user-kind clients whose resolver is browser-oriented (a
|
|
256
|
-
// relative-URL `fetch('/api/ablo-session')`); arming a timer there fires
|
|
257
|
-
// that resolver under Node, where fetch has no origin for a relative URL and
|
|
258
|
-
// throws "Failed to parse URL" on every tick. Long-lived server
|
|
259
|
-
// participants are the opposite case: their socket must outlive the
|
|
260
|
-
// credential's lifetime, so they get the timer. The reactive re-mint hook
|
|
261
|
-
// (`setRefresher` above) stays unconditional: it only fires on a real
|
|
262
|
-
// connection probe, which can't happen during a bare server-side module
|
|
263
|
-
// evaluation.
|
|
264
|
-
if (typeof window !== 'undefined' || opts?.proactiveInNode === true) {
|
|
265
|
-
// A missed tick (throttled in the background) is recovered by the next
|
|
266
|
-
// one, or by the reactive probe. This timer is the only proactive
|
|
267
|
-
// pre-roll — it keeps the key warm ahead of expiry even while the socket
|
|
268
|
-
// sits healthy and connected, a state the connection never probes. The
|
|
269
|
-
// delay comes from the minted credential's real `expiresAt` when the
|
|
270
|
-
// refresher reports one (about two-thirds of the remaining lifetime), with
|
|
271
|
-
// the 10-minute value as both ceiling and fallback, so a deployment that
|
|
272
|
-
// mints shorter-lived keys still pre-rolls in time rather than dropping to
|
|
273
|
-
// reactive-only recovery.
|
|
274
|
-
let timer = null;
|
|
275
|
-
const scheduleNext = () => {
|
|
276
|
-
if (timer !== null)
|
|
277
|
-
clearTimeout(timer); // idempotent re-arm
|
|
278
|
-
const delay = computePrerollDelayMs(this.credentialExpiresAtMs, Date.now());
|
|
279
|
-
timer = setTimeout(() => {
|
|
280
|
-
// `.finally` keeps the chain alive after a transient failure; a
|
|
281
|
-
// successful mint has already re-armed via `prerollReschedule` (the
|
|
282
|
-
// clearTimeout above makes the double-arm a no-op).
|
|
283
|
-
void refresh().finally(scheduleNext);
|
|
284
|
-
}, delay);
|
|
285
|
-
// Node: never keep a finishing process alive just for the pre-roll
|
|
286
|
-
// (browser timers have no unref — optional call is a no-op there).
|
|
287
|
-
timer.unref?.();
|
|
288
|
-
};
|
|
289
|
-
scheduleNext();
|
|
290
|
-
this.prerollReschedule = scheduleNext;
|
|
291
|
-
teardowns.push(() => {
|
|
292
|
-
if (timer !== null)
|
|
293
|
-
clearTimeout(timer);
|
|
294
|
-
timer = null;
|
|
295
|
-
this.prerollReschedule = null;
|
|
296
|
-
});
|
|
297
|
-
// OS-wake, on desktop only: the Electron shell bridges `powerMonitor`
|
|
298
|
-
// 'resume' to this DOM event. It is the one event trigger this lifecycle
|
|
299
|
-
// still owns, because `visibilitychange` does not fire on wake-from-sleep
|
|
300
|
-
// and the connection's own browser listeners don't cover wake. It is
|
|
301
|
-
// browser-gated separately from the timer, since a `proactiveInNode` host
|
|
302
|
-
// has no `window` to listen on and no OS sleep to wake from. Coming back
|
|
303
|
-
// online and regaining tab visibility are handled elsewhere — the
|
|
304
|
-
// connection already re-probes through this same credential path — so
|
|
305
|
-
// listening for them here too would only fire a second, redundant mint.
|
|
306
|
-
if (typeof window !== 'undefined') {
|
|
307
|
-
const onWake = () => void refresh();
|
|
308
|
-
window.addEventListener('ablo:wake', onWake);
|
|
309
|
-
teardowns.push(() => { window.removeEventListener('ablo:wake', onWake); });
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
this.credentialLifecycleTeardown = () => {
|
|
313
|
-
for (const t of teardowns)
|
|
314
|
-
t();
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
/** Tear down the proactive credential lifecycle (idempotent). */
|
|
318
|
-
stop() {
|
|
319
|
-
this.credentialLifecycleTeardown?.();
|
|
320
|
-
this.credentialLifecycleTeardown = null;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
7
|
+
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '../transaction/transport/credentialLifecycle.js';
|
|
@@ -10,18 +10,12 @@
|
|
|
10
10
|
* point lives on the store and drives this pipeline through {@link enqueueDelta}
|
|
11
11
|
* and {@link flushPendingDeltas}.
|
|
12
12
|
*/
|
|
13
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
13
14
|
import { ModelScope } from '../InstanceCache.js';
|
|
14
15
|
import type { Model } from '../Model.js';
|
|
15
|
-
import type { ModelData } from '../types/modelData.js';
|
|
16
16
|
import type { SyncDelta } from './SyncWebSocket.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
action: 'add' | 'update' | 'remove' | 'archive' | 'verify';
|
|
20
|
-
modelName: string;
|
|
21
|
-
modelId: string;
|
|
22
|
-
data?: ModelData | null;
|
|
23
|
-
transactionId?: string;
|
|
24
|
-
}
|
|
17
|
+
import type { ModelData } from '../transaction/types/modelData.js';
|
|
18
|
+
import { type AbloPlugin, type AppliedChange } from '../transaction/plugin.js';
|
|
25
19
|
/**
|
|
26
20
|
* What the pipeline needs back from the surrounding store: the shared mutable
|
|
27
21
|
* pipeline state (backed by the store's own fields through accessors), narrow
|
|
@@ -29,6 +23,14 @@ interface DeltaDbResult {
|
|
|
29
23
|
* subclass's overrides continue to take effect.
|
|
30
24
|
*/
|
|
31
25
|
export interface DeltaPipelineContext {
|
|
26
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
27
|
+
readonly runtime?: RuntimeContext;
|
|
28
|
+
/**
|
|
29
|
+
* The installed plugins, whose declared stage handlers this pipeline
|
|
30
|
+
* dispatches at each boundary. Empty (or absent) on directly-constructed
|
|
31
|
+
* stores, where the store's own apply is the whole pipeline.
|
|
32
|
+
*/
|
|
33
|
+
readonly stagePlugins?: readonly AbloPlugin[];
|
|
32
34
|
pendingDeltas: SyncDelta[];
|
|
33
35
|
batchTimer: ReturnType<typeof setTimeout> | null;
|
|
34
36
|
/** Queue for deltas arriving during an active bootstrap; null when none. */
|
|
@@ -52,11 +54,11 @@ export interface DeltaPipelineContext {
|
|
|
52
54
|
data: ModelData | null;
|
|
53
55
|
transactionId?: string;
|
|
54
56
|
}[]): Promise<{
|
|
55
|
-
results:
|
|
57
|
+
results: AppliedChange[];
|
|
56
58
|
persistedSyncId: number;
|
|
57
59
|
}>;
|
|
58
60
|
/** Applies persisted delta results to the in-memory pool, with the host's relation enrichment bound. */
|
|
59
|
-
applyDeltaBatchToPool(results:
|
|
61
|
+
applyDeltaBatchToPool(results: AppliedChange[]): void;
|
|
60
62
|
/** Acknowledges a sync id back to the server; a no-op when the socket is down. */
|
|
61
63
|
acknowledge(syncId: number): void;
|
|
62
64
|
readonly objectPool: {
|
|
@@ -110,4 +112,3 @@ export declare function scheduleDeltaFlush(ctx: DeltaPipelineContext): void;
|
|
|
110
112
|
* and advances the acknowledgement cursor once the store write has committed.
|
|
111
113
|
*/
|
|
112
114
|
export declare function flushPendingDeltas(ctx: DeltaPipelineContext): Promise<void>;
|
|
113
|
-
export {};
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* and {@link flushPendingDeltas}.
|
|
12
12
|
*/
|
|
13
13
|
import { runInAction } from 'mobx';
|
|
14
|
-
import {
|
|
14
|
+
import { globalRuntime } from '../context.js';
|
|
15
15
|
import { ModelScope } from '../InstanceCache.js';
|
|
16
|
+
import { runStage, pluginsForStage, } from '../transaction/plugin.js';
|
|
16
17
|
/**
|
|
17
18
|
* Handles a sync-group ('G' or 'S') delta whose handler rejected after the
|
|
18
19
|
* applied watermark had already advanced. That delta will never be redelivered,
|
|
@@ -22,7 +23,7 @@ import { ModelScope } from '../InstanceCache.js';
|
|
|
22
23
|
* It never throws, because it runs inside the pipeline's fire-and-forget path.
|
|
23
24
|
*/
|
|
24
25
|
export function handleGroupHandlerFailure(ctx, delta, error) {
|
|
25
|
-
|
|
26
|
+
(ctx.runtime ?? globalRuntime).logger.error('Your access changed but cached data could not be cleared — resetting local data.', {
|
|
26
27
|
syncId: delta.id,
|
|
27
28
|
error: error instanceof Error ? error.message : String(error),
|
|
28
29
|
});
|
|
@@ -166,6 +167,8 @@ export function enqueueDelta(ctx, delta, options = {}) {
|
|
|
166
167
|
if (delta.actionType === 'D') {
|
|
167
168
|
ctx.cascadeCancelTransactionsForDeletedParent(delta.modelName, delta.modelId);
|
|
168
169
|
}
|
|
170
|
+
// The delta is accepted and queued — the `receive` stage boundary.
|
|
171
|
+
runStage(ctx.stagePlugins ?? [], 'receive', { delta });
|
|
169
172
|
ctx.pendingDeltas.push(delta);
|
|
170
173
|
return true;
|
|
171
174
|
}
|
|
@@ -190,7 +193,9 @@ export function scheduleDeltaFlush(ctx) {
|
|
|
190
193
|
export async function flushPendingDeltas(ctx) {
|
|
191
194
|
if (ctx.pendingDeltas.length === 0)
|
|
192
195
|
return;
|
|
196
|
+
const stagePlugins = ctx.stagePlugins ?? [];
|
|
193
197
|
const deduplicatedDeltas = ctx.deduplicateDeltas(ctx.pendingDeltas);
|
|
198
|
+
runStage(stagePlugins, 'dedupe', { deltas: deduplicatedDeltas });
|
|
194
199
|
// Custom entities → apply straight to the pool, skipping the local store.
|
|
195
200
|
const customDeltas = deduplicatedDeltas.filter((d) => ctx.isCustomEntity(d.modelName));
|
|
196
201
|
if (customDeltas.length > 0) {
|
|
@@ -237,8 +242,18 @@ export async function flushPendingDeltas(ctx) {
|
|
|
237
242
|
transactionId: d.transactionId,
|
|
238
243
|
})));
|
|
239
244
|
const dbResults = batch.results;
|
|
240
|
-
|
|
241
|
-
|
|
245
|
+
runStage(stagePlugins, 'persist', { deltas: regularDeltas });
|
|
246
|
+
// Apply the batch results to the in-memory graph. When a plugin has
|
|
247
|
+
// declared the `apply` stage, its handlers ARE the apply — the
|
|
248
|
+
// materialiser attached where it said it would. The direct call is the
|
|
249
|
+
// bridge for stores constructed without plugins (subclasses, tests),
|
|
250
|
+
// whose own apply is the whole pipeline.
|
|
251
|
+
if (pluginsForStage(stagePlugins, 'apply').length > 0) {
|
|
252
|
+
runStage(stagePlugins, 'apply', { changes: dbResults });
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
ctx.applyDeltaBatchToPool(dbResults);
|
|
256
|
+
}
|
|
242
257
|
// Acknowledge and advance the sync cursor, gated on persistence.
|
|
243
258
|
//
|
|
244
259
|
// We must acknowledge `persistedSyncId` — the high-water mark of deltas whose
|
|
@@ -251,8 +266,10 @@ export async function flushPendingDeltas(ctx) {
|
|
|
251
266
|
if (persistedSyncId > ctx.lastAckedId) {
|
|
252
267
|
ctx.acknowledge(persistedSyncId);
|
|
253
268
|
ctx.advancePersisted(persistedSyncId);
|
|
269
|
+
runStage(stagePlugins, 'acknowledge', { syncId: persistedSyncId });
|
|
254
270
|
}
|
|
255
271
|
// Cache invalidation happens automatically via the 'models:changed' event.
|
|
272
|
+
runStage(stagePlugins, 'notify', { changes: dbResults });
|
|
256
273
|
ctx.pendingDeltas = [];
|
|
257
274
|
if (ctx.batchTimer) {
|
|
258
275
|
clearTimeout(ctx.batchTimer);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Every handler takes a {@link GroupChangeContext}, the narrow facade through
|
|
10
10
|
* which it reaches the client's local storage and connection lifecycle hooks.
|
|
11
11
|
*/
|
|
12
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
12
13
|
import type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload } from './SyncWebSocket.js';
|
|
13
14
|
/**
|
|
14
15
|
* The collaborators the group-change handlers depend on. It gathers the
|
|
@@ -55,6 +56,8 @@ export interface GroupChangeContext {
|
|
|
55
56
|
handleGroupAdded(payload: GroupAddedPayload, syncId: number): Promise<void>;
|
|
56
57
|
computeUpdatedSyncGroups(payload: SyncGroupChangePayload): string[];
|
|
57
58
|
forceFullRebootstrap(): void;
|
|
59
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
60
|
+
readonly runtime?: RuntimeContext;
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
60
63
|
* Handles a 'G' (group-change) delta. The server sends two shapes of this
|