@abloatai/ablo 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +34 -0
- package/README.md +104 -88
- package/dist/BaseSyncedStore.d.ts +140 -266
- package/dist/BaseSyncedStore.js +338 -739
- package/dist/Database.d.ts +62 -77
- package/dist/Database.js +106 -127
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +16 -15
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +52 -69
- package/dist/ModelRegistry.d.ts +46 -25
- package/dist/ModelRegistry.js +32 -30
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +6 -7
- package/dist/SyncClient.d.ts +119 -109
- package/dist/SyncClient.js +303 -224
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +39 -31
- package/dist/agent/Agent.js +35 -23
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +30 -31
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +7 -8
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/credentialPolicy.d.ts +74 -71
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +7 -18
- package/dist/auth/credentialSource.js +10 -18
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +34 -40
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +483 -369
- package/dist/client/Ablo.d.ts +107 -836
- package/dist/client/Ablo.js +174 -833
- package/dist/client/ApiClient.d.ts +44 -20
- package/dist/client/ApiClient.js +193 -44
- package/dist/client/auth.d.ts +51 -60
- package/dist/client/auth.js +137 -110
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +35 -0
- package/dist/client/consoleLogger.js +44 -0
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +26 -31
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +158 -124
- package/dist/client/credentialEndpoint.d.ts +61 -0
- package/dist/client/credentialEndpoint.js +86 -0
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +21 -0
- package/dist/client/hostedEndpoints.js +21 -0
- package/dist/client/httpClient.d.ts +58 -54
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +49 -59
- package/dist/client/modelRegistration.d.ts +10 -0
- package/dist/client/modelRegistration.js +301 -0
- package/dist/client/options.d.ts +373 -0
- package/dist/client/options.js +6 -0
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +333 -0
- package/dist/client/resourceTypes.js +7 -0
- package/dist/client/schemaConfig.d.ts +44 -0
- package/dist/client/schemaConfig.js +176 -0
- package/dist/client/sessionMint.d.ts +17 -13
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +9 -10
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +28 -0
- package/dist/client/wsMutationExecutor.js +71 -0
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -7
- package/dist/coordination/index.d.ts +13 -4
- package/dist/coordination/index.js +29 -4
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -11
- package/dist/coordination/trace.js +13 -15
- package/dist/core/DatabaseManager.d.ts +5 -8
- package/dist/core/DatabaseManager.js +38 -40
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +27 -34
- package/dist/core/QueryView.d.ts +17 -5
- package/dist/core/QueryView.js +6 -7
- package/dist/core/StoreManager.d.ts +14 -16
- package/dist/core/StoreManager.js +26 -25
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +18 -13
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +57 -54
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +145 -0
- package/dist/core/storeContract.js +12 -0
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +118 -101
- package/dist/errorCodes.js +277 -260
- package/dist/errors.d.ts +170 -165
- package/dist/errors.js +161 -151
- package/dist/index.d.ts +30 -27
- package/dist/index.js +90 -82
- package/dist/interfaces/index.d.ts +108 -133
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +59 -49
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +149 -155
- package/dist/mutators/defineMutators.d.ts +24 -37
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +105 -101
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +32 -16
- package/dist/query/client.js +103 -72
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +7 -11
- package/dist/react/AbloProvider.js +24 -17
- package/dist/react/context.d.ts +27 -146
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +17 -15
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +11 -12
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +9 -9
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +10 -4
- package/dist/react/useReactive.js +2 -3
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +3 -3
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +35 -0
- package/dist/schema/ddlLock.js +46 -0
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +36 -49
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +5 -4
- package/dist/schema/index.js +29 -21
- package/dist/schema/model.d.ts +121 -146
- package/dist/schema/model.js +24 -35
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +7 -1
- package/dist/schema/queries.d.ts +30 -32
- package/dist/schema/queries.js +24 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +38 -0
- package/dist/schema/residency.js +30 -0
- package/dist/schema/roles.d.ts +45 -27
- package/dist/schema/roles.js +52 -21
- package/dist/schema/schema.d.ts +36 -45
- package/dist/schema/schema.js +42 -39
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +36 -39
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +27 -50
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +31 -26
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +34 -28
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +28 -26
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +10 -9
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +27 -29
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +27 -19
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +30 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +94 -0
- package/dist/source/factory.js +268 -0
- package/dist/source/index.d.ts +10 -462
- package/dist/source/index.js +17 -421
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +10 -11
- package/dist/source/next.js +7 -8
- package/dist/source/pushQueue.d.ts +70 -48
- package/dist/source/pushQueue.js +36 -29
- package/dist/source/signing.d.ts +88 -0
- package/dist/source/signing.js +159 -0
- package/dist/source/types.d.ts +351 -0
- package/dist/source/types.js +43 -0
- package/dist/stores/ObjectStore.d.ts +11 -12
- package/dist/stores/ObjectStore.js +34 -35
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +8 -12
- package/dist/stores/SyncActionStore.js +77 -46
- package/dist/surface.d.ts +28 -21
- package/dist/surface.js +28 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
- package/dist/sync/ConnectionManager.d.ts +47 -50
- package/dist/sync/ConnectionManager.js +74 -70
- package/dist/sync/NetworkProbe.d.ts +27 -31
- package/dist/sync/NetworkProbe.js +67 -72
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
- package/dist/sync/SyncWebSocket.d.ts +175 -250
- package/dist/sync/SyncWebSocket.js +431 -769
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +38 -30
- package/dist/sync/bootstrapApply.d.ts +70 -0
- package/dist/sync/bootstrapApply.js +73 -0
- package/dist/sync/commitFrames.d.ts +44 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +108 -25
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +13 -17
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +322 -0
- package/dist/sync/deltaPipeline.d.ts +113 -0
- package/dist/sync/deltaPipeline.js +261 -0
- package/dist/sync/groupChange.d.ts +113 -0
- package/dist/sync/groupChange.js +242 -0
- package/dist/sync/heartbeat.d.ts +63 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +27 -27
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +40 -0
- package/dist/sync/syncCursor.js +55 -0
- package/dist/sync/syncPlan.d.ts +54 -0
- package/dist/sync/syncPlan.js +50 -0
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +116 -0
- package/dist/sync/wsFrameHandlers.js +374 -0
- package/dist/testing/fixtures/bootstrap.d.ts +21 -17
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +31 -34
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +12 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -18
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +3 -3
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +21 -34
- package/dist/testing/mocks/MockSyncContext.js +16 -45
- package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +190 -221
- package/dist/transactions/TransactionQueue.js +424 -822
- package/dist/transactions/TransactionStore.d.ts +20 -0
- package/dist/transactions/TransactionStore.js +53 -0
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +58 -0
- package/dist/transactions/coalesceRules.js +140 -0
- package/dist/transactions/commitPayload.d.ts +130 -0
- package/dist/transactions/commitPayload.js +143 -0
- package/dist/transactions/deltaConfirmation.d.ts +58 -0
- package/dist/transactions/deltaConfirmation.js +215 -0
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/replayValidation.d.ts +99 -0
- package/dist/transactions/replayValidation.js +111 -0
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +74 -80
- package/dist/types/index.js +22 -27
- package/dist/types/modelData.d.ts +10 -0
- package/dist/types/modelData.js +9 -0
- package/dist/types/participant.d.ts +20 -0
- package/dist/types/participant.js +10 -0
- package/dist/types/streams.d.ts +216 -209
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +44 -100
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +35 -27
- package/dist/wire/errorEnvelope.js +38 -32
- package/dist/wire/frames.d.ts +150 -67
- package/dist/wire/frames.js +48 -1
- package/dist/wire/index.d.ts +18 -13
- package/dist/wire/index.js +36 -13
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +38 -0
- package/dist/wire/protocol.js +38 -0
- package/dist/wire/protocolVersion.d.ts +60 -0
- package/dist/wire/protocolVersion.js +67 -0
- package/docs/api-keys.md +4 -3
- package/docs/coordination.md +59 -0
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +24 -21
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -1
- package/dist/server/storage-mode.js +0 -18
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* InstanceCache is the in-memory cache of live model instances, keyed by id and
|
|
3
|
+
* deduplicated so each entity has a single instance. It maintains type and
|
|
4
|
+
* foreign-key indexes for fast lookups, evicts entries under a size cap, and
|
|
5
|
+
* notifies subscribers and query views as models are added, updated, or
|
|
6
|
+
* removed. It holds a {@link ModelRegistry} to map between model names and
|
|
7
|
+
* constructor classes, but performs no persistence of its own.
|
|
6
8
|
*/
|
|
7
9
|
import { makeObservable, observable, action, computed, runInAction } from 'mobx';
|
|
10
|
+
import { Model } from './Model.js';
|
|
11
|
+
import { ModelRegistry } from './ModelRegistry.js';
|
|
8
12
|
import { getContext } from './context.js';
|
|
9
13
|
import { AbloValidationError } from './errors.js';
|
|
10
14
|
import { ModelScope } from './types/index.js';
|
|
11
15
|
import { ViewRegistry } from './core/ViewRegistry.js';
|
|
12
16
|
import { QueryView } from './core/QueryView.js';
|
|
13
|
-
// Re-
|
|
17
|
+
// Re-exported so `import { ModelScope } from './InstanceCache.js'` resolves
|
|
14
18
|
export { ModelScope };
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
20
|
+
* The in-memory cache of model instances, keyed by id and deduplicated so each
|
|
21
|
+
* entity resolves to a single instance.
|
|
17
22
|
*/
|
|
18
|
-
export class
|
|
23
|
+
export class InstanceCache {
|
|
19
24
|
// Single source of truth for all models (observable for reactivity)
|
|
20
25
|
entries = observable.map();
|
|
21
26
|
typeIndex = observable.map();
|
|
@@ -26,9 +31,10 @@ export class ObjectPool {
|
|
|
26
31
|
// Deduplication tracking
|
|
27
32
|
recentAdditions = new Map(); // "modelType:modelId" -> timestamp
|
|
28
33
|
deltaHistory = new Map();
|
|
29
|
-
// No intermediate cache layer — getByType() reads typeIndex
|
|
30
|
-
//
|
|
31
|
-
// reactivity source
|
|
34
|
+
// No intermediate cache layer — getByType() reads typeIndex and entries
|
|
35
|
+
// directly. Both are observable, so the data structures are themselves the
|
|
36
|
+
// reactivity source; there are no computed getters with conditional cache
|
|
37
|
+
// invalidation to get wrong.
|
|
32
38
|
// Foreign key indexes: Map<"ModelType:fieldName", Map<fieldValue, ObservableSet<modelId>>>
|
|
33
39
|
// Enables O(1) lookups like "all SlideLayer models where slideId = X"
|
|
34
40
|
// instead of scanning all models of a type and filtering.
|
|
@@ -62,10 +68,11 @@ export class ObjectPool {
|
|
|
62
68
|
code: 'pool_subscribe_unregistered',
|
|
63
69
|
});
|
|
64
70
|
}
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
let subs = this.subscriptions.get(modelName);
|
|
72
|
+
if (!subs) {
|
|
73
|
+
subs = new Set();
|
|
74
|
+
this.subscriptions.set(modelName, subs);
|
|
67
75
|
}
|
|
68
|
-
const subs = this.subscriptions.get(modelName);
|
|
69
76
|
const erased = callback;
|
|
70
77
|
subs.add(erased);
|
|
71
78
|
return () => subs.delete(erased);
|
|
@@ -99,15 +106,14 @@ export class ObjectPool {
|
|
|
99
106
|
gcInterval: config.gcInterval ?? 60000, // 1 minute
|
|
100
107
|
useWeakRefs: config.useWeakRefs ?? true,
|
|
101
108
|
};
|
|
102
|
-
//
|
|
109
|
+
// Store the model registry reference
|
|
103
110
|
if (!modelRegistry) {
|
|
104
|
-
throw new AbloValidationError('
|
|
111
|
+
throw new AbloValidationError('InstanceCache requires ModelRegistry for production-safe model name lookup', { code: 'pool_registry_missing' });
|
|
105
112
|
}
|
|
106
113
|
this.registry = modelRegistry;
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// Linear-style: no computed cache layer. entries + typeIndex are both observable.
|
|
114
|
+
// Type indexes are initialized on first use, so models can be registered
|
|
115
|
+
// after the InstanceCache is created; the first getByType call builds them.
|
|
116
|
+
// No computed cache layer: entries and typeIndex are both observable, and
|
|
111
117
|
// getByType() reads them directly, so MobX always tracks the dependency.
|
|
112
118
|
makeObservable(this, {
|
|
113
119
|
add: action,
|
|
@@ -123,24 +129,11 @@ export class ObjectPool {
|
|
|
123
129
|
});
|
|
124
130
|
this.startGC();
|
|
125
131
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
initializeTypeIndexes() {
|
|
132
|
-
const names = this.registry.getRegisteredModelNames();
|
|
133
|
-
for (const modelName of names) {
|
|
134
|
-
if (!this.typeIndex.has(modelName)) {
|
|
135
|
-
this.typeIndex.set(modelName, observable.set());
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// No computed getters — getByType() reads typeIndex + entries directly.
|
|
140
|
-
// This eliminates the conditional dependency bug where MobX lost tracking
|
|
141
|
-
// because _cacheInvalid (non-observable) gated whether entries was read.
|
|
142
|
-
// _rebuildCaches and _invalidateCache removed — no cache layer to manage.
|
|
143
|
-
// typeIndex + entries are observable and read directly by getByType().
|
|
132
|
+
// No computed getters — getByType() reads typeIndex and entries directly.
|
|
133
|
+
// Both are observable, so MobX always tracks the dependency; there is no
|
|
134
|
+
// conditional cache path that could silently drop a dependency.
|
|
135
|
+
// There is no cache layer to manage: typeIndex and entries are observable
|
|
136
|
+
// and read directly by getByType().
|
|
144
137
|
resolveModel(entry, id) {
|
|
145
138
|
if (entry.model)
|
|
146
139
|
return entry.model;
|
|
@@ -155,6 +148,10 @@ export class ObjectPool {
|
|
|
155
148
|
}
|
|
156
149
|
return undefined;
|
|
157
150
|
}
|
|
151
|
+
// The type parameter appears only in the return position on purpose: this is
|
|
152
|
+
// an ergonomic typed accessor (like Map<K, V>.get) that centralizes what would
|
|
153
|
+
// otherwise be an `as T` cast at every call site.
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
158
155
|
get(id) {
|
|
159
156
|
const entry = this.entries.get(id);
|
|
160
157
|
if (!entry) {
|
|
@@ -203,7 +200,7 @@ export class ObjectPool {
|
|
|
203
200
|
}
|
|
204
201
|
// Check if model already exists to prevent duplicates
|
|
205
202
|
const existingEntry = this.entries.get(id);
|
|
206
|
-
if (existingEntry
|
|
203
|
+
if (existingEntry?.model && !existingEntry.model.disposed) {
|
|
207
204
|
// Model already exists and is valid, update its scope if needed
|
|
208
205
|
if (existingEntry.scope !== scope) {
|
|
209
206
|
runInAction(() => {
|
|
@@ -234,12 +231,12 @@ export class ObjectPool {
|
|
|
234
231
|
(history.lastAction === 'U' || history.lastAction === 'D')) {
|
|
235
232
|
// Internal delta-ordering anomaly that reconciles on the next
|
|
236
233
|
// catch-up — forensic, not consumer-actionable → debug.
|
|
237
|
-
getContext().logger.debug(`
|
|
234
|
+
getContext().logger.debug(`InstanceCache.add() SUSPICIOUS: INSERT after ${history.lastAction}`, { modelType, id, syncId: deltaInfo.syncId });
|
|
238
235
|
}
|
|
239
236
|
}
|
|
240
237
|
// Update delta history
|
|
241
238
|
this.deltaHistory.set(addKey, {
|
|
242
|
-
lastAction: deltaInfo.action
|
|
239
|
+
lastAction: deltaInfo.action ?? 'U',
|
|
243
240
|
lastSyncId: deltaInfo.syncId,
|
|
244
241
|
timestamp: Date.now(),
|
|
245
242
|
});
|
|
@@ -320,7 +317,7 @@ export class ObjectPool {
|
|
|
320
317
|
}
|
|
321
318
|
// Skip if model already exists and is valid
|
|
322
319
|
const existingEntry = this.entries.get(id);
|
|
323
|
-
if (existingEntry
|
|
320
|
+
if (existingEntry?.model && !existingEntry.model.disposed) {
|
|
324
321
|
if (existingEntry.scope !== scope) {
|
|
325
322
|
this.entries.set(id, { ...existingEntry, scope });
|
|
326
323
|
this.accessTimes.set(id, now);
|
|
@@ -348,7 +345,7 @@ export class ObjectPool {
|
|
|
348
345
|
// loader (`ensureDeckLayers`, `prefetchSlideLayers`, bootstrap
|
|
349
346
|
// hydration) was in the pool but invisible to `hasMany` lookups
|
|
350
347
|
// — `slide.layers` returned `[]` until the user clicked a layer
|
|
351
|
-
// and
|
|
348
|
+
// and something else ran a non-batch `add` that happened to
|
|
352
349
|
// populate the FK index as a side effect. The UX symptom was
|
|
353
350
|
// "slides show empty until you click on one." Adding this one
|
|
354
351
|
// line closes the gap.
|
|
@@ -421,8 +418,8 @@ export class ObjectPool {
|
|
|
421
418
|
const entry = this.entries.get(id);
|
|
422
419
|
if (!entry)
|
|
423
420
|
continue;
|
|
424
|
-
const modelName = entry.model?.getModelName()
|
|
425
|
-
// FK/type cleanup must run
|
|
421
|
+
const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
|
|
422
|
+
// FK/type cleanup must run before entries.delete — see `remove()`
|
|
426
423
|
// for the full explanation. Same bug, same fix.
|
|
427
424
|
this.removeFromTypeIndex(id, modelName);
|
|
428
425
|
this.entries.delete(id);
|
|
@@ -430,8 +427,10 @@ export class ObjectPool {
|
|
|
430
427
|
if (modelName) {
|
|
431
428
|
this.viewRegistry.notifyRemoved(modelName, id);
|
|
432
429
|
}
|
|
433
|
-
const model = entry.model
|
|
434
|
-
|
|
430
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
431
|
+
// A non-Model object can reach the pool (see `clear`); only dispose a real one.
|
|
432
|
+
if (typeof model?.dispose === 'function')
|
|
433
|
+
model.dispose();
|
|
435
434
|
const addKey = modelName ? `${modelName}:${id}` : id;
|
|
436
435
|
this.recentAdditions.delete(addKey);
|
|
437
436
|
this.deltaHistory.delete(addKey);
|
|
@@ -455,7 +454,7 @@ export class ObjectPool {
|
|
|
455
454
|
const entry = this.entries.get(id);
|
|
456
455
|
if (!entry)
|
|
457
456
|
return false;
|
|
458
|
-
const modelName = entry.model?.getModelName()
|
|
457
|
+
const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
|
|
459
458
|
// Order matters here: `removeFromTypeIndex` → `removeFromForeignKeyIndex`
|
|
460
459
|
// reads the FK field values off the model via `this.entries.get(id)`.
|
|
461
460
|
// If we `this.entries.delete(id)` first, the model is gone and the
|
|
@@ -463,7 +462,7 @@ export class ObjectPool {
|
|
|
463
462
|
// That causes `getByForeignKey(..., parentId)` to report
|
|
464
463
|
// `matched > returned` (dropped-no-entry) and, on the UI, keeps the
|
|
465
464
|
// stale layer visible until the next reload rebuilds the index
|
|
466
|
-
// from fresh data. Do the FK/type cleanup
|
|
465
|
+
// from fresh data. Do the FK/type cleanup first, then delete the
|
|
467
466
|
// entry.
|
|
468
467
|
runInAction(() => {
|
|
469
468
|
this.removeFromTypeIndex(id, modelName);
|
|
@@ -474,8 +473,10 @@ export class ObjectPool {
|
|
|
474
473
|
if (modelName) {
|
|
475
474
|
this.viewRegistry.notifyRemoved(modelName, id);
|
|
476
475
|
}
|
|
477
|
-
const model = entry.model
|
|
478
|
-
|
|
476
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
477
|
+
// A non-Model object can reach the pool (see `clear`); only dispose a real one.
|
|
478
|
+
if (typeof model?.dispose === 'function')
|
|
479
|
+
model.dispose();
|
|
479
480
|
// Clean tracking
|
|
480
481
|
const addKey = modelName ? `${modelName}:${id}` : id;
|
|
481
482
|
this.recentAdditions.delete(addKey);
|
|
@@ -485,14 +486,14 @@ export class ObjectPool {
|
|
|
485
486
|
}
|
|
486
487
|
removeFromArchive(id) {
|
|
487
488
|
const entry = this.entries.get(id);
|
|
488
|
-
if (
|
|
489
|
+
if (entry?.scope !== ModelScope.archived) {
|
|
489
490
|
return false;
|
|
490
491
|
}
|
|
491
492
|
return this.remove(id);
|
|
492
493
|
}
|
|
493
494
|
getByType(modelClass, scope = ModelScope.all) {
|
|
494
|
-
//
|
|
495
|
-
//
|
|
495
|
+
// Read typeIndex and entries directly. Both are observable maps, so MobX
|
|
496
|
+
// always tracks the dependency — there is no conditional cache path.
|
|
496
497
|
let actualModelName = this.registry.getModelNameFromConstructor(modelClass);
|
|
497
498
|
if (!actualModelName) {
|
|
498
499
|
actualModelName = this.registry.getModelNameFromConstructor(modelClass);
|
|
@@ -518,7 +519,7 @@ export class ObjectPool {
|
|
|
518
519
|
return [];
|
|
519
520
|
}
|
|
520
521
|
// Resolve each ID from entries (observable) with scope filtering.
|
|
521
|
-
// Note: we do
|
|
522
|
+
// Note: we do not check `instanceof modelClass`, because schema-generated
|
|
522
523
|
// dynamic classes and hand-coded classes are different constructors that
|
|
523
524
|
// both represent the same model type. The typeIndex lookup by name is
|
|
524
525
|
// authoritative — if the name matched, the model belongs to this type.
|
|
@@ -608,12 +609,12 @@ export class ObjectPool {
|
|
|
608
609
|
createFromData(data, ModelClass, opts) {
|
|
609
610
|
// Support multiple model identifier fields for backwards compatibility
|
|
610
611
|
const modelName = data.__typename ?? data.__class ?? data.modelName ?? 'Unknown';
|
|
611
|
-
const Constructor = ModelClass
|
|
612
|
+
const Constructor = ModelClass ?? this.registry.getModelByName(modelName);
|
|
612
613
|
if (!Constructor) {
|
|
613
|
-
if (
|
|
614
|
+
if (modelName === 'Unknown') {
|
|
614
615
|
// Malformed row with no type marker — dropped, but nothing the consumer
|
|
615
616
|
// can act on (the actionable schema-drift case is handled below) → debug.
|
|
616
|
-
getContext().logger.debug('
|
|
617
|
+
getContext().logger.debug('InstanceCache.createFromData: No model identifier found', { data });
|
|
617
618
|
getContext().modelDebugLogger?.logError('Unknown', 'CREATE', 'No model identifier found', data);
|
|
618
619
|
return null;
|
|
619
620
|
}
|
|
@@ -628,16 +629,16 @@ export class ObjectPool {
|
|
|
628
629
|
// isn't in your schema, so these rows are silently skipped. Keep at warn,
|
|
629
630
|
// consumer register (their model name + the `ablo status` fix); forensics ride debug.
|
|
630
631
|
getContext().logger.warn(`Received data for "${modelName}", which isn't in your schema — these rows will be skipped. Run \`ablo status\` to compare your local schema with the server.`);
|
|
631
|
-
getContext().logger.debug(`
|
|
632
|
+
getContext().logger.debug(`InstanceCache.createFromData: No constructor found for model "${modelName}"`, { data });
|
|
632
633
|
getContext().modelDebugLogger?.logError(modelName, 'CREATE', `No constructor found for model "${modelName}"`, data);
|
|
633
634
|
return null;
|
|
634
635
|
}
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
//
|
|
636
|
+
// If the model already exists, update it in place instead of creating a
|
|
637
|
+
// duplicate. Keeping the existing instance alive preserves React's
|
|
638
|
+
// references and MobX's observation tracking.
|
|
638
639
|
if (data.id && this.entries.has(data.id)) {
|
|
639
640
|
const existing = this.get(data.id);
|
|
640
|
-
if (existing
|
|
641
|
+
if (existing?.getModelName() === modelName) {
|
|
641
642
|
// Same ID and same type - update existing model with new data and return it
|
|
642
643
|
existing.updateFromData(data);
|
|
643
644
|
return existing;
|
|
@@ -656,7 +657,7 @@ export class ObjectPool {
|
|
|
656
657
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
657
658
|
// Internal construction failure — captured via observability below and
|
|
658
659
|
// re-fetched on resync; the stack is forensic → debug.
|
|
659
|
-
getContext().logger.debug(`[
|
|
660
|
+
getContext().logger.debug(`[InstanceCache.createFromData] FAILED ${modelName}`, { errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
660
661
|
getContext().observability.captureTransactionFailure({
|
|
661
662
|
context: 'createFromData',
|
|
662
663
|
modelName,
|
|
@@ -680,11 +681,8 @@ export class ObjectPool {
|
|
|
680
681
|
const preserveObserved = options.preserveObserved ?? false;
|
|
681
682
|
const preservedIds = [];
|
|
682
683
|
const preservedEntries = [];
|
|
683
|
-
let disposedCount = 0;
|
|
684
|
-
let checkedCount = 0;
|
|
685
684
|
for (const [id, entry] of this.entries) {
|
|
686
|
-
const model = entry.model
|
|
687
|
-
checkedCount++;
|
|
685
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
688
686
|
// Check if this model should be preserved (has active React observers)
|
|
689
687
|
if (preserveObserved &&
|
|
690
688
|
model &&
|
|
@@ -695,8 +693,12 @@ export class ObjectPool {
|
|
|
695
693
|
preservedEntries.push([id, entry]);
|
|
696
694
|
continue;
|
|
697
695
|
}
|
|
698
|
-
|
|
699
|
-
|
|
696
|
+
// `rowAsModel` only casts, so a non-Model object can reach the pool (see
|
|
697
|
+
// the SyncClient no-op UPDATE guard). Guard that `dispose` is actually
|
|
698
|
+
// callable — mirroring the `hasObservedCollections` typeof-check above —
|
|
699
|
+
// rather than assume every pooled entry is a real Model.
|
|
700
|
+
if (typeof model?.dispose === 'function')
|
|
701
|
+
model.dispose();
|
|
700
702
|
}
|
|
701
703
|
// Save access times for preserved entries before clearing
|
|
702
704
|
const preservedAccessTimes = new Map();
|
|
@@ -724,7 +726,7 @@ export class ObjectPool {
|
|
|
724
726
|
// Re-add preserved entries (also rebuilds foreign key indexes via addToTypeIndex)
|
|
725
727
|
for (const [id, entry] of preservedEntries) {
|
|
726
728
|
this.entries.set(id, entry);
|
|
727
|
-
const model = entry.model
|
|
729
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
728
730
|
if (model) {
|
|
729
731
|
this.addToTypeIndex(id, model.getModelName());
|
|
730
732
|
}
|
|
@@ -780,9 +782,9 @@ export class ObjectPool {
|
|
|
780
782
|
scopeCounts.live++;
|
|
781
783
|
else if (entry.scope === ModelScope.archived)
|
|
782
784
|
scopeCounts.archived++;
|
|
783
|
-
const modelName = entry.model?.getModelName()
|
|
785
|
+
const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
|
|
784
786
|
if (modelName) {
|
|
785
|
-
typeCounts.set(modelName, (typeCounts.get(modelName)
|
|
787
|
+
typeCounts.set(modelName, (typeCounts.get(modelName) ?? 0) + 1);
|
|
786
788
|
}
|
|
787
789
|
}
|
|
788
790
|
return {
|
|
@@ -823,12 +825,12 @@ export class ObjectPool {
|
|
|
823
825
|
let skippedObserved = 0;
|
|
824
826
|
for (const [id, entry] of this.entries) {
|
|
825
827
|
// Check if model has expired based on last access time
|
|
826
|
-
const lastAccessed = this.accessTimes.get(id)
|
|
828
|
+
const lastAccessed = this.accessTimes.get(id) ?? 0;
|
|
827
829
|
if (now - lastAccessed > this.config.maxAge) {
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
+
// Do not GC a model that has observed collections — disposing one
|
|
831
|
+
// React is still observing would break it (per MobX guidance).
|
|
830
832
|
// See: https://mobx.js.org/lazy-observables.html
|
|
831
|
-
const model = entry.model
|
|
833
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
832
834
|
if (model &&
|
|
833
835
|
typeof model.hasObservedCollections === 'function' &&
|
|
834
836
|
model.hasObservedCollections()) {
|
|
@@ -869,7 +871,7 @@ export class ObjectPool {
|
|
|
869
871
|
}
|
|
870
872
|
}
|
|
871
873
|
if (skippedObserved > 0) {
|
|
872
|
-
getContext().logger.debug(`[
|
|
874
|
+
getContext().logger.debug(`[InstanceCache GC] Skipped ${skippedObserved} models with active React observers`);
|
|
873
875
|
}
|
|
874
876
|
// Also clean up old tracking data
|
|
875
877
|
this.clearDeltaHistory();
|
|
@@ -881,6 +883,12 @@ export class ObjectPool {
|
|
|
881
883
|
if (this.gcTimer)
|
|
882
884
|
return;
|
|
883
885
|
this.gcTimer = setInterval(() => this.gc(), this.config.gcInterval);
|
|
886
|
+
// Don't hold a headless Node process open just for pool GC — without
|
|
887
|
+
// this, an agent that never calls disconnect() can never exit. No-op in
|
|
888
|
+
// browsers (where setInterval returns a number without `unref`), which is
|
|
889
|
+
// why the call is optional even though the Node timer type always has it.
|
|
890
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
891
|
+
this.gcTimer.unref?.();
|
|
884
892
|
}
|
|
885
893
|
stopGC() {
|
|
886
894
|
if (this.gcTimer) {
|
|
@@ -894,13 +902,13 @@ export class ObjectPool {
|
|
|
894
902
|
let oldestTime = Date.now();
|
|
895
903
|
for (const [id, entry] of this.entries) {
|
|
896
904
|
// Skip models that are being observed by React - they must stay alive
|
|
897
|
-
const model = entry.model
|
|
905
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
898
906
|
if (model &&
|
|
899
907
|
typeof model.hasObservedCollections === 'function' &&
|
|
900
908
|
model.hasObservedCollections()) {
|
|
901
909
|
continue;
|
|
902
910
|
}
|
|
903
|
-
const entryAccessTime = this.accessTimes.get(id)
|
|
911
|
+
const entryAccessTime = this.accessTimes.get(id) ?? 0;
|
|
904
912
|
if (entryAccessTime < oldestTime) {
|
|
905
913
|
oldest = [id, entry];
|
|
906
914
|
oldestTime = entryAccessTime;
|
|
@@ -964,7 +972,7 @@ export class ObjectPool {
|
|
|
964
972
|
getByForeignKey(modelName, fieldName, fieldValue) {
|
|
965
973
|
const indexKey = `${modelName}:${fieldName}`;
|
|
966
974
|
const index = this.foreignKeyIndexes.get(indexKey);
|
|
967
|
-
// Both empty-path early-returns below are
|
|
975
|
+
// Both empty-path early-returns below are normal states, not errors:
|
|
968
976
|
// a model with no FK index yet (not populated), or an index with no
|
|
969
977
|
// entry for this specific parent id (entity genuinely has no
|
|
970
978
|
// children). These used to `console.warn` diagnostic dumps on every
|
|
@@ -1005,7 +1013,7 @@ export class ObjectPool {
|
|
|
1005
1013
|
// has dangling refs (legacy orphan deltas, pending CREATE
|
|
1006
1014
|
// transactions, etc.). Noisy at warn level, useful during
|
|
1007
1015
|
// investigation.
|
|
1008
|
-
getContext().logger.debug('[
|
|
1016
|
+
getContext().logger.debug('[InstanceCache.getByForeignKey] ROWS DROPPED', {
|
|
1009
1017
|
modelName,
|
|
1010
1018
|
fieldName,
|
|
1011
1019
|
fieldValue,
|
|
@@ -1031,7 +1039,7 @@ export class ObjectPool {
|
|
|
1031
1039
|
if (!fields)
|
|
1032
1040
|
return;
|
|
1033
1041
|
for (const fieldName of fields) {
|
|
1034
|
-
const fieldValue =
|
|
1042
|
+
const fieldValue = model.getField(fieldName);
|
|
1035
1043
|
if (typeof fieldValue !== 'string')
|
|
1036
1044
|
continue;
|
|
1037
1045
|
const indexKey = `${modelName}:${fieldName}`;
|
|
@@ -1061,7 +1069,7 @@ export class ObjectPool {
|
|
|
1061
1069
|
if (!model)
|
|
1062
1070
|
return;
|
|
1063
1071
|
for (const fieldName of fields) {
|
|
1064
|
-
const fieldValue =
|
|
1072
|
+
const fieldValue = model.getField(fieldName);
|
|
1065
1073
|
if (typeof fieldValue !== 'string')
|
|
1066
1074
|
continue;
|
|
1067
1075
|
const indexKey = `${modelName}:${fieldName}`;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* data on-demand with intelligent caching and batching.
|
|
2
|
+
* LazyReferenceCollection implements a lazy-loaded one-to-many relationship.
|
|
3
|
+
* It loads related rows on demand, caches them, and batches lookups so that
|
|
4
|
+
* reading a parent's children does not trigger a query per parent.
|
|
6
5
|
*/
|
|
7
6
|
import type { Model } from './Model.js';
|
|
8
7
|
import { Database } from './Database.js';
|
|
9
|
-
import {
|
|
8
|
+
import { InstanceCache } from './InstanceCache.js';
|
|
10
9
|
/**
|
|
11
10
|
* Options for LazyReferenceCollection behavior
|
|
12
11
|
*/
|
|
@@ -23,14 +22,11 @@ export interface LazyCollectionOptions {
|
|
|
23
22
|
autoRefresh?: boolean;
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
* - Observable for React integration
|
|
32
|
-
* - Memory efficient with intelligent caching
|
|
33
|
-
* - Support for filtering and sorting
|
|
25
|
+
* A lazy-loaded one-to-many relationship between a parent {@link Model} and
|
|
26
|
+
* its children. It reads from the local store first and falls back to the
|
|
27
|
+
* network, batches lookups to avoid one query per parent, and is observable so
|
|
28
|
+
* a React component re-renders when the set of children changes. Loaded items
|
|
29
|
+
* can be filtered, sorted, and limited through {@link LazyCollectionOptions}.
|
|
34
30
|
*/
|
|
35
31
|
export declare class LazyReferenceCollection<T extends Model> {
|
|
36
32
|
private modelName;
|
|
@@ -45,7 +41,7 @@ export declare class LazyReferenceCollection<T extends Model> {
|
|
|
45
41
|
* Set global dependencies for all LazyReferenceCollection instances
|
|
46
42
|
* Called once during SyncedStore initialization
|
|
47
43
|
*/
|
|
48
|
-
static setDependencies(database: Database, objectPool:
|
|
44
|
+
static setDependencies(database: Database, objectPool: InstanceCache): void;
|
|
49
45
|
/**
|
|
50
46
|
* Clear dependencies (e.g., on logout/store disposal)
|
|
51
47
|
*/
|
|
@@ -82,7 +78,7 @@ export declare class LazyReferenceCollection<T extends Model> {
|
|
|
82
78
|
/**
|
|
83
79
|
* Get the collection value (triggers hydration if needed).
|
|
84
80
|
*
|
|
85
|
-
* Filters out items whose id is no longer in the
|
|
81
|
+
* Filters out items whose id is no longer in the InstanceCache. The
|
|
86
82
|
* local `items` array isn't auto-synced with `pool.remove()` — a
|
|
87
83
|
* deleted entity would linger here until hydrate() re-runs on
|
|
88
84
|
* reload. Reading `pool.has(item.id)` inside this computed getter
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* data on-demand with intelligent caching and batching.
|
|
2
|
+
* LazyReferenceCollection implements a lazy-loaded one-to-many relationship.
|
|
3
|
+
* It loads related rows on demand, caches them, and batches lookups so that
|
|
4
|
+
* reading a parent's children does not trigger a query per parent.
|
|
6
5
|
*/
|
|
7
6
|
import { makeObservable, observable, action, computed, onBecomeObserved, onBecomeUnobserved, } from 'mobx';
|
|
7
|
+
import { Database } from './Database.js';
|
|
8
|
+
import { InstanceCache } from './InstanceCache.js';
|
|
8
9
|
import { getActiveRegistry } from './ModelRegistry.js';
|
|
9
10
|
import { AbloValidationError } from './errors.js';
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
* - Observable for React integration
|
|
17
|
-
* - Memory efficient with intelligent caching
|
|
18
|
-
* - Support for filtering and sorting
|
|
12
|
+
* A lazy-loaded one-to-many relationship between a parent {@link Model} and
|
|
13
|
+
* its children. It reads from the local store first and falls back to the
|
|
14
|
+
* network, batches lookups to avoid one query per parent, and is observable so
|
|
15
|
+
* a React component re-renders when the set of children changes. Loaded items
|
|
16
|
+
* can be filtered, sorted, and limited through {@link LazyCollectionOptions}.
|
|
19
17
|
*/
|
|
20
18
|
export class LazyReferenceCollection {
|
|
21
19
|
modelName;
|
|
@@ -130,7 +128,7 @@ export class LazyReferenceCollection {
|
|
|
130
128
|
/**
|
|
131
129
|
* Get the collection value (triggers hydration if needed).
|
|
132
130
|
*
|
|
133
|
-
* Filters out items whose id is no longer in the
|
|
131
|
+
* Filters out items whose id is no longer in the InstanceCache. The
|
|
134
132
|
* local `items` array isn't auto-synced with `pool.remove()` — a
|
|
135
133
|
* deleted entity would linger here until hydrate() re-runs on
|
|
136
134
|
* reload. Reading `pool.has(item.id)` inside this computed getter
|
|
@@ -260,7 +258,7 @@ export class LazyReferenceCollection {
|
|
|
260
258
|
throw new AbloValidationError(`Database dependency not provided to LazyReferenceCollection for ${this.modelName}`, { code: 'lazy_ref_db_missing' });
|
|
261
259
|
}
|
|
262
260
|
if (!this.objectPool) {
|
|
263
|
-
throw new AbloValidationError(`
|
|
261
|
+
throw new AbloValidationError(`InstanceCache dependency not provided to LazyReferenceCollection for ${this.modelName}`, { code: 'lazy_ref_pool_missing' });
|
|
264
262
|
}
|
|
265
263
|
const store = this.database.getStore(this.modelName);
|
|
266
264
|
const rawData = store ? await store.getAllFromIndex(this.foreignKey, this.parent.id) : [];
|
|
@@ -280,7 +278,7 @@ export class LazyReferenceCollection {
|
|
|
280
278
|
// narrow-checked here rather than assumed.
|
|
281
279
|
if (typeof id !== 'string')
|
|
282
280
|
continue;
|
|
283
|
-
// Check if already in
|
|
281
|
+
// Check if already in InstanceCache
|
|
284
282
|
let model = this.objectPool.get(id);
|
|
285
283
|
if (!model) {
|
|
286
284
|
// Create new model instance
|
|
@@ -289,6 +287,9 @@ export class LazyReferenceCollection {
|
|
|
289
287
|
this.objectPool.add(model);
|
|
290
288
|
}
|
|
291
289
|
if (model) {
|
|
290
|
+
// The by-name registry lookup types instances as the base
|
|
291
|
+
// `Model`; this collection's `modelName` IS `T`'s registered
|
|
292
|
+
// name (fixed at construction), so the instance is a `T`.
|
|
292
293
|
models.push(model);
|
|
293
294
|
}
|
|
294
295
|
}
|