@abloatai/ablo 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +101 -85
- package/dist/BaseSyncedStore.d.ts +85 -88
- package/dist/BaseSyncedStore.js +131 -147
- package/dist/Database.d.ts +54 -68
- package/dist/Database.js +97 -113
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +12 -16
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +46 -61
- package/dist/ModelRegistry.d.ts +21 -19
- package/dist/ModelRegistry.js +23 -27
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +5 -6
- package/dist/SyncClient.d.ts +112 -112
- package/dist/SyncClient.js +165 -172
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +27 -32
- package/dist/agent/Agent.js +18 -19
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +6 -7
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +69 -74
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +6 -5
- package/dist/auth/credentialSource.js +9 -10
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +31 -37
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +167 -119
- package/dist/client/Ablo.d.ts +73 -73
- package/dist/client/Ablo.js +125 -160
- package/dist/client/ApiClient.d.ts +30 -19
- package/dist/client/ApiClient.js +133 -38
- package/dist/client/auth.d.ts +47 -47
- package/dist/client/auth.js +108 -117
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +5 -6
- package/dist/client/consoleLogger.js +5 -6
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +25 -30
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +152 -122
- package/dist/client/credentialEndpoint.d.ts +40 -42
- package/dist/client/credentialEndpoint.js +35 -36
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +9 -12
- package/dist/client/hostedEndpoints.js +9 -12
- package/dist/client/httpClient.d.ts +57 -53
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +47 -58
- package/dist/client/modelRegistration.d.ts +5 -9
- package/dist/client/modelRegistration.js +67 -87
- package/dist/client/options.d.ts +134 -157
- package/dist/client/options.js +3 -7
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +64 -75
- package/dist/client/resourceTypes.js +4 -10
- package/dist/client/schemaConfig.d.ts +31 -43
- package/dist/client/schemaConfig.js +38 -50
- package/dist/client/sessionMint.d.ts +16 -12
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +8 -9
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +15 -20
- package/dist/client/wsMutationExecutor.js +17 -23
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -4
- package/dist/coordination/index.d.ts +10 -8
- package/dist/coordination/index.js +14 -12
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -10
- package/dist/coordination/trace.js +13 -14
- package/dist/core/DatabaseManager.d.ts +5 -7
- package/dist/core/DatabaseManager.js +15 -19
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +22 -28
- package/dist/core/QueryView.d.ts +8 -8
- package/dist/core/QueryView.js +2 -2
- package/dist/core/StoreManager.d.ts +12 -14
- package/dist/core/StoreManager.js +21 -24
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +17 -12
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +42 -43
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +63 -61
- package/dist/core/storeContract.js +8 -12
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +107 -99
- package/dist/errorCodes.js +131 -132
- package/dist/errors.d.ts +160 -166
- package/dist/errors.js +155 -158
- package/dist/index.d.ts +30 -27
- package/dist/index.js +89 -86
- package/dist/interfaces/index.d.ts +102 -113
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +41 -40
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +145 -156
- package/dist/mutators/defineMutators.d.ts +23 -34
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +104 -100
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +28 -23
- package/dist/query/client.js +45 -43
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +2 -2
- package/dist/react/context.d.ts +25 -28
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +16 -14
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +10 -11
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +8 -8
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useReactive.js +2 -2
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +20 -24
- package/dist/schema/ddlLock.js +18 -23
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +35 -48
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +21 -23
- package/dist/schema/model.d.ts +118 -143
- package/dist/schema/model.js +22 -33
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +5 -3
- package/dist/schema/queries.d.ts +29 -31
- package/dist/schema/queries.js +23 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +16 -13
- package/dist/schema/residency.js +16 -13
- package/dist/schema/roles.d.ts +36 -43
- package/dist/schema/roles.js +31 -37
- package/dist/schema/schema.d.ts +33 -42
- package/dist/schema/schema.js +31 -32
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +28 -31
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +30 -25
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +30 -25
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +24 -23
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +9 -8
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +22 -25
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +17 -11
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +28 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +46 -58
- package/dist/source/factory.js +22 -27
- package/dist/source/index.d.ts +7 -9
- package/dist/source/index.js +12 -14
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +9 -10
- package/dist/source/next.js +6 -7
- package/dist/source/pushQueue.d.ts +69 -47
- package/dist/source/pushQueue.js +32 -28
- package/dist/source/signing.d.ts +46 -17
- package/dist/source/signing.js +28 -11
- package/dist/source/types.d.ts +121 -104
- package/dist/source/types.js +13 -14
- package/dist/stores/ObjectStore.d.ts +10 -11
- package/dist/stores/ObjectStore.js +11 -12
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +7 -11
- package/dist/stores/SyncActionStore.js +13 -17
- package/dist/surface.d.ts +27 -20
- package/dist/surface.js +27 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
- package/dist/sync/ConnectionManager.d.ts +39 -50
- package/dist/sync/ConnectionManager.js +55 -66
- package/dist/sync/NetworkProbe.d.ts +24 -29
- package/dist/sync/NetworkProbe.js +63 -69
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
- package/dist/sync/SyncWebSocket.d.ts +139 -165
- package/dist/sync/SyncWebSocket.js +191 -223
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +11 -11
- package/dist/sync/bootstrapApply.d.ts +34 -24
- package/dist/sync/bootstrapApply.js +27 -19
- package/dist/sync/commitFrames.d.ts +21 -20
- package/dist/sync/commitFrames.js +18 -18
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +105 -23
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +12 -14
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +104 -104
- package/dist/sync/credentialLifecycle.js +140 -147
- package/dist/sync/deltaPipeline.d.ts +36 -34
- package/dist/sync/deltaPipeline.js +64 -65
- package/dist/sync/groupChange.d.ts +63 -61
- package/dist/sync/groupChange.js +74 -78
- package/dist/sync/heartbeat.d.ts +34 -33
- package/dist/sync/heartbeat.js +31 -31
- package/dist/sync/participants.d.ts +19 -19
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +17 -21
- package/dist/sync/syncCursor.js +17 -21
- package/dist/sync/syncPlan.d.ts +28 -36
- package/dist/sync/syncPlan.js +18 -19
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +35 -36
- package/dist/sync/wsFrameHandlers.js +63 -67
- package/dist/testing/fixtures/bootstrap.d.ts +12 -6
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +30 -33
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +11 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +2 -2
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
- package/dist/testing/mocks/MockSyncContext.js +15 -13
- package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +181 -176
- package/dist/transactions/TransactionQueue.js +338 -350
- package/dist/transactions/TransactionStore.d.ts +6 -4
- package/dist/transactions/TransactionStore.js +6 -4
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +41 -17
- package/dist/transactions/coalesceRules.js +40 -17
- package/dist/transactions/commitPayload.d.ts +48 -52
- package/dist/transactions/commitPayload.js +48 -57
- package/dist/transactions/deltaConfirmation.d.ts +20 -22
- package/dist/transactions/deltaConfirmation.js +37 -45
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
- package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +71 -77
- package/dist/types/index.js +22 -22
- package/dist/types/modelData.d.ts +6 -8
- package/dist/types/modelData.js +5 -7
- package/dist/types/participant.d.ts +10 -11
- package/dist/types/participant.js +6 -8
- package/dist/types/streams.d.ts +208 -195
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +30 -31
- package/dist/wire/errorEnvelope.js +34 -40
- package/dist/wire/frames.d.ts +79 -86
- package/dist/wire/frames.js +26 -33
- package/dist/wire/index.d.ts +14 -12
- package/dist/wire/index.js +30 -26
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +25 -32
- package/dist/wire/protocol.js +25 -32
- package/dist/wire/protocolVersion.d.ts +44 -40
- package/dist/wire/protocolVersion.js +44 -40
- package/docs/coordination.md +59 -0
- package/package.json +11 -10
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -8
- package/dist/server/storage-mode.js +0 -28
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/transactions/optimistic.d.ts +0 -24
- package/dist/transactions/optimistic.js +0 -45
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Keeps a per-scope history of reversible changes so a surface can offer undo
|
|
3
|
+
* and redo. Each mutator invocation records an ordered list of inverse
|
|
4
|
+
* operations; `undo()` pops the most recent group and replays those inverses
|
|
5
|
+
* without recording them, then moves the entry onto the redo stack.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* that explicitly below).
|
|
7
|
+
* History is divided into named scopes, one per surface — a deck editor, a
|
|
8
|
+
* spreadsheet, and so on — reached through {@link UndoManager.getScope}. Undo in
|
|
9
|
+
* one surface never affects another.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - No persistence across sessions (in-memory stack).
|
|
14
|
-
* - No collaborative awareness — undoing after a teammate edited the same
|
|
15
|
-
* row produces a "last writer wins" outcome, not a true merge.
|
|
16
|
-
* - Server-side mutation rejection after optimistic apply does NOT
|
|
17
|
-
* automatically invalidate the undo stack. Consumers should `clear()`
|
|
18
|
-
* the scope on sync error if they want strict correctness.
|
|
11
|
+
* Two things to know about its reach. History lives in memory and does not
|
|
12
|
+
* persist across sessions. And if the server rejects a change after it was
|
|
13
|
+
* applied optimistically, the undo stack is not invalidated automatically; call
|
|
14
|
+
* {@link UndoScope.clear} on a sync error if you need strict correctness.
|
|
19
15
|
*/
|
|
20
16
|
import { getContext } from '../context.js';
|
|
21
17
|
import { createTransaction } from './Transaction.js';
|
|
22
18
|
import { parseUndoEntry } from './inverseOp.js';
|
|
23
19
|
import { resolveOps, DEFAULT_UNDO_CONFLICT_POLICY, } from './undoApply.js';
|
|
24
|
-
/** Normalize a registered model name to
|
|
25
|
-
* (mirrors TransactionQueue's `normalizeModelKey`). */
|
|
20
|
+
/** Normalize a registered model name to its lowercased alias form. */
|
|
26
21
|
const normalizeModelAlias = (modelName) => modelName.replace('Model', '').toLowerCase();
|
|
27
22
|
/**
|
|
28
|
-
* A single undo stack for one surface
|
|
29
|
-
*
|
|
30
|
-
*
|
|
23
|
+
* A single undo stack for one surface, obtained from
|
|
24
|
+
* {@link UndoManager.getScope}. Call {@link UndoScope.record} after a mutator to
|
|
25
|
+
* add an entry, and {@link UndoScope.undo} / {@link UndoScope.redo} to move
|
|
26
|
+
* through the history.
|
|
31
27
|
*/
|
|
32
28
|
/**
|
|
33
|
-
* How long a
|
|
34
|
-
*
|
|
35
|
-
* generous
|
|
36
|
-
*
|
|
29
|
+
* How long a pending replay-echo marker stays armed before it is pruned. A real
|
|
30
|
+
* echo returns within a couple of local-store round-trips (tens of milliseconds);
|
|
31
|
+
* this is a generous ceiling so that an echo which never arrives — for instance,
|
|
32
|
+
* because the write was skipped while offline — cannot suppress a genuine later
|
|
33
|
+
* edit to the same row indefinitely.
|
|
37
34
|
*/
|
|
38
35
|
const REPLAY_ECHO_TTL_MS = 5000;
|
|
39
36
|
export class UndoScope {
|
|
@@ -45,36 +42,34 @@ export class UndoScope {
|
|
|
45
42
|
maxHistory;
|
|
46
43
|
conflictPolicy;
|
|
47
44
|
/**
|
|
48
|
-
* Observers notified after each successful {@link record}.
|
|
49
|
-
* user actions only
|
|
50
|
-
* without calling `record
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* observer can never wedge the editor's recording path.
|
|
45
|
+
* Observers notified after each successful {@link UndoScope.record}. They see
|
|
46
|
+
* forward user actions only: undo and redo move entries between the stacks
|
|
47
|
+
* without calling `record`, so a listener never observes a reversal. It is a
|
|
48
|
+
* deliberately generic hook — analytics or audit code can watch the stream of
|
|
49
|
+
* committed mutations without the scope knowing about it. A listener that throws
|
|
50
|
+
* is isolated so it cannot break recording.
|
|
55
51
|
*/
|
|
56
52
|
recordListeners = new Set();
|
|
57
53
|
/**
|
|
58
|
-
* Observers notified after
|
|
59
|
-
*
|
|
60
|
-
* reversals too, so React
|
|
61
|
-
* stream-recording path
|
|
62
|
-
* a
|
|
63
|
-
* and a
|
|
54
|
+
* Observers notified after any stack change — record, undo, redo, or clear.
|
|
55
|
+
* Unlike {@link recordListeners}, which fires on forward actions only, this
|
|
56
|
+
* fires on reversals too, so a React consumer can keep `canUndo` and `canRedo`
|
|
57
|
+
* current. Because the stream-recording path adds entries without triggering a
|
|
58
|
+
* render, a component that read `canUndo` on its last render would otherwise go
|
|
59
|
+
* stale and a keyboard handler gated on it would quietly do nothing.
|
|
64
60
|
*/
|
|
65
61
|
changeListeners = new Set();
|
|
66
62
|
/**
|
|
67
|
-
*
|
|
68
|
-
* promise so they run strictly in the order they were
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* Serializing the whole scope closes both holes with one mechanism.
|
|
63
|
+
* The serialization tail. Recording, undo, and redo all chain off this one
|
|
64
|
+
* promise, so they run strictly in the order they were invoked and never
|
|
65
|
+
* interleave. This matters for correctness, not just throughput, in two ways.
|
|
66
|
+
* Ordering: callers often fire writes without awaiting them, so without
|
|
67
|
+
* serialization an entry would land on the stack when its mutator resolves, and
|
|
68
|
+
* a fast second write could record before a slow first — replaying undo in the
|
|
69
|
+
* wrong order. Snapshot integrity: each recording reads and clears a model's
|
|
70
|
+
* modified-field markers, which form the undo baseline, so two recordings
|
|
71
|
+
* interleaving on the same model would corrupt each other's before-image.
|
|
72
|
+
* Serializing the whole scope closes both gaps at once.
|
|
78
73
|
*/
|
|
79
74
|
tail = Promise.resolve();
|
|
80
75
|
/** Predicate selecting which models this surface records (see options). */
|
|
@@ -84,37 +79,37 @@ export class UndoScope {
|
|
|
84
79
|
/** Unsubscribe from the local-mutation stream. */
|
|
85
80
|
unsubscribe;
|
|
86
81
|
/**
|
|
87
|
-
* True while
|
|
88
|
-
* commit path
|
|
89
|
-
*
|
|
90
|
-
*
|
|
82
|
+
* True while undo or redo is replaying operations. A replay writes through the
|
|
83
|
+
* normal commit path and therefore re-emits on the local-mutation stream; this
|
|
84
|
+
* flag tells the scope's own listener to ignore those writes so they are not
|
|
85
|
+
* recorded again.
|
|
91
86
|
*/
|
|
92
87
|
replaying = false;
|
|
93
|
-
/**
|
|
88
|
+
/** Operations collected during the current tick, flushed together as one entry. */
|
|
94
89
|
batch = [];
|
|
95
90
|
flushScheduled = false;
|
|
96
91
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
92
|
+
* An open grouping session. While set, stream operations accumulate here across
|
|
93
|
+
* ticks instead of flushing each tick, so a multi-tick action — a drag, or a
|
|
94
|
+
* whole streaming AI response — collapses into a single undo step.
|
|
95
|
+
* {@link UndoScope.endGroup} flushes it.
|
|
101
96
|
*/
|
|
102
97
|
group = null;
|
|
103
98
|
/**
|
|
104
|
-
*
|
|
99
|
+
* Suppression of a replay's asynchronous echo, keyed by `${modelKey}:${id}`.
|
|
105
100
|
*
|
|
106
|
-
* The synchronous {@link replaying} flag only
|
|
107
|
-
*
|
|
108
|
-
* synchronously:
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
101
|
+
* The synchronous {@link UndoScope.replaying} flag catches only echoes
|
|
102
|
+
* delivered inline while operations are applied. In practice the engine does not
|
|
103
|
+
* emit a replayed write's echo synchronously: the commit is deferred behind a
|
|
104
|
+
* local-store write, so the echo arrives on the stream after undo or redo has
|
|
105
|
+
* already reset `replaying` and pushed its entry. That late echo would be
|
|
106
|
+
* recorded as a new edit — and recording clears the redo stack, so every undo
|
|
107
|
+
* would quietly destroy its own redo. To prevent that, the row of each operation
|
|
108
|
+
* about to be replayed is marked here synchronously, before the write, and one
|
|
109
|
+
* mark is consumed when the matching mutation arrives, whenever that is. Marks
|
|
110
|
+
* carry a time-to-live so an echo that never arrives — because the write was
|
|
111
|
+
* skipped while offline — cannot linger and wrongly suppress a much later, real
|
|
112
|
+
* edit to the same row.
|
|
118
113
|
*/
|
|
119
114
|
pendingReplayEchoes = new Map();
|
|
120
115
|
constructor(schema, store, organizationId, options = {}) {
|
|
@@ -124,10 +119,10 @@ export class UndoScope {
|
|
|
124
119
|
this.maxHistory = options.maxHistory ?? 100;
|
|
125
120
|
this.conflictPolicy = options.conflictPolicy ?? DEFAULT_UNDO_CONFLICT_POLICY;
|
|
126
121
|
this.tracksModel = options.tracksModel;
|
|
127
|
-
// Build the registered
|
|
128
|
-
// reports
|
|
129
|
-
// and the replay transaction are keyed by the
|
|
130
|
-
// `'slideLayers'`)
|
|
122
|
+
// Build the map from registered name to schema key. The mutation stream
|
|
123
|
+
// reports a model's registered name (for example `'SlideLayer'`), but inverse
|
|
124
|
+
// operations and the replay transaction are keyed by the schema key (for
|
|
125
|
+
// example `'slideLayers'`), so map every reasonable spelling to the schema key.
|
|
131
126
|
for (const schemaKey of Object.keys(this.schema.models)) {
|
|
132
127
|
const def = this.schema.models[schemaKey];
|
|
133
128
|
const typename = def?.typename ?? schemaKey;
|
|
@@ -137,23 +132,21 @@ export class UndoScope {
|
|
|
137
132
|
this.schemaKeyByAlias.set(normalizeModelAlias(alias), schemaKey);
|
|
138
133
|
}
|
|
139
134
|
}
|
|
140
|
-
// Subscribe to the local-mutation stream
|
|
141
|
-
// stream recording.
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// and the flag is removed. Optional on the contract so minimal test
|
|
146
|
-
// doubles can omit it (undo then records nothing).
|
|
135
|
+
// Subscribe to the local-mutation stream only when this scope opts into
|
|
136
|
+
// stream recording. A scope using explicit `record()` calls instead keeps
|
|
137
|
+
// `recordFromStream` false so writes are not counted twice. The stream method
|
|
138
|
+
// on the store is optional, so a minimal test double can omit it, in which
|
|
139
|
+
// case undo records nothing.
|
|
147
140
|
this.unsubscribe =
|
|
148
141
|
options.recordFromStream && this.store.subscribeLocalMutations
|
|
149
142
|
? this.store.subscribeLocalMutations((m) => { this.onLocalMutation(m); })
|
|
150
143
|
: () => { };
|
|
151
144
|
}
|
|
152
145
|
/**
|
|
153
|
-
*
|
|
154
|
-
* collapses into
|
|
155
|
-
*
|
|
156
|
-
*
|
|
146
|
+
* Opens a grouping session: every stream-recorded operation until
|
|
147
|
+
* {@link UndoScope.endGroup} collapses into one undo entry. Call it at the start
|
|
148
|
+
* of a gesture, such as a pointer-down, or at the start of an AI response. A
|
|
149
|
+
* second call closes the previous group first.
|
|
157
150
|
*/
|
|
158
151
|
beginGroup(label) {
|
|
159
152
|
if (this.group)
|
|
@@ -210,9 +203,9 @@ export class UndoScope {
|
|
|
210
203
|
}
|
|
211
204
|
}
|
|
212
205
|
/**
|
|
213
|
-
*
|
|
214
|
-
* synchronously, before
|
|
215
|
-
*
|
|
206
|
+
* Arms echo suppression for the rows a replay is about to write. Called
|
|
207
|
+
* synchronously, before the writes, so the marks exist however long the engine
|
|
208
|
+
* takes to surface each echo on the stream. See {@link UndoScope.pendingReplayEchoes}.
|
|
216
209
|
*/
|
|
217
210
|
markReplayEchoes(ops) {
|
|
218
211
|
const expiresAt = Date.now() + REPLAY_ECHO_TTL_MS;
|
|
@@ -228,9 +221,9 @@ export class UndoScope {
|
|
|
228
221
|
}
|
|
229
222
|
}
|
|
230
223
|
/**
|
|
231
|
-
* If `${schemaKey}:${modelId}` has an armed
|
|
232
|
-
*
|
|
233
|
-
* marks
|
|
224
|
+
* If `${schemaKey}:${modelId}` has an armed mark, consume one and report that
|
|
225
|
+
* this mutation is the scope's own replay echo, so the caller drops it. Expired
|
|
226
|
+
* marks are pruned along the way, so an echo that never arrives cannot linger.
|
|
234
227
|
*/
|
|
235
228
|
consumeReplayEcho(schemaKey, modelId) {
|
|
236
229
|
if (this.pendingReplayEchoes.size === 0)
|
|
@@ -256,11 +249,11 @@ export class UndoScope {
|
|
|
256
249
|
null);
|
|
257
250
|
}
|
|
258
251
|
/**
|
|
259
|
-
*
|
|
260
|
-
* and out-of-scope models, derives the forward
|
|
261
|
-
* mutation's `data
|
|
262
|
-
* per-tick flush so a burst of writes
|
|
263
|
-
*
|
|
252
|
+
* The stream listener, and the only place stream-recorded entries originate. It
|
|
253
|
+
* skips replay echoes and out-of-scope models, derives the forward and inverse
|
|
254
|
+
* operations from the mutation's `data` and `previousData`, and defers the stack
|
|
255
|
+
* push to a per-tick flush, so a burst of writes — aligning five layers at once,
|
|
256
|
+
* say — becomes a single undo step.
|
|
264
257
|
*/
|
|
265
258
|
onLocalMutation(m) {
|
|
266
259
|
if (this.replaying)
|
|
@@ -309,7 +302,7 @@ export class UndoScope {
|
|
|
309
302
|
const collected = this.batch;
|
|
310
303
|
this.batch = [];
|
|
311
304
|
const forwards = collected.map((c) => c.forward);
|
|
312
|
-
// Undo applies inverses in
|
|
305
|
+
// Undo applies the inverses in reverse order of how the forwards ran.
|
|
313
306
|
const inverses = collected
|
|
314
307
|
.map((c) => c.inverse)
|
|
315
308
|
.filter((i) => i !== null)
|
|
@@ -330,27 +323,25 @@ export class UndoScope {
|
|
|
330
323
|
return result;
|
|
331
324
|
}
|
|
332
325
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
326
|
+
* Runs a recording mutator by itself on the scope's serialization chain, so its
|
|
327
|
+
* snapshot, write, and {@link UndoScope.record} happen atomically with respect to
|
|
328
|
+
* undo and redo. This is used by the explicit-record path; the stream-recording
|
|
329
|
+
* path does not need it, since it derives entries from already-committed
|
|
330
|
+
* mutations.
|
|
338
331
|
*/
|
|
339
332
|
runRecorded(work) {
|
|
340
333
|
return this.enqueue(work);
|
|
341
334
|
}
|
|
342
335
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* (untrusted input). Best practice: validate at trust boundaries, type-check
|
|
353
|
-
* internal calls.
|
|
336
|
+
* Records one entry onto the undo stack and clears the redo stack. It is fed
|
|
337
|
+
* both by the per-tick flush and grouping paths from the local-mutation stream
|
|
338
|
+
* and by direct callers using explicit recording. Entries are built internally
|
|
339
|
+
* and therefore trusted, so the schema check here runs only outside production:
|
|
340
|
+
* it catches recorder bugs early, rejecting a malformed operation at ingestion
|
|
341
|
+
* with a clear path rather than letting it fail later during replay, without
|
|
342
|
+
* paying a validation cost on every user action in production. The real
|
|
343
|
+
* validation boundary is {@link parseUndoEntry}, applied to entries loaded from
|
|
344
|
+
* persistence, which is untrusted input.
|
|
354
345
|
*/
|
|
355
346
|
record(entry) {
|
|
356
347
|
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
@@ -364,13 +355,11 @@ export class UndoScope {
|
|
|
364
355
|
this.emitChange();
|
|
365
356
|
}
|
|
366
357
|
/**
|
|
367
|
-
*
|
|
368
|
-
* each {@link record} call,
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* `{ kind, modelKey, data }` ops), so a consumer can derive what changed
|
|
373
|
-
* (e.g. "a slideLayers row of type 'chart' was created") without re-querying.
|
|
358
|
+
* Subscribes to every recorded mutation. The listener fires synchronously at the
|
|
359
|
+
* end of each {@link UndoScope.record} call, once the entry is on the undo stack,
|
|
360
|
+
* and the returned function unsubscribes it. The listener receives the full
|
|
361
|
+
* {@link UndoEntry} — its `forwards` carry the `{ kind, modelKey, data }`
|
|
362
|
+
* operations — so a consumer can tell what changed without querying again.
|
|
374
363
|
*/
|
|
375
364
|
onRecord(listener) {
|
|
376
365
|
this.recordListeners.add(listener);
|
|
@@ -384,18 +373,17 @@ export class UndoScope {
|
|
|
384
373
|
listener(entry);
|
|
385
374
|
}
|
|
386
375
|
catch (err) {
|
|
387
|
-
// A faulty observer must never break the
|
|
388
|
-
//
|
|
389
|
-
// is at fault, so this is actionable → warn (no engine tag on the line).
|
|
376
|
+
// A faulty observer must never break the recording path. The consumer's
|
|
377
|
+
// own onRecord callback is at fault, so log it as an actionable warning.
|
|
390
378
|
getContext().logger.warn('An undo/redo onRecord listener threw — your callback should not throw', err);
|
|
391
379
|
}
|
|
392
380
|
}
|
|
393
381
|
}
|
|
394
382
|
/**
|
|
395
|
-
*
|
|
396
|
-
* `useUndoScope` to re-render so `canUndo
|
|
397
|
-
* consumer
|
|
398
|
-
*
|
|
383
|
+
* Subscribes to any stack change — record, undo, redo, or clear. The React
|
|
384
|
+
* `useUndoScope` hook uses this to re-render so `canUndo` and `canRedo` stay
|
|
385
|
+
* current for every consumer, not only the component that invoked undo or redo.
|
|
386
|
+
* The returned function unsubscribes.
|
|
399
387
|
*/
|
|
400
388
|
onChange(listener) {
|
|
401
389
|
this.changeListeners.add(listener);
|
|
@@ -409,8 +397,8 @@ export class UndoScope {
|
|
|
409
397
|
listener();
|
|
410
398
|
}
|
|
411
399
|
catch (err) {
|
|
412
|
-
//
|
|
413
|
-
//
|
|
400
|
+
// The consumer's own onChange callback is at fault, so log it as an
|
|
401
|
+
// actionable warning.
|
|
414
402
|
getContext().logger.warn('An undo/redo onChange listener threw — your callback should not throw', err);
|
|
415
403
|
}
|
|
416
404
|
}
|
|
@@ -422,12 +410,11 @@ export class UndoScope {
|
|
|
422
410
|
return this.redoStack.length > 0;
|
|
423
411
|
}
|
|
424
412
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* my change only where it still stands.
|
|
413
|
+
* Pops the most recent entry, applies its inverse operations, and pushes it onto
|
|
414
|
+
* the redo stack. Under the default `skip-stale` policy the inverses are first
|
|
415
|
+
* filtered against the current state — paired with the entry's forwards, which
|
|
416
|
+
* record what this change set — so a field a collaborator changed afterward is
|
|
417
|
+
* left untouched, and undo reverts the change only where it still stands.
|
|
431
418
|
*/
|
|
432
419
|
undo() {
|
|
433
420
|
return this.enqueue(async () => {
|
|
@@ -436,19 +423,19 @@ export class UndoScope {
|
|
|
436
423
|
return;
|
|
437
424
|
const tx = createTransaction(this.schema, this.store, this.organizationId);
|
|
438
425
|
const ops = resolveOps(entry.inverses, entry.forwards, this.store, this.conflictPolicy);
|
|
439
|
-
// Suppress
|
|
440
|
-
// new
|
|
441
|
-
// covers the
|
|
442
|
-
// returns. Cleared in `finally` even if a replay
|
|
426
|
+
// Suppress the scope's own stream listener so replayed writes are not
|
|
427
|
+
// recorded as new entries. `replaying` covers echoes delivered inline;
|
|
428
|
+
// `markReplayEchoes` covers the asynchronous echo that lands after this
|
|
429
|
+
// method returns. Cleared in `finally` even if a replay throws.
|
|
443
430
|
this.markReplayEchoes(ops);
|
|
444
431
|
this.replaying = true;
|
|
445
432
|
try {
|
|
446
433
|
await applyOps(tx, ops);
|
|
447
434
|
}
|
|
448
435
|
catch (err) {
|
|
449
|
-
// The replay was rejected (
|
|
450
|
-
// so restore the entry to the undo stack rather than
|
|
451
|
-
//
|
|
436
|
+
// The replay was rejected (for example, a server 409). Nothing changed,
|
|
437
|
+
// so restore the entry to the undo stack rather than dropping it, which
|
|
438
|
+
// would also strand it off the redo stack and lose the action entirely.
|
|
452
439
|
this.undoStack.push(entry);
|
|
453
440
|
this.emitChange();
|
|
454
441
|
throw err;
|
|
@@ -463,10 +450,11 @@ export class UndoScope {
|
|
|
463
450
|
});
|
|
464
451
|
}
|
|
465
452
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* re-asserts
|
|
453
|
+
* Pops the most recently undone entry, re-applies its forward operations, and
|
|
454
|
+
* pushes it onto the undo stack. It mirrors {@link UndoScope.undo}: the forwards
|
|
455
|
+
* are filtered against the current state — paired with the entry's inverses,
|
|
456
|
+
* which record what undo restored — so redo re-asserts the change only where the
|
|
457
|
+
* undone value still stands.
|
|
470
458
|
*/
|
|
471
459
|
redo() {
|
|
472
460
|
return this.enqueue(async () => {
|
|
@@ -523,9 +511,9 @@ export class UndoScope {
|
|
|
523
511
|
}
|
|
524
512
|
}
|
|
525
513
|
/**
|
|
526
|
-
*
|
|
527
|
-
* when the mutation
|
|
528
|
-
* previous values
|
|
514
|
+
* Derives the forward and inverse operation for a single local mutation. Returns
|
|
515
|
+
* null when the mutation cannot be reversed — for example, an update with no
|
|
516
|
+
* captured previous values — so the caller drops it rather than push a half-entry.
|
|
529
517
|
*/
|
|
530
518
|
function buildUndoOps(m, modelKey) {
|
|
531
519
|
const id = m.modelId;
|
|
@@ -572,8 +560,9 @@ function buildUndoOps(m, modelKey) {
|
|
|
572
560
|
}
|
|
573
561
|
// ── Manager ────────────────────────────────────────────────────────────────
|
|
574
562
|
/**
|
|
575
|
-
*
|
|
576
|
-
* during engine setup
|
|
563
|
+
* The registry of named undo scopes. One instance is created per application
|
|
564
|
+
* during engine setup, and each surface finds its scope by name through
|
|
565
|
+
* {@link UndoManager.getScope}.
|
|
577
566
|
*/
|
|
578
567
|
export class UndoManager {
|
|
579
568
|
schema;
|
|
@@ -600,17 +589,17 @@ export class UndoManager {
|
|
|
600
589
|
}
|
|
601
590
|
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
602
591
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
592
|
+
* Replays a list of operations through a {@link Transaction}. Used by both undo,
|
|
593
|
+
* which replays the captured inverses, and redo, which replays the captured
|
|
594
|
+
* forwards. Each operation is awaited in turn to preserve ordering.
|
|
606
595
|
*/
|
|
607
596
|
async function applyOps(tx, ops) {
|
|
608
597
|
const mutateAny = tx.mutations;
|
|
609
598
|
for (const op of ops) {
|
|
610
599
|
const m = mutateAny[op.modelKey];
|
|
611
600
|
if (!m) {
|
|
612
|
-
// A persisted inverse op
|
|
613
|
-
//
|
|
601
|
+
// A persisted inverse op references a model the schema no longer has;
|
|
602
|
+
// fail with a clear message rather than an opaque TypeError.
|
|
614
603
|
throw new Error(`Cannot undo: model "${op.modelKey}" is not part of the current schema.`);
|
|
615
604
|
}
|
|
616
605
|
switch (op.kind) {
|
|
@@ -1,53 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Declares a tree of named custom mutators grouped by model key. Each mutator is
|
|
3
|
+
* a plain async function that receives `{ tx, args }` and composes any number of
|
|
4
|
+
* `tx.mutations.*` and `tx.read.*` calls to carry out a named operation, such as
|
|
5
|
+
* `slides.createWithLayers`.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* dispatcher lives in `./Transaction` (the `tx` object) and
|
|
11
|
-
* `../react/useMutators` (the React-side invoker builder). Keeping those
|
|
12
|
-
* concerns separate makes the types trivially inferable at the call site:
|
|
13
|
-
* `defineMutators(schema, { ... })` returns the literal object the consumer
|
|
14
|
-
* wrote, so `typeof mutators` carries every mutator's exact `args`/result
|
|
15
|
-
* signature into `useMutators`.
|
|
7
|
+
* The function is purely a place for types to anchor and returns its input
|
|
8
|
+
* unchanged; the runtime that dispatches a mutator lives elsewhere — the
|
|
9
|
+
* transaction object it receives and the React hook that invokes it. Because
|
|
10
|
+
* `defineMutators(schema, { ... })` returns the exact object you wrote,
|
|
11
|
+
* `typeof mutators` carries every mutator's precise `args` and result types
|
|
12
|
+
* through to wherever they are invoked.
|
|
16
13
|
*/
|
|
17
14
|
import type { Schema } from '../schema/schema.js';
|
|
18
15
|
import type { Transaction } from './Transaction.js';
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* opt into the inference they need — the `MutatorDefs` record relaxes to
|
|
25
|
-
* `unknown` to let heterogeneous mutator trees unify without `any`.
|
|
17
|
+
* The signature of a single custom mutator. The engine supplies `tx`; you control
|
|
18
|
+
* `args`, in whatever shape you like, and the resolved return value. `TArgs` and
|
|
19
|
+
* `TResult` are bounded by `unknown` rather than `any`, so a mixed tree of
|
|
20
|
+
* mutators can be typed together without falling back to `any`.
|
|
26
21
|
*/
|
|
27
22
|
export type MutatorFn<S extends Schema, TArgs, TResult = void> = (options: {
|
|
28
23
|
tx: Transaction<S>;
|
|
29
24
|
args: TArgs;
|
|
30
25
|
}) => Promise<TResult>;
|
|
31
26
|
/**
|
|
32
|
-
* The shape
|
|
33
|
-
* values are named mutator functions.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* mutators inline, TypeScript infers the concrete `TArgs`/`TResult` for each
|
|
38
|
-
* function — the `unknown` here is just a ceiling, not what the consumer
|
|
39
|
-
* ends up seeing.
|
|
27
|
+
* The shape {@link defineMutators} accepts: an optional record per model key
|
|
28
|
+
* whose values are named mutator functions. The `unknown` bounds keep the public
|
|
29
|
+
* boundary type-safe without `any`; when you write your mutators inline,
|
|
30
|
+
* TypeScript still infers the concrete `args` and result of each function, so the
|
|
31
|
+
* `unknown` here is only a ceiling, not what you end up working with.
|
|
40
32
|
*/
|
|
41
33
|
export type MutatorDefs<S extends Schema> = {
|
|
42
34
|
[K in keyof S['models']]?: Record<string, MutatorFn<S, never, unknown>>;
|
|
43
35
|
};
|
|
44
36
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* Pattern mirrors Zero's own `defineMutators` / `createBuilder` — there is
|
|
50
|
-
* no runtime work to do here, it's purely a location for type inference to
|
|
51
|
-
* anchor.
|
|
37
|
+
* Returns the mutators object unchanged while constraining its shape against the
|
|
38
|
+
* schema. The `S` generic pins the model keys, and the `M` generic is inferred as
|
|
39
|
+
* a `const`, so each mutator's literal signature survives. There is no runtime
|
|
40
|
+
* work here; the function exists purely as a place for type inference to anchor.
|
|
52
41
|
*/
|
|
53
42
|
export declare function defineMutators<S extends Schema, const M extends MutatorDefs<S>>(_schema: S, mutators: M): M;
|