@abloatai/ablo 0.36.0 → 0.37.1
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 -2
- package/CHANGELOG.md +55 -2014
- package/NOTICE +2 -2
- package/README.md +45 -63
- package/assets/banner.png +0 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +2 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -0
- package/dist/coordination.d.ts +2 -0
- package/dist/coordination.d.ts.map +1 -0
- package/dist/coordination.js +2 -0
- package/dist/coordination.js.map +1 -0
- package/dist/index.d.ts +3 -114
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -163
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +3 -0
- package/dist/react.js.map +1 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/source-conformance.d.ts +2 -0
- package/dist/source-conformance.d.ts.map +1 -0
- package/dist/source-conformance.js +2 -0
- package/dist/source-conformance.js.map +1 -0
- package/dist/source-drizzle.d.ts +2 -0
- package/dist/source-drizzle.d.ts.map +1 -0
- package/dist/source-drizzle.js +2 -0
- package/dist/source-drizzle.js.map +1 -0
- package/dist/source-kysely.d.ts +2 -0
- package/dist/source-kysely.d.ts.map +1 -0
- package/dist/source-kysely.js +2 -0
- package/dist/source-kysely.js.map +1 -0
- package/dist/source-next.d.ts +2 -0
- package/dist/source-next.d.ts.map +1 -0
- package/dist/source-next.js +2 -0
- package/dist/source-next.js.map +1 -0
- package/dist/source.d.ts +2 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +2 -0
- package/dist/source.js.map +1 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +2 -0
- package/dist/wire.js.map +1 -0
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +7 -6
- package/docs/api.md +10 -10
- package/docs/client-behavior.md +5 -5
- package/docs/coordination.md +52 -62
- package/docs/data-sources.md +1 -1
- package/docs/examples/agent-human.md +4 -4
- package/docs/examples/ai-sdk-tool.md +1 -1
- package/docs/examples/existing-python-backend.md +15 -4
- package/docs/examples/nextjs.md +27 -6
- package/docs/examples/server-agent.md +2 -2
- package/docs/how-it-works.md +4 -4
- package/docs/identity.md +2 -1
- package/docs/integration-guide.md +24 -13
- package/docs/internal/README.md +18 -0
- package/docs/internal/agent-fleet-coordination-design.md +171 -0
- package/docs/internal/agent-orchestration.md +58 -0
- package/docs/internal/commit-identifiers.md +91 -0
- package/docs/internal/concurrency-open-decisions.md +37 -0
- package/docs/internal/data-source-reverse-channel.md +150 -0
- package/docs/internal/per-field-conflict-detection.md +165 -0
- package/docs/internal/postgres-replication.md +64 -0
- package/docs/internal/serializable-schema.md +119 -0
- package/docs/internal/structure.md +32 -0
- package/docs/mcp.md +2 -2
- package/docs/migration.md +3 -3
- package/docs/quickstart.md +2 -2
- package/docs/react.md +5 -5
- package/docs/schema-contract.md +3 -3
- package/docs/sessions.md +91 -37
- package/examples/README.md +2 -2
- package/examples/data-source/README.md +1 -1
- package/examples/data-source/ablo-driver.ts +1 -1
- package/examples/data-source/customer-server.ts +1 -1
- package/examples/data-source/run.ts +1 -1
- package/examples/data-source/schema.ts +1 -1
- package/examples/quickstart.ts +2 -2
- package/llms.txt +8 -8
- package/package.json +63 -166
- package/bin/ablo.cjs +0 -39
- package/dist/BaseSyncedStore.d.ts +0 -843
- package/dist/BaseSyncedStore.js +0 -1971
- package/dist/Database.d.ts +0 -323
- package/dist/Database.js +0 -1502
- package/dist/InstanceCache.d.ts +0 -237
- package/dist/InstanceCache.js +0 -1166
- package/dist/LazyReferenceCollection.d.ts +0 -177
- package/dist/LazyReferenceCollection.js +0 -461
- package/dist/Model.d.ts +0 -454
- package/dist/Model.js +0 -919
- package/dist/ModelRegistry.d.ts +0 -225
- package/dist/ModelRegistry.js +0 -539
- package/dist/NetworkMonitor.d.ts +0 -28
- package/dist/NetworkMonitor.js +0 -79
- package/dist/RuntimeContext.d.ts +0 -52
- package/dist/RuntimeContext.js +0 -80
- package/dist/SyncClient.d.ts +0 -541
- package/dist/SyncClient.js +0 -2202
- package/dist/adapters/alwaysOnline.d.ts +0 -14
- package/dist/adapters/alwaysOnline.js +0 -17
- package/dist/adapters/inMemoryStorage.d.ts +0 -31
- package/dist/adapters/inMemoryStorage.js +0 -110
- package/dist/ai-sdk/coordinatedTool.d.ts +0 -120
- package/dist/ai-sdk/coordinatedTool.js +0 -134
- package/dist/ai-sdk/coordinationContext.d.ts +0 -46
- package/dist/ai-sdk/coordinationContext.js +0 -106
- package/dist/ai-sdk/index.d.ts +0 -121
- package/dist/ai-sdk/index.js +0 -121
- package/dist/ai-sdk/wrap.d.ts +0 -65
- package/dist/ai-sdk/wrap.js +0 -39
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +0 -8
- package/dist/batching/index.d.ts +0 -55
- package/dist/batching/index.js +0 -147
- package/dist/client/Ablo.d.ts +0 -231
- package/dist/client/Ablo.js +0 -160
- package/dist/client/abloClient.d.ts +0 -309
- package/dist/client/abloClient.js +0 -13
- package/dist/client/clientPrelude.d.ts +0 -52
- package/dist/client/clientPrelude.js +0 -60
- package/dist/client/consoleLogger.d.ts +0 -35
- package/dist/client/consoleLogger.js +0 -44
- package/dist/client/coreClient.d.ts +0 -60
- package/dist/client/coreClient.js +0 -118
- package/dist/client/createInternalComponents.d.ts +0 -50
- package/dist/client/createInternalComponents.js +0 -98
- package/dist/client/createModelProxy.d.ts +0 -248
- package/dist/client/createModelProxy.js +0 -884
- package/dist/client/humans.d.ts +0 -69
- package/dist/client/humans.js +0 -78
- package/dist/client/modelRegistration.d.ts +0 -10
- package/dist/client/modelRegistration.js +0 -312
- package/dist/client/options.d.ts +0 -461
- package/dist/client/options.js +0 -7
- package/dist/client/reactiveEngine.d.ts +0 -53
- package/dist/client/reactiveEngine.js +0 -688
- package/dist/client/resourceTypes.d.ts +0 -12
- package/dist/client/resourceTypes.js +0 -10
- package/dist/client/schemaConfig.d.ts +0 -44
- package/dist/client/schemaConfig.js +0 -185
- package/dist/client/storeCluster.d.ts +0 -47
- package/dist/client/storeCluster.js +0 -118
- package/dist/client/storeLifecycle.d.ts +0 -61
- package/dist/client/storeLifecycle.js +0 -231
- package/dist/client/validateAbloOptions.d.ts +0 -42
- package/dist/client/validateAbloOptions.js +0 -43
- package/dist/client/wsMutationExecutor.d.ts +0 -27
- package/dist/client/wsMutationExecutor.js +0 -72
- package/dist/context.d.ts +0 -42
- package/dist/context.js +0 -81
- package/dist/coordination/ClaimLog.d.ts +0 -26
- package/dist/coordination/ClaimLog.js +0 -32
- package/dist/coordination/index.d.ts +0 -1
- package/dist/coordination/index.js +0 -8
- package/dist/core/index.d.ts +0 -33
- package/dist/core/index.js +0 -48
- package/dist/docs/catalog.d.ts +0 -72
- package/dist/docs/catalog.js +0 -230
- package/dist/docs/index.d.ts +0 -10
- package/dist/docs/index.js +0 -10
- package/dist/environment.d.ts +0 -1
- package/dist/environment.js +0 -8
- package/dist/interfaces/index.d.ts +0 -311
- package/dist/interfaces/index.js +0 -9
- package/dist/keys/index.d.ts +0 -1
- package/dist/keys/index.js +0 -8
- package/dist/mutators/RecordingMutation.d.ts +0 -36
- package/dist/mutators/RecordingMutation.js +0 -182
- package/dist/mutators/Transaction.d.ts +0 -40
- package/dist/mutators/Transaction.js +0 -58
- package/dist/mutators/UndoManager.d.ts +0 -258
- package/dist/mutators/UndoManager.js +0 -658
- package/dist/mutators/defineMutators.d.ts +0 -60
- package/dist/mutators/defineMutators.js +0 -18
- package/dist/mutators/inverseOp.d.ts +0 -126
- package/dist/mutators/inverseOp.js +0 -71
- package/dist/mutators/mutateActions.d.ts +0 -45
- package/dist/mutators/mutateActions.js +0 -105
- package/dist/mutators/readerActions.d.ts +0 -33
- package/dist/mutators/readerActions.js +0 -57
- package/dist/mutators/undoApply.d.ts +0 -51
- package/dist/mutators/undoApply.js +0 -117
- package/dist/policy/index.d.ts +0 -21
- package/dist/policy/index.js +0 -20
- package/dist/query/QueryProcessor.d.ts +0 -75
- package/dist/query/QueryProcessor.js +0 -255
- package/dist/query/client.d.ts +0 -64
- package/dist/query/client.js +0 -138
- package/dist/query/types.d.ts +0 -85
- package/dist/query/types.js +0 -16
- package/dist/react/AbloProvider.d.ts +0 -242
- package/dist/react/AbloProvider.js +0 -458
- package/dist/react/ClientSideSuspense.d.ts +0 -36
- package/dist/react/ClientSideSuspense.js +0 -17
- package/dist/react/DefaultFallback.d.ts +0 -24
- package/dist/react/DefaultFallback.js +0 -43
- package/dist/react/context.d.ts +0 -55
- package/dist/react/context.js +0 -29
- package/dist/react/createAbloReact.d.ts +0 -56
- package/dist/react/createAbloReact.js +0 -51
- package/dist/react/index.d.ts +0 -62
- package/dist/react/index.js +0 -69
- package/dist/react/internalContext.d.ts +0 -33
- package/dist/react/internalContext.js +0 -3
- package/dist/react/useAblo.d.ts +0 -82
- package/dist/react/useAblo.js +0 -120
- package/dist/react/useCurrentUserId.d.ts +0 -22
- package/dist/react/useCurrentUserId.js +0 -34
- package/dist/react/useErrorListener.d.ts +0 -20
- package/dist/react/useErrorListener.js +0 -38
- package/dist/react/useMutationFailureListener.d.ts +0 -26
- package/dist/react/useMutationFailureListener.js +0 -38
- package/dist/react/useMutators.d.ts +0 -56
- package/dist/react/useMutators.js +0 -84
- package/dist/react/useReactive.d.ts +0 -35
- package/dist/react/useReactive.js +0 -123
- package/dist/react/useSyncStatus.d.ts +0 -59
- package/dist/react/useSyncStatus.js +0 -76
- package/dist/react/useUndoScope.d.ts +0 -34
- package/dist/react/useUndoScope.js +0 -81
- package/dist/schema/coordination.d.ts +0 -112
- package/dist/schema/coordination.js +0 -133
- package/dist/schema/ddl.d.ts +0 -97
- package/dist/schema/ddl.js +0 -491
- package/dist/schema/ddlLock.d.ts +0 -35
- package/dist/schema/ddlLock.js +0 -46
- package/dist/schema/diff.d.ts +0 -225
- package/dist/schema/diff.js +0 -289
- package/dist/schema/generate.d.ts +0 -19
- package/dist/schema/generate.js +0 -86
- package/dist/schema/index.d.ts +0 -42
- package/dist/schema/index.js +0 -80
- package/dist/schema/queries.d.ts +0 -201
- package/dist/schema/queries.js +0 -144
- package/dist/schema/select.d.ts +0 -40
- package/dist/schema/select.js +0 -90
- package/dist/schema/serialize.d.ts +0 -115
- package/dist/schema/serialize.js +0 -265
- package/dist/schema/sugar.d.ts +0 -109
- package/dist/schema/sugar.js +0 -83
- package/dist/schema/syncDeltaRow.d.ts +0 -6
- package/dist/schema/syncDeltaRow.js +0 -6
- package/dist/server/adapter.d.ts +0 -173
- package/dist/server/adapter.js +0 -18
- package/dist/server/commit.d.ts +0 -107
- package/dist/server/commit.js +0 -1
- package/dist/server/index.d.ts +0 -14
- package/dist/server/index.js +0 -2
- package/dist/server/readConfig.d.ts +0 -80
- package/dist/server/readConfig.js +0 -8
- package/dist/server/storageMode.d.ts +0 -23
- package/dist/server/storageMode.js +0 -17
- package/dist/source/adapter.d.ts +0 -83
- package/dist/source/adapter.js +0 -24
- package/dist/source/adapters/drizzle.d.ts +0 -48
- package/dist/source/adapters/drizzle.js +0 -219
- package/dist/source/adapters/kysely.d.ts +0 -42
- package/dist/source/adapters/kysely.js +0 -205
- package/dist/source/adapters/kyselyMutationCore.d.ts +0 -76
- package/dist/source/adapters/kyselyMutationCore.js +0 -125
- package/dist/source/adapters/memory.d.ts +0 -13
- package/dist/source/adapters/memory.js +0 -130
- package/dist/source/adapters/prisma.d.ts +0 -63
- package/dist/source/adapters/prisma.js +0 -202
- package/dist/source/conformance.d.ts +0 -37
- package/dist/source/conformance.js +0 -215
- package/dist/source/connector.d.ts +0 -95
- package/dist/source/connector.js +0 -266
- package/dist/source/connectorProtocol.d.ts +0 -154
- package/dist/source/connectorProtocol.js +0 -163
- package/dist/source/contract.d.ts +0 -195
- package/dist/source/contract.js +0 -164
- package/dist/source/factory.d.ts +0 -92
- package/dist/source/factory.js +0 -286
- package/dist/source/idempotency.d.ts +0 -61
- package/dist/source/idempotency.js +0 -144
- package/dist/source/index.d.ts +0 -23
- package/dist/source/index.js +0 -30
- package/dist/source/migrations.d.ts +0 -21
- package/dist/source/migrations.js +0 -103
- package/dist/source/next.d.ts +0 -32
- package/dist/source/next.js +0 -25
- package/dist/source/pushQueue.d.ts +0 -134
- package/dist/source/pushQueue.js +0 -256
- package/dist/source/signing.d.ts +0 -92
- package/dist/source/signing.js +0 -162
- package/dist/source/types.d.ts +0 -401
- package/dist/source/types.js +0 -59
- package/dist/storeContract.d.ts +0 -145
- package/dist/storeContract.js +0 -12
- package/dist/stores/DatabaseManager.d.ts +0 -107
- package/dist/stores/DatabaseManager.js +0 -388
- package/dist/stores/ObjectStore.d.ts +0 -115
- package/dist/stores/ObjectStore.js +0 -393
- package/dist/stores/ObjectStoreContract.d.ts +0 -38
- package/dist/stores/ObjectStoreContract.js +0 -1
- package/dist/stores/StoreManager.d.ts +0 -114
- package/dist/stores/StoreManager.js +0 -304
- package/dist/stores/SyncActionStore.d.ts +0 -99
- package/dist/stores/SyncActionStore.js +0 -506
- package/dist/stores/openIDBWithTimeout.d.ts +0 -65
- package/dist/stores/openIDBWithTimeout.js +0 -153
- package/dist/stores/syncAction.d.ts +0 -26
- package/dist/stores/syncAction.js +0 -16
- package/dist/surface.d.ts +0 -36
- package/dist/surface.js +0 -75
- package/dist/sync/BootstrapFetcher.d.ts +0 -284
- package/dist/sync/BootstrapFetcher.js +0 -964
- package/dist/sync/ConnectionManager.d.ts +0 -8
- package/dist/sync/ConnectionManager.js +0 -8
- package/dist/sync/OnDemandLoader.d.ts +0 -231
- package/dist/sync/OnDemandLoader.js +0 -743
- package/dist/sync/SubscriptionManager.d.ts +0 -159
- package/dist/sync/SubscriptionManager.js +0 -243
- package/dist/sync/SyncWebSocket.d.ts +0 -173
- package/dist/sync/SyncWebSocket.js +0 -438
- package/dist/sync/awaitClaimGrant.d.ts +0 -6
- package/dist/sync/awaitClaimGrant.js +0 -6
- package/dist/sync/bootstrapApply.d.ts +0 -73
- package/dist/sync/bootstrapApply.js +0 -73
- package/dist/sync/commitFrames.d.ts +0 -8
- package/dist/sync/commitFrames.js +0 -8
- package/dist/sync/contextPorts.d.ts +0 -18
- package/dist/sync/contextPorts.js +0 -31
- package/dist/sync/createClaimStream.d.ts +0 -7
- package/dist/sync/createClaimStream.js +0 -7
- package/dist/sync/createPresenceStream.d.ts +0 -69
- package/dist/sync/createPresenceStream.js +0 -200
- package/dist/sync/createSnapshot.d.ts +0 -29
- package/dist/sync/createSnapshot.js +0 -118
- package/dist/sync/credentialLifecycle.d.ts +0 -7
- package/dist/sync/credentialLifecycle.js +0 -7
- package/dist/sync/deltaPipeline.d.ts +0 -114
- package/dist/sync/deltaPipeline.js +0 -278
- package/dist/sync/groupChange.d.ts +0 -116
- package/dist/sync/groupChange.js +0 -244
- package/dist/sync/participants.d.ts +0 -132
- package/dist/sync/participants.js +0 -346
- package/dist/sync/persistedPrefix.d.ts +0 -12
- package/dist/sync/persistedPrefix.js +0 -22
- package/dist/sync/schemaDrift.d.ts +0 -55
- package/dist/sync/schemaDrift.js +0 -53
- package/dist/sync/schemas.d.ts +0 -71
- package/dist/sync/schemas.js +0 -94
- package/dist/sync/syncCursor.d.ts +0 -40
- package/dist/sync/syncCursor.js +0 -55
- package/dist/sync/syncPlan.d.ts +0 -54
- package/dist/sync/syncPlan.js +0 -50
- package/dist/sync/wsFrameHandlers.d.ts +0 -8
- package/dist/sync/wsFrameHandlers.js +0 -8
- package/dist/syncLog/contract.d.ts +0 -20
- package/dist/syncLog/contract.js +0 -19
- package/dist/syncLog/index.d.ts +0 -1
- package/dist/syncLog/index.js +0 -1
- package/dist/transaction/ablo.d.ts +0 -88
- package/dist/transaction/ablo.js +0 -33
- package/dist/transaction/auth/apiKey.d.ts +0 -152
- package/dist/transaction/auth/apiKey.js +0 -419
- package/dist/transaction/auth/bootstrapScope.d.ts +0 -15
- package/dist/transaction/auth/bootstrapScope.js +0 -1
- package/dist/transaction/auth/capability.d.ts +0 -212
- package/dist/transaction/auth/capability.js +0 -224
- package/dist/transaction/auth/credentialEndpoint.d.ts +0 -61
- package/dist/transaction/auth/credentialEndpoint.js +0 -86
- package/dist/transaction/auth/credentialPolicy.d.ts +0 -148
- package/dist/transaction/auth/credentialPolicy.js +0 -125
- package/dist/transaction/auth/credentialSource.d.ts +0 -30
- package/dist/transaction/auth/credentialSource.js +0 -55
- package/dist/transaction/auth/hostedEndpoints.d.ts +0 -21
- package/dist/transaction/auth/hostedEndpoints.js +0 -21
- package/dist/transaction/auth/identity.d.ts +0 -55
- package/dist/transaction/auth/identity.js +0 -210
- package/dist/transaction/auth/index.d.ts +0 -162
- package/dist/transaction/auth/index.js +0 -304
- package/dist/transaction/auth/schemas.d.ts +0 -59
- package/dist/transaction/auth/schemas.js +0 -85
- package/dist/transaction/auth/sessionMint.d.ts +0 -28
- package/dist/transaction/auth/sessionMint.js +0 -85
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +0 -56
- package/dist/transaction/coordination/awaitClaimGrant.js +0 -124
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +0 -84
- package/dist/transaction/coordination/claimHeartbeatLoop.js +0 -108
- package/dist/transaction/coordination/claimMeta.d.ts +0 -49
- package/dist/transaction/coordination/claimMeta.js +0 -52
- package/dist/transaction/coordination/createClaimStream.d.ts +0 -64
- package/dist/transaction/coordination/createClaimStream.js +0 -475
- package/dist/transaction/coordination/events.d.ts +0 -74
- package/dist/transaction/coordination/events.js +0 -7
- package/dist/transaction/coordination/index.d.ts +0 -19
- package/dist/transaction/coordination/index.js +0 -45
- package/dist/transaction/coordination/locator.d.ts +0 -104
- package/dist/transaction/coordination/locator.js +0 -102
- package/dist/transaction/coordination/schema.d.ts +0 -1536
- package/dist/transaction/coordination/schema.js +0 -1177
- package/dist/transaction/coordination/targetConflict.d.ts +0 -2
- package/dist/transaction/coordination/targetConflict.js +0 -107
- package/dist/transaction/coordination/trace.d.ts +0 -78
- package/dist/transaction/coordination/trace.js +0 -138
- package/dist/transaction/durableWrites.d.ts +0 -62
- package/dist/transaction/durableWrites.js +0 -71
- package/dist/transaction/environment.d.ts +0 -105
- package/dist/transaction/environment.js +0 -108
- package/dist/transaction/errorCodes.d.ts +0 -403
- package/dist/transaction/errorCodes.js +0 -484
- package/dist/transaction/errors.d.ts +0 -428
- package/dist/transaction/errors.js +0 -686
- package/dist/transaction/footprint.d.ts +0 -111
- package/dist/transaction/footprint.js +0 -0
- package/dist/transaction/index.d.ts +0 -20
- package/dist/transaction/index.js +0 -20
- package/dist/transaction/keys/index.d.ts +0 -87
- package/dist/transaction/keys/index.js +0 -207
- package/dist/transaction/log/syncDeltaRow.d.ts +0 -158
- package/dist/transaction/log/syncDeltaRow.js +0 -95
- package/dist/transaction/logPosition.d.ts +0 -97
- package/dist/transaction/logPosition.js +0 -125
- package/dist/transaction/logger.d.ts +0 -16
- package/dist/transaction/logger.js +0 -7
- package/dist/transaction/observability.d.ts +0 -53
- package/dist/transaction/observability.js +0 -19
- package/dist/transaction/persistence.d.ts +0 -12
- package/dist/transaction/persistence.js +0 -11
- package/dist/transaction/plugin.d.ts +0 -285
- package/dist/transaction/plugin.js +0 -106
- package/dist/transaction/policy/types.d.ts +0 -217
- package/dist/transaction/policy/types.js +0 -126
- package/dist/transaction/resources/functionalUpdate.d.ts +0 -79
- package/dist/transaction/resources/functionalUpdate.js +0 -87
- package/dist/transaction/resources/httpResources.d.ts +0 -321
- package/dist/transaction/resources/httpResources.js +0 -7
- package/dist/transaction/resources/modelOperations.d.ts +0 -427
- package/dist/transaction/resources/modelOperations.js +0 -12
- package/dist/transaction/resources/mutationOptions.d.ts +0 -66
- package/dist/transaction/resources/mutationOptions.js +0 -9
- package/dist/transaction/resources/where.d.ts +0 -101
- package/dist/transaction/resources/where.js +0 -115
- package/dist/transaction/resources/writeOptionsSchema.d.ts +0 -47
- package/dist/transaction/resources/writeOptionsSchema.js +0 -73
- package/dist/transaction/schema/field.d.ts +0 -120
- package/dist/transaction/schema/field.js +0 -265
- package/dist/transaction/schema/fieldRef.d.ts +0 -38
- package/dist/transaction/schema/fieldRef.js +0 -11
- package/dist/transaction/schema/loadStrategy.d.ts +0 -45
- package/dist/transaction/schema/loadStrategy.js +0 -46
- package/dist/transaction/schema/model.d.ts +0 -379
- package/dist/transaction/schema/model.js +0 -123
- package/dist/transaction/schema/openapi.d.ts +0 -58
- package/dist/transaction/schema/openapi.js +0 -501
- package/dist/transaction/schema/relation.d.ts +0 -204
- package/dist/transaction/schema/relation.js +0 -104
- package/dist/transaction/schema/residency.d.ts +0 -29
- package/dist/transaction/schema/residency.js +0 -25
- package/dist/transaction/schema/roles.d.ts +0 -249
- package/dist/transaction/schema/roles.js +0 -230
- package/dist/transaction/schema/schema.d.ts +0 -351
- package/dist/transaction/schema/schema.js +0 -325
- package/dist/transaction/schema/tenancy.d.ts +0 -139
- package/dist/transaction/schema/tenancy.js +0 -190
- package/dist/transaction/transactionLayer.d.ts +0 -82
- package/dist/transaction/transactionLayer.js +0 -24
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +0 -143
- package/dist/transaction/transactions/settlement/commitEnvelope.js +0 -161
- package/dist/transaction/transactions/settlement/httpCommitEnvelope.d.ts +0 -53
- package/dist/transaction/transactions/settlement/httpCommitEnvelope.js +0 -207
- package/dist/transaction/transactions/settlement/idempotencyKey.d.ts +0 -10
- package/dist/transaction/transactions/settlement/idempotencyKey.js +0 -9
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +0 -112
- package/dist/transaction/transactions/settlement/pendingWrite.js +0 -20
- package/dist/transaction/transport/commitFrames.d.ts +0 -90
- package/dist/transaction/transport/commitFrames.js +0 -134
- package/dist/transaction/transport/connectionManager.d.ts +0 -215
- package/dist/transaction/transport/connectionManager.js +0 -673
- package/dist/transaction/transport/credentialLifecycle.d.ts +0 -177
- package/dist/transaction/transport/credentialLifecycle.js +0 -324
- package/dist/transaction/transport/heartbeat.d.ts +0 -65
- package/dist/transaction/transport/heartbeat.js +0 -93
- package/dist/transaction/transport/httpClient.d.ts +0 -131
- package/dist/transaction/transport/httpClient.js +0 -146
- package/dist/transaction/transport/httpOptions.d.ts +0 -33
- package/dist/transaction/transport/httpOptions.js +0 -12
- package/dist/transaction/transport/httpTransport.d.ts +0 -8
- package/dist/transaction/transport/httpTransport.js +0 -1388
- package/dist/transaction/transport/networkProbe.d.ts +0 -84
- package/dist/transaction/transport/networkProbe.js +0 -207
- package/dist/transaction/transport/wsFrameHandlers.d.ts +0 -128
- package/dist/transaction/transport/wsFrameHandlers.js +0 -429
- package/dist/transaction/transport/wsTransport.d.ts +0 -574
- package/dist/transaction/transport/wsTransport.js +0 -1023
- package/dist/transaction/types/assertExact.d.ts +0 -17
- package/dist/transaction/types/assertExact.js +0 -1
- package/dist/transaction/types/global.d.ts +0 -107
- package/dist/transaction/types/global.js +0 -40
- package/dist/transaction/types/index.d.ts +0 -205
- package/dist/transaction/types/index.js +0 -56
- package/dist/transaction/types/modelData.d.ts +0 -10
- package/dist/transaction/types/modelData.js +0 -9
- package/dist/transaction/types/participant.d.ts +0 -20
- package/dist/transaction/types/participant.js +0 -10
- package/dist/transaction/types/streams.d.ts +0 -550
- package/dist/transaction/types/streams.js +0 -11
- package/dist/transaction/utils/asyncIterator.d.ts +0 -34
- package/dist/transaction/utils/asyncIterator.js +0 -135
- package/dist/transaction/utils/duration.d.ts +0 -50
- package/dist/transaction/utils/duration.js +0 -77
- package/dist/transaction/utils/json.d.ts +0 -57
- package/dist/transaction/utils/json.js +0 -276
- package/dist/transaction/wire/accountResponses.d.ts +0 -420
- package/dist/transaction/wire/accountResponses.js +0 -290
- package/dist/transaction/wire/auth.d.ts +0 -56
- package/dist/transaction/wire/auth.js +0 -63
- package/dist/transaction/wire/bootstrapReason.d.ts +0 -9
- package/dist/transaction/wire/bootstrapReason.js +0 -8
- package/dist/transaction/wire/claimEvent.d.ts +0 -76
- package/dist/transaction/wire/claimEvent.js +0 -73
- package/dist/transaction/wire/claims.d.ts +0 -530
- package/dist/transaction/wire/claims.js +0 -327
- package/dist/transaction/wire/commit.d.ts +0 -603
- package/dist/transaction/wire/commit.js +0 -321
- package/dist/transaction/wire/delta.d.ts +0 -250
- package/dist/transaction/wire/delta.js +0 -147
- package/dist/transaction/wire/errorEnvelope.d.ts +0 -72
- package/dist/transaction/wire/errorEnvelope.js +0 -123
- package/dist/transaction/wire/feedCursor.d.ts +0 -60
- package/dist/transaction/wire/feedCursor.js +0 -82
- package/dist/transaction/wire/feedEvent.d.ts +0 -204
- package/dist/transaction/wire/feedEvent.js +0 -65
- package/dist/transaction/wire/frames.d.ts +0 -194
- package/dist/transaction/wire/frames.js +0 -50
- package/dist/transaction/wire/inboundFrames.d.ts +0 -562
- package/dist/transaction/wire/inboundFrames.js +0 -116
- package/dist/transaction/wire/index.d.ts +0 -54
- package/dist/transaction/wire/index.js +0 -83
- package/dist/transaction/wire/listEnvelope.d.ts +0 -37
- package/dist/transaction/wire/listEnvelope.js +0 -42
- package/dist/transaction/wire/modelMutations.d.ts +0 -31
- package/dist/transaction/wire/modelMutations.js +0 -52
- package/dist/transaction/wire/modelResponses.d.ts +0 -85
- package/dist/transaction/wire/modelResponses.js +0 -43
- package/dist/transaction/wire/modelShape.d.ts +0 -78
- package/dist/transaction/wire/modelShape.js +0 -74
- package/dist/transaction/wire/protocol.d.ts +0 -38
- package/dist/transaction/wire/protocol.js +0 -38
- package/dist/transaction/wire/protocolVersion.d.ts +0 -73
- package/dist/transaction/wire/protocolVersion.js +0 -83
- package/dist/transactions/mutations/MutationQueue.d.ts +0 -661
- package/dist/transactions/mutations/MutationQueue.js +0 -2807
- package/dist/transactions/mutations/MutationStore.d.ts +0 -20
- package/dist/transactions/mutations/MutationStore.js +0 -53
- package/dist/transactions/mutations/UnconfirmedWrites.d.ts +0 -82
- package/dist/transactions/mutations/UnconfirmedWrites.js +0 -104
- package/dist/transactions/mutations/coalesceRules.d.ts +0 -58
- package/dist/transactions/mutations/coalesceRules.js +0 -140
- package/dist/transactions/mutations/commitLatency.d.ts +0 -52
- package/dist/transactions/mutations/commitLatency.js +0 -130
- package/dist/transactions/mutations/commitOutboxStore.d.ts +0 -28
- package/dist/transactions/mutations/commitOutboxStore.js +0 -26
- package/dist/transactions/mutations/commitPayload.d.ts +0 -165
- package/dist/transactions/mutations/commitPayload.js +0 -152
- package/dist/transactions/mutations/deltaConfirmation.d.ts +0 -63
- package/dist/transactions/mutations/deltaConfirmation.js +0 -235
- package/dist/transactions/mutations/durableWriteStore.d.ts +0 -14
- package/dist/transactions/mutations/durableWriteStore.js +0 -12
- package/dist/transactions/mutations/optimisticApply.d.ts +0 -49
- package/dist/transactions/mutations/optimisticApply.js +0 -65
- package/dist/transactions/mutations/replayValidation.d.ts +0 -187
- package/dist/transactions/mutations/replayValidation.js +0 -164
- package/dist/utils/mobxSetup.d.ts +0 -53
- package/dist/utils/mobxSetup.js +0 -330
- package/dist/views/QueryView.d.ts +0 -79
- package/dist/views/QueryView.js +0 -218
- package/dist/views/ViewRegistry.d.ts +0 -20
- package/dist/views/ViewRegistry.js +0 -55
- package/dist/views/incrementalView.d.ts +0 -45
- package/dist/views/incrementalView.js +0 -69
- package/dist/webhooks/events.d.ts +0 -43
- package/dist/webhooks/events.js +0 -42
- package/dist/webhooks/index.d.ts +0 -8
- package/dist/webhooks/index.js +0 -8
- package/dist/wire/index.d.ts +0 -1
- package/dist/wire/index.js +0 -8
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Per-Field Conflict Detection (Track A)
|
|
2
|
+
|
|
3
|
+
Maintainer decision doc. Scopes the move from entity-level to field-level stale
|
|
4
|
+
detection in `executeCommit`. Library-free; restores Linear parity for the
|
|
5
|
+
disjoint-field case while keeping the agent-specific `readAt` reject.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`executeCommit` Step 0 (`apps/sync-server/src/mutators/commit.ts`) detects stale
|
|
10
|
+
writes at **entity granularity**:
|
|
11
|
+
|
|
12
|
+
```sql
|
|
13
|
+
SELECT MAX(id) FROM sync_deltas WHERE model_name = ? AND model_id = ?
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If `observed > op.readAt`, the op conflicts. This means two writers touching
|
|
17
|
+
**different fields** of the same row collide falsely: agent A sets
|
|
18
|
+
`report.status`, human B sets `report.reviewer`, B carried a `readAt` from before A's
|
|
19
|
+
write → B is rejected with `AbloStaleContextError`, even though the edits never
|
|
20
|
+
overlapped.
|
|
21
|
+
|
|
22
|
+
This is an over-rejection. It is also stricter than the system we
|
|
23
|
+
reverse-engineered from (Linear), which never had this problem.
|
|
24
|
+
|
|
25
|
+
## Why Linear says this is right
|
|
26
|
+
|
|
27
|
+
The model is reverse-engineered from Linear's sync engine. Linear's design
|
|
28
|
+
confirms every load-bearing choice here:
|
|
29
|
+
|
|
30
|
+
- **Transactions are property-level.** Linear's `UpdateTransaction` records
|
|
31
|
+
"the name of the changed property and its previous value" — it carries only
|
|
32
|
+
the changed properties, not a whole-object snapshot. Our `changed_fields`
|
|
33
|
+
column re-derives exactly that.
|
|
34
|
+
- **Resolution is last-writer-wins, per property, by total order:** `syncId`
|
|
35
|
+
(our `sync_id_seq`) is the total order. A partial transaction applies only its
|
|
36
|
+
properties, so two clients editing **different** properties both win — LWW
|
|
37
|
+
only bites on the **same** property.
|
|
38
|
+
- **CRDT is used only for issue descriptions.** Linear keeps LWW-per-property
|
|
39
|
+
for structured fields and reserves a CRDT for the one rich-text body. That is
|
|
40
|
+
the same Track A / Track B line we draw: this doc is Track A; rich-text bodies
|
|
41
|
+
(TipTap `content_json`) are out of scope and belong to a separate CRDT track.
|
|
42
|
+
|
|
43
|
+
So Track A is not a new feature — it **restores Linear parity** at the property
|
|
44
|
+
level we had flattened to entity level.
|
|
45
|
+
|
|
46
|
+
Sources: [reverse-linear-sync-engine (CTO-endorsed)](https://github.com/wzhudev/reverse-linear-sync-engine/blob/main/SUMMARY.md),
|
|
47
|
+
[Architectures for Central Server Collaboration — Weidner](https://mattweidner.com/2024/06/04/server-architectures.html).
|
|
48
|
+
|
|
49
|
+
## The constraint that shapes the design
|
|
50
|
+
|
|
51
|
+
`sync_deltas.data` stores the **full post-update row**, not the changed columns.
|
|
52
|
+
This was a deliberate change (see `feedback_partial_update_delta_ui_drift`) so
|
|
53
|
+
the live-pool update path fires MobX reactivity for nested fields. Consequence:
|
|
54
|
+
we **cannot** recover "which fields did this delta change" from `data` — a
|
|
55
|
+
full-row snapshot does not tell you what moved.
|
|
56
|
+
|
|
57
|
+
We do have the changed set for free at write time: `Object.keys(snakeInput)` at
|
|
58
|
+
`commit.ts` UPDATE branch, after the unknown-column strip and before the
|
|
59
|
+
`updated_at` injection. So this is a write-side capture + a read-side
|
|
60
|
+
intersection, not a diff-the-snapshots problem.
|
|
61
|
+
|
|
62
|
+
## Design
|
|
63
|
+
|
|
64
|
+
### 1. Schema: `sync_deltas.changed_fields text[]` (nullable)
|
|
65
|
+
|
|
66
|
+
- Populate **only for UPDATE** with the real changed columns
|
|
67
|
+
(`Object.keys(snakeInput)` after strip, before `updated_at`).
|
|
68
|
+
- Leave `null` for CREATE / DELETE / ARCHIVE / UNARCHIVE.
|
|
69
|
+
|
|
70
|
+
`null` is semantically "whole-entity change" and always conflicts. This gives a
|
|
71
|
+
**safe migration**: every pre-migration delta is `null`, so detection falls back
|
|
72
|
+
to exactly today's entity-level behavior. Field granularity phases in only as
|
|
73
|
+
new deltas land — no risky backfill.
|
|
74
|
+
|
|
75
|
+
We store **field names only**, not previous values. LWW needs no value
|
|
76
|
+
comparison (latest `sync_id` wins); names are sufficient for the overlap check
|
|
77
|
+
that drives the optional reject. Prev-values would only matter for
|
|
78
|
+
"same field, same value ⇒ not a conflict" tie-breaking — defer it.
|
|
79
|
+
|
|
80
|
+
### 2. Detection rewrite: Step 0 (`commit.ts`)
|
|
81
|
+
|
|
82
|
+
Replace the scalar `MAX(id)` with a field-aware scan:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// op's own field set (snake-cased, framework cols excluded)
|
|
86
|
+
const opFields = new Set(Object.keys(op.input ?? {}).map(toSnakeCase));
|
|
87
|
+
|
|
88
|
+
const rows = await tx.unsafe(
|
|
89
|
+
`SELECT id, changed_fields FROM sync_deltas
|
|
90
|
+
WHERE model_name = $1 AND model_id = $2 AND id > $3
|
|
91
|
+
ORDER BY id DESC`,
|
|
92
|
+
[mapping.modelName, op.id, op.readAt] as never[],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Conflict iff a newer delta touched a field this op also writes,
|
|
96
|
+
// OR a newer delta is whole-entity (changed_fields IS NULL → CREATE/DELETE).
|
|
97
|
+
const overlap = rows.find(
|
|
98
|
+
(r) => r.changedFields === null || r.changedFields.some((f) => opFields.has(f)),
|
|
99
|
+
);
|
|
100
|
+
if (overlap) {
|
|
101
|
+
conflicts.push({
|
|
102
|
+
/* ...existing fields... */
|
|
103
|
+
observedSyncId: overlap.id,
|
|
104
|
+
conflictingFields: intersect(overlap.changedFields, opFields),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Disjoint-field concurrent writes now produce **no conflict** — they both apply.
|
|
110
|
+
That is LWW-per-field achieved by *not rejecting*; no merge code.
|
|
111
|
+
|
|
112
|
+
### 3. Policy type: additive (`packages/transaction/src/policy/types.ts`)
|
|
113
|
+
|
|
114
|
+
Extend `StaleContextConflict` with:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
readonly conflictingFields?: readonly string[];
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pure addition. `defaultPolicy` still rejects; existing policies compile
|
|
121
|
+
unchanged. A policy can now reason at field granularity, e.g. allow when the
|
|
122
|
+
only conflicting field is cosmetic.
|
|
123
|
+
|
|
124
|
+
### 4. Scope boundary (honest)
|
|
125
|
+
|
|
126
|
+
Granularity is **column-level**, not JSON-path. Two writers editing different
|
|
127
|
+
keys *inside* one `content_json` column still conflict — that is the rich-text
|
|
128
|
+
case (Track B / CRDT), not this. JSON Merge Patch (RFC 7386) sub-column
|
|
129
|
+
granularity is a later refinement on the same column; v1 stops at columns.
|
|
130
|
+
|
|
131
|
+
## Relationship to the existing `readAt` reject
|
|
132
|
+
|
|
133
|
+
Linear is pure LWW-per-property with no stale check. We keep `readAt` /
|
|
134
|
+
`onStale: 'reject'` as an **opt-in** for the agent-reasoned-against-stale-state
|
|
135
|
+
case (an LLM that read a stale value and reasoned on it is a real failure mode
|
|
136
|
+
humans rarely hit). After Track A:
|
|
137
|
+
|
|
138
|
+
- **No `readAt`** → LWW-per-field, Linear parity: disjoint fields never conflict.
|
|
139
|
+
- **`readAt` set** → reject only if a newer delta touched a field this op also
|
|
140
|
+
writes. The `changed_fields` column makes that intersection computable.
|
|
141
|
+
- **`onStale: 'overwrite'`** → unchanged; still skips detection entirely.
|
|
142
|
+
|
|
143
|
+
## Index
|
|
144
|
+
|
|
145
|
+
Verify a `(model_name, model_id, id)` index exists on `sync_deltas` (the old
|
|
146
|
+
`MAX(id)` relied on it too). The new query is a bounded range scan (`id >
|
|
147
|
+
readAt`, usually a small recent window) on the same index prefix.
|
|
148
|
+
|
|
149
|
+
## Tests (vitest, sync-server)
|
|
150
|
+
|
|
151
|
+
- Disjoint fields (A: `status`, B: `assignee`, same row, both stale `readAt`) →
|
|
152
|
+
**both commit, no `AbloStaleContextError`** — the regression that proves the win.
|
|
153
|
+
- Same field, both stale → still rejects (default policy unchanged).
|
|
154
|
+
- DELETE after `readAt` → conflicts regardless of op fields (`null`).
|
|
155
|
+
- Pre-migration delta (`null`) in the window → conflicts (back-compat).
|
|
156
|
+
- `onStale: 'overwrite'` → still skips detection.
|
|
157
|
+
|
|
158
|
+
## Touch list
|
|
159
|
+
|
|
160
|
+
- `sync_deltas` migration — add `changed_fields text[]`.
|
|
161
|
+
- `commit.ts` — Step 0 detect rewrite + UPDATE write path populates
|
|
162
|
+
`changed_fields` + `deltaInfos` shape.
|
|
163
|
+
- `apps/sync-server/src/db/deltas.ts` — insert path carries `changed_fields`.
|
|
164
|
+
- `packages/transaction/src/policy/types.ts` — additive `conflictingFields`.
|
|
165
|
+
- vitest in sync-server.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Postgres replication: internal architecture
|
|
2
|
+
|
|
3
|
+
> **Status: wired and covered by unit and real-Postgres journeys.** This is server-internal code under `apps/sync-server/src/replication/postgres/`; it is not an SDK surface.
|
|
4
|
+
|
|
5
|
+
## Why this exists
|
|
6
|
+
|
|
7
|
+
Ablo observes a customer's Postgres through a publication and logical-replication slot. The customer owns the schema and write path. Ablo decodes committed changes, appends them to its control-plane log, and serves sync from that log. The low-level decoder and lifecycle draw on Zero and PowerSync patterns; ADR 0002 governs the product boundary.
|
|
8
|
+
|
|
9
|
+
## The one job
|
|
10
|
+
|
|
11
|
+
**Postgres `pgoutput` messages → `PreparedDelta[]` + a confirmed LSN.** The consumer writes through `appendExternalDeltas`; deltas land in the control-plane `sync_deltas` log and use the normal fan-out pipeline.
|
|
12
|
+
|
|
13
|
+
## Module map (`apps/sync-server/src/replication/postgres/`)
|
|
14
|
+
|
|
15
|
+
| file | role | provenance |
|
|
16
|
+
| -------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------- |
|
|
17
|
+
| `binaryReader.ts` | big-endian protocol reader | modeled on Zero |
|
|
18
|
+
| `pgoutputTypes.ts`, `pgoutput.ts` | typed `pgoutput` messages and decoder | modeled on Zero |
|
|
19
|
+
| `lsn.ts` | `LSN` string ↔ `bigint` (`toBigInt`/`fromBigInt`) | ported subset ← Zero `lsn.ts` |
|
|
20
|
+
| `connection.ts`, `stream.ts`, `streamAdapter.ts` | dedicated query/replication connections and stream adapter | Zero/PowerSync patterns |
|
|
21
|
+
| `assembler.ts` | buffers a transaction and maps changes to `PreparedDelta` | Ablo adapter |
|
|
22
|
+
| `consumer.ts` | persist-before-ack consume loop with retry | PowerSync/Ablo patterns |
|
|
23
|
+
| `slot.ts`, `slotLease.ts`, `backfill.ts`, `watermark.ts` | slot ownership, initial snapshot, and durable progress | Ablo |
|
|
24
|
+
| `sources.ts`, `fleet.ts`, `start.ts` | registry resolution, reconciliation, start/stop lifecycle | Ablo |
|
|
25
|
+
| `preflight.ts`, `readiness.ts`, `publicationDrift.ts` | registration checks and runtime diagnostics | Ablo |
|
|
26
|
+
|
|
27
|
+
## Data flow
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
registered Postgres source
|
|
31
|
+
→ replication slot + initial snapshot
|
|
32
|
+
→ pgoutput stream
|
|
33
|
+
→ TransactionAssembler
|
|
34
|
+
→ WalConsumer
|
|
35
|
+
→ appendExternalDeltas(controlSql, deltas, context)
|
|
36
|
+
→ persist watermark
|
|
37
|
+
→ acknowledge commit LSN
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Mapping (in `TransactionAssembler`, mirrors `events.ts:eventsToDeltas`)
|
|
41
|
+
|
|
42
|
+
- `actionType`: `insert→'I'`, `update→'U'`, `delete→'D'` (the 1:1 pgoutput↔Ablo coincidence).
|
|
43
|
+
- `modelName`: `mapping.tableToModel(schema, table)` — `null` skips the change.
|
|
44
|
+
- `modelId`: `mapping.rowToModelId(key)` over the replica-identity key.
|
|
45
|
+
- `data`: the row bound as an **object, never pre-stringified** (the jsonb double-encode trap at `deltaAppend.ts`).
|
|
46
|
+
- `transactionId`: `String(xid)`.
|
|
47
|
+
|
|
48
|
+
## Load-bearing invariants
|
|
49
|
+
|
|
50
|
+
- **Persist-before-ack** (`WalConsumer`): `appendExternalDeltas` and the watermark transaction resolve before `ack(commitLsn)`. A crash between them replays work instead of losing it.
|
|
51
|
+
- **Keepalive watermark** (`streamAdapter.ts`, `stream.ts`): every reply carries the last confirmed LSN, never the server's live position — the timed status update included.
|
|
52
|
+
- **Liveness off the socket** (`stream.ts`): a status update goes out every 75% of the upstream's `wal_sender_timeout` whether or not the consumer is reading, so backpressure cannot get the connection terminated for silence; inbound silence on a stream we are reading for twice that long destroys it and falls into the per-source backoff. A `wal_sender_timeout` of 0 runs untimed.
|
|
53
|
+
- **Failover-capable slot** (`slot.ts`): from PostgreSQL 17 the slot is created with `FAILOVER true`, so a customer failover leaves our position intact instead of forcing the re-snapshot path. It only takes effect where the standby has `sync_replication_slots = on`, which the preflight recommends and never requires.
|
|
54
|
+
- **Fresh subscription per retry** (`WalConsumer`): every backoff iteration opens a new subscription so a half-dead socket / stale relation cache never carries into the retry.
|
|
55
|
+
- **Per-source isolation** (`start.ts`, `fleet.ts`): one broken source reports and retries without stopping healthy sources.
|
|
56
|
+
- **Runtime reconciliation** (`fleet.ts`): registrations, removals, schema changes, and secret rotations converge without a server restart.
|
|
57
|
+
|
|
58
|
+
## Tests
|
|
59
|
+
|
|
60
|
+
Unit tests live beside the implementation in `replication/postgres/__tests__`. Real-Postgres coverage is grouped under `src/__journeys__/postgres-replication-*.journey.test.ts`: registration, registry migration, backfill, live streaming, source changes, bootstrap, query serving, read cutover, and customer-database isolation.
|
|
61
|
+
|
|
62
|
+
## Operations
|
|
63
|
+
|
|
64
|
+
Registration is the enable signal. `startPostgresReplication` starts the fleet after the server begins listening; `postgresReplicationReady` is drained during graceful shutdown. Use `docs/runbooks/connect-customer-database-postgres-replication.md` for source setup and live verification.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# A `Schema` is serializable
|
|
2
|
+
|
|
3
|
+
A `Schema` (output of `defineSchema`) is JSON-serializable except for two
|
|
4
|
+
things, both of which are client-only:
|
|
5
|
+
|
|
6
|
+
- **Zod validators:** `model().schema` / `.shape`, `Schema.validators`. Used
|
|
7
|
+
by the client for type inference + validation. The server never reads them
|
|
8
|
+
(it checks `information_schema.columns` and does no field-shape validation in
|
|
9
|
+
the commit path).
|
|
10
|
+
|
|
11
|
+
Everything the server reads — `typename`, `tableName`, `mutable`, `load`, the
|
|
12
|
+
canonical `tenancy` descriptor (the `policy` authoring option is normalized away
|
|
13
|
+
at build), bootstrap hints, `relations` (`foreignKeyColumn`), field names, and
|
|
14
|
+
`identityRoles` — is plain data.
|
|
15
|
+
|
|
16
|
+
## Identity roles are pure data
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface IdentityRole {
|
|
20
|
+
kind: string;
|
|
21
|
+
template: string; // 'org:{id}'
|
|
22
|
+
source: IdentityRoleSource; // { field: 'organizationId', multi: false }
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The runtime behaviour lives in `extractIdentityIds(identity, source)`, a pure
|
|
27
|
+
function `composeIdentitySyncGroups` calls once per role. `identityRole({ kind,
|
|
28
|
+
template, source, multi? })` is the factory. Absent/falsy fields yield `[]`, so
|
|
29
|
+
a role whose field isn't present (a user with no `teamIds`) is a silent no-op —
|
|
30
|
+
org-only, org+user, and org+team are just different `identityRoles` arrays, not
|
|
31
|
+
different code paths. The engine ships zero prefixes; `org:`/`user:`/`team:`
|
|
32
|
+
live only in `ablo.schema.ts`.
|
|
33
|
+
|
|
34
|
+
## Why this matters
|
|
35
|
+
|
|
36
|
+
Because a `Schema` carries no closures, the same object works in-process and,
|
|
37
|
+
for a hosted multi-tenant server, after being reconstructed from JSON over the
|
|
38
|
+
control plane (the GraphQL `printSchema` / `buildSchema` model). One type,
|
|
39
|
+
both places — no separate server-side schema type.
|
|
40
|
+
|
|
41
|
+
`apps/sync-server` reads the live `schema` directly today
|
|
42
|
+
(`buildModelMap(schema)`, `composeIdentitySyncGroups` via the `@ablo/schema`
|
|
43
|
+
wrapper).
|
|
44
|
+
|
|
45
|
+
## Trust boundary
|
|
46
|
+
|
|
47
|
+
Never trust a client-connection schema for authz (Zero/Convex/Instant). A
|
|
48
|
+
client connection may carry only the schema **version** for compatibility
|
|
49
|
+
gating; the authoritative `Schema` arrives over an authenticated control-plane
|
|
50
|
+
path. The identity passed to `composeIdentitySyncGroups` is server-resolved
|
|
51
|
+
trusted claims.
|
|
52
|
+
|
|
53
|
+
## Wire form (`serialize.ts`)
|
|
54
|
+
|
|
55
|
+
`serializeSchema(schema): string` / `parseSchema(json): Schema` are the
|
|
56
|
+
control-plane transport — the GraphQL `printSchema`/`buildSchema` model. The
|
|
57
|
+
JSON (`SchemaJSON`, envelope `{ v, models, identityRoles }`) carries every
|
|
58
|
+
model's routing/scoping metadata, relations (incl. resolved
|
|
59
|
+
`foreignKeyColumn`), field metadata, and identity roles. `parseSchema` rebuilds
|
|
60
|
+
each model's Zod permissively from `FieldMeta` (the server does no field-shape
|
|
61
|
+
validation) and drops `computed` closures. `schemaHash(schema)` is the stable
|
|
62
|
+
FNV-1a content hash used for connect-time gating. Round-trip tested in
|
|
63
|
+
`__tests__/serialize.test.ts`.
|
|
64
|
+
|
|
65
|
+
## Storage + runtime resolution (`apps/sync-server/src/schema/`): built
|
|
66
|
+
|
|
67
|
+
- **`ablo_schemas` table** (`packages/database/prisma/models/sync.prisma`,
|
|
68
|
+
`SchemaArtifact`) — `(organizationId, version, schemaJson, schemaHash, state,
|
|
69
|
+
error, createdBy, createdAt, activatedAt)`, unique `(orgId, version)`. State
|
|
70
|
+
`pending|validated|active|overwritten|failed`, ≤1 active per tenant (Convex
|
|
71
|
+
`_schemas` machine; Zero's "row in the operational DB"). *Migration written,
|
|
72
|
+
not applied — 0 users, Neon direct-endpoint rule.*
|
|
73
|
+
- **`pgSchemaStore` / `memorySchemaStore`** (`schemaStore.ts`) — mirrors
|
|
74
|
+
`pgApiKeyStore`. `insertPending` assigns `MAX(version)+1`; `activate` is a
|
|
75
|
+
transaction that demotes the current active → `overwritten` then promotes the
|
|
76
|
+
target. State-machine invariants tested.
|
|
77
|
+
- **`createSchemaRegistry(store)`** (`schemaRegistry.ts`) — `load(orgId)` parses
|
|
78
|
+
the active artifact's `schemaJson` to a `Schema` and caches it (shared
|
|
79
|
+
in-flight promise across concurrent cold loads); `invalidate(orgId)` busts it
|
|
80
|
+
on activation (Convex `schema_registry`). This is the seam that turns the
|
|
81
|
+
boot-time `import { schema }` into per-tenant runtime resolution.
|
|
82
|
+
|
|
83
|
+
## Push route (`apps/sync-server/src/routes/schema.ts`): built
|
|
84
|
+
|
|
85
|
+
`POST /api/schema`, mounted in `index.ts` (`schemaRoutes({ provider, store,
|
|
86
|
+
registry })`). Auth: secret `sk_` key carrying the `schema:push` scope —
|
|
87
|
+
`Identity.scopes` was added and `apiKeyProvider` now populates it from the key
|
|
88
|
+
row's `scopes` column (restricted `rk_` keys get no `scopes`, so they're
|
|
89
|
+
excluded). Tenant comes from `identity.organizationId`, never the body. Flow:
|
|
90
|
+
read `{ schema, force? }` → validate via `parseSchema` (throws → 400) →
|
|
91
|
+
authoritative hash via `schemaHash(parsed)` → reject removed-model changes (409)
|
|
92
|
+
unless `force` → no-op fast path on identical hash (200) → `insertPending` →
|
|
93
|
+
`activate` → `registry.invalidate(org)` → 201 `{ schemaId, version, hash }`. 6
|
|
94
|
+
route tests.
|
|
95
|
+
|
|
96
|
+
## CLI (`packages/ablo-cli`): built
|
|
97
|
+
|
|
98
|
+
`ablo push` (`src/push.ts`, dispatched from `index.ts`). Imports the
|
|
99
|
+
user's `sync/schema.ts` at runtime via tsx's `tsImport` (the real object —
|
|
100
|
+
`migrate`'s regex parse can't produce a faithful AST), then `serializeSchema`
|
|
101
|
+
+ `schemaHash` and POSTs `{ schema, force, renames }` to `POST /api/schema`
|
|
102
|
+
with `Authorization: Bearer $ABLO_API_KEY`. Flags: `--schema`, `--export`,
|
|
103
|
+
`--url` (`$ABLO_API_URL`, default `https://api.abloatai.com`), `--force`,
|
|
104
|
+
`--rename old:new` (repeatable). The route honors `renames` so a renamed model
|
|
105
|
+
isn't flagged as a removed-model incompatibility. `parsePushArgs` unit-tested.
|
|
106
|
+
|
|
107
|
+
## Schema drift is advisory
|
|
108
|
+
|
|
109
|
+
Bootstrap includes the tenant's active schema hash. The client compares it to
|
|
110
|
+
its built-in hash and warns once when they differ. Hash drift never closes the
|
|
111
|
+
WebSocket: an additive rollout must allow old and new clients to overlap while
|
|
112
|
+
data is expanded, dual-read/written, backfilled, verified, and finally
|
|
113
|
+
contracted. Breaking wire shapes use the protocol-version codec registry
|
|
114
|
+
instead.
|
|
115
|
+
|
|
116
|
+
## Not built yet
|
|
117
|
+
- **Switch the hot paths to per-tenant `registry.load(org)`:** boot still does
|
|
118
|
+
the single-tenant `import { schema }`; `buildModelMap`/bootstrap/commit
|
|
119
|
+
reading the registry per request is the final multi-tenant wiring.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Repository Structure
|
|
2
|
+
|
|
3
|
+
The public repository preserves the same ownership boundaries as the main
|
|
4
|
+
monorepo. `@abloatai/ablo` is the product package; the packages beneath it are
|
|
5
|
+
implementation owners and first-party extension surfaces.
|
|
6
|
+
|
|
7
|
+
| Workspace | Responsibility |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `packages/ablo` | Branded SDK, public entrypoints, docs, examples, release assets |
|
|
10
|
+
| `packages/transaction` | Headless HTTP client, canonical contracts, reads, commits, settlement, claims, durable observation |
|
|
11
|
+
| `packages/humans` | Reactive materializer, WebSocket transport, presence, browser persistence, React |
|
|
12
|
+
| `packages/agent` | Agent behavior, perception, and coordination helpers |
|
|
13
|
+
| `packages/cli` | Project setup, database connection, schema operations, and diagnostics |
|
|
14
|
+
| `packages/tsconfig` | Private shared compiler configuration |
|
|
15
|
+
|
|
16
|
+
Applications install and import `@abloatai/ablo`. The root entrypoint is the
|
|
17
|
+
headless HTTP API. Human-facing reactive behavior is explicit:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import Ablo from '@abloatai/ablo';
|
|
21
|
+
import ReactiveAblo from '@abloatai/ablo/client';
|
|
22
|
+
import { AbloProvider, useAblo } from '@abloatai/ablo/react';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The backend implementation remains in `apps/sync-server` in the private
|
|
26
|
+
monorepo. It consumes transaction contracts but is not part of the public SDK
|
|
27
|
+
repository.
|
|
28
|
+
|
|
29
|
+
Internal packages must not import the branded facade. Dependencies point from
|
|
30
|
+
the facade to the owners, from humans and agents to transaction, and never back
|
|
31
|
+
upward. The public mirror copies these workspaces as workspaces; it does not
|
|
32
|
+
flatten them or generate compatibility source.
|
package/docs/mcp.md
CHANGED
|
@@ -57,7 +57,7 @@ Each tool mirrors an SDK verb, scoped to a model + id. Model names come from
|
|
|
57
57
|
|
|
58
58
|
| Tool | Mirrors | Does |
|
|
59
59
|
|---|---|---|
|
|
60
|
-
| `get_model` | `ablo.<model>.local.
|
|
60
|
+
| `get_model` | `ablo.<model>.local.get(id)` | read latest state + active claims |
|
|
61
61
|
| `list_records` | `ablo.<model>.list({…})` | cursor-paginated list with filters |
|
|
62
62
|
| `create_model` | `ablo.<model>.create({ data })` | guarded create |
|
|
63
63
|
| `update_model` | `ablo.<model>.update({ id, … })` | guarded update |
|
|
@@ -132,7 +132,7 @@ loading everything into context.
|
|
|
132
132
|
|
|
133
133
|
Reusable, parameterised templates that drive an end-to-end flow:
|
|
134
134
|
|
|
135
|
-
- `integrate-
|
|
135
|
+
- `integrate-ablo` — wire the SDK into an existing project.
|
|
136
136
|
- `add-agent` — add an agent worker that coordinates via claims and
|
|
137
137
|
conflict-safe writes.
|
|
138
138
|
- `define-schema` — design a Zod-first schema from a description, then run
|
package/docs/migration.md
CHANGED
|
@@ -14,7 +14,7 @@ change when you upgrade.
|
|
|
14
14
|
| Version | What changed | What to do |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| **0.36.0** | `ttlSeconds` deprecated on the join surfaces in favour of `ttl` | `useJoin({ scope, ttlSeconds: '5m' })` → `useJoin({ scope, ttl: '5m' })`; same for `ParticipantJoinOptions`. Both spellings work until 0.37.0 |
|
|
17
|
-
| **0.35.0** | Synchronous reads moved under `local`, mirroring the async verbs | `get(id)` → `local.
|
|
17
|
+
| **0.35.0** | Synchronous reads moved under `local`, mirroring the async verbs | `get(id)` → `local.get(id)`; `getAll(options)` → `local.list(options)`; `getCount(options)` → `local.count(options)` |
|
|
18
18
|
| **0.35.0** | `causedByTaskId` write option + seven `turn_*` error codes removed | Delete the `causedByTaskId` argument from writes; a branch on `turn_validation_failed` was unreachable and can go with it |
|
|
19
19
|
| **0.34.0** | Presence verb renamed `watch` → `join` | `ablo.<model>.watch(ids)` → `ablo.<model>.join(ids)`; `useWatch` → `useJoin`; the `WatchOptions` / `UseWatchOptions` / `UseWatchReturn` types → `JoinOptions` / `UseJoinOptions` / `UseJoinReturn`; error code `model_watch_not_configured` → `model_join_not_configured` |
|
|
20
20
|
| **0.28.0** | Removed React placeholders that had no working runtime | `usePresence` → `usePeers` or `useJoin`; `useClaim` → `ablo.<model>.claim`; `SyncGroupProvider` / `useSyncGroup` → `useJoin({ scope })` |
|
|
@@ -56,7 +56,7 @@ unchanged: it has always carried seconds and still does.
|
|
|
56
56
|
- const task = ablo.tasks.get(id);
|
|
57
57
|
- const open = ablo.tasks.getAll({ where: { status: 'open' } });
|
|
58
58
|
- const count = ablo.tasks.getCount({ where: { status: 'open' } });
|
|
59
|
-
+ const task = ablo.tasks.local.
|
|
59
|
+
+ const task = ablo.tasks.local.get(id);
|
|
60
60
|
+ const open = ablo.tasks.local.list({ where: { status: 'open' } });
|
|
61
61
|
+ const count = ablo.tasks.local.count({ where: { status: 'open' } });
|
|
62
62
|
```
|
|
@@ -304,7 +304,7 @@ modifier are named siblings. Reactive local reads stay on the synchronous
|
|
|
304
304
|
+ await ablo.tasks.retrieve({ id })
|
|
305
305
|
|
|
306
306
|
- useAblo((ablo) => ablo.tasks.retrieve(id)) ?? serverTask
|
|
307
|
-
+ useAblo((ablo) => ablo.tasks.local.
|
|
307
|
+
+ useAblo((ablo) => ablo.tasks.local.get(id)) ?? serverTask
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
`claim` now returns a disposable handle instead of taking a callback. The handle
|
package/docs/quickstart.md
CHANGED
|
@@ -215,12 +215,12 @@ const updated = await ablo.weatherReports.update({
|
|
|
215
215
|
console.log({ id: updated.id, status: updated.status }); // { id: '...', status: 'ready' }
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
Read a single row back with `
|
|
218
|
+
Read a single row back with `get({ id })`. It resolves to the row, or to
|
|
219
219
|
`undefined` when no row has that id — so narrow it once, then the fields are
|
|
220
220
|
fully typed:
|
|
221
221
|
|
|
222
222
|
```ts
|
|
223
|
-
const report = await ablo.weatherReports.
|
|
223
|
+
const report = await ablo.weatherReports.get({ id: created.id });
|
|
224
224
|
if (!report) throw new Error(`weatherReports ${created.id} not found`);
|
|
225
225
|
|
|
226
226
|
console.log(report.status); // 'ready'
|
package/docs/react.md
CHANGED
|
@@ -98,7 +98,7 @@ org / team / user map to what a participant can see.
|
|
|
98
98
|
import { useAblo } from '@abloatai/ablo/react';
|
|
99
99
|
|
|
100
100
|
export function ReportView({ report: serverReport }: { report: { id: string; location: string } }) {
|
|
101
|
-
const report = useAblo((ablo) => ablo.weatherReports.local.
|
|
101
|
+
const report = useAblo((ablo) => ablo.weatherReports.local.get(serverReport.id)) ?? serverReport;
|
|
102
102
|
const active = useAblo((ablo) => ablo.weatherReports.claim.state({ id: serverReport.id }));
|
|
103
103
|
const claimed = Boolean(active);
|
|
104
104
|
|
|
@@ -108,7 +108,7 @@ export function ReportView({ report: serverReport }: { report: { id: string; loc
|
|
|
108
108
|
|
|
109
109
|
The hook:
|
|
110
110
|
|
|
111
|
-
1. Uses the same `ablo.<model>.local.
|
|
111
|
+
1. Uses the same `ablo.<model>.local.get(id)` / `.local.list()` methods you'd call anywhere
|
|
112
112
|
else in the SDK — the hook just makes them reactive.
|
|
113
113
|
2. Tracks the model fields read by the selector and re-renders when confirmed
|
|
114
114
|
deltas arrive.
|
|
@@ -123,7 +123,7 @@ effects, or writes:
|
|
|
123
123
|
const abloClient = useAblo();
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
Prefer selector reads like `useAblo((ablo) => ablo.<model>.local.
|
|
126
|
+
Prefer selector reads like `useAblo((ablo) => ablo.<model>.local.get(id))`. Older hooks
|
|
127
127
|
also accept a string model name; prefer the selector form shown above.
|
|
128
128
|
|
|
129
129
|
For collections, keep the selector on the model client too:
|
|
@@ -141,12 +141,12 @@ const reports = useAblo((ablo) =>
|
|
|
141
141
|
## Server Load
|
|
142
142
|
|
|
143
143
|
```tsx
|
|
144
|
-
const report = await ablo.weatherReports.
|
|
144
|
+
const report = await ablo.weatherReports.get({ id });
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
Use `retrieve` in Server Components when the row may not be in the local pool
|
|
148
148
|
yet — it hydrates from the local store and the server, and returns a Promise, so
|
|
149
|
-
`await` it. (Server reads come in two shapes: `
|
|
149
|
+
`await` it. (Server reads come in two shapes: `get({ id })` for one row and
|
|
150
150
|
`list({ where })` for many; both are async. The synchronous local reads are
|
|
151
151
|
the `local` reads, used in render below.)
|
|
152
152
|
|
package/docs/schema-contract.md
CHANGED
|
@@ -12,7 +12,7 @@ defineSchema(...) -> ablo.<model>.create/retrieve/update/claim(...)
|
|
|
12
12
|
That one object drives:
|
|
13
13
|
|
|
14
14
|
- typed model clients in trusted server runtimes,
|
|
15
|
-
- React selectors through `useAblo((ablo) => ablo.<model>.local.
|
|
15
|
+
- React selectors through `useAblo((ablo) => ablo.<model>.local.get(id))`,
|
|
16
16
|
- agent and background-worker writes,
|
|
17
17
|
- Data Source request/response shape when your database stays canonical,
|
|
18
18
|
- hosted schema push, migration planning, and schema-version gating.
|
|
@@ -70,14 +70,14 @@ readable, you just don't author them.
|
|
|
70
70
|
Use async reads when the row may not be local:
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
|
-
const report = await ablo.weatherReports.
|
|
73
|
+
const report = await ablo.weatherReports.get({ id: reportId });
|
|
74
74
|
const ready = await ablo.weatherReports.list({ where: { status: 'ready' } });
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
Use synchronous local reads in render after data has synced:
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
|
-
const report = ablo.weatherReports.local.
|
|
80
|
+
const report = ablo.weatherReports.local.get(reportId);
|
|
81
81
|
const pending = ablo.weatherReports.local.list({ where: { status: 'pending' } });
|
|
82
82
|
```
|
|
83
83
|
|