joist-core 2.4.0-next.4 → 2.4.0-next.41
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/build/BaseEntity.d.cts +3 -4
- package/build/BaseEntity.d.cts.map +1 -1
- package/build/BaseEntity.d.mts +3 -4
- package/build/BaseEntity.d.mts.map +1 -1
- package/build/Entity.d.cts +3 -4
- package/build/Entity.d.cts.map +1 -1
- package/build/Entity.d.mts +3 -4
- package/build/Entity.d.mts.map +1 -1
- package/build/EntityFields.d.cts +3 -4
- package/build/EntityFields.d.cts.map +1 -1
- package/build/EntityFields.d.mts +3 -4
- package/build/EntityFields.d.mts.map +1 -1
- package/build/EntityManager.cjs +64 -20
- package/build/EntityManager.cjs.map +1 -1
- package/build/EntityManager.d.cts +86 -53
- package/build/EntityManager.d.cts.map +1 -1
- package/build/EntityManager.d.mts +86 -53
- package/build/EntityManager.d.mts.map +1 -1
- package/build/EntityManager.js +64 -22
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.cjs +0 -1
- package/build/EntityMetadata.cjs.map +1 -1
- package/build/EntityMetadata.d.cts +39 -36
- package/build/EntityMetadata.d.cts.map +1 -1
- package/build/EntityMetadata.d.mts +39 -36
- package/build/EntityMetadata.d.mts.map +1 -1
- package/build/EntityMetadata.js +0 -1
- package/build/EntityMetadata.js.map +1 -1
- package/build/EnumMetadata.d.cts +1 -2
- package/build/EnumMetadata.d.cts.map +1 -1
- package/build/EnumMetadata.d.mts +1 -2
- package/build/EnumMetadata.d.mts.map +1 -1
- package/build/IndexManager.cjs +23 -5
- package/build/IndexManager.cjs.map +1 -1
- package/build/IndexManager.d.cts +2 -3
- package/build/IndexManager.d.cts.map +1 -1
- package/build/IndexManager.d.mts +2 -3
- package/build/IndexManager.d.mts.map +1 -1
- package/build/IndexManager.js +23 -5
- package/build/IndexManager.js.map +1 -1
- package/build/InstanceData.cjs.map +1 -1
- package/build/InstanceData.d.cts +1 -2
- package/build/InstanceData.d.cts.map +1 -1
- package/build/InstanceData.d.mts +1 -2
- package/build/InstanceData.d.mts.map +1 -1
- package/build/InstanceData.js.map +1 -1
- package/build/PluginManager.cjs.map +1 -1
- package/build/PluginManager.d.cts +4 -4
- package/build/PluginManager.d.cts.map +1 -1
- package/build/PluginManager.d.mts +4 -4
- package/build/PluginManager.d.mts.map +1 -1
- package/build/PluginManager.js.map +1 -1
- package/build/RowData.d.cts +3 -4
- package/build/RowData.d.cts.map +1 -1
- package/build/RowData.d.mts +3 -4
- package/build/RowData.d.mts.map +1 -1
- package/build/batchloaders/BatchLoader.cjs.map +1 -1
- package/build/batchloaders/BatchLoader.d.cts +1 -2
- package/build/batchloaders/BatchLoader.d.cts.map +1 -1
- package/build/batchloaders/BatchLoader.d.mts +1 -2
- package/build/batchloaders/BatchLoader.d.mts.map +1 -1
- package/build/batchloaders/BatchLoader.js.map +1 -1
- package/build/batchloaders/enumCollectionBatchLoader.d.cts +2 -3
- package/build/batchloaders/enumCollectionBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/enumCollectionBatchLoader.d.mts +2 -3
- package/build/batchloaders/enumCollectionBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/loadBatchLoader.d.cts +2 -3
- package/build/batchloaders/loadBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/loadBatchLoader.d.mts +2 -3
- package/build/batchloaders/loadBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/manyToManyBatchLoader.d.cts +2 -3
- package/build/batchloaders/manyToManyBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/manyToManyBatchLoader.d.mts +2 -3
- package/build/batchloaders/manyToManyBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/oneToManyBatchLoader.d.cts +2 -3
- package/build/batchloaders/oneToManyBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/oneToManyBatchLoader.d.mts +2 -3
- package/build/batchloaders/oneToManyBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/oneToOneBatchLoader.d.cts +2 -3
- package/build/batchloaders/oneToOneBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/oneToOneBatchLoader.d.mts +2 -3
- package/build/batchloaders/oneToOneBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/populateBatchLoader.cjs +2 -2
- package/build/batchloaders/populateBatchLoader.d.cts +2 -3
- package/build/batchloaders/populateBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/populateBatchLoader.d.mts +2 -3
- package/build/batchloaders/populateBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/populateBatchLoader.js +2 -2
- package/build/batchloaders/recursiveChildrenBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.d.cts +2 -3
- package/build/batchloaders/recursiveChildrenBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.d.mts +2 -3
- package/build/batchloaders/recursiveChildrenBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.d.cts +2 -3
- package/build/batchloaders/recursiveM2mBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.d.mts +2 -3
- package/build/batchloaders/recursiveM2mBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.js +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.d.cts +2 -3
- package/build/batchloaders/recursiveParentsBatchLoader.d.cts.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.d.mts +2 -3
- package/build/batchloaders/recursiveParentsBatchLoader.d.mts.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js +1 -1
- package/build/changes.cjs.map +1 -1
- package/build/changes.d.cts +9 -10
- package/build/changes.d.cts.map +1 -1
- package/build/changes.d.mts +9 -10
- package/build/changes.d.mts.map +1 -1
- package/build/changes.js.map +1 -1
- package/build/config.cjs +75 -1
- package/build/config.cjs.map +1 -1
- package/build/config.d.cts +99 -17
- package/build/config.d.cts.map +1 -1
- package/build/config.d.mts +99 -17
- package/build/config.d.mts.map +1 -1
- package/build/config.js +75 -1
- package/build/config.js.map +1 -1
- package/build/configure.cjs +6 -2
- package/build/configure.cjs.map +1 -1
- package/build/configure.d.cts +8 -9
- package/build/configure.d.cts.map +1 -1
- package/build/configure.d.mts +8 -9
- package/build/configure.d.mts.map +1 -1
- package/build/configure.js +6 -2
- package/build/configure.js.map +1 -1
- package/build/dataloaders/fastWhereFilterHash.d.cts +1 -2
- package/build/dataloaders/fastWhereFilterHash.d.cts.map +1 -1
- package/build/dataloaders/fastWhereFilterHash.d.mts +1 -2
- package/build/dataloaders/fastWhereFilterHash.d.mts.map +1 -1
- package/build/dataloaders/findByUniqueDataLoader.d.cts +2 -3
- package/build/dataloaders/findByUniqueDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findByUniqueDataLoader.d.mts +2 -3
- package/build/dataloaders/findByUniqueDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.cts +4 -5
- package/build/dataloaders/findCountDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.mts +4 -5
- package/build/dataloaders/findCountDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findDataLoader.cjs +1 -1
- package/build/dataloaders/findDataLoader.d.cts +11 -12
- package/build/dataloaders/findDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findDataLoader.d.mts +11 -12
- package/build/dataloaders/findDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findDataLoader.js +1 -1
- package/build/dataloaders/findIdsDataLoader.d.cts +2 -3
- package/build/dataloaders/findIdsDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.mts +2 -3
- package/build/dataloaders/findIdsDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.cjs +6 -2
- package/build/dataloaders/findOrCreateDataLoader.cjs.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.d.cts +3 -3
- package/build/dataloaders/findOrCreateDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.d.mts +3 -3
- package/build/dataloaders/findOrCreateDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.js +7 -3
- package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.cjs +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.cts +1 -2
- package/build/dataloaders/findPaginatedDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.mts +1 -2
- package/build/dataloaders/findPaginatedDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +1 -1
- package/build/dataloaders/lensDataLoader.d.cts +2 -3
- package/build/dataloaders/lensDataLoader.d.cts.map +1 -1
- package/build/dataloaders/lensDataLoader.d.mts +2 -3
- package/build/dataloaders/lensDataLoader.d.mts.map +1 -1
- package/build/dataloaders/manyToManyFindDataLoader.d.cts +2 -3
- package/build/dataloaders/manyToManyFindDataLoader.d.cts.map +1 -1
- package/build/dataloaders/manyToManyFindDataLoader.d.mts +2 -3
- package/build/dataloaders/manyToManyFindDataLoader.d.mts.map +1 -1
- package/build/dataloaders/oneToManyFindDataLoader.d.cts +2 -3
- package/build/dataloaders/oneToManyFindDataLoader.d.cts.map +1 -1
- package/build/dataloaders/oneToManyFindDataLoader.d.mts +2 -3
- package/build/dataloaders/oneToManyFindDataLoader.d.mts.map +1 -1
- package/build/defaults.cjs +2 -2
- package/build/defaults.cjs.map +1 -1
- package/build/defaults.d.cts +7 -7
- package/build/defaults.d.cts.map +1 -1
- package/build/defaults.d.mts +7 -7
- package/build/defaults.d.mts.map +1 -1
- package/build/defaults.js +2 -2
- package/build/defaults.js.map +1 -1
- package/build/drivers/Driver.d.cts +2 -3
- package/build/drivers/Driver.d.cts.map +1 -1
- package/build/drivers/Driver.d.mts +2 -3
- package/build/drivers/Driver.d.mts.map +1 -1
- package/build/drivers/EntityWriter.d.cts +7 -7
- package/build/drivers/EntityWriter.d.cts.map +1 -1
- package/build/drivers/EntityWriter.d.mts +7 -7
- package/build/drivers/EntityWriter.d.mts.map +1 -1
- package/build/drivers/IdAssigner.cjs.map +1 -1
- package/build/drivers/IdAssigner.d.cts +5 -5
- package/build/drivers/IdAssigner.d.cts.map +1 -1
- package/build/drivers/IdAssigner.d.mts +5 -5
- package/build/drivers/IdAssigner.d.mts.map +1 -1
- package/build/drivers/IdAssigner.js.map +1 -1
- package/build/drivers/driverApi.d.cts +2 -3
- package/build/drivers/driverApi.d.cts.map +1 -1
- package/build/drivers/driverApi.d.mts +2 -3
- package/build/drivers/driverApi.d.mts.map +1 -1
- package/build/fields.cjs +1 -0
- package/build/fields.cjs.map +1 -1
- package/build/fields.d.cts +6 -7
- package/build/fields.d.cts.map +1 -1
- package/build/fields.d.mts +6 -7
- package/build/fields.d.mts.map +1 -1
- package/build/fields.js +1 -0
- package/build/fields.js.map +1 -1
- package/build/flush/FlushLock.cjs.map +1 -1
- package/build/flush/FlushLock.d.cts +1 -2
- package/build/flush/FlushLock.d.cts.map +1 -1
- package/build/flush/FlushLock.d.mts +1 -2
- package/build/flush/FlushLock.d.mts.map +1 -1
- package/build/flush/FlushLock.js.map +1 -1
- package/build/flush/JoinRows.cjs.map +1 -1
- package/build/flush/JoinRows.d.cts +5 -6
- package/build/flush/JoinRows.d.cts.map +1 -1
- package/build/flush/JoinRows.d.mts +5 -6
- package/build/flush/JoinRows.d.mts.map +1 -1
- package/build/flush/JoinRows.js.map +1 -1
- package/build/flush/PendingChanges.d.cts +5 -6
- package/build/flush/PendingChanges.d.cts.map +1 -1
- package/build/flush/PendingChanges.d.mts +5 -6
- package/build/flush/PendingChanges.d.mts.map +1 -1
- package/build/flush/Todo.d.cts +5 -6
- package/build/flush/Todo.d.cts.map +1 -1
- package/build/flush/Todo.d.mts +5 -6
- package/build/flush/Todo.d.mts.map +1 -1
- package/build/getProperties.d.cts +3 -4
- package/build/getProperties.d.cts.map +1 -1
- package/build/getProperties.d.mts +3 -4
- package/build/getProperties.d.mts.map +1 -1
- package/build/index.cjs +14 -8
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +20 -19
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +20 -19
- package/build/index.d.mts.map +1 -1
- package/build/index.js +14 -11
- package/build/index.js.map +1 -1
- package/build/json.cjs +3 -3
- package/build/json.d.cts +11 -11
- package/build/json.d.cts.map +1 -1
- package/build/json.d.mts +11 -11
- package/build/json.d.mts.map +1 -1
- package/build/json.js +3 -3
- package/build/keys.d.cts +27 -27
- package/build/keys.d.cts.map +1 -1
- package/build/keys.d.mts +27 -27
- package/build/keys.d.mts.map +1 -1
- package/build/loading/HintTree.d.cts +3 -4
- package/build/loading/HintTree.d.cts.map +1 -1
- package/build/loading/HintTree.d.mts +3 -4
- package/build/loading/HintTree.d.mts.map +1 -1
- package/build/loading/IsLoadedCache.cjs.map +1 -1
- package/build/loading/IsLoadedCache.d.cts +2 -3
- package/build/loading/IsLoadedCache.d.cts.map +1 -1
- package/build/loading/IsLoadedCache.d.mts +2 -3
- package/build/loading/IsLoadedCache.d.mts.map +1 -1
- package/build/loading/IsLoadedCache.js.map +1 -1
- package/build/loading/loadHints.cjs +1 -1
- package/build/loading/loadHints.d.cts +17 -17
- package/build/loading/loadHints.d.cts.map +1 -1
- package/build/loading/loadHints.d.mts +17 -17
- package/build/loading/loadHints.d.mts.map +1 -1
- package/build/loading/loadHints.js +1 -1
- package/build/loading/loadLens.d.cts +13 -13
- package/build/loading/loadLens.d.cts.map +1 -1
- package/build/loading/loadLens.d.mts +13 -13
- package/build/loading/loadLens.d.mts.map +1 -1
- package/build/loading/withLoaded.cjs +2 -2
- package/build/loading/withLoaded.d.cts +5 -6
- package/build/loading/withLoaded.d.cts.map +1 -1
- package/build/loading/withLoaded.d.mts +5 -6
- package/build/loading/withLoaded.d.mts.map +1 -1
- package/build/loading/withLoaded.js +2 -2
- package/build/logging/FactoryLogger.d.cts +3 -4
- package/build/logging/FactoryLogger.d.cts.map +1 -1
- package/build/logging/FactoryLogger.d.mts +3 -4
- package/build/logging/FactoryLogger.d.mts.map +1 -1
- package/build/logging/FieldLogger.cjs.map +1 -1
- package/build/logging/FieldLogger.d.cts +4 -5
- package/build/logging/FieldLogger.d.cts.map +1 -1
- package/build/logging/FieldLogger.d.mts +4 -5
- package/build/logging/FieldLogger.d.mts.map +1 -1
- package/build/logging/FieldLogger.js.map +1 -1
- package/build/logging/ReactionLogger.d.cts +7 -7
- package/build/logging/ReactionLogger.d.cts.map +1 -1
- package/build/logging/ReactionLogger.d.mts +7 -7
- package/build/logging/ReactionLogger.d.mts.map +1 -1
- package/build/newEntity.cjs.map +1 -1
- package/build/newEntity.d.cts +4 -5
- package/build/newEntity.d.cts.map +1 -1
- package/build/newEntity.d.mts +4 -5
- package/build/newEntity.d.mts.map +1 -1
- package/build/newEntity.js.map +1 -1
- package/build/newTestInstance.cjs +1 -0
- package/build/newTestInstance.cjs.map +1 -1
- package/build/newTestInstance.d.cts +21 -21
- package/build/newTestInstance.d.cts.map +1 -1
- package/build/newTestInstance.d.mts +21 -21
- package/build/newTestInstance.d.mts.map +1 -1
- package/build/newTestInstance.js +1 -0
- package/build/newTestInstance.js.map +1 -1
- package/build/normalizeHints.d.cts +7 -8
- package/build/normalizeHints.d.cts.map +1 -1
- package/build/normalizeHints.d.mts +7 -8
- package/build/normalizeHints.d.mts.map +1 -1
- package/build/nowUTC.d.cts +4 -5
- package/build/nowUTC.d.cts.map +1 -1
- package/build/nowUTC.d.mts +4 -5
- package/build/nowUTC.d.mts.map +1 -1
- package/build/plugins/ImmutableEntitiesPlugin.d.cts +1 -2
- package/build/plugins/ImmutableEntitiesPlugin.d.cts.map +1 -1
- package/build/plugins/ImmutableEntitiesPlugin.d.mts +1 -2
- package/build/plugins/ImmutableEntitiesPlugin.d.mts.map +1 -1
- package/build/plugins/PreloadPlugin.d.cts +3 -4
- package/build/plugins/PreloadPlugin.d.cts.map +1 -1
- package/build/plugins/PreloadPlugin.d.mts +3 -4
- package/build/plugins/PreloadPlugin.d.mts.map +1 -1
- package/build/preloading/JsonAggregatePreloader.d.cts +1 -2
- package/build/preloading/JsonAggregatePreloader.d.cts.map +1 -1
- package/build/preloading/JsonAggregatePreloader.d.mts +1 -2
- package/build/preloading/JsonAggregatePreloader.d.mts.map +1 -1
- package/build/preloading/canPreload.d.cts +1 -2
- package/build/preloading/canPreload.d.cts.map +1 -1
- package/build/preloading/canPreload.d.mts +1 -2
- package/build/preloading/canPreload.d.mts.map +1 -1
- package/build/preloading/partitionHint.cjs +1 -1
- package/build/preloading/partitionHint.d.cts +1 -2
- package/build/preloading/partitionHint.d.cts.map +1 -1
- package/build/preloading/partitionHint.d.mts +1 -2
- package/build/preloading/partitionHint.d.mts.map +1 -1
- package/build/preloading/partitionHint.js +1 -1
- package/build/queries/ConditionBuilder.d.cts +2 -2
- package/build/queries/ConditionBuilder.d.cts.map +1 -1
- package/build/queries/ConditionBuilder.d.mts +2 -2
- package/build/queries/ConditionBuilder.d.mts.map +1 -1
- package/build/queries/conditions.d.cts +11 -12
- package/build/queries/conditions.d.cts.map +1 -1
- package/build/queries/conditions.d.mts +11 -12
- package/build/queries/conditions.d.mts.map +1 -1
- package/build/queries/entityQueryUtils.d.cts +4 -5
- package/build/queries/entityQueryUtils.d.cts.map +1 -1
- package/build/queries/entityQueryUtils.d.mts +4 -5
- package/build/queries/entityQueryUtils.d.mts.map +1 -1
- package/build/queries/find/Aliases.d.cts +16 -16
- package/build/queries/find/Aliases.d.cts.map +1 -1
- package/build/queries/find/Aliases.d.mts +16 -16
- package/build/queries/find/Aliases.d.mts.map +1 -1
- package/build/queries/find/DeferredAlias.d.cts +5 -6
- package/build/queries/find/DeferredAlias.d.cts.map +1 -1
- package/build/queries/find/DeferredAlias.d.mts +5 -6
- package/build/queries/find/DeferredAlias.d.mts.map +1 -1
- package/build/queries/find/EntityFilter.d.cts +12 -13
- package/build/queries/find/EntityFilter.d.cts.map +1 -1
- package/build/queries/find/EntityFilter.d.mts +12 -13
- package/build/queries/find/EntityFilter.d.mts.map +1 -1
- package/build/queries/find/EntityGraphQLFilter.d.cts +8 -9
- package/build/queries/find/EntityGraphQLFilter.d.cts.map +1 -1
- package/build/queries/find/EntityGraphQLFilter.d.mts +8 -9
- package/build/queries/find/EntityGraphQLFilter.d.mts.map +1 -1
- package/build/queries/find/QueryParser.collectionJoins.d.cts +1 -2
- package/build/queries/find/QueryParser.collectionJoins.d.cts.map +1 -1
- package/build/queries/find/QueryParser.collectionJoins.d.mts +1 -2
- package/build/queries/find/QueryParser.collectionJoins.d.mts.map +1 -1
- package/build/queries/find/QueryParser.d.cts +18 -18
- package/build/queries/find/QueryParser.d.cts.map +1 -1
- package/build/queries/find/QueryParser.d.mts +18 -18
- package/build/queries/find/QueryParser.d.mts.map +1 -1
- package/build/queries/find/QueryParser.pruning.d.cts +2 -3
- package/build/queries/find/QueryParser.pruning.d.cts.map +1 -1
- package/build/queries/find/QueryParser.pruning.d.mts +2 -3
- package/build/queries/find/QueryParser.pruning.d.mts.map +1 -1
- package/build/queries/find/QueryVisitor.d.cts +4 -4
- package/build/queries/find/QueryVisitor.d.cts.map +1 -1
- package/build/queries/find/QueryVisitor.d.mts +4 -4
- package/build/queries/find/QueryVisitor.d.mts.map +1 -1
- package/build/queries/find/buildFindQuery.d.cts +3 -3
- package/build/queries/find/buildFindQuery.d.cts.map +1 -1
- package/build/queries/find/buildFindQuery.d.mts +3 -3
- package/build/queries/find/buildFindQuery.d.mts.map +1 -1
- package/build/queries/find/scopes.cjs.map +1 -1
- package/build/queries/find/scopes.d.cts +12 -13
- package/build/queries/find/scopes.d.cts.map +1 -1
- package/build/queries/find/scopes.d.mts +12 -13
- package/build/queries/find/scopes.d.mts.map +1 -1
- package/build/queries/find/scopes.js.map +1 -1
- package/build/queries/parsedConditions.d.cts +7 -8
- package/build/queries/parsedConditions.d.cts.map +1 -1
- package/build/queries/parsedConditions.d.mts +7 -8
- package/build/queries/parsedConditions.d.mts.map +1 -1
- package/build/queries/renderConditions.d.cts +3 -4
- package/build/queries/renderConditions.d.cts.map +1 -1
- package/build/queries/renderConditions.d.mts +3 -4
- package/build/queries/renderConditions.d.mts.map +1 -1
- package/build/queries/skipCondition.d.cts +1 -2
- package/build/queries/skipCondition.d.cts.map +1 -1
- package/build/queries/skipCondition.d.mts +1 -2
- package/build/queries/skipCondition.d.mts.map +1 -1
- package/build/queries/sql/AliasAssigner.cjs.map +1 -1
- package/build/queries/sql/AliasAssigner.d.cts +1 -2
- package/build/queries/sql/AliasAssigner.d.cts.map +1 -1
- package/build/queries/sql/AliasAssigner.d.mts +1 -2
- package/build/queries/sql/AliasAssigner.d.mts.map +1 -1
- package/build/queries/sql/AliasAssigner.js.map +1 -1
- package/build/queries/sql/Expr.cjs +38 -0
- package/build/queries/sql/Expr.cjs.map +1 -1
- package/build/queries/sql/Expr.d.cts +45 -27
- package/build/queries/sql/Expr.d.cts.map +1 -1
- package/build/queries/sql/Expr.d.mts +45 -27
- package/build/queries/sql/Expr.d.mts.map +1 -1
- package/build/queries/sql/Expr.js +38 -1
- package/build/queries/sql/Expr.js.map +1 -1
- package/build/queries/sql/JoinTree.d.cts +5 -5
- package/build/queries/sql/JoinTree.d.cts.map +1 -1
- package/build/queries/sql/JoinTree.d.mts +5 -5
- package/build/queries/sql/JoinTree.d.mts.map +1 -1
- package/build/queries/sql/Tables.cjs +9 -0
- package/build/queries/sql/Tables.cjs.map +1 -1
- package/build/queries/sql/Tables.d.cts +46 -39
- package/build/queries/sql/Tables.d.cts.map +1 -1
- package/build/queries/sql/Tables.d.mts +46 -39
- package/build/queries/sql/Tables.d.mts.map +1 -1
- package/build/queries/sql/Tables.js +10 -1
- package/build/queries/sql/Tables.js.map +1 -1
- package/build/queries/sql/custom.d.cts +14 -14
- package/build/queries/sql/custom.d.cts.map +1 -1
- package/build/queries/sql/custom.d.mts +14 -14
- package/build/queries/sql/custom.d.mts.map +1 -1
- package/build/queries/sql/execute.cjs +4 -9
- package/build/queries/sql/execute.cjs.map +1 -1
- package/build/queries/sql/execute.d.cts +45 -28
- package/build/queries/sql/execute.d.cts.map +1 -1
- package/build/queries/sql/execute.d.mts +45 -28
- package/build/queries/sql/execute.d.mts.map +1 -1
- package/build/queries/sql/execute.js +5 -10
- package/build/queries/sql/execute.js.map +1 -1
- package/build/queries/sql/expressions/arrayAgg.d.cts +5 -6
- package/build/queries/sql/expressions/arrayAgg.d.cts.map +1 -1
- package/build/queries/sql/expressions/arrayAgg.d.mts +5 -6
- package/build/queries/sql/expressions/arrayAgg.d.mts.map +1 -1
- package/build/queries/sql/expressions/case.d.cts +10 -10
- package/build/queries/sql/expressions/case.d.cts.map +1 -1
- package/build/queries/sql/expressions/case.d.mts +10 -10
- package/build/queries/sql/expressions/case.d.mts.map +1 -1
- package/build/queries/sql/expressions/coalesce.d.cts +5 -6
- package/build/queries/sql/expressions/coalesce.d.cts.map +1 -1
- package/build/queries/sql/expressions/coalesce.d.mts +5 -6
- package/build/queries/sql/expressions/coalesce.d.mts.map +1 -1
- package/build/queries/sql/expressions/codecs.d.cts +1 -2
- package/build/queries/sql/expressions/codecs.d.cts.map +1 -1
- package/build/queries/sql/expressions/codecs.d.mts +1 -2
- package/build/queries/sql/expressions/codecs.d.mts.map +1 -1
- package/build/queries/sql/expressions/expression.d.cts +6 -6
- package/build/queries/sql/expressions/expression.d.cts.map +1 -1
- package/build/queries/sql/expressions/expression.d.mts +6 -6
- package/build/queries/sql/expressions/expression.d.mts.map +1 -1
- package/build/queries/sql/expressions/greatest.d.cts +5 -6
- package/build/queries/sql/expressions/greatest.d.cts.map +1 -1
- package/build/queries/sql/expressions/greatest.d.mts +5 -6
- package/build/queries/sql/expressions/greatest.d.mts.map +1 -1
- package/build/queries/sql/expressions/least.d.cts +5 -6
- package/build/queries/sql/expressions/least.d.cts.map +1 -1
- package/build/queries/sql/expressions/least.d.mts +5 -6
- package/build/queries/sql/expressions/least.d.mts.map +1 -1
- package/build/queries/sql/expressions/nullIf.d.cts +5 -6
- package/build/queries/sql/expressions/nullIf.d.cts.map +1 -1
- package/build/queries/sql/expressions/nullIf.d.mts +5 -6
- package/build/queries/sql/expressions/nullIf.d.mts.map +1 -1
- package/build/queries/sql/expressions/parseExpression.d.cts +5 -6
- package/build/queries/sql/expressions/parseExpression.d.cts.map +1 -1
- package/build/queries/sql/expressions/parseExpression.d.mts +5 -6
- package/build/queries/sql/expressions/parseExpression.d.mts.map +1 -1
- package/build/queries/sql/expressions/types.d.cts +11 -11
- package/build/queries/sql/expressions/types.d.cts.map +1 -1
- package/build/queries/sql/expressions/types.d.mts +11 -11
- package/build/queries/sql/expressions/types.d.mts.map +1 -1
- package/build/queries/sql/keywords.d.cts +4 -5
- package/build/queries/sql/keywords.d.cts.map +1 -1
- package/build/queries/sql/keywords.d.mts +4 -5
- package/build/queries/sql/keywords.d.mts.map +1 -1
- package/build/queries/sql/query.cjs +59 -23
- package/build/queries/sql/query.cjs.map +1 -1
- package/build/queries/sql/query.d.cts +108 -77
- package/build/queries/sql/query.d.cts.map +1 -1
- package/build/queries/sql/query.d.mts +108 -77
- package/build/queries/sql/query.d.mts.map +1 -1
- package/build/queries/sql/query.js +58 -23
- package/build/queries/sql/query.js.map +1 -1
- package/build/queries/unnest.d.cts +2 -3
- package/build/queries/unnest.d.cts.map +1 -1
- package/build/queries/unnest.d.mts +2 -3
- package/build/queries/unnest.d.mts.map +1 -1
- package/build/queries/valueFilters.d.cts +5 -6
- package/build/queries/valueFilters.d.cts.map +1 -1
- package/build/queries/valueFilters.d.mts +5 -6
- package/build/queries/valueFilters.d.mts.map +1 -1
- package/build/reactivity/ReactionsManager.cjs +168 -2
- package/build/reactivity/ReactionsManager.cjs.map +1 -1
- package/build/reactivity/ReactionsManager.d.cts +56 -4
- package/build/reactivity/ReactionsManager.d.cts.map +1 -1
- package/build/reactivity/ReactionsManager.d.mts +56 -4
- package/build/reactivity/ReactionsManager.d.mts.map +1 -1
- package/build/reactivity/ReactionsManager.js +169 -3
- package/build/reactivity/ReactionsManager.js.map +1 -1
- package/build/reactivity/reactiveHints.cjs +3 -3
- package/build/reactivity/reactiveHints.cjs.map +1 -1
- package/build/reactivity/reactiveHints.d.cts +18 -18
- package/build/reactivity/reactiveHints.d.cts.map +1 -1
- package/build/reactivity/reactiveHints.d.mts +18 -18
- package/build/reactivity/reactiveHints.d.mts.map +1 -1
- package/build/reactivity/reactiveHints.js +3 -3
- package/build/reactivity/reactiveHints.js.map +1 -1
- package/build/relations/AbstractPropertyImpl.cjs.map +1 -1
- package/build/relations/AbstractPropertyImpl.d.cts +1 -2
- package/build/relations/AbstractPropertyImpl.d.cts.map +1 -1
- package/build/relations/AbstractPropertyImpl.d.mts +1 -2
- package/build/relations/AbstractPropertyImpl.d.mts.map +1 -1
- package/build/relations/AbstractPropertyImpl.js.map +1 -1
- package/build/relations/AbstractRelationImpl.d.cts +1 -2
- package/build/relations/AbstractRelationImpl.d.cts.map +1 -1
- package/build/relations/AbstractRelationImpl.d.mts +1 -2
- package/build/relations/AbstractRelationImpl.d.mts.map +1 -1
- package/build/relations/AsyncProperty.cjs.map +1 -1
- package/build/relations/AsyncProperty.d.cts +5 -6
- package/build/relations/AsyncProperty.d.cts.map +1 -1
- package/build/relations/AsyncProperty.d.mts +5 -6
- package/build/relations/AsyncProperty.d.mts.map +1 -1
- package/build/relations/AsyncProperty.js.map +1 -1
- package/build/relations/AsyncReactiveField.cjs +3 -3
- package/build/relations/AsyncReactiveField.cjs.map +1 -1
- package/build/relations/AsyncReactiveField.d.cts +4 -5
- package/build/relations/AsyncReactiveField.d.cts.map +1 -1
- package/build/relations/AsyncReactiveField.d.mts +4 -5
- package/build/relations/AsyncReactiveField.d.mts.map +1 -1
- package/build/relations/AsyncReactiveField.js +3 -3
- package/build/relations/AsyncReactiveField.js.map +1 -1
- package/build/relations/Collection.d.cts +4 -5
- package/build/relations/Collection.d.cts.map +1 -1
- package/build/relations/Collection.d.mts +4 -5
- package/build/relations/Collection.d.mts.map +1 -1
- package/build/relations/CustomCollection.cjs.map +1 -1
- package/build/relations/CustomCollection.d.cts +3 -4
- package/build/relations/CustomCollection.d.cts.map +1 -1
- package/build/relations/CustomCollection.d.mts +3 -4
- package/build/relations/CustomCollection.d.mts.map +1 -1
- package/build/relations/CustomCollection.js.map +1 -1
- package/build/relations/CustomReference.cjs.map +1 -1
- package/build/relations/CustomReference.d.cts +3 -4
- package/build/relations/CustomReference.d.cts.map +1 -1
- package/build/relations/CustomReference.d.mts +3 -4
- package/build/relations/CustomReference.d.mts.map +1 -1
- package/build/relations/CustomReference.js.map +1 -1
- package/build/relations/EnumCollection.cjs.map +1 -1
- package/build/relations/EnumCollection.d.cts +5 -5
- package/build/relations/EnumCollection.d.cts.map +1 -1
- package/build/relations/EnumCollection.d.mts +5 -5
- package/build/relations/EnumCollection.d.mts.map +1 -1
- package/build/relations/EnumCollection.js.map +1 -1
- package/build/relations/LargeCollection.d.cts +1 -2
- package/build/relations/LargeCollection.d.cts.map +1 -1
- package/build/relations/LargeCollection.d.mts +1 -2
- package/build/relations/LargeCollection.d.mts.map +1 -1
- package/build/relations/LazyField.cjs +14 -0
- package/build/relations/LazyField.cjs.map +1 -1
- package/build/relations/LazyField.d.cts +13 -8
- package/build/relations/LazyField.d.cts.map +1 -1
- package/build/relations/LazyField.d.mts +13 -8
- package/build/relations/LazyField.d.mts.map +1 -1
- package/build/relations/LazyField.js +14 -0
- package/build/relations/LazyField.js.map +1 -1
- package/build/relations/ManyToManyCollection.cjs.map +1 -1
- package/build/relations/ManyToManyCollection.d.cts +2 -3
- package/build/relations/ManyToManyCollection.d.cts.map +1 -1
- package/build/relations/ManyToManyCollection.d.mts +2 -3
- package/build/relations/ManyToManyCollection.d.mts.map +1 -1
- package/build/relations/ManyToManyCollection.js.map +1 -1
- package/build/relations/ManyToManyLargeCollection.d.cts +2 -3
- package/build/relations/ManyToManyLargeCollection.d.cts.map +1 -1
- package/build/relations/ManyToManyLargeCollection.d.mts +2 -3
- package/build/relations/ManyToManyLargeCollection.d.mts.map +1 -1
- package/build/relations/ManyToOneReference.cjs.map +1 -1
- package/build/relations/ManyToOneReference.d.cts +6 -7
- package/build/relations/ManyToOneReference.d.cts.map +1 -1
- package/build/relations/ManyToOneReference.d.mts +6 -7
- package/build/relations/ManyToOneReference.d.mts.map +1 -1
- package/build/relations/ManyToOneReference.js.map +1 -1
- package/build/relations/OneToManyCollection.cjs.map +1 -1
- package/build/relations/OneToManyCollection.d.cts +2 -3
- package/build/relations/OneToManyCollection.d.cts.map +1 -1
- package/build/relations/OneToManyCollection.d.mts +2 -3
- package/build/relations/OneToManyCollection.d.mts.map +1 -1
- package/build/relations/OneToManyCollection.js.map +1 -1
- package/build/relations/OneToManyLargeCollection.cjs.map +1 -1
- package/build/relations/OneToManyLargeCollection.d.cts +2 -3
- package/build/relations/OneToManyLargeCollection.d.cts.map +1 -1
- package/build/relations/OneToManyLargeCollection.d.mts +2 -3
- package/build/relations/OneToManyLargeCollection.d.mts.map +1 -1
- package/build/relations/OneToManyLargeCollection.js.map +1 -1
- package/build/relations/OneToOneReference.cjs.map +1 -1
- package/build/relations/OneToOneReference.d.cts +7 -7
- package/build/relations/OneToOneReference.d.cts.map +1 -1
- package/build/relations/OneToOneReference.d.mts +7 -7
- package/build/relations/OneToOneReference.d.mts.map +1 -1
- package/build/relations/OneToOneReference.js.map +1 -1
- package/build/relations/PolymorphicReference.cjs.map +1 -1
- package/build/relations/PolymorphicReference.d.cts +4 -5
- package/build/relations/PolymorphicReference.d.cts.map +1 -1
- package/build/relations/PolymorphicReference.d.mts +4 -5
- package/build/relations/PolymorphicReference.d.mts.map +1 -1
- package/build/relations/PolymorphicReference.js.map +1 -1
- package/build/relations/PropertySymbols.d.cts +1 -2
- package/build/relations/PropertySymbols.d.cts.map +1 -1
- package/build/relations/PropertySymbols.d.mts +1 -2
- package/build/relations/PropertySymbols.d.mts.map +1 -1
- package/build/relations/ReactiveField.cjs +2 -2
- package/build/relations/ReactiveField.cjs.map +1 -1
- package/build/relations/ReactiveField.d.cts +4 -5
- package/build/relations/ReactiveField.d.cts.map +1 -1
- package/build/relations/ReactiveField.d.mts +4 -5
- package/build/relations/ReactiveField.d.mts.map +1 -1
- package/build/relations/ReactiveField.js +2 -2
- package/build/relations/ReactiveField.js.map +1 -1
- package/build/relations/ReactiveGetter.cjs.map +1 -1
- package/build/relations/ReactiveGetter.d.cts +4 -5
- package/build/relations/ReactiveGetter.d.cts.map +1 -1
- package/build/relations/ReactiveGetter.d.mts +4 -5
- package/build/relations/ReactiveGetter.d.mts.map +1 -1
- package/build/relations/ReactiveGetter.js.map +1 -1
- package/build/relations/ReactiveManyToMany.cjs +1 -1
- package/build/relations/ReactiveManyToMany.cjs.map +1 -1
- package/build/relations/ReactiveManyToMany.d.cts +4 -5
- package/build/relations/ReactiveManyToMany.d.cts.map +1 -1
- package/build/relations/ReactiveManyToMany.d.mts +4 -5
- package/build/relations/ReactiveManyToMany.d.mts.map +1 -1
- package/build/relations/ReactiveManyToMany.js +1 -1
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.cjs.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.cts +4 -5
- package/build/relations/ReactiveManyToManyOtherSide.d.cts.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.mts +4 -5
- package/build/relations/ReactiveManyToManyOtherSide.d.mts.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
- package/build/relations/ReactiveReference.cjs +1 -1
- package/build/relations/ReactiveReference.cjs.map +1 -1
- package/build/relations/ReactiveReference.d.cts +5 -6
- package/build/relations/ReactiveReference.d.cts.map +1 -1
- package/build/relations/ReactiveReference.d.mts +5 -6
- package/build/relations/ReactiveReference.d.mts.map +1 -1
- package/build/relations/ReactiveReference.js +1 -1
- package/build/relations/ReactiveReference.js.map +1 -1
- package/build/relations/ReadOnlyCollection.d.cts +4 -5
- package/build/relations/ReadOnlyCollection.d.cts.map +1 -1
- package/build/relations/ReadOnlyCollection.d.mts +4 -5
- package/build/relations/ReadOnlyCollection.d.mts.map +1 -1
- package/build/relations/RecursiveCollection.cjs +3 -3
- package/build/relations/RecursiveCollection.cjs.map +1 -1
- package/build/relations/RecursiveCollection.d.cts +10 -10
- package/build/relations/RecursiveCollection.d.cts.map +1 -1
- package/build/relations/RecursiveCollection.d.mts +10 -10
- package/build/relations/RecursiveCollection.d.mts.map +1 -1
- package/build/relations/RecursiveCollection.js +3 -3
- package/build/relations/RecursiveCollection.js.map +1 -1
- package/build/relations/RecursiveCycleError.d.cts +2 -2
- package/build/relations/RecursiveCycleError.d.cts.map +1 -1
- package/build/relations/RecursiveCycleError.d.mts +2 -2
- package/build/relations/RecursiveCycleError.d.mts.map +1 -1
- package/build/relations/Reference.d.cts +5 -5
- package/build/relations/Reference.d.cts.map +1 -1
- package/build/relations/Reference.d.mts +5 -5
- package/build/relations/Reference.d.mts.map +1 -1
- package/build/relations/ReferenceSymbols.d.cts +1 -2
- package/build/relations/ReferenceSymbols.d.cts.map +1 -1
- package/build/relations/ReferenceSymbols.d.mts +1 -2
- package/build/relations/ReferenceSymbols.d.mts.map +1 -1
- package/build/relations/Relation.d.cts +3 -3
- package/build/relations/Relation.d.cts.map +1 -1
- package/build/relations/Relation.d.mts +3 -3
- package/build/relations/Relation.d.mts.map +1 -1
- package/build/relations/RelationSymbols.d.cts +2 -3
- package/build/relations/RelationSymbols.d.cts.map +1 -1
- package/build/relations/RelationSymbols.d.mts +2 -3
- package/build/relations/RelationSymbols.d.mts.map +1 -1
- package/build/relations/hasAsyncMethod.cjs.map +1 -1
- package/build/relations/hasAsyncMethod.d.cts +6 -6
- package/build/relations/hasAsyncMethod.d.cts.map +1 -1
- package/build/relations/hasAsyncMethod.d.mts +6 -6
- package/build/relations/hasAsyncMethod.d.mts.map +1 -1
- package/build/relations/hasAsyncMethod.js.map +1 -1
- package/build/relations/hasManyDerived.d.cts +2 -2
- package/build/relations/hasManyDerived.d.cts.map +1 -1
- package/build/relations/hasManyDerived.d.mts +2 -2
- package/build/relations/hasManyDerived.d.mts.map +1 -1
- package/build/relations/hasManyThrough.d.cts +1 -2
- package/build/relations/hasManyThrough.d.cts.map +1 -1
- package/build/relations/hasManyThrough.d.mts +1 -2
- package/build/relations/hasManyThrough.d.mts.map +1 -1
- package/build/relations/hasOneDerived.d.cts +1 -2
- package/build/relations/hasOneDerived.d.cts.map +1 -1
- package/build/relations/hasOneDerived.d.mts +1 -2
- package/build/relations/hasOneDerived.d.mts.map +1 -1
- package/build/relations/hasOneThrough.d.cts +1 -2
- package/build/relations/hasOneThrough.d.cts.map +1 -1
- package/build/relations/hasOneThrough.d.mts +1 -2
- package/build/relations/hasOneThrough.d.mts.map +1 -1
- package/build/relations/hasProperty.cjs +2 -2
- package/build/relations/hasProperty.cjs.map +1 -1
- package/build/relations/hasProperty.d.cts +8 -8
- package/build/relations/hasProperty.d.cts.map +1 -1
- package/build/relations/hasProperty.d.mts +8 -8
- package/build/relations/hasProperty.d.mts.map +1 -1
- package/build/relations/hasProperty.js +2 -2
- package/build/relations/hasProperty.js.map +1 -1
- package/build/relations/index.cjs +5 -5
- package/build/relations/index.js +5 -5
- package/build/relations/isCascadeDelete.d.cts +1 -2
- package/build/relations/isCascadeDelete.d.cts.map +1 -1
- package/build/relations/isCascadeDelete.d.mts +1 -2
- package/build/relations/isCascadeDelete.d.mts.map +1 -1
- package/build/resurrection.cjs +11 -1
- package/build/resurrection.cjs.map +1 -1
- package/build/resurrection.d.cts +5 -3
- package/build/resurrection.d.cts.map +1 -1
- package/build/resurrection.d.mts +5 -3
- package/build/resurrection.d.mts.map +1 -1
- package/build/resurrection.js +11 -2
- package/build/resurrection.js.map +1 -1
- package/build/rules.d.cts +20 -20
- package/build/rules.d.cts.map +1 -1
- package/build/rules.d.mts +20 -20
- package/build/rules.d.mts.map +1 -1
- package/build/runtimeConfig.d.cts +4 -5
- package/build/runtimeConfig.d.cts.map +1 -1
- package/build/runtimeConfig.d.mts +4 -5
- package/build/runtimeConfig.d.mts.map +1 -1
- package/build/serde/TypeInfo.d.cts +3 -4
- package/build/serde/TypeInfo.d.cts.map +1 -1
- package/build/serde/TypeInfo.d.mts +3 -4
- package/build/serde/TypeInfo.d.mts.map +1 -1
- package/build/serde/columns.d.cts +2 -3
- package/build/serde/columns.d.cts.map +1 -1
- package/build/serde/columns.d.mts +2 -3
- package/build/serde/columns.d.mts.map +1 -1
- package/build/serde/fieldSerde.d.cts +9 -9
- package/build/serde/fieldSerde.d.cts.map +1 -1
- package/build/serde/fieldSerde.d.mts +9 -9
- package/build/serde/fieldSerde.d.mts.map +1 -1
- package/build/serde/serde.cjs +35 -0
- package/build/serde/serde.cjs.map +1 -1
- package/build/serde/serde.d.cts +37 -19
- package/build/serde/serde.d.cts.map +1 -1
- package/build/serde/serde.d.mts +37 -19
- package/build/serde/serde.d.mts.map +1 -1
- package/build/serde/serde.js +35 -1
- package/build/serde/serde.js.map +1 -1
- package/build/serde/temporal.d.cts +5 -5
- package/build/serde/temporal.d.cts.map +1 -1
- package/build/serde/temporal.d.mts +5 -5
- package/build/serde/temporal.d.mts.map +1 -1
- package/build/serde/temporalMappers.d.cts +4 -5
- package/build/serde/temporalMappers.d.cts.map +1 -1
- package/build/serde/temporalMappers.d.mts +4 -5
- package/build/serde/temporalMappers.d.mts.map +1 -1
- package/build/transitions.cjs +89 -0
- package/build/transitions.cjs.map +1 -0
- package/build/transitions.d.cts +109 -0
- package/build/transitions.d.cts.map +1 -0
- package/build/transitions.d.mts +109 -0
- package/build/transitions.d.mts.map +1 -0
- package/build/transitions.js +84 -0
- package/build/transitions.js.map +1 -0
- package/build/trusted.d.cts +2 -3
- package/build/trusted.d.cts.map +1 -1
- package/build/trusted.d.mts +2 -3
- package/build/trusted.d.mts.map +1 -1
- package/build/typeMap.d.cts +17 -17
- package/build/typeMap.d.cts.map +1 -1
- package/build/typeMap.d.mts +17 -17
- package/build/typeMap.d.mts.map +1 -1
- package/build/upsert.d.cts +4 -4
- package/build/upsert.d.cts.map +1 -1
- package/build/upsert.d.mts +4 -4
- package/build/upsert.d.mts.map +1 -1
- package/build/utils.d.cts +27 -28
- package/build/utils.d.cts.map +1 -1
- package/build/utils.d.mts +27 -28
- package/build/utils.d.mts.map +1 -1
- package/package.json +6 -6
package/build/IndexManager.cjs
CHANGED
|
@@ -3,6 +3,7 @@ const require_relations_Reference = require("./relations/Reference.cjs");
|
|
|
3
3
|
require("./relations/index.cjs");
|
|
4
4
|
const require_utils = require("./utils.cjs");
|
|
5
5
|
const require_EntityMetadata = require("./EntityMetadata.cjs");
|
|
6
|
+
const require_dataloaders_fastWhereFilterHash = require("./dataloaders/fastWhereFilterHash.cjs");
|
|
6
7
|
const require_Entity = require("./Entity.cjs");
|
|
7
8
|
//#region src/IndexManager.ts
|
|
8
9
|
const emptySet = /* @__PURE__ */ new Set();
|
|
@@ -80,7 +81,7 @@ var IndexManager = class {
|
|
|
80
81
|
if (!entry) {
|
|
81
82
|
entry = {
|
|
82
83
|
field,
|
|
83
|
-
index: new FieldIndex(),
|
|
84
|
+
index: new FieldIndex(meta.columns[fieldName]?.isArray ?? false),
|
|
84
85
|
indexedCount: 0
|
|
85
86
|
};
|
|
86
87
|
indexes.set(fieldName, entry);
|
|
@@ -132,7 +133,12 @@ function getFieldValue(entity, fieldName, field) {
|
|
|
132
133
|
* - relations not having loaded their instances yet
|
|
133
134
|
*/
|
|
134
135
|
var FieldIndex = class {
|
|
136
|
+
isArray;
|
|
135
137
|
#valueToEntities = /* @__PURE__ */ new Map();
|
|
138
|
+
#arrayToEntities = /* @__PURE__ */ new Map();
|
|
139
|
+
constructor(isArray) {
|
|
140
|
+
this.isArray = isArray;
|
|
141
|
+
}
|
|
136
142
|
/** @return entities that have `value` as their current value for this field. */
|
|
137
143
|
get(value) {
|
|
138
144
|
if (require_Entity.isEntity(value) && value.idTaggedMaybe) {
|
|
@@ -142,6 +148,8 @@ var FieldIndex = class {
|
|
|
142
148
|
return matchId ?? matchInstance;
|
|
143
149
|
}
|
|
144
150
|
if (value === null) value = void 0;
|
|
151
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
152
|
+
if (arrayKey !== void 0) return this.#arrayToEntities.get(arrayKey);
|
|
145
153
|
return this.#valueToEntities.get(value);
|
|
146
154
|
}
|
|
147
155
|
add(value, entity) {
|
|
@@ -153,17 +161,27 @@ var FieldIndex = class {
|
|
|
153
161
|
this.doRemove(value, entity);
|
|
154
162
|
}
|
|
155
163
|
doAdd(value, entity) {
|
|
156
|
-
const
|
|
157
|
-
|
|
164
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
165
|
+
const index = arrayKey === void 0 ? this.#valueToEntities : this.#arrayToEntities;
|
|
166
|
+
const key = arrayKey ?? value;
|
|
167
|
+
const set = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
168
|
+
if (set.size === 0) index.set(key, set);
|
|
158
169
|
set.add(entity);
|
|
159
170
|
}
|
|
160
171
|
doRemove(value, entity) {
|
|
161
|
-
const
|
|
172
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
173
|
+
const index = arrayKey === void 0 ? this.#valueToEntities : this.#arrayToEntities;
|
|
174
|
+
const key = arrayKey ?? value;
|
|
175
|
+
const set = index.get(key);
|
|
162
176
|
if (set) {
|
|
163
177
|
set.delete(entity);
|
|
164
|
-
if (set.size === 0)
|
|
178
|
+
if (set.size === 0) index.delete(key);
|
|
165
179
|
}
|
|
166
180
|
}
|
|
181
|
+
/** Preserves array order and element types, including bigint values that JSON cannot encode. */
|
|
182
|
+
maybeArrayKey(value) {
|
|
183
|
+
return this.isArray && Array.isArray(value) ? require_dataloaders_fastWhereFilterHash.fastWhereFilterHash(value) : void 0;
|
|
184
|
+
}
|
|
167
185
|
};
|
|
168
186
|
function intersectSets(set1, set2) {
|
|
169
187
|
const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IndexManager.cjs","names":["#indexes","groupBy","getMetadata","isLoadedReference","#valueToEntities","isEntity"],"sources":["../src/IndexManager.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityMetadata, type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { type ManyToOneReference, type PolymorphicReference, isLoadedReference } from \"src/relations/index.ts\";\nimport { groupBy } from \"src/utils.ts\";\n\ntype FieldValue = any;\ntype FieldName = string;\ntype EntityTag = string;\ntype FieldIndexEntry = {\n field: Field;\n index: FieldIndex;\n /** Tracks how far into the EM's list-per-tag array this field index has been lazily populated. */\n indexedCount: number;\n};\nconst emptySet = new Set<Entity>();\n\n// The test reproducing a n^2 with n=500 went from 100ms to 50ms if indexed\nexport const indexThreshold = 500;\n\n/**\n * IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.\n *\n * Key features:\n * - Only indexes entity types with >1000 entities to avoid overhead for small datasets\n * - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)\n * - Automatically maintains indexes when fields are updated via setField()\n */\nexport class IndexManager {\n readonly #indexes: Map<EntityTag, Map<FieldName, FieldIndexEntry>> = new Map();\n\n /** @return if we should index entities of this type/count. */\n shouldIndexType(entityCount: number): boolean {\n return entityCount >= indexThreshold;\n }\n\n /** Visible for testing. */\n isIndexed(tagName: string): boolean {\n return this.#indexes.has(tagName);\n }\n\n /** Enables indexing for an entity type and builds indexes for the requested fields. */\n enableIndexingForType<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: object): void {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n this.#indexes.set(tagName, new Map());\n }\n\n const fieldNames = Object.keys(where);\n const fieldIndexes = this.#indexes.get(tagName)!;\n const missingFieldNames = fieldNames.filter((fieldName) => !fieldIndexes.has(fieldName));\n if (missingFieldNames.length === 0) return;\n\n // If subtypes are involved, group by each subtype\n if (meta.baseType || meta.subTypes.length > 0) {\n [...groupBy(entities, (e) => getMetadata(e)).entries()].forEach(([meta, entities]) => {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n });\n } else {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n }\n }\n\n /** Updates indexes when a field value changes. */\n maybeUpdateFieldIndex(entity: Entity, fieldName: string, oldValue: any, newValue: any): void {\n // Fast return for the common case, which is no indexing\n if (this.#indexes.size === 0) return;\n\n const meta = getMetadata(entity);\n const fieldIndexes = this.#indexes.get(meta.tagName);\n if (!fieldIndexes) return; // Type not indexed\n\n const fieldIndex = fieldIndexes.get(fieldName)?.index;\n if (!fieldIndex) return; // Field is not indexed\n\n fieldIndex.remove(oldValue, entity);\n fieldIndex.add(newValue, entity);\n }\n\n /**\n * Finds entities matching the given where clause using indexes.\n *\n * Returns the raw candidate set (possibly a live internal index set, so do not mutate it);\n * the caller applies its own subtype/deleted filtering while building the final array.\n */\n findMatching<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: any): ReadonlySet<T> {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n throw new Error(`${meta.type} is not indexed`);\n }\n const fieldIndexes = this.#indexes.get(tagName)!;\n // Start with all entities of the 1st condition, then intersect (AND) each subsequent field in `where`\n let candidates: Set<Entity> | undefined;\n for (const [fieldName, value] of Object.entries(where)) {\n this.indexUnindexedEntities(entities, fieldName);\n const fieldCandidates = fieldIndexes.get(fieldName)?.index.get(value);\n if (!candidates) {\n candidates = fieldCandidates ?? emptySet; // This is the 1st clause\n } else {\n candidates = fieldCandidates ? intersectSets(candidates, fieldCandidates) : emptySet;\n }\n // Early exit if no candidates remain\n if (candidates.size === 0) break;\n }\n if (candidates === undefined) {\n throw new Error(`Expected where clause with at least one condition`);\n }\n return candidates as unknown as ReadonlySet<T>;\n }\n\n // `entities` should all be of the exact same subtype\n private addEntitiesToIndex(meta: EntityMetadata, entities: Entity[], fieldNames: string[]): void {\n const { tagName } = meta;\n const indexes = this.#indexes.get(tagName)!;\n // Iterate over each field so we can do a shared fieldIndex lookup\n for (const fieldName of fieldNames) {\n const field = meta.allFields[fieldName];\n if (!field) continue;\n let entry = indexes.get(fieldName);\n if (!entry) {\n entry = { field, index: new FieldIndex(), indexedCount: 0 };\n indexes.set(fieldName, entry);\n }\n for (const entity of entities) {\n // Deleted entities never match, and reading a flushed one's relations throws\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n }\n\n /** Lazily indexes newly-registered entities only for fields that are queried. */\n private indexUnindexedEntities<T extends Entity>(entities: T[], fieldName: string): void {\n const first = entities[0];\n if (first === undefined) return;\n\n const meta = getMetadata(first);\n const entry = this.#indexes.get(meta.tagName)?.get(fieldName);\n if (entry === undefined || entry.indexedCount === entities.length) return;\n\n for (let i = entry.indexedCount; i < entities.length; i++) {\n const entity = entities[i];\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n}\n\n// Use the same field access pattern as entityMatches\nfunction getFieldValue(entity: Entity, fieldName: string, field: Field): any {\n const fn = fieldName as keyof Entity;\n switch (field.kind) {\n case \"primaryKey\":\n return entity.idTaggedMaybe;\n case \"enum\":\n case \"primitive\":\n return (entity as any)[fn];\n case \"m2o\":\n case \"poly\":\n const relation = (entity as any)[fn] as\n | ManyToOneReference<Entity, any, any>\n | PolymorphicReference<Entity, any, any>;\n if (isLoadedReference(relation)) {\n return relation.get;\n } else if (relation.isSet) {\n return relation.id;\n } else {\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\n/**\n * For a given field, like `firstName`, holds the reverse index of value `bob` -> entities `[a1, a2]`.\n *\n * This also handles indexes `Entity` values as both instances & IDs, to handle both:\n * - new/unpersisted entities not yet having IDs, and\n * - relations not having loaded their instances yet\n */\nclass FieldIndex {\n readonly #valueToEntities = new Map<FieldValue, Set<Entity>>();\n\n /** @return entities that have `value` as their current value for this field. */\n get(value: any): Set<Entity> | undefined {\n if (isEntity(value) && value.idTaggedMaybe) {\n // If `value` is a persisted entity like `a:1`, and entities could have indexed their `author_id=a:1` field\n // values as the unloaded \"just an a:1 string id\" value, before `a:1` was loaded into memory.\n const matchId = this.#valueToEntities.get(value.idTaggedMaybe);\n // But other entities could also have indexes `a:1` as an instance, so go ahead and check both\n const matchInstance = this.#valueToEntities.get(value);\n if (matchId && matchInstance) {\n return new Set([...matchId, ...matchInstance]);\n }\n return matchId ?? matchInstance;\n }\n // Treat null and undefined as equivalent for unset relations\n if (value === null) value = undefined;\n return this.#valueToEntities.get(value);\n }\n\n add(value: any, entity: Entity): void {\n // If this is an entity, store both the entity itself & its ID\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doAdd(value.idTaggedMaybe, entity);\n }\n this.doAdd(value, entity);\n }\n\n remove(value: any, entity: Entity): void {\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doRemove(value.idTaggedMaybe, entity);\n }\n this.doRemove(value, entity);\n }\n\n private doAdd(value: any, entity: Entity): void {\n const set = this.#valueToEntities.get(value) ?? new Set();\n if (set.size === 0) this.#valueToEntities.set(value, set);\n set.add(entity);\n }\n\n private doRemove(value: any, entity: Entity): void {\n const set = this.#valueToEntities.get(value);\n if (set) {\n set.delete(entity);\n if (set.size === 0) {\n this.#valueToEntities.delete(value);\n }\n }\n }\n}\n\nfunction intersectSets<T>(set1: Set<T>, set2: Set<T>): Set<T> {\n const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];\n const result = new Set<T>();\n for (const item of smaller) {\n if (larger.has(item)) {\n result.add(item);\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IndexManager.cjs","names":["groupBy","getMetadata","isLoadedReference","isEntity","fastWhereFilterHash"],"sources":["../src/IndexManager.ts"],"sourcesContent":["import { fastWhereFilterHash } from \"src/dataloaders/fastWhereFilterHash.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityMetadata, type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { type ManyToOneReference, type PolymorphicReference, isLoadedReference } from \"src/relations/index.ts\";\nimport { groupBy } from \"src/utils.ts\";\n\ntype FieldValue = any;\ntype FieldName = string;\ntype EntityTag = string;\ntype FieldIndexEntry = {\n field: Field;\n index: FieldIndex;\n /** Tracks how far into the EM's list-per-tag array this field index has been lazily populated. */\n indexedCount: number;\n};\nconst emptySet = new Set<Entity>();\n\n// The test reproducing a n^2 with n=500 went from 100ms to 50ms if indexed\nexport const indexThreshold = 500;\n\n/**\n * IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.\n *\n * Key features:\n * - Only indexes entity types with >1000 entities to avoid overhead for small datasets\n * - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)\n * - Automatically maintains indexes when fields are updated via setField()\n */\nexport class IndexManager {\n readonly #indexes: Map<EntityTag, Map<FieldName, FieldIndexEntry>> = new Map();\n\n /** @return if we should index entities of this type/count. */\n shouldIndexType(entityCount: number): boolean {\n return entityCount >= indexThreshold;\n }\n\n /** Visible for testing. */\n isIndexed(tagName: string): boolean {\n return this.#indexes.has(tagName);\n }\n\n /** Enables indexing for an entity type and builds indexes for the requested fields. */\n enableIndexingForType<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: object): void {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n this.#indexes.set(tagName, new Map());\n }\n\n const fieldNames = Object.keys(where);\n const fieldIndexes = this.#indexes.get(tagName)!;\n const missingFieldNames = fieldNames.filter((fieldName) => !fieldIndexes.has(fieldName));\n if (missingFieldNames.length === 0) return;\n\n // If subtypes are involved, group by each subtype\n if (meta.baseType || meta.subTypes.length > 0) {\n [...groupBy(entities, (e) => getMetadata(e)).entries()].forEach(([meta, entities]) => {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n });\n } else {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n }\n }\n\n /** Updates indexes when a field value changes. */\n maybeUpdateFieldIndex(entity: Entity, fieldName: string, oldValue: any, newValue: any): void {\n // Fast return for the common case, which is no indexing\n if (this.#indexes.size === 0) return;\n\n const meta = getMetadata(entity);\n const fieldIndexes = this.#indexes.get(meta.tagName);\n if (!fieldIndexes) return; // Type not indexed\n\n const fieldIndex = fieldIndexes.get(fieldName)?.index;\n if (!fieldIndex) return; // Field is not indexed\n\n fieldIndex.remove(oldValue, entity);\n fieldIndex.add(newValue, entity);\n }\n\n /**\n * Finds entities matching the given where clause using indexes.\n *\n * Returns the raw candidate set (possibly a live internal index set, so do not mutate it);\n * the caller applies its own subtype/deleted filtering while building the final array.\n */\n findMatching<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: any): ReadonlySet<T> {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n throw new Error(`${meta.type} is not indexed`);\n }\n const fieldIndexes = this.#indexes.get(tagName)!;\n // Start with all entities of the 1st condition, then intersect (AND) each subsequent field in `where`\n let candidates: Set<Entity> | undefined;\n for (const [fieldName, value] of Object.entries(where)) {\n this.indexUnindexedEntities(entities, fieldName);\n const fieldCandidates = fieldIndexes.get(fieldName)?.index.get(value);\n if (!candidates) {\n candidates = fieldCandidates ?? emptySet; // This is the 1st clause\n } else {\n candidates = fieldCandidates ? intersectSets(candidates, fieldCandidates) : emptySet;\n }\n // Early exit if no candidates remain\n if (candidates.size === 0) break;\n }\n if (candidates === undefined) {\n throw new Error(`Expected where clause with at least one condition`);\n }\n return candidates as unknown as ReadonlySet<T>;\n }\n\n // `entities` should all be of the exact same subtype\n private addEntitiesToIndex(meta: EntityMetadata, entities: Entity[], fieldNames: string[]): void {\n const { tagName } = meta;\n const indexes = this.#indexes.get(tagName)!;\n // Iterate over each field so we can do a shared fieldIndex lookup\n for (const fieldName of fieldNames) {\n const field = meta.allFields[fieldName];\n if (!field) continue;\n let entry = indexes.get(fieldName);\n if (!entry) {\n entry = { field, index: new FieldIndex(meta.columns[fieldName]?.isArray ?? false), indexedCount: 0 };\n indexes.set(fieldName, entry);\n }\n for (const entity of entities) {\n // Deleted entities never match, and reading a flushed one's relations throws\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n }\n\n /** Lazily indexes newly-registered entities only for fields that are queried. */\n private indexUnindexedEntities<T extends Entity>(entities: T[], fieldName: string): void {\n const first = entities[0];\n if (first === undefined) return;\n\n const meta = getMetadata(first);\n const entry = this.#indexes.get(meta.tagName)?.get(fieldName);\n if (entry === undefined || entry.indexedCount === entities.length) return;\n\n for (let i = entry.indexedCount; i < entities.length; i++) {\n const entity = entities[i];\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n}\n\n// Use the same field access pattern as entityMatches\nfunction getFieldValue(entity: Entity, fieldName: string, field: Field): any {\n const fn = fieldName as keyof Entity;\n switch (field.kind) {\n case \"primaryKey\":\n return entity.idTaggedMaybe;\n case \"enum\":\n case \"primitive\":\n return (entity as any)[fn];\n case \"m2o\":\n case \"poly\":\n const relation = (entity as any)[fn] as\n | ManyToOneReference<Entity, any, any>\n | PolymorphicReference<Entity, any, any>;\n if (isLoadedReference(relation)) {\n return relation.get;\n } else if (relation.isSet) {\n return relation.id;\n } else {\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\n/**\n * For a given field, like `firstName`, holds the reverse index of value `bob` -> entities `[a1, a2]`.\n *\n * This also handles indexes `Entity` values as both instances & IDs, to handle both:\n * - new/unpersisted entities not yet having IDs, and\n * - relations not having loaded their instances yet\n */\nclass FieldIndex {\n readonly #valueToEntities = new Map<FieldValue, Set<Entity>>();\n // Keep serialized arrays separate so their keys cannot collide with scalar string values.\n readonly #arrayToEntities = new Map<string, Set<Entity>>();\n\n constructor(private readonly isArray: boolean) {}\n\n /** @return entities that have `value` as their current value for this field. */\n get(value: any): Set<Entity> | undefined {\n if (isEntity(value) && value.idTaggedMaybe) {\n // If `value` is a persisted entity like `a:1`, and entities could have indexed their `author_id=a:1` field\n // values as the unloaded \"just an a:1 string id\" value, before `a:1` was loaded into memory.\n const matchId = this.#valueToEntities.get(value.idTaggedMaybe);\n // But other entities could also have indexes `a:1` as an instance, so go ahead and check both\n const matchInstance = this.#valueToEntities.get(value);\n if (matchId && matchInstance) {\n return new Set([...matchId, ...matchInstance]);\n }\n return matchId ?? matchInstance;\n }\n // Treat null and undefined as equivalent for unset relations\n if (value === null) value = undefined;\n const arrayKey = this.maybeArrayKey(value);\n if (arrayKey !== undefined) return this.#arrayToEntities.get(arrayKey);\n return this.#valueToEntities.get(value);\n }\n\n add(value: any, entity: Entity): void {\n // If this is an entity, store both the entity itself & its ID\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doAdd(value.idTaggedMaybe, entity);\n }\n this.doAdd(value, entity);\n }\n\n remove(value: any, entity: Entity): void {\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doRemove(value.idTaggedMaybe, entity);\n }\n this.doRemove(value, entity);\n }\n\n private doAdd(value: any, entity: Entity): void {\n const arrayKey = this.maybeArrayKey(value);\n const index = arrayKey === undefined ? this.#valueToEntities : this.#arrayToEntities;\n const key = arrayKey ?? value;\n const set = index.get(key) ?? new Set();\n if (set.size === 0) index.set(key, set);\n set.add(entity);\n }\n\n private doRemove(value: any, entity: Entity): void {\n const arrayKey = this.maybeArrayKey(value);\n const index = arrayKey === undefined ? this.#valueToEntities : this.#arrayToEntities;\n const key = arrayKey ?? value;\n const set = index.get(key);\n if (set) {\n set.delete(entity);\n if (set.size === 0) {\n index.delete(key);\n }\n }\n }\n\n /** Preserves array order and element types, including bigint values that JSON cannot encode. */\n private maybeArrayKey(value: unknown): string | undefined {\n return this.isArray && Array.isArray(value) ? fastWhereFilterHash(value) : undefined;\n }\n}\n\nfunction intersectSets<T>(set1: Set<T>, set2: Set<T>): Set<T> {\n const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];\n const result = new Set<T>();\n for (const item of smaller) {\n if (larger.has(item)) {\n result.add(item);\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
package/build/IndexManager.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EntityMetadata } from "./EntityMetadata.cjs";
|
|
2
2
|
import { Entity } from "./Entity.cjs";
|
|
3
3
|
//#region src/IndexManager.d.ts
|
|
4
|
-
declare const indexThreshold = 500;
|
|
4
|
+
export declare const indexThreshold = 500;
|
|
5
5
|
/**
|
|
6
6
|
* IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.
|
|
7
7
|
*
|
|
@@ -10,7 +10,7 @@ declare const indexThreshold = 500;
|
|
|
10
10
|
* - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)
|
|
11
11
|
* - Automatically maintains indexes when fields are updated via setField()
|
|
12
12
|
*/
|
|
13
|
-
declare class IndexManager {
|
|
13
|
+
export declare class IndexManager {
|
|
14
14
|
#private;
|
|
15
15
|
/** @return if we should index entities of this type/count. */
|
|
16
16
|
shouldIndexType(entityCount: number): boolean;
|
|
@@ -32,5 +32,4 @@ declare class IndexManager {
|
|
|
32
32
|
private indexUnindexedEntities;
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
|
-
export { IndexManager, indexThreshold };
|
|
36
35
|
//# sourceMappingURL=IndexManager.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IndexManager.d.cts","names":[],"sources":["../src/IndexManager.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"IndexManager.d.cts","names":[],"sources":["../src/IndexManager.ts"],"mappings":";;;qBAkBa;;;;;;;;;qBAUA;;;EAIX,gBAAgB;;EAKhB,UAAU;;EAKV,sBAAsB,UAAU,QAAQ,MAAM,eAAe,IAAI,UAAU,KAAK;;EAsBhF,sBAAsB,QAAQ,QAAQ,mBAAmB,eAAe;;;;;;;EAqBxE,aAAa,UAAU,QAAQ,MAAM,eAAe,IAAI,UAAU,KAAK,aAAa,YAAY;UA0BxF;;UAuBA"}
|
package/build/IndexManager.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EntityMetadata } from "./EntityMetadata.mjs";
|
|
2
2
|
import { Entity } from "./Entity.mjs";
|
|
3
3
|
//#region src/IndexManager.d.ts
|
|
4
|
-
declare const indexThreshold = 500;
|
|
4
|
+
export declare const indexThreshold = 500;
|
|
5
5
|
/**
|
|
6
6
|
* IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.
|
|
7
7
|
*
|
|
@@ -10,7 +10,7 @@ declare const indexThreshold = 500;
|
|
|
10
10
|
* - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)
|
|
11
11
|
* - Automatically maintains indexes when fields are updated via setField()
|
|
12
12
|
*/
|
|
13
|
-
declare class IndexManager {
|
|
13
|
+
export declare class IndexManager {
|
|
14
14
|
#private;
|
|
15
15
|
/** @return if we should index entities of this type/count. */
|
|
16
16
|
shouldIndexType(entityCount: number): boolean;
|
|
@@ -32,5 +32,4 @@ declare class IndexManager {
|
|
|
32
32
|
private indexUnindexedEntities;
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
|
-
export { IndexManager, indexThreshold };
|
|
36
35
|
//# sourceMappingURL=IndexManager.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IndexManager.d.mts","names":[],"sources":["../src/IndexManager.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"IndexManager.d.mts","names":[],"sources":["../src/IndexManager.ts"],"mappings":";;;qBAkBa;;;;;;;;;qBAUA;;;EAIX,gBAAgB;;EAKhB,UAAU;;EAKV,sBAAsB,UAAU,QAAQ,MAAM,eAAe,IAAI,UAAU,KAAK;;EAsBhF,sBAAsB,QAAQ,QAAQ,mBAAmB,eAAe;;;;;;;EAqBxE,aAAa,UAAU,QAAQ,MAAM,eAAe,IAAI,UAAU,KAAK,aAAa,YAAY;UA0BxF;;UAuBA"}
|
package/build/IndexManager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isLoadedReference } from "./relations/Reference.js";
|
|
|
2
2
|
import "./relations/index.js";
|
|
3
3
|
import { groupBy } from "./utils.js";
|
|
4
4
|
import { getMetadata } from "./EntityMetadata.js";
|
|
5
|
+
import { fastWhereFilterHash } from "./dataloaders/fastWhereFilterHash.js";
|
|
5
6
|
import { isEntity } from "./Entity.js";
|
|
6
7
|
//#region src/IndexManager.ts
|
|
7
8
|
const emptySet = /* @__PURE__ */ new Set();
|
|
@@ -79,7 +80,7 @@ var IndexManager = class {
|
|
|
79
80
|
if (!entry) {
|
|
80
81
|
entry = {
|
|
81
82
|
field,
|
|
82
|
-
index: new FieldIndex(),
|
|
83
|
+
index: new FieldIndex(meta.columns[fieldName]?.isArray ?? false),
|
|
83
84
|
indexedCount: 0
|
|
84
85
|
};
|
|
85
86
|
indexes.set(fieldName, entry);
|
|
@@ -131,7 +132,12 @@ function getFieldValue(entity, fieldName, field) {
|
|
|
131
132
|
* - relations not having loaded their instances yet
|
|
132
133
|
*/
|
|
133
134
|
var FieldIndex = class {
|
|
135
|
+
isArray;
|
|
134
136
|
#valueToEntities = /* @__PURE__ */ new Map();
|
|
137
|
+
#arrayToEntities = /* @__PURE__ */ new Map();
|
|
138
|
+
constructor(isArray) {
|
|
139
|
+
this.isArray = isArray;
|
|
140
|
+
}
|
|
135
141
|
/** @return entities that have `value` as their current value for this field. */
|
|
136
142
|
get(value) {
|
|
137
143
|
if (isEntity(value) && value.idTaggedMaybe) {
|
|
@@ -141,6 +147,8 @@ var FieldIndex = class {
|
|
|
141
147
|
return matchId ?? matchInstance;
|
|
142
148
|
}
|
|
143
149
|
if (value === null) value = void 0;
|
|
150
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
151
|
+
if (arrayKey !== void 0) return this.#arrayToEntities.get(arrayKey);
|
|
144
152
|
return this.#valueToEntities.get(value);
|
|
145
153
|
}
|
|
146
154
|
add(value, entity) {
|
|
@@ -152,17 +160,27 @@ var FieldIndex = class {
|
|
|
152
160
|
this.doRemove(value, entity);
|
|
153
161
|
}
|
|
154
162
|
doAdd(value, entity) {
|
|
155
|
-
const
|
|
156
|
-
|
|
163
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
164
|
+
const index = arrayKey === void 0 ? this.#valueToEntities : this.#arrayToEntities;
|
|
165
|
+
const key = arrayKey ?? value;
|
|
166
|
+
const set = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
167
|
+
if (set.size === 0) index.set(key, set);
|
|
157
168
|
set.add(entity);
|
|
158
169
|
}
|
|
159
170
|
doRemove(value, entity) {
|
|
160
|
-
const
|
|
171
|
+
const arrayKey = this.maybeArrayKey(value);
|
|
172
|
+
const index = arrayKey === void 0 ? this.#valueToEntities : this.#arrayToEntities;
|
|
173
|
+
const key = arrayKey ?? value;
|
|
174
|
+
const set = index.get(key);
|
|
161
175
|
if (set) {
|
|
162
176
|
set.delete(entity);
|
|
163
|
-
if (set.size === 0)
|
|
177
|
+
if (set.size === 0) index.delete(key);
|
|
164
178
|
}
|
|
165
179
|
}
|
|
180
|
+
/** Preserves array order and element types, including bigint values that JSON cannot encode. */
|
|
181
|
+
maybeArrayKey(value) {
|
|
182
|
+
return this.isArray && Array.isArray(value) ? fastWhereFilterHash(value) : void 0;
|
|
183
|
+
}
|
|
166
184
|
};
|
|
167
185
|
function intersectSets(set1, set2) {
|
|
168
186
|
const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IndexManager.js","names":["#indexes","#valueToEntities"],"sources":["../src/IndexManager.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityMetadata, type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { type ManyToOneReference, type PolymorphicReference, isLoadedReference } from \"src/relations/index.ts\";\nimport { groupBy } from \"src/utils.ts\";\n\ntype FieldValue = any;\ntype FieldName = string;\ntype EntityTag = string;\ntype FieldIndexEntry = {\n field: Field;\n index: FieldIndex;\n /** Tracks how far into the EM's list-per-tag array this field index has been lazily populated. */\n indexedCount: number;\n};\nconst emptySet = new Set<Entity>();\n\n// The test reproducing a n^2 with n=500 went from 100ms to 50ms if indexed\nexport const indexThreshold = 500;\n\n/**\n * IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.\n *\n * Key features:\n * - Only indexes entity types with >1000 entities to avoid overhead for small datasets\n * - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)\n * - Automatically maintains indexes when fields are updated via setField()\n */\nexport class IndexManager {\n readonly #indexes: Map<EntityTag, Map<FieldName, FieldIndexEntry>> = new Map();\n\n /** @return if we should index entities of this type/count. */\n shouldIndexType(entityCount: number): boolean {\n return entityCount >= indexThreshold;\n }\n\n /** Visible for testing. */\n isIndexed(tagName: string): boolean {\n return this.#indexes.has(tagName);\n }\n\n /** Enables indexing for an entity type and builds indexes for the requested fields. */\n enableIndexingForType<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: object): void {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n this.#indexes.set(tagName, new Map());\n }\n\n const fieldNames = Object.keys(where);\n const fieldIndexes = this.#indexes.get(tagName)!;\n const missingFieldNames = fieldNames.filter((fieldName) => !fieldIndexes.has(fieldName));\n if (missingFieldNames.length === 0) return;\n\n // If subtypes are involved, group by each subtype\n if (meta.baseType || meta.subTypes.length > 0) {\n [...groupBy(entities, (e) => getMetadata(e)).entries()].forEach(([meta, entities]) => {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n });\n } else {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n }\n }\n\n /** Updates indexes when a field value changes. */\n maybeUpdateFieldIndex(entity: Entity, fieldName: string, oldValue: any, newValue: any): void {\n // Fast return for the common case, which is no indexing\n if (this.#indexes.size === 0) return;\n\n const meta = getMetadata(entity);\n const fieldIndexes = this.#indexes.get(meta.tagName);\n if (!fieldIndexes) return; // Type not indexed\n\n const fieldIndex = fieldIndexes.get(fieldName)?.index;\n if (!fieldIndex) return; // Field is not indexed\n\n fieldIndex.remove(oldValue, entity);\n fieldIndex.add(newValue, entity);\n }\n\n /**\n * Finds entities matching the given where clause using indexes.\n *\n * Returns the raw candidate set (possibly a live internal index set, so do not mutate it);\n * the caller applies its own subtype/deleted filtering while building the final array.\n */\n findMatching<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: any): ReadonlySet<T> {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n throw new Error(`${meta.type} is not indexed`);\n }\n const fieldIndexes = this.#indexes.get(tagName)!;\n // Start with all entities of the 1st condition, then intersect (AND) each subsequent field in `where`\n let candidates: Set<Entity> | undefined;\n for (const [fieldName, value] of Object.entries(where)) {\n this.indexUnindexedEntities(entities, fieldName);\n const fieldCandidates = fieldIndexes.get(fieldName)?.index.get(value);\n if (!candidates) {\n candidates = fieldCandidates ?? emptySet; // This is the 1st clause\n } else {\n candidates = fieldCandidates ? intersectSets(candidates, fieldCandidates) : emptySet;\n }\n // Early exit if no candidates remain\n if (candidates.size === 0) break;\n }\n if (candidates === undefined) {\n throw new Error(`Expected where clause with at least one condition`);\n }\n return candidates as unknown as ReadonlySet<T>;\n }\n\n // `entities` should all be of the exact same subtype\n private addEntitiesToIndex(meta: EntityMetadata, entities: Entity[], fieldNames: string[]): void {\n const { tagName } = meta;\n const indexes = this.#indexes.get(tagName)!;\n // Iterate over each field so we can do a shared fieldIndex lookup\n for (const fieldName of fieldNames) {\n const field = meta.allFields[fieldName];\n if (!field) continue;\n let entry = indexes.get(fieldName);\n if (!entry) {\n entry = { field, index: new FieldIndex(), indexedCount: 0 };\n indexes.set(fieldName, entry);\n }\n for (const entity of entities) {\n // Deleted entities never match, and reading a flushed one's relations throws\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n }\n\n /** Lazily indexes newly-registered entities only for fields that are queried. */\n private indexUnindexedEntities<T extends Entity>(entities: T[], fieldName: string): void {\n const first = entities[0];\n if (first === undefined) return;\n\n const meta = getMetadata(first);\n const entry = this.#indexes.get(meta.tagName)?.get(fieldName);\n if (entry === undefined || entry.indexedCount === entities.length) return;\n\n for (let i = entry.indexedCount; i < entities.length; i++) {\n const entity = entities[i];\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n}\n\n// Use the same field access pattern as entityMatches\nfunction getFieldValue(entity: Entity, fieldName: string, field: Field): any {\n const fn = fieldName as keyof Entity;\n switch (field.kind) {\n case \"primaryKey\":\n return entity.idTaggedMaybe;\n case \"enum\":\n case \"primitive\":\n return (entity as any)[fn];\n case \"m2o\":\n case \"poly\":\n const relation = (entity as any)[fn] as\n | ManyToOneReference<Entity, any, any>\n | PolymorphicReference<Entity, any, any>;\n if (isLoadedReference(relation)) {\n return relation.get;\n } else if (relation.isSet) {\n return relation.id;\n } else {\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\n/**\n * For a given field, like `firstName`, holds the reverse index of value `bob` -> entities `[a1, a2]`.\n *\n * This also handles indexes `Entity` values as both instances & IDs, to handle both:\n * - new/unpersisted entities not yet having IDs, and\n * - relations not having loaded their instances yet\n */\nclass FieldIndex {\n readonly #valueToEntities = new Map<FieldValue, Set<Entity>>();\n\n /** @return entities that have `value` as their current value for this field. */\n get(value: any): Set<Entity> | undefined {\n if (isEntity(value) && value.idTaggedMaybe) {\n // If `value` is a persisted entity like `a:1`, and entities could have indexed their `author_id=a:1` field\n // values as the unloaded \"just an a:1 string id\" value, before `a:1` was loaded into memory.\n const matchId = this.#valueToEntities.get(value.idTaggedMaybe);\n // But other entities could also have indexes `a:1` as an instance, so go ahead and check both\n const matchInstance = this.#valueToEntities.get(value);\n if (matchId && matchInstance) {\n return new Set([...matchId, ...matchInstance]);\n }\n return matchId ?? matchInstance;\n }\n // Treat null and undefined as equivalent for unset relations\n if (value === null) value = undefined;\n return this.#valueToEntities.get(value);\n }\n\n add(value: any, entity: Entity): void {\n // If this is an entity, store both the entity itself & its ID\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doAdd(value.idTaggedMaybe, entity);\n }\n this.doAdd(value, entity);\n }\n\n remove(value: any, entity: Entity): void {\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doRemove(value.idTaggedMaybe, entity);\n }\n this.doRemove(value, entity);\n }\n\n private doAdd(value: any, entity: Entity): void {\n const set = this.#valueToEntities.get(value) ?? new Set();\n if (set.size === 0) this.#valueToEntities.set(value, set);\n set.add(entity);\n }\n\n private doRemove(value: any, entity: Entity): void {\n const set = this.#valueToEntities.get(value);\n if (set) {\n set.delete(entity);\n if (set.size === 0) {\n this.#valueToEntities.delete(value);\n }\n }\n }\n}\n\nfunction intersectSets<T>(set1: Set<T>, set2: Set<T>): Set<T> {\n const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];\n const result = new Set<T>();\n for (const item of smaller) {\n if (larger.has(item)) {\n result.add(item);\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IndexManager.js","names":[],"sources":["../src/IndexManager.ts"],"sourcesContent":["import { fastWhereFilterHash } from \"src/dataloaders/fastWhereFilterHash.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityMetadata, type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { type ManyToOneReference, type PolymorphicReference, isLoadedReference } from \"src/relations/index.ts\";\nimport { groupBy } from \"src/utils.ts\";\n\ntype FieldValue = any;\ntype FieldName = string;\ntype EntityTag = string;\ntype FieldIndexEntry = {\n field: Field;\n index: FieldIndex;\n /** Tracks how far into the EM's list-per-tag array this field index has been lazily populated. */\n indexedCount: number;\n};\nconst emptySet = new Set<Entity>();\n\n// The test reproducing a n^2 with n=500 went from 100ms to 50ms if indexed\nexport const indexThreshold = 500;\n\n/**\n * IndexManager provides field-based indexing for entity queries to avoid O(n) linear scans of `em.entities`.\n *\n * Key features:\n * - Only indexes entity types with >1000 entities to avoid overhead for small datasets\n * - Supports dual indexing for m2o/poly fields (by ID for saved, by instance for unsaved)\n * - Automatically maintains indexes when fields are updated via setField()\n */\nexport class IndexManager {\n readonly #indexes: Map<EntityTag, Map<FieldName, FieldIndexEntry>> = new Map();\n\n /** @return if we should index entities of this type/count. */\n shouldIndexType(entityCount: number): boolean {\n return entityCount >= indexThreshold;\n }\n\n /** Visible for testing. */\n isIndexed(tagName: string): boolean {\n return this.#indexes.has(tagName);\n }\n\n /** Enables indexing for an entity type and builds indexes for the requested fields. */\n enableIndexingForType<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: object): void {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n this.#indexes.set(tagName, new Map());\n }\n\n const fieldNames = Object.keys(where);\n const fieldIndexes = this.#indexes.get(tagName)!;\n const missingFieldNames = fieldNames.filter((fieldName) => !fieldIndexes.has(fieldName));\n if (missingFieldNames.length === 0) return;\n\n // If subtypes are involved, group by each subtype\n if (meta.baseType || meta.subTypes.length > 0) {\n [...groupBy(entities, (e) => getMetadata(e)).entries()].forEach(([meta, entities]) => {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n });\n } else {\n this.addEntitiesToIndex(meta, entities, missingFieldNames);\n }\n }\n\n /** Updates indexes when a field value changes. */\n maybeUpdateFieldIndex(entity: Entity, fieldName: string, oldValue: any, newValue: any): void {\n // Fast return for the common case, which is no indexing\n if (this.#indexes.size === 0) return;\n\n const meta = getMetadata(entity);\n const fieldIndexes = this.#indexes.get(meta.tagName);\n if (!fieldIndexes) return; // Type not indexed\n\n const fieldIndex = fieldIndexes.get(fieldName)?.index;\n if (!fieldIndex) return; // Field is not indexed\n\n fieldIndex.remove(oldValue, entity);\n fieldIndex.add(newValue, entity);\n }\n\n /**\n * Finds entities matching the given where clause using indexes.\n *\n * Returns the raw candidate set (possibly a live internal index set, so do not mutate it);\n * the caller applies its own subtype/deleted filtering while building the final array.\n */\n findMatching<T extends Entity>(meta: EntityMetadata<T>, entities: T[], where: any): ReadonlySet<T> {\n const { tagName } = meta;\n if (!this.#indexes.has(tagName)) {\n throw new Error(`${meta.type} is not indexed`);\n }\n const fieldIndexes = this.#indexes.get(tagName)!;\n // Start with all entities of the 1st condition, then intersect (AND) each subsequent field in `where`\n let candidates: Set<Entity> | undefined;\n for (const [fieldName, value] of Object.entries(where)) {\n this.indexUnindexedEntities(entities, fieldName);\n const fieldCandidates = fieldIndexes.get(fieldName)?.index.get(value);\n if (!candidates) {\n candidates = fieldCandidates ?? emptySet; // This is the 1st clause\n } else {\n candidates = fieldCandidates ? intersectSets(candidates, fieldCandidates) : emptySet;\n }\n // Early exit if no candidates remain\n if (candidates.size === 0) break;\n }\n if (candidates === undefined) {\n throw new Error(`Expected where clause with at least one condition`);\n }\n return candidates as unknown as ReadonlySet<T>;\n }\n\n // `entities` should all be of the exact same subtype\n private addEntitiesToIndex(meta: EntityMetadata, entities: Entity[], fieldNames: string[]): void {\n const { tagName } = meta;\n const indexes = this.#indexes.get(tagName)!;\n // Iterate over each field so we can do a shared fieldIndex lookup\n for (const fieldName of fieldNames) {\n const field = meta.allFields[fieldName];\n if (!field) continue;\n let entry = indexes.get(fieldName);\n if (!entry) {\n entry = { field, index: new FieldIndex(meta.columns[fieldName]?.isArray ?? false), indexedCount: 0 };\n indexes.set(fieldName, entry);\n }\n for (const entity of entities) {\n // Deleted entities never match, and reading a flushed one's relations throws\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n }\n\n /** Lazily indexes newly-registered entities only for fields that are queried. */\n private indexUnindexedEntities<T extends Entity>(entities: T[], fieldName: string): void {\n const first = entities[0];\n if (first === undefined) return;\n\n const meta = getMetadata(first);\n const entry = this.#indexes.get(meta.tagName)?.get(fieldName);\n if (entry === undefined || entry.indexedCount === entities.length) return;\n\n for (let i = entry.indexedCount; i < entities.length; i++) {\n const entity = entities[i];\n if (entity.isDeletedEntity) continue;\n const value = getFieldValue(entity, fieldName, entry.field);\n entry.index.add(value, entity);\n }\n entry.indexedCount = entities.length;\n }\n}\n\n// Use the same field access pattern as entityMatches\nfunction getFieldValue(entity: Entity, fieldName: string, field: Field): any {\n const fn = fieldName as keyof Entity;\n switch (field.kind) {\n case \"primaryKey\":\n return entity.idTaggedMaybe;\n case \"enum\":\n case \"primitive\":\n return (entity as any)[fn];\n case \"m2o\":\n case \"poly\":\n const relation = (entity as any)[fn] as\n | ManyToOneReference<Entity, any, any>\n | PolymorphicReference<Entity, any, any>;\n if (isLoadedReference(relation)) {\n return relation.get;\n } else if (relation.isSet) {\n return relation.id;\n } else {\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\n/**\n * For a given field, like `firstName`, holds the reverse index of value `bob` -> entities `[a1, a2]`.\n *\n * This also handles indexes `Entity` values as both instances & IDs, to handle both:\n * - new/unpersisted entities not yet having IDs, and\n * - relations not having loaded their instances yet\n */\nclass FieldIndex {\n readonly #valueToEntities = new Map<FieldValue, Set<Entity>>();\n // Keep serialized arrays separate so their keys cannot collide with scalar string values.\n readonly #arrayToEntities = new Map<string, Set<Entity>>();\n\n constructor(private readonly isArray: boolean) {}\n\n /** @return entities that have `value` as their current value for this field. */\n get(value: any): Set<Entity> | undefined {\n if (isEntity(value) && value.idTaggedMaybe) {\n // If `value` is a persisted entity like `a:1`, and entities could have indexed their `author_id=a:1` field\n // values as the unloaded \"just an a:1 string id\" value, before `a:1` was loaded into memory.\n const matchId = this.#valueToEntities.get(value.idTaggedMaybe);\n // But other entities could also have indexes `a:1` as an instance, so go ahead and check both\n const matchInstance = this.#valueToEntities.get(value);\n if (matchId && matchInstance) {\n return new Set([...matchId, ...matchInstance]);\n }\n return matchId ?? matchInstance;\n }\n // Treat null and undefined as equivalent for unset relations\n if (value === null) value = undefined;\n const arrayKey = this.maybeArrayKey(value);\n if (arrayKey !== undefined) return this.#arrayToEntities.get(arrayKey);\n return this.#valueToEntities.get(value);\n }\n\n add(value: any, entity: Entity): void {\n // If this is an entity, store both the entity itself & its ID\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doAdd(value.idTaggedMaybe, entity);\n }\n this.doAdd(value, entity);\n }\n\n remove(value: any, entity: Entity): void {\n if (isEntity(value) && value.idTaggedMaybe) {\n this.doRemove(value.idTaggedMaybe, entity);\n }\n this.doRemove(value, entity);\n }\n\n private doAdd(value: any, entity: Entity): void {\n const arrayKey = this.maybeArrayKey(value);\n const index = arrayKey === undefined ? this.#valueToEntities : this.#arrayToEntities;\n const key = arrayKey ?? value;\n const set = index.get(key) ?? new Set();\n if (set.size === 0) index.set(key, set);\n set.add(entity);\n }\n\n private doRemove(value: any, entity: Entity): void {\n const arrayKey = this.maybeArrayKey(value);\n const index = arrayKey === undefined ? this.#valueToEntities : this.#arrayToEntities;\n const key = arrayKey ?? value;\n const set = index.get(key);\n if (set) {\n set.delete(entity);\n if (set.size === 0) {\n index.delete(key);\n }\n }\n }\n\n /** Preserves array order and element types, including bigint values that JSON cannot encode. */\n private maybeArrayKey(value: unknown): string | undefined {\n return this.isArray && Array.isArray(value) ? fastWhereFilterHash(value) : undefined;\n }\n}\n\nfunction intersectSets<T>(set1: Set<T>, set2: Set<T>): Set<T> {\n const [smaller, larger] = set1.size <= set2.size ? [set1, set2] : [set2, set1];\n const result = new Set<T>();\n for (const item of smaller) {\n if (larger.has(item)) {\n result.add(item);\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstanceData.cjs","names":["emptyRowData","#new","#flags","#deleted","#originalData","#referenceHistory","hasAnyKey"],"sources":["../src/InstanceData.ts"],"sourcesContent":["import { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { type RowData, emptyRowData } from \"src/RowData.ts\";\nimport { hasAnyKey } from \"src/utils.ts\";\n\n/** The `#orm` metadata field we track on each instance. */\nexport class InstanceData {\n /** The entity this instance data belongs to. */\n readonly entity: Entity;\n /** All entities must be associated to an `EntityManager` to handle lazy loading/etc. */\n readonly em: EntityManager;\n /** A pointer to our entity type's metadata. */\n readonly metadata: EntityMetadata;\n /** A bag for our lazy-initialized relations, allocated on first relation access. */\n relations: Record<any, any> | undefined = undefined;\n /** The as-loaded database row, i.e. our `rowIndex`-th row within our query's `RowData` result. */\n rowData: RowData = emptyRowData;\n /** Our row's index within `rowData`; see {@link RowData}. */\n rowIndex: number = 0;\n /** The domain-value of fields, lazily converted (if needed) on read from the database columns. */\n data: Record<string, any>;\n /** A bag to keep the original values, allocated on first mutation (most entities are never mutated). */\n #originalData: Record<any, any> | undefined = undefined;\n /** Reference values that were set during this unit of work, allocated on first m2o/poly change. */\n #referenceHistory: Record<string, any[]> | undefined = undefined;\n /** A bag to keep the flushed-to-database values, only for AsyncReactiveField reactivity. */\n flushedData: Record<any, any> | undefined;\n /** Packs the new/deleted `Operation`s + isTouched into one slot; see the accessors below. */\n #flags: number = 0;\n /** The `a#1`, `a#2` index if we're an em.create-d entity. We never unset this, to keep ids stable across flushes. */\n createId: string | undefined;\n /** The zero-based order this entity was registered with its EntityManager. */\n entityIndex: number = -1;\n\n /** Whether our entity is new or not. */\n get #new(): Operation | undefined {\n const value = this.#flags & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #new(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b11) | (op ?? 0);\n }\n\n /**\n * Whether our entity has been deleted or not.\n *\n * - `pending` means we've been marked for deletion via `em.delete` but not issued a `DELETE`\n * - `flushed` means we've flushed a `DELETE` but `em.flush` hasn't fully completed yet, likely due to AsyncReactiveField calcs\n * - `deleted` means we've been flushed and `em.flush` has completed\n */\n get #deleted(): Operation | undefined {\n const value = (this.#flags >> 2) & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #deleted(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b1100) | ((op ?? 0) << 2);\n }\n\n /** Whether our entity should flush regardless of any other changes. */\n get isTouched(): boolean {\n return (this.#flags & 0b10000) !== 0;\n }\n\n set isTouched(value: boolean) {\n this.#flags = value ? this.#flags | 0b10000 : this.#flags & ~0b10000;\n }\n\n /** Creates the `#orm` field. */\n constructor(em: EntityManager, entity: Entity, metadata: EntityMetadata, isNew: boolean) {\n this.entity = entity;\n this.em = em;\n this.metadata = metadata;\n if (isNew) {\n this.#new = Operation.Pending;\n this.data = {};\n this.flushedData = undefined;\n if (em) this.markMaybePending();\n } else {\n this.#new = undefined;\n this.data = {};\n this.flushedData = undefined;\n }\n }\n\n /**\n * Returns the as-loaded value of `columnName`, i.e. `getColumn(\"rank\")`.\n *\n * This saves callers from knowing that `rowData`/`rowIndex` are a pair, but note it is\n * a snapshot of the query that loaded us, and specifically:\n *\n * - The value is driver-level, i.e. numeric enum ids, untagged foreign keys, and temporals\n * as strings; use `getField` if you want the domain value, and\n * - The value is not updated by `em.flush`, so after a flush it is the pre-flush value, and\n * - New entities have no row, so every column is `undefined`.\n */\n getColumn(columnName: string): any {\n return this.rowData.get(this.rowIndex, columnName);\n }\n\n /** If `em.flush` has detected a dirty RQF, reset our internal dirty state, w/o reseting our external-dirty state. */\n resetForRqfLoop() {\n this.flushedData = {};\n if (this.#new === Operation.Pending) this.#new = Operation.Flushed;\n if (this.#deleted === Operation.Pending) this.#deleted = Operation.Flushed;\n }\n\n resetAfterFlushed() {\n this.#originalData = undefined;\n this.#referenceHistory = undefined;\n this.flushedData = undefined;\n this.isTouched = false;\n if (this.#new === Operation.Pending || this.#new === Operation.Flushed) {\n this.#new = Operation.Complete;\n }\n if (this.#deleted === Operation.Pending || this.#deleted === Operation.Flushed) {\n this.#deleted = Operation.Complete;\n }\n this.unmarkMaybePending();\n }\n\n /** Our public-facing `isNewEntity`. */\n get isNewEntity(): boolean {\n return this.#new !== undefined && this.#new !== Operation.Complete;\n }\n\n /** Our public-facing `isDeletedEntity`. */\n get isDeletedEntity(): boolean {\n return this.#deleted !== undefined;\n }\n\n /** The original values of mutated fields; a shared empty object until the entity is actually mutated. */\n get originalData(): Record<any, any> {\n return this.#originalData ?? emptyObject;\n }\n\n /** Our public-facing `isDirtyEntity`. */\n get isDirtyEntity(): boolean {\n return hasAnyKey(this.#originalData);\n }\n\n /** This is our internal \"is pending flush\", which is aware of RQF micro-flush loops. */\n get pendingOperation(): \"insert\" | \"update\" | \"delete\" | \"none\" | \"created-then-deleted\" {\n if (this.#deleted !== undefined) {\n // Mark entities that were created and them immediately `em.delete`-d; granted this is\n // probably rare, but we shouldn't run hooks or have the driver try and delete these.\n // Note that we return them, instead of skipping entirely, so that fixupCreatedThenDeleted\n // can later be called.\n return this.#deleted === Operation.Pending && this.#new === Operation.Pending\n ? \"created-then-deleted\"\n : this.#deleted === Operation.Pending\n ? \"delete\"\n : \"none\";\n } else if (this.#new === Operation.Pending) {\n // Per ^, if `#new === flushed`, we fall through to check if micro-flush UPDATEs are required\n return \"insert\";\n } else if (this.flushedData) {\n return hasAnyKey(this.flushedData) ? \"update\" : \"none\";\n } else {\n return this.isTouched || hasAnyKey(this.#originalData) ? \"update\" : \"none\";\n }\n }\n\n get changedFields(): string[] {\n return Object.keys(this.changedData);\n }\n\n /** The changed-fields bag (flushedData during RQF loops, else originalData) without allocating a keys array. */\n get changedData(): Record<any, any> {\n return this.flushedData ?? this.#originalData ?? emptyObject;\n }\n\n /** Called when an `em.load` tries to find the entity and it's just gone from the db. */\n markDeletedBecauseNotFound(): void {\n this.#deleted = Operation.Complete;\n }\n\n /** Called by `em.delete`, returns true if this is new information. */\n markDeleted(): boolean {\n if (this.#deleted === undefined) {\n // Let any OneToManyCollection/ReactiveField/ReactiveReference.get caches know that they should recalc\n // (i.e. their filterDeleted logic) by asserting the `deleteBook.author` field is changing.\n for (const field of Object.values(this.metadata.allFields)) {\n // We only do reference/collection fields and skip the primitives, b/c we assume something like\n // `{ books: \"title\" }` doesn't need `resetIsLoaded(..., \"title\")` b/c the same hint will also\n // be watching \"did books change\", and so get reset that way.\n if (field.kind === \"m2o\" || field.kind === \"poly\" || field.kind === \"m2m\" || field.kind === \"m2mEnum\") {\n getEmInternalApi(this.em).isLoadedCache.resetIsLoaded(this.entity, field.fieldName);\n } else if (field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"primaryKey\") {\n // Any reactable watching these fields will also be watching reference/collection\n } else if (field.kind === \"o2m\" || field.kind === \"o2o\") {\n // Any reactable watching these actually gets triggered by the primary m2o/poly side changing\n } else if (field.kind === \"lo2m\") {\n // Doesn't support reactivity\n } else {\n throw new Error(`Unhandled field kind ${field}`);\n }\n }\n this.#deleted = Operation.Pending;\n this.markMaybePending();\n return true;\n }\n return false;\n }\n\n fixupCreatedThenDeleted(): void {\n // Ideally we could do this via `resetAfterFlushed`?\n this.#deleted = Operation.Complete;\n this.unmarkMaybePending();\n }\n\n /** Marks this entity as needing a flush even if no fields changed. */\n markTouched(): void {\n this.isTouched = true;\n this.markMaybePending();\n }\n\n /** Marks a field as dirty with its pre-mutation value. */\n markFieldDirty(fieldName: string, value: any): void {\n (this.#originalData ??= {})[fieldName] = value;\n this.markMaybePending();\n }\n\n /** Marks a reverted field as clean again. */\n markFieldClean(fieldName: string): void {\n if (this.#originalData) delete this.#originalData[fieldName];\n this.markMaybePending();\n }\n\n /** Marks this entity as a possible flush candidate. */\n markMaybePending(): void {\n getEmInternalApi(this.em).markMaybePending(this.entity);\n }\n\n /** Removes this entity from the possible flush candidate set. */\n private unmarkMaybePending(): void {\n getEmInternalApi(this.em).unmarkMaybePending(this.entity);\n }\n\n /** Remembers a reference value that may need to be reverse-walked later. */\n rememberReferenceValue(fieldName: string, value: any): void {\n if (value === undefined) return;\n const values = ((this.#referenceHistory ??= {})[fieldName] ??= []);\n if (!values.includes(value)) values.push(value);\n }\n\n /** Returns reference values that may need to be reverse-walked later. */\n getReferenceHistory(fieldName: string): readonly any[] {\n return this.#referenceHistory?.[fieldName] ?? emptyArray;\n }\n\n get isDeletedAndFlushed(): boolean {\n return this.#deleted === Operation.Complete;\n }\n\n /**\n * Returns true if the entity is new or was new in this EM.\n *\n * This is primarily used for lazy-initializing relations, i.e. if:\n *\n * - A new `Author` is created\n * - We `em.flush` the author to the database\n * - Then `a1.books` is accessed for the first time\n *\n * We can have a high-confidence that `a1` has no books, because we just\n * created it, so we can set the OneToManyCollection to loaded.\n */\n get isOrWasNew(): boolean {\n // #new will be left as `complete`\n return this.#new !== undefined;\n }\n}\n\n/** An enum to track our insert/delete state progression. */\nenum Operation {\n Pending = 1,\n Flushed = 2,\n Complete = 3,\n}\n\n/** A shared frozen bag for un-mutated entities; strict mode makes accidental writes throw. */\nconst emptyObject: Record<any, any> = Object.freeze({});\n\n/** A shared frozen array for fields with no remembered reference values. */\nconst emptyArray: readonly any[] = Object.freeze([]);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InstanceData.cjs","names":["emptyRowData","hasAnyKey"],"sources":["../src/InstanceData.ts"],"sourcesContent":["import { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { type RowData, emptyRowData } from \"src/RowData.ts\";\nimport { hasAnyKey } from \"src/utils.ts\";\n\n/** The `#orm` metadata field we track on each instance. */\nexport class InstanceData {\n /** The entity this instance data belongs to. */\n readonly entity: Entity;\n /** All entities must be associated to an `EntityManager` to handle lazy loading/etc. */\n readonly em: EntityManager;\n /** A pointer to our entity type's metadata. */\n readonly metadata: EntityMetadata;\n /** A bag for our lazy-initialized relations, allocated on first relation access. */\n relations: Record<any, any> | undefined = undefined;\n /** The as-loaded database row, i.e. our `rowIndex`-th row within our query's `RowData` result. */\n rowData: RowData = emptyRowData;\n /** Our row's index within `rowData`; see {@link RowData}. */\n rowIndex: number = 0;\n /** The domain-value of fields, lazily converted (if needed) on read from the database columns. */\n data: Record<string, any>;\n /** A bag to keep the original values, allocated on first mutation (most entities are never mutated). */\n #originalData: Record<any, any> | undefined = undefined;\n /** Reference values that were set during this unit of work, allocated on first m2o/poly change. */\n #referenceHistory: Record<string, any[]> | undefined = undefined;\n /** A bag to keep the flushed-to-database values, only for AsyncReactiveField reactivity. */\n flushedData: Record<any, any> | undefined;\n /** Packs the new/deleted `Operation`s + isTouched into one slot; see the accessors below. */\n #flags: number = 0;\n /** The `a#1`, `a#2` index if we're an em.create-d entity. We never unset this, to keep ids stable across flushes. */\n createId: string | undefined;\n /** The zero-based order this entity was registered with its EntityManager. */\n entityIndex: number = -1;\n\n /** Whether our entity is new or not. */\n get #new(): Operation | undefined {\n const value = this.#flags & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #new(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b11) | (op ?? 0);\n }\n\n /**\n * Whether our entity has been deleted or not.\n *\n * - `pending` means we've been marked for deletion via `em.delete` but not issued a `DELETE`\n * - `flushed` means we've flushed a `DELETE` but `em.flush` hasn't fully completed yet, likely due to AsyncReactiveField calcs\n * - `deleted` means we've been flushed and `em.flush` has completed\n */\n get #deleted(): Operation | undefined {\n const value = (this.#flags >> 2) & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #deleted(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b1100) | ((op ?? 0) << 2);\n }\n\n /** Whether our entity should flush regardless of any other changes. */\n get isTouched(): boolean {\n return (this.#flags & 0b10000) !== 0;\n }\n\n set isTouched(value: boolean) {\n this.#flags = value ? this.#flags | 0b10000 : this.#flags & ~0b10000;\n }\n\n /** Creates the `#orm` field. */\n constructor(em: EntityManager, entity: Entity, metadata: EntityMetadata, isNew: boolean) {\n this.entity = entity;\n this.em = em;\n this.metadata = metadata;\n if (isNew) {\n this.#new = Operation.Pending;\n this.data = {};\n this.flushedData = undefined;\n if (em) this.markMaybePending();\n } else {\n this.#new = undefined;\n this.data = {};\n this.flushedData = undefined;\n }\n }\n\n /**\n * Returns the as-loaded value of `columnName`, i.e. `getColumn(\"rank\")`.\n *\n * This saves callers from knowing that `rowData`/`rowIndex` are a pair, but note it is\n * a snapshot of the query that loaded us, and specifically:\n *\n * - The value is driver-level, i.e. numeric enum ids, untagged foreign keys, and temporals\n * as strings; use `getField` if you want the domain value, and\n * - The value is not updated by `em.flush`, so after a flush it is the pre-flush value, and\n * - New entities have no row, so every column is `undefined`.\n */\n getColumn(columnName: string): any {\n return this.rowData.get(this.rowIndex, columnName);\n }\n\n /** If `em.flush` has detected a dirty RQF, reset our internal dirty state, w/o reseting our external-dirty state. */\n resetForRqfLoop() {\n this.flushedData = {};\n if (this.#new === Operation.Pending) this.#new = Operation.Flushed;\n if (this.#deleted === Operation.Pending) this.#deleted = Operation.Flushed;\n }\n\n resetAfterFlushed() {\n this.#originalData = undefined;\n this.#referenceHistory = undefined;\n this.flushedData = undefined;\n this.isTouched = false;\n if (this.#new === Operation.Pending || this.#new === Operation.Flushed) {\n this.#new = Operation.Complete;\n }\n if (this.#deleted === Operation.Pending || this.#deleted === Operation.Flushed) {\n this.#deleted = Operation.Complete;\n }\n this.unmarkMaybePending();\n }\n\n /** Our public-facing `isNewEntity`. */\n get isNewEntity(): boolean {\n return this.#new !== undefined && this.#new !== Operation.Complete;\n }\n\n /** Our public-facing `isDeletedEntity`. */\n get isDeletedEntity(): boolean {\n return this.#deleted !== undefined;\n }\n\n /** The original values of mutated fields; a shared empty object until the entity is actually mutated. */\n get originalData(): Record<any, any> {\n return this.#originalData ?? emptyObject;\n }\n\n /** Our public-facing `isDirtyEntity`. */\n get isDirtyEntity(): boolean {\n return hasAnyKey(this.#originalData);\n }\n\n /** This is our internal \"is pending flush\", which is aware of RQF micro-flush loops. */\n get pendingOperation(): \"insert\" | \"update\" | \"delete\" | \"none\" | \"created-then-deleted\" {\n if (this.#deleted !== undefined) {\n // Mark entities that were created and them immediately `em.delete`-d; granted this is\n // probably rare, but we shouldn't run hooks or have the driver try and delete these.\n // Note that we return them, instead of skipping entirely, so that fixupCreatedThenDeleted\n // can later be called.\n return this.#deleted === Operation.Pending && this.#new === Operation.Pending\n ? \"created-then-deleted\"\n : this.#deleted === Operation.Pending\n ? \"delete\"\n : \"none\";\n } else if (this.#new === Operation.Pending) {\n // Per ^, if `#new === flushed`, we fall through to check if micro-flush UPDATEs are required\n return \"insert\";\n } else if (this.flushedData) {\n return hasAnyKey(this.flushedData) ? \"update\" : \"none\";\n } else {\n return this.isTouched || hasAnyKey(this.#originalData) ? \"update\" : \"none\";\n }\n }\n\n get changedFields(): string[] {\n return Object.keys(this.changedData);\n }\n\n /** The changed-fields bag (flushedData during RQF loops, else originalData) without allocating a keys array. */\n get changedData(): Record<any, any> {\n return this.flushedData ?? this.#originalData ?? emptyObject;\n }\n\n /** Called when an `em.load` tries to find the entity and it's just gone from the db. */\n markDeletedBecauseNotFound(): void {\n this.#deleted = Operation.Complete;\n }\n\n /** Called by `em.delete`, returns true if this is new information. */\n markDeleted(): boolean {\n if (this.#deleted === undefined) {\n // Let any OneToManyCollection/ReactiveField/ReactiveReference.get caches know that they should recalc\n // (i.e. their filterDeleted logic) by asserting the `deleteBook.author` field is changing.\n for (const field of Object.values(this.metadata.allFields)) {\n // We only do reference/collection fields and skip the primitives, b/c we assume something like\n // `{ books: \"title\" }` doesn't need `resetIsLoaded(..., \"title\")` b/c the same hint will also\n // be watching \"did books change\", and so get reset that way.\n if (field.kind === \"m2o\" || field.kind === \"poly\" || field.kind === \"m2m\" || field.kind === \"m2mEnum\") {\n getEmInternalApi(this.em).isLoadedCache.resetIsLoaded(this.entity, field.fieldName);\n } else if (field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"primaryKey\") {\n // Any reactable watching these fields will also be watching reference/collection\n } else if (field.kind === \"o2m\" || field.kind === \"o2o\") {\n // Any reactable watching these actually gets triggered by the primary m2o/poly side changing\n } else if (field.kind === \"lo2m\") {\n // Doesn't support reactivity\n } else {\n throw new Error(`Unhandled field kind ${field}`);\n }\n }\n this.#deleted = Operation.Pending;\n this.markMaybePending();\n return true;\n }\n return false;\n }\n\n fixupCreatedThenDeleted(): void {\n // Ideally we could do this via `resetAfterFlushed`?\n this.#deleted = Operation.Complete;\n this.unmarkMaybePending();\n }\n\n /** Marks this entity as needing a flush even if no fields changed. */\n markTouched(): void {\n this.isTouched = true;\n this.markMaybePending();\n }\n\n /** Marks a field as dirty with its pre-mutation value. */\n markFieldDirty(fieldName: string, value: any): void {\n (this.#originalData ??= {})[fieldName] = value;\n this.markMaybePending();\n }\n\n /** Marks a reverted field as clean again. */\n markFieldClean(fieldName: string): void {\n if (this.#originalData) delete this.#originalData[fieldName];\n this.markMaybePending();\n }\n\n /** Marks this entity as a possible flush candidate. */\n markMaybePending(): void {\n getEmInternalApi(this.em).markMaybePending(this.entity);\n }\n\n /** Removes this entity from the possible flush candidate set. */\n private unmarkMaybePending(): void {\n getEmInternalApi(this.em).unmarkMaybePending(this.entity);\n }\n\n /** Remembers a reference value that may need to be reverse-walked later. */\n rememberReferenceValue(fieldName: string, value: any): void {\n if (value === undefined) return;\n const values = ((this.#referenceHistory ??= {})[fieldName] ??= []);\n if (!values.includes(value)) values.push(value);\n }\n\n /** Returns reference values that may need to be reverse-walked later. */\n getReferenceHistory(fieldName: string): readonly any[] {\n return this.#referenceHistory?.[fieldName] ?? emptyArray;\n }\n\n get isDeletedAndFlushed(): boolean {\n return this.#deleted === Operation.Complete;\n }\n\n /**\n * Returns true if the entity is new or was new in this EM.\n *\n * This is primarily used for lazy-initializing relations, i.e. if:\n *\n * - A new `Author` is created\n * - We `em.flush` the author to the database\n * - Then `a1.books` is accessed for the first time\n *\n * We can have a high-confidence that `a1` has no books, because we just\n * created it, so we can set the OneToManyCollection to loaded.\n */\n get isOrWasNew(): boolean {\n // #new will be left as `complete`\n return this.#new !== undefined;\n }\n}\n\n/** An enum to track our insert/delete state progression. */\nenum Operation {\n Pending = 1,\n Flushed = 2,\n Complete = 3,\n}\n\n/** A shared frozen bag for un-mutated entities; strict mode makes accidental writes throw. */\nconst emptyObject: Record<any, any> = Object.freeze({});\n\n/** A shared frozen array for fields with no remembered reference values. */\nconst emptyArray: readonly any[] = Object.freeze([]);\n"],"mappings":"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"}
|
package/build/InstanceData.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ import { EntityManager } from "./EntityManager.cjs";
|
|
|
4
4
|
import { Entity } from "./Entity.cjs";
|
|
5
5
|
//#region src/InstanceData.d.ts
|
|
6
6
|
/** The `#orm` metadata field we track on each instance. */
|
|
7
|
-
declare class InstanceData {
|
|
7
|
+
export declare class InstanceData {
|
|
8
8
|
#private;
|
|
9
9
|
/** The entity this instance data belongs to. */
|
|
10
10
|
readonly entity: Entity;
|
|
@@ -94,5 +94,4 @@ declare class InstanceData {
|
|
|
94
94
|
get isOrWasNew(): boolean;
|
|
95
95
|
}
|
|
96
96
|
//#endregion
|
|
97
|
-
export { InstanceData };
|
|
98
97
|
//# sourceMappingURL=InstanceData.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstanceData.d.cts","names":[],"sources":["../src/InstanceData.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"InstanceData.d.cts","names":[],"sources":["../src/InstanceData.ts"],"mappings":";;;;;;qBAOa;;;WAEF,QAAQ;;WAER,IAAI;;WAEJ,UAAU;;EAEnB,WAAW;;EAEX,SAAS;;EAET;;EAEA,MAAM;;EAMN,aAAa;;EAIb;;EAEA;;MA6BI;MAIA,UAAU;;EAKd,YAAY,IAAI,eAAe,QAAQ,QAAQ,UAAU,gBAAgB;;;;;;;;;;;;EA2BzE,UAAU;;EAKV;EAMA;;MAeI;;MAKA;;MAKA,gBAAgB;;MAKhB;;MAKA;MAqBA;;MAKA,eAAe;;EAKnB;;EAKA;EA2BA;;EAOA;;EAMA,eAAe,mBAAmB;;EAMlC,eAAe;;EAMf;;UAKQ;;EAKR,uBAAuB,mBAAmB;;EAO1C,oBAAoB;MAIhB;;;;;;;;;;;;;MAgBA"}
|
package/build/InstanceData.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ import { EntityManager } from "./EntityManager.mjs";
|
|
|
4
4
|
import { Entity } from "./Entity.mjs";
|
|
5
5
|
//#region src/InstanceData.d.ts
|
|
6
6
|
/** The `#orm` metadata field we track on each instance. */
|
|
7
|
-
declare class InstanceData {
|
|
7
|
+
export declare class InstanceData {
|
|
8
8
|
#private;
|
|
9
9
|
/** The entity this instance data belongs to. */
|
|
10
10
|
readonly entity: Entity;
|
|
@@ -94,5 +94,4 @@ declare class InstanceData {
|
|
|
94
94
|
get isOrWasNew(): boolean;
|
|
95
95
|
}
|
|
96
96
|
//#endregion
|
|
97
|
-
export { InstanceData };
|
|
98
97
|
//# sourceMappingURL=InstanceData.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstanceData.d.mts","names":[],"sources":["../src/InstanceData.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"InstanceData.d.mts","names":[],"sources":["../src/InstanceData.ts"],"mappings":";;;;;;qBAOa;;;WAEF,QAAQ;;WAER,IAAI;;WAEJ,UAAU;;EAEnB,WAAW;;EAEX,SAAS;;EAET;;EAEA,MAAM;;EAMN,aAAa;;EAIb;;EAEA;;MA6BI;MAIA,UAAU;;EAKd,YAAY,IAAI,eAAe,QAAQ,QAAQ,UAAU,gBAAgB;;;;;;;;;;;;EA2BzE,UAAU;;EAKV;EAMA;;MAeI;;MAKA;;MAKA,gBAAgB;;MAKhB;;MAKA;MAqBA;;MAKA,eAAe;;EAKnB;;EAKA;EA2BA;;EAOA;;EAMA,eAAe,mBAAmB;;EAMlC,eAAe;;EAMf;;UAKQ;;EAKR,uBAAuB,mBAAmB;;EAO1C,oBAAoB;MAIhB;;;;;;;;;;;;;MAgBA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstanceData.js","names":["#new","#flags","#deleted","#originalData","#referenceHistory"],"sources":["../src/InstanceData.ts"],"sourcesContent":["import { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { type RowData, emptyRowData } from \"src/RowData.ts\";\nimport { hasAnyKey } from \"src/utils.ts\";\n\n/** The `#orm` metadata field we track on each instance. */\nexport class InstanceData {\n /** The entity this instance data belongs to. */\n readonly entity: Entity;\n /** All entities must be associated to an `EntityManager` to handle lazy loading/etc. */\n readonly em: EntityManager;\n /** A pointer to our entity type's metadata. */\n readonly metadata: EntityMetadata;\n /** A bag for our lazy-initialized relations, allocated on first relation access. */\n relations: Record<any, any> | undefined = undefined;\n /** The as-loaded database row, i.e. our `rowIndex`-th row within our query's `RowData` result. */\n rowData: RowData = emptyRowData;\n /** Our row's index within `rowData`; see {@link RowData}. */\n rowIndex: number = 0;\n /** The domain-value of fields, lazily converted (if needed) on read from the database columns. */\n data: Record<string, any>;\n /** A bag to keep the original values, allocated on first mutation (most entities are never mutated). */\n #originalData: Record<any, any> | undefined = undefined;\n /** Reference values that were set during this unit of work, allocated on first m2o/poly change. */\n #referenceHistory: Record<string, any[]> | undefined = undefined;\n /** A bag to keep the flushed-to-database values, only for AsyncReactiveField reactivity. */\n flushedData: Record<any, any> | undefined;\n /** Packs the new/deleted `Operation`s + isTouched into one slot; see the accessors below. */\n #flags: number = 0;\n /** The `a#1`, `a#2` index if we're an em.create-d entity. We never unset this, to keep ids stable across flushes. */\n createId: string | undefined;\n /** The zero-based order this entity was registered with its EntityManager. */\n entityIndex: number = -1;\n\n /** Whether our entity is new or not. */\n get #new(): Operation | undefined {\n const value = this.#flags & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #new(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b11) | (op ?? 0);\n }\n\n /**\n * Whether our entity has been deleted or not.\n *\n * - `pending` means we've been marked for deletion via `em.delete` but not issued a `DELETE`\n * - `flushed` means we've flushed a `DELETE` but `em.flush` hasn't fully completed yet, likely due to AsyncReactiveField calcs\n * - `deleted` means we've been flushed and `em.flush` has completed\n */\n get #deleted(): Operation | undefined {\n const value = (this.#flags >> 2) & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #deleted(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b1100) | ((op ?? 0) << 2);\n }\n\n /** Whether our entity should flush regardless of any other changes. */\n get isTouched(): boolean {\n return (this.#flags & 0b10000) !== 0;\n }\n\n set isTouched(value: boolean) {\n this.#flags = value ? this.#flags | 0b10000 : this.#flags & ~0b10000;\n }\n\n /** Creates the `#orm` field. */\n constructor(em: EntityManager, entity: Entity, metadata: EntityMetadata, isNew: boolean) {\n this.entity = entity;\n this.em = em;\n this.metadata = metadata;\n if (isNew) {\n this.#new = Operation.Pending;\n this.data = {};\n this.flushedData = undefined;\n if (em) this.markMaybePending();\n } else {\n this.#new = undefined;\n this.data = {};\n this.flushedData = undefined;\n }\n }\n\n /**\n * Returns the as-loaded value of `columnName`, i.e. `getColumn(\"rank\")`.\n *\n * This saves callers from knowing that `rowData`/`rowIndex` are a pair, but note it is\n * a snapshot of the query that loaded us, and specifically:\n *\n * - The value is driver-level, i.e. numeric enum ids, untagged foreign keys, and temporals\n * as strings; use `getField` if you want the domain value, and\n * - The value is not updated by `em.flush`, so after a flush it is the pre-flush value, and\n * - New entities have no row, so every column is `undefined`.\n */\n getColumn(columnName: string): any {\n return this.rowData.get(this.rowIndex, columnName);\n }\n\n /** If `em.flush` has detected a dirty RQF, reset our internal dirty state, w/o reseting our external-dirty state. */\n resetForRqfLoop() {\n this.flushedData = {};\n if (this.#new === Operation.Pending) this.#new = Operation.Flushed;\n if (this.#deleted === Operation.Pending) this.#deleted = Operation.Flushed;\n }\n\n resetAfterFlushed() {\n this.#originalData = undefined;\n this.#referenceHistory = undefined;\n this.flushedData = undefined;\n this.isTouched = false;\n if (this.#new === Operation.Pending || this.#new === Operation.Flushed) {\n this.#new = Operation.Complete;\n }\n if (this.#deleted === Operation.Pending || this.#deleted === Operation.Flushed) {\n this.#deleted = Operation.Complete;\n }\n this.unmarkMaybePending();\n }\n\n /** Our public-facing `isNewEntity`. */\n get isNewEntity(): boolean {\n return this.#new !== undefined && this.#new !== Operation.Complete;\n }\n\n /** Our public-facing `isDeletedEntity`. */\n get isDeletedEntity(): boolean {\n return this.#deleted !== undefined;\n }\n\n /** The original values of mutated fields; a shared empty object until the entity is actually mutated. */\n get originalData(): Record<any, any> {\n return this.#originalData ?? emptyObject;\n }\n\n /** Our public-facing `isDirtyEntity`. */\n get isDirtyEntity(): boolean {\n return hasAnyKey(this.#originalData);\n }\n\n /** This is our internal \"is pending flush\", which is aware of RQF micro-flush loops. */\n get pendingOperation(): \"insert\" | \"update\" | \"delete\" | \"none\" | \"created-then-deleted\" {\n if (this.#deleted !== undefined) {\n // Mark entities that were created and them immediately `em.delete`-d; granted this is\n // probably rare, but we shouldn't run hooks or have the driver try and delete these.\n // Note that we return them, instead of skipping entirely, so that fixupCreatedThenDeleted\n // can later be called.\n return this.#deleted === Operation.Pending && this.#new === Operation.Pending\n ? \"created-then-deleted\"\n : this.#deleted === Operation.Pending\n ? \"delete\"\n : \"none\";\n } else if (this.#new === Operation.Pending) {\n // Per ^, if `#new === flushed`, we fall through to check if micro-flush UPDATEs are required\n return \"insert\";\n } else if (this.flushedData) {\n return hasAnyKey(this.flushedData) ? \"update\" : \"none\";\n } else {\n return this.isTouched || hasAnyKey(this.#originalData) ? \"update\" : \"none\";\n }\n }\n\n get changedFields(): string[] {\n return Object.keys(this.changedData);\n }\n\n /** The changed-fields bag (flushedData during RQF loops, else originalData) without allocating a keys array. */\n get changedData(): Record<any, any> {\n return this.flushedData ?? this.#originalData ?? emptyObject;\n }\n\n /** Called when an `em.load` tries to find the entity and it's just gone from the db. */\n markDeletedBecauseNotFound(): void {\n this.#deleted = Operation.Complete;\n }\n\n /** Called by `em.delete`, returns true if this is new information. */\n markDeleted(): boolean {\n if (this.#deleted === undefined) {\n // Let any OneToManyCollection/ReactiveField/ReactiveReference.get caches know that they should recalc\n // (i.e. their filterDeleted logic) by asserting the `deleteBook.author` field is changing.\n for (const field of Object.values(this.metadata.allFields)) {\n // We only do reference/collection fields and skip the primitives, b/c we assume something like\n // `{ books: \"title\" }` doesn't need `resetIsLoaded(..., \"title\")` b/c the same hint will also\n // be watching \"did books change\", and so get reset that way.\n if (field.kind === \"m2o\" || field.kind === \"poly\" || field.kind === \"m2m\" || field.kind === \"m2mEnum\") {\n getEmInternalApi(this.em).isLoadedCache.resetIsLoaded(this.entity, field.fieldName);\n } else if (field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"primaryKey\") {\n // Any reactable watching these fields will also be watching reference/collection\n } else if (field.kind === \"o2m\" || field.kind === \"o2o\") {\n // Any reactable watching these actually gets triggered by the primary m2o/poly side changing\n } else if (field.kind === \"lo2m\") {\n // Doesn't support reactivity\n } else {\n throw new Error(`Unhandled field kind ${field}`);\n }\n }\n this.#deleted = Operation.Pending;\n this.markMaybePending();\n return true;\n }\n return false;\n }\n\n fixupCreatedThenDeleted(): void {\n // Ideally we could do this via `resetAfterFlushed`?\n this.#deleted = Operation.Complete;\n this.unmarkMaybePending();\n }\n\n /** Marks this entity as needing a flush even if no fields changed. */\n markTouched(): void {\n this.isTouched = true;\n this.markMaybePending();\n }\n\n /** Marks a field as dirty with its pre-mutation value. */\n markFieldDirty(fieldName: string, value: any): void {\n (this.#originalData ??= {})[fieldName] = value;\n this.markMaybePending();\n }\n\n /** Marks a reverted field as clean again. */\n markFieldClean(fieldName: string): void {\n if (this.#originalData) delete this.#originalData[fieldName];\n this.markMaybePending();\n }\n\n /** Marks this entity as a possible flush candidate. */\n markMaybePending(): void {\n getEmInternalApi(this.em).markMaybePending(this.entity);\n }\n\n /** Removes this entity from the possible flush candidate set. */\n private unmarkMaybePending(): void {\n getEmInternalApi(this.em).unmarkMaybePending(this.entity);\n }\n\n /** Remembers a reference value that may need to be reverse-walked later. */\n rememberReferenceValue(fieldName: string, value: any): void {\n if (value === undefined) return;\n const values = ((this.#referenceHistory ??= {})[fieldName] ??= []);\n if (!values.includes(value)) values.push(value);\n }\n\n /** Returns reference values that may need to be reverse-walked later. */\n getReferenceHistory(fieldName: string): readonly any[] {\n return this.#referenceHistory?.[fieldName] ?? emptyArray;\n }\n\n get isDeletedAndFlushed(): boolean {\n return this.#deleted === Operation.Complete;\n }\n\n /**\n * Returns true if the entity is new or was new in this EM.\n *\n * This is primarily used for lazy-initializing relations, i.e. if:\n *\n * - A new `Author` is created\n * - We `em.flush` the author to the database\n * - Then `a1.books` is accessed for the first time\n *\n * We can have a high-confidence that `a1` has no books, because we just\n * created it, so we can set the OneToManyCollection to loaded.\n */\n get isOrWasNew(): boolean {\n // #new will be left as `complete`\n return this.#new !== undefined;\n }\n}\n\n/** An enum to track our insert/delete state progression. */\nenum Operation {\n Pending = 1,\n Flushed = 2,\n Complete = 3,\n}\n\n/** A shared frozen bag for un-mutated entities; strict mode makes accidental writes throw. */\nconst emptyObject: Record<any, any> = Object.freeze({});\n\n/** A shared frozen array for fields with no remembered reference values. */\nconst emptyArray: readonly any[] = Object.freeze([]);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InstanceData.js","names":[],"sources":["../src/InstanceData.ts"],"sourcesContent":["import { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { type RowData, emptyRowData } from \"src/RowData.ts\";\nimport { hasAnyKey } from \"src/utils.ts\";\n\n/** The `#orm` metadata field we track on each instance. */\nexport class InstanceData {\n /** The entity this instance data belongs to. */\n readonly entity: Entity;\n /** All entities must be associated to an `EntityManager` to handle lazy loading/etc. */\n readonly em: EntityManager;\n /** A pointer to our entity type's metadata. */\n readonly metadata: EntityMetadata;\n /** A bag for our lazy-initialized relations, allocated on first relation access. */\n relations: Record<any, any> | undefined = undefined;\n /** The as-loaded database row, i.e. our `rowIndex`-th row within our query's `RowData` result. */\n rowData: RowData = emptyRowData;\n /** Our row's index within `rowData`; see {@link RowData}. */\n rowIndex: number = 0;\n /** The domain-value of fields, lazily converted (if needed) on read from the database columns. */\n data: Record<string, any>;\n /** A bag to keep the original values, allocated on first mutation (most entities are never mutated). */\n #originalData: Record<any, any> | undefined = undefined;\n /** Reference values that were set during this unit of work, allocated on first m2o/poly change. */\n #referenceHistory: Record<string, any[]> | undefined = undefined;\n /** A bag to keep the flushed-to-database values, only for AsyncReactiveField reactivity. */\n flushedData: Record<any, any> | undefined;\n /** Packs the new/deleted `Operation`s + isTouched into one slot; see the accessors below. */\n #flags: number = 0;\n /** The `a#1`, `a#2` index if we're an em.create-d entity. We never unset this, to keep ids stable across flushes. */\n createId: string | undefined;\n /** The zero-based order this entity was registered with its EntityManager. */\n entityIndex: number = -1;\n\n /** Whether our entity is new or not. */\n get #new(): Operation | undefined {\n const value = this.#flags & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #new(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b11) | (op ?? 0);\n }\n\n /**\n * Whether our entity has been deleted or not.\n *\n * - `pending` means we've been marked for deletion via `em.delete` but not issued a `DELETE`\n * - `flushed` means we've flushed a `DELETE` but `em.flush` hasn't fully completed yet, likely due to AsyncReactiveField calcs\n * - `deleted` means we've been flushed and `em.flush` has completed\n */\n get #deleted(): Operation | undefined {\n const value = (this.#flags >> 2) & 0b11;\n return value === 0 ? undefined : value;\n }\n\n set #deleted(op: Operation | undefined) {\n this.#flags = (this.#flags & ~0b1100) | ((op ?? 0) << 2);\n }\n\n /** Whether our entity should flush regardless of any other changes. */\n get isTouched(): boolean {\n return (this.#flags & 0b10000) !== 0;\n }\n\n set isTouched(value: boolean) {\n this.#flags = value ? this.#flags | 0b10000 : this.#flags & ~0b10000;\n }\n\n /** Creates the `#orm` field. */\n constructor(em: EntityManager, entity: Entity, metadata: EntityMetadata, isNew: boolean) {\n this.entity = entity;\n this.em = em;\n this.metadata = metadata;\n if (isNew) {\n this.#new = Operation.Pending;\n this.data = {};\n this.flushedData = undefined;\n if (em) this.markMaybePending();\n } else {\n this.#new = undefined;\n this.data = {};\n this.flushedData = undefined;\n }\n }\n\n /**\n * Returns the as-loaded value of `columnName`, i.e. `getColumn(\"rank\")`.\n *\n * This saves callers from knowing that `rowData`/`rowIndex` are a pair, but note it is\n * a snapshot of the query that loaded us, and specifically:\n *\n * - The value is driver-level, i.e. numeric enum ids, untagged foreign keys, and temporals\n * as strings; use `getField` if you want the domain value, and\n * - The value is not updated by `em.flush`, so after a flush it is the pre-flush value, and\n * - New entities have no row, so every column is `undefined`.\n */\n getColumn(columnName: string): any {\n return this.rowData.get(this.rowIndex, columnName);\n }\n\n /** If `em.flush` has detected a dirty RQF, reset our internal dirty state, w/o reseting our external-dirty state. */\n resetForRqfLoop() {\n this.flushedData = {};\n if (this.#new === Operation.Pending) this.#new = Operation.Flushed;\n if (this.#deleted === Operation.Pending) this.#deleted = Operation.Flushed;\n }\n\n resetAfterFlushed() {\n this.#originalData = undefined;\n this.#referenceHistory = undefined;\n this.flushedData = undefined;\n this.isTouched = false;\n if (this.#new === Operation.Pending || this.#new === Operation.Flushed) {\n this.#new = Operation.Complete;\n }\n if (this.#deleted === Operation.Pending || this.#deleted === Operation.Flushed) {\n this.#deleted = Operation.Complete;\n }\n this.unmarkMaybePending();\n }\n\n /** Our public-facing `isNewEntity`. */\n get isNewEntity(): boolean {\n return this.#new !== undefined && this.#new !== Operation.Complete;\n }\n\n /** Our public-facing `isDeletedEntity`. */\n get isDeletedEntity(): boolean {\n return this.#deleted !== undefined;\n }\n\n /** The original values of mutated fields; a shared empty object until the entity is actually mutated. */\n get originalData(): Record<any, any> {\n return this.#originalData ?? emptyObject;\n }\n\n /** Our public-facing `isDirtyEntity`. */\n get isDirtyEntity(): boolean {\n return hasAnyKey(this.#originalData);\n }\n\n /** This is our internal \"is pending flush\", which is aware of RQF micro-flush loops. */\n get pendingOperation(): \"insert\" | \"update\" | \"delete\" | \"none\" | \"created-then-deleted\" {\n if (this.#deleted !== undefined) {\n // Mark entities that were created and them immediately `em.delete`-d; granted this is\n // probably rare, but we shouldn't run hooks or have the driver try and delete these.\n // Note that we return them, instead of skipping entirely, so that fixupCreatedThenDeleted\n // can later be called.\n return this.#deleted === Operation.Pending && this.#new === Operation.Pending\n ? \"created-then-deleted\"\n : this.#deleted === Operation.Pending\n ? \"delete\"\n : \"none\";\n } else if (this.#new === Operation.Pending) {\n // Per ^, if `#new === flushed`, we fall through to check if micro-flush UPDATEs are required\n return \"insert\";\n } else if (this.flushedData) {\n return hasAnyKey(this.flushedData) ? \"update\" : \"none\";\n } else {\n return this.isTouched || hasAnyKey(this.#originalData) ? \"update\" : \"none\";\n }\n }\n\n get changedFields(): string[] {\n return Object.keys(this.changedData);\n }\n\n /** The changed-fields bag (flushedData during RQF loops, else originalData) without allocating a keys array. */\n get changedData(): Record<any, any> {\n return this.flushedData ?? this.#originalData ?? emptyObject;\n }\n\n /** Called when an `em.load` tries to find the entity and it's just gone from the db. */\n markDeletedBecauseNotFound(): void {\n this.#deleted = Operation.Complete;\n }\n\n /** Called by `em.delete`, returns true if this is new information. */\n markDeleted(): boolean {\n if (this.#deleted === undefined) {\n // Let any OneToManyCollection/ReactiveField/ReactiveReference.get caches know that they should recalc\n // (i.e. their filterDeleted logic) by asserting the `deleteBook.author` field is changing.\n for (const field of Object.values(this.metadata.allFields)) {\n // We only do reference/collection fields and skip the primitives, b/c we assume something like\n // `{ books: \"title\" }` doesn't need `resetIsLoaded(..., \"title\")` b/c the same hint will also\n // be watching \"did books change\", and so get reset that way.\n if (field.kind === \"m2o\" || field.kind === \"poly\" || field.kind === \"m2m\" || field.kind === \"m2mEnum\") {\n getEmInternalApi(this.em).isLoadedCache.resetIsLoaded(this.entity, field.fieldName);\n } else if (field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"primaryKey\") {\n // Any reactable watching these fields will also be watching reference/collection\n } else if (field.kind === \"o2m\" || field.kind === \"o2o\") {\n // Any reactable watching these actually gets triggered by the primary m2o/poly side changing\n } else if (field.kind === \"lo2m\") {\n // Doesn't support reactivity\n } else {\n throw new Error(`Unhandled field kind ${field}`);\n }\n }\n this.#deleted = Operation.Pending;\n this.markMaybePending();\n return true;\n }\n return false;\n }\n\n fixupCreatedThenDeleted(): void {\n // Ideally we could do this via `resetAfterFlushed`?\n this.#deleted = Operation.Complete;\n this.unmarkMaybePending();\n }\n\n /** Marks this entity as needing a flush even if no fields changed. */\n markTouched(): void {\n this.isTouched = true;\n this.markMaybePending();\n }\n\n /** Marks a field as dirty with its pre-mutation value. */\n markFieldDirty(fieldName: string, value: any): void {\n (this.#originalData ??= {})[fieldName] = value;\n this.markMaybePending();\n }\n\n /** Marks a reverted field as clean again. */\n markFieldClean(fieldName: string): void {\n if (this.#originalData) delete this.#originalData[fieldName];\n this.markMaybePending();\n }\n\n /** Marks this entity as a possible flush candidate. */\n markMaybePending(): void {\n getEmInternalApi(this.em).markMaybePending(this.entity);\n }\n\n /** Removes this entity from the possible flush candidate set. */\n private unmarkMaybePending(): void {\n getEmInternalApi(this.em).unmarkMaybePending(this.entity);\n }\n\n /** Remembers a reference value that may need to be reverse-walked later. */\n rememberReferenceValue(fieldName: string, value: any): void {\n if (value === undefined) return;\n const values = ((this.#referenceHistory ??= {})[fieldName] ??= []);\n if (!values.includes(value)) values.push(value);\n }\n\n /** Returns reference values that may need to be reverse-walked later. */\n getReferenceHistory(fieldName: string): readonly any[] {\n return this.#referenceHistory?.[fieldName] ?? emptyArray;\n }\n\n get isDeletedAndFlushed(): boolean {\n return this.#deleted === Operation.Complete;\n }\n\n /**\n * Returns true if the entity is new or was new in this EM.\n *\n * This is primarily used for lazy-initializing relations, i.e. if:\n *\n * - A new `Author` is created\n * - We `em.flush` the author to the database\n * - Then `a1.books` is accessed for the first time\n *\n * We can have a high-confidence that `a1` has no books, because we just\n * created it, so we can set the OneToManyCollection to loaded.\n */\n get isOrWasNew(): boolean {\n // #new will be left as `complete`\n return this.#new !== undefined;\n }\n}\n\n/** An enum to track our insert/delete state progression. */\nenum Operation {\n Pending = 1,\n Flushed = 2,\n Complete = 3,\n}\n\n/** A shared frozen bag for un-mutated entities; strict mode makes accidental writes throw. */\nconst emptyObject: Record<any, any> = Object.freeze({});\n\n/** A shared frozen array for fields with no remembered reference values. */\nconst emptyArray: readonly any[] = Object.freeze([]);\n"],"mappings":";;;;;;;AAOA,IAAa,eAAb,MAA0B;;CAExB;;CAEA;;CAEA;;CAEA,YAA0C,KAAA;;CAE1C,UAAmB;;CAEnB,WAAmB;;CAEnB;;CAEA,gBAA8C,KAAA;;CAE9C,oBAAuD,KAAA;;CAEvD;;CAEA,SAAiB;;CAEjB;;CAEA,cAAsB;;CAGtB,IAAI,OAA8B;EAChC,MAAM,QAAQ,KAAK,SAAS;EAC5B,OAAO,UAAU,IAAI,KAAA,IAAY;CACnC;CAEA,IAAI,KAAK,IAA2B;EAClC,KAAK,SAAU,KAAK,SAAS,MAAU,MAAM;CAC/C;;;;;;;;CASA,IAAI,WAAkC;EACpC,MAAM,QAAS,KAAK,UAAU,IAAK;EACnC,OAAO,UAAU,IAAI,KAAA,IAAY;CACnC;CAEA,IAAI,SAAS,IAA2B;EACtC,KAAK,SAAU,KAAK,SAAS,OAAa,MAAM,MAAM;CACxD;;CAGA,IAAI,YAAqB;EACvB,QAAQ,KAAK,SAAS,QAAa;CACrC;CAEA,IAAI,UAAU,OAAgB;EAC5B,KAAK,SAAS,QAAQ,KAAK,SAAS,KAAU,KAAK,SAAS;CAC9D;;CAGA,YAAY,IAAmB,QAAgB,UAA0B,OAAgB;EACvF,KAAK,SAAS;EACd,KAAK,KAAK;EACV,KAAK,WAAW;EAChB,IAAI,OAAO;GACT,KAAK,OAAA;GACL,KAAK,OAAO,CAAC;GACb,KAAK,cAAc,KAAA;GACnB,IAAI,IAAI,KAAK,iBAAiB;EAChC,OAAO;GACL,KAAK,OAAO,KAAA;GACZ,KAAK,OAAO,CAAC;GACb,KAAK,cAAc,KAAA;EACrB;CACF;;;;;;;;;;;;CAaA,UAAU,YAAyB;EACjC,OAAO,KAAK,QAAQ,IAAI,KAAK,UAAU,UAAU;CACnD;;CAGA,kBAAkB;EAChB,KAAK,cAAc,CAAC;EACpB,IAAI,KAAK,SAAA,GAA4B,KAAK,OAAA;EAC1C,IAAI,KAAK,aAAA,GAAgC,KAAK,WAAA;CAChD;CAEA,oBAAoB;EAClB,KAAK,gBAAgB,KAAA;EACrB,KAAK,oBAAoB,KAAA;EACzB,KAAK,cAAc,KAAA;EACnB,KAAK,YAAY;EACjB,IAAI,KAAK,SAAA,KAA8B,KAAK,SAAA,GAC1C,KAAK,OAAA;EAEP,IAAI,KAAK,aAAA,KAAkC,KAAK,aAAA,GAC9C,KAAK,WAAA;EAEP,KAAK,mBAAmB;CAC1B;;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAK,SAAS,KAAA,KAAa,KAAK,SAAA;CACzC;;CAGA,IAAI,kBAA2B;EAC7B,OAAO,KAAK,aAAa,KAAA;CAC3B;;CAGA,IAAI,eAAiC;EACnC,OAAO,KAAK,iBAAiB;CAC/B;;CAGA,IAAI,gBAAyB;EAC3B,OAAO,UAAU,KAAK,aAAa;CACrC;;CAGA,IAAI,mBAAqF;EACvF,IAAI,KAAK,aAAa,KAAA,GAKpB,OAAO,KAAK,aAAA,KAAkC,KAAK,SAAA,IAC/C,yBACA,KAAK,aAAA,IACH,WACA;OACD,IAAI,KAAK,SAAA,GAEd,OAAO;OACF,IAAI,KAAK,aACd,OAAO,UAAU,KAAK,WAAW,IAAI,WAAW;OAEhD,OAAO,KAAK,aAAa,UAAU,KAAK,aAAa,IAAI,WAAW;CAExE;CAEA,IAAI,gBAA0B;EAC5B,OAAO,OAAO,KAAK,KAAK,WAAW;CACrC;;CAGA,IAAI,cAAgC;EAClC,OAAO,KAAK,eAAe,KAAK,iBAAiB;CACnD;;CAGA,6BAAmC;EACjC,KAAK,WAAA;CACP;;CAGA,cAAuB;EACrB,IAAI,KAAK,aAAa,KAAA,GAAW;GAG/B,KAAK,MAAM,SAAS,OAAO,OAAO,KAAK,SAAS,SAAS,GAIvD,IAAI,MAAM,SAAS,SAAS,MAAM,SAAS,UAAU,MAAM,SAAS,SAAS,MAAM,SAAS,WAC1F,iBAAiB,KAAK,EAAE,CAAC,CAAC,cAAc,cAAc,KAAK,QAAQ,MAAM,SAAS;QAC7E,IAAI,MAAM,SAAS,eAAe,MAAM,SAAS,UAAU,MAAM,SAAS,cAAc,CAE/F,OAAO,IAAI,MAAM,SAAS,SAAS,MAAM,SAAS,OAAO,CAEzD,OAAO,IAAI,MAAM,SAAS,QAAQ,CAElC,OACE,MAAM,IAAI,MAAM,wBAAwB,OAAO;GAGnD,KAAK,WAAA;GACL,KAAK,iBAAiB;GACtB,OAAO;EACT;EACA,OAAO;CACT;CAEA,0BAAgC;EAE9B,KAAK,WAAA;EACL,KAAK,mBAAmB;CAC1B;;CAGA,cAAoB;EAClB,KAAK,YAAY;EACjB,KAAK,iBAAiB;CACxB;;CAGA,eAAe,WAAmB,OAAkB;EAClD,CAAC,KAAK,kBAAkB,CAAC,EAAA,CAAG,aAAa;EACzC,KAAK,iBAAiB;CACxB;;CAGA,eAAe,WAAyB;EACtC,IAAI,KAAK,eAAe,OAAO,KAAK,cAAc;EAClD,KAAK,iBAAiB;CACxB;;CAGA,mBAAyB;EACvB,iBAAiB,KAAK,EAAE,CAAC,CAAC,iBAAiB,KAAK,MAAM;CACxD;;CAGA,qBAAmC;EACjC,iBAAiB,KAAK,EAAE,CAAC,CAAC,mBAAmB,KAAK,MAAM;CAC1D;;CAGA,uBAAuB,WAAmB,OAAkB;EAC1D,IAAI,UAAU,KAAA,GAAW;EACzB,MAAM,SAAU,CAAC,KAAK,sBAAsB,CAAC,EAAA,CAAG,eAAe,CAAC;EAChE,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO,KAAK,KAAK;CAChD;;CAGA,oBAAoB,WAAmC;EACrD,OAAO,KAAK,oBAAoB,cAAc;CAChD;CAEA,IAAI,sBAA+B;EACjC,OAAO,KAAK,aAAA;CACd;;;;;;;;;;;;;CAcA,IAAI,aAAsB;EAExB,OAAO,KAAK,SAAS,KAAA;CACvB;AACF;;AAUA,MAAM,cAAgC,OAAO,OAAO,CAAC,CAAC;;AAGtD,MAAM,aAA6B,OAAO,OAAO,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginManager.cjs","names":["isDefined"
|
|
1
|
+
{"version":3,"file":"PluginManager.cjs","names":["isDefined"],"sources":["../src/PluginManager.ts"],"sourcesContent":["import { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager, type FindOperation, isDefined } from \"src/EntityManager.ts\";\nimport { type EntityMetadata } from \"src/EntityMetadata.ts\";\nimport type { JoinRowTodo, Todo } from \"src/flush/Todo.ts\";\nimport type { ParsedFindQuery } from \"src/queries/find/QueryParser.ts\";\nimport { type RowData } from \"src/RowData.ts\";\n\ninterface PluginMethods {\n /**\n * Called before a field value is retrieved from an entity via getField.\n *\n * @param entity The entity instance being accessed\n * @param field The field name being retrieved\n */\n beforeGetField?(entity: Entity, field: string): void;\n /**\n * Called before a field value is set on an entity via setField.\n *\n * @param entity The entity instance being modified\n * @param field The field name being set\n * @param newValue The new value being assigned to the field\n */\n beforeSetField?(entity: Entity, field: string, newValue: any): void;\n /**\n * Called before a find operation is executed.\n *\n * @param meta Metadata for the entity type being queried\n * @param operation The type of find operation being performed\n * @param query The parsed query conditions\n * @param settings Query settings including limit and offset\n */\n beforeFind?(\n meta: EntityMetadata,\n operation: FindOperation,\n query: ParsedFindQuery,\n settings: {\n limit?: number;\n offset?: number;\n allowMultipleLeftJoins?: boolean;\n optimizeJoinsToExists?: boolean;\n pruneJoins?: boolean;\n keepAliases?: string[];\n },\n ): void;\n /**\n * Called after a find operation has been executed to observe the raw database rows.\n *\n * The result is an observation-only {@link RowData} view — i.e. `rows.rowCount` for metrics,\n * `rows.get(i, columnName)` for specific cells, or `rows.toRows()` to materialize POJO rows.\n * For lazy results, cells decode on access, so a hook that only counts rows costs nothing;\n * materializing via `toRows()` decodes every cell of every row. Mutating materialized rows is\n * unsupported and is not guaranteed to affect entity hydration.\n *\n * @param meta Metadata for the entity type that was queried\n * @param operation The type of find operation that was performed\n * @param rows A read-only view of the raw database rows returned from the query\n */\n afterFind?(meta: EntityMetadata, operation: FindOperation, rows: RowData): void;\n\n beforeValidate?(entities: readonly Entity[]): Promise<void> | void;\n\n afterValidate?(entities: readonly Entity[]): Promise<void> | void;\n\n afterWrite(entityTodos: Record<string, Todo>, joinRowTodos: Record<string, JoinRowTodo>): void;\n}\n\nconst syncPluginMethods = [\n \"beforeGetField\",\n \"beforeSetField\",\n \"beforeFind\",\n \"afterFind\",\n \"afterWrite\",\n] as const satisfies readonly (keyof PluginMethods)[];\nconst asyncPluginMethods = [\"beforeValidate\", \"afterValidate\"] as const satisfies readonly (keyof PluginMethods)[];\nconst emsSymbol = Symbol(\"ems\");\n/**\n * Base class for plugins that hook into entity lifecycle events.\n *\n * Plugins can implement any of the PluginMethods to intercept entity operations\n * and are automatically registered with the EntityManager when added via PluginManager.\n */\nexport abstract class Plugin {\n // We need to store the EntityManager references in a WeakRef array so that they can be garbage collected. If we\n // didn't do this, then multiple EMs with the same plugin instance could keep each other from being garbage collected.\n [emsSymbol]: WeakRef<EntityManager>[] = [];\n\n maybeCloneForNewEm(): this | undefined {\n return undefined;\n }\n\n get ems(): readonly EntityManager[] {\n return this[emsSymbol]\n .values()\n .map((wr) => wr.deref())\n .filter(isDefined)\n .toArray();\n }\n}\n\nexport interface Plugin extends PluginMethods {}\n\n/**\n * Manages plugin lifecycle and dispatches plugin callbacks to registered plugins.\n *\n * The PluginManager optimizes callback dispatch by only creating dispatcher methods\n * for callbacks that have at least one registered plugin, making unused plugin hooks\n * zero-cost at runtime.\n */\nexport class PluginManager implements Required<PluginMethods> {\n #plugins = new Set<Plugin>();\n readonly #pluginsByCallback: Partial<Record<keyof Plugin, Plugin[]>> = {};\n constructor(public readonly em: EntityManager) {}\n\n /** Returns whether any registered plugin implements `method`, i.e. so callers can skip building its args. */\n hasHook(method: keyof PluginMethods): boolean {\n return this.#pluginsByCallback[method] !== undefined;\n }\n\n /**\n * Registers a plugin with this EntityManager.\n *\n * Dynamically creates dispatcher methods for any plugin callbacks implemented by this plugin,\n * enabling lazy initialization of only the hooks that are actually used.\n *\n * @throws Error if the plugin is already registered with another EntityManager\n */\n addPlugin(plugin: Plugin) {\n if (this.#plugins.has(plugin)) return; // if we're already added, then we can just early exit\n this.#plugins.add(plugin);\n plugin[emsSymbol].push(new WeakRef(this.em));\n for (const method of syncPluginMethods) {\n if (method in plugin) {\n (this.#pluginsByCallback[method] ??= []).push(plugin);\n // As a performance optimization, we only create the actual implementation for each method on the plugin manager\n // once we have at least one plugin using that method. This is to make it so any unused plugin methods are\n // effectively no-ops when called from within the em.\n if (!Object.hasOwn(this, method)) {\n this[method] = function (this: PluginManager, ...args: any[]) {\n for (const plugin of this.#pluginsByCallback[method]!) {\n (plugin[method] as (...args: any[]) => unknown)(...args);\n }\n };\n }\n }\n }\n for (const method of asyncPluginMethods) {\n if (method in plugin) {\n (this.#pluginsByCallback[method] ??= []).push(plugin);\n // Validate-phase plugin hooks are intentionally awaited without making hot-path hooks async.\n if (!Object.hasOwn(this, method)) {\n this[method] = async function (this: PluginManager, ...args: any[]) {\n for (const plugin of this.#pluginsByCallback[method]!) {\n await (plugin[method] as (...args: any[]) => Promise<void> | void)(...args);\n }\n };\n }\n }\n }\n }\n\n get plugins(): readonly Plugin[] {\n return [...this.#plugins];\n }\n\n cloneTo(other: PluginManager) {\n this.#plugins\n .values()\n .map((plugin) => plugin.maybeCloneForNewEm())\n .filter(isDefined)\n .forEach((plugin) => other.addPlugin(plugin));\n }\n\n // Defined as no-op functions initially instead of using optional chaining for performance reasons, see:\n // https://adventures.nodeland.dev/archive/noop-functions-vs-optional-chaining-a-performance/\n\n beforeGetField(entity: Entity, field: string): void {}\n beforeSetField(entity: Entity, field: string, newValue: any): void {}\n beforeFind(\n meta: EntityMetadata,\n operation: FindOperation,\n query: ParsedFindQuery,\n settings: {\n limit?: number;\n offset?: number;\n allowMultipleLeftJoins?: boolean;\n optimizeJoinsToExists?: boolean;\n pruneJoins?: boolean;\n keepAliases?: string[];\n },\n ): void {}\n afterFind(meta: EntityMetadata, operation: FindOperation, rows: RowData) {}\n beforeValidate(entities: readonly Entity[]): Promise<void> | void {}\n afterValidate(entities: readonly Entity[]): Promise<void> | void {}\n afterWrite(entityTodos: Record<string, Todo>, joinRowTodos: Record<string, JoinRowTodo>): void {}\n}\n"],"mappings":";;;;;AAkEA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;AACF;AACA,MAAM,qBAAqB,CAAC,kBAAkB,eAAe;AAC7D,MAAM,YAAY,OAAO,KAAK;;;;;;;AAO9B,IAAsB,SAAtB,MAA6B;CAG3B,CAAC,aAAuC,CAAC;CAEzC,qBAAuC,CAEvC;CAEA,IAAI,MAAgC;EAClC,OAAO,KAAK,UAAU,CACnB,OAAO,CAAC,CACR,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC,CACvB,OAAOA,sBAAAA,SAAS,CAAC,CACjB,QAAQ;CACb;AACF;;;;;;;;AAWA,IAAa,gBAAb,MAA8D;CAGhC;CAF5B,2BAAW,IAAI,IAAY;CAC3B,qBAAuE,CAAC;CACxE,YAAY,IAAmC;EAAnB,KAAA,KAAA;CAAoB;;CAGhD,QAAQ,QAAsC;EAC5C,OAAO,KAAK,mBAAmB,YAAY,KAAA;CAC7C;;;;;;;;;CAUA,UAAU,QAAgB;EACxB,IAAI,KAAK,SAAS,IAAI,MAAM,GAAG;EAC/B,KAAK,SAAS,IAAI,MAAM;EACxB,OAAO,UAAU,CAAC,KAAK,IAAI,QAAQ,KAAK,EAAE,CAAC;EAC3C,KAAK,MAAM,UAAU,mBACnB,IAAI,UAAU,QAAQ;GACpB,CAAC,KAAK,mBAAmB,YAAY,CAAC,EAAA,CAAG,KAAK,MAAM;GAIpD,IAAI,CAAC,OAAO,OAAO,MAAM,MAAM,GAC7B,KAAK,UAAU,SAA+B,GAAG,MAAa;IAC5D,KAAK,MAAM,UAAU,KAAK,mBAAmB,SAC3C,OAAQ,OAAO,CAAiC,GAAG,IAAI;GAE3D;EAEJ;EAEF,KAAK,MAAM,UAAU,oBACnB,IAAI,UAAU,QAAQ;GACpB,CAAC,KAAK,mBAAmB,YAAY,CAAC,EAAA,CAAG,KAAK,MAAM;GAEpD,IAAI,CAAC,OAAO,OAAO,MAAM,MAAM,GAC7B,KAAK,UAAU,eAAqC,GAAG,MAAa;IAClE,KAAK,MAAM,UAAU,KAAK,mBAAmB,SAC3C,MAAO,OAAO,OAAO,CAA8C,GAAG,IAAI;GAE9E;EAEJ;CAEJ;CAEA,IAAI,UAA6B;EAC/B,OAAO,CAAC,GAAG,KAAK,QAAQ;CAC1B;CAEA,QAAQ,OAAsB;EAC5B,KAAK,SACF,OAAO,CAAC,CACR,KAAK,WAAW,OAAO,mBAAmB,CAAC,CAAC,CAC5C,OAAOA,sBAAAA,SAAS,CAAC,CACjB,SAAS,WAAW,MAAM,UAAU,MAAM,CAAC;CAChD;CAKA,eAAe,QAAgB,OAAqB,CAAC;CACrD,eAAe,QAAgB,OAAe,UAAqB,CAAC;CACpE,WACE,MACA,WACA,OACA,UAQM,CAAC;CACT,UAAU,MAAsB,WAA0B,MAAe,CAAC;CAC1E,eAAe,UAAmD,CAAC;CACnE,cAAc,UAAmD,CAAC;CAClE,WAAW,aAAmC,cAAiD,CAAC;AAClG"}
|