joist-core 2.3.0-next.2 → 2.3.0-next.21
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/AliasAssigner.d.ts +0 -1
- package/build/Aliases.d.ts +0 -1
- package/build/BaseEntity.d.ts +0 -1
- package/build/ConditionBuilder.d.ts +0 -1
- package/build/ConditionBuilder.js +1 -1
- package/build/ConditionBuilder.js.map +1 -1
- package/build/Entity.d.ts +0 -1
- package/build/EntityFields.d.ts +0 -1
- package/build/EntityFilter.d.ts +0 -1
- package/build/EntityGraphQLFilter.d.ts +0 -1
- package/build/EntityManager.d.ts +3 -3
- package/build/EntityManager.js +298 -115
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +22 -3
- package/build/EntityMetadata.js +5 -3
- package/build/EntityMetadata.js.map +1 -1
- package/build/EnumMetadata.d.ts +0 -1
- package/build/FlushLock.d.ts +0 -1
- package/build/HintTree.d.ts +0 -1
- package/build/IndexManager.d.ts +4 -3
- package/build/IndexManager.js +4 -2
- package/build/IndexManager.js.map +1 -1
- package/build/InstanceData.d.ts +8 -8
- package/build/InstanceData.js +57 -25
- package/build/InstanceData.js.map +1 -1
- package/build/IsLoadedCache.d.ts +0 -1
- package/build/JoinRows.d.ts +0 -1
- package/build/PendingChanges.d.ts +0 -1
- package/build/PluginManager.d.ts +0 -1
- package/build/QueryParser.collectionJoins.d.ts +0 -1
- package/build/QueryParser.d.ts +7 -1
- package/build/QueryParser.js +25 -1
- package/build/QueryParser.js.map +1 -1
- package/build/QueryParser.pruning.d.ts +0 -1
- package/build/QueryVisitor.d.ts +0 -1
- package/build/ReactionsManager.d.ts +1 -2
- package/build/ReactionsManager.js +24 -20
- package/build/ReactionsManager.js.map +1 -1
- package/build/Todo.d.ts +1 -2
- package/build/Todo.js +16 -5
- package/build/Todo.js.map +1 -1
- package/build/batchloaders/BatchLoader.d.ts +0 -1
- package/build/batchloaders/enumCollectionBatchLoader.d.ts +0 -1
- package/build/batchloaders/loadBatchLoader.d.ts +0 -1
- package/build/batchloaders/loadBatchLoader.js +1 -1
- package/build/batchloaders/loadBatchLoader.js.map +1 -1
- package/build/batchloaders/manyToManyBatchLoader.d.ts +0 -1
- package/build/batchloaders/oneToManyBatchLoader.d.ts +0 -1
- package/build/batchloaders/oneToOneBatchLoader.d.ts +0 -1
- package/build/batchloaders/populateBatchLoader.d.ts +0 -1
- package/build/batchloaders/populateBatchLoader.js +4 -2
- package/build/batchloaders/populateBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.d.ts +0 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js +3 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.d.ts +0 -1
- package/build/batchloaders/recursiveM2mBatchLoader.js +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.d.ts +0 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js.map +1 -1
- package/build/changes.d.ts +0 -1
- package/build/changes.js +4 -1
- package/build/changes.js.map +1 -1
- package/build/config.d.ts +19 -1
- package/build/config.js +36 -23
- package/build/config.js.map +1 -1
- package/build/configure.d.ts +0 -1
- package/build/configure.js +66 -2
- package/build/configure.js.map +1 -1
- package/build/dataloaders/fastWhereFilterHash.d.ts +0 -1
- package/build/dataloaders/findByUniqueDataLoader.d.ts +0 -1
- package/build/dataloaders/findCountDataLoader.d.ts +0 -1
- package/build/dataloaders/findDataLoader.d.ts +0 -1
- package/build/dataloaders/findDataLoader.js +21 -21
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.ts +0 -1
- package/build/dataloaders/findOrCreateDataLoader.d.ts +0 -1
- package/build/dataloaders/findOrCreateDataLoader.js +2 -2
- package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.ts +0 -1
- package/build/dataloaders/lensDataLoader.d.ts +0 -1
- package/build/dataloaders/manyToManyFindDataLoader.d.ts +0 -1
- package/build/dataloaders/oneToManyFindDataLoader.d.ts +0 -1
- package/build/defaults.d.ts +0 -1
- package/build/driverApi.d.ts +0 -1
- package/build/drivers/Driver.d.ts +0 -1
- package/build/drivers/EntityWriter.d.ts +0 -1
- package/build/drivers/EntityWriter.js +32 -3
- package/build/drivers/EntityWriter.js.map +1 -1
- package/build/drivers/IdAssigner.d.ts +0 -1
- package/build/drivers/buildRawQuery.d.ts +0 -1
- package/build/drivers/buildUtils.d.ts +0 -1
- package/build/drivers/index.d.ts +0 -1
- package/build/fields.d.ts +0 -1
- package/build/fields.js +2 -2
- package/build/fields.js.map +1 -1
- package/build/getProperties.d.ts +3 -4
- package/build/getProperties.js +3 -3
- package/build/getProperties.js.map +1 -1
- package/build/index.d.ts +1 -2
- package/build/index.js +5 -1
- package/build/index.js.map +1 -1
- package/build/json.d.ts +0 -1
- package/build/k.d.ts +0 -1
- package/build/keys.d.ts +7 -7
- package/build/keys.js +102 -46
- package/build/keys.js.map +1 -1
- package/build/keywords.d.ts +0 -1
- package/build/loadHints.d.ts +0 -1
- package/build/loadLens.d.ts +0 -1
- package/build/logging/FactoryLogger.d.ts +0 -1
- package/build/logging/FieldLogger.d.ts +2 -1
- package/build/logging/FieldLogger.js +10 -1
- package/build/logging/FieldLogger.js.map +1 -1
- package/build/logging/ReactionLogger.d.ts +2 -1
- package/build/logging/ReactionLogger.js +15 -1
- package/build/logging/ReactionLogger.js.map +1 -1
- package/build/newEntity.d.ts +2 -3
- package/build/newEntity.js +10 -8
- package/build/newEntity.js.map +1 -1
- package/build/newTestInstance.d.ts +0 -1
- package/build/newTestInstance.js +4 -0
- package/build/newTestInstance.js.map +1 -1
- package/build/normalizeHints.d.ts +0 -1
- package/build/plugins/ImmutableEntitiesPlugin.d.ts +0 -1
- package/build/plugins/PreloadPlugin.d.ts +0 -1
- package/build/preloading/JsonAggregatePreloader.d.ts +0 -1
- package/build/preloading/JsonAggregatePreloader.js +4 -3
- package/build/preloading/JsonAggregatePreloader.js.map +1 -1
- package/build/preloading/canPreload.d.ts +0 -1
- package/build/preloading/partitionHint.d.ts +0 -1
- package/build/reactiveHints.d.ts +0 -1
- package/build/relations/AbstractPropertyImpl.d.ts +0 -1
- package/build/relations/AbstractRelationImpl.d.ts +0 -1
- package/build/relations/AsyncProperty.d.ts +0 -1
- package/build/relations/AsyncReactiveField.d.ts +13 -9
- package/build/relations/AsyncReactiveField.js +22 -19
- package/build/relations/AsyncReactiveField.js.map +1 -1
- package/build/relations/Collection.d.ts +0 -1
- package/build/relations/CustomCollection.d.ts +0 -1
- package/build/relations/CustomReference.d.ts +0 -1
- package/build/relations/EnumCollection.d.ts +0 -1
- package/build/relations/LargeCollection.d.ts +0 -1
- package/build/relations/LazyField.d.ts +46 -0
- package/build/relations/LazyField.js +133 -0
- package/build/relations/LazyField.js.map +1 -0
- package/build/relations/ManyToManyCollection.d.ts +0 -1
- package/build/relations/ManyToManyCollection.js +5 -3
- package/build/relations/ManyToManyCollection.js.map +1 -1
- package/build/relations/ManyToManyLargeCollection.d.ts +0 -1
- package/build/relations/ManyToOneReference.d.ts +0 -1
- package/build/relations/OneToManyCollection.d.ts +0 -1
- package/build/relations/OneToManyCollection.js +3 -1
- package/build/relations/OneToManyCollection.js.map +1 -1
- package/build/relations/OneToManyLargeCollection.d.ts +0 -1
- package/build/relations/OneToOneReference.d.ts +0 -1
- package/build/relations/OneToOneReference.js +1 -1
- package/build/relations/OneToOneReference.js.map +1 -1
- package/build/relations/PolymorphicReference.d.ts +0 -1
- package/build/relations/ReactiveField.d.ts +0 -1
- package/build/relations/ReactiveGetter.d.ts +0 -1
- package/build/relations/ReactiveManyToMany.d.ts +0 -1
- package/build/relations/ReactiveManyToMany.js +1 -1
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.ts +0 -1
- package/build/relations/ReactiveManyToManyOtherSide.js +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
- package/build/relations/ReactiveReference.d.ts +0 -1
- package/build/relations/ReactiveReference.js +1 -1
- package/build/relations/ReactiveReference.js.map +1 -1
- package/build/relations/ReadOnlyCollection.d.ts +0 -1
- package/build/relations/RecursiveCollection.d.ts +0 -1
- package/build/relations/RecursiveCycleError.d.ts +0 -1
- package/build/relations/Reference.d.ts +0 -1
- package/build/relations/Relation.d.ts +0 -1
- package/build/relations/hasAsyncMethod.d.ts +0 -1
- package/build/relations/hasManyDerived.d.ts +0 -1
- package/build/relations/hasManyThrough.d.ts +0 -1
- package/build/relations/hasOneDerived.d.ts +0 -1
- package/build/relations/hasOneThrough.d.ts +0 -1
- package/build/relations/hasProperty.d.ts +0 -1
- package/build/relations/index.d.ts +1 -1
- package/build/relations/index.js +7 -2
- package/build/relations/index.js.map +1 -1
- package/build/resurrection.d.ts +0 -1
- package/build/rules.d.ts +8 -1
- package/build/rules.js +17 -0
- package/build/rules.js.map +1 -1
- package/build/runtimeConfig.d.ts +4 -1
- package/build/runtimeConfig.js +5 -0
- package/build/runtimeConfig.js.map +1 -1
- package/build/scopes.d.ts +0 -1
- package/build/serde.d.ts +0 -1
- package/build/serde.js +9 -7
- package/build/serde.js.map +1 -1
- package/build/temporal.d.ts +0 -1
- package/build/temporalMappers.d.ts +0 -1
- package/build/trusted.d.ts +0 -1
- package/build/typeMap.d.ts +0 -1
- package/build/unnest.d.ts +0 -1
- package/build/upsert.d.ts +0 -1
- package/build/utils.d.ts +2 -1
- package/build/utils.js +7 -0
- package/build/utils.js.map +1 -1
- package/build/withLoaded.d.ts +0 -1
- package/package.json +2 -2
- package/build/AliasAssigner.d.ts.map +0 -1
- package/build/Aliases.d.ts.map +0 -1
- package/build/BaseEntity.d.ts.map +0 -1
- package/build/ConditionBuilder.d.ts.map +0 -1
- package/build/Entity.d.ts.map +0 -1
- package/build/EntityFields.d.ts.map +0 -1
- package/build/EntityFilter.d.ts.map +0 -1
- package/build/EntityGraphQLFilter.d.ts.map +0 -1
- package/build/EntityManager.d.ts.map +0 -1
- package/build/EntityMetadata.d.ts.map +0 -1
- package/build/EnumMetadata.d.ts.map +0 -1
- package/build/FlushLock.d.ts.map +0 -1
- package/build/HintTree.d.ts.map +0 -1
- package/build/IndexManager.d.ts.map +0 -1
- package/build/InstanceData.d.ts.map +0 -1
- package/build/IsLoadedCache.d.ts.map +0 -1
- package/build/JoinRows.d.ts.map +0 -1
- package/build/PendingChanges.d.ts.map +0 -1
- package/build/PluginManager.d.ts.map +0 -1
- package/build/QueryParser.collectionJoins.d.ts.map +0 -1
- package/build/QueryParser.collectionJoins.test.d.ts +0 -2
- package/build/QueryParser.collectionJoins.test.d.ts.map +0 -1
- package/build/QueryParser.collectionJoins.test.js +0 -772
- package/build/QueryParser.collectionJoins.test.js.map +0 -1
- package/build/QueryParser.d.ts.map +0 -1
- package/build/QueryParser.pruning.d.ts.map +0 -1
- package/build/QueryParser.pruning.test.d.ts +0 -2
- package/build/QueryParser.pruning.test.d.ts.map +0 -1
- package/build/QueryParser.pruning.test.js +0 -106
- package/build/QueryParser.pruning.test.js.map +0 -1
- package/build/QueryParser.test.d.ts +0 -2
- package/build/QueryParser.test.d.ts.map +0 -1
- package/build/QueryParser.test.js +0 -27
- package/build/QueryParser.test.js.map +0 -1
- package/build/QueryVisitor.d.ts.map +0 -1
- package/build/ReactionsManager.d.ts.map +0 -1
- package/build/Todo.d.ts.map +0 -1
- package/build/batchloaders/BatchLoader.d.ts.map +0 -1
- package/build/batchloaders/enumCollectionBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/loadBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/manyToManyBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/oneToManyBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/oneToOneBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/populateBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/recursiveM2mBatchLoader.d.ts.map +0 -1
- package/build/batchloaders/recursiveParentsBatchLoader.d.ts.map +0 -1
- package/build/changes.d.ts.map +0 -1
- package/build/config.d.ts.map +0 -1
- package/build/config.test.d.ts +0 -2
- package/build/config.test.d.ts.map +0 -1
- package/build/config.test.js +0 -53
- package/build/config.test.js.map +0 -1
- package/build/configure.d.ts.map +0 -1
- package/build/dataloaders/fastWhereFilterHash.d.ts.map +0 -1
- package/build/dataloaders/fastWhereFilterHash.test.d.ts +0 -2
- package/build/dataloaders/fastWhereFilterHash.test.d.ts.map +0 -1
- package/build/dataloaders/fastWhereFilterHash.test.js +0 -59
- package/build/dataloaders/fastWhereFilterHash.test.js.map +0 -1
- package/build/dataloaders/findByUniqueDataLoader.d.ts.map +0 -1
- package/build/dataloaders/findCountDataLoader.d.ts.map +0 -1
- package/build/dataloaders/findDataLoader.d.ts.map +0 -1
- package/build/dataloaders/findIdsDataLoader.d.ts.map +0 -1
- package/build/dataloaders/findOrCreateDataLoader.d.ts.map +0 -1
- package/build/dataloaders/findPaginatedDataLoader.d.ts.map +0 -1
- package/build/dataloaders/lensDataLoader.d.ts.map +0 -1
- package/build/dataloaders/manyToManyFindDataLoader.d.ts.map +0 -1
- package/build/dataloaders/oneToManyFindDataLoader.d.ts.map +0 -1
- package/build/defaults.d.ts.map +0 -1
- package/build/driverApi.d.ts.map +0 -1
- package/build/drivers/Driver.d.ts.map +0 -1
- package/build/drivers/EntityWriter.d.ts.map +0 -1
- package/build/drivers/IdAssigner.d.ts.map +0 -1
- package/build/drivers/buildRawQuery.d.ts.map +0 -1
- package/build/drivers/buildUtils.d.ts.map +0 -1
- package/build/drivers/index.d.ts.map +0 -1
- package/build/fields.d.ts.map +0 -1
- package/build/getProperties.d.ts.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/json.d.ts.map +0 -1
- package/build/k.d.ts.map +0 -1
- package/build/keys.d.ts.map +0 -1
- package/build/keywords.d.ts.map +0 -1
- package/build/loadHints.d.ts.map +0 -1
- package/build/loadLens.d.ts.map +0 -1
- package/build/logging/FactoryLogger.d.ts.map +0 -1
- package/build/logging/FieldLogger.d.ts.map +0 -1
- package/build/logging/ReactionLogger.d.ts.map +0 -1
- package/build/newEntity.d.ts.map +0 -1
- package/build/newTestInstance.d.ts.map +0 -1
- package/build/normalizeHints.d.ts.map +0 -1
- package/build/plugins/ImmutableEntitiesPlugin.d.ts.map +0 -1
- package/build/plugins/PreloadPlugin.d.ts.map +0 -1
- package/build/preloading/JsonAggregatePreloader.d.ts.map +0 -1
- package/build/preloading/canPreload.d.ts.map +0 -1
- package/build/preloading/partitionHint.d.ts.map +0 -1
- package/build/reactiveHints.d.ts.map +0 -1
- package/build/relations/AbstractPropertyImpl.d.ts.map +0 -1
- package/build/relations/AbstractRelationImpl.d.ts.map +0 -1
- package/build/relations/AsyncProperty.d.ts.map +0 -1
- package/build/relations/AsyncReactiveField.d.ts.map +0 -1
- package/build/relations/Collection.d.ts.map +0 -1
- package/build/relations/CustomCollection.d.ts.map +0 -1
- package/build/relations/CustomReference.d.ts.map +0 -1
- package/build/relations/EnumCollection.d.ts.map +0 -1
- package/build/relations/LargeCollection.d.ts.map +0 -1
- package/build/relations/ManyToManyCollection.d.ts.map +0 -1
- package/build/relations/ManyToManyLargeCollection.d.ts.map +0 -1
- package/build/relations/ManyToOneReference.d.ts.map +0 -1
- package/build/relations/OneToManyCollection.d.ts.map +0 -1
- package/build/relations/OneToManyLargeCollection.d.ts.map +0 -1
- package/build/relations/OneToOneReference.d.ts.map +0 -1
- package/build/relations/PolymorphicReference.d.ts.map +0 -1
- package/build/relations/ReactiveField.d.ts.map +0 -1
- package/build/relations/ReactiveGetter.d.ts.map +0 -1
- package/build/relations/ReactiveManyToMany.d.ts.map +0 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.ts.map +0 -1
- package/build/relations/ReactiveReference.d.ts.map +0 -1
- package/build/relations/ReadOnlyCollection.d.ts.map +0 -1
- package/build/relations/RecursiveCollection.d.ts.map +0 -1
- package/build/relations/RecursiveCycleError.d.ts.map +0 -1
- package/build/relations/Reference.d.ts.map +0 -1
- package/build/relations/Relation.d.ts.map +0 -1
- package/build/relations/hasAsyncMethod.d.ts.map +0 -1
- package/build/relations/hasManyDerived.d.ts.map +0 -1
- package/build/relations/hasManyThrough.d.ts.map +0 -1
- package/build/relations/hasOneDerived.d.ts.map +0 -1
- package/build/relations/hasOneThrough.d.ts.map +0 -1
- package/build/relations/hasProperty.d.ts.map +0 -1
- package/build/relations/index.d.ts.map +0 -1
- package/build/resurrection.d.ts.map +0 -1
- package/build/rules.d.ts.map +0 -1
- package/build/runtimeConfig.d.ts.map +0 -1
- package/build/scopes.d.ts.map +0 -1
- package/build/serde.d.ts.map +0 -1
- package/build/temporal.d.ts.map +0 -1
- package/build/temporalMappers.d.ts.map +0 -1
- package/build/trusted.d.ts.map +0 -1
- package/build/typeMap.d.ts.map +0 -1
- package/build/unnest.d.ts.map +0 -1
- package/build/upsert.d.ts.map +0 -1
- package/build/utils.d.ts.map +0 -1
- package/build/withLoaded.d.ts.map +0 -1
package/build/EntityManager.js
CHANGED
|
@@ -43,6 +43,7 @@ const index_1 = require("./index");
|
|
|
43
43
|
const IsLoadedCache_1 = require("./IsLoadedCache");
|
|
44
44
|
const JoinRows_1 = require("./JoinRows");
|
|
45
45
|
const loadHints_1 = require("./loadHints");
|
|
46
|
+
const FieldLogger_1 = require("./logging/FieldLogger");
|
|
46
47
|
const newEntity_1 = require("./newEntity");
|
|
47
48
|
const newTestInstance_1 = require("./newTestInstance");
|
|
48
49
|
const PluginManager_1 = require("./PluginManager");
|
|
@@ -51,6 +52,7 @@ const reactiveHints_1 = require("./reactiveHints");
|
|
|
51
52
|
const relations_1 = require("./relations");
|
|
52
53
|
const AsyncProperty_1 = require("./relations/AsyncProperty");
|
|
53
54
|
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
55
|
+
const LazyField_1 = require("./relations/LazyField");
|
|
54
56
|
const RecursiveCollection_1 = require("./relations/RecursiveCollection");
|
|
55
57
|
const scopes_1 = require("./scopes");
|
|
56
58
|
const Todo_1 = require("./Todo");
|
|
@@ -98,6 +100,8 @@ class EntityManager {
|
|
|
98
100
|
// Provides field-based indexing for entity types with >1000 entities to optimize findWithNewOrChanged
|
|
99
101
|
#indexManager = new IndexManager_1.IndexManager();
|
|
100
102
|
#isValidating = false;
|
|
103
|
+
// Set while regular (pre-flush) validation rules run, so `em.find*` can fail fast (see #assertFindAllowed)
|
|
104
|
+
#findRestricted = false;
|
|
101
105
|
#pendingPercolate = new Map();
|
|
102
106
|
#preloadedRelations = new Map();
|
|
103
107
|
/**
|
|
@@ -124,7 +128,7 @@ class EntityManager {
|
|
|
124
128
|
afterCommit: [],
|
|
125
129
|
};
|
|
126
130
|
#preloader;
|
|
127
|
-
#fieldLogger;
|
|
131
|
+
#fieldLogger = FieldLogger_1.noopFieldLogger;
|
|
128
132
|
#isLoadedCache = new IsLoadedCache_1.IsLoadedCache();
|
|
129
133
|
#merging;
|
|
130
134
|
/** Track `a#1`, `a#2`, etc indexes for `em.create`-d entities. */
|
|
@@ -265,6 +269,7 @@ class EntityManager {
|
|
|
265
269
|
return this.#entitiesById.get(id);
|
|
266
270
|
}
|
|
267
271
|
async find(type, where, options) {
|
|
272
|
+
this.#assertFindAllowed("find");
|
|
268
273
|
const { populate, ...rest } = options || {};
|
|
269
274
|
const normalized = (0, index_1.mergeFindOptions)(where, rest);
|
|
270
275
|
const settings = { where: normalized.where, ...normalized.options };
|
|
@@ -278,6 +283,14 @@ class EntityManager {
|
|
|
278
283
|
}
|
|
279
284
|
return result;
|
|
280
285
|
}
|
|
286
|
+
/** Fails fast if `em.${method}` is called from a regular validation rule, i.e. steers to `config.addCommitRule`. */
|
|
287
|
+
#assertFindAllowed(method) {
|
|
288
|
+
if (this.#findRestricted) {
|
|
289
|
+
throw new Error(`em.${method} cannot be called from a validation rule (added via config.addRule), because rules run ` +
|
|
290
|
+
`before the flush and would query stale, pre-flush data. Use config.addCommitRule instead, which runs ` +
|
|
291
|
+
`after the INSERT/UPDATE/DELETEs are flushed (within the same transaction) and so sees the changed state.`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
281
294
|
/** Runs the post-parse find pipeline: plugins mutate the logical AST, then Joist optimizes/prunes before SQL. */
|
|
282
295
|
async executeFind(meta, operation, parsed, settings) {
|
|
283
296
|
const { checkLimit, findSettings } = this.prepareFind(meta, operation, parsed, settings);
|
|
@@ -348,6 +361,7 @@ class EntityManager {
|
|
|
348
361
|
return list[0];
|
|
349
362
|
}
|
|
350
363
|
async findByUnique(type, where, options = {}) {
|
|
364
|
+
this.#assertFindAllowed("findByUnique");
|
|
351
365
|
const { populate, softDeletes = "exclude" } = options;
|
|
352
366
|
const entries = Object.entries(where);
|
|
353
367
|
if (entries.length !== 1) {
|
|
@@ -383,6 +397,7 @@ class EntityManager {
|
|
|
383
397
|
* Note: this method is not currently auto-batched, so it will cause N+1s if called in a loop.
|
|
384
398
|
*/
|
|
385
399
|
async findCount(type, where, options = {}) {
|
|
400
|
+
this.#assertFindAllowed("findCount");
|
|
386
401
|
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
387
402
|
const settings = { where: normalized.where, ...normalized.options };
|
|
388
403
|
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings).catch(function findCount(err) {
|
|
@@ -416,6 +431,7 @@ class EntityManager {
|
|
|
416
431
|
* as `em.find`.
|
|
417
432
|
*/
|
|
418
433
|
async findIds(type, where, options = {}) {
|
|
434
|
+
this.#assertFindAllowed("findIds");
|
|
419
435
|
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
420
436
|
const settings = { where: normalized.where, ...normalized.options };
|
|
421
437
|
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings).catch(function findIds(err) {
|
|
@@ -709,14 +725,13 @@ class EntityManager {
|
|
|
709
725
|
}
|
|
710
726
|
async loadAll(type, _ids, hint) {
|
|
711
727
|
const meta = (0, index_1.getMetadata)(type);
|
|
712
|
-
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
713
|
-
|
|
728
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot;
|
|
729
|
+
// misses remember their tagged id + position so the all-hit path skips the `ids` copy entirely
|
|
714
730
|
const entities = new Array(_ids.length);
|
|
715
731
|
let idsToLoad;
|
|
716
732
|
let positionsToLoad;
|
|
717
733
|
for (let i = 0; i < _ids.length; i++) {
|
|
718
734
|
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
719
|
-
ids[i] = id;
|
|
720
735
|
const entity = this.findExistingInstance(id);
|
|
721
736
|
if (entity) {
|
|
722
737
|
entities[i] = entity;
|
|
@@ -726,25 +741,23 @@ class EntityManager {
|
|
|
726
741
|
(positionsToLoad ??= []).push(i);
|
|
727
742
|
}
|
|
728
743
|
}
|
|
729
|
-
if (idsToLoad
|
|
744
|
+
if (idsToLoad) {
|
|
730
745
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
731
746
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
732
747
|
.catch(function loadAll(err) {
|
|
733
748
|
throw appendStack(err, new Error());
|
|
734
749
|
});
|
|
735
|
-
|
|
736
|
-
|
|
750
|
+
let idsNotFound;
|
|
751
|
+
for (let j = 0; j < positionsToLoad.length; j++) {
|
|
752
|
+
const entity = this.findExistingInstance(idsToLoad[j]);
|
|
753
|
+
entities[positionsToLoad[j]] = entity;
|
|
754
|
+
if (entity === undefined)
|
|
755
|
+
(idsNotFound ??= []).push(idsToLoad[j]);
|
|
737
756
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
for (let i = 0; i < entities.length; i++) {
|
|
741
|
-
if (entities[i] === undefined) {
|
|
742
|
-
(idsNotFound ??= []).push(ids[i]);
|
|
757
|
+
if (idsNotFound) {
|
|
758
|
+
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
743
759
|
}
|
|
744
760
|
}
|
|
745
|
-
if (idsNotFound) {
|
|
746
|
-
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
747
|
-
}
|
|
748
761
|
const loadedEntities = entities;
|
|
749
762
|
if (hint) {
|
|
750
763
|
await this.populate(loadedEntities, hint);
|
|
@@ -759,36 +772,43 @@ class EntityManager {
|
|
|
759
772
|
}
|
|
760
773
|
async loadAllIfExists(type, _ids, hint) {
|
|
761
774
|
const meta = (0, index_1.getMetadata)(type);
|
|
762
|
-
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
763
|
-
|
|
764
|
-
const
|
|
775
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot;
|
|
776
|
+
// the all-hit path returns the single pre-sized array without any id copies or rebuilds
|
|
777
|
+
const maybeEntities = new Array(_ids.length);
|
|
765
778
|
let idsToLoad;
|
|
779
|
+
let positionsToLoad;
|
|
766
780
|
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
767
781
|
for (let i = 0; i < _ids.length; i++) {
|
|
768
782
|
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
769
|
-
ids[i] = id;
|
|
770
783
|
const entity = this.findExistingInstance(id);
|
|
771
784
|
if (entity) {
|
|
772
|
-
|
|
785
|
+
maybeEntities[i] = entity;
|
|
773
786
|
}
|
|
774
787
|
else {
|
|
775
788
|
(idsToLoad ??= []).push(id);
|
|
789
|
+
(positionsToLoad ??= []).push(i);
|
|
776
790
|
}
|
|
777
791
|
}
|
|
778
|
-
|
|
792
|
+
let entities;
|
|
793
|
+
if (idsToLoad) {
|
|
779
794
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
780
795
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
781
796
|
.catch(function loadAllIfExists(err) {
|
|
782
797
|
throw appendStack(err, new Error());
|
|
783
798
|
});
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
799
|
+
// Fill in whichever missed ids actually exist, then compact out the not-founds
|
|
800
|
+
for (let j = 0; j < positionsToLoad.length; j++) {
|
|
801
|
+
maybeEntities[positionsToLoad[j]] = this.findExistingInstance(idsToLoad[j]);
|
|
802
|
+
}
|
|
803
|
+
entities = [];
|
|
804
|
+
for (const entity of maybeEntities) {
|
|
805
|
+
if (entity !== undefined)
|
|
789
806
|
entities.push(entity);
|
|
790
807
|
}
|
|
791
808
|
}
|
|
809
|
+
else {
|
|
810
|
+
entities = maybeEntities;
|
|
811
|
+
}
|
|
792
812
|
if (hint) {
|
|
793
813
|
await this.populate(entities, hint);
|
|
794
814
|
}
|
|
@@ -996,12 +1016,12 @@ class EntityManager {
|
|
|
996
1016
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
997
1017
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
998
1018
|
if (alreadyRanHooks.size > 0) {
|
|
999
|
-
maybeBumpUpdatedAt(this.#rm, (0, Todo_1.createTodos)(
|
|
1019
|
+
maybeBumpUpdatedAt(this.#rm, (0, Todo_1.createTodos)(alreadyRanHooks), now);
|
|
1000
1020
|
}
|
|
1001
1021
|
// Run hooks in a series of loops until things "settle down"
|
|
1002
1022
|
while (pendingHooks.size > 0) {
|
|
1003
1023
|
await this.#fl.allowWrites(async () => {
|
|
1004
|
-
let todos = (0, Todo_1.createTodos)(
|
|
1024
|
+
let todos = (0, Todo_1.createTodos)(pendingHooks);
|
|
1005
1025
|
await (0, defaults_1.setAsyncDefaults)(suppressedDefaultTypeErrors, this.ctx, Todo_1.Todo.groupInsertsByTypeAndSubType(todos));
|
|
1006
1026
|
maybeBumpUpdatedAt(this.#rm, todos, now);
|
|
1007
1027
|
// Run our hooks
|
|
@@ -1059,16 +1079,25 @@ class EntityManager {
|
|
|
1059
1079
|
this.#isValidating = true;
|
|
1060
1080
|
await pluginManager.beforeValidate(changedEntities);
|
|
1061
1081
|
if (validate) {
|
|
1062
|
-
//
|
|
1063
|
-
// `
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1082
|
+
// Regular rules run pre-flush, so `em.find*` would query stale data; block it and steer
|
|
1083
|
+
// users to `config.addCommitRule` (which runs post-flush). Only rules are restricted —
|
|
1084
|
+
// `afterValidation` hooks & plugins below are left free to query.
|
|
1085
|
+
this.#findRestricted = true;
|
|
1086
|
+
try {
|
|
1087
|
+
// Run simple rules first b/c it includes not-null/required rules, so that then when we run
|
|
1088
|
+
// `validateReactiveRules` next, the app's lambdas won't see fundamentally invalid entities & NPE.
|
|
1089
|
+
await validateSimpleRules(entityTodos);
|
|
1090
|
+
// After we've let any "author is not set" simple rules fail before prematurely throwing
|
|
1091
|
+
// the "of course that caused an NPE" `TypeError`s, if all the authors *were* valid/set,
|
|
1092
|
+
// and we still have TypeErrors (from derived valeus), they were real, unrelated errors
|
|
1093
|
+
// that the user should see.
|
|
1094
|
+
if (suppressedDefaultTypeErrors.length > 0)
|
|
1095
|
+
throw suppressedDefaultTypeErrors[0];
|
|
1096
|
+
await validateReactiveRules(this, this.#rm.logger, entityTodos, joinRowTodos);
|
|
1097
|
+
}
|
|
1098
|
+
finally {
|
|
1099
|
+
this.#findRestricted = false;
|
|
1100
|
+
}
|
|
1072
1101
|
await afterValidation(this.ctx, entityTodos);
|
|
1073
1102
|
}
|
|
1074
1103
|
await pluginManager.afterValidate(changedEntities);
|
|
@@ -1077,6 +1106,61 @@ class EntityManager {
|
|
|
1077
1106
|
this.#isValidating = false;
|
|
1078
1107
|
}
|
|
1079
1108
|
};
|
|
1109
|
+
// Runs `addCommitRule`s, which look exactly like regular validation rules but fire here,
|
|
1110
|
+
// after the INSERT/UPDATE/DELETEs have hit the db (but before COMMIT), so that any
|
|
1111
|
+
// `em.find`s they make query the just-flushed/changed state within the transaction.
|
|
1112
|
+
const runCommitValidation = async (entityTodos, joinRowTodos) => {
|
|
1113
|
+
// Drop the pre-flush find/query caches so commit rules' `em.find`s re-query and see the
|
|
1114
|
+
// just-flushed rows, instead of stale results cached earlier in this same flush.
|
|
1115
|
+
this.#dataloaders = {};
|
|
1116
|
+
this.#batchLoaders = {};
|
|
1117
|
+
this.#preloadedRelations = new Map();
|
|
1118
|
+
try {
|
|
1119
|
+
// `isValidating` keeps derived-field reads from creating (now-unflushable) dirty state.
|
|
1120
|
+
this.#isValidating = true;
|
|
1121
|
+
await validateSimpleRules(entityTodos, (config) => config.commitRules);
|
|
1122
|
+
await validateReactiveRules(this, this.#rm.logger, entityTodos, joinRowTodos, (meta) => meta.reactiveCommitRules);
|
|
1123
|
+
}
|
|
1124
|
+
finally {
|
|
1125
|
+
this.#isValidating = false;
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
// Commit rules run once, at the end, over just the flushed entities/join-rows whose type has
|
|
1129
|
+
// commit rules (see `meta.hasCommitRules`). We accumulate that subset here as we flush — deduping
|
|
1130
|
+
// across the RQF loop below (which resets entity dirty-state) — so we neither keep every todo
|
|
1131
|
+
// nor re-scan/merge them afterwards; apps without commit rules leave these empty and pay ~nothing.
|
|
1132
|
+
const commitRuleTodos = {};
|
|
1133
|
+
const commitRuleJoinRowTodos = {};
|
|
1134
|
+
const commitRuleSeen = new Set();
|
|
1135
|
+
function collectCommitRuleTodos(entityTodos, joinRowTodos) {
|
|
1136
|
+
if (skipValidation)
|
|
1137
|
+
return;
|
|
1138
|
+
for (const todo of Object.values(entityTodos)) {
|
|
1139
|
+
if (!todo.metadata.hasCommitRules)
|
|
1140
|
+
continue;
|
|
1141
|
+
for (const key of ["inserts", "updates", "deletes"]) {
|
|
1142
|
+
for (const e of todo[key]) {
|
|
1143
|
+
if (!commitRuleSeen.has(e)) {
|
|
1144
|
+
commitRuleSeen.add(e);
|
|
1145
|
+
(0, Todo_1.getTodo)(commitRuleTodos, e)[key].push(e);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
for (const [table, todo] of Object.entries(joinRowTodos)) {
|
|
1151
|
+
if (!todo.m2m.meta.hasCommitRules && !todo.m2m.otherMeta?.hasCommitRules)
|
|
1152
|
+
continue;
|
|
1153
|
+
const into = (commitRuleJoinRowTodos[table] ??= { ...todo, newRows: [], deletedRows: [] });
|
|
1154
|
+
for (const key of ["newRows", "deletedRows"]) {
|
|
1155
|
+
for (const r of todo[key]) {
|
|
1156
|
+
if (!commitRuleSeen.has(r)) {
|
|
1157
|
+
commitRuleSeen.add(r);
|
|
1158
|
+
into[key].push(r);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1080
1164
|
// Run hooks (in iterative loops if hooks mutate new entities) on pending entities
|
|
1081
1165
|
let entitiesToFlush = await runHooksOnPendingEntities();
|
|
1082
1166
|
for (const e of entitiesToFlush)
|
|
@@ -1095,6 +1179,8 @@ class EntityManager {
|
|
|
1095
1179
|
do {
|
|
1096
1180
|
if (Object.keys(entityTodos).length > 0 || Object.keys(joinRowTodos).length > 0) {
|
|
1097
1181
|
await this.driver.flush(this, entityTodos, joinRowTodos);
|
|
1182
|
+
// Accumulate the commit-rule-relevant subset before the RQF loop clobbers/resets these
|
|
1183
|
+
collectCommitRuleTodos(entityTodos, joinRowTodos);
|
|
1098
1184
|
}
|
|
1099
1185
|
// Now that we've flushed, we can let plugins know what we've done.
|
|
1100
1186
|
pluginManager.afterWrite(entityTodos, joinRowTodos);
|
|
@@ -1130,6 +1216,18 @@ class EntityManager {
|
|
|
1130
1216
|
entityTodos = {};
|
|
1131
1217
|
}
|
|
1132
1218
|
} while (Object.keys(entityTodos).length > 0);
|
|
1219
|
+
// Now that all SQL has hit the db (but before COMMIT), run any `addCommitRule`s so their
|
|
1220
|
+
// `em.find`s see the changed state. `commitRuleTodos` is only non-empty if a flushed entity
|
|
1221
|
+
// actually has commit rules, so apps without them skip this (and its logging) entirely.
|
|
1222
|
+
if (Object.keys(commitRuleTodos).length > 0 || Object.keys(commitRuleJoinRowTodos).length > 0) {
|
|
1223
|
+
// Make just-inserted entities findable by id (like the RQF loop does) so commit rules'
|
|
1224
|
+
// `em.find`s return these same instances rather than hydrating duplicate ones.
|
|
1225
|
+
for (const e of allFlushedEntities) {
|
|
1226
|
+
if (e.isNewEntity && !e.isDeletedEntity)
|
|
1227
|
+
this.#entitiesById.set(e.idTagged, e);
|
|
1228
|
+
}
|
|
1229
|
+
await runCommitValidation(commitRuleTodos, commitRuleJoinRowTodos);
|
|
1230
|
+
}
|
|
1133
1231
|
// Run `beforeCommit once right before COMMIT
|
|
1134
1232
|
await beforeCommit(this.ctx, allFlushedEntities);
|
|
1135
1233
|
if (this.mode === "in-memory-writes") {
|
|
@@ -1141,13 +1239,17 @@ class EntityManager {
|
|
|
1141
1239
|
await afterCommit(this.ctx, allFlushedEntities);
|
|
1142
1240
|
// Update the `#orm` field to reflect the new state
|
|
1143
1241
|
for (const e of allFlushedEntities) {
|
|
1242
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(e);
|
|
1144
1243
|
if (e.isNewEntity && !e.isDeletedEntity)
|
|
1145
1244
|
this.#entitiesById.set(e.idTagged, e);
|
|
1146
|
-
|
|
1147
|
-
// Reset AsyncQueryProperties since DB state may have changed
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1245
|
+
instanceData.resetAfterFlushed();
|
|
1246
|
+
// Reset AsyncQueryProperties & LazyFields since DB state may have changed
|
|
1247
|
+
const { relations } = instanceData;
|
|
1248
|
+
if (relations) {
|
|
1249
|
+
for (const rel of Object.values(relations)) {
|
|
1250
|
+
if (rel instanceof AsyncProperty_1.AsyncPropertyImpl || rel instanceof LazyField_1.LazyFieldImpl)
|
|
1251
|
+
rel.resetAfterFlush();
|
|
1252
|
+
}
|
|
1151
1253
|
}
|
|
1152
1254
|
}
|
|
1153
1255
|
// Update the joinRows refs to reflect the new state
|
|
@@ -1185,16 +1287,28 @@ class EntityManager {
|
|
|
1185
1287
|
}
|
|
1186
1288
|
}
|
|
1187
1289
|
}
|
|
1290
|
+
// node-pg keeps the DETAIL line (i.e. "Key (identity_id, final_id)=(102591, null) already exists")
|
|
1291
|
+
// on a separate `detail` field, so keep it to suffix onto messages to help debugging.
|
|
1292
|
+
const detail = e && typeof e === "object" && "detail" in e && typeof e.detail === "string" ? e.detail : undefined;
|
|
1188
1293
|
if (e && typeof e === "object" && "constraint" in e && typeof e.constraint === "string") {
|
|
1189
1294
|
// node-pg errors use `constraint` to indicate the constraint name
|
|
1190
1295
|
const message = config_1.constraintNameToValidationError[e.constraint];
|
|
1191
1296
|
if (message) {
|
|
1192
|
-
|
|
1297
|
+
const errors = new index_1.ValidationErrors(message);
|
|
1298
|
+
// Keep the user-facing `errors[].message` pretty, but suffix the DETAIL onto the top-level
|
|
1299
|
+
// `message` so the debugging context survives into logs/stack traces.
|
|
1300
|
+
if (detail)
|
|
1301
|
+
errors.message = `${message} — ${detail}`;
|
|
1302
|
+
throw errors;
|
|
1193
1303
|
}
|
|
1194
1304
|
}
|
|
1195
1305
|
if (e instanceof InMemoryRollbackError) {
|
|
1196
1306
|
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1197
1307
|
}
|
|
1308
|
+
// For raw (non-pretty) pg errors, fold the DETAIL into `message` so it survives into logs.
|
|
1309
|
+
if (detail && e instanceof Error) {
|
|
1310
|
+
e.message = `${e.message} — ${detail}`;
|
|
1311
|
+
}
|
|
1198
1312
|
throw e;
|
|
1199
1313
|
}
|
|
1200
1314
|
finally {
|
|
@@ -1302,13 +1416,12 @@ class EntityManager {
|
|
|
1302
1416
|
*/
|
|
1303
1417
|
hydrate(type, rows, options) {
|
|
1304
1418
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1305
|
-
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1306
1419
|
const overwriteExisting = options?.overwriteExisting === true;
|
|
1307
1420
|
let i = 0;
|
|
1308
1421
|
const entities = new Array(rows.length);
|
|
1309
1422
|
for (const row of rows) {
|
|
1310
1423
|
const id = row["id"];
|
|
1311
|
-
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") :
|
|
1424
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : (0, index_1.keyToTaggedId)(maybeBaseMeta, id);
|
|
1312
1425
|
// See if this is already in our UoW
|
|
1313
1426
|
let entity = this.findExistingInstance(taggedId);
|
|
1314
1427
|
if (!entity) {
|
|
@@ -1316,7 +1429,10 @@ class EntityManager {
|
|
|
1316
1429
|
const meta = findConcreteMeta(maybeBaseMeta, row);
|
|
1317
1430
|
// Pass id as a hint that we're in hydrate mode
|
|
1318
1431
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1319
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1432
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1433
|
+
instanceData.row = row;
|
|
1434
|
+
// Seed the id to share the identity-map key string and skip the id serde on first read
|
|
1435
|
+
instanceData.data["id"] = taggedId;
|
|
1320
1436
|
this.#doRegister(entity, taggedId, meta, true);
|
|
1321
1437
|
}
|
|
1322
1438
|
else if (overwriteExisting) {
|
|
@@ -1329,17 +1445,25 @@ class EntityManager {
|
|
|
1329
1445
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1330
1446
|
const { data } = instanceData;
|
|
1331
1447
|
const dataKeys = Object.keys(data);
|
|
1332
|
-
|
|
1448
|
+
// `id` is seeded at hydrate and can never change, so it never needs refreshing
|
|
1449
|
+
if (dataKeys.length > ("id" in data ? 1 : 0)) {
|
|
1333
1450
|
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1334
|
-
|
|
1451
|
+
// For existing entities this is exactly `changes.fieldsWithoutRelations`, minus the proxy allocation
|
|
1452
|
+
const changedFields = instanceData.isNewEntity
|
|
1453
|
+
? entity.changes.fieldsWithoutRelations
|
|
1454
|
+
: Object.keys(instanceData.originalData);
|
|
1335
1455
|
if (changedFields.length === 0) {
|
|
1336
1456
|
for (const fieldName of dataKeys) {
|
|
1457
|
+
if (fieldName === "id")
|
|
1458
|
+
continue;
|
|
1337
1459
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1338
1460
|
serde.setOnEntity(data, row);
|
|
1339
1461
|
}
|
|
1340
1462
|
}
|
|
1341
1463
|
else {
|
|
1342
1464
|
for (const fieldName of dataKeys) {
|
|
1465
|
+
if (fieldName === "id")
|
|
1466
|
+
continue;
|
|
1343
1467
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1344
1468
|
serde.setOnEntity(data, row);
|
|
1345
1469
|
// Make the field look not-dirty
|
|
@@ -1460,7 +1584,7 @@ class EntityManager {
|
|
|
1460
1584
|
}
|
|
1461
1585
|
const writeFn = getDefaultWriteFn(this.ctx);
|
|
1462
1586
|
if (typeof arg === "boolean") {
|
|
1463
|
-
this.#fieldLogger = arg ? new index_1.FieldLogger([], writeFn) :
|
|
1587
|
+
this.#fieldLogger = arg ? new index_1.FieldLogger([], writeFn) : FieldLogger_1.noopFieldLogger;
|
|
1464
1588
|
}
|
|
1465
1589
|
else if (typeof arg === "string" || Array.isArray(arg)) {
|
|
1466
1590
|
const specs = Array.isArray(arg) ? arg : [arg];
|
|
@@ -1495,15 +1619,18 @@ class EntityManager {
|
|
|
1495
1619
|
const entities = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1496
1620
|
// Don't bother filtering if there's no where clause (particularly b/c IndexManager.findMatching
|
|
1497
1621
|
// really expects there to be at least 1 condition)
|
|
1498
|
-
if (
|
|
1622
|
+
if (!(0, utils_1.hasAnyKey)(where)) {
|
|
1499
1623
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1500
1624
|
}
|
|
1501
1625
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1502
1626
|
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1627
|
+
// Build the final array in one pass, still filtering `instanceof cstr` to handle subtyping
|
|
1628
|
+
const result = [];
|
|
1629
|
+
for (const e of this.#indexManager.findMatching(meta, entities, where)) {
|
|
1630
|
+
if (e instanceof cstr && !e.isDeletedEntity)
|
|
1631
|
+
result.push(e);
|
|
1632
|
+
}
|
|
1633
|
+
return result;
|
|
1507
1634
|
}
|
|
1508
1635
|
else {
|
|
1509
1636
|
return entities
|
|
@@ -1626,6 +1753,8 @@ class EntityManager {
|
|
|
1626
1753
|
continue;
|
|
1627
1754
|
}
|
|
1628
1755
|
const { relations } = oldEntity.__data;
|
|
1756
|
+
if (!relations)
|
|
1757
|
+
continue;
|
|
1629
1758
|
for (const [field, relation] of Object.entries(relations)) {
|
|
1630
1759
|
// With lazyRelation, custom relations are inserted into the `relations` map. Custom relations don't
|
|
1631
1760
|
// store any data, so we can ignore them by checking if the relation implements `import`
|
|
@@ -1742,7 +1871,7 @@ class EntityManager {
|
|
|
1742
1871
|
}
|
|
1743
1872
|
// api will be undefined during getFakeInstance
|
|
1744
1873
|
const api = getEmInternalApi(this);
|
|
1745
|
-
api?.fieldLogger
|
|
1874
|
+
api?.fieldLogger.logCreate(entity);
|
|
1746
1875
|
(0, index_1.setOpts)(entity, opts, { partial, calledFromConstructor: true });
|
|
1747
1876
|
// Apply any synchronous defaults, after the opts have been applied
|
|
1748
1877
|
if (!this.fakeInstance) {
|
|
@@ -1851,8 +1980,10 @@ exports.TooManyError = TooManyError;
|
|
|
1851
1980
|
* from the currently-changed entities back to each rule's originally-defined-in entity,
|
|
1852
1981
|
* and ensure those entities are added to `todos`.
|
|
1853
1982
|
*/
|
|
1854
|
-
async function validateReactiveRules(em, logger, todos, joinRowTodos
|
|
1855
|
-
|
|
1983
|
+
async function validateReactiveRules(em, logger, todos, joinRowTodos,
|
|
1984
|
+
// Which list of reactive rules to run, i.e. the pre-flush `reactiveRules` or the post-flush `reactiveCommitRules`
|
|
1985
|
+
getRules = (meta) => meta.reactiveRules) {
|
|
1986
|
+
logger.logStartingValidate(em, todos);
|
|
1856
1987
|
// Use a map of rule -> Set<Entity> so that we only invoke a rule once per entity,
|
|
1857
1988
|
// even if it was triggered by multiple changed fields.
|
|
1858
1989
|
const fns = new Map();
|
|
@@ -1869,15 +2000,18 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1869
2000
|
entities = new Set();
|
|
1870
2001
|
fns.set(rule.fn, entities);
|
|
1871
2002
|
}
|
|
1872
|
-
logger
|
|
2003
|
+
logger.logWalked(triggered, rule, found, "validate");
|
|
1873
2004
|
found.forEach((entity) => {
|
|
1874
2005
|
entities.add(entity);
|
|
1875
2006
|
});
|
|
1876
2007
|
}
|
|
1877
2008
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1878
|
-
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1879
2009
|
// Find each statically-declared reactive rule for the given entity type
|
|
1880
|
-
const rules = todo.metadata
|
|
2010
|
+
const rules = getRules(todo.metadata);
|
|
2011
|
+
// Skip the entities spread for rule-less types
|
|
2012
|
+
if (rules.length === 0)
|
|
2013
|
+
return [];
|
|
2014
|
+
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1881
2015
|
return rules.map((rule) => {
|
|
1882
2016
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1883
2017
|
const triggered = entities.filter((e) => {
|
|
@@ -1887,10 +2021,12 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1887
2021
|
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1888
2022
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1889
2023
|
return true;
|
|
1890
|
-
// Otherwise see if the changed fields
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2024
|
+
// Otherwise see if the changed fields overlap with the rule's fields; for existing entities
|
|
2025
|
+
// `changes.fieldsWithoutRelations` is exactly `Object.keys(originalData)`, so probe originalData
|
|
2026
|
+
// directly instead of allocating a proxy + keys array per entity per rule.
|
|
2027
|
+
const { originalData } = (0, BaseEntity_1.getInstanceData)(e);
|
|
2028
|
+
for (const field of rule.fields) {
|
|
2029
|
+
if (field in originalData)
|
|
1894
2030
|
return true;
|
|
1895
2031
|
}
|
|
1896
2032
|
return false;
|
|
@@ -1903,8 +2039,8 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1903
2039
|
// For enum m2ms the columns include the enum's numeric id, so keep only the entity values.
|
|
1904
2040
|
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns)).filter(Entity_1.isEntity);
|
|
1905
2041
|
// The owning-entity side (applies to both entity-to-entity and entity-to-enum m2ms).
|
|
1906
|
-
const a = todo.m2m.meta
|
|
1907
|
-
.
|
|
2042
|
+
const a = getRules(todo.m2m.meta)
|
|
2043
|
+
.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1908
2044
|
.map((rule) => {
|
|
1909
2045
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1910
2046
|
return followAndQueue(triggered, rule);
|
|
@@ -1912,8 +2048,8 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1912
2048
|
// The "other" side only exists for entity-to-entity m2ms; enums have no reverse field.
|
|
1913
2049
|
const { otherMeta, otherFieldName } = todo.m2m;
|
|
1914
2050
|
const b = otherMeta
|
|
1915
|
-
? otherMeta
|
|
1916
|
-
.
|
|
2051
|
+
? getRules(otherMeta)
|
|
2052
|
+
.filter((rule) => rule.fields.includes(otherFieldName))
|
|
1917
2053
|
.map((rule) => {
|
|
1918
2054
|
const triggered = entities.filter((e) => e instanceof otherMeta.cstr);
|
|
1919
2055
|
return followAndQueue(triggered, rule);
|
|
@@ -1932,22 +2068,37 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1932
2068
|
// Run *non-reactive* (those with `fields: undefined`) rules of explicitly mutated entities,
|
|
1933
2069
|
// because even for reactive validations on mutated entities, we defer to addReactiveValidations
|
|
1934
2070
|
// to mark only the rules that need to run.
|
|
1935
|
-
async function validateSimpleRules(todos
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2071
|
+
async function validateSimpleRules(todos,
|
|
2072
|
+
// Which config list to pull rules from, i.e. the pre-flush `rules` or the post-flush `commitRules`
|
|
2073
|
+
getRules = (config) => config.rules) {
|
|
2074
|
+
let p = undefined;
|
|
2075
|
+
for (const todoKey in todos) {
|
|
2076
|
+
const { inserts, updates } = todos[todoKey];
|
|
2077
|
+
for (const entities of [inserts, updates]) {
|
|
2078
|
+
for (const entity of entities) {
|
|
2079
|
+
if (entity.isDeletedEntity)
|
|
2080
|
+
continue;
|
|
2081
|
+
for (const m of (0, index_1.getBaseAndSelfMetas)((0, index_1.getMetadata)(entity))) {
|
|
2082
|
+
for (const rule of getRules(m.config.__data)) {
|
|
2083
|
+
if (rule.hint !== undefined)
|
|
2084
|
+
continue;
|
|
2085
|
+
(p ??= []).push(invokeRule(entity, rule.fn));
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (!p)
|
|
2092
|
+
return;
|
|
1946
2093
|
const errors = (0, utils_1.failIfAnyRejected)(await Promise.allSettled(p)).flat();
|
|
1947
2094
|
if (errors.length > 0) {
|
|
1948
2095
|
throw new index_1.ValidationErrors(errors);
|
|
1949
2096
|
}
|
|
1950
2097
|
}
|
|
2098
|
+
/** Invokes a single validation rule, coercing its result/sync throws into a promise. */
|
|
2099
|
+
async function invokeRule(entity, fn) {
|
|
2100
|
+
return coerceError(entity, await fn(entity));
|
|
2101
|
+
}
|
|
1951
2102
|
function driverBeforeBegin(em, txn) {
|
|
1952
2103
|
return Promise.all(getEmInternalApi(em).hooks.beforeBegin.map((fn) => fn(em, txn)));
|
|
1953
2104
|
}
|
|
@@ -1961,23 +2112,54 @@ function driverAfterCommit(em, txn) {
|
|
|
1961
2112
|
return Promise.all(getEmInternalApi(em).hooks.afterCommit.map((fn) => fn(em, txn)));
|
|
1962
2113
|
}
|
|
1963
2114
|
async function runHookOnTodos(ctx, hook, todos, keys) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2115
|
+
// Only collect entities for todos whose type hierarchy actually has this hook, so that
|
|
2116
|
+
// hook-less bulk flushes skip the per-entity array building entirely.
|
|
2117
|
+
let entities = undefined;
|
|
2118
|
+
for (const todoKey in todos) {
|
|
2119
|
+
const todo = todos[todoKey];
|
|
2120
|
+
if (!metaHasHook(todo.metadata, hook))
|
|
2121
|
+
continue;
|
|
2122
|
+
for (const k of keys) {
|
|
2123
|
+
for (const e of todo[k]) {
|
|
2124
|
+
if (k !== "deletes" && e.isDeletedEntity)
|
|
2125
|
+
continue;
|
|
2126
|
+
(entities ??= []).push(e);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (entities)
|
|
2131
|
+
return runHook(ctx, hook, entities);
|
|
2132
|
+
}
|
|
2133
|
+
/** Returns whether `meta`'s base/self/sub hierarchy has any `hook` functions registered. */
|
|
2134
|
+
function metaHasHook(meta, hook) {
|
|
2135
|
+
for (const m of (0, index_1.getBaseSelfAndSubMetas)(meta)) {
|
|
2136
|
+
if (m.config.__data.hooks[hook].length > 0)
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
return false;
|
|
1968
2140
|
}
|
|
1969
2141
|
async function runHook(ctx, hook, entities) {
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2142
|
+
let p = undefined;
|
|
2143
|
+
for (const entity of entities) {
|
|
2144
|
+
for (const m of (0, index_1.getBaseAndSelfMetas)((0, index_1.getMetadata)(entity))) {
|
|
2145
|
+
for (const fn of m.config.__data.hooks[hook]) {
|
|
2146
|
+
// Use an explicit `async` invoke to ensure all hooks are promises, i.e. so that a
|
|
2147
|
+
// non-promise hook blowing up doesn't orphan the others.
|
|
2148
|
+
(p ??= []).push(invokeHook(fn, entity, ctx));
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
if (!p)
|
|
2153
|
+
return;
|
|
1976
2154
|
// Use `allSettled` so that even if 1 hook blows up, we don't orphan other hooks mid-flush
|
|
1977
2155
|
// (causes weird errors when/if they try to access the EntityManager that has "moved on")
|
|
1978
2156
|
const results = await Promise.allSettled(p);
|
|
1979
2157
|
(0, utils_1.failIfAnyRejected)(results);
|
|
1980
2158
|
}
|
|
2159
|
+
/** Invokes a single hook fn, coercing sync throws into rejections. */
|
|
2160
|
+
async function invokeHook(fn, entity, ctx) {
|
|
2161
|
+
return fn(entity, ctx);
|
|
2162
|
+
}
|
|
1981
2163
|
function beforeDelete(ctx, todos) {
|
|
1982
2164
|
return runHookOnTodos(ctx, "beforeDelete", todos, ["deletes"]);
|
|
1983
2165
|
}
|
|
@@ -1997,16 +2179,21 @@ function afterValidation(ctx, todos) {
|
|
|
1997
2179
|
function entitiesFromTodos(entityTodos, joinRowTodos) {
|
|
1998
2180
|
const entities = new Set();
|
|
1999
2181
|
for (const todo of Object.values(entityTodos)) {
|
|
2000
|
-
[
|
|
2182
|
+
for (const list of [todo.inserts, todo.updates, todo.deletes]) {
|
|
2183
|
+
for (const entity of list)
|
|
2184
|
+
entities.add(entity);
|
|
2185
|
+
}
|
|
2001
2186
|
}
|
|
2002
2187
|
for (const todo of Object.values(joinRowTodos)) {
|
|
2003
|
-
[
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2188
|
+
for (const rows of [todo.newRows, todo.deletedRows]) {
|
|
2189
|
+
for (const row of rows) {
|
|
2190
|
+
// For enum m2ms, a column value is the enum's numeric id rather than an entity.
|
|
2191
|
+
for (const value of Object.values(row.columns)) {
|
|
2192
|
+
if ((0, Entity_1.isEntity)(value))
|
|
2193
|
+
entities.add(value);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2010
2197
|
}
|
|
2011
2198
|
return [...entities];
|
|
2012
2199
|
}
|
|
@@ -2040,23 +2227,19 @@ function coerceError(entity, maybeError) {
|
|
|
2040
2227
|
}
|
|
2041
2228
|
/** Evaluates each (non-async) derived field to see if it's value has changed. */
|
|
2042
2229
|
function recalcSynchronousDerivedFields(todos) {
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
derivedFields.forEach((fieldName) => {
|
|
2057
|
-
// setField will intelligently mark/not mark the field as dirty.
|
|
2058
|
-
(0, fields_1.setField)(entity, fieldName, entity[fieldName]);
|
|
2059
|
-
});
|
|
2230
|
+
for (const todoKey in todos) {
|
|
2231
|
+
const todo = todos[todoKey];
|
|
2232
|
+
for (const entities of [todo.inserts, todo.updates]) {
|
|
2233
|
+
for (const entity of entities) {
|
|
2234
|
+
if (entity.isDeletedEntity)
|
|
2235
|
+
continue;
|
|
2236
|
+
// Use the per-meta cached field list instead of rebuilding a map per hook loop
|
|
2237
|
+
for (const fieldName of (0, index_1.getMetadata)(entity).syncDerivedFields) {
|
|
2238
|
+
// setField will intelligently mark/not mark the field as dirty.
|
|
2239
|
+
(0, fields_1.setField)(entity, fieldName, entity[fieldName]);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2060
2243
|
}
|
|
2061
2244
|
}
|
|
2062
2245
|
/** Recursively crawls through `entity`, with the given populate `deep` hint, and adds anything found to `found`. */
|