@abloatai/ablo 0.34.1 → 0.35.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 +674 -5
- package/README.md +39 -22
- package/dist/BaseSyncedStore.d.ts +152 -44
- package/dist/BaseSyncedStore.js +300 -184
- package/dist/Database.d.ts +9 -24
- package/dist/Database.js +37 -22
- package/dist/InstanceCache.d.ts +25 -4
- package/dist/InstanceCache.js +48 -15
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +6 -6
- package/dist/Model.js +10 -10
- package/dist/ModelRegistry.d.ts +4 -4
- package/dist/ModelRegistry.js +3 -3
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +42 -32
- package/dist/SyncClient.js +166 -110
- 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/cli.cjs +3344 -1073
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +118 -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 +4 -4
- package/dist/client/createInternalComponents.js +9 -8
- package/dist/client/createModelProxy.d.ts +78 -373
- package/dist/client/createModelProxy.js +114 -86
- package/dist/client/humans.d.ts +48 -0
- package/dist/client/humans.js +52 -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 +48 -0
- package/dist/client/reactiveEngine.js +910 -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/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 +9 -9
- package/dist/context.js +10 -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/DatabaseManager.js +1 -1
- package/dist/core/QueryView.d.ts +1 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/StoreManager.d.ts +4 -23
- package/dist/core/StoreManager.js +5 -55
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/storeContract.d.ts +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +227 -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 +40 -34
- package/dist/index.js +26 -20
- 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 +2 -2
- package/dist/query/client.js +4 -4
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +6 -8
- package/dist/react/AbloProvider.js +5 -7
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js +1 -1
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +3 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +3 -3
- package/dist/react/useAblo.js +1 -1
- 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 +86 -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 +10 -10
- package/dist/schema/index.js +18 -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 +3 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +17 -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/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/footprint.d.ts +111 -0
- package/dist/source/footprint.js +0 -0
- 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/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +2 -10
- 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 +123 -6
- package/dist/sync/BootstrapFetcher.js +492 -66
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +2 -2
- package/dist/sync/OnDemandLoader.js +60 -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/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 +1 -1
- package/dist/sync/participants.d.ts +5 -4
- package/dist/sync/participants.js +29 -22
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +21 -32
- package/dist/sync/schemas.js +26 -17
- 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/testing/fixtures/bootstrap.d.ts +1 -1
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/httpResponses.d.ts +70 -0
- package/dist/testing/fixtures/httpResponses.js +90 -0
- package/dist/testing/fixtures/models.js +1 -1
- package/dist/testing/helpers/wait.js +1 -1
- package/dist/testing/mocks/MockMutationExecutor.d.ts +2 -2
- package/dist/testing/mocks/MockMutationExecutor.js +8 -14
- package/dist/testing/mocks/MockSyncContext.d.ts +11 -11
- package/dist/testing/mocks/MockSyncContext.js +10 -9
- package/dist/testing/mocks/MockSyncStore.js +1 -1
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- 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 +177 -0
- package/dist/transaction/auth/capability.js +199 -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 +49 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +112 -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 +44 -0
- package/dist/transaction/coordination/locator.d.ts +83 -0
- package/dist/transaction/coordination/locator.js +82 -0
- package/dist/transaction/coordination/schema.d.ts +1473 -0
- package/dist/{coordination → transaction/coordination}/schema.js +490 -55
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +103 -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} +11 -11
- package/dist/{errorCodes.js → transaction/errorCodes.js} +35 -12
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- 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 +192 -0
- package/dist/transaction/plugin.js +87 -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 +266 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +319 -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 +85 -0
- package/dist/transaction/resources/where.js +70 -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 +5 -5
- package/dist/{schema → transaction/schema}/field.js +5 -5
- 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 +57 -0
- package/dist/transaction/schema/openapi.js +340 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +14 -14
- 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 +12 -10
- package/dist/{schema → transaction/schema}/schema.js +4 -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 +4 -5
- 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} +10 -36
- 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/{client → transaction/transport}/httpClient.d.ts +59 -16
- package/dist/{client → transaction/transport}/httpClient.js +5 -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 +171 -85
- 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 +576 -0
- package/dist/transaction/transport/wsTransport.js +1017 -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 +63 -45
- 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 +351 -0
- package/dist/transaction/wire/accountResponses.js +255 -0
- package/dist/transaction/wire/auth.d.ts +49 -0
- package/dist/transaction/wire/auth.js +57 -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 +463 -0
- package/dist/transaction/wire/claims.js +229 -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 +177 -0
- package/dist/transaction/wire/feedEvent.js +39 -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 +552 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +50 -0
- package/dist/transaction/wire/index.js +74 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +79 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +110 -59
- 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} +16 -15
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +12 -12
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +11 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +7 -7
- 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} +3 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +4 -3
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- 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 +19 -12
- package/docs/api-keys.md +8 -4
- package/docs/api.md +22 -18
- package/docs/audit.md +2 -0
- package/docs/cli.md +31 -3
- package/docs/client-behavior.md +8 -6
- package/docs/concurrency-convention.md +30 -24
- package/docs/coordination.md +48 -38
- package/docs/data-sources.md +3 -1
- package/docs/debugging.md +5 -3
- 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 +45 -44
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +32 -29
- package/docs/guarantees.md +4 -2
- package/docs/how-it-works.md +9 -7
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +58 -54
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +17 -16
- package/docs/interaction-model.md +6 -4
- package/docs/mcp.md +41 -16
- package/docs/migration.md +63 -5
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +2 -0
- package/docs/quickstart.md +22 -5
- package/docs/react.md +12 -10
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +3 -1
- package/docs/webhooks.md +3 -1
- package/llms.txt +47 -17
- package/package.json +10 -8
- 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/client/durableWrites.d.ts +0 -21
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/transactions/durableWriteStore.js +0 -30
- 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/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/coordination}/claimHeartbeatLoop.d.ts +0 -0
- /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.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/{utils → transaction/utils}/duration.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/duration.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/SyncClient.js
CHANGED
|
@@ -4,24 +4,26 @@
|
|
|
4
4
|
* optimistic changes, holds them while the client is offline, sends them to
|
|
5
5
|
* the server when connectivity returns, and resolves conflicts when the
|
|
6
6
|
* server's version of a row disagrees with the local one. It sits between the
|
|
7
|
-
* reactive object pool and the {@link
|
|
7
|
+
* reactive object pool and the {@link MutationQueue} that delivers writes
|
|
8
8
|
* over the network.
|
|
9
9
|
*/
|
|
10
10
|
import { runInAction } from 'mobx';
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
import { InstanceCache, ModelScope } from './InstanceCache.js';
|
|
13
13
|
import { Model } from './Model.js';
|
|
14
|
+
import { snapshotJsonValue } from './transaction/utils/json.js';
|
|
14
15
|
// ModelRegistry instance accessed via this.objectPool.registry
|
|
15
|
-
import { LoadStrategy } from './types/index.js';
|
|
16
|
+
import { LoadStrategy } from './transaction/types/index.js';
|
|
16
17
|
import { getContext } from './context.js';
|
|
17
|
-
import { AbloAuthenticationError, AbloError, AbloValidationError } from './errors.js';
|
|
18
|
+
import { AbloAuthenticationError, AbloError, AbloValidationError } from './transaction/errors.js';
|
|
18
19
|
import { EventEmitter } from 'events';
|
|
19
20
|
import { NetworkMonitor } from './NetworkMonitor.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
21
|
+
import { MutationQueue } from './transactions/mutations/MutationQueue.js';
|
|
22
|
+
import { observeCommitLatency, } from './transactions/mutations/commitLatency.js';
|
|
23
|
+
import { legacyPendingMutationRecordSchema, PENDING_MUTATION_REPLAY_WINDOW_MS, pendingMutationRecordId, pendingMutationRecordSchema, persistedMutationSchema, } from './transactions/mutations/replayValidation.js';
|
|
24
|
+
import { UnconfirmedWrites, } from './transactions/mutations/UnconfirmedWrites.js';
|
|
25
|
+
import { LogPosition } from './transaction/logPosition.js';
|
|
26
|
+
import { DatabaseCommitOutboxStore, } from './transactions/mutations/commitOutboxStore.js';
|
|
25
27
|
/**
|
|
26
28
|
* Reports whether an incoming snapshot record is strictly newer than the
|
|
27
29
|
* model already in the pool. The comparison uses the server-stamped
|
|
@@ -73,7 +75,7 @@ export class SyncClient extends EventEmitter {
|
|
|
73
75
|
* @internal — test seam, stripped from the published declarations by
|
|
74
76
|
* `stripInternal`. Unit suites deliver queue lifecycle events directly.
|
|
75
77
|
*/
|
|
76
|
-
|
|
78
|
+
mutationQueue;
|
|
77
79
|
observers = new Set();
|
|
78
80
|
// Authentication context
|
|
79
81
|
userId = null;
|
|
@@ -104,19 +106,19 @@ export class SyncClient extends EventEmitter {
|
|
|
104
106
|
isDisposed = false;
|
|
105
107
|
/**
|
|
106
108
|
* The client's position in the global delta order, held as the single
|
|
107
|
-
* canonical {@link
|
|
109
|
+
* canonical {@link LogPosition} instance. The store advances `applied` and
|
|
108
110
|
* `persisted` as deltas land, the queue advances `acked` on commit
|
|
109
111
|
* responses, and snapshots and claims read `readFloor`.
|
|
110
112
|
*/
|
|
111
|
-
position = new
|
|
113
|
+
position = new LogPosition();
|
|
112
114
|
constructor(objectPool, database, commitOutbox = new DatabaseCommitOutboxStore(database), commitOutboxNamespace = 'default') {
|
|
113
115
|
super();
|
|
114
116
|
this.objectPool = objectPool;
|
|
115
117
|
this.database = database;
|
|
116
118
|
this.commitOutboxNamespace = commitOutboxNamespace;
|
|
117
119
|
this.networkMonitor = new NetworkMonitor();
|
|
118
|
-
// Initialize
|
|
119
|
-
this.
|
|
120
|
+
// Initialize MutationQueue with proper configuration
|
|
121
|
+
this.mutationQueue = new MutationQueue({
|
|
120
122
|
position: this.position,
|
|
121
123
|
maxBatchSize: 50, // Larger batches keep the batch count low for bulk operations
|
|
122
124
|
// A short delay keeps writes responsive; coalescing still groups them
|
|
@@ -128,8 +130,8 @@ export class SyncClient extends EventEmitter {
|
|
|
128
130
|
strategy: 'last-write-wins',
|
|
129
131
|
},
|
|
130
132
|
});
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
133
|
+
this.mutationQueue.setCommitOutbox(commitOutbox);
|
|
134
|
+
this.mutationQueue.on('commit:envelope_persisted', (event) => {
|
|
133
135
|
if (event.sourceMutationIds.length === 0)
|
|
134
136
|
return;
|
|
135
137
|
const consumed = new Set(event.sourceMutationIds);
|
|
@@ -140,7 +142,7 @@ export class SyncClient extends EventEmitter {
|
|
|
140
142
|
this.scheduleSync();
|
|
141
143
|
}
|
|
142
144
|
});
|
|
143
|
-
this.
|
|
145
|
+
this.mutationQueue.on('transaction:completed', (transaction) => {
|
|
144
146
|
const completed = new Set(transaction.sourceMutationIds ?? []);
|
|
145
147
|
if (completed.size > 0) {
|
|
146
148
|
this.pendingMutations = this.pendingMutations.filter((mutation) => !completed.has(mutation.mutationId));
|
|
@@ -155,7 +157,7 @@ export class SyncClient extends EventEmitter {
|
|
|
155
157
|
this.scheduleSync();
|
|
156
158
|
}
|
|
157
159
|
});
|
|
158
|
-
this.
|
|
160
|
+
this.mutationQueue.on('transaction:failed', ({ transaction }) => {
|
|
159
161
|
const failed = transaction.sourceMutationIds ?? [];
|
|
160
162
|
if (failed.length === 0)
|
|
161
163
|
return;
|
|
@@ -175,8 +177,8 @@ export class SyncClient extends EventEmitter {
|
|
|
175
177
|
this.scheduleSync();
|
|
176
178
|
}
|
|
177
179
|
});
|
|
178
|
-
// Provide connection state to
|
|
179
|
-
this.
|
|
180
|
+
// Provide connection state to MutationQueue - prevents rollbacks during disconnection
|
|
181
|
+
this.mutationQueue.setConnectionChecker(() => this.connectionState === 'connected');
|
|
180
182
|
// Restore object-pool state when a transaction is rolled back. If the
|
|
181
183
|
// server rejects a write or it times out, the model's previous state is
|
|
182
184
|
// put back. Because writes are no longer applied to IndexedDB
|
|
@@ -201,7 +203,7 @@ export class SyncClient extends EventEmitter {
|
|
|
201
203
|
// losing a failed reconnect flush silently.
|
|
202
204
|
this.networkMonitor.on('online', () => {
|
|
203
205
|
void this.handleReconnection().catch((error) => {
|
|
204
|
-
getContext().observability.
|
|
206
|
+
getContext().observability.captureMutationFailure({
|
|
205
207
|
context: 'network-online-reconnection',
|
|
206
208
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
207
209
|
});
|
|
@@ -209,7 +211,7 @@ export class SyncClient extends EventEmitter {
|
|
|
209
211
|
});
|
|
210
212
|
this.networkMonitor.on('offline', () => {
|
|
211
213
|
void this.handleDisconnection().catch((error) => {
|
|
212
|
-
getContext().observability.
|
|
214
|
+
getContext().observability.captureMutationFailure({
|
|
213
215
|
context: 'network-offline-handler',
|
|
214
216
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
215
217
|
});
|
|
@@ -233,12 +235,12 @@ export class SyncClient extends EventEmitter {
|
|
|
233
235
|
* discard the optimistic state silently.
|
|
234
236
|
*
|
|
235
237
|
* Treating both paths the same caused the deletion-flicker bug: every
|
|
236
|
-
* cancelled update on a multi-
|
|
238
|
+
* cancelled update on a multi-child record fired a per-model observer
|
|
237
239
|
* event and a `[SyncClient.rollback]` warn, producing N renders and N
|
|
238
240
|
* spam log lines for one user-initiated delete.
|
|
239
241
|
*/
|
|
240
242
|
setupTransactionRollbackHandling() {
|
|
241
|
-
this.
|
|
243
|
+
this.mutationQueue.on('optimistic:rollback', (event) => {
|
|
242
244
|
const { model, previousState, transaction, reason, error } = event;
|
|
243
245
|
// Local cleanup path — discard quietly. The optimistic state was
|
|
244
246
|
// applied to a model that's already disposed by the cascading
|
|
@@ -253,7 +255,7 @@ export class SyncClient extends EventEmitter {
|
|
|
253
255
|
// `AbloServerError` with `httpStatus: 500`). Falling back to
|
|
254
256
|
// generic message lets us still see unstructured errors.
|
|
255
257
|
// Mechanic-level breadcrumb only. The authoritative, user-facing
|
|
256
|
-
// reason is logged once at `warn` by `
|
|
258
|
+
// reason is logged once at `warn` by `MutationQueue.handleFailure`
|
|
257
259
|
// (`Permanent error - rolling back`). Logging the same typed cause
|
|
258
260
|
// again here at `warn` is what produced three identical dumps per
|
|
259
261
|
// rejected write — keep it at `debug` so the rollback mechanics are
|
|
@@ -303,7 +305,7 @@ export class SyncClient extends EventEmitter {
|
|
|
303
305
|
if (model.disposed) {
|
|
304
306
|
// Follow-on of an already-logged permanent error, not its own
|
|
305
307
|
// problem: the tx that failed has already surfaced the cause in
|
|
306
|
-
//
|
|
308
|
+
// MutationQueue. Restoring a disposed model is a no-op by
|
|
307
309
|
// design (can't revive the private isDisposed flag), so keep this
|
|
308
310
|
// at `debug` instead of emitting a second `warn` that reads as a
|
|
309
311
|
// distinct failure in the console.
|
|
@@ -334,7 +336,7 @@ export class SyncClient extends EventEmitter {
|
|
|
334
336
|
});
|
|
335
337
|
}
|
|
336
338
|
catch (error) {
|
|
337
|
-
getContext().observability.
|
|
339
|
+
getContext().observability.captureMutationFailure({
|
|
338
340
|
context: 'rollback-failed',
|
|
339
341
|
transactionId: transaction.id,
|
|
340
342
|
modelName: transaction.modelName,
|
|
@@ -345,13 +347,13 @@ export class SyncClient extends EventEmitter {
|
|
|
345
347
|
});
|
|
346
348
|
}
|
|
347
349
|
/**
|
|
348
|
-
* Forward reconciliation requests from the {@link
|
|
350
|
+
* Forward reconciliation requests from the {@link MutationQueue} to the
|
|
349
351
|
* sync layer. When delta confirmation times out, the queue emits
|
|
350
352
|
* `reconciliation:needed` instead of rolling back, so optimistic state the
|
|
351
353
|
* server may already have committed is never destroyed.
|
|
352
354
|
*/
|
|
353
355
|
setupReconciliationForwarding() {
|
|
354
|
-
this.
|
|
356
|
+
this.mutationQueue.on('reconciliation:needed', (event) => {
|
|
355
357
|
getContext().observability.captureReconciliation({
|
|
356
358
|
reason: event.reason,
|
|
357
359
|
model: event.model,
|
|
@@ -372,12 +374,12 @@ export class SyncClient extends EventEmitter {
|
|
|
372
374
|
* deliver the missing deltas and confirm the transaction.
|
|
373
375
|
*/
|
|
374
376
|
setupAwaitingTransactionPersistence() {
|
|
375
|
-
this.
|
|
377
|
+
this.mutationQueue.on('transaction:persist_awaiting', (event) => {
|
|
376
378
|
// void is safe: the handler's body is fully try/catch'd.
|
|
377
379
|
void this.persistAwaitingTransaction(event);
|
|
378
380
|
});
|
|
379
381
|
// Clean up persisted awaiting transactions when they're finally confirmed
|
|
380
|
-
this.
|
|
382
|
+
this.mutationQueue.on('transaction:completed', (tx) => {
|
|
381
383
|
// void is safe: the handler's body is fully try/catch'd.
|
|
382
384
|
void this.removeAwaitingTransaction(tx.id);
|
|
383
385
|
});
|
|
@@ -389,11 +391,11 @@ export class SyncClient extends EventEmitter {
|
|
|
389
391
|
// server processes it, we drain on rollback too so a stale id
|
|
390
392
|
// doesn't permanently silence a foreign delta sharing the same id
|
|
391
393
|
// (vanishingly unlikely for UUIDs, but cheap insurance).
|
|
392
|
-
this.
|
|
394
|
+
this.mutationQueue.on('transaction:created', (tx) => {
|
|
393
395
|
if (!tx.localOnly)
|
|
394
396
|
this.echoTracker.markPending(tx.id);
|
|
395
397
|
});
|
|
396
|
-
this.
|
|
398
|
+
this.mutationQueue.on('optimistic:rollback', (event) => {
|
|
397
399
|
this.echoTracker.drainOnRollback(event.transaction.id);
|
|
398
400
|
});
|
|
399
401
|
}
|
|
@@ -420,7 +422,7 @@ export class SyncClient extends EventEmitter {
|
|
|
420
422
|
});
|
|
421
423
|
}
|
|
422
424
|
catch (error) {
|
|
423
|
-
getContext().observability.
|
|
425
|
+
getContext().observability.captureMutationFailure({
|
|
424
426
|
context: 'persist-awaiting-transaction',
|
|
425
427
|
modelName: event.model,
|
|
426
428
|
modelId: event.modelId,
|
|
@@ -446,7 +448,7 @@ export class SyncClient extends EventEmitter {
|
|
|
446
448
|
this.userId = userId;
|
|
447
449
|
this.organizationId = organizationId;
|
|
448
450
|
getContext().observability.setContext(userId, organizationId);
|
|
449
|
-
this.
|
|
451
|
+
this.mutationQueue.setCommitOutboxScope({
|
|
450
452
|
organizationId,
|
|
451
453
|
participantId: userId,
|
|
452
454
|
namespace: this.commitOutboxNamespace,
|
|
@@ -461,7 +463,7 @@ export class SyncClient extends EventEmitter {
|
|
|
461
463
|
// Restore exact, already-sealed requests first. The returned source ids
|
|
462
464
|
// suppress any legacy queue entry left behind by an older non-atomic
|
|
463
465
|
// handoff.
|
|
464
|
-
const sealedMutationIds = await this.
|
|
466
|
+
const sealedMutationIds = await this.mutationQueue.restoreDurableCommits();
|
|
465
467
|
await this.restoreMutationQueue(sealedMutationIds);
|
|
466
468
|
// Read the initial network status from the injected OnlineStatusProvider.
|
|
467
469
|
// In the browser this reflects the host's connectivity signal; in Node it
|
|
@@ -501,7 +503,7 @@ export class SyncClient extends EventEmitter {
|
|
|
501
503
|
* 2. **Required-field gate** — if the row is missing any field listed
|
|
502
504
|
* in the model's `requiredFields`, return `null` so the caller
|
|
503
505
|
* skips this record. Used for FK columns whose absence renders the
|
|
504
|
-
* row unrecoverable (e.g. a
|
|
506
|
+
* row unrecoverable (e.g. a Block with no sectionId).
|
|
505
507
|
*
|
|
506
508
|
* The engine itself is product-neutral: model identity (which fields
|
|
507
509
|
* to back-fill, which absences are fatal) lives entirely in the
|
|
@@ -559,7 +561,7 @@ export class SyncClient extends EventEmitter {
|
|
|
559
561
|
// Get model types that should be hydrated on startup (skip lazy per LSE)
|
|
560
562
|
const modelTypes = this.objectPool.registry.getRegisteredModelNames().filter((name) => {
|
|
561
563
|
const meta = this.objectPool.registry.getMetadata(name);
|
|
562
|
-
return
|
|
564
|
+
return meta?.loadStrategy === LoadStrategy.instant;
|
|
563
565
|
});
|
|
564
566
|
const totalStart = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
565
567
|
// Phase 1: Fetch all data from IndexedDB and create model instances (async I/O).
|
|
@@ -609,7 +611,7 @@ export class SyncClient extends EventEmitter {
|
|
|
609
611
|
getContext().logger.info(`[SyncClient.hydrate] Successfully healed ${recordsToHeal.length} ${modelType} records`);
|
|
610
612
|
}
|
|
611
613
|
catch (err) {
|
|
612
|
-
getContext().observability.
|
|
614
|
+
getContext().observability.captureMutationFailure({
|
|
613
615
|
context: 'persist-healed-records',
|
|
614
616
|
modelName: modelType,
|
|
615
617
|
error: err instanceof Error ? err : new Error(String(err)),
|
|
@@ -681,7 +683,7 @@ export class SyncClient extends EventEmitter {
|
|
|
681
683
|
// Model types to rehydrate (same filter as hydrateFromDatabase)
|
|
682
684
|
const modelTypes = this.objectPool.registry.getRegisteredModelNames().filter((name) => {
|
|
683
685
|
const meta = this.objectPool.registry.getMetadata(name);
|
|
684
|
-
return
|
|
686
|
+
return meta?.loadStrategy === LoadStrategy.instant;
|
|
685
687
|
});
|
|
686
688
|
// ── Phase 1: Read from IndexedDB & create model instances (async I/O) ──
|
|
687
689
|
const allModels = [];
|
|
@@ -819,8 +821,9 @@ export class SyncClient extends EventEmitter {
|
|
|
819
821
|
// object can reach here with `hasChanges === undefined`. `undefined === false`
|
|
820
822
|
// is false → we fall through to the normal path rather than risk dropping a
|
|
821
823
|
// real write. Only a genuine Model with an empty dirty-set is skipped.
|
|
822
|
-
if (type === 'update' && model.hasChanges === false)
|
|
824
|
+
if (type === 'update' && model.hasChanges === false) {
|
|
823
825
|
return;
|
|
826
|
+
}
|
|
824
827
|
// Capture changes before the pool action runs. Pool operations —
|
|
825
828
|
// upsert in particular — can clear the model's local changes, so
|
|
826
829
|
// capturing first ensures they are never lost.
|
|
@@ -834,7 +837,7 @@ export class SyncClient extends EventEmitter {
|
|
|
834
837
|
modelId: model.id,
|
|
835
838
|
});
|
|
836
839
|
// QueryProcessor uses `models:changed` to invalidate caches. Coalesce
|
|
837
|
-
// to one event per microtask: a paste of 100
|
|
840
|
+
// to one event per microtask: a paste of 100 rows should re-run
|
|
838
841
|
// affected queries ONCE, not 100×.
|
|
839
842
|
this.markModelChanged(model.getModelName());
|
|
840
843
|
}
|
|
@@ -876,7 +879,7 @@ export class SyncClient extends EventEmitter {
|
|
|
876
879
|
/**
|
|
877
880
|
* Update existing model with pre-computed changes.
|
|
878
881
|
* Used by saveManyOptimized when incoming models have empty change-tracking
|
|
879
|
-
* (e.g. freshly constructed
|
|
882
|
+
* (e.g. freshly constructed cell models from a bulk document decomposition).
|
|
880
883
|
*/
|
|
881
884
|
updateWithChanges(model, changes) {
|
|
882
885
|
getContext().logger.debug(`SyncClient.updateWithChanges`, {
|
|
@@ -903,7 +906,7 @@ export class SyncClient extends EventEmitter {
|
|
|
903
906
|
modelId: model.id,
|
|
904
907
|
});
|
|
905
908
|
}
|
|
906
|
-
/** Expose the GraphQL client for atomic mutations (e.g.,
|
|
909
|
+
/** Expose the GraphQL client for atomic mutations (e.g., createSectionWithBlocks).
|
|
907
910
|
* Used by SyncedStore for operations that bypass the transaction queue
|
|
908
911
|
* but still need optimistic pool updates at the sync layer. */
|
|
909
912
|
get gql() {
|
|
@@ -917,7 +920,7 @@ export class SyncClient extends EventEmitter {
|
|
|
917
920
|
}
|
|
918
921
|
/**
|
|
919
922
|
* Clear all pending mutations for a specific model
|
|
920
|
-
* Called before deletion to prevent "
|
|
923
|
+
* Called before deletion to prevent "record not found" errors on the server
|
|
921
924
|
*/
|
|
922
925
|
clearPendingMutationsForModel(modelId) {
|
|
923
926
|
const beforeCount = this.pendingMutations.length;
|
|
@@ -931,7 +934,7 @@ export class SyncClient extends EventEmitter {
|
|
|
931
934
|
remainingCount: afterCount,
|
|
932
935
|
});
|
|
933
936
|
for (const mutation of removed) {
|
|
934
|
-
// Once staged,
|
|
937
|
+
// Once staged, MutationQueue owns cancellation and transfers this
|
|
935
938
|
// source id into the superseding delete envelope. Deleting the journal
|
|
936
939
|
// row here would make that valid atomic promotion look like a
|
|
937
940
|
// multi-tab loser. Truly unstaged work can be canceled locally.
|
|
@@ -946,7 +949,7 @@ export class SyncClient extends EventEmitter {
|
|
|
946
949
|
}
|
|
947
950
|
/**
|
|
948
951
|
* Upload a file and create its attachment record. The upload runs through
|
|
949
|
-
* the {@link
|
|
952
|
+
* the {@link MutationQueue}, and a model is built from the server's
|
|
950
953
|
* response and added to the pool.
|
|
951
954
|
*/
|
|
952
955
|
async uploadFile(file, options) {
|
|
@@ -956,8 +959,8 @@ export class SyncClient extends EventEmitter {
|
|
|
956
959
|
});
|
|
957
960
|
}
|
|
958
961
|
try {
|
|
959
|
-
// Use
|
|
960
|
-
const result = await this.
|
|
962
|
+
// Use MutationQueue to handle the upload mutation
|
|
963
|
+
const result = await this.mutationQueue.uploadAttachment(file, {
|
|
961
964
|
id: options.id,
|
|
962
965
|
attachableType: options.attachableType,
|
|
963
966
|
attachableId: options.attachableId,
|
|
@@ -985,7 +988,7 @@ export class SyncClient extends EventEmitter {
|
|
|
985
988
|
return null;
|
|
986
989
|
}
|
|
987
990
|
catch (error) {
|
|
988
|
-
getContext().observability.
|
|
991
|
+
getContext().observability.captureMutationFailure({
|
|
989
992
|
context: 'file-upload',
|
|
990
993
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
991
994
|
});
|
|
@@ -1015,7 +1018,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1015
1018
|
attachableId: options.attachableId,
|
|
1016
1019
|
metadata: options.metadata,
|
|
1017
1020
|
}));
|
|
1018
|
-
const results = await this.
|
|
1021
|
+
const results = await this.mutationQueue.batchUploadAttachments(files, items, {
|
|
1019
1022
|
userId: this.userId,
|
|
1020
1023
|
organizationId: this.organizationId,
|
|
1021
1024
|
});
|
|
@@ -1044,7 +1047,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1044
1047
|
* IndexedDB persistence and the server push are deferred to a microtask, so
|
|
1045
1048
|
* many pushes within the same tick collapse into a single serialization and
|
|
1046
1049
|
* a single process call. Without the deferral, queueing a hundred mutations
|
|
1047
|
-
* at once — a large paste, a document import, bulk
|
|
1050
|
+
* at once — a large paste, a document import, bulk row creation — would
|
|
1048
1051
|
* reserialize the whole growing queue a hundred times, an O(N²) cost in
|
|
1049
1052
|
* `model.toJSON()`.
|
|
1050
1053
|
*
|
|
@@ -1113,20 +1116,59 @@ export class SyncClient extends EventEmitter {
|
|
|
1113
1116
|
return;
|
|
1114
1117
|
}
|
|
1115
1118
|
try {
|
|
1116
|
-
|
|
1119
|
+
// Build each durable row independently. A malformed mutation must not
|
|
1120
|
+
// prevent unrelated writes from the same event-loop burst from being
|
|
1121
|
+
// journaled and dispatched.
|
|
1122
|
+
const entries = [];
|
|
1123
|
+
for (const mutation of batch) {
|
|
1124
|
+
try {
|
|
1125
|
+
entries.push({
|
|
1126
|
+
mutation,
|
|
1127
|
+
record: this.pendingMutationRecord(mutation),
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
catch (error) {
|
|
1131
|
+
mutation.rejectJournal?.(error);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (entries.length === 0)
|
|
1135
|
+
return;
|
|
1117
1136
|
const database = this.database;
|
|
1137
|
+
const saveOne = async (record) => {
|
|
1138
|
+
// Some adapters (including IndexedDB's put path) can throw before
|
|
1139
|
+
// returning a Promise. The async wrapper turns that into an individual
|
|
1140
|
+
// rejected outcome instead of aborting construction of the whole list.
|
|
1141
|
+
await database.saveTransaction(record);
|
|
1142
|
+
};
|
|
1143
|
+
let outcomes;
|
|
1118
1144
|
if (database.saveTransactions) {
|
|
1119
|
-
|
|
1145
|
+
try {
|
|
1146
|
+
await database.saveTransactions(entries.map(({ record }) => record));
|
|
1147
|
+
outcomes = entries.map(() => ({
|
|
1148
|
+
status: 'fulfilled',
|
|
1149
|
+
value: undefined,
|
|
1150
|
+
}));
|
|
1151
|
+
}
|
|
1152
|
+
catch {
|
|
1153
|
+
// IndexedDB aborts a multi-row transaction when any value cannot be
|
|
1154
|
+
// cloned. Retry one row per transaction to identify the offender and
|
|
1155
|
+
// preserve every valid sibling. Re-putting a row is idempotent by id.
|
|
1156
|
+
outcomes = await Promise.allSettled(entries.map(({ record }) => saveOne(record)));
|
|
1157
|
+
}
|
|
1120
1158
|
}
|
|
1121
1159
|
else {
|
|
1122
|
-
await Promise.
|
|
1160
|
+
outcomes = await Promise.allSettled(entries.map(({ record }) => saveOne(record)));
|
|
1161
|
+
}
|
|
1162
|
+
for (const [index, entry] of entries.entries()) {
|
|
1163
|
+
const outcome = outcomes[index];
|
|
1164
|
+
if (outcome?.status === 'fulfilled') {
|
|
1165
|
+
entry.mutation.resolveJournal?.();
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
entry.mutation.rejectJournal?.(outcome?.reason ??
|
|
1169
|
+
new Error('Mutation journal persistence failed'));
|
|
1170
|
+
}
|
|
1123
1171
|
}
|
|
1124
|
-
for (const mutation of batch)
|
|
1125
|
-
mutation.resolveJournal?.();
|
|
1126
|
-
}
|
|
1127
|
-
catch (error) {
|
|
1128
|
-
for (const mutation of batch)
|
|
1129
|
-
mutation.rejectJournal?.(error);
|
|
1130
1172
|
}
|
|
1131
1173
|
finally {
|
|
1132
1174
|
for (const mutation of batch) {
|
|
@@ -1156,7 +1198,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1156
1198
|
if (!this.userId || !this.organizationId) {
|
|
1157
1199
|
throw new AbloValidationError('Cannot persist a mutation before participant scope is initialized', { code: 'write_options_invalid' });
|
|
1158
1200
|
}
|
|
1159
|
-
|
|
1201
|
+
const candidate = {
|
|
1160
1202
|
id: pendingMutationRecordId(mutation.mutationId),
|
|
1161
1203
|
type: 'pending_mutation',
|
|
1162
1204
|
storageVersion: 2,
|
|
@@ -1179,7 +1221,10 @@ export class SyncClient extends EventEmitter {
|
|
|
1179
1221
|
namespace: this.commitOutboxNamespace,
|
|
1180
1222
|
},
|
|
1181
1223
|
timestamp: mutation.timestamp.getTime(),
|
|
1182
|
-
}
|
|
1224
|
+
};
|
|
1225
|
+
// One framework-neutral boundary contract owns both Proxy unwrapping and
|
|
1226
|
+
// rejection of values JSON would silently corrupt before IndexedDB sees it.
|
|
1227
|
+
return pendingMutationRecordSchema.parse(snapshotJsonValue(candidate, '$.pendingMutation'));
|
|
1183
1228
|
}
|
|
1184
1229
|
async persistPendingMutation(mutation) {
|
|
1185
1230
|
await this.database.saveTransaction(this.pendingMutationRecord(mutation));
|
|
@@ -1292,7 +1337,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1292
1337
|
getContext().logger.debug('[SyncClient] Failed to restore offline mutation queue', {
|
|
1293
1338
|
error: error instanceof Error ? error.message : String(error),
|
|
1294
1339
|
});
|
|
1295
|
-
getContext().observability.
|
|
1340
|
+
getContext().observability.captureMutationFailure({
|
|
1296
1341
|
context: 'restore-mutation-queue',
|
|
1297
1342
|
error: error instanceof Error ? error : String(error),
|
|
1298
1343
|
});
|
|
@@ -1303,7 +1348,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1303
1348
|
*
|
|
1304
1349
|
* Best Practice: Only sync models that still exist locally (local-first principle)
|
|
1305
1350
|
* - If a model was deleted locally → skip any pending updates/creates for it
|
|
1306
|
-
* - This prevents "
|
|
1351
|
+
* - This prevents "record not found" errors from fast copy-paste-delete workflows
|
|
1307
1352
|
*/
|
|
1308
1353
|
async processPendingMutations() {
|
|
1309
1354
|
if (this.pendingMutations.length === 0)
|
|
@@ -1374,7 +1419,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1374
1419
|
this.stagedMutationIds.delete(mutation.mutationId);
|
|
1375
1420
|
this.pendingMutations = this.pendingMutations.filter((pending) => pending.mutationId !== mutation.mutationId);
|
|
1376
1421
|
mutation.rejectStaged?.(outcome.reason);
|
|
1377
|
-
getContext().observability.
|
|
1422
|
+
getContext().observability.captureMutationFailure({
|
|
1378
1423
|
context: 'persist-pending-mutation',
|
|
1379
1424
|
error: outcome.reason instanceof Error
|
|
1380
1425
|
? outcome.reason
|
|
@@ -1384,12 +1429,12 @@ export class SyncClient extends EventEmitter {
|
|
|
1384
1429
|
// Stage every mutation synchronously within the same event-loop tick;
|
|
1385
1430
|
// the transaction queue's microtask batches and sends them together.
|
|
1386
1431
|
for (const mutation of journaledMutations) {
|
|
1387
|
-
// Stage synchronously -
|
|
1432
|
+
// Stage synchronously - MutationQueue handles batching, retry, and errors
|
|
1388
1433
|
this.stageMutation(mutation);
|
|
1389
1434
|
}
|
|
1390
1435
|
}
|
|
1391
1436
|
/**
|
|
1392
|
-
* Stage mutation to
|
|
1437
|
+
* Stage mutation to MutationQueue - mutations in same tick are batched via microtask
|
|
1393
1438
|
*
|
|
1394
1439
|
* @param mutation.capturedChanges - Pre-captured changes to use instead of re-reading from model
|
|
1395
1440
|
*/
|
|
@@ -1406,7 +1451,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1406
1451
|
// apply, store add). That means the write never entered the queue, so
|
|
1407
1452
|
// capture it instead of dropping it silently.
|
|
1408
1453
|
const captureStagingFailure = (error) => {
|
|
1409
|
-
getContext().observability.
|
|
1454
|
+
getContext().observability.captureMutationFailure({
|
|
1410
1455
|
context: `stage-mutation-${mutation.type}`,
|
|
1411
1456
|
modelName: mutation.model.getModelName(),
|
|
1412
1457
|
modelId: mutation.model.id,
|
|
@@ -1415,8 +1460,8 @@ export class SyncClient extends EventEmitter {
|
|
|
1415
1460
|
this.stagedMutationIds.delete(mutation.mutationId);
|
|
1416
1461
|
};
|
|
1417
1462
|
const staging = mutation.type === 'update'
|
|
1418
|
-
? this.
|
|
1419
|
-
: this.
|
|
1463
|
+
? this.mutationQueue.update(mutation.model, ctx, mutation.capturedChanges, mutation.writeOptions, mutation.mutationId)
|
|
1464
|
+
: this.mutationQueue[mutation.type].bind(this.mutationQueue)(mutation.model, ctx, mutation.writeOptions, mutation.mutationId);
|
|
1420
1465
|
staging
|
|
1421
1466
|
.then(() => mutation.resolveStaged?.())
|
|
1422
1467
|
.catch((error) => {
|
|
@@ -1535,7 +1580,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1535
1580
|
try {
|
|
1536
1581
|
// Prefer a single batch flush for pending mutations (fast path)
|
|
1537
1582
|
try {
|
|
1538
|
-
await this.
|
|
1583
|
+
await this.mutationQueue.flushOfflineQueue();
|
|
1539
1584
|
}
|
|
1540
1585
|
catch { }
|
|
1541
1586
|
// Process all queued mutations
|
|
@@ -1544,7 +1589,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1544
1589
|
this.emit('sync:reconnected');
|
|
1545
1590
|
}
|
|
1546
1591
|
catch (error) {
|
|
1547
|
-
getContext().observability.
|
|
1592
|
+
getContext().observability.captureMutationFailure({
|
|
1548
1593
|
context: 'reconnection-sync',
|
|
1549
1594
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
1550
1595
|
});
|
|
@@ -1581,11 +1626,11 @@ export class SyncClient extends EventEmitter {
|
|
|
1581
1626
|
getContext().observability.breadcrumb(`Connection: ${oldState} → ${state}`, 'sync.websocket');
|
|
1582
1627
|
if (state === 'connected') {
|
|
1583
1628
|
this.emit('connection:established');
|
|
1584
|
-
this.
|
|
1629
|
+
this.mutationQueue.setConnectionState('connected');
|
|
1585
1630
|
}
|
|
1586
1631
|
else if (state === 'disconnected') {
|
|
1587
1632
|
this.emit('connection:disconnected');
|
|
1588
|
-
this.
|
|
1633
|
+
this.mutationQueue.setConnectionState('disconnected');
|
|
1589
1634
|
}
|
|
1590
1635
|
}
|
|
1591
1636
|
}
|
|
@@ -1643,8 +1688,8 @@ export class SyncClient extends EventEmitter {
|
|
|
1643
1688
|
// Browser online state may have marked the client connected before the
|
|
1644
1689
|
// WebSocket itself was ready. Always kick both durable lanes on the real
|
|
1645
1690
|
// socket event, even when the high-level state did not change.
|
|
1646
|
-
void this.
|
|
1647
|
-
getContext().observability.
|
|
1691
|
+
void this.mutationQueue.flushOfflineQueue().catch((error) => {
|
|
1692
|
+
getContext().observability.captureMutationFailure({
|
|
1648
1693
|
context: 'restore-commit-outbox',
|
|
1649
1694
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
1650
1695
|
});
|
|
@@ -1663,7 +1708,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1663
1708
|
this.removeAllListeners();
|
|
1664
1709
|
}
|
|
1665
1710
|
/**
|
|
1666
|
-
* Notify the {@link
|
|
1711
|
+
* Notify the {@link MutationQueue} of an incoming delta so it can confirm
|
|
1667
1712
|
* hosted writes by sync-id threshold and queued forwards by their echoed
|
|
1668
1713
|
* source-batch correlation id.
|
|
1669
1714
|
* @param syncId - The sync id of the received delta.
|
|
@@ -1672,7 +1717,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1672
1717
|
*/
|
|
1673
1718
|
onDeltaReceived(syncId, transactionId, correlationId) {
|
|
1674
1719
|
try {
|
|
1675
|
-
this.
|
|
1720
|
+
this.mutationQueue.onDeltaReceived(syncId, transactionId, correlationId);
|
|
1676
1721
|
}
|
|
1677
1722
|
catch (e) {
|
|
1678
1723
|
getContext().observability.breadcrumb('Failed to notify delta received', 'sync.transaction', 'warning', {
|
|
@@ -1687,20 +1732,20 @@ export class SyncClient extends EventEmitter {
|
|
|
1687
1732
|
* deletion. The store calls this when a delete delta arrives for a parent,
|
|
1688
1733
|
* cancelling any queued writes on children that reference it.
|
|
1689
1734
|
*
|
|
1690
|
-
* @param childModelName - The child model type (for example, `
|
|
1691
|
-
* @param foreignKey - The foreign-key property name (for example, `
|
|
1735
|
+
* @param childModelName - The child model type (for example, `Block`).
|
|
1736
|
+
* @param foreignKey - The foreign-key property name (for example, `sectionId`).
|
|
1692
1737
|
* @param parentId - The id of the deleted parent.
|
|
1693
1738
|
* @returns The number of transactions cancelled.
|
|
1694
1739
|
*/
|
|
1695
1740
|
cancelTransactionsByForeignKey(childModelName, foreignKey, parentId) {
|
|
1696
|
-
return this.
|
|
1741
|
+
return this.mutationQueue.cancelTransactionsByForeignKey(childModelName, foreignKey, parentId);
|
|
1697
1742
|
}
|
|
1698
1743
|
/**
|
|
1699
1744
|
* Wait for a transaction to be confirmed by its delta echo. Delegates to the
|
|
1700
|
-
* {@link
|
|
1745
|
+
* {@link MutationQueue}, which handles the confirmation timeout.
|
|
1701
1746
|
*/
|
|
1702
1747
|
waitForDeltaConfirmation(transactionId) {
|
|
1703
|
-
return this.
|
|
1748
|
+
return this.mutationQueue.waitForConfirmation(transactionId);
|
|
1704
1749
|
}
|
|
1705
1750
|
/**
|
|
1706
1751
|
* Force sync now - process pending mutations
|
|
@@ -1728,11 +1773,11 @@ export class SyncClient extends EventEmitter {
|
|
|
1728
1773
|
};
|
|
1729
1774
|
}
|
|
1730
1775
|
/**
|
|
1731
|
-
* Get pending transaction count from
|
|
1776
|
+
* Get pending transaction count from MutationQueue
|
|
1732
1777
|
* Used by SyncedStore to compute hasUnsyncedChanges
|
|
1733
1778
|
*/
|
|
1734
1779
|
getPendingTransactionCount() {
|
|
1735
|
-
const stats = this.
|
|
1780
|
+
const stats = this.mutationQueue.getStats();
|
|
1736
1781
|
// Include pending and executing as "unsynced"
|
|
1737
1782
|
// awaiting_delta transactions are included in 'executing' until confirmed
|
|
1738
1783
|
// Completed and failed are "synced" (either done or gave up)
|
|
@@ -1744,12 +1789,12 @@ export class SyncClient extends EventEmitter {
|
|
|
1744
1789
|
*/
|
|
1745
1790
|
onTransactionEvent(event, callback) {
|
|
1746
1791
|
const eventName = `transaction:${event}`;
|
|
1747
|
-
this.
|
|
1748
|
-
return () => this.
|
|
1792
|
+
this.mutationQueue.on(eventName, callback);
|
|
1793
|
+
return () => this.mutationQueue.off(eventName, callback);
|
|
1749
1794
|
}
|
|
1750
1795
|
/**
|
|
1751
1796
|
* Subscribe to mutation failures with the full payload. Mirrors the
|
|
1752
|
-
* underlying
|
|
1797
|
+
* underlying MutationQueue 'transaction:failed' shape so consumers
|
|
1753
1798
|
* can render typed UI (toast keyed by `AbloError.type`, route-level
|
|
1754
1799
|
* "this entity reverted" boundaries, telemetry).
|
|
1755
1800
|
*
|
|
@@ -1759,15 +1804,26 @@ export class SyncClient extends EventEmitter {
|
|
|
1759
1804
|
* this typed listener drives user-visible surfaces.
|
|
1760
1805
|
*/
|
|
1761
1806
|
onMutationFailure(listener) {
|
|
1762
|
-
this.
|
|
1763
|
-
return () => this.
|
|
1807
|
+
this.mutationQueue.on('transaction:failed', listener);
|
|
1808
|
+
return () => this.mutationQueue.off('transaction:failed', listener);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Subscribe to commit round-trip latency, split into the local seal and the
|
|
1812
|
+
* remote acknowledgement. Fires once per completed commit.
|
|
1813
|
+
*
|
|
1814
|
+
* Taps the {@link MutationQueue} emitter for the same reason
|
|
1815
|
+
* {@link onMutationFailure} does: the commit lifecycle events originate
|
|
1816
|
+
* there and the SyncClient's own emitter never rebroadcasts them.
|
|
1817
|
+
*/
|
|
1818
|
+
onCommitLatency(listener) {
|
|
1819
|
+
return observeCommitLatency(this.mutationQueue, listener);
|
|
1764
1820
|
}
|
|
1765
1821
|
/**
|
|
1766
|
-
* Subscribe to local transaction creation with the full {@link
|
|
1822
|
+
* Subscribe to local transaction creation with the full {@link QueuedMutation}
|
|
1767
1823
|
* payload (`type`, `modelName`, `modelId`, `data`, `previousData`). This is
|
|
1768
1824
|
* the feed the store's local-mutation subscription taps for undo recording.
|
|
1769
1825
|
*
|
|
1770
|
-
* It subscribes to the {@link
|
|
1826
|
+
* It subscribes to the {@link MutationQueue}'s emitter directly, since
|
|
1771
1827
|
* that is the only emitter that fires `transaction:created`. The SyncClient's
|
|
1772
1828
|
* own emitter (reached through {@link subscribe}) never rebroadcasts that
|
|
1773
1829
|
* event, so routing undo through `subscribe('transaction:created')` would
|
|
@@ -1775,7 +1831,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1775
1831
|
* reason.
|
|
1776
1832
|
*/
|
|
1777
1833
|
onLocalTransaction(listener) {
|
|
1778
|
-
this.
|
|
1834
|
+
this.mutationQueue.on('transaction:created', listener);
|
|
1779
1835
|
const snapshotsByCommit = new Map();
|
|
1780
1836
|
const onCommitStaging = (payload) => {
|
|
1781
1837
|
snapshotsByCommit.set(payload.clientTxId, payload.operations.map((operation) => {
|
|
@@ -1843,14 +1899,14 @@ export class SyncClient extends EventEmitter {
|
|
|
1843
1899
|
});
|
|
1844
1900
|
});
|
|
1845
1901
|
};
|
|
1846
|
-
this.
|
|
1847
|
-
this.
|
|
1848
|
-
this.
|
|
1902
|
+
this.mutationQueue.on('commit:staging', onCommitStaging);
|
|
1903
|
+
this.mutationQueue.on('commit:seal_failed', onCommitSealFailed);
|
|
1904
|
+
this.mutationQueue.on('commit:created', onCommitCreated);
|
|
1849
1905
|
return () => {
|
|
1850
|
-
this.
|
|
1851
|
-
this.
|
|
1852
|
-
this.
|
|
1853
|
-
this.
|
|
1906
|
+
this.mutationQueue.off('transaction:created', listener);
|
|
1907
|
+
this.mutationQueue.off('commit:staging', onCommitStaging);
|
|
1908
|
+
this.mutationQueue.off('commit:seal_failed', onCommitSealFailed);
|
|
1909
|
+
this.mutationQueue.off('commit:created', onCommitCreated);
|
|
1854
1910
|
snapshotsByCommit.clear();
|
|
1855
1911
|
};
|
|
1856
1912
|
}
|
|
@@ -1858,14 +1914,14 @@ export class SyncClient extends EventEmitter {
|
|
|
1858
1914
|
* Wait for the latest in-flight transaction for (modelName, modelId)
|
|
1859
1915
|
* to be confirmed by the server, or reject if it's rolled back.
|
|
1860
1916
|
* Resolves immediately when no transaction is in flight — see
|
|
1861
|
-
* `
|
|
1917
|
+
* `MutationQueue.confirmationFor` for the lookup contract.
|
|
1862
1918
|
*
|
|
1863
1919
|
* Distinct from `waitForDeltaConfirmation(transactionId)` which keys
|
|
1864
1920
|
* off a known tx id; this variant is for call sites that hold a
|
|
1865
1921
|
* Model reference but never see the underlying transaction.
|
|
1866
1922
|
*/
|
|
1867
1923
|
waitForConfirmation(modelName, modelId) {
|
|
1868
|
-
return this.
|
|
1924
|
+
return this.mutationQueue.confirmationFor(modelName, modelId);
|
|
1869
1925
|
}
|
|
1870
1926
|
/**
|
|
1871
1927
|
* Get detailed debug info for the sync debug page
|
|
@@ -1874,7 +1930,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1874
1930
|
return {
|
|
1875
1931
|
connectionState: this.connectionState,
|
|
1876
1932
|
pendingMutationsCount: this.pendingMutations.length,
|
|
1877
|
-
|
|
1933
|
+
mutationQueue: this.mutationQueue.getDebugInfo(),
|
|
1878
1934
|
};
|
|
1879
1935
|
}
|
|
1880
1936
|
// --- Best-practice assignment ops ---
|
|
@@ -1900,7 +1956,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1900
1956
|
* Mark a local transaction as optimistically applied. The matching
|
|
1901
1957
|
* server delta (when it arrives with the same `transactionId`) will
|
|
1902
1958
|
* be recognized as an echo and skip the pool mutation. Called
|
|
1903
|
-
* automatically by `
|
|
1959
|
+
* automatically by `MutationQueue` when a transaction is staged;
|
|
1904
1960
|
* exposed publicly so tests can drive the API directly.
|
|
1905
1961
|
*/
|
|
1906
1962
|
markTransactionPending(transactionId) {
|
|
@@ -1916,15 +1972,15 @@ export class SyncClient extends EventEmitter {
|
|
|
1916
1972
|
return this.echoTracker.getMetrics();
|
|
1917
1973
|
}
|
|
1918
1974
|
/**
|
|
1919
|
-
* Package-internal accessor for the {@link
|
|
1975
|
+
* Package-internal accessor for the {@link MutationQueue}. Used by
|
|
1920
1976
|
* `Ablo.commits.create()` to route raw multi-operation envelopes through the
|
|
1921
1977
|
* same retry-on-reconnect lane as the model proxy path, and by tests to
|
|
1922
1978
|
* exercise the queue's interaction with {@link markTransactionPending} on the
|
|
1923
1979
|
* real instance the SyncClient subscribes to. It is not re-exported to SDK
|
|
1924
1980
|
* consumers; `Ablo` is the public surface.
|
|
1925
1981
|
*/
|
|
1926
|
-
|
|
1927
|
-
return this.
|
|
1982
|
+
getMutationQueue() {
|
|
1983
|
+
return this.mutationQueue;
|
|
1928
1984
|
}
|
|
1929
1985
|
applyDeltaBatchToPool(dbResults, enrichRelations) {
|
|
1930
1986
|
const modelsToAdd = [];
|
|
@@ -1932,8 +1988,8 @@ export class SyncClient extends EventEmitter {
|
|
|
1932
1988
|
const idsToRemove = [];
|
|
1933
1989
|
const idsToArchive = [];
|
|
1934
1990
|
// Pre-pass: collect every id slated for `remove` in this batch. The
|
|
1935
|
-
//
|
|
1936
|
-
// user themself) deletes a
|
|
1991
|
+
// parent-delete flicker came from this exact pattern: a peer (or the
|
|
1992
|
+
// user themself) deletes a parent with N children; the commit produces
|
|
1937
1993
|
// BOTH residual `update` deltas (from the optimistic edits that
|
|
1938
1994
|
// happened just before the delete) AND `remove` deltas. The
|
|
1939
1995
|
// `update` branch below would `createFromData` the row back into
|
|
@@ -1962,7 +2018,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1962
2018
|
}
|
|
1963
2019
|
// If a later op in this batch will remove this id, skip earlier
|
|
1964
2020
|
// add/update ops on it. Server FK ordering can produce
|
|
1965
|
-
// U(
|
|
2021
|
+
// U(child)+D(child) when an optimistic edit and a delete both
|
|
1966
2022
|
// commit in the same window; only the final state matters.
|
|
1967
2023
|
if ((action === 'add' || action === 'update') && idsBeingRemoved.has(modelId)) {
|
|
1968
2024
|
continue;
|