@abloatai/ablo 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +101 -85
- package/dist/BaseSyncedStore.d.ts +85 -88
- package/dist/BaseSyncedStore.js +131 -147
- package/dist/Database.d.ts +54 -68
- package/dist/Database.js +97 -113
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +12 -16
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +46 -61
- package/dist/ModelRegistry.d.ts +21 -19
- package/dist/ModelRegistry.js +23 -27
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +5 -6
- package/dist/SyncClient.d.ts +112 -112
- package/dist/SyncClient.js +165 -172
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +27 -32
- package/dist/agent/Agent.js +18 -19
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +6 -7
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +69 -74
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +6 -5
- package/dist/auth/credentialSource.js +9 -10
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +31 -37
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +167 -119
- package/dist/client/Ablo.d.ts +73 -73
- package/dist/client/Ablo.js +125 -160
- package/dist/client/ApiClient.d.ts +30 -19
- package/dist/client/ApiClient.js +133 -38
- package/dist/client/auth.d.ts +47 -47
- package/dist/client/auth.js +108 -117
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +5 -6
- package/dist/client/consoleLogger.js +5 -6
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +25 -30
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +152 -122
- package/dist/client/credentialEndpoint.d.ts +40 -42
- package/dist/client/credentialEndpoint.js +35 -36
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +9 -12
- package/dist/client/hostedEndpoints.js +9 -12
- package/dist/client/httpClient.d.ts +57 -53
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +47 -58
- package/dist/client/modelRegistration.d.ts +5 -9
- package/dist/client/modelRegistration.js +67 -87
- package/dist/client/options.d.ts +134 -157
- package/dist/client/options.js +3 -7
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +64 -75
- package/dist/client/resourceTypes.js +4 -10
- package/dist/client/schemaConfig.d.ts +31 -43
- package/dist/client/schemaConfig.js +38 -50
- package/dist/client/sessionMint.d.ts +16 -12
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +8 -9
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +15 -20
- package/dist/client/wsMutationExecutor.js +17 -23
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -4
- package/dist/coordination/index.d.ts +10 -8
- package/dist/coordination/index.js +14 -12
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -10
- package/dist/coordination/trace.js +13 -14
- package/dist/core/DatabaseManager.d.ts +5 -7
- package/dist/core/DatabaseManager.js +15 -19
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +22 -28
- package/dist/core/QueryView.d.ts +8 -8
- package/dist/core/QueryView.js +2 -2
- package/dist/core/StoreManager.d.ts +12 -14
- package/dist/core/StoreManager.js +21 -24
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +17 -12
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +42 -43
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +63 -61
- package/dist/core/storeContract.js +8 -12
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +107 -99
- package/dist/errorCodes.js +131 -132
- package/dist/errors.d.ts +160 -166
- package/dist/errors.js +155 -158
- package/dist/index.d.ts +30 -27
- package/dist/index.js +89 -86
- package/dist/interfaces/index.d.ts +102 -113
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +41 -40
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +145 -156
- package/dist/mutators/defineMutators.d.ts +23 -34
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +104 -100
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +28 -23
- package/dist/query/client.js +45 -43
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +2 -2
- package/dist/react/context.d.ts +25 -28
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +16 -14
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +10 -11
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +8 -8
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useReactive.js +2 -2
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +20 -24
- package/dist/schema/ddlLock.js +18 -23
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +35 -48
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +21 -23
- package/dist/schema/model.d.ts +118 -143
- package/dist/schema/model.js +22 -33
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +5 -3
- package/dist/schema/queries.d.ts +29 -31
- package/dist/schema/queries.js +23 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +16 -13
- package/dist/schema/residency.js +16 -13
- package/dist/schema/roles.d.ts +36 -43
- package/dist/schema/roles.js +31 -37
- package/dist/schema/schema.d.ts +33 -42
- package/dist/schema/schema.js +31 -32
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +28 -31
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +30 -25
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +30 -25
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +24 -23
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +9 -8
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +22 -25
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +17 -11
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +28 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +46 -58
- package/dist/source/factory.js +22 -27
- package/dist/source/index.d.ts +7 -9
- package/dist/source/index.js +12 -14
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +9 -10
- package/dist/source/next.js +6 -7
- package/dist/source/pushQueue.d.ts +69 -47
- package/dist/source/pushQueue.js +32 -28
- package/dist/source/signing.d.ts +46 -17
- package/dist/source/signing.js +28 -11
- package/dist/source/types.d.ts +121 -104
- package/dist/source/types.js +13 -14
- package/dist/stores/ObjectStore.d.ts +10 -11
- package/dist/stores/ObjectStore.js +11 -12
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +7 -11
- package/dist/stores/SyncActionStore.js +13 -17
- package/dist/surface.d.ts +27 -20
- package/dist/surface.js +27 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
- package/dist/sync/ConnectionManager.d.ts +39 -50
- package/dist/sync/ConnectionManager.js +55 -66
- package/dist/sync/NetworkProbe.d.ts +24 -29
- package/dist/sync/NetworkProbe.js +63 -69
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
- package/dist/sync/SyncWebSocket.d.ts +139 -165
- package/dist/sync/SyncWebSocket.js +191 -223
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +11 -11
- package/dist/sync/bootstrapApply.d.ts +34 -24
- package/dist/sync/bootstrapApply.js +27 -19
- package/dist/sync/commitFrames.d.ts +21 -20
- package/dist/sync/commitFrames.js +18 -18
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +105 -23
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +12 -14
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +104 -104
- package/dist/sync/credentialLifecycle.js +140 -147
- package/dist/sync/deltaPipeline.d.ts +36 -34
- package/dist/sync/deltaPipeline.js +64 -65
- package/dist/sync/groupChange.d.ts +63 -61
- package/dist/sync/groupChange.js +74 -78
- package/dist/sync/heartbeat.d.ts +34 -33
- package/dist/sync/heartbeat.js +31 -31
- package/dist/sync/participants.d.ts +19 -19
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +17 -21
- package/dist/sync/syncCursor.js +17 -21
- package/dist/sync/syncPlan.d.ts +28 -36
- package/dist/sync/syncPlan.js +18 -19
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +35 -36
- package/dist/sync/wsFrameHandlers.js +63 -67
- package/dist/testing/fixtures/bootstrap.d.ts +12 -6
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +30 -33
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +11 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +2 -2
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
- package/dist/testing/mocks/MockSyncContext.js +15 -13
- package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +181 -176
- package/dist/transactions/TransactionQueue.js +338 -350
- package/dist/transactions/TransactionStore.d.ts +6 -4
- package/dist/transactions/TransactionStore.js +6 -4
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +41 -17
- package/dist/transactions/coalesceRules.js +40 -17
- package/dist/transactions/commitPayload.d.ts +48 -52
- package/dist/transactions/commitPayload.js +48 -57
- package/dist/transactions/deltaConfirmation.d.ts +20 -22
- package/dist/transactions/deltaConfirmation.js +37 -45
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
- package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +71 -77
- package/dist/types/index.js +22 -22
- package/dist/types/modelData.d.ts +6 -8
- package/dist/types/modelData.js +5 -7
- package/dist/types/participant.d.ts +10 -11
- package/dist/types/participant.js +6 -8
- package/dist/types/streams.d.ts +208 -195
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +30 -31
- package/dist/wire/errorEnvelope.js +34 -40
- package/dist/wire/frames.d.ts +79 -86
- package/dist/wire/frames.js +26 -33
- package/dist/wire/index.d.ts +14 -12
- package/dist/wire/index.js +30 -26
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +25 -32
- package/dist/wire/protocol.js +25 -32
- package/dist/wire/protocolVersion.d.ts +44 -40
- package/dist/wire/protocolVersion.js +44 -40
- package/docs/coordination.md +59 -0
- package/package.json +11 -10
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -8
- package/dist/server/storage-mode.js +0 -28
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/transactions/optimistic.d.ts +0 -24
- package/dist/transactions/optimistic.js +0 -45
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,8 +1,10 @@
|
|
|
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 { Model } from './Model.js';
|
|
8
10
|
import { ModelRegistry } from './ModelRegistry.js';
|
|
@@ -23,9 +25,10 @@ interface DeltaInfo {
|
|
|
23
25
|
syncId?: number;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
28
|
+
* The in-memory cache of model instances, keyed by id and deduplicated so each
|
|
29
|
+
* entity resolves to a single instance.
|
|
27
30
|
*/
|
|
28
|
-
export declare class
|
|
31
|
+
export declare class InstanceCache {
|
|
29
32
|
private entries;
|
|
30
33
|
private typeIndex;
|
|
31
34
|
private accessTimes;
|
|
@@ -45,12 +48,6 @@ export declare class ObjectPool {
|
|
|
45
48
|
subscribe<T extends Model>(modelClass: ModelConstructor<T>, callback: (model: T) => void): () => void;
|
|
46
49
|
private notifySubscribers;
|
|
47
50
|
constructor(config?: PoolConfig, modelRegistry?: ModelRegistry);
|
|
48
|
-
/**
|
|
49
|
-
* 🔧 PRODUCTION FIX: Initialize type indexes for all registered models
|
|
50
|
-
* This prevents the "No type index found" error in production where constructor
|
|
51
|
-
* references are lost due to minification.
|
|
52
|
-
*/
|
|
53
|
-
private initializeTypeIndexes;
|
|
54
51
|
private resolveModel;
|
|
55
52
|
get<T extends Model = Model>(id: string): T | undefined;
|
|
56
53
|
/**
|
|
@@ -115,7 +112,7 @@ export declare class ObjectPool {
|
|
|
115
112
|
/**
|
|
116
113
|
* Throw (instead of warn + return null) when the wire names a model
|
|
117
114
|
* this client never registered. Passed by the developer-await read
|
|
118
|
-
* path (`
|
|
115
|
+
* path (`OnDemandLoader`'s network leg) so a schema collision
|
|
119
116
|
* — the org's pushed schema names `Document` but this client only
|
|
120
117
|
* registered `documents` — surfaces AT the row that failed, with the
|
|
121
118
|
* known-model list and `ablo status` pointer, instead of bubbling up
|
|
@@ -1,10 +1,12 @@
|
|
|
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
|
-
import { makeObservable, observable, action, computed, runInAction
|
|
9
|
+
import { makeObservable, observable, action, computed, runInAction } from 'mobx';
|
|
8
10
|
import { Model } from './Model.js';
|
|
9
11
|
import { ModelRegistry } from './ModelRegistry.js';
|
|
10
12
|
import { getContext } from './context.js';
|
|
@@ -12,12 +14,13 @@ import { AbloValidationError } from './errors.js';
|
|
|
12
14
|
import { ModelScope } from './types/index.js';
|
|
13
15
|
import { ViewRegistry } from './core/ViewRegistry.js';
|
|
14
16
|
import { QueryView } from './core/QueryView.js';
|
|
15
|
-
// Re-
|
|
17
|
+
// Re-exported so `import { ModelScope } from './InstanceCache.js'` resolves
|
|
16
18
|
export { ModelScope };
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
20
|
+
* The in-memory cache of model instances, keyed by id and deduplicated so each
|
|
21
|
+
* entity resolves to a single instance.
|
|
19
22
|
*/
|
|
20
|
-
export class
|
|
23
|
+
export class InstanceCache {
|
|
21
24
|
// Single source of truth for all models (observable for reactivity)
|
|
22
25
|
entries = observable.map();
|
|
23
26
|
typeIndex = observable.map();
|
|
@@ -28,9 +31,10 @@ export class ObjectPool {
|
|
|
28
31
|
// Deduplication tracking
|
|
29
32
|
recentAdditions = new Map(); // "modelType:modelId" -> timestamp
|
|
30
33
|
deltaHistory = new Map();
|
|
31
|
-
// No intermediate cache layer — getByType() reads typeIndex
|
|
32
|
-
//
|
|
33
|
-
// 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.
|
|
34
38
|
// Foreign key indexes: Map<"ModelType:fieldName", Map<fieldValue, ObservableSet<modelId>>>
|
|
35
39
|
// Enables O(1) lookups like "all SlideLayer models where slideId = X"
|
|
36
40
|
// instead of scanning all models of a type and filtering.
|
|
@@ -64,10 +68,11 @@ export class ObjectPool {
|
|
|
64
68
|
code: 'pool_subscribe_unregistered',
|
|
65
69
|
});
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
let subs = this.subscriptions.get(modelName);
|
|
72
|
+
if (!subs) {
|
|
73
|
+
subs = new Set();
|
|
74
|
+
this.subscriptions.set(modelName, subs);
|
|
69
75
|
}
|
|
70
|
-
const subs = this.subscriptions.get(modelName);
|
|
71
76
|
const erased = callback;
|
|
72
77
|
subs.add(erased);
|
|
73
78
|
return () => subs.delete(erased);
|
|
@@ -101,15 +106,14 @@ export class ObjectPool {
|
|
|
101
106
|
gcInterval: config.gcInterval ?? 60000, // 1 minute
|
|
102
107
|
useWeakRefs: config.useWeakRefs ?? true,
|
|
103
108
|
};
|
|
104
|
-
//
|
|
109
|
+
// Store the model registry reference
|
|
105
110
|
if (!modelRegistry) {
|
|
106
|
-
throw new AbloValidationError('
|
|
111
|
+
throw new AbloValidationError('InstanceCache requires ModelRegistry for production-safe model name lookup', { code: 'pool_registry_missing' });
|
|
107
112
|
}
|
|
108
113
|
this.registry = modelRegistry;
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// 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
|
|
113
117
|
// getByType() reads them directly, so MobX always tracks the dependency.
|
|
114
118
|
makeObservable(this, {
|
|
115
119
|
add: action,
|
|
@@ -125,24 +129,11 @@ export class ObjectPool {
|
|
|
125
129
|
});
|
|
126
130
|
this.startGC();
|
|
127
131
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
initializeTypeIndexes() {
|
|
134
|
-
const names = this.registry.getRegisteredModelNames();
|
|
135
|
-
for (const modelName of names) {
|
|
136
|
-
if (!this.typeIndex.has(modelName)) {
|
|
137
|
-
this.typeIndex.set(modelName, observable.set());
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// No computed getters — getByType() reads typeIndex + entries directly.
|
|
142
|
-
// This eliminates the conditional dependency bug where MobX lost tracking
|
|
143
|
-
// because _cacheInvalid (non-observable) gated whether entries was read.
|
|
144
|
-
// _rebuildCaches and _invalidateCache removed — no cache layer to manage.
|
|
145
|
-
// 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().
|
|
146
137
|
resolveModel(entry, id) {
|
|
147
138
|
if (entry.model)
|
|
148
139
|
return entry.model;
|
|
@@ -157,6 +148,10 @@ export class ObjectPool {
|
|
|
157
148
|
}
|
|
158
149
|
return undefined;
|
|
159
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
|
|
160
155
|
get(id) {
|
|
161
156
|
const entry = this.entries.get(id);
|
|
162
157
|
if (!entry) {
|
|
@@ -236,12 +231,12 @@ export class ObjectPool {
|
|
|
236
231
|
(history.lastAction === 'U' || history.lastAction === 'D')) {
|
|
237
232
|
// Internal delta-ordering anomaly that reconciles on the next
|
|
238
233
|
// catch-up — forensic, not consumer-actionable → debug.
|
|
239
|
-
getContext().logger.debug(`
|
|
234
|
+
getContext().logger.debug(`InstanceCache.add() SUSPICIOUS: INSERT after ${history.lastAction}`, { modelType, id, syncId: deltaInfo.syncId });
|
|
240
235
|
}
|
|
241
236
|
}
|
|
242
237
|
// Update delta history
|
|
243
238
|
this.deltaHistory.set(addKey, {
|
|
244
|
-
lastAction: deltaInfo.action
|
|
239
|
+
lastAction: deltaInfo.action ?? 'U',
|
|
245
240
|
lastSyncId: deltaInfo.syncId,
|
|
246
241
|
timestamp: Date.now(),
|
|
247
242
|
});
|
|
@@ -350,7 +345,7 @@ export class ObjectPool {
|
|
|
350
345
|
// loader (`ensureDeckLayers`, `prefetchSlideLayers`, bootstrap
|
|
351
346
|
// hydration) was in the pool but invisible to `hasMany` lookups
|
|
352
347
|
// — `slide.layers` returned `[]` until the user clicked a layer
|
|
353
|
-
// and
|
|
348
|
+
// and something else ran a non-batch `add` that happened to
|
|
354
349
|
// populate the FK index as a side effect. The UX symptom was
|
|
355
350
|
// "slides show empty until you click on one." Adding this one
|
|
356
351
|
// line closes the gap.
|
|
@@ -423,8 +418,8 @@ export class ObjectPool {
|
|
|
423
418
|
const entry = this.entries.get(id);
|
|
424
419
|
if (!entry)
|
|
425
420
|
continue;
|
|
426
|
-
const modelName = entry.model?.getModelName()
|
|
427
|
-
// 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()`
|
|
428
423
|
// for the full explanation. Same bug, same fix.
|
|
429
424
|
this.removeFromTypeIndex(id, modelName);
|
|
430
425
|
this.entries.delete(id);
|
|
@@ -432,8 +427,10 @@ export class ObjectPool {
|
|
|
432
427
|
if (modelName) {
|
|
433
428
|
this.viewRegistry.notifyRemoved(modelName, id);
|
|
434
429
|
}
|
|
435
|
-
const model = entry.model
|
|
436
|
-
|
|
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();
|
|
437
434
|
const addKey = modelName ? `${modelName}:${id}` : id;
|
|
438
435
|
this.recentAdditions.delete(addKey);
|
|
439
436
|
this.deltaHistory.delete(addKey);
|
|
@@ -457,7 +454,7 @@ export class ObjectPool {
|
|
|
457
454
|
const entry = this.entries.get(id);
|
|
458
455
|
if (!entry)
|
|
459
456
|
return false;
|
|
460
|
-
const modelName = entry.model?.getModelName()
|
|
457
|
+
const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
|
|
461
458
|
// Order matters here: `removeFromTypeIndex` → `removeFromForeignKeyIndex`
|
|
462
459
|
// reads the FK field values off the model via `this.entries.get(id)`.
|
|
463
460
|
// If we `this.entries.delete(id)` first, the model is gone and the
|
|
@@ -465,7 +462,7 @@ export class ObjectPool {
|
|
|
465
462
|
// That causes `getByForeignKey(..., parentId)` to report
|
|
466
463
|
// `matched > returned` (dropped-no-entry) and, on the UI, keeps the
|
|
467
464
|
// stale layer visible until the next reload rebuilds the index
|
|
468
|
-
// from fresh data. Do the FK/type cleanup
|
|
465
|
+
// from fresh data. Do the FK/type cleanup first, then delete the
|
|
469
466
|
// entry.
|
|
470
467
|
runInAction(() => {
|
|
471
468
|
this.removeFromTypeIndex(id, modelName);
|
|
@@ -476,8 +473,10 @@ export class ObjectPool {
|
|
|
476
473
|
if (modelName) {
|
|
477
474
|
this.viewRegistry.notifyRemoved(modelName, id);
|
|
478
475
|
}
|
|
479
|
-
const model = entry.model
|
|
480
|
-
|
|
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();
|
|
481
480
|
// Clean tracking
|
|
482
481
|
const addKey = modelName ? `${modelName}:${id}` : id;
|
|
483
482
|
this.recentAdditions.delete(addKey);
|
|
@@ -487,14 +486,14 @@ export class ObjectPool {
|
|
|
487
486
|
}
|
|
488
487
|
removeFromArchive(id) {
|
|
489
488
|
const entry = this.entries.get(id);
|
|
490
|
-
if (
|
|
489
|
+
if (entry?.scope !== ModelScope.archived) {
|
|
491
490
|
return false;
|
|
492
491
|
}
|
|
493
492
|
return this.remove(id);
|
|
494
493
|
}
|
|
495
494
|
getByType(modelClass, scope = ModelScope.all) {
|
|
496
|
-
//
|
|
497
|
-
//
|
|
495
|
+
// Read typeIndex and entries directly. Both are observable maps, so MobX
|
|
496
|
+
// always tracks the dependency — there is no conditional cache path.
|
|
498
497
|
let actualModelName = this.registry.getModelNameFromConstructor(modelClass);
|
|
499
498
|
if (!actualModelName) {
|
|
500
499
|
actualModelName = this.registry.getModelNameFromConstructor(modelClass);
|
|
@@ -520,7 +519,7 @@ export class ObjectPool {
|
|
|
520
519
|
return [];
|
|
521
520
|
}
|
|
522
521
|
// Resolve each ID from entries (observable) with scope filtering.
|
|
523
|
-
// Note: we do
|
|
522
|
+
// Note: we do not check `instanceof modelClass`, because schema-generated
|
|
524
523
|
// dynamic classes and hand-coded classes are different constructors that
|
|
525
524
|
// both represent the same model type. The typeIndex lookup by name is
|
|
526
525
|
// authoritative — if the name matched, the model belongs to this type.
|
|
@@ -610,12 +609,12 @@ export class ObjectPool {
|
|
|
610
609
|
createFromData(data, ModelClass, opts) {
|
|
611
610
|
// Support multiple model identifier fields for backwards compatibility
|
|
612
611
|
const modelName = data.__typename ?? data.__class ?? data.modelName ?? 'Unknown';
|
|
613
|
-
const Constructor = ModelClass
|
|
612
|
+
const Constructor = ModelClass ?? this.registry.getModelByName(modelName);
|
|
614
613
|
if (!Constructor) {
|
|
615
|
-
if (
|
|
614
|
+
if (modelName === 'Unknown') {
|
|
616
615
|
// Malformed row with no type marker — dropped, but nothing the consumer
|
|
617
616
|
// can act on (the actionable schema-drift case is handled below) → debug.
|
|
618
|
-
getContext().logger.debug('
|
|
617
|
+
getContext().logger.debug('InstanceCache.createFromData: No model identifier found', { data });
|
|
619
618
|
getContext().modelDebugLogger?.logError('Unknown', 'CREATE', 'No model identifier found', data);
|
|
620
619
|
return null;
|
|
621
620
|
}
|
|
@@ -630,16 +629,16 @@ export class ObjectPool {
|
|
|
630
629
|
// isn't in your schema, so these rows are silently skipped. Keep at warn,
|
|
631
630
|
// consumer register (their model name + the `ablo status` fix); forensics ride debug.
|
|
632
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.`);
|
|
633
|
-
getContext().logger.debug(`
|
|
632
|
+
getContext().logger.debug(`InstanceCache.createFromData: No constructor found for model "${modelName}"`, { data });
|
|
634
633
|
getContext().modelDebugLogger?.logError(modelName, 'CREATE', `No constructor found for model "${modelName}"`, data);
|
|
635
634
|
return null;
|
|
636
635
|
}
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
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.
|
|
640
639
|
if (data.id && this.entries.has(data.id)) {
|
|
641
640
|
const existing = this.get(data.id);
|
|
642
|
-
if (existing
|
|
641
|
+
if (existing?.getModelName() === modelName) {
|
|
643
642
|
// Same ID and same type - update existing model with new data and return it
|
|
644
643
|
existing.updateFromData(data);
|
|
645
644
|
return existing;
|
|
@@ -658,7 +657,7 @@ export class ObjectPool {
|
|
|
658
657
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
659
658
|
// Internal construction failure — captured via observability below and
|
|
660
659
|
// re-fetched on resync; the stack is forensic → debug.
|
|
661
|
-
getContext().logger.debug(`[
|
|
660
|
+
getContext().logger.debug(`[InstanceCache.createFromData] FAILED ${modelName}`, { errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
662
661
|
getContext().observability.captureTransactionFailure({
|
|
663
662
|
context: 'createFromData',
|
|
664
663
|
modelName,
|
|
@@ -682,11 +681,8 @@ export class ObjectPool {
|
|
|
682
681
|
const preserveObserved = options.preserveObserved ?? false;
|
|
683
682
|
const preservedIds = [];
|
|
684
683
|
const preservedEntries = [];
|
|
685
|
-
let disposedCount = 0;
|
|
686
|
-
let checkedCount = 0;
|
|
687
684
|
for (const [id, entry] of this.entries) {
|
|
688
|
-
const model = entry.model
|
|
689
|
-
checkedCount++;
|
|
685
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
690
686
|
// Check if this model should be preserved (has active React observers)
|
|
691
687
|
if (preserveObserved &&
|
|
692
688
|
model &&
|
|
@@ -697,8 +693,12 @@ export class ObjectPool {
|
|
|
697
693
|
preservedEntries.push([id, entry]);
|
|
698
694
|
continue;
|
|
699
695
|
}
|
|
700
|
-
|
|
701
|
-
|
|
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();
|
|
702
702
|
}
|
|
703
703
|
// Save access times for preserved entries before clearing
|
|
704
704
|
const preservedAccessTimes = new Map();
|
|
@@ -726,7 +726,7 @@ export class ObjectPool {
|
|
|
726
726
|
// Re-add preserved entries (also rebuilds foreign key indexes via addToTypeIndex)
|
|
727
727
|
for (const [id, entry] of preservedEntries) {
|
|
728
728
|
this.entries.set(id, entry);
|
|
729
|
-
const model = entry.model
|
|
729
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
730
730
|
if (model) {
|
|
731
731
|
this.addToTypeIndex(id, model.getModelName());
|
|
732
732
|
}
|
|
@@ -782,9 +782,9 @@ export class ObjectPool {
|
|
|
782
782
|
scopeCounts.live++;
|
|
783
783
|
else if (entry.scope === ModelScope.archived)
|
|
784
784
|
scopeCounts.archived++;
|
|
785
|
-
const modelName = entry.model?.getModelName()
|
|
785
|
+
const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
|
|
786
786
|
if (modelName) {
|
|
787
|
-
typeCounts.set(modelName, (typeCounts.get(modelName)
|
|
787
|
+
typeCounts.set(modelName, (typeCounts.get(modelName) ?? 0) + 1);
|
|
788
788
|
}
|
|
789
789
|
}
|
|
790
790
|
return {
|
|
@@ -825,12 +825,12 @@ export class ObjectPool {
|
|
|
825
825
|
let skippedObserved = 0;
|
|
826
826
|
for (const [id, entry] of this.entries) {
|
|
827
827
|
// Check if model has expired based on last access time
|
|
828
|
-
const lastAccessed = this.accessTimes.get(id)
|
|
828
|
+
const lastAccessed = this.accessTimes.get(id) ?? 0;
|
|
829
829
|
if (now - lastAccessed > this.config.maxAge) {
|
|
830
|
-
//
|
|
831
|
-
//
|
|
830
|
+
// Do not GC a model that has observed collections — disposing one
|
|
831
|
+
// React is still observing would break it (per MobX guidance).
|
|
832
832
|
// See: https://mobx.js.org/lazy-observables.html
|
|
833
|
-
const model = entry.model
|
|
833
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
834
834
|
if (model &&
|
|
835
835
|
typeof model.hasObservedCollections === 'function' &&
|
|
836
836
|
model.hasObservedCollections()) {
|
|
@@ -871,7 +871,7 @@ export class ObjectPool {
|
|
|
871
871
|
}
|
|
872
872
|
}
|
|
873
873
|
if (skippedObserved > 0) {
|
|
874
|
-
getContext().logger.debug(`[
|
|
874
|
+
getContext().logger.debug(`[InstanceCache GC] Skipped ${skippedObserved} models with active React observers`);
|
|
875
875
|
}
|
|
876
876
|
// Also clean up old tracking data
|
|
877
877
|
this.clearDeltaHistory();
|
|
@@ -885,7 +885,9 @@ export class ObjectPool {
|
|
|
885
885
|
this.gcTimer = setInterval(() => this.gc(), this.config.gcInterval);
|
|
886
886
|
// Don't hold a headless Node process open just for pool GC — without
|
|
887
887
|
// this, an agent that never calls disconnect() can never exit. No-op in
|
|
888
|
-
// browsers (where setInterval returns a number without `unref`)
|
|
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
|
|
889
891
|
this.gcTimer.unref?.();
|
|
890
892
|
}
|
|
891
893
|
stopGC() {
|
|
@@ -900,13 +902,13 @@ export class ObjectPool {
|
|
|
900
902
|
let oldestTime = Date.now();
|
|
901
903
|
for (const [id, entry] of this.entries) {
|
|
902
904
|
// Skip models that are being observed by React - they must stay alive
|
|
903
|
-
const model = entry.model
|
|
905
|
+
const model = entry.model ?? entry.weakRef?.deref();
|
|
904
906
|
if (model &&
|
|
905
907
|
typeof model.hasObservedCollections === 'function' &&
|
|
906
908
|
model.hasObservedCollections()) {
|
|
907
909
|
continue;
|
|
908
910
|
}
|
|
909
|
-
const entryAccessTime = this.accessTimes.get(id)
|
|
911
|
+
const entryAccessTime = this.accessTimes.get(id) ?? 0;
|
|
910
912
|
if (entryAccessTime < oldestTime) {
|
|
911
913
|
oldest = [id, entry];
|
|
912
914
|
oldestTime = entryAccessTime;
|
|
@@ -970,7 +972,7 @@ export class ObjectPool {
|
|
|
970
972
|
getByForeignKey(modelName, fieldName, fieldValue) {
|
|
971
973
|
const indexKey = `${modelName}:${fieldName}`;
|
|
972
974
|
const index = this.foreignKeyIndexes.get(indexKey);
|
|
973
|
-
// Both empty-path early-returns below are
|
|
975
|
+
// Both empty-path early-returns below are normal states, not errors:
|
|
974
976
|
// a model with no FK index yet (not populated), or an index with no
|
|
975
977
|
// entry for this specific parent id (entity genuinely has no
|
|
976
978
|
// children). These used to `console.warn` diagnostic dumps on every
|
|
@@ -1011,7 +1013,7 @@ export class ObjectPool {
|
|
|
1011
1013
|
// has dangling refs (legacy orphan deltas, pending CREATE
|
|
1012
1014
|
// transactions, etc.). Noisy at warn level, useful during
|
|
1013
1015
|
// investigation.
|
|
1014
|
-
getContext().logger.debug('[
|
|
1016
|
+
getContext().logger.debug('[InstanceCache.getByForeignKey] ROWS DROPPED', {
|
|
1015
1017
|
modelName,
|
|
1016
1018
|
fieldName,
|
|
1017
1019
|
fieldValue,
|
|
@@ -1037,7 +1039,7 @@ export class ObjectPool {
|
|
|
1037
1039
|
if (!fields)
|
|
1038
1040
|
return;
|
|
1039
1041
|
for (const fieldName of fields) {
|
|
1040
|
-
const fieldValue =
|
|
1042
|
+
const fieldValue = model.getField(fieldName);
|
|
1041
1043
|
if (typeof fieldValue !== 'string')
|
|
1042
1044
|
continue;
|
|
1043
1045
|
const indexKey = `${modelName}:${fieldName}`;
|
|
@@ -1067,7 +1069,7 @@ export class ObjectPool {
|
|
|
1067
1069
|
if (!model)
|
|
1068
1070
|
return;
|
|
1069
1071
|
for (const fieldName of fields) {
|
|
1070
|
-
const fieldValue =
|
|
1072
|
+
const fieldValue = model.getField(fieldName);
|
|
1071
1073
|
if (typeof fieldValue !== 'string')
|
|
1072
1074
|
continue;
|
|
1073
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,23 +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';
|
|
8
7
|
import { Database } from './Database.js';
|
|
9
|
-
import {
|
|
8
|
+
import { InstanceCache } from './InstanceCache.js';
|
|
10
9
|
import { getActiveRegistry } from './ModelRegistry.js';
|
|
11
10
|
import { AbloValidationError } from './errors.js';
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
* - Observable for React integration
|
|
19
|
-
* - Memory efficient with intelligent caching
|
|
20
|
-
* - 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}.
|
|
21
17
|
*/
|
|
22
18
|
export class LazyReferenceCollection {
|
|
23
19
|
modelName;
|
|
@@ -132,7 +128,7 @@ export class LazyReferenceCollection {
|
|
|
132
128
|
/**
|
|
133
129
|
* Get the collection value (triggers hydration if needed).
|
|
134
130
|
*
|
|
135
|
-
* Filters out items whose id is no longer in the
|
|
131
|
+
* Filters out items whose id is no longer in the InstanceCache. The
|
|
136
132
|
* local `items` array isn't auto-synced with `pool.remove()` — a
|
|
137
133
|
* deleted entity would linger here until hydrate() re-runs on
|
|
138
134
|
* reload. Reading `pool.has(item.id)` inside this computed getter
|
|
@@ -262,7 +258,7 @@ export class LazyReferenceCollection {
|
|
|
262
258
|
throw new AbloValidationError(`Database dependency not provided to LazyReferenceCollection for ${this.modelName}`, { code: 'lazy_ref_db_missing' });
|
|
263
259
|
}
|
|
264
260
|
if (!this.objectPool) {
|
|
265
|
-
throw new AbloValidationError(`
|
|
261
|
+
throw new AbloValidationError(`InstanceCache dependency not provided to LazyReferenceCollection for ${this.modelName}`, { code: 'lazy_ref_pool_missing' });
|
|
266
262
|
}
|
|
267
263
|
const store = this.database.getStore(this.modelName);
|
|
268
264
|
const rawData = store ? await store.getAllFromIndex(this.foreignKey, this.parent.id) : [];
|
|
@@ -282,7 +278,7 @@ export class LazyReferenceCollection {
|
|
|
282
278
|
// narrow-checked here rather than assumed.
|
|
283
279
|
if (typeof id !== 'string')
|
|
284
280
|
continue;
|
|
285
|
-
// Check if already in
|
|
281
|
+
// Check if already in InstanceCache
|
|
286
282
|
let model = this.objectPool.get(id);
|
|
287
283
|
if (!model) {
|
|
288
284
|
// Create new model instance
|