joist-core 2.2.0 → 2.3.0-next.10
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/Aliases.d.ts.map +1 -1
- package/build/Aliases.js.map +1 -1
- package/build/BaseEntity.d.ts +2 -0
- package/build/BaseEntity.d.ts.map +1 -1
- package/build/BaseEntity.js +1 -1
- package/build/BaseEntity.js.map +1 -1
- package/build/Entity.d.ts +1 -0
- package/build/Entity.d.ts.map +1 -1
- package/build/EntityFilter.d.ts +29 -10
- package/build/EntityFilter.d.ts.map +1 -1
- package/build/EntityFilter.js +18 -0
- package/build/EntityFilter.js.map +1 -1
- package/build/EntityGraphQLFilter.d.ts +3 -0
- package/build/EntityGraphQLFilter.d.ts.map +1 -1
- package/build/EntityGraphQLFilter.js.map +1 -1
- package/build/EntityManager.d.ts +36 -50
- package/build/EntityManager.d.ts.map +1 -1
- package/build/EntityManager.js +261 -125
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +52 -2
- package/build/EntityMetadata.d.ts.map +1 -1
- package/build/EntityMetadata.js +17 -0
- package/build/EntityMetadata.js.map +1 -1
- package/build/IndexManager.d.ts +5 -5
- package/build/IndexManager.d.ts.map +1 -1
- package/build/IndexManager.js +48 -32
- package/build/IndexManager.js.map +1 -1
- package/build/InstanceData.d.ts +16 -2
- package/build/InstanceData.d.ts.map +1 -1
- package/build/InstanceData.js +37 -4
- package/build/InstanceData.js.map +1 -1
- package/build/IsLoadedCache.d.ts.map +1 -1
- package/build/IsLoadedCache.js +31 -25
- package/build/IsLoadedCache.js.map +1 -1
- package/build/JoinRows.d.ts +54 -12
- package/build/JoinRows.d.ts.map +1 -1
- package/build/JoinRows.js +114 -44
- package/build/JoinRows.js.map +1 -1
- package/build/QueryParser.collectionJoins.js +8 -2
- package/build/QueryParser.collectionJoins.js.map +1 -1
- package/build/QueryParser.d.ts +15 -3
- package/build/QueryParser.d.ts.map +1 -1
- package/build/QueryParser.js +293 -190
- package/build/QueryParser.js.map +1 -1
- package/build/QueryParser.pruning.d.ts.map +1 -1
- package/build/QueryParser.pruning.js +8 -1
- package/build/QueryParser.pruning.js.map +1 -1
- package/build/QueryParser.test.d.ts +2 -0
- package/build/QueryParser.test.d.ts.map +1 -0
- package/build/QueryParser.test.js +27 -0
- package/build/QueryParser.test.js.map +1 -0
- package/build/ReactionsManager.d.ts +2 -1
- package/build/ReactionsManager.d.ts.map +1 -1
- package/build/ReactionsManager.js +53 -49
- package/build/ReactionsManager.js.map +1 -1
- package/build/Todo.d.ts +4 -0
- package/build/Todo.d.ts.map +1 -1
- package/build/Todo.js.map +1 -1
- package/build/batchloaders/BatchLoader.d.ts.map +1 -1
- package/build/batchloaders/BatchLoader.js +6 -1
- package/build/batchloaders/BatchLoader.js.map +1 -1
- package/build/batchloaders/enumCollectionBatchLoader.d.ts +7 -0
- package/build/batchloaders/enumCollectionBatchLoader.d.ts.map +1 -0
- package/build/batchloaders/enumCollectionBatchLoader.js +54 -0
- package/build/batchloaders/enumCollectionBatchLoader.js.map +1 -0
- package/build/batchloaders/manyToManyBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/manyToManyBatchLoader.js +10 -2
- package/build/batchloaders/manyToManyBatchLoader.js.map +1 -1
- package/build/batchloaders/populateBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/populateBatchLoader.js +26 -8
- package/build/batchloaders/populateBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js +2 -0
- package/build/batchloaders/recursiveChildrenBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.js +1 -3
- package/build/batchloaders/recursiveM2mBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js +2 -0
- package/build/batchloaders/recursiveParentsBatchLoader.js.map +1 -1
- package/build/changes.d.ts +13 -0
- package/build/changes.d.ts.map +1 -1
- package/build/changes.js +49 -0
- package/build/changes.js.map +1 -1
- package/build/configure.d.ts +5 -3
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +70 -0
- package/build/configure.js.map +1 -1
- package/build/dataloaders/fastWhereFilterHash.d.ts +15 -0
- package/build/dataloaders/fastWhereFilterHash.d.ts.map +1 -0
- package/build/dataloaders/fastWhereFilterHash.js +164 -0
- package/build/dataloaders/fastWhereFilterHash.js.map +1 -0
- package/build/dataloaders/fastWhereFilterHash.test.d.ts +2 -0
- package/build/dataloaders/fastWhereFilterHash.test.d.ts.map +1 -0
- package/build/dataloaders/fastWhereFilterHash.test.js +59 -0
- package/build/dataloaders/fastWhereFilterHash.test.js.map +1 -0
- package/build/dataloaders/findCountDataLoader.d.ts +13 -4
- package/build/dataloaders/findCountDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +58 -15
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.d.ts +49 -10
- package/build/dataloaders/findDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findDataLoader.js +217 -108
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.ts +1 -2
- package/build/dataloaders/findIdsDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.js +18 -17
- package/build/dataloaders/findIdsDataLoader.js.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.js +3 -3
- package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.ts +7 -0
- package/build/dataloaders/findPaginatedDataLoader.d.ts.map +1 -0
- package/build/dataloaders/findPaginatedDataLoader.js +84 -0
- package/build/dataloaders/findPaginatedDataLoader.js.map +1 -0
- package/build/dataloaders/manyToManyFindDataLoader.js +7 -1
- package/build/dataloaders/manyToManyFindDataLoader.js.map +1 -1
- package/build/drivers/EntityWriter.js +13 -7
- package/build/drivers/EntityWriter.js.map +1 -1
- package/build/drivers/buildRawQuery.d.ts +6 -4
- package/build/drivers/buildRawQuery.d.ts.map +1 -1
- package/build/drivers/buildRawQuery.js +8 -4
- package/build/drivers/buildRawQuery.js.map +1 -1
- package/build/fields.d.ts +2 -0
- package/build/fields.d.ts.map +1 -1
- package/build/fields.js +18 -6
- package/build/fields.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -1
- package/build/index.js.map +1 -1
- package/build/keys.d.ts.map +1 -1
- package/build/keys.js +8 -6
- package/build/keys.js.map +1 -1
- package/build/loadHints.d.ts +13 -4
- package/build/loadHints.d.ts.map +1 -1
- package/build/loadHints.js +63 -9
- package/build/loadHints.js.map +1 -1
- package/build/loadLens.d.ts +11 -0
- package/build/loadLens.d.ts.map +1 -1
- package/build/loadLens.js +43 -10
- package/build/loadLens.js.map +1 -1
- package/build/newEntity.d.ts.map +1 -1
- package/build/newEntity.js +16 -14
- package/build/newEntity.js.map +1 -1
- package/build/newTestInstance.d.ts.map +1 -1
- package/build/newTestInstance.js +3 -0
- package/build/newTestInstance.js.map +1 -1
- package/build/normalizeHints.d.ts +7 -0
- package/build/normalizeHints.d.ts.map +1 -1
- package/build/normalizeHints.js +40 -2
- package/build/normalizeHints.js.map +1 -1
- package/build/preloading/JsonAggregatePreloader.d.ts.map +1 -1
- package/build/preloading/JsonAggregatePreloader.js +91 -9
- package/build/preloading/JsonAggregatePreloader.js.map +1 -1
- package/build/preloading/canPreload.d.ts +2 -2
- package/build/preloading/canPreload.d.ts.map +1 -1
- package/build/preloading/canPreload.js +3 -0
- package/build/preloading/canPreload.js.map +1 -1
- package/build/preloading/partitionHint.d.ts.map +1 -1
- package/build/preloading/partitionHint.js +10 -4
- package/build/preloading/partitionHint.js.map +1 -1
- package/build/reactiveHints.d.ts +2 -2
- package/build/reactiveHints.d.ts.map +1 -1
- package/build/reactiveHints.js +9 -0
- package/build/reactiveHints.js.map +1 -1
- package/build/relations/EnumCollection.d.ts +81 -0
- package/build/relations/EnumCollection.d.ts.map +1 -0
- package/build/relations/EnumCollection.js +211 -0
- package/build/relations/EnumCollection.js.map +1 -0
- package/build/relations/ManyToManyCollection.d.ts +1 -0
- package/build/relations/ManyToManyCollection.d.ts.map +1 -1
- package/build/relations/ManyToManyCollection.js +10 -5
- package/build/relations/ManyToManyCollection.js.map +1 -1
- package/build/relations/ManyToManyLargeCollection.d.ts +3 -2
- package/build/relations/ManyToManyLargeCollection.d.ts.map +1 -1
- package/build/relations/ManyToManyLargeCollection.js +7 -4
- package/build/relations/ManyToManyLargeCollection.js.map +1 -1
- package/build/relations/OneToManyCollection.d.ts.map +1 -1
- package/build/relations/OneToManyCollection.js +5 -3
- package/build/relations/OneToManyCollection.js.map +1 -1
- package/build/relations/OneToManyLargeCollection.d.ts.map +1 -1
- package/build/relations/OneToManyLargeCollection.js +2 -1
- package/build/relations/OneToManyLargeCollection.js.map +1 -1
- package/build/relations/ReactiveManyToMany.d.ts +1 -0
- package/build/relations/ReactiveManyToMany.d.ts.map +1 -1
- package/build/relations/ReactiveManyToMany.js +4 -1
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.ts +1 -0
- package/build/relations/ReactiveManyToManyOtherSide.d.ts.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js +4 -1
- package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
- package/build/relations/RecursiveCollection.d.ts +2 -6
- package/build/relations/RecursiveCollection.d.ts.map +1 -1
- package/build/relations/RecursiveCollection.js +9 -17
- package/build/relations/RecursiveCollection.js.map +1 -1
- package/build/relations/RecursiveCycleError.d.ts +13 -0
- package/build/relations/RecursiveCycleError.d.ts.map +1 -0
- package/build/relations/RecursiveCycleError.js +15 -0
- package/build/relations/RecursiveCycleError.js.map +1 -0
- package/build/relations/hasOneThrough.d.ts.map +1 -1
- package/build/relations/hasOneThrough.js +6 -4
- package/build/relations/hasOneThrough.js.map +1 -1
- package/build/relations/hasProperty.d.ts +2 -1
- package/build/relations/hasProperty.d.ts.map +1 -1
- package/build/relations/hasProperty.js +15 -5
- package/build/relations/hasProperty.js.map +1 -1
- package/build/relations/index.d.ts +1 -0
- package/build/relations/index.d.ts.map +1 -1
- package/build/relations/index.js +5 -2
- package/build/relations/index.js.map +1 -1
- package/build/rules.js +1 -1
- package/build/rules.js.map +1 -1
- package/build/scopes.d.ts +185 -0
- package/build/scopes.d.ts.map +1 -0
- package/build/scopes.js +302 -0
- package/build/scopes.js.map +1 -0
- package/build/temporal.d.ts +32 -2
- package/build/temporal.d.ts.map +1 -1
- package/build/temporal.js +18 -0
- package/build/temporal.js.map +1 -1
- package/build/upsert.d.ts.map +1 -1
- package/build/upsert.js +9 -2
- package/build/upsert.js.map +1 -1
- package/build/utils.d.ts +2 -0
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +12 -0
- package/build/utils.js.map +1 -1
- package/package.json +8 -10
- package/build/caches.d.ts +0 -6
- package/build/caches.d.ts.map +0 -1
- package/build/caches.js +0 -42
- package/build/caches.js.map +0 -1
package/build/EntityManager.js
CHANGED
|
@@ -29,7 +29,6 @@ const IndexManager_1 = require("./IndexManager");
|
|
|
29
29
|
// We alias `Entity => EntityW` to denote "Entity wide" i.e. the non-narrowed Entity
|
|
30
30
|
const loadBatchLoader_1 = require("./batchloaders/loadBatchLoader");
|
|
31
31
|
const populateBatchLoader_1 = require("./batchloaders/populateBatchLoader");
|
|
32
|
-
const caches_1 = require("./caches");
|
|
33
32
|
const config_1 = require("./config");
|
|
34
33
|
const configure_1 = require("./configure");
|
|
35
34
|
const findByUniqueDataLoader_1 = require("./dataloaders/findByUniqueDataLoader");
|
|
@@ -37,20 +36,23 @@ const findCountDataLoader_1 = require("./dataloaders/findCountDataLoader");
|
|
|
37
36
|
const findDataLoader_1 = require("./dataloaders/findDataLoader");
|
|
38
37
|
const findIdsDataLoader_1 = require("./dataloaders/findIdsDataLoader");
|
|
39
38
|
const findOrCreateDataLoader_1 = require("./dataloaders/findOrCreateDataLoader");
|
|
39
|
+
const findPaginatedDataLoader_1 = require("./dataloaders/findPaginatedDataLoader");
|
|
40
40
|
const Entity_1 = require("./Entity");
|
|
41
41
|
const FlushLock_1 = require("./FlushLock");
|
|
42
42
|
const index_1 = require("./index");
|
|
43
43
|
const IsLoadedCache_1 = require("./IsLoadedCache");
|
|
44
44
|
const JoinRows_1 = require("./JoinRows");
|
|
45
|
+
const loadHints_1 = require("./loadHints");
|
|
45
46
|
const newEntity_1 = require("./newEntity");
|
|
46
47
|
const newTestInstance_1 = require("./newTestInstance");
|
|
47
48
|
const PluginManager_1 = require("./PluginManager");
|
|
48
49
|
const ReactionsManager_1 = require("./ReactionsManager");
|
|
49
50
|
const reactiveHints_1 = require("./reactiveHints");
|
|
50
51
|
const relations_1 = require("./relations");
|
|
51
|
-
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
52
52
|
const AsyncProperty_1 = require("./relations/AsyncProperty");
|
|
53
|
+
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
53
54
|
const RecursiveCollection_1 = require("./relations/RecursiveCollection");
|
|
55
|
+
const scopes_1 = require("./scopes");
|
|
54
56
|
const Todo_1 = require("./Todo");
|
|
55
57
|
const trusted_1 = require("./trusted");
|
|
56
58
|
const upsert_1 = require("./upsert");
|
|
@@ -87,6 +89,8 @@ class EntityManager {
|
|
|
87
89
|
txn;
|
|
88
90
|
entityLimit = defaultEntityLimit;
|
|
89
91
|
#entitiesArray = [];
|
|
92
|
+
// Incrementally track dirty entities so we don't have to scan `#entityArray` during flush
|
|
93
|
+
#maybePendingFlushEntities = new Set();
|
|
90
94
|
// Indexes the currently loaded entities by their tagged ids and `toTaggedString` ids (i.e. `a#`). This fixes
|
|
91
95
|
// real-world performance issues where `findExistingInstance` scanning `#entities` was an `O(n^2)`.
|
|
92
96
|
#entitiesById = new Map();
|
|
@@ -105,6 +109,7 @@ class EntityManager {
|
|
|
105
109
|
* so that both see the most accurate state.
|
|
106
110
|
*/
|
|
107
111
|
#pendingDeletes = [];
|
|
112
|
+
#hasAnyDeletes = false;
|
|
108
113
|
#dataloaders = {};
|
|
109
114
|
#batchLoaders = {};
|
|
110
115
|
#joinRows = {};
|
|
@@ -154,6 +159,20 @@ class EntityManager {
|
|
|
154
159
|
indexManager: this.#indexManager,
|
|
155
160
|
isLoadedCache: this.#isLoadedCache,
|
|
156
161
|
pluginManager,
|
|
162
|
+
markMaybePending(entity) {
|
|
163
|
+
em.#maybePendingFlushEntities.add(entity);
|
|
164
|
+
},
|
|
165
|
+
unmarkMaybePending(entity) {
|
|
166
|
+
em.#maybePendingFlushEntities.delete(entity);
|
|
167
|
+
},
|
|
168
|
+
hasAnyDeletes() {
|
|
169
|
+
return em.#hasAnyDeletes;
|
|
170
|
+
},
|
|
171
|
+
pendingDeleteIds(type) {
|
|
172
|
+
return em.#pendingDeletes
|
|
173
|
+
.filter((entity) => entity instanceof type && entity.idMaybe !== undefined)
|
|
174
|
+
.map((entity) => (0, index_1.keyToNumber)((0, index_1.getMetadata)(entity), entity.id));
|
|
175
|
+
},
|
|
157
176
|
isMerging(entity) {
|
|
158
177
|
return em.#merging?.has(entity) ?? false;
|
|
159
178
|
},
|
|
@@ -219,7 +238,8 @@ class EntityManager {
|
|
|
219
238
|
}
|
|
220
239
|
for (const joinRows of Object.values(this.#joinRows)) {
|
|
221
240
|
const todo = joinRows.toTodo();
|
|
222
|
-
|
|
241
|
+
// Enum m2ms (EnumCollections) aren't entity-to-entity, so skip them here.
|
|
242
|
+
if (todo && !todo.m2m.otherEnum) {
|
|
223
243
|
for (const row of todo.newRows) {
|
|
224
244
|
const entities = Object.values(row.columns);
|
|
225
245
|
changes.push({ kind: "m2m", op: "add", joinTableName: todo.m2m.joinTableName, entities });
|
|
@@ -246,10 +266,11 @@ class EntityManager {
|
|
|
246
266
|
}
|
|
247
267
|
async find(type, where, options) {
|
|
248
268
|
const { populate, ...rest } = options || {};
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
.
|
|
269
|
+
const normalized = (0, index_1.mergeFindOptions)(where, rest);
|
|
270
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
271
|
+
const result = await (hasPaginationSettings(normalized.options)
|
|
272
|
+
? (0, findPaginatedDataLoader_1.findPaginatedDataLoader)(this, type, settings, populate)
|
|
273
|
+
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)).catch(function find(err) {
|
|
253
274
|
throw appendStack(err, new Error());
|
|
254
275
|
});
|
|
255
276
|
if (populate) {
|
|
@@ -257,37 +278,52 @@ class EntityManager {
|
|
|
257
278
|
}
|
|
258
279
|
return result;
|
|
259
280
|
}
|
|
260
|
-
async findPaginated(type, where, options) {
|
|
261
|
-
const { populate, limit, offset, ...rest } = options || {};
|
|
262
|
-
const meta = (0, index_1.getMetadata)(type);
|
|
263
|
-
const query = (0, index_1.parseFindQuery)(meta, where, rest);
|
|
264
|
-
const rows = await this.executeFind(meta, "findPaginated", query, { ...rest, limit, offset, checkLimit: false });
|
|
265
|
-
const result = this.hydrate(type, rows);
|
|
266
|
-
if (populate) {
|
|
267
|
-
await this.populate(result, populate);
|
|
268
|
-
}
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
281
|
/** Runs the post-parse find pipeline: plugins mutate the logical AST, then Joist optimizes/prunes before SQL. */
|
|
272
282
|
async executeFind(meta, operation, parsed, settings) {
|
|
273
|
-
const { checkLimit,
|
|
283
|
+
const { checkLimit, findSettings } = this.prepareFind(meta, operation, parsed, settings);
|
|
284
|
+
return this.executePreparedFind(meta, operation, parsed, findSettings, checkLimit);
|
|
285
|
+
}
|
|
286
|
+
/** Executes a query that has already had find hooks and optimizations applied. */
|
|
287
|
+
async executePreparedFind(meta, operation, parsed, findSettings, checkLimit) {
|
|
274
288
|
const { pluginManager } = getEmInternalApi(this);
|
|
275
|
-
|
|
276
|
-
(0, index_1.optimizeCollectionJoins)(parsed, settings);
|
|
277
|
-
const rows = await this.driver.executeFind(this, parsed, driverSettings);
|
|
289
|
+
const rows = await this.driver.executeFind(this, parsed, findSettings);
|
|
278
290
|
// Check by default unless explicitly disabled or the caller removed the LIMIT via `limit: undefined`
|
|
279
|
-
const shouldCheck = checkLimit ?? !("limit" in
|
|
291
|
+
const shouldCheck = checkLimit ?? !("limit" in findSettings && findSettings.limit === undefined);
|
|
280
292
|
if (shouldCheck && rows.length >= this.entityLimit) {
|
|
281
293
|
throw new Error(`Query returned more than ${this.entityLimit} entityLimit rows`);
|
|
282
294
|
}
|
|
283
295
|
pluginManager.afterFind(meta, operation, rows);
|
|
284
296
|
return rows;
|
|
285
297
|
}
|
|
286
|
-
|
|
287
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Runs pre-SQL find hooks and optimizations against a parsed query.
|
|
300
|
+
*
|
|
301
|
+
* This allows plugins to see "pre-batched" / "logical" query ASTs, instead of our
|
|
302
|
+
* more complicated `_find` batched queries. The flow would be:
|
|
303
|
+
*
|
|
304
|
+
* - A loader calls `prepareFind(originalQuery)`
|
|
305
|
+
* - Plugins inspect/modify the query as/if needed
|
|
306
|
+
* - The loader crafts a new, more complicated query that embeds the originalQuery
|
|
307
|
+
* - The loader calls `executePreparedFind` with the 2nd query
|
|
308
|
+
*/
|
|
309
|
+
prepareFind(meta, operation, parsed, settings) {
|
|
310
|
+
const { checkLimit, ...findSettings } = settings;
|
|
311
|
+
const { pluginManager } = getEmInternalApi(this);
|
|
312
|
+
// Plugins may mutate the settings object, so return the post-hook version that loaders must reuse.
|
|
313
|
+
pluginManager.beforeFind(meta, operation, parsed, findSettings);
|
|
314
|
+
(0, index_1.optimizeCollectionJoins)(parsed, settings);
|
|
315
|
+
return { checkLimit, findSettings };
|
|
288
316
|
}
|
|
289
|
-
async
|
|
290
|
-
|
|
317
|
+
async findGql(type, where, options) {
|
|
318
|
+
if (!options) {
|
|
319
|
+
return this.find(type, where);
|
|
320
|
+
}
|
|
321
|
+
const normalized = { ...options };
|
|
322
|
+
if ("limit" in normalized)
|
|
323
|
+
normalized.limit = normalized.limit ?? undefined;
|
|
324
|
+
if ("offset" in normalized)
|
|
325
|
+
normalized.offset = normalized.offset ?? undefined;
|
|
326
|
+
return this.find(type, where, normalized);
|
|
291
327
|
}
|
|
292
328
|
async findOne(type, where, options) {
|
|
293
329
|
const list = await this.find(type, where, options);
|
|
@@ -329,6 +365,9 @@ class EntityManager {
|
|
|
329
365
|
}
|
|
330
366
|
else {
|
|
331
367
|
const [entity] = this.hydrate(type, [row]);
|
|
368
|
+
if (this.#hasAnyDeletes && entity.isDeletedEntity) {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
332
371
|
if (populate) {
|
|
333
372
|
await this.populate(entity, populate);
|
|
334
373
|
}
|
|
@@ -344,17 +383,16 @@ class EntityManager {
|
|
|
344
383
|
* Note: this method is not currently auto-batched, so it will cause N+1s if called in a loop.
|
|
345
384
|
*/
|
|
346
385
|
async findCount(type, where, options = {}) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.catch(function findCount(err) {
|
|
386
|
+
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
387
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
388
|
+
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings).catch(function findCount(err) {
|
|
351
389
|
throw appendStack(err, new Error());
|
|
352
390
|
});
|
|
353
391
|
// If the user is do "count all", we can adjust the number up/down based on
|
|
354
392
|
// WIP creates/deletes. We can't do this if the WHERE clause is populated b/c
|
|
355
393
|
// then we'd also have to eval each created/deleted entity against the WHERE
|
|
356
394
|
// clause before knowing if it should adjust teh amount.
|
|
357
|
-
const isSelectAll =
|
|
395
|
+
const isSelectAll = (0, scopes_1.isSelectAllFilter)(where, options.conditions);
|
|
358
396
|
if (isSelectAll) {
|
|
359
397
|
const tagged = this.#entitiesByTag.get((0, index_1.getMetadata)(type).tagName) ?? [];
|
|
360
398
|
for (const entity of tagged) {
|
|
@@ -378,10 +416,9 @@ class EntityManager {
|
|
|
378
416
|
* as `em.find`.
|
|
379
417
|
*/
|
|
380
418
|
async findIds(type, where, options = {}) {
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.catch(function findIds(err) {
|
|
419
|
+
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
420
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
421
|
+
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings).catch(function findIds(err) {
|
|
385
422
|
throw appendStack(err, new Error());
|
|
386
423
|
});
|
|
387
424
|
}
|
|
@@ -485,6 +522,7 @@ class EntityManager {
|
|
|
485
522
|
case "primaryKey":
|
|
486
523
|
case "o2m":
|
|
487
524
|
case "m2m":
|
|
525
|
+
case "m2mEnum":
|
|
488
526
|
case "o2o":
|
|
489
527
|
case "lo2m":
|
|
490
528
|
return undefined;
|
|
@@ -613,6 +651,7 @@ class EntityManager {
|
|
|
613
651
|
else if (kind === "primitive" ||
|
|
614
652
|
kind === "primaryKey" ||
|
|
615
653
|
kind === "enum" ||
|
|
654
|
+
kind === "m2mEnum" ||
|
|
616
655
|
kind === "poly" ||
|
|
617
656
|
kind === "m2o" ||
|
|
618
657
|
kind === "lo2m") {
|
|
@@ -670,48 +709,86 @@ class EntityManager {
|
|
|
670
709
|
}
|
|
671
710
|
async loadAll(type, _ids, hint) {
|
|
672
711
|
const meta = (0, index_1.getMetadata)(type);
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
712
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
713
|
+
const ids = new Array(_ids.length);
|
|
714
|
+
const entities = new Array(_ids.length);
|
|
715
|
+
let idsToLoad;
|
|
716
|
+
let positionsToLoad;
|
|
717
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
718
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
719
|
+
ids[i] = id;
|
|
720
|
+
const entity = this.findExistingInstance(id);
|
|
721
|
+
if (entity) {
|
|
722
|
+
entities[i] = entity;
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
(idsToLoad ??= []).push(id);
|
|
726
|
+
(positionsToLoad ??= []).push(i);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
676
730
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
677
731
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
678
732
|
.catch(function loadAll(err) {
|
|
679
733
|
throw appendStack(err, new Error());
|
|
680
734
|
});
|
|
735
|
+
for (const i of positionsToLoad) {
|
|
736
|
+
entities[i] = this.findExistingInstance(ids[i]);
|
|
737
|
+
}
|
|
681
738
|
}
|
|
682
|
-
|
|
683
|
-
for (
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
739
|
+
let idsNotFound;
|
|
740
|
+
for (let i = 0; i < entities.length; i++) {
|
|
741
|
+
if (entities[i] === undefined) {
|
|
742
|
+
(idsNotFound ??= []).push(ids[i]);
|
|
743
|
+
}
|
|
687
744
|
}
|
|
688
|
-
if (
|
|
689
|
-
const idsNotFound = ids.filter((_, i) => entities[i] === undefined);
|
|
745
|
+
if (idsNotFound) {
|
|
690
746
|
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
691
747
|
}
|
|
748
|
+
const loadedEntities = entities;
|
|
692
749
|
if (hint) {
|
|
693
|
-
await this.populate(
|
|
750
|
+
await this.populate(loadedEntities, hint);
|
|
694
751
|
}
|
|
695
752
|
if (meta.inheritanceType === "sti" && meta.baseType) {
|
|
696
|
-
const wrongType =
|
|
753
|
+
const wrongType = loadedEntities.filter((e) => !(e instanceof meta.cstr));
|
|
697
754
|
if (wrongType.length > 0) {
|
|
698
755
|
throw new Error(`${wrongType.join(", ")} were not of type ${meta.cstr.name}`);
|
|
699
756
|
}
|
|
700
757
|
}
|
|
701
|
-
return
|
|
758
|
+
return loadedEntities;
|
|
702
759
|
}
|
|
703
760
|
async loadAllIfExists(type, _ids, hint) {
|
|
704
761
|
const meta = (0, index_1.getMetadata)(type);
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
762
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
763
|
+
const ids = new Array(_ids.length);
|
|
764
|
+
const entities = [];
|
|
765
|
+
let idsToLoad;
|
|
766
|
+
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
767
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
768
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
769
|
+
ids[i] = id;
|
|
770
|
+
const entity = this.findExistingInstance(id);
|
|
771
|
+
if (entity) {
|
|
772
|
+
entities.push(entity);
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
(idsToLoad ??= []).push(id);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
708
779
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
709
780
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
710
781
|
.catch(function loadAllIfExists(err) {
|
|
711
782
|
throw appendStack(err, new Error());
|
|
712
783
|
});
|
|
784
|
+
// Now that everything is loaded, recalc `entities`
|
|
785
|
+
entities.length = 0;
|
|
786
|
+
for (const id of ids) {
|
|
787
|
+
const entity = this.findExistingInstance(id);
|
|
788
|
+
if (entity)
|
|
789
|
+
entities.push(entity);
|
|
790
|
+
}
|
|
713
791
|
}
|
|
714
|
-
const entities = ids.map((id) => this.findExistingInstance(id)).filter(Boolean);
|
|
715
792
|
if (hint) {
|
|
716
793
|
await this.populate(entities, hint);
|
|
717
794
|
}
|
|
@@ -762,6 +839,12 @@ class EntityManager {
|
|
|
762
839
|
if (list.length === 0) {
|
|
763
840
|
return !fn ? entityOrList : fn(entityOrList);
|
|
764
841
|
}
|
|
842
|
+
// Avoid building a HintTree/batchloader for persisted entities when everything is already loaded; in the phase 9
|
|
843
|
+
// benchmark this moved already-loaded populates from ~1.10ms to ~0.89ms, and nested already-loaded populates from
|
|
844
|
+
// ~2.39ms to ~1.94ms.
|
|
845
|
+
if (!opts.forceReload && list.every((entity) => !entity.isNewEntity && (0, loadHints_1.isLoadedForPopulate)(entity, hintOpt))) {
|
|
846
|
+
return fn ? fn(entityOrList) : entityOrList;
|
|
847
|
+
}
|
|
765
848
|
const meta = (0, index_1.getMetadata)(list[0]);
|
|
766
849
|
if (this.#preloader) {
|
|
767
850
|
// If we can preload, prevent promise deadlocking by one large-batch preload populate (which can't have
|
|
@@ -810,9 +893,11 @@ class EntityManager {
|
|
|
810
893
|
delete(entityOrArray) {
|
|
811
894
|
for (const entity of (0, utils_1.toArray)(entityOrArray)) {
|
|
812
895
|
// Early return if already deleted.
|
|
813
|
-
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted(
|
|
896
|
+
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted();
|
|
814
897
|
if (!alreadyMarked)
|
|
815
898
|
continue;
|
|
899
|
+
// This monotonic flag lets find hot paths skip scanning results until a delete has ever happened in this EM.
|
|
900
|
+
this.#hasAnyDeletes = true;
|
|
816
901
|
// Any derived fields that read this entity will need recalc-d
|
|
817
902
|
this.#rm.queueAllDownstreamFields(entity, "deleted");
|
|
818
903
|
// Synchronously unhook the entity if the relations are loaded
|
|
@@ -906,7 +991,7 @@ class EntityManager {
|
|
|
906
991
|
const pendingHooks = new Set();
|
|
907
992
|
// Subset of pendingFlush entities that had hooks invoked in a prior `runHooksOnPendingEntities`
|
|
908
993
|
const alreadyRanHooks = new Set();
|
|
909
|
-
findPendingFlushEntities(this
|
|
994
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
910
995
|
// If we're re-looping for AsyncReactiveField, make sure to bump updatedAt
|
|
911
996
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
912
997
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
@@ -930,9 +1015,13 @@ class EntityManager {
|
|
|
930
1015
|
// fields, they'd be via the synchronous getter and would not be stale.
|
|
931
1016
|
recalcSynchronousDerivedFields(todos);
|
|
932
1017
|
// The hooks could have deleted this-loop or prior-loop entities, so re-cascade again.
|
|
933
|
-
|
|
934
|
-
//
|
|
935
|
-
|
|
1018
|
+
// Reactions (the recalc below) can themselves `em.delete` entities, so keep draining
|
|
1019
|
+
// until both deletes and recalcs have settled.
|
|
1020
|
+
do {
|
|
1021
|
+
await this.flushDeletes();
|
|
1022
|
+
// The hooks could have changed fields, so recalc again.
|
|
1023
|
+
await this.#rm.recalcPendingReactables("reactables");
|
|
1024
|
+
} while (this.#pendingDeletes.length > 0);
|
|
936
1025
|
// We may have reactables that failed earlier, but will succeed now that hooks have been run and cascade
|
|
937
1026
|
// deletes have been processed
|
|
938
1027
|
if (this.#rm.hasPendingTypeErrors) {
|
|
@@ -945,7 +1034,7 @@ class EntityManager {
|
|
|
945
1034
|
hooksInvoked.add(e);
|
|
946
1035
|
pendingHooks.clear();
|
|
947
1036
|
// See if the hooks mutated any new, not-yet-hooksInvoked entities
|
|
948
|
-
findPendingFlushEntities(this
|
|
1037
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
949
1038
|
// The final run of recalcPendingReactables could have left us with pending type errors and no entities in
|
|
950
1039
|
// pendingHooks. If so, we need to re-run recalcPendingTypeErrors to get those errors to transition into
|
|
951
1040
|
// suppressed errors so that we will fail after simpleValidation.
|
|
@@ -1004,7 +1093,7 @@ class EntityManager {
|
|
|
1004
1093
|
// The driver will handle the right thing if we're already in an existing transaction.
|
|
1005
1094
|
await this.driver.transaction(this, async () => {
|
|
1006
1095
|
do {
|
|
1007
|
-
if (Object.keys(entityTodos).length > 0 || Object.keys(joinRowTodos)) {
|
|
1096
|
+
if (Object.keys(entityTodos).length > 0 || Object.keys(joinRowTodos).length > 0) {
|
|
1008
1097
|
await this.driver.flush(this, entityTodos, joinRowTodos);
|
|
1009
1098
|
}
|
|
1010
1099
|
// Now that we've flushed, we can let plugins know what we've done.
|
|
@@ -1080,7 +1169,7 @@ class EntityManager {
|
|
|
1080
1169
|
for (const e of createdThenDeleted)
|
|
1081
1170
|
(0, BaseEntity_1.getInstanceData)(e).fixupCreatedThenDeleted();
|
|
1082
1171
|
this.#merging?.clear();
|
|
1083
|
-
return [...allFlushedEntities];
|
|
1172
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1084
1173
|
}
|
|
1085
1174
|
catch (e) {
|
|
1086
1175
|
if (e instanceof RecursiveCollection_1.RecursiveCycleError) {
|
|
@@ -1096,15 +1185,28 @@ class EntityManager {
|
|
|
1096
1185
|
}
|
|
1097
1186
|
}
|
|
1098
1187
|
}
|
|
1188
|
+
// node-pg keeps the DETAIL line (i.e. "Key (identity_id, final_id)=(102591, null) already exists")
|
|
1189
|
+
// on a separate `detail` field, so keep it to suffix onto messages to help debugging.
|
|
1190
|
+
const detail = e && typeof e === "object" && "detail" in e && typeof e.detail === "string" ? e.detail : undefined;
|
|
1099
1191
|
if (e && typeof e === "object" && "constraint" in e && typeof e.constraint === "string") {
|
|
1100
1192
|
// node-pg errors use `constraint` to indicate the constraint name
|
|
1101
1193
|
const message = config_1.constraintNameToValidationError[e.constraint];
|
|
1102
1194
|
if (message) {
|
|
1103
|
-
|
|
1195
|
+
const errors = new index_1.ValidationErrors(message);
|
|
1196
|
+
// Keep the user-facing `errors[].message` pretty, but suffix the DETAIL onto the top-level
|
|
1197
|
+
// `message` so the debugging context survives into logs/stack traces.
|
|
1198
|
+
if (detail)
|
|
1199
|
+
errors.message = `${message} — ${detail}`;
|
|
1200
|
+
throw errors;
|
|
1104
1201
|
}
|
|
1105
1202
|
}
|
|
1106
|
-
if (e instanceof InMemoryRollbackError)
|
|
1107
|
-
return [...allFlushedEntities];
|
|
1203
|
+
if (e instanceof InMemoryRollbackError) {
|
|
1204
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1205
|
+
}
|
|
1206
|
+
// For raw (non-pretty) pg errors, fold the DETAIL into `message` so it survives into logs.
|
|
1207
|
+
if (detail && e instanceof Error) {
|
|
1208
|
+
e.message = `${e.message} — ${detail}`;
|
|
1209
|
+
}
|
|
1108
1210
|
throw e;
|
|
1109
1211
|
}
|
|
1110
1212
|
finally {
|
|
@@ -1212,10 +1314,13 @@ class EntityManager {
|
|
|
1212
1314
|
*/
|
|
1213
1315
|
hydrate(type, rows, options) {
|
|
1214
1316
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1317
|
+
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1318
|
+
const overwriteExisting = options?.overwriteExisting === true;
|
|
1215
1319
|
let i = 0;
|
|
1216
1320
|
const entities = new Array(rows.length);
|
|
1217
1321
|
for (const row of rows) {
|
|
1218
|
-
const
|
|
1322
|
+
const id = row["id"];
|
|
1323
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : `${taggedIdPrefix}${id}`;
|
|
1219
1324
|
// See if this is already in our UoW
|
|
1220
1325
|
let entity = this.findExistingInstance(taggedId);
|
|
1221
1326
|
if (!entity) {
|
|
@@ -1224,23 +1329,36 @@ class EntityManager {
|
|
|
1224
1329
|
// Pass id as a hint that we're in hydrate mode
|
|
1225
1330
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1226
1331
|
(0, BaseEntity_1.getInstanceData)(entity).row = row;
|
|
1227
|
-
this.#doRegister(entity, taggedId);
|
|
1332
|
+
this.#doRegister(entity, taggedId, meta, true);
|
|
1228
1333
|
}
|
|
1229
|
-
else if (
|
|
1334
|
+
else if (overwriteExisting) {
|
|
1230
1335
|
// Usually if the entity already exists, we don't write over it, but in this case we assume that
|
|
1231
1336
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1232
1337
|
// First swap out the old row with the new row
|
|
1233
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1338
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1339
|
+
instanceData.row = row;
|
|
1234
1340
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1235
1341
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1236
|
-
const { data
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1342
|
+
const { data } = instanceData;
|
|
1343
|
+
const dataKeys = Object.keys(data);
|
|
1344
|
+
if (dataKeys.length > 0) {
|
|
1345
|
+
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1346
|
+
const changedFields = entity.changes.fieldsWithoutRelations;
|
|
1347
|
+
if (changedFields.length === 0) {
|
|
1348
|
+
for (const fieldName of dataKeys) {
|
|
1349
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1350
|
+
serde.setOnEntity(data, row);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
else {
|
|
1354
|
+
for (const fieldName of dataKeys) {
|
|
1355
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1356
|
+
serde.setOnEntity(data, row);
|
|
1357
|
+
// Make the field look not-dirty
|
|
1358
|
+
if (changedFields.includes(fieldName)) {
|
|
1359
|
+
instanceData.markFieldClean(fieldName);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1244
1362
|
}
|
|
1245
1363
|
}
|
|
1246
1364
|
}
|
|
@@ -1249,8 +1367,9 @@ class EntityManager {
|
|
|
1249
1367
|
return entities;
|
|
1250
1368
|
}
|
|
1251
1369
|
touch(entityOrEntities) {
|
|
1252
|
-
for (const entity of (0, utils_1.toArray)(entityOrEntities))
|
|
1253
|
-
(0, BaseEntity_1.getInstanceData)(entity).
|
|
1370
|
+
for (const entity of (0, utils_1.toArray)(entityOrEntities)) {
|
|
1371
|
+
(0, BaseEntity_1.getInstanceData)(entity).markTouched();
|
|
1372
|
+
}
|
|
1254
1373
|
}
|
|
1255
1374
|
async recalc(entityOrEntities) {
|
|
1256
1375
|
// Look for async reactive fields
|
|
@@ -1327,9 +1446,16 @@ class EntityManager {
|
|
|
1327
1446
|
// Run the beforeDelete hook before we unhook the entity
|
|
1328
1447
|
const todos = (0, Todo_1.createTodos)(entities);
|
|
1329
1448
|
await beforeDelete(this.ctx, todos);
|
|
1330
|
-
// For all relations, unhook the entity from the other side
|
|
1331
|
-
//
|
|
1332
|
-
|
|
1449
|
+
// For all relations, unhook the entity from the other side; this append path is optimized for
|
|
1450
|
+
// large deletes with ~100k relations by avoiding `flatMap` intermediates and `concat` copies.
|
|
1451
|
+
for (const entity of entities) {
|
|
1452
|
+
const relations = (0, index_1.getRelations)(entity);
|
|
1453
|
+
const start = relationsToCleanup.length;
|
|
1454
|
+
relationsToCleanup.length += relations.length;
|
|
1455
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1456
|
+
relationsToCleanup[start + i] = relations[i];
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1333
1459
|
entities = this.#pendingDeletes;
|
|
1334
1460
|
this.#pendingDeletes = [];
|
|
1335
1461
|
}
|
|
@@ -1385,9 +1511,9 @@ class EntityManager {
|
|
|
1385
1511
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1386
1512
|
}
|
|
1387
1513
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1388
|
-
this.#indexManager.enableIndexingForType(meta, entities);
|
|
1514
|
+
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1389
1515
|
return (this.#indexManager
|
|
1390
|
-
.findMatching(meta, where)
|
|
1516
|
+
.findMatching(meta, entities, where)
|
|
1391
1517
|
// Still filter by `instanceof cstr` to handle subtyping
|
|
1392
1518
|
.filter((e) => e instanceof cstr && !e.isDeletedEntity));
|
|
1393
1519
|
}
|
|
@@ -1487,7 +1613,8 @@ class EntityManager {
|
|
|
1487
1613
|
continue;
|
|
1488
1614
|
const { originalData: oldOriginalData, data: oldData } = oldInstanceData;
|
|
1489
1615
|
const newEntity = mapEntity(oldEntity);
|
|
1490
|
-
const
|
|
1616
|
+
const newInstanceData = newEntity.__data;
|
|
1617
|
+
const { data: newData } = newInstanceData;
|
|
1491
1618
|
// for new entities, anything in `data` is changed and should be copied across. for existing entities, we
|
|
1492
1619
|
// only care about changed fields, which are enumerated by originalData
|
|
1493
1620
|
const maybeEntity = (value) => ((0, Entity_1.isEntity)(value) ? mapEntity(value) : value);
|
|
@@ -1495,7 +1622,7 @@ class EntityManager {
|
|
|
1495
1622
|
for (const field of fields) {
|
|
1496
1623
|
// copy over originalData so .changes is consistent across ems
|
|
1497
1624
|
if (field in oldOriginalData)
|
|
1498
|
-
|
|
1625
|
+
newInstanceData.markFieldDirty(field, maybeEntity(oldOriginalData[field]));
|
|
1499
1626
|
newData[field] = maybeEntity(oldData[field]);
|
|
1500
1627
|
}
|
|
1501
1628
|
}
|
|
@@ -1505,7 +1632,7 @@ class EntityManager {
|
|
|
1505
1632
|
const newEntity = mapEntity(oldEntity);
|
|
1506
1633
|
if (oldEntity.isDeletedEntity) {
|
|
1507
1634
|
// If the old entity was deleted, that should be persisted in the new em
|
|
1508
|
-
newEntity.__data.markDeleted(
|
|
1635
|
+
newEntity.__data.markDeleted();
|
|
1509
1636
|
// deleted entities will fail if you try to `get` their relations, so skip them since they should be cleared
|
|
1510
1637
|
// out regardless
|
|
1511
1638
|
continue;
|
|
@@ -1650,11 +1777,11 @@ class EntityManager {
|
|
|
1650
1777
|
}
|
|
1651
1778
|
}
|
|
1652
1779
|
/** Registers a newly-instantiated entity with our EntityManager; only called by #doCreate and hydrate. */
|
|
1653
|
-
#doRegister(entity, id) {
|
|
1780
|
+
#doRegister(entity, id, meta, skipDuplicateCheck = false) {
|
|
1654
1781
|
// Keep our indexes up to date...
|
|
1655
1782
|
const maybeId = id ?? entity.idTaggedMaybe;
|
|
1656
1783
|
if (maybeId) {
|
|
1657
|
-
if (this.findExistingInstance(maybeId) !== undefined) {
|
|
1784
|
+
if (!skipDuplicateCheck && this.findExistingInstance(maybeId) !== undefined) {
|
|
1658
1785
|
throw new Error(`Entity ${entity} has a duplicate instance already loaded`);
|
|
1659
1786
|
}
|
|
1660
1787
|
this.#entitiesById.set(maybeId, entity);
|
|
@@ -1663,8 +1790,9 @@ class EntityManager {
|
|
|
1663
1790
|
// Also register by the `a#1` style tagged string for new entities
|
|
1664
1791
|
this.#entitiesById.set(entity.toTaggedString(), entity);
|
|
1665
1792
|
}
|
|
1793
|
+
(0, BaseEntity_1.getInstanceData)(entity).entityIndex = this.#entitiesArray.length;
|
|
1666
1794
|
this.#entitiesArray.push(entity);
|
|
1667
|
-
|
|
1795
|
+
meta ??= (0, index_1.getMetadata)(entity);
|
|
1668
1796
|
const set = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1669
1797
|
if (set.length === 0)
|
|
1670
1798
|
this.#entitiesByTag.set(meta.tagName, set);
|
|
@@ -1678,8 +1806,6 @@ class EntityManager {
|
|
|
1678
1806
|
.join(", ");
|
|
1679
1807
|
throw new Error(`More than ${this.entityLimit} entities have been instantiated (top entities: ${topTypes})`);
|
|
1680
1808
|
}
|
|
1681
|
-
// If indexing is enabled for this type, add it...
|
|
1682
|
-
this.#indexManager.maybeIndexEntity(entity);
|
|
1683
1809
|
}
|
|
1684
1810
|
}
|
|
1685
1811
|
exports.EntityManager = EntityManager;
|
|
@@ -1763,14 +1889,14 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1763
1889
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1764
1890
|
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1765
1891
|
// Find each statically-declared reactive rule for the given entity type
|
|
1766
|
-
const rules =
|
|
1892
|
+
const rules = todo.metadata.reactiveRules;
|
|
1767
1893
|
return rules.map((rule) => {
|
|
1768
1894
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1769
1895
|
const triggered = entities.filter((e) => {
|
|
1770
1896
|
// If the rule is for a different subtype, skip it
|
|
1771
1897
|
if (!(e instanceof rule.source))
|
|
1772
1898
|
return false;
|
|
1773
|
-
// Any new-or-deleted entity fires every rule (
|
|
1899
|
+
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1774
1900
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1775
1901
|
return true;
|
|
1776
1902
|
// Otherwise see if the changed fields overlaps with the rule's fields
|
|
@@ -1786,22 +1912,26 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1786
1912
|
});
|
|
1787
1913
|
});
|
|
1788
1914
|
const p2 = Object.values(joinRowTodos).flatMap((todo) => {
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1915
|
+
// For enum m2ms the columns include the enum's numeric id, so keep only the entity values.
|
|
1916
|
+
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns)).filter(Entity_1.isEntity);
|
|
1917
|
+
// The owning-entity side (applies to both entity-to-entity and entity-to-enum m2ms).
|
|
1918
|
+
const a = todo.m2m.meta
|
|
1919
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1793
1920
|
.map((rule) => {
|
|
1794
1921
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1795
1922
|
return followAndQueue(triggered, rule);
|
|
1796
1923
|
});
|
|
1797
|
-
//
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1924
|
+
// The "other" side only exists for entity-to-entity m2ms; enums have no reverse field.
|
|
1925
|
+
const { otherMeta, otherFieldName } = todo.m2m;
|
|
1926
|
+
const b = otherMeta
|
|
1927
|
+
? otherMeta
|
|
1928
|
+
.reactiveRules.filter((rule) => rule.fields.includes(otherFieldName))
|
|
1929
|
+
.map((rule) => {
|
|
1930
|
+
const triggered = entities.filter((e) => e instanceof otherMeta.cstr);
|
|
1931
|
+
return followAndQueue(triggered, rule);
|
|
1932
|
+
})
|
|
1933
|
+
: [];
|
|
1934
|
+
return [...a, ...b];
|
|
1805
1935
|
});
|
|
1806
1936
|
(0, utils_1.failIfAnyRejected)(await Promise.allSettled([...p1, ...p2]));
|
|
1807
1937
|
// Now that we've found the fn+entities to run, run them and collect any errors
|
|
@@ -1883,7 +2013,11 @@ function entitiesFromTodos(entityTodos, joinRowTodos) {
|
|
|
1883
2013
|
}
|
|
1884
2014
|
for (const todo of Object.values(joinRowTodos)) {
|
|
1885
2015
|
[...todo.newRows, ...todo.deletedRows].forEach((row) => {
|
|
1886
|
-
|
|
2016
|
+
// For enum m2ms, a column value is the enum's numeric id rather than an entity.
|
|
2017
|
+
Object.values(row.columns).forEach((value) => {
|
|
2018
|
+
if ((0, Entity_1.isEntity)(value))
|
|
2019
|
+
entities.add(value);
|
|
2020
|
+
});
|
|
1887
2021
|
});
|
|
1888
2022
|
}
|
|
1889
2023
|
return [...entities];
|
|
@@ -2025,7 +2159,7 @@ function maybeBumpUpdatedAt(rm, todos, now) {
|
|
|
2025
2159
|
// it has changed. This is technically true, but this will break the oplock SQL generation,
|
|
2026
2160
|
// so force the field to be dirty.
|
|
2027
2161
|
const orm = (0, BaseEntity_1.getInstanceData)(e);
|
|
2028
|
-
orm.
|
|
2162
|
+
orm.markFieldDirty(updatedAt, (0, fields_1.getField)(e, updatedAt));
|
|
2029
2163
|
const serde = todo.metadata.fields[updatedAt].serde;
|
|
2030
2164
|
orm.data[updatedAt] = serde.mapFromNow(now);
|
|
2031
2165
|
rm.queueDownstreamReactables(e, updatedAt);
|
|
@@ -2062,17 +2196,13 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2062
2196
|
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, row.id)} must be instantiated via a subtype`);
|
|
2063
2197
|
}
|
|
2064
2198
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2065
|
-
return maybeBaseMeta.
|
|
2199
|
+
return maybeBaseMeta.subTypesByType.get(row.__class) ?? maybeBaseMeta;
|
|
2066
2200
|
}
|
|
2067
2201
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2068
2202
|
// Look for the STI discriminator value
|
|
2069
2203
|
const baseMeta = (0, index_1.getBaseMeta)(maybeBaseMeta);
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
throw new Error("Discriminator field must be an enum");
|
|
2073
|
-
const columnName = field.serde.columns[0].columnName;
|
|
2074
|
-
const value = row[columnName];
|
|
2075
|
-
return baseMeta.subTypes.find((st) => st.stiDiscriminatorValue === value) ?? baseMeta;
|
|
2204
|
+
const value = row[baseMeta.stiDiscriminatorColumnName];
|
|
2205
|
+
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2076
2206
|
}
|
|
2077
2207
|
else {
|
|
2078
2208
|
throw new Error("Unknown inheritance type");
|
|
@@ -2081,7 +2211,7 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2081
2211
|
/** Sets the `Animal.type` enum to the right subtype value. */
|
|
2082
2212
|
function setStiDiscriminatorValue(baseMeta, entity) {
|
|
2083
2213
|
const typeName = entity.constructor.name;
|
|
2084
|
-
const st = baseMeta.
|
|
2214
|
+
const st = baseMeta.subTypesByType.get(typeName);
|
|
2085
2215
|
if (st) {
|
|
2086
2216
|
const field = baseMeta.fields[baseMeta.stiDiscriminatorField];
|
|
2087
2217
|
const code = field.enumDetailType.findById(st.stiDiscriminatorValue).code;
|
|
@@ -2091,19 +2221,25 @@ function setStiDiscriminatorValue(baseMeta, entity) {
|
|
|
2091
2221
|
entity[baseMeta.stiDiscriminatorField] = undefined;
|
|
2092
2222
|
}
|
|
2093
2223
|
}
|
|
2094
|
-
function findPendingFlushEntities(
|
|
2095
|
-
for (const e of
|
|
2096
|
-
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
else {
|
|
2101
|
-
alreadyRanHooks.add(e);
|
|
2102
|
-
}
|
|
2103
|
-
pendingFlush.add(e);
|
|
2224
|
+
function findPendingFlushEntities(maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks) {
|
|
2225
|
+
for (const e of maybePendingFlushEntities) {
|
|
2226
|
+
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation === "none") {
|
|
2227
|
+
maybePendingFlushEntities.delete(e);
|
|
2228
|
+
continue;
|
|
2104
2229
|
}
|
|
2230
|
+
if (!hooksInvoked.has(e)) {
|
|
2231
|
+
pendingHooks.add(e);
|
|
2232
|
+
}
|
|
2233
|
+
else {
|
|
2234
|
+
alreadyRanHooks.add(e);
|
|
2235
|
+
}
|
|
2236
|
+
pendingFlush.add(e);
|
|
2105
2237
|
}
|
|
2106
2238
|
}
|
|
2239
|
+
/** Returns true if the caller explicitly asked `find` to use SQL pagination. */
|
|
2240
|
+
function hasPaginationSettings(options) {
|
|
2241
|
+
return "limit" in options || "offset" in options;
|
|
2242
|
+
}
|
|
2107
2243
|
/** An error we throw to get knex to `ROLLBACK`, but then catch. */
|
|
2108
2244
|
class InMemoryRollbackError extends Error {
|
|
2109
2245
|
}
|