joist-core 2.2.0 → 2.3.0-next.2
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 +248 -124
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +48 -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 +5 -2
- 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 +2 -2
- 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) {
|
|
@@ -1103,8 +1192,9 @@ class EntityManager {
|
|
|
1103
1192
|
throw new index_1.ValidationErrors(message);
|
|
1104
1193
|
}
|
|
1105
1194
|
}
|
|
1106
|
-
if (e instanceof InMemoryRollbackError)
|
|
1107
|
-
return [...allFlushedEntities];
|
|
1195
|
+
if (e instanceof InMemoryRollbackError) {
|
|
1196
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1197
|
+
}
|
|
1108
1198
|
throw e;
|
|
1109
1199
|
}
|
|
1110
1200
|
finally {
|
|
@@ -1212,10 +1302,13 @@ class EntityManager {
|
|
|
1212
1302
|
*/
|
|
1213
1303
|
hydrate(type, rows, options) {
|
|
1214
1304
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1305
|
+
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1306
|
+
const overwriteExisting = options?.overwriteExisting === true;
|
|
1215
1307
|
let i = 0;
|
|
1216
1308
|
const entities = new Array(rows.length);
|
|
1217
1309
|
for (const row of rows) {
|
|
1218
|
-
const
|
|
1310
|
+
const id = row["id"];
|
|
1311
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : `${taggedIdPrefix}${id}`;
|
|
1219
1312
|
// See if this is already in our UoW
|
|
1220
1313
|
let entity = this.findExistingInstance(taggedId);
|
|
1221
1314
|
if (!entity) {
|
|
@@ -1224,23 +1317,36 @@ class EntityManager {
|
|
|
1224
1317
|
// Pass id as a hint that we're in hydrate mode
|
|
1225
1318
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1226
1319
|
(0, BaseEntity_1.getInstanceData)(entity).row = row;
|
|
1227
|
-
this.#doRegister(entity, taggedId);
|
|
1320
|
+
this.#doRegister(entity, taggedId, meta, true);
|
|
1228
1321
|
}
|
|
1229
|
-
else if (
|
|
1322
|
+
else if (overwriteExisting) {
|
|
1230
1323
|
// Usually if the entity already exists, we don't write over it, but in this case we assume that
|
|
1231
1324
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1232
1325
|
// First swap out the old row with the new row
|
|
1233
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1326
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1327
|
+
instanceData.row = row;
|
|
1234
1328
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1235
1329
|
// (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
|
-
|
|
1330
|
+
const { data } = instanceData;
|
|
1331
|
+
const dataKeys = Object.keys(data);
|
|
1332
|
+
if (dataKeys.length > 0) {
|
|
1333
|
+
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1334
|
+
const changedFields = entity.changes.fieldsWithoutRelations;
|
|
1335
|
+
if (changedFields.length === 0) {
|
|
1336
|
+
for (const fieldName of dataKeys) {
|
|
1337
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1338
|
+
serde.setOnEntity(data, row);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
for (const fieldName of dataKeys) {
|
|
1343
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1344
|
+
serde.setOnEntity(data, row);
|
|
1345
|
+
// Make the field look not-dirty
|
|
1346
|
+
if (changedFields.includes(fieldName)) {
|
|
1347
|
+
instanceData.markFieldClean(fieldName);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1244
1350
|
}
|
|
1245
1351
|
}
|
|
1246
1352
|
}
|
|
@@ -1249,8 +1355,9 @@ class EntityManager {
|
|
|
1249
1355
|
return entities;
|
|
1250
1356
|
}
|
|
1251
1357
|
touch(entityOrEntities) {
|
|
1252
|
-
for (const entity of (0, utils_1.toArray)(entityOrEntities))
|
|
1253
|
-
(0, BaseEntity_1.getInstanceData)(entity).
|
|
1358
|
+
for (const entity of (0, utils_1.toArray)(entityOrEntities)) {
|
|
1359
|
+
(0, BaseEntity_1.getInstanceData)(entity).markTouched();
|
|
1360
|
+
}
|
|
1254
1361
|
}
|
|
1255
1362
|
async recalc(entityOrEntities) {
|
|
1256
1363
|
// Look for async reactive fields
|
|
@@ -1327,9 +1434,16 @@ class EntityManager {
|
|
|
1327
1434
|
// Run the beforeDelete hook before we unhook the entity
|
|
1328
1435
|
const todos = (0, Todo_1.createTodos)(entities);
|
|
1329
1436
|
await beforeDelete(this.ctx, todos);
|
|
1330
|
-
// For all relations, unhook the entity from the other side
|
|
1331
|
-
//
|
|
1332
|
-
|
|
1437
|
+
// For all relations, unhook the entity from the other side; this append path is optimized for
|
|
1438
|
+
// large deletes with ~100k relations by avoiding `flatMap` intermediates and `concat` copies.
|
|
1439
|
+
for (const entity of entities) {
|
|
1440
|
+
const relations = (0, index_1.getRelations)(entity);
|
|
1441
|
+
const start = relationsToCleanup.length;
|
|
1442
|
+
relationsToCleanup.length += relations.length;
|
|
1443
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1444
|
+
relationsToCleanup[start + i] = relations[i];
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1333
1447
|
entities = this.#pendingDeletes;
|
|
1334
1448
|
this.#pendingDeletes = [];
|
|
1335
1449
|
}
|
|
@@ -1385,9 +1499,9 @@ class EntityManager {
|
|
|
1385
1499
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1386
1500
|
}
|
|
1387
1501
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1388
|
-
this.#indexManager.enableIndexingForType(meta, entities);
|
|
1502
|
+
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1389
1503
|
return (this.#indexManager
|
|
1390
|
-
.findMatching(meta, where)
|
|
1504
|
+
.findMatching(meta, entities, where)
|
|
1391
1505
|
// Still filter by `instanceof cstr` to handle subtyping
|
|
1392
1506
|
.filter((e) => e instanceof cstr && !e.isDeletedEntity));
|
|
1393
1507
|
}
|
|
@@ -1487,7 +1601,8 @@ class EntityManager {
|
|
|
1487
1601
|
continue;
|
|
1488
1602
|
const { originalData: oldOriginalData, data: oldData } = oldInstanceData;
|
|
1489
1603
|
const newEntity = mapEntity(oldEntity);
|
|
1490
|
-
const
|
|
1604
|
+
const newInstanceData = newEntity.__data;
|
|
1605
|
+
const { data: newData } = newInstanceData;
|
|
1491
1606
|
// for new entities, anything in `data` is changed and should be copied across. for existing entities, we
|
|
1492
1607
|
// only care about changed fields, which are enumerated by originalData
|
|
1493
1608
|
const maybeEntity = (value) => ((0, Entity_1.isEntity)(value) ? mapEntity(value) : value);
|
|
@@ -1495,7 +1610,7 @@ class EntityManager {
|
|
|
1495
1610
|
for (const field of fields) {
|
|
1496
1611
|
// copy over originalData so .changes is consistent across ems
|
|
1497
1612
|
if (field in oldOriginalData)
|
|
1498
|
-
|
|
1613
|
+
newInstanceData.markFieldDirty(field, maybeEntity(oldOriginalData[field]));
|
|
1499
1614
|
newData[field] = maybeEntity(oldData[field]);
|
|
1500
1615
|
}
|
|
1501
1616
|
}
|
|
@@ -1505,7 +1620,7 @@ class EntityManager {
|
|
|
1505
1620
|
const newEntity = mapEntity(oldEntity);
|
|
1506
1621
|
if (oldEntity.isDeletedEntity) {
|
|
1507
1622
|
// If the old entity was deleted, that should be persisted in the new em
|
|
1508
|
-
newEntity.__data.markDeleted(
|
|
1623
|
+
newEntity.__data.markDeleted();
|
|
1509
1624
|
// deleted entities will fail if you try to `get` their relations, so skip them since they should be cleared
|
|
1510
1625
|
// out regardless
|
|
1511
1626
|
continue;
|
|
@@ -1650,11 +1765,11 @@ class EntityManager {
|
|
|
1650
1765
|
}
|
|
1651
1766
|
}
|
|
1652
1767
|
/** Registers a newly-instantiated entity with our EntityManager; only called by #doCreate and hydrate. */
|
|
1653
|
-
#doRegister(entity, id) {
|
|
1768
|
+
#doRegister(entity, id, meta, skipDuplicateCheck = false) {
|
|
1654
1769
|
// Keep our indexes up to date...
|
|
1655
1770
|
const maybeId = id ?? entity.idTaggedMaybe;
|
|
1656
1771
|
if (maybeId) {
|
|
1657
|
-
if (this.findExistingInstance(maybeId) !== undefined) {
|
|
1772
|
+
if (!skipDuplicateCheck && this.findExistingInstance(maybeId) !== undefined) {
|
|
1658
1773
|
throw new Error(`Entity ${entity} has a duplicate instance already loaded`);
|
|
1659
1774
|
}
|
|
1660
1775
|
this.#entitiesById.set(maybeId, entity);
|
|
@@ -1663,8 +1778,9 @@ class EntityManager {
|
|
|
1663
1778
|
// Also register by the `a#1` style tagged string for new entities
|
|
1664
1779
|
this.#entitiesById.set(entity.toTaggedString(), entity);
|
|
1665
1780
|
}
|
|
1781
|
+
(0, BaseEntity_1.getInstanceData)(entity).entityIndex = this.#entitiesArray.length;
|
|
1666
1782
|
this.#entitiesArray.push(entity);
|
|
1667
|
-
|
|
1783
|
+
meta ??= (0, index_1.getMetadata)(entity);
|
|
1668
1784
|
const set = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1669
1785
|
if (set.length === 0)
|
|
1670
1786
|
this.#entitiesByTag.set(meta.tagName, set);
|
|
@@ -1678,8 +1794,6 @@ class EntityManager {
|
|
|
1678
1794
|
.join(", ");
|
|
1679
1795
|
throw new Error(`More than ${this.entityLimit} entities have been instantiated (top entities: ${topTypes})`);
|
|
1680
1796
|
}
|
|
1681
|
-
// If indexing is enabled for this type, add it...
|
|
1682
|
-
this.#indexManager.maybeIndexEntity(entity);
|
|
1683
1797
|
}
|
|
1684
1798
|
}
|
|
1685
1799
|
exports.EntityManager = EntityManager;
|
|
@@ -1763,14 +1877,14 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1763
1877
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1764
1878
|
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1765
1879
|
// Find each statically-declared reactive rule for the given entity type
|
|
1766
|
-
const rules =
|
|
1880
|
+
const rules = todo.metadata.reactiveRules;
|
|
1767
1881
|
return rules.map((rule) => {
|
|
1768
1882
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1769
1883
|
const triggered = entities.filter((e) => {
|
|
1770
1884
|
// If the rule is for a different subtype, skip it
|
|
1771
1885
|
if (!(e instanceof rule.source))
|
|
1772
1886
|
return false;
|
|
1773
|
-
// Any new-or-deleted entity fires every rule (
|
|
1887
|
+
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1774
1888
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1775
1889
|
return true;
|
|
1776
1890
|
// Otherwise see if the changed fields overlaps with the rule's fields
|
|
@@ -1786,22 +1900,26 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1786
1900
|
});
|
|
1787
1901
|
});
|
|
1788
1902
|
const p2 = Object.values(joinRowTodos).flatMap((todo) => {
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1903
|
+
// For enum m2ms the columns include the enum's numeric id, so keep only the entity values.
|
|
1904
|
+
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns)).filter(Entity_1.isEntity);
|
|
1905
|
+
// The owning-entity side (applies to both entity-to-entity and entity-to-enum m2ms).
|
|
1906
|
+
const a = todo.m2m.meta
|
|
1907
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1793
1908
|
.map((rule) => {
|
|
1794
1909
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1795
1910
|
return followAndQueue(triggered, rule);
|
|
1796
1911
|
});
|
|
1797
|
-
//
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1912
|
+
// The "other" side only exists for entity-to-entity m2ms; enums have no reverse field.
|
|
1913
|
+
const { otherMeta, otherFieldName } = todo.m2m;
|
|
1914
|
+
const b = otherMeta
|
|
1915
|
+
? otherMeta
|
|
1916
|
+
.reactiveRules.filter((rule) => rule.fields.includes(otherFieldName))
|
|
1917
|
+
.map((rule) => {
|
|
1918
|
+
const triggered = entities.filter((e) => e instanceof otherMeta.cstr);
|
|
1919
|
+
return followAndQueue(triggered, rule);
|
|
1920
|
+
})
|
|
1921
|
+
: [];
|
|
1922
|
+
return [...a, ...b];
|
|
1805
1923
|
});
|
|
1806
1924
|
(0, utils_1.failIfAnyRejected)(await Promise.allSettled([...p1, ...p2]));
|
|
1807
1925
|
// Now that we've found the fn+entities to run, run them and collect any errors
|
|
@@ -1883,7 +2001,11 @@ function entitiesFromTodos(entityTodos, joinRowTodos) {
|
|
|
1883
2001
|
}
|
|
1884
2002
|
for (const todo of Object.values(joinRowTodos)) {
|
|
1885
2003
|
[...todo.newRows, ...todo.deletedRows].forEach((row) => {
|
|
1886
|
-
|
|
2004
|
+
// For enum m2ms, a column value is the enum's numeric id rather than an entity.
|
|
2005
|
+
Object.values(row.columns).forEach((value) => {
|
|
2006
|
+
if ((0, Entity_1.isEntity)(value))
|
|
2007
|
+
entities.add(value);
|
|
2008
|
+
});
|
|
1887
2009
|
});
|
|
1888
2010
|
}
|
|
1889
2011
|
return [...entities];
|
|
@@ -2025,7 +2147,7 @@ function maybeBumpUpdatedAt(rm, todos, now) {
|
|
|
2025
2147
|
// it has changed. This is technically true, but this will break the oplock SQL generation,
|
|
2026
2148
|
// so force the field to be dirty.
|
|
2027
2149
|
const orm = (0, BaseEntity_1.getInstanceData)(e);
|
|
2028
|
-
orm.
|
|
2150
|
+
orm.markFieldDirty(updatedAt, (0, fields_1.getField)(e, updatedAt));
|
|
2029
2151
|
const serde = todo.metadata.fields[updatedAt].serde;
|
|
2030
2152
|
orm.data[updatedAt] = serde.mapFromNow(now);
|
|
2031
2153
|
rm.queueDownstreamReactables(e, updatedAt);
|
|
@@ -2062,17 +2184,13 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2062
2184
|
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, row.id)} must be instantiated via a subtype`);
|
|
2063
2185
|
}
|
|
2064
2186
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2065
|
-
return maybeBaseMeta.
|
|
2187
|
+
return maybeBaseMeta.subTypesByType.get(row.__class) ?? maybeBaseMeta;
|
|
2066
2188
|
}
|
|
2067
2189
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2068
2190
|
// Look for the STI discriminator value
|
|
2069
2191
|
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;
|
|
2192
|
+
const value = row[baseMeta.stiDiscriminatorColumnName];
|
|
2193
|
+
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2076
2194
|
}
|
|
2077
2195
|
else {
|
|
2078
2196
|
throw new Error("Unknown inheritance type");
|
|
@@ -2081,7 +2199,7 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2081
2199
|
/** Sets the `Animal.type` enum to the right subtype value. */
|
|
2082
2200
|
function setStiDiscriminatorValue(baseMeta, entity) {
|
|
2083
2201
|
const typeName = entity.constructor.name;
|
|
2084
|
-
const st = baseMeta.
|
|
2202
|
+
const st = baseMeta.subTypesByType.get(typeName);
|
|
2085
2203
|
if (st) {
|
|
2086
2204
|
const field = baseMeta.fields[baseMeta.stiDiscriminatorField];
|
|
2087
2205
|
const code = field.enumDetailType.findById(st.stiDiscriminatorValue).code;
|
|
@@ -2091,19 +2209,25 @@ function setStiDiscriminatorValue(baseMeta, entity) {
|
|
|
2091
2209
|
entity[baseMeta.stiDiscriminatorField] = undefined;
|
|
2092
2210
|
}
|
|
2093
2211
|
}
|
|
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);
|
|
2212
|
+
function findPendingFlushEntities(maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks) {
|
|
2213
|
+
for (const e of maybePendingFlushEntities) {
|
|
2214
|
+
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation === "none") {
|
|
2215
|
+
maybePendingFlushEntities.delete(e);
|
|
2216
|
+
continue;
|
|
2104
2217
|
}
|
|
2218
|
+
if (!hooksInvoked.has(e)) {
|
|
2219
|
+
pendingHooks.add(e);
|
|
2220
|
+
}
|
|
2221
|
+
else {
|
|
2222
|
+
alreadyRanHooks.add(e);
|
|
2223
|
+
}
|
|
2224
|
+
pendingFlush.add(e);
|
|
2105
2225
|
}
|
|
2106
2226
|
}
|
|
2227
|
+
/** Returns true if the caller explicitly asked `find` to use SQL pagination. */
|
|
2228
|
+
function hasPaginationSettings(options) {
|
|
2229
|
+
return "limit" in options || "offset" in options;
|
|
2230
|
+
}
|
|
2107
2231
|
/** An error we throw to get knex to `ROLLBACK`, but then catch. */
|
|
2108
2232
|
class InMemoryRollbackError extends Error {
|
|
2109
2233
|
}
|