@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
|
@@ -1,743 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loads model rows on demand — the lazy-load path of the sync engine. When
|
|
3
|
-
* something needs an entity that the initial bootstrap did not fetch,
|
|
4
|
-
* {@link OnDemandLoader.fetch | fetch} finds it and populates the
|
|
5
|
-
* in-memory {@link InstanceCache} so the rest of the engine can read it normally.
|
|
6
|
-
*
|
|
7
|
-
* A fetch resolves against three tiers in order, stopping at the first that can
|
|
8
|
-
* answer:
|
|
9
|
-
* 1. The object pool — if rows already in memory match the query, return them.
|
|
10
|
-
* 2. Local storage — if matching rows exist there, hydrate the pool and return.
|
|
11
|
-
* 3. The network — post the query to `/sync/query`, then hydrate both the pool
|
|
12
|
-
* and local storage.
|
|
13
|
-
*
|
|
14
|
-
* Concurrent calls with the same query key share one in-flight promise, so a
|
|
15
|
-
* burst of components mounting and asking for the same data on first paint
|
|
16
|
-
* triggers a single fetch rather than one each.
|
|
17
|
-
*
|
|
18
|
-
* The coordinator does not replace the bootstrap (which fully syncs instantly
|
|
19
|
-
* loaded models) or the live delta stream (pushed over the WebSocket). It only
|
|
20
|
-
* fills the gap for lazily loaded models read by id or filter after the engine
|
|
21
|
-
* is ready.
|
|
22
|
-
*/
|
|
23
|
-
import { ModelScope } from '../InstanceCache.js';
|
|
24
|
-
import { AbloValidationError } from '../transaction/errors.js';
|
|
25
|
-
import { postQuery } from '../query/client.js';
|
|
26
|
-
function timestampMs(value) {
|
|
27
|
-
if (value instanceof Date) {
|
|
28
|
-
const timestamp = value.getTime();
|
|
29
|
-
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
30
|
-
}
|
|
31
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
32
|
-
return value;
|
|
33
|
-
if (typeof value !== 'string')
|
|
34
|
-
return undefined;
|
|
35
|
-
const parsed = Date.parse(value);
|
|
36
|
-
return Number.isNaN(parsed) ? undefined : parsed;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* A query response is a snapshot, not an ordered delta. It may have started
|
|
40
|
-
* before a local optimistic write and completed after it. Only treat the row
|
|
41
|
-
* as authoritative over an existing model when its server timestamp is newer;
|
|
42
|
-
* missing timestamps retain the legacy merge behavior for custom sources that
|
|
43
|
-
* do not expose `updatedAt`.
|
|
44
|
-
*/
|
|
45
|
-
function snapshotDoesNotAdvanceModel(data, model) {
|
|
46
|
-
const incoming = timestampMs(data.updatedAt);
|
|
47
|
-
const resident = timestampMs(model.updatedAt);
|
|
48
|
-
return incoming !== undefined && resident !== undefined && incoming <= resident;
|
|
49
|
-
}
|
|
50
|
-
export class OnDemandLoader {
|
|
51
|
-
opts;
|
|
52
|
-
inFlight = new Map();
|
|
53
|
-
/**
|
|
54
|
-
* Query keys with a background confirm currently in flight. Distinct from
|
|
55
|
-
* {@link inFlight} (which dedupes *blocking* callers awaiting the same
|
|
56
|
-
* fetch): this set dedupes the fire-and-forget network confirm kicked off
|
|
57
|
-
* after a local-first read returns cached data, so a burst of mounts that
|
|
58
|
-
* all hit the warm pool/IDB don't each spawn their own redundant fetch.
|
|
59
|
-
*/
|
|
60
|
-
revalidating = new Set();
|
|
61
|
-
/**
|
|
62
|
-
* Query keys that have been satisfied from the server at least once this
|
|
63
|
-
* session. Once a key is here, repeat reads serve purely from the pool with
|
|
64
|
-
* no network round-trip: the WebSocket delta stream keeps those pool rows
|
|
65
|
-
* fresh, so re-running the HTTP query would be redundant polling. This ledger
|
|
66
|
-
* is what stops an already-open view from re-querying on every navigation.
|
|
67
|
-
*
|
|
68
|
-
* Cleared on reconnect (see {@link invalidate}) so that, after a connection
|
|
69
|
-
* drop where deltas may have been missed, the next read re-confirms once.
|
|
70
|
-
*/
|
|
71
|
-
hydratedKeys = new Set();
|
|
72
|
-
authTokenProvider = null;
|
|
73
|
-
/**
|
|
74
|
-
* The credential-recovery hook (the store's `recoverFromAuthRejection`),
|
|
75
|
-
* late-bound like {@link setAuthTokenProvider} because the store does not
|
|
76
|
-
* exist yet when the coordinator is constructed. Handed to `postQuery` so a
|
|
77
|
-
* 401 on the lazy lane re-mints through the same single-flight path the
|
|
78
|
-
* WebSocket probe uses, then replays the query once — instead of silently
|
|
79
|
-
* returning empty rows against an expired key.
|
|
80
|
-
*/
|
|
81
|
-
credentialRecovery = null;
|
|
82
|
-
constructor(opts) {
|
|
83
|
-
this.opts = opts;
|
|
84
|
-
// Reading the deprecated `getCapabilityToken` is deliberate: it's the
|
|
85
|
-
// back-compat shim that keeps older callers who still pass it working
|
|
86
|
-
// until they migrate to `getAuthToken`.
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
88
|
-
this.authTokenProvider = opts.getAuthToken ?? opts.getCapabilityToken ?? null;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Late-bind the auth token getter. Browser cookie consumers can omit this;
|
|
92
|
-
* bearer consumers need it so lazy HTTP queries use the same credential as
|
|
93
|
-
* bootstrap and the WebSocket.
|
|
94
|
-
*/
|
|
95
|
-
setAuthTokenProvider(provider) {
|
|
96
|
-
this.authTokenProvider = provider;
|
|
97
|
-
}
|
|
98
|
-
/** Late-bind the auth-recovery backbone. See {@link credentialRecovery}. */
|
|
99
|
-
setCredentialRecovery(recover) {
|
|
100
|
-
this.credentialRecovery = recover;
|
|
101
|
-
}
|
|
102
|
-
/** @deprecated Use `setAuthTokenProvider`. */
|
|
103
|
-
setCapabilityTokenProvider(provider) {
|
|
104
|
-
this.setAuthTokenProvider(provider);
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Fetch matching rows for a model, hydrating the pool from IDB or
|
|
108
|
-
* network if not already present. Idempotent and single-flight
|
|
109
|
-
* deduped on the (modelName, where, orderBy, limit) tuple.
|
|
110
|
-
*/
|
|
111
|
-
async fetch(modelName, options) {
|
|
112
|
-
const typename = this.resolveTypename(modelName);
|
|
113
|
-
const ModelClass = this.opts.registry.getModelByName(typename)
|
|
114
|
-
?? this.opts.registry.getModelByName(modelName);
|
|
115
|
-
if (!ModelClass) {
|
|
116
|
-
throw new AbloValidationError(`OnDemandLoader.fetch: unknown model "${modelName}" — ` +
|
|
117
|
-
`not registered in the schema.`, { code: 'model_not_registered' });
|
|
118
|
-
}
|
|
119
|
-
const clauses = normalizeWhere(options?.where);
|
|
120
|
-
const queryKey = stableKey(modelName, clauses, options?.orderBy, options?.limit, options?.expand);
|
|
121
|
-
// Single-flight: an identical hydration is already in flight.
|
|
122
|
-
const inFlight = this.inFlight.get(queryKey);
|
|
123
|
-
if (inFlight)
|
|
124
|
-
return inFlight;
|
|
125
|
-
const work = this.runFetch(modelName, typename, ModelClass, clauses, options, queryKey);
|
|
126
|
-
this.inFlight.set(queryKey, work);
|
|
127
|
-
// The rejection (if any) reaches callers via the returned `work`; this
|
|
128
|
-
// side-chain only clears the single-flight slot. Without the trailing
|
|
129
|
-
// catch, `.finally()` mirrors the rejection into a second, unhandled
|
|
130
|
-
// promise even when every caller handles theirs.
|
|
131
|
-
void work
|
|
132
|
-
.finally(() => {
|
|
133
|
-
this.inFlight.delete(queryKey);
|
|
134
|
-
})
|
|
135
|
-
.catch(() => undefined);
|
|
136
|
-
return work;
|
|
137
|
-
}
|
|
138
|
-
async runFetch(modelName, typename, ModelClass, clauses, options, queryKey) {
|
|
139
|
-
// `{ type: 'complete' }` is the only way to force a server round-trip:
|
|
140
|
-
// read-after-write certainty. Every other read is local-first.
|
|
141
|
-
const explicitComplete = options?.type === 'complete';
|
|
142
|
-
const expand = options?.expand;
|
|
143
|
-
const hasExpand = !!(expand && expand.length > 0);
|
|
144
|
-
// Fast path — this exact query was already satisfied from the server this
|
|
145
|
-
// session. The WebSocket delta stream has kept the pool fresh since, so a
|
|
146
|
-
// repeat read needs no network: serve straight from local. This is what
|
|
147
|
-
// stops an already-open view from re-querying on every navigation when no
|
|
148
|
-
// new deltas have arrived.
|
|
149
|
-
if (!explicitComplete && this.hydratedKeys.has(queryKey)) {
|
|
150
|
-
return applyLimit(await this.readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand), options?.limit);
|
|
151
|
-
}
|
|
152
|
-
// Not yet hydrated (or an explicit complete read). For a non-complete read
|
|
153
|
-
// without expand, if there is anything local to show (a warm pool, or local
|
|
154
|
-
// storage after a reload), hand it back immediately and confirm with the
|
|
155
|
-
// server once in the background — then mark the key hydrated so subsequent
|
|
156
|
-
// reads are purely local. First paint never blocks on the network.
|
|
157
|
-
//
|
|
158
|
-
// Expand queries are deliberately excluded here: the presence of a primary
|
|
159
|
-
// row says nothing about whether its relations are loaded. Returning the
|
|
160
|
-
// parent now would surface it with empty children, letting a readiness flag
|
|
161
|
-
// flip before the children exist. So an un-hydrated expand query falls
|
|
162
|
-
// through to the blocking fetch that brings parent and children together;
|
|
163
|
-
// the second open is served by the fast path.
|
|
164
|
-
if (!explicitComplete && !hasExpand) {
|
|
165
|
-
const local = await this.readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand);
|
|
166
|
-
if (local.length > 0) {
|
|
167
|
-
this.scheduleHydratingFetch(queryKey, modelName, typename, clauses, options);
|
|
168
|
-
return applyLimit(local, options?.limit);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Cold cache, or caller demanded server-confirmed state: block on the
|
|
172
|
-
// network, then mark this query hydrated so future reads serve local.
|
|
173
|
-
const networkModels = await this.fetchFromNetwork(modelName, typename, clauses, options);
|
|
174
|
-
this.hydratedKeys.add(queryKey);
|
|
175
|
-
if (networkModels.length > 0)
|
|
176
|
-
return applyLimit(networkModels, options?.limit);
|
|
177
|
-
// Network returned nothing — fall back to whatever's local (e.g. a
|
|
178
|
-
// complete read whose server result was empty but IDB still holds rows).
|
|
179
|
-
return applyLimit(await this.readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand), options?.limit);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Read a query's rows from local storage only — pool first, then IndexedDB
|
|
183
|
-
* on a pool miss (cold start after reload, or LRU eviction), hydrating the
|
|
184
|
-
* pool from IDB as a side effect. Resolves requested `expand` relations from
|
|
185
|
-
* their own local stores too. Never touches the network.
|
|
186
|
-
*/
|
|
187
|
-
async readLocal(modelName, typename, ModelClass, clauses, hasExpand, expand) {
|
|
188
|
-
let local = scanPool(this.opts.objectPool, ModelClass, clauses);
|
|
189
|
-
if (local.length === 0) {
|
|
190
|
-
const fromIdb = await scanIdb(this.opts.database, typename, clauses);
|
|
191
|
-
const idbModels = fromIdb
|
|
192
|
-
.map((raw) => this.hydrateOne(raw, typename))
|
|
193
|
-
.filter((m) => m !== null);
|
|
194
|
-
if (idbModels.length > 0) {
|
|
195
|
-
this.opts.objectPool.addBatch(idbModels, ModelScope.live);
|
|
196
|
-
local = idbModels;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (hasExpand && expand && local.length > 0) {
|
|
200
|
-
await this.hydrateExpandedFromLocal(modelName, local.map((m) => m.id), expand);
|
|
201
|
-
}
|
|
202
|
-
return local;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Drop the hydration ledger so the next read of each query re-confirms with
|
|
206
|
-
* the server. Called on reconnect — after a connection drop, deltas may have
|
|
207
|
-
* been missed, so the "WS keeps the pool fresh" assumption no longer holds
|
|
208
|
-
* until a fresh fetch (or the engine's delta catch-up) reconciles.
|
|
209
|
-
*/
|
|
210
|
-
invalidate() {
|
|
211
|
-
this.hydratedKeys.clear();
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Run the network leg of a fetch: query the server, hydrate primary rows
|
|
215
|
-
* (and any expanded relations) into the pool, and persist them to IDB.
|
|
216
|
-
* Shared by the blocking path (`runFetch` step 3) and the background
|
|
217
|
-
* revalidation kicked off after an `'unknown'` local hit.
|
|
218
|
-
*/
|
|
219
|
-
async fetchFromNetwork(modelName, typename, clauses, options) {
|
|
220
|
-
const networkRows = await this.queryNetwork(modelName, clauses, options);
|
|
221
|
-
const networkModels = networkRows
|
|
222
|
-
// Strict: a row the server returned whose type name this client never
|
|
223
|
-
// registered is a genuine schema collision (the pushed schema differs
|
|
224
|
-
// from the local one). Throw here, naming the cause, rather than silently
|
|
225
|
-
// dropping the row and failing downstream as `entity_not_found`.
|
|
226
|
-
.map((raw) => this.hydrateOne(raw, typename, { strict: true }))
|
|
227
|
-
.filter((m) => m !== null);
|
|
228
|
-
if (networkModels.length > 0) {
|
|
229
|
-
this.opts.objectPool.addBatch(networkModels, ModelScope.live);
|
|
230
|
-
// Background IDB write — don't block the caller. Expanded children are
|
|
231
|
-
// persisted to their own stores inside `queryNetwork`/`hydrateExpanded`.
|
|
232
|
-
void this.persistToIdb(modelName, networkRows);
|
|
233
|
-
}
|
|
234
|
-
return networkModels;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Fires the single background confirm for a query that was just served from
|
|
238
|
-
* local cache but is not hydrated yet. On success the key is marked hydrated,
|
|
239
|
-
* so every later read serves purely from local with no network until a
|
|
240
|
-
* reconnect invalidates the ledger. Deduped per query key so a render burst
|
|
241
|
-
* does not stampede. Errors are swallowed — the caller already has a usable
|
|
242
|
-
* local snapshot, and a failed confirm leaves the key un-hydrated so the next
|
|
243
|
-
* read simply tries again.
|
|
244
|
-
*/
|
|
245
|
-
scheduleHydratingFetch(queryKey, modelName, typename, clauses, options) {
|
|
246
|
-
if (this.revalidating.has(queryKey))
|
|
247
|
-
return;
|
|
248
|
-
this.revalidating.add(queryKey);
|
|
249
|
-
void this.fetchFromNetwork(modelName, typename, clauses, options)
|
|
250
|
-
.then(() => {
|
|
251
|
-
this.hydratedKeys.add(queryKey);
|
|
252
|
-
})
|
|
253
|
-
.catch(() => undefined)
|
|
254
|
-
.finally(() => {
|
|
255
|
-
this.revalidating.delete(queryKey);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Hydrate a parent's `hasMany`/`hasOne` relations from their OWN local
|
|
260
|
-
* stores (pool first, then IndexedDB by the FK secondary index) into the
|
|
261
|
-
* pool. The mirror of {@link hydrateExpanded} for the local read path:
|
|
262
|
-
* `hydrateExpanded` walks server-JOINed nested rows, this walks the child
|
|
263
|
-
* model's own store keyed by the relation's foreign key.
|
|
264
|
-
*
|
|
265
|
-
* Fully schema-driven via the relation's `target` + `foreignKey` — no
|
|
266
|
-
* per-model special-casing. `belongsTo` relations are skipped: those point
|
|
267
|
-
* at a single parent (the inverse direction), already covered by the
|
|
268
|
-
* primary scan when that parent is itself the fetched model.
|
|
269
|
-
*/
|
|
270
|
-
async hydrateExpandedFromLocal(parentModelName, parentIds, relationNames) {
|
|
271
|
-
if (parentIds.length === 0)
|
|
272
|
-
return;
|
|
273
|
-
const parentDef = this.getModelDef(parentModelName);
|
|
274
|
-
if (!parentDef?.relations)
|
|
275
|
-
return;
|
|
276
|
-
for (const rel of relationNames) {
|
|
277
|
-
const relDef = parentDef.relations[rel];
|
|
278
|
-
if (!relDef)
|
|
279
|
-
continue;
|
|
280
|
-
if (relDef.type !== 'hasMany' && relDef.type !== 'hasOne')
|
|
281
|
-
continue;
|
|
282
|
-
const targetKey = relDef.target;
|
|
283
|
-
const foreignKey = relDef.foreignKey;
|
|
284
|
-
if (!targetKey || !foreignKey)
|
|
285
|
-
continue;
|
|
286
|
-
const targetTypename = this.resolveTypename(targetKey);
|
|
287
|
-
// Skip parents whose children are already pool-resident (O(1) when the
|
|
288
|
-
// FK is indexed). Falls through to a local read for the rest.
|
|
289
|
-
const missing = parentIds.filter((pid) => this.opts.objectPool.getByForeignKey(targetTypename, foreignKey, pid).length === 0);
|
|
290
|
-
if (missing.length === 0)
|
|
291
|
-
continue;
|
|
292
|
-
const rows = await this.readChildrenLocal(targetTypename, foreignKey, missing);
|
|
293
|
-
const models = rows
|
|
294
|
-
.map((raw) => this.hydrateOne(this.stampTypename(raw, targetTypename), targetTypename))
|
|
295
|
-
.filter((m) => m !== null);
|
|
296
|
-
if (models.length > 0) {
|
|
297
|
-
this.opts.objectPool.addBatch(models, ModelScope.live);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Read a child model's rows from local storage by foreign key.
|
|
303
|
-
*
|
|
304
|
-
* Uses the FK secondary index (O(matches) per parent) only when the schema
|
|
305
|
-
* declares one — `getAllFromIndex` resolves `[]` for a missing index rather
|
|
306
|
-
* than throwing, so the decision is made up front from the registry, not by
|
|
307
|
-
* catching. Unindexed FKs — and in-memory stores, which carry no secondary
|
|
308
|
-
* indexes at all — fall back to a single full-store scan filtered in JS.
|
|
309
|
-
*/
|
|
310
|
-
async readChildrenLocal(childTypename, foreignKey, parentIds) {
|
|
311
|
-
const store = this.opts.database.getStore(childTypename);
|
|
312
|
-
if (!store)
|
|
313
|
-
return [];
|
|
314
|
-
const isIndexed = this.opts.registry.getIndexedProperties(childTypename).includes(foreignKey);
|
|
315
|
-
if (isIndexed) {
|
|
316
|
-
const collected = [];
|
|
317
|
-
for (const pid of parentIds) {
|
|
318
|
-
const rows = await store.getAllFromIndex(foreignKey, pid);
|
|
319
|
-
if (Array.isArray(rows))
|
|
320
|
-
collected.push(...rows);
|
|
321
|
-
}
|
|
322
|
-
// A non-empty result means the index is live (browser IDB). Empty can
|
|
323
|
-
// mean "no children" OR "no physical index" (in-memory) — fall through
|
|
324
|
-
// to the scan so the in-memory/SSR path stays correct.
|
|
325
|
-
if (collected.length > 0)
|
|
326
|
-
return collected;
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
const all = await store.getAll();
|
|
330
|
-
if (!Array.isArray(all))
|
|
331
|
-
return [];
|
|
332
|
-
const idSet = new Set(parentIds);
|
|
333
|
-
return all.filter((r) => idSet.has((r)[foreignKey]));
|
|
334
|
-
}
|
|
335
|
-
catch {
|
|
336
|
-
return [];
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
/** Typed accessor for a model's schema definition (typename + relations). */
|
|
340
|
-
getModelDef(modelName) {
|
|
341
|
-
return this.opts.schema.models?.[modelName];
|
|
342
|
-
}
|
|
343
|
-
hydrateOne(raw, typename, opts) {
|
|
344
|
-
if (!raw || typeof raw !== 'object')
|
|
345
|
-
return null;
|
|
346
|
-
const obj = raw;
|
|
347
|
-
if (typeof obj.id !== 'string')
|
|
348
|
-
return null;
|
|
349
|
-
if (this.opts.objectPool.has(obj.id)) {
|
|
350
|
-
// Keep the existing instance alive when a query refreshes it. A query
|
|
351
|
-
// can carry fresher server state after a missed delta, but unlike the
|
|
352
|
-
// ordered delta stream it can also finish late with an older snapshot;
|
|
353
|
-
// the reconciliation below distinguishes those cases before applying.
|
|
354
|
-
const existing = this.opts.objectPool.get(obj.id);
|
|
355
|
-
if (existing) {
|
|
356
|
-
const stamped = this.stampTypename(obj, typename);
|
|
357
|
-
// Network queries are unordered snapshots. A request that began before
|
|
358
|
-
// an optimistic resize can return afterward with the old row; applying
|
|
359
|
-
// it here would visibly snap the live model back, and the matching
|
|
360
|
-
// authoritative delta cannot repair it because own echoes are
|
|
361
|
-
// intentionally suppressed. Keep a newer resident row intact.
|
|
362
|
-
if (snapshotDoesNotAdvanceModel(stamped, existing))
|
|
363
|
-
return existing;
|
|
364
|
-
// If the source has no comparable timestamp, retain pending local
|
|
365
|
-
// fields while accepting unrelated server fields. This is the same
|
|
366
|
-
// local-first merge contract used by SyncClient's delta resolver.
|
|
367
|
-
const localChanges = existing.getChanges();
|
|
368
|
-
existing.updateFromData(Object.keys(localChanges).length > 0
|
|
369
|
-
? { ...stamped, ...localChanges, updatedAt: existing.updatedAt }
|
|
370
|
-
: stamped);
|
|
371
|
-
return existing;
|
|
372
|
-
}
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
// Stamp the known relation typename onto the row when the source
|
|
376
|
-
// (IndexedDB rows, sometimes network rows) didn't carry one. Without
|
|
377
|
-
// this, InstanceCache.createFromData falls through to the 'Unknown'
|
|
378
|
-
// model-name branch and emits the
|
|
379
|
-
// "InstanceCache.createFromData: No model identifier found" warning,
|
|
380
|
-
// failing to hydrate the entity from cache (network path then has to
|
|
381
|
-
// re-populate it). The typename comes from the schema relation
|
|
382
|
-
// (`'Block'`, `'Section'`, etc.) so no guessing involved.
|
|
383
|
-
const stamped = this.stampTypename(obj, typename);
|
|
384
|
-
return this.opts.objectPool.createFromData(stamped, undefined, opts);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Stamp `__typename` onto a row when it's known (from the schema's
|
|
388
|
-
* relation target). Strips the mangled `_Typename` key the
|
|
389
|
-
* `postgres.camel` driver leaves behind when the server's SQL
|
|
390
|
-
* bakes `__typename` into a JSONB literal — the driver's
|
|
391
|
-
* snake↔camel transform misreads `__typename` as `_typename` with
|
|
392
|
-
* a leading underscore and produces `_Typename`. InstanceCache only
|
|
393
|
-
* recognises `__typename`, so without this step nested rows fall
|
|
394
|
-
* through to the 'Unknown' branch and never instantiate.
|
|
395
|
-
*/
|
|
396
|
-
stampTypename(item, typename) {
|
|
397
|
-
if (!item || typeof item !== 'object' || !typename)
|
|
398
|
-
return item;
|
|
399
|
-
const obj = item;
|
|
400
|
-
if (obj.__typename === typename)
|
|
401
|
-
return obj;
|
|
402
|
-
// Drop the driver-mangled `_Typename` AND any row-carried `__typename`
|
|
403
|
-
// that disagrees with the schema's: these rows were returned FOR this
|
|
404
|
-
// model's query, so the schema typename is correct by construction — and
|
|
405
|
-
// without stripping it, the spread would put the row's variant (a server
|
|
406
|
-
// echoing the schema KEY `tasks` instead of the typename `Task`) back on
|
|
407
|
-
// top of the stamp, sending hydration to the strict unknown-model error.
|
|
408
|
-
const { _Typename: _dropMangled, __typename: _dropRowVariant, ...rest } = obj;
|
|
409
|
-
void _dropMangled;
|
|
410
|
-
void _dropRowVariant;
|
|
411
|
-
return { __typename: typename, ...rest };
|
|
412
|
-
}
|
|
413
|
-
async queryNetwork(modelName, clauses, options) {
|
|
414
|
-
const typename = this.resolveTypename(modelName);
|
|
415
|
-
const orderEntries = options?.orderBy ? Object.entries(options.orderBy) : [];
|
|
416
|
-
const firstOrder = orderEntries[0];
|
|
417
|
-
const query = {
|
|
418
|
-
model: typename,
|
|
419
|
-
where: clauses.map((c) => this.columnizeClause(modelName, c)),
|
|
420
|
-
...(firstOrder
|
|
421
|
-
? {
|
|
422
|
-
orderBy: this.columnizeField(modelName, firstOrder[0]),
|
|
423
|
-
order: firstOrder[1] ?? 'asc',
|
|
424
|
-
}
|
|
425
|
-
: {}),
|
|
426
|
-
...(options?.limit ? { limit: options.limit } : {}),
|
|
427
|
-
...(options?.expand && options.expand.length > 0
|
|
428
|
-
? { related: options.expand }
|
|
429
|
-
: {}),
|
|
430
|
-
};
|
|
431
|
-
const result = await postQuery({
|
|
432
|
-
baseUrl: this.opts.baseUrl,
|
|
433
|
-
getAuthToken: this.authTokenProvider ?? undefined,
|
|
434
|
-
recoverCredential: this.credentialRecovery ?? undefined,
|
|
435
|
-
runtime: this.opts.runtime,
|
|
436
|
-
}, { queries: [query] });
|
|
437
|
-
const rows = Array.isArray(result.results[0]) ? result.results[0] : [];
|
|
438
|
-
// Normalize: wire rows lack `__typename` when the server elides it.
|
|
439
|
-
const normalized = rows.map((row) => {
|
|
440
|
-
if (row && typeof row === 'object' && !('__typename' in row)) {
|
|
441
|
-
return { __typename: typename, ...row };
|
|
442
|
-
}
|
|
443
|
-
return row;
|
|
444
|
-
});
|
|
445
|
-
// Expand: server returns related entities nested under each row
|
|
446
|
-
// (`row.blocks = [{...}, ...]`). Walk the nested shape, stamp the
|
|
447
|
-
// typename from the schema's relation metadata (the server bakes
|
|
448
|
-
// `__typename` into the JSONB but the postgres.camel driver
|
|
449
|
-
// mangles it to `_Typename` mid-flight, so client-side stamping
|
|
450
|
-
// is the only reliable path), hydrate each related row into its
|
|
451
|
-
// own typed pool, then leave the nested arrays in place on the
|
|
452
|
-
// primary row.
|
|
453
|
-
if (options?.expand && options.expand.length > 0) {
|
|
454
|
-
this.hydrateExpanded(modelName, normalized, options.expand);
|
|
455
|
-
}
|
|
456
|
-
return normalized;
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Hydrate nested expanded rows. Resolves each relation's target
|
|
460
|
-
* typename via the schema and stamps `__typename` on every nested
|
|
461
|
-
* row before passing to `hydrateOne` — the server's JSONB
|
|
462
|
-
* `__typename` field gets mangled by `postgres.camel` (`__typename`
|
|
463
|
-
* → `_Typename`), so the SDK can't trust whatever string lands.
|
|
464
|
-
*/
|
|
465
|
-
hydrateExpanded(parentModelName, rows, relationNames) {
|
|
466
|
-
const parentDef = this.getModelDef(parentModelName);
|
|
467
|
-
for (const row of rows) {
|
|
468
|
-
if (!row || typeof row !== 'object')
|
|
469
|
-
continue;
|
|
470
|
-
const obj = row;
|
|
471
|
-
for (const rel of relationNames) {
|
|
472
|
-
const nested = obj[rel];
|
|
473
|
-
if (!nested)
|
|
474
|
-
continue;
|
|
475
|
-
// Resolve target typename via parent's relations map.
|
|
476
|
-
const relDef = parentDef?.relations?.[rel];
|
|
477
|
-
const targetKey = relDef?.target;
|
|
478
|
-
const targetTypename = targetKey ? this.resolveTypename(targetKey) : undefined;
|
|
479
|
-
const items = Array.isArray(nested) ? nested : [nested];
|
|
480
|
-
const models = [];
|
|
481
|
-
const stampedItems = [];
|
|
482
|
-
for (const item of items) {
|
|
483
|
-
const stamped = this.stampTypename(item, targetTypename);
|
|
484
|
-
stampedItems.push(stamped);
|
|
485
|
-
const m = this.hydrateOne(stamped);
|
|
486
|
-
if (m)
|
|
487
|
-
models.push(m);
|
|
488
|
-
}
|
|
489
|
-
if (models.length > 0) {
|
|
490
|
-
this.opts.objectPool.addBatch(models, ModelScope.live);
|
|
491
|
-
}
|
|
492
|
-
// Persist expanded children to their OWN typed store so they survive
|
|
493
|
-
// reload and can be re-served by `hydrateExpandedFromLocal` — without
|
|
494
|
-
// this, expand-fetched relations live only inside the parent's row
|
|
495
|
-
// and are lost to a lazy child query after a cold start.
|
|
496
|
-
if (stampedItems.length > 0 && targetKey) {
|
|
497
|
-
void this.persistToIdb(targetKey, stampedItems);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
async persistToIdb(modelName, rows) {
|
|
503
|
-
const store = this.opts.database.getStore(this.resolveTypename(modelName));
|
|
504
|
-
if (!store)
|
|
505
|
-
return;
|
|
506
|
-
for (const row of rows) {
|
|
507
|
-
try {
|
|
508
|
-
await store.put(row);
|
|
509
|
-
}
|
|
510
|
-
catch {
|
|
511
|
-
// IDB writes are best-effort — a transient quota/transaction
|
|
512
|
-
// failure shouldn't break the hydration's primary purpose.
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
resolveTypename(modelName) {
|
|
517
|
-
// Schema is the source of truth for wire typenames. The model proxy
|
|
518
|
-
// is keyed by camelCase plural (`blocks`) but the wire query +
|
|
519
|
-
// InstanceCache typeIndex use the typename (`Block`).
|
|
520
|
-
const def = this.opts.schema
|
|
521
|
-
.models?.[modelName];
|
|
522
|
-
return def?.typename ?? modelName;
|
|
523
|
-
}
|
|
524
|
-
columnizeField(modelName, field) {
|
|
525
|
-
const fields = this.opts.schema.models?.[modelName]?.fields;
|
|
526
|
-
if (fields) {
|
|
527
|
-
const direct = fields[field]?.column;
|
|
528
|
-
if (direct)
|
|
529
|
-
return direct;
|
|
530
|
-
for (const [fieldName, meta] of Object.entries(fields)) {
|
|
531
|
-
const conventional = columnize(fieldName);
|
|
532
|
-
if (field === fieldName || field === conventional || field === meta.column) {
|
|
533
|
-
return meta.column ?? conventional;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return /[A-Z]/.test(field) ? columnize(field) : field;
|
|
538
|
-
}
|
|
539
|
-
columnizeClause(modelName, clause) {
|
|
540
|
-
const finalCol = this.columnizeField(modelName, clause[0]);
|
|
541
|
-
if (clause.length === 2)
|
|
542
|
-
return [finalCol, clause[1]];
|
|
543
|
-
return [finalCol, clause[1], clause[2]];
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
// ── Helpers ────────────────────────────────────────────────────────────
|
|
547
|
-
function stableKey(modelName, clauses, orderBy, limit, expand) {
|
|
548
|
-
// Sort clauses by their stringified form so caller order doesn't
|
|
549
|
-
// produce different dedup keys for semantically identical queries.
|
|
550
|
-
const sorted = [...clauses].map((c) => [...c]).sort((a, b) => {
|
|
551
|
-
const ka = JSON.stringify(a);
|
|
552
|
-
const kb = JSON.stringify(b);
|
|
553
|
-
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
554
|
-
});
|
|
555
|
-
// Expand is part of the query identity: `sections where report=r1` and the same
|
|
556
|
-
// with `expand:['blocks']` hydrate different data, so they must not share a
|
|
557
|
-
// ledger/dedup key. Sorted so relation order doesn't fork the key.
|
|
558
|
-
const expandKey = expand && expand.length > 0 ? [...expand].sort() : undefined;
|
|
559
|
-
return JSON.stringify({ modelName, where: sorted, orderBy, limit, expand: expandKey });
|
|
560
|
-
}
|
|
561
|
-
function applyLimit(arr, limit) {
|
|
562
|
-
return typeof limit === 'number' ? arr.slice(0, limit) : arr;
|
|
563
|
-
}
|
|
564
|
-
function scanPool(pool, ModelClass, clauses) {
|
|
565
|
-
const all = pool.getByType(ModelClass);
|
|
566
|
-
if (clauses.length === 0)
|
|
567
|
-
return all;
|
|
568
|
-
return all.filter((entity) => matchesClauses(entity, clauses));
|
|
569
|
-
}
|
|
570
|
-
async function scanIdb(database, modelName, clauses) {
|
|
571
|
-
const store = database.getStore(modelName);
|
|
572
|
-
if (!store)
|
|
573
|
-
return [];
|
|
574
|
-
// Fast path: a single equality `id` lookup hits the primary key.
|
|
575
|
-
const eqClauses = extractEqClauses(clauses);
|
|
576
|
-
if (clauses.length === 1 && eqClauses.id !== undefined && typeof eqClauses.id === 'string') {
|
|
577
|
-
try {
|
|
578
|
-
const row = await store.get(eqClauses.id);
|
|
579
|
-
return row ? [row] : [];
|
|
580
|
-
}
|
|
581
|
-
catch {
|
|
582
|
-
return [];
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Index-aware path: when every clause is equality and exactly one
|
|
586
|
-
// non-id string column is constrained, hit that column's index for
|
|
587
|
-
// an O(matches) read. Anything involving LIKE/ILIKE/ranges falls
|
|
588
|
-
// through to full-scan + filter.
|
|
589
|
-
if (clausesAreAllEquality(clauses)) {
|
|
590
|
-
const indexedKeys = Object.keys(eqClauses).filter((k) => k !== 'id' && typeof eqClauses[k] === 'string');
|
|
591
|
-
const idxKey = indexedKeys.length === 1 ? indexedKeys[0] : undefined;
|
|
592
|
-
if (idxKey !== undefined) {
|
|
593
|
-
try {
|
|
594
|
-
const rows = await store.getAllFromIndex(idxKey, eqClauses[idxKey]);
|
|
595
|
-
if (Array.isArray(rows)) {
|
|
596
|
-
return rows.filter((r) => matchesClauses(r, clauses));
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
catch {
|
|
600
|
-
// index doesn't exist — fall through to full-scan path.
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
try {
|
|
605
|
-
const rows = await store.getAll();
|
|
606
|
-
return Array.isArray(rows)
|
|
607
|
-
? rows.filter((r) => matchesClauses(r, clauses))
|
|
608
|
-
: [];
|
|
609
|
-
}
|
|
610
|
-
catch {
|
|
611
|
-
return [];
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Normalize `LoadWhere<T>` input to the canonical `readonly WhereClause[]`
|
|
616
|
-
* tuple form used throughout `runFetch`. Tuple inputs pass through; object
|
|
617
|
-
* inputs become one `['col', '=', val]` or `['col', 'IN', vals]` per key.
|
|
618
|
-
*
|
|
619
|
-
* Detection: an array whose first element is itself an array is treated
|
|
620
|
-
* as tuple form. Object form is the fallback.
|
|
621
|
-
*
|
|
622
|
-
* Exported so callers can pre-normalize (e.g., for tests, or to inspect
|
|
623
|
-
* the canonical clauses before passing them to `load`/`subscribe`).
|
|
624
|
-
*/
|
|
625
|
-
export function normalizeWhere(where) {
|
|
626
|
-
if (where == null)
|
|
627
|
-
return [];
|
|
628
|
-
if (Array.isArray(where)) {
|
|
629
|
-
// Tuple form — assumed to already use server-side column names.
|
|
630
|
-
return where;
|
|
631
|
-
}
|
|
632
|
-
if (typeof where === 'object') {
|
|
633
|
-
const obj = where;
|
|
634
|
-
return Object.entries(obj).map(([key, value]) => {
|
|
635
|
-
if (Array.isArray(value)) {
|
|
636
|
-
return [key, 'IN', value];
|
|
637
|
-
}
|
|
638
|
-
return [key, value];
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
return [];
|
|
642
|
-
}
|
|
643
|
-
/** Equality-only subset of clauses, keyed by column. Used by IDB fast paths. */
|
|
644
|
-
function extractEqClauses(clauses) {
|
|
645
|
-
const out = {};
|
|
646
|
-
for (const c of clauses) {
|
|
647
|
-
if (c.length === 2) {
|
|
648
|
-
out[c[0]] = c[1];
|
|
649
|
-
}
|
|
650
|
-
else if (c[1] === '=') {
|
|
651
|
-
out[c[0]] = c[2];
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
return out;
|
|
655
|
-
}
|
|
656
|
-
function clausesAreAllEquality(clauses) {
|
|
657
|
-
return clauses.every((c) => c.length === 2 || c[1] === '=');
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Operator-aware predicate. Mirrors the server's WhereOp semantics for
|
|
661
|
-
* local matching against pool/IDB rows. LIKE/ILIKE use SQL wildcards
|
|
662
|
-
* (`%` = any chars, `_` = one char) translated to a JS regex.
|
|
663
|
-
*
|
|
664
|
-
* Exported so callers can apply the same predicate to in-memory
|
|
665
|
-
* collections (tests, batch operations) using the canonical clauses.
|
|
666
|
-
*/
|
|
667
|
-
export function matchesClauses(entity, clauses) {
|
|
668
|
-
for (const clause of clauses) {
|
|
669
|
-
const col = clause[0];
|
|
670
|
-
const op = clause.length === 2 ? '=' : clause[1];
|
|
671
|
-
const expected = clause.length === 2 ? clause[1] : clause[2];
|
|
672
|
-
const v = entity[col];
|
|
673
|
-
if (!matchOp(v, op, expected))
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
return true;
|
|
677
|
-
}
|
|
678
|
-
function matchOp(actual, op, expected) {
|
|
679
|
-
switch (op) {
|
|
680
|
-
case '=':
|
|
681
|
-
return actual === expected;
|
|
682
|
-
case '!=':
|
|
683
|
-
return actual !== expected;
|
|
684
|
-
case '<':
|
|
685
|
-
return compareOrdered(actual, expected, (a, b) => a < b);
|
|
686
|
-
case '<=':
|
|
687
|
-
return compareOrdered(actual, expected, (a, b) => a <= b);
|
|
688
|
-
case '>':
|
|
689
|
-
return compareOrdered(actual, expected, (a, b) => a > b);
|
|
690
|
-
case '>=':
|
|
691
|
-
return compareOrdered(actual, expected, (a, b) => a >= b);
|
|
692
|
-
case 'IN':
|
|
693
|
-
return Array.isArray(expected) && expected.some((alt) => alt === actual);
|
|
694
|
-
case 'NOT IN':
|
|
695
|
-
return Array.isArray(expected) && !expected.some((alt) => alt === actual);
|
|
696
|
-
case 'IS':
|
|
697
|
-
// SQL `IS` is null-equality; the only meaningful right-hand side here is null.
|
|
698
|
-
return actual === expected;
|
|
699
|
-
case 'IS NOT':
|
|
700
|
-
return actual !== expected;
|
|
701
|
-
case 'LIKE':
|
|
702
|
-
return typeof actual === 'string' && typeof expected === 'string' && likeRegex(expected, false).test(actual);
|
|
703
|
-
case 'NOT LIKE':
|
|
704
|
-
return typeof actual === 'string' && typeof expected === 'string' && !likeRegex(expected, false).test(actual);
|
|
705
|
-
case 'ILIKE':
|
|
706
|
-
return typeof actual === 'string' && typeof expected === 'string' && likeRegex(expected, true).test(actual);
|
|
707
|
-
case 'NOT ILIKE':
|
|
708
|
-
return typeof actual === 'string' && typeof expected === 'string' && !likeRegex(expected, true).test(actual);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Ordered comparison helper. Both operands must be non-null and the same
|
|
713
|
-
* comparable primitive (string-vs-string or number-vs-number). Mixed
|
|
714
|
-
* types fall back to JS's loose ordering, which would be confusing — so
|
|
715
|
-
* we reject early to match SQL semantics (a NULL operand yields false).
|
|
716
|
-
*/
|
|
717
|
-
function compareOrdered(actual, expected, cmp) {
|
|
718
|
-
if (actual == null || expected == null)
|
|
719
|
-
return false;
|
|
720
|
-
if (typeof actual === 'number' && typeof expected === 'number') {
|
|
721
|
-
return cmp(actual, expected);
|
|
722
|
-
}
|
|
723
|
-
if (typeof actual === 'string' && typeof expected === 'string') {
|
|
724
|
-
return cmp(actual, expected);
|
|
725
|
-
}
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
728
|
-
/** Translate a SQL LIKE/ILIKE pattern to a JS regex (`%` → `.*`, `_` → `.`). */
|
|
729
|
-
function likeRegex(pattern, insensitive) {
|
|
730
|
-
// Escape regex specials *except* `%` and `_`, then translate those.
|
|
731
|
-
const escaped = pattern.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
732
|
-
const body = escaped.replace(/%/g, '.*').replace(/_/g, '.');
|
|
733
|
-
return new RegExp(`^${body}$`, insensitive ? 'i' : '');
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Schema fields are camelCase (`sectionId`); the wire query expects
|
|
737
|
-
* the server-side column name. The query server's input resolver
|
|
738
|
-
* casing-folds, but we send snake_case to match the convention used
|
|
739
|
-
* by the existing loaders' postQuery calls (`'section_id'` etc.).
|
|
740
|
-
*/
|
|
741
|
-
function columnize(field) {
|
|
742
|
-
return field.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
743
|
-
}
|