joist-core 2.2.0-next.4 → 2.2.0-next.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/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 +27 -8
- package/build/EntityFilter.d.ts.map +1 -1
- package/build/EntityFilter.js +18 -0
- package/build/EntityFilter.js.map +1 -1
- package/build/EntityManager.d.ts +14 -10
- package/build/EntityManager.d.ts.map +1 -1
- package/build/EntityManager.js +184 -90
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +20 -1
- package/build/EntityMetadata.d.ts.map +1 -1
- package/build/EntityMetadata.js +13 -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 +36 -3
- 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.js +1 -1
- package/build/JoinRows.js.map +1 -1
- package/build/QueryParser.d.ts +6 -1
- package/build/QueryParser.d.ts.map +1 -1
- package/build/QueryParser.js +265 -190
- package/build/QueryParser.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 +16 -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/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/manyToManyBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/manyToManyBatchLoader.js +1 -1
- 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/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 +12 -2
- package/build/dataloaders/findCountDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +46 -4
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.d.ts +14 -1
- package/build/dataloaders/findDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findDataLoader.js +53 -44
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.js +5 -3
- 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.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +11 -5
- package/build/dataloaders/findPaginatedDataLoader.js.map +1 -1
- package/build/drivers/EntityWriter.js +13 -7
- package/build/drivers/EntityWriter.js.map +1 -1
- package/build/fields.d.ts.map +1 -1
- package/build/fields.js +16 -6
- package/build/fields.js.map +1 -1
- package/build/index.d.ts +2 -0
- 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 +9 -0
- 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/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/relations/ManyToManyCollection.d.ts.map +1 -1
- package/build/relations/ManyToManyCollection.js +2 -2
- package/build/relations/ManyToManyCollection.js.map +1 -1
- package/build/relations/ManyToManyLargeCollection.d.ts.map +1 -1
- package/build/relations/ManyToManyLargeCollection.js +2 -1
- 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.map +1 -1
- package/build/relations/ReactiveManyToMany.js +1 -1
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.d.ts.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js +1 -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/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/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 +3 -5
- 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");
|
|
@@ -43,6 +42,7 @@ const FlushLock_1 = require("./FlushLock");
|
|
|
43
42
|
const index_1 = require("./index");
|
|
44
43
|
const IsLoadedCache_1 = require("./IsLoadedCache");
|
|
45
44
|
const JoinRows_1 = require("./JoinRows");
|
|
45
|
+
const loadHints_1 = require("./loadHints");
|
|
46
46
|
const newEntity_1 = require("./newEntity");
|
|
47
47
|
const newTestInstance_1 = require("./newTestInstance");
|
|
48
48
|
const PluginManager_1 = require("./PluginManager");
|
|
@@ -52,6 +52,7 @@ const relations_1 = require("./relations");
|
|
|
52
52
|
const AsyncProperty_1 = require("./relations/AsyncProperty");
|
|
53
53
|
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
54
54
|
const RecursiveCollection_1 = require("./relations/RecursiveCollection");
|
|
55
|
+
const scopes_1 = require("./scopes");
|
|
55
56
|
const Todo_1 = require("./Todo");
|
|
56
57
|
const trusted_1 = require("./trusted");
|
|
57
58
|
const upsert_1 = require("./upsert");
|
|
@@ -88,6 +89,8 @@ class EntityManager {
|
|
|
88
89
|
txn;
|
|
89
90
|
entityLimit = defaultEntityLimit;
|
|
90
91
|
#entitiesArray = [];
|
|
92
|
+
// Incrementally track dirty entities so we don't have to scan `#entityArray` during flush
|
|
93
|
+
#maybePendingFlushEntities = new Set();
|
|
91
94
|
// Indexes the currently loaded entities by their tagged ids and `toTaggedString` ids (i.e. `a#`). This fixes
|
|
92
95
|
// real-world performance issues where `findExistingInstance` scanning `#entities` was an `O(n^2)`.
|
|
93
96
|
#entitiesById = new Map();
|
|
@@ -106,6 +109,7 @@ class EntityManager {
|
|
|
106
109
|
* so that both see the most accurate state.
|
|
107
110
|
*/
|
|
108
111
|
#pendingDeletes = [];
|
|
112
|
+
#hasAnyDeletes = false;
|
|
109
113
|
#dataloaders = {};
|
|
110
114
|
#batchLoaders = {};
|
|
111
115
|
#joinRows = {};
|
|
@@ -155,6 +159,20 @@ class EntityManager {
|
|
|
155
159
|
indexManager: this.#indexManager,
|
|
156
160
|
isLoadedCache: this.#isLoadedCache,
|
|
157
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
|
+
},
|
|
158
176
|
isMerging(entity) {
|
|
159
177
|
return em.#merging?.has(entity) ?? false;
|
|
160
178
|
},
|
|
@@ -247,11 +265,11 @@ class EntityManager {
|
|
|
247
265
|
}
|
|
248
266
|
async find(type, where, options) {
|
|
249
267
|
const { populate, ...rest } = options || {};
|
|
250
|
-
const
|
|
251
|
-
const
|
|
268
|
+
const normalized = (0, index_1.mergeFindOptions)(where, rest);
|
|
269
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
270
|
+
const result = await (hasPaginationSettings(normalized.options)
|
|
252
271
|
? (0, findPaginatedDataLoader_1.findPaginatedDataLoader)(this, type, settings, populate)
|
|
253
|
-
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate))
|
|
254
|
-
.catch(function find(err) {
|
|
272
|
+
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)).catch(function find(err) {
|
|
255
273
|
throw appendStack(err, new Error());
|
|
256
274
|
});
|
|
257
275
|
if (populate) {
|
|
@@ -346,6 +364,9 @@ class EntityManager {
|
|
|
346
364
|
}
|
|
347
365
|
else {
|
|
348
366
|
const [entity] = this.hydrate(type, [row]);
|
|
367
|
+
if (this.#hasAnyDeletes && entity.isDeletedEntity) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
349
370
|
if (populate) {
|
|
350
371
|
await this.populate(entity, populate);
|
|
351
372
|
}
|
|
@@ -361,16 +382,16 @@ class EntityManager {
|
|
|
361
382
|
* Note: this method is not currently auto-batched, so it will cause N+1s if called in a loop.
|
|
362
383
|
*/
|
|
363
384
|
async findCount(type, where, options = {}) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
385
|
+
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
386
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
387
|
+
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings).catch(function findCount(err) {
|
|
367
388
|
throw appendStack(err, new Error());
|
|
368
389
|
});
|
|
369
390
|
// If the user is do "count all", we can adjust the number up/down based on
|
|
370
391
|
// WIP creates/deletes. We can't do this if the WHERE clause is populated b/c
|
|
371
392
|
// then we'd also have to eval each created/deleted entity against the WHERE
|
|
372
393
|
// clause before knowing if it should adjust teh amount.
|
|
373
|
-
const isSelectAll =
|
|
394
|
+
const isSelectAll = (0, scopes_1.isSelectAllFilter)(where, options.conditions);
|
|
374
395
|
if (isSelectAll) {
|
|
375
396
|
const tagged = this.#entitiesByTag.get((0, index_1.getMetadata)(type).tagName) ?? [];
|
|
376
397
|
for (const entity of tagged) {
|
|
@@ -394,9 +415,9 @@ class EntityManager {
|
|
|
394
415
|
* as `em.find`.
|
|
395
416
|
*/
|
|
396
417
|
async findIds(type, where, options = {}) {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
418
|
+
const normalized = (0, findCountDataLoader_1.mergeCountOptions)(where, options);
|
|
419
|
+
const settings = { where: normalized.where, ...normalized.options };
|
|
420
|
+
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings).catch(function findIds(err) {
|
|
400
421
|
throw appendStack(err, new Error());
|
|
401
422
|
});
|
|
402
423
|
}
|
|
@@ -685,48 +706,86 @@ class EntityManager {
|
|
|
685
706
|
}
|
|
686
707
|
async loadAll(type, _ids, hint) {
|
|
687
708
|
const meta = (0, index_1.getMetadata)(type);
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
709
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
710
|
+
const ids = new Array(_ids.length);
|
|
711
|
+
const entities = new Array(_ids.length);
|
|
712
|
+
let idsToLoad;
|
|
713
|
+
let positionsToLoad;
|
|
714
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
715
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
716
|
+
ids[i] = id;
|
|
717
|
+
const entity = this.findExistingInstance(id);
|
|
718
|
+
if (entity) {
|
|
719
|
+
entities[i] = entity;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
(idsToLoad ??= []).push(id);
|
|
723
|
+
(positionsToLoad ??= []).push(i);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
691
727
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
692
728
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
693
729
|
.catch(function loadAll(err) {
|
|
694
730
|
throw appendStack(err, new Error());
|
|
695
731
|
});
|
|
732
|
+
for (const i of positionsToLoad) {
|
|
733
|
+
entities[i] = this.findExistingInstance(ids[i]);
|
|
734
|
+
}
|
|
696
735
|
}
|
|
697
|
-
|
|
698
|
-
for (
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
736
|
+
let idsNotFound;
|
|
737
|
+
for (let i = 0; i < entities.length; i++) {
|
|
738
|
+
if (entities[i] === undefined) {
|
|
739
|
+
(idsNotFound ??= []).push(ids[i]);
|
|
740
|
+
}
|
|
702
741
|
}
|
|
703
|
-
if (
|
|
704
|
-
const idsNotFound = ids.filter((_, i) => entities[i] === undefined);
|
|
742
|
+
if (idsNotFound) {
|
|
705
743
|
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
706
744
|
}
|
|
745
|
+
const loadedEntities = entities;
|
|
707
746
|
if (hint) {
|
|
708
|
-
await this.populate(
|
|
747
|
+
await this.populate(loadedEntities, hint);
|
|
709
748
|
}
|
|
710
749
|
if (meta.inheritanceType === "sti" && meta.baseType) {
|
|
711
|
-
const wrongType =
|
|
750
|
+
const wrongType = loadedEntities.filter((e) => !(e instanceof meta.cstr));
|
|
712
751
|
if (wrongType.length > 0) {
|
|
713
752
|
throw new Error(`${wrongType.join(", ")} were not of type ${meta.cstr.name}`);
|
|
714
753
|
}
|
|
715
754
|
}
|
|
716
|
-
return
|
|
755
|
+
return loadedEntities;
|
|
717
756
|
}
|
|
718
757
|
async loadAllIfExists(type, _ids, hint) {
|
|
719
758
|
const meta = (0, index_1.getMetadata)(type);
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
|
|
759
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
760
|
+
const ids = new Array(_ids.length);
|
|
761
|
+
const entities = [];
|
|
762
|
+
let idsToLoad;
|
|
763
|
+
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
764
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
765
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
766
|
+
ids[i] = id;
|
|
767
|
+
const entity = this.findExistingInstance(id);
|
|
768
|
+
if (entity) {
|
|
769
|
+
entities.push(entity);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
(idsToLoad ??= []).push(id);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
723
776
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
724
777
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
725
778
|
.catch(function loadAllIfExists(err) {
|
|
726
779
|
throw appendStack(err, new Error());
|
|
727
780
|
});
|
|
781
|
+
// Now that everything is loaded, recalc `entities`
|
|
782
|
+
entities.length = 0;
|
|
783
|
+
for (const id of ids) {
|
|
784
|
+
const entity = this.findExistingInstance(id);
|
|
785
|
+
if (entity)
|
|
786
|
+
entities.push(entity);
|
|
787
|
+
}
|
|
728
788
|
}
|
|
729
|
-
const entities = ids.map((id) => this.findExistingInstance(id)).filter(Boolean);
|
|
730
789
|
if (hint) {
|
|
731
790
|
await this.populate(entities, hint);
|
|
732
791
|
}
|
|
@@ -777,6 +836,12 @@ class EntityManager {
|
|
|
777
836
|
if (list.length === 0) {
|
|
778
837
|
return !fn ? entityOrList : fn(entityOrList);
|
|
779
838
|
}
|
|
839
|
+
// Avoid building a HintTree/batchloader for persisted entities when everything is already loaded; in the phase 9
|
|
840
|
+
// benchmark this moved already-loaded populates from ~1.10ms to ~0.89ms, and nested already-loaded populates from
|
|
841
|
+
// ~2.39ms to ~1.94ms.
|
|
842
|
+
if (!opts.forceReload && list.every((entity) => !entity.isNewEntity && (0, loadHints_1.isLoadedForPopulate)(entity, hintOpt))) {
|
|
843
|
+
return fn ? fn(entityOrList) : entityOrList;
|
|
844
|
+
}
|
|
780
845
|
const meta = (0, index_1.getMetadata)(list[0]);
|
|
781
846
|
if (this.#preloader) {
|
|
782
847
|
// If we can preload, prevent promise deadlocking by one large-batch preload populate (which can't have
|
|
@@ -825,9 +890,11 @@ class EntityManager {
|
|
|
825
890
|
delete(entityOrArray) {
|
|
826
891
|
for (const entity of (0, utils_1.toArray)(entityOrArray)) {
|
|
827
892
|
// Early return if already deleted.
|
|
828
|
-
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted(
|
|
893
|
+
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted();
|
|
829
894
|
if (!alreadyMarked)
|
|
830
895
|
continue;
|
|
896
|
+
// This monotonic flag lets find hot paths skip scanning results until a delete has ever happened in this EM.
|
|
897
|
+
this.#hasAnyDeletes = true;
|
|
831
898
|
// Any derived fields that read this entity will need recalc-d
|
|
832
899
|
this.#rm.queueAllDownstreamFields(entity, "deleted");
|
|
833
900
|
// Synchronously unhook the entity if the relations are loaded
|
|
@@ -921,7 +988,7 @@ class EntityManager {
|
|
|
921
988
|
const pendingHooks = new Set();
|
|
922
989
|
// Subset of pendingFlush entities that had hooks invoked in a prior `runHooksOnPendingEntities`
|
|
923
990
|
const alreadyRanHooks = new Set();
|
|
924
|
-
findPendingFlushEntities(this
|
|
991
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
925
992
|
// If we're re-looping for AsyncReactiveField, make sure to bump updatedAt
|
|
926
993
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
927
994
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
@@ -945,9 +1012,13 @@ class EntityManager {
|
|
|
945
1012
|
// fields, they'd be via the synchronous getter and would not be stale.
|
|
946
1013
|
recalcSynchronousDerivedFields(todos);
|
|
947
1014
|
// The hooks could have deleted this-loop or prior-loop entities, so re-cascade again.
|
|
948
|
-
|
|
949
|
-
//
|
|
950
|
-
|
|
1015
|
+
// Reactions (the recalc below) can themselves `em.delete` entities, so keep draining
|
|
1016
|
+
// until both deletes and recalcs have settled.
|
|
1017
|
+
do {
|
|
1018
|
+
await this.flushDeletes();
|
|
1019
|
+
// The hooks could have changed fields, so recalc again.
|
|
1020
|
+
await this.#rm.recalcPendingReactables("reactables");
|
|
1021
|
+
} while (this.#pendingDeletes.length > 0);
|
|
951
1022
|
// We may have reactables that failed earlier, but will succeed now that hooks have been run and cascade
|
|
952
1023
|
// deletes have been processed
|
|
953
1024
|
if (this.#rm.hasPendingTypeErrors) {
|
|
@@ -960,7 +1031,7 @@ class EntityManager {
|
|
|
960
1031
|
hooksInvoked.add(e);
|
|
961
1032
|
pendingHooks.clear();
|
|
962
1033
|
// See if the hooks mutated any new, not-yet-hooksInvoked entities
|
|
963
|
-
findPendingFlushEntities(this
|
|
1034
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
964
1035
|
// The final run of recalcPendingReactables could have left us with pending type errors and no entities in
|
|
965
1036
|
// pendingHooks. If so, we need to re-run recalcPendingTypeErrors to get those errors to transition into
|
|
966
1037
|
// suppressed errors so that we will fail after simpleValidation.
|
|
@@ -1095,7 +1166,7 @@ class EntityManager {
|
|
|
1095
1166
|
for (const e of createdThenDeleted)
|
|
1096
1167
|
(0, BaseEntity_1.getInstanceData)(e).fixupCreatedThenDeleted();
|
|
1097
1168
|
this.#merging?.clear();
|
|
1098
|
-
return [...allFlushedEntities];
|
|
1169
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1099
1170
|
}
|
|
1100
1171
|
catch (e) {
|
|
1101
1172
|
if (e instanceof RecursiveCollection_1.RecursiveCycleError) {
|
|
@@ -1118,8 +1189,9 @@ class EntityManager {
|
|
|
1118
1189
|
throw new index_1.ValidationErrors(message);
|
|
1119
1190
|
}
|
|
1120
1191
|
}
|
|
1121
|
-
if (e instanceof InMemoryRollbackError)
|
|
1122
|
-
return [...allFlushedEntities];
|
|
1192
|
+
if (e instanceof InMemoryRollbackError) {
|
|
1193
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1194
|
+
}
|
|
1123
1195
|
throw e;
|
|
1124
1196
|
}
|
|
1125
1197
|
finally {
|
|
@@ -1227,10 +1299,13 @@ class EntityManager {
|
|
|
1227
1299
|
*/
|
|
1228
1300
|
hydrate(type, rows, options) {
|
|
1229
1301
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1302
|
+
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1303
|
+
const overwriteExisting = options?.overwriteExisting === true;
|
|
1230
1304
|
let i = 0;
|
|
1231
1305
|
const entities = new Array(rows.length);
|
|
1232
1306
|
for (const row of rows) {
|
|
1233
|
-
const
|
|
1307
|
+
const id = row["id"];
|
|
1308
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : `${taggedIdPrefix}${id}`;
|
|
1234
1309
|
// See if this is already in our UoW
|
|
1235
1310
|
let entity = this.findExistingInstance(taggedId);
|
|
1236
1311
|
if (!entity) {
|
|
@@ -1239,23 +1314,36 @@ class EntityManager {
|
|
|
1239
1314
|
// Pass id as a hint that we're in hydrate mode
|
|
1240
1315
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1241
1316
|
(0, BaseEntity_1.getInstanceData)(entity).row = row;
|
|
1242
|
-
this.#doRegister(entity, taggedId);
|
|
1317
|
+
this.#doRegister(entity, taggedId, meta, true);
|
|
1243
1318
|
}
|
|
1244
|
-
else if (
|
|
1319
|
+
else if (overwriteExisting) {
|
|
1245
1320
|
// Usually if the entity already exists, we don't write over it, but in this case we assume that
|
|
1246
1321
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1247
1322
|
// First swap out the old row with the new row
|
|
1248
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1323
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1324
|
+
instanceData.row = row;
|
|
1249
1325
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1250
1326
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1251
|
-
const { data
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1327
|
+
const { data } = instanceData;
|
|
1328
|
+
const dataKeys = Object.keys(data);
|
|
1329
|
+
if (dataKeys.length > 0) {
|
|
1330
|
+
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1331
|
+
const changedFields = entity.changes.fieldsWithoutRelations;
|
|
1332
|
+
if (changedFields.length === 0) {
|
|
1333
|
+
for (const fieldName of dataKeys) {
|
|
1334
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1335
|
+
serde.setOnEntity(data, row);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
for (const fieldName of dataKeys) {
|
|
1340
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1341
|
+
serde.setOnEntity(data, row);
|
|
1342
|
+
// Make the field look not-dirty
|
|
1343
|
+
if (changedFields.includes(fieldName)) {
|
|
1344
|
+
instanceData.markFieldClean(fieldName);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1259
1347
|
}
|
|
1260
1348
|
}
|
|
1261
1349
|
}
|
|
@@ -1264,8 +1352,9 @@ class EntityManager {
|
|
|
1264
1352
|
return entities;
|
|
1265
1353
|
}
|
|
1266
1354
|
touch(entityOrEntities) {
|
|
1267
|
-
for (const entity of (0, utils_1.toArray)(entityOrEntities))
|
|
1268
|
-
(0, BaseEntity_1.getInstanceData)(entity).
|
|
1355
|
+
for (const entity of (0, utils_1.toArray)(entityOrEntities)) {
|
|
1356
|
+
(0, BaseEntity_1.getInstanceData)(entity).markTouched();
|
|
1357
|
+
}
|
|
1269
1358
|
}
|
|
1270
1359
|
async recalc(entityOrEntities) {
|
|
1271
1360
|
// Look for async reactive fields
|
|
@@ -1342,9 +1431,16 @@ class EntityManager {
|
|
|
1342
1431
|
// Run the beforeDelete hook before we unhook the entity
|
|
1343
1432
|
const todos = (0, Todo_1.createTodos)(entities);
|
|
1344
1433
|
await beforeDelete(this.ctx, todos);
|
|
1345
|
-
// For all relations, unhook the entity from the other side
|
|
1346
|
-
//
|
|
1347
|
-
|
|
1434
|
+
// For all relations, unhook the entity from the other side; this append path is optimized for
|
|
1435
|
+
// large deletes with ~100k relations by avoiding `flatMap` intermediates and `concat` copies.
|
|
1436
|
+
for (const entity of entities) {
|
|
1437
|
+
const relations = (0, index_1.getRelations)(entity);
|
|
1438
|
+
const start = relationsToCleanup.length;
|
|
1439
|
+
relationsToCleanup.length += relations.length;
|
|
1440
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1441
|
+
relationsToCleanup[start + i] = relations[i];
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1348
1444
|
entities = this.#pendingDeletes;
|
|
1349
1445
|
this.#pendingDeletes = [];
|
|
1350
1446
|
}
|
|
@@ -1400,9 +1496,9 @@ class EntityManager {
|
|
|
1400
1496
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1401
1497
|
}
|
|
1402
1498
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1403
|
-
this.#indexManager.enableIndexingForType(meta, entities);
|
|
1499
|
+
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1404
1500
|
return (this.#indexManager
|
|
1405
|
-
.findMatching(meta, where)
|
|
1501
|
+
.findMatching(meta, entities, where)
|
|
1406
1502
|
// Still filter by `instanceof cstr` to handle subtyping
|
|
1407
1503
|
.filter((e) => e instanceof cstr && !e.isDeletedEntity));
|
|
1408
1504
|
}
|
|
@@ -1502,7 +1598,8 @@ class EntityManager {
|
|
|
1502
1598
|
continue;
|
|
1503
1599
|
const { originalData: oldOriginalData, data: oldData } = oldInstanceData;
|
|
1504
1600
|
const newEntity = mapEntity(oldEntity);
|
|
1505
|
-
const
|
|
1601
|
+
const newInstanceData = newEntity.__data;
|
|
1602
|
+
const { data: newData } = newInstanceData;
|
|
1506
1603
|
// for new entities, anything in `data` is changed and should be copied across. for existing entities, we
|
|
1507
1604
|
// only care about changed fields, which are enumerated by originalData
|
|
1508
1605
|
const maybeEntity = (value) => ((0, Entity_1.isEntity)(value) ? mapEntity(value) : value);
|
|
@@ -1510,7 +1607,7 @@ class EntityManager {
|
|
|
1510
1607
|
for (const field of fields) {
|
|
1511
1608
|
// copy over originalData so .changes is consistent across ems
|
|
1512
1609
|
if (field in oldOriginalData)
|
|
1513
|
-
|
|
1610
|
+
newInstanceData.markFieldDirty(field, maybeEntity(oldOriginalData[field]));
|
|
1514
1611
|
newData[field] = maybeEntity(oldData[field]);
|
|
1515
1612
|
}
|
|
1516
1613
|
}
|
|
@@ -1520,7 +1617,7 @@ class EntityManager {
|
|
|
1520
1617
|
const newEntity = mapEntity(oldEntity);
|
|
1521
1618
|
if (oldEntity.isDeletedEntity) {
|
|
1522
1619
|
// If the old entity was deleted, that should be persisted in the new em
|
|
1523
|
-
newEntity.__data.markDeleted(
|
|
1620
|
+
newEntity.__data.markDeleted();
|
|
1524
1621
|
// deleted entities will fail if you try to `get` their relations, so skip them since they should be cleared
|
|
1525
1622
|
// out regardless
|
|
1526
1623
|
continue;
|
|
@@ -1665,11 +1762,11 @@ class EntityManager {
|
|
|
1665
1762
|
}
|
|
1666
1763
|
}
|
|
1667
1764
|
/** Registers a newly-instantiated entity with our EntityManager; only called by #doCreate and hydrate. */
|
|
1668
|
-
#doRegister(entity, id) {
|
|
1765
|
+
#doRegister(entity, id, meta, skipDuplicateCheck = false) {
|
|
1669
1766
|
// Keep our indexes up to date...
|
|
1670
1767
|
const maybeId = id ?? entity.idTaggedMaybe;
|
|
1671
1768
|
if (maybeId) {
|
|
1672
|
-
if (this.findExistingInstance(maybeId) !== undefined) {
|
|
1769
|
+
if (!skipDuplicateCheck && this.findExistingInstance(maybeId) !== undefined) {
|
|
1673
1770
|
throw new Error(`Entity ${entity} has a duplicate instance already loaded`);
|
|
1674
1771
|
}
|
|
1675
1772
|
this.#entitiesById.set(maybeId, entity);
|
|
@@ -1678,8 +1775,9 @@ class EntityManager {
|
|
|
1678
1775
|
// Also register by the `a#1` style tagged string for new entities
|
|
1679
1776
|
this.#entitiesById.set(entity.toTaggedString(), entity);
|
|
1680
1777
|
}
|
|
1778
|
+
(0, BaseEntity_1.getInstanceData)(entity).entityIndex = this.#entitiesArray.length;
|
|
1681
1779
|
this.#entitiesArray.push(entity);
|
|
1682
|
-
|
|
1780
|
+
meta ??= (0, index_1.getMetadata)(entity);
|
|
1683
1781
|
const set = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1684
1782
|
if (set.length === 0)
|
|
1685
1783
|
this.#entitiesByTag.set(meta.tagName, set);
|
|
@@ -1693,8 +1791,6 @@ class EntityManager {
|
|
|
1693
1791
|
.join(", ");
|
|
1694
1792
|
throw new Error(`More than ${this.entityLimit} entities have been instantiated (top entities: ${topTypes})`);
|
|
1695
1793
|
}
|
|
1696
|
-
// If indexing is enabled for this type, add it...
|
|
1697
|
-
this.#indexManager.maybeIndexEntity(entity);
|
|
1698
1794
|
}
|
|
1699
1795
|
}
|
|
1700
1796
|
exports.EntityManager = EntityManager;
|
|
@@ -1778,14 +1874,14 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1778
1874
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1779
1875
|
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1780
1876
|
// Find each statically-declared reactive rule for the given entity type
|
|
1781
|
-
const rules =
|
|
1877
|
+
const rules = todo.metadata.reactiveRules;
|
|
1782
1878
|
return rules.map((rule) => {
|
|
1783
1879
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1784
1880
|
const triggered = entities.filter((e) => {
|
|
1785
1881
|
// If the rule is for a different subtype, skip it
|
|
1786
1882
|
if (!(e instanceof rule.source))
|
|
1787
1883
|
return false;
|
|
1788
|
-
// Any new-or-deleted entity fires every rule (
|
|
1884
|
+
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1789
1885
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1790
1886
|
return true;
|
|
1791
1887
|
// Otherwise see if the changed fields overlaps with the rule's fields
|
|
@@ -1803,15 +1899,15 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1803
1899
|
const p2 = Object.values(joinRowTodos).flatMap((todo) => {
|
|
1804
1900
|
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns));
|
|
1805
1901
|
// Do the first side
|
|
1806
|
-
const p1 =
|
|
1807
|
-
.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1902
|
+
const p1 = todo.m2m.meta
|
|
1903
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1808
1904
|
.map((rule) => {
|
|
1809
1905
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1810
1906
|
return followAndQueue(triggered, rule);
|
|
1811
1907
|
});
|
|
1812
1908
|
// And the second side
|
|
1813
|
-
const p2 =
|
|
1814
|
-
.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1909
|
+
const p2 = todo.m2m.otherMeta
|
|
1910
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1815
1911
|
.map((rule) => {
|
|
1816
1912
|
const triggered = entities.filter((e) => e instanceof todo.m2m.otherMeta.cstr);
|
|
1817
1913
|
return followAndQueue(triggered, rule);
|
|
@@ -2040,7 +2136,7 @@ function maybeBumpUpdatedAt(rm, todos, now) {
|
|
|
2040
2136
|
// it has changed. This is technically true, but this will break the oplock SQL generation,
|
|
2041
2137
|
// so force the field to be dirty.
|
|
2042
2138
|
const orm = (0, BaseEntity_1.getInstanceData)(e);
|
|
2043
|
-
orm.
|
|
2139
|
+
orm.markFieldDirty(updatedAt, (0, fields_1.getField)(e, updatedAt));
|
|
2044
2140
|
const serde = todo.metadata.fields[updatedAt].serde;
|
|
2045
2141
|
orm.data[updatedAt] = serde.mapFromNow(now);
|
|
2046
2142
|
rm.queueDownstreamReactables(e, updatedAt);
|
|
@@ -2077,17 +2173,13 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2077
2173
|
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, row.id)} must be instantiated via a subtype`);
|
|
2078
2174
|
}
|
|
2079
2175
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2080
|
-
return maybeBaseMeta.
|
|
2176
|
+
return maybeBaseMeta.subTypesByType.get(row.__class) ?? maybeBaseMeta;
|
|
2081
2177
|
}
|
|
2082
2178
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2083
2179
|
// Look for the STI discriminator value
|
|
2084
2180
|
const baseMeta = (0, index_1.getBaseMeta)(maybeBaseMeta);
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
throw new Error("Discriminator field must be an enum");
|
|
2088
|
-
const columnName = field.serde.columns[0].columnName;
|
|
2089
|
-
const value = row[columnName];
|
|
2090
|
-
return baseMeta.subTypes.find((st) => st.stiDiscriminatorValue === value) ?? baseMeta;
|
|
2181
|
+
const value = row[baseMeta.stiDiscriminatorColumnName];
|
|
2182
|
+
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2091
2183
|
}
|
|
2092
2184
|
else {
|
|
2093
2185
|
throw new Error("Unknown inheritance type");
|
|
@@ -2096,7 +2188,7 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2096
2188
|
/** Sets the `Animal.type` enum to the right subtype value. */
|
|
2097
2189
|
function setStiDiscriminatorValue(baseMeta, entity) {
|
|
2098
2190
|
const typeName = entity.constructor.name;
|
|
2099
|
-
const st = baseMeta.
|
|
2191
|
+
const st = baseMeta.subTypesByType.get(typeName);
|
|
2100
2192
|
if (st) {
|
|
2101
2193
|
const field = baseMeta.fields[baseMeta.stiDiscriminatorField];
|
|
2102
2194
|
const code = field.enumDetailType.findById(st.stiDiscriminatorValue).code;
|
|
@@ -2106,17 +2198,19 @@ function setStiDiscriminatorValue(baseMeta, entity) {
|
|
|
2106
2198
|
entity[baseMeta.stiDiscriminatorField] = undefined;
|
|
2107
2199
|
}
|
|
2108
2200
|
}
|
|
2109
|
-
function findPendingFlushEntities(
|
|
2110
|
-
for (const e of
|
|
2111
|
-
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2201
|
+
function findPendingFlushEntities(maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks) {
|
|
2202
|
+
for (const e of maybePendingFlushEntities) {
|
|
2203
|
+
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation === "none") {
|
|
2204
|
+
maybePendingFlushEntities.delete(e);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
if (!hooksInvoked.has(e)) {
|
|
2208
|
+
pendingHooks.add(e);
|
|
2209
|
+
}
|
|
2210
|
+
else {
|
|
2211
|
+
alreadyRanHooks.add(e);
|
|
2119
2212
|
}
|
|
2213
|
+
pendingFlush.add(e);
|
|
2120
2214
|
}
|
|
2121
2215
|
}
|
|
2122
2216
|
/** Returns true if the caller explicitly asked `find` to use SQL pagination. */
|