joist-core 2.2.0-next.3 → 2.2.0-next.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/BaseEntity.d.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/EntityManager.d.ts +6 -2
- package/build/EntityManager.d.ts.map +1 -1
- package/build/EntityManager.js +170 -88
- 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.collectionJoins.js +8 -2
- package/build/QueryParser.collectionJoins.js.map +1 -1
- package/build/QueryParser.d.ts +4 -2
- package/build/QueryParser.d.ts.map +1 -1
- package/build/QueryParser.js +19 -5
- 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 +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 +3 -3
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +65 -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 +1 -2
- package/build/dataloaders/findCountDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +48 -13
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.d.ts +10 -5
- package/build/dataloaders/findDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findDataLoader.js +109 -82
- 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 +16 -15
- 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 +1 -2
- package/build/dataloaders/findPaginatedDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +33 -25
- 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/drivers/buildRawQuery.d.ts.map +1 -1
- package/build/drivers/buildRawQuery.js +5 -1
- package/build/drivers/buildRawQuery.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/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/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");
|
|
@@ -88,6 +88,8 @@ class EntityManager {
|
|
|
88
88
|
txn;
|
|
89
89
|
entityLimit = defaultEntityLimit;
|
|
90
90
|
#entitiesArray = [];
|
|
91
|
+
// Incrementally track dirty entities so we don't have to scan `#entityArray` during flush
|
|
92
|
+
#maybePendingFlushEntities = new Set();
|
|
91
93
|
// Indexes the currently loaded entities by their tagged ids and `toTaggedString` ids (i.e. `a#`). This fixes
|
|
92
94
|
// real-world performance issues where `findExistingInstance` scanning `#entities` was an `O(n^2)`.
|
|
93
95
|
#entitiesById = new Map();
|
|
@@ -106,6 +108,7 @@ class EntityManager {
|
|
|
106
108
|
* so that both see the most accurate state.
|
|
107
109
|
*/
|
|
108
110
|
#pendingDeletes = [];
|
|
111
|
+
#hasAnyDeletes = false;
|
|
109
112
|
#dataloaders = {};
|
|
110
113
|
#batchLoaders = {};
|
|
111
114
|
#joinRows = {};
|
|
@@ -155,6 +158,20 @@ class EntityManager {
|
|
|
155
158
|
indexManager: this.#indexManager,
|
|
156
159
|
isLoadedCache: this.#isLoadedCache,
|
|
157
160
|
pluginManager,
|
|
161
|
+
markMaybePending(entity) {
|
|
162
|
+
em.#maybePendingFlushEntities.add(entity);
|
|
163
|
+
},
|
|
164
|
+
unmarkMaybePending(entity) {
|
|
165
|
+
em.#maybePendingFlushEntities.delete(entity);
|
|
166
|
+
},
|
|
167
|
+
hasAnyDeletes() {
|
|
168
|
+
return em.#hasAnyDeletes;
|
|
169
|
+
},
|
|
170
|
+
pendingDeleteIds(type) {
|
|
171
|
+
return em.#pendingDeletes
|
|
172
|
+
.filter((entity) => entity instanceof type && entity.idMaybe !== undefined)
|
|
173
|
+
.map((entity) => (0, index_1.keyToNumber)((0, index_1.getMetadata)(entity), entity.id));
|
|
174
|
+
},
|
|
158
175
|
isMerging(entity) {
|
|
159
176
|
return em.#merging?.has(entity) ?? false;
|
|
160
177
|
},
|
|
@@ -248,12 +265,9 @@ class EntityManager {
|
|
|
248
265
|
async find(type, where, options) {
|
|
249
266
|
const { populate, ...rest } = options || {};
|
|
250
267
|
const settings = { where, ...rest };
|
|
251
|
-
const
|
|
268
|
+
const result = await (hasPaginationSettings(rest)
|
|
252
269
|
? (0, findPaginatedDataLoader_1.findPaginatedDataLoader)(this, type, settings, populate)
|
|
253
|
-
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)
|
|
254
|
-
const result = await loader
|
|
255
|
-
.load(settings)
|
|
256
|
-
.catch(function find(err) {
|
|
270
|
+
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)).catch(function find(err) {
|
|
257
271
|
throw appendStack(err, new Error());
|
|
258
272
|
});
|
|
259
273
|
if (populate) {
|
|
@@ -348,6 +362,9 @@ class EntityManager {
|
|
|
348
362
|
}
|
|
349
363
|
else {
|
|
350
364
|
const [entity] = this.hydrate(type, [row]);
|
|
365
|
+
if (this.#hasAnyDeletes && entity.isDeletedEntity) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
351
368
|
if (populate) {
|
|
352
369
|
await this.populate(entity, populate);
|
|
353
370
|
}
|
|
@@ -364,16 +381,14 @@ class EntityManager {
|
|
|
364
381
|
*/
|
|
365
382
|
async findCount(type, where, options = {}) {
|
|
366
383
|
const settings = { where, ...options };
|
|
367
|
-
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings)
|
|
368
|
-
.load(settings)
|
|
369
|
-
.catch(function findCount(err) {
|
|
384
|
+
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings).catch(function findCount(err) {
|
|
370
385
|
throw appendStack(err, new Error());
|
|
371
386
|
});
|
|
372
387
|
// If the user is do "count all", we can adjust the number up/down based on
|
|
373
388
|
// WIP creates/deletes. We can't do this if the WHERE clause is populated b/c
|
|
374
389
|
// then we'd also have to eval each created/deleted entity against the WHERE
|
|
375
390
|
// clause before knowing if it should adjust teh amount.
|
|
376
|
-
const isSelectAll = Object.keys(where).length === 0;
|
|
391
|
+
const isSelectAll = Object.keys(where).length === 0 && options.conditions === undefined;
|
|
377
392
|
if (isSelectAll) {
|
|
378
393
|
const tagged = this.#entitiesByTag.get((0, index_1.getMetadata)(type).tagName) ?? [];
|
|
379
394
|
for (const entity of tagged) {
|
|
@@ -398,9 +413,7 @@ class EntityManager {
|
|
|
398
413
|
*/
|
|
399
414
|
async findIds(type, where, options = {}) {
|
|
400
415
|
const settings = { where, ...options };
|
|
401
|
-
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings)
|
|
402
|
-
.load(settings)
|
|
403
|
-
.catch(function findIds(err) {
|
|
416
|
+
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings).catch(function findIds(err) {
|
|
404
417
|
throw appendStack(err, new Error());
|
|
405
418
|
});
|
|
406
419
|
}
|
|
@@ -689,48 +702,86 @@ class EntityManager {
|
|
|
689
702
|
}
|
|
690
703
|
async loadAll(type, _ids, hint) {
|
|
691
704
|
const meta = (0, index_1.getMetadata)(type);
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
705
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
706
|
+
const ids = new Array(_ids.length);
|
|
707
|
+
const entities = new Array(_ids.length);
|
|
708
|
+
let idsToLoad;
|
|
709
|
+
let positionsToLoad;
|
|
710
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
711
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
712
|
+
ids[i] = id;
|
|
713
|
+
const entity = this.findExistingInstance(id);
|
|
714
|
+
if (entity) {
|
|
715
|
+
entities[i] = entity;
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
(idsToLoad ??= []).push(id);
|
|
719
|
+
(positionsToLoad ??= []).push(i);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
695
723
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
696
724
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
697
725
|
.catch(function loadAll(err) {
|
|
698
726
|
throw appendStack(err, new Error());
|
|
699
727
|
});
|
|
728
|
+
for (const i of positionsToLoad) {
|
|
729
|
+
entities[i] = this.findExistingInstance(ids[i]);
|
|
730
|
+
}
|
|
700
731
|
}
|
|
701
|
-
|
|
702
|
-
for (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
732
|
+
let idsNotFound;
|
|
733
|
+
for (let i = 0; i < entities.length; i++) {
|
|
734
|
+
if (entities[i] === undefined) {
|
|
735
|
+
(idsNotFound ??= []).push(ids[i]);
|
|
736
|
+
}
|
|
706
737
|
}
|
|
707
|
-
if (
|
|
708
|
-
const idsNotFound = ids.filter((_, i) => entities[i] === undefined);
|
|
738
|
+
if (idsNotFound) {
|
|
709
739
|
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
710
740
|
}
|
|
741
|
+
const loadedEntities = entities;
|
|
711
742
|
if (hint) {
|
|
712
|
-
await this.populate(
|
|
743
|
+
await this.populate(loadedEntities, hint);
|
|
713
744
|
}
|
|
714
745
|
if (meta.inheritanceType === "sti" && meta.baseType) {
|
|
715
|
-
const wrongType =
|
|
746
|
+
const wrongType = loadedEntities.filter((e) => !(e instanceof meta.cstr));
|
|
716
747
|
if (wrongType.length > 0) {
|
|
717
748
|
throw new Error(`${wrongType.join(", ")} were not of type ${meta.cstr.name}`);
|
|
718
749
|
}
|
|
719
750
|
}
|
|
720
|
-
return
|
|
751
|
+
return loadedEntities;
|
|
721
752
|
}
|
|
722
753
|
async loadAllIfExists(type, _ids, hint) {
|
|
723
754
|
const meta = (0, index_1.getMetadata)(type);
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
755
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
756
|
+
const ids = new Array(_ids.length);
|
|
757
|
+
const entities = [];
|
|
758
|
+
let idsToLoad;
|
|
759
|
+
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
760
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
761
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
762
|
+
ids[i] = id;
|
|
763
|
+
const entity = this.findExistingInstance(id);
|
|
764
|
+
if (entity) {
|
|
765
|
+
entities.push(entity);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
(idsToLoad ??= []).push(id);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
727
772
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
728
773
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
729
774
|
.catch(function loadAllIfExists(err) {
|
|
730
775
|
throw appendStack(err, new Error());
|
|
731
776
|
});
|
|
777
|
+
// Now that everything is loaded, recalc `entities`
|
|
778
|
+
entities.length = 0;
|
|
779
|
+
for (const id of ids) {
|
|
780
|
+
const entity = this.findExistingInstance(id);
|
|
781
|
+
if (entity)
|
|
782
|
+
entities.push(entity);
|
|
783
|
+
}
|
|
732
784
|
}
|
|
733
|
-
const entities = ids.map((id) => this.findExistingInstance(id)).filter(Boolean);
|
|
734
785
|
if (hint) {
|
|
735
786
|
await this.populate(entities, hint);
|
|
736
787
|
}
|
|
@@ -781,6 +832,12 @@ class EntityManager {
|
|
|
781
832
|
if (list.length === 0) {
|
|
782
833
|
return !fn ? entityOrList : fn(entityOrList);
|
|
783
834
|
}
|
|
835
|
+
// Avoid building a HintTree/batchloader for persisted entities when everything is already loaded; in the phase 9
|
|
836
|
+
// benchmark this moved already-loaded populates from ~1.10ms to ~0.89ms, and nested already-loaded populates from
|
|
837
|
+
// ~2.39ms to ~1.94ms.
|
|
838
|
+
if (!opts.forceReload && list.every((entity) => !entity.isNewEntity && (0, loadHints_1.isLoadedForPopulate)(entity, hintOpt))) {
|
|
839
|
+
return fn ? fn(entityOrList) : entityOrList;
|
|
840
|
+
}
|
|
784
841
|
const meta = (0, index_1.getMetadata)(list[0]);
|
|
785
842
|
if (this.#preloader) {
|
|
786
843
|
// If we can preload, prevent promise deadlocking by one large-batch preload populate (which can't have
|
|
@@ -829,9 +886,11 @@ class EntityManager {
|
|
|
829
886
|
delete(entityOrArray) {
|
|
830
887
|
for (const entity of (0, utils_1.toArray)(entityOrArray)) {
|
|
831
888
|
// Early return if already deleted.
|
|
832
|
-
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted(
|
|
889
|
+
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted();
|
|
833
890
|
if (!alreadyMarked)
|
|
834
891
|
continue;
|
|
892
|
+
// This monotonic flag lets find hot paths skip scanning results until a delete has ever happened in this EM.
|
|
893
|
+
this.#hasAnyDeletes = true;
|
|
835
894
|
// Any derived fields that read this entity will need recalc-d
|
|
836
895
|
this.#rm.queueAllDownstreamFields(entity, "deleted");
|
|
837
896
|
// Synchronously unhook the entity if the relations are loaded
|
|
@@ -925,7 +984,7 @@ class EntityManager {
|
|
|
925
984
|
const pendingHooks = new Set();
|
|
926
985
|
// Subset of pendingFlush entities that had hooks invoked in a prior `runHooksOnPendingEntities`
|
|
927
986
|
const alreadyRanHooks = new Set();
|
|
928
|
-
findPendingFlushEntities(this
|
|
987
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
929
988
|
// If we're re-looping for AsyncReactiveField, make sure to bump updatedAt
|
|
930
989
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
931
990
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
@@ -964,7 +1023,7 @@ class EntityManager {
|
|
|
964
1023
|
hooksInvoked.add(e);
|
|
965
1024
|
pendingHooks.clear();
|
|
966
1025
|
// See if the hooks mutated any new, not-yet-hooksInvoked entities
|
|
967
|
-
findPendingFlushEntities(this
|
|
1026
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
968
1027
|
// The final run of recalcPendingReactables could have left us with pending type errors and no entities in
|
|
969
1028
|
// pendingHooks. If so, we need to re-run recalcPendingTypeErrors to get those errors to transition into
|
|
970
1029
|
// suppressed errors so that we will fail after simpleValidation.
|
|
@@ -1099,7 +1158,7 @@ class EntityManager {
|
|
|
1099
1158
|
for (const e of createdThenDeleted)
|
|
1100
1159
|
(0, BaseEntity_1.getInstanceData)(e).fixupCreatedThenDeleted();
|
|
1101
1160
|
this.#merging?.clear();
|
|
1102
|
-
return [...allFlushedEntities];
|
|
1161
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1103
1162
|
}
|
|
1104
1163
|
catch (e) {
|
|
1105
1164
|
if (e instanceof RecursiveCollection_1.RecursiveCycleError) {
|
|
@@ -1122,8 +1181,9 @@ class EntityManager {
|
|
|
1122
1181
|
throw new index_1.ValidationErrors(message);
|
|
1123
1182
|
}
|
|
1124
1183
|
}
|
|
1125
|
-
if (e instanceof InMemoryRollbackError)
|
|
1126
|
-
return [...allFlushedEntities];
|
|
1184
|
+
if (e instanceof InMemoryRollbackError) {
|
|
1185
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1186
|
+
}
|
|
1127
1187
|
throw e;
|
|
1128
1188
|
}
|
|
1129
1189
|
finally {
|
|
@@ -1231,10 +1291,13 @@ class EntityManager {
|
|
|
1231
1291
|
*/
|
|
1232
1292
|
hydrate(type, rows, options) {
|
|
1233
1293
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1294
|
+
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1295
|
+
const overwriteExisting = options?.overwriteExisting === true;
|
|
1234
1296
|
let i = 0;
|
|
1235
1297
|
const entities = new Array(rows.length);
|
|
1236
1298
|
for (const row of rows) {
|
|
1237
|
-
const
|
|
1299
|
+
const id = row["id"];
|
|
1300
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : `${taggedIdPrefix}${id}`;
|
|
1238
1301
|
// See if this is already in our UoW
|
|
1239
1302
|
let entity = this.findExistingInstance(taggedId);
|
|
1240
1303
|
if (!entity) {
|
|
@@ -1243,23 +1306,36 @@ class EntityManager {
|
|
|
1243
1306
|
// Pass id as a hint that we're in hydrate mode
|
|
1244
1307
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1245
1308
|
(0, BaseEntity_1.getInstanceData)(entity).row = row;
|
|
1246
|
-
this.#doRegister(entity, taggedId);
|
|
1309
|
+
this.#doRegister(entity, taggedId, meta, true);
|
|
1247
1310
|
}
|
|
1248
|
-
else if (
|
|
1311
|
+
else if (overwriteExisting) {
|
|
1249
1312
|
// Usually if the entity already exists, we don't write over it, but in this case we assume that
|
|
1250
1313
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1251
1314
|
// First swap out the old row with the new row
|
|
1252
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1315
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1316
|
+
instanceData.row = row;
|
|
1253
1317
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1254
1318
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1255
|
-
const { data
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1319
|
+
const { data } = instanceData;
|
|
1320
|
+
const dataKeys = Object.keys(data);
|
|
1321
|
+
if (dataKeys.length > 0) {
|
|
1322
|
+
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1323
|
+
const changedFields = entity.changes.fieldsWithoutRelations;
|
|
1324
|
+
if (changedFields.length === 0) {
|
|
1325
|
+
for (const fieldName of dataKeys) {
|
|
1326
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1327
|
+
serde.setOnEntity(data, row);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
for (const fieldName of dataKeys) {
|
|
1332
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1333
|
+
serde.setOnEntity(data, row);
|
|
1334
|
+
// Make the field look not-dirty
|
|
1335
|
+
if (changedFields.includes(fieldName)) {
|
|
1336
|
+
instanceData.markFieldClean(fieldName);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1263
1339
|
}
|
|
1264
1340
|
}
|
|
1265
1341
|
}
|
|
@@ -1268,8 +1344,9 @@ class EntityManager {
|
|
|
1268
1344
|
return entities;
|
|
1269
1345
|
}
|
|
1270
1346
|
touch(entityOrEntities) {
|
|
1271
|
-
for (const entity of (0, utils_1.toArray)(entityOrEntities))
|
|
1272
|
-
(0, BaseEntity_1.getInstanceData)(entity).
|
|
1347
|
+
for (const entity of (0, utils_1.toArray)(entityOrEntities)) {
|
|
1348
|
+
(0, BaseEntity_1.getInstanceData)(entity).markTouched();
|
|
1349
|
+
}
|
|
1273
1350
|
}
|
|
1274
1351
|
async recalc(entityOrEntities) {
|
|
1275
1352
|
// Look for async reactive fields
|
|
@@ -1346,9 +1423,16 @@ class EntityManager {
|
|
|
1346
1423
|
// Run the beforeDelete hook before we unhook the entity
|
|
1347
1424
|
const todos = (0, Todo_1.createTodos)(entities);
|
|
1348
1425
|
await beforeDelete(this.ctx, todos);
|
|
1349
|
-
// For all relations, unhook the entity from the other side
|
|
1350
|
-
//
|
|
1351
|
-
|
|
1426
|
+
// For all relations, unhook the entity from the other side; this append path is optimized for
|
|
1427
|
+
// large deletes with ~100k relations by avoiding `flatMap` intermediates and `concat` copies.
|
|
1428
|
+
for (const entity of entities) {
|
|
1429
|
+
const relations = (0, index_1.getRelations)(entity);
|
|
1430
|
+
const start = relationsToCleanup.length;
|
|
1431
|
+
relationsToCleanup.length += relations.length;
|
|
1432
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1433
|
+
relationsToCleanup[start + i] = relations[i];
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1352
1436
|
entities = this.#pendingDeletes;
|
|
1353
1437
|
this.#pendingDeletes = [];
|
|
1354
1438
|
}
|
|
@@ -1404,9 +1488,9 @@ class EntityManager {
|
|
|
1404
1488
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1405
1489
|
}
|
|
1406
1490
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1407
|
-
this.#indexManager.enableIndexingForType(meta, entities);
|
|
1491
|
+
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1408
1492
|
return (this.#indexManager
|
|
1409
|
-
.findMatching(meta, where)
|
|
1493
|
+
.findMatching(meta, entities, where)
|
|
1410
1494
|
// Still filter by `instanceof cstr` to handle subtyping
|
|
1411
1495
|
.filter((e) => e instanceof cstr && !e.isDeletedEntity));
|
|
1412
1496
|
}
|
|
@@ -1506,7 +1590,8 @@ class EntityManager {
|
|
|
1506
1590
|
continue;
|
|
1507
1591
|
const { originalData: oldOriginalData, data: oldData } = oldInstanceData;
|
|
1508
1592
|
const newEntity = mapEntity(oldEntity);
|
|
1509
|
-
const
|
|
1593
|
+
const newInstanceData = newEntity.__data;
|
|
1594
|
+
const { data: newData } = newInstanceData;
|
|
1510
1595
|
// for new entities, anything in `data` is changed and should be copied across. for existing entities, we
|
|
1511
1596
|
// only care about changed fields, which are enumerated by originalData
|
|
1512
1597
|
const maybeEntity = (value) => ((0, Entity_1.isEntity)(value) ? mapEntity(value) : value);
|
|
@@ -1514,7 +1599,7 @@ class EntityManager {
|
|
|
1514
1599
|
for (const field of fields) {
|
|
1515
1600
|
// copy over originalData so .changes is consistent across ems
|
|
1516
1601
|
if (field in oldOriginalData)
|
|
1517
|
-
|
|
1602
|
+
newInstanceData.markFieldDirty(field, maybeEntity(oldOriginalData[field]));
|
|
1518
1603
|
newData[field] = maybeEntity(oldData[field]);
|
|
1519
1604
|
}
|
|
1520
1605
|
}
|
|
@@ -1524,7 +1609,7 @@ class EntityManager {
|
|
|
1524
1609
|
const newEntity = mapEntity(oldEntity);
|
|
1525
1610
|
if (oldEntity.isDeletedEntity) {
|
|
1526
1611
|
// If the old entity was deleted, that should be persisted in the new em
|
|
1527
|
-
newEntity.__data.markDeleted(
|
|
1612
|
+
newEntity.__data.markDeleted();
|
|
1528
1613
|
// deleted entities will fail if you try to `get` their relations, so skip them since they should be cleared
|
|
1529
1614
|
// out regardless
|
|
1530
1615
|
continue;
|
|
@@ -1669,11 +1754,11 @@ class EntityManager {
|
|
|
1669
1754
|
}
|
|
1670
1755
|
}
|
|
1671
1756
|
/** Registers a newly-instantiated entity with our EntityManager; only called by #doCreate and hydrate. */
|
|
1672
|
-
#doRegister(entity, id) {
|
|
1757
|
+
#doRegister(entity, id, meta, skipDuplicateCheck = false) {
|
|
1673
1758
|
// Keep our indexes up to date...
|
|
1674
1759
|
const maybeId = id ?? entity.idTaggedMaybe;
|
|
1675
1760
|
if (maybeId) {
|
|
1676
|
-
if (this.findExistingInstance(maybeId) !== undefined) {
|
|
1761
|
+
if (!skipDuplicateCheck && this.findExistingInstance(maybeId) !== undefined) {
|
|
1677
1762
|
throw new Error(`Entity ${entity} has a duplicate instance already loaded`);
|
|
1678
1763
|
}
|
|
1679
1764
|
this.#entitiesById.set(maybeId, entity);
|
|
@@ -1682,8 +1767,9 @@ class EntityManager {
|
|
|
1682
1767
|
// Also register by the `a#1` style tagged string for new entities
|
|
1683
1768
|
this.#entitiesById.set(entity.toTaggedString(), entity);
|
|
1684
1769
|
}
|
|
1770
|
+
(0, BaseEntity_1.getInstanceData)(entity).entityIndex = this.#entitiesArray.length;
|
|
1685
1771
|
this.#entitiesArray.push(entity);
|
|
1686
|
-
|
|
1772
|
+
meta ??= (0, index_1.getMetadata)(entity);
|
|
1687
1773
|
const set = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1688
1774
|
if (set.length === 0)
|
|
1689
1775
|
this.#entitiesByTag.set(meta.tagName, set);
|
|
@@ -1697,8 +1783,6 @@ class EntityManager {
|
|
|
1697
1783
|
.join(", ");
|
|
1698
1784
|
throw new Error(`More than ${this.entityLimit} entities have been instantiated (top entities: ${topTypes})`);
|
|
1699
1785
|
}
|
|
1700
|
-
// If indexing is enabled for this type, add it...
|
|
1701
|
-
this.#indexManager.maybeIndexEntity(entity);
|
|
1702
1786
|
}
|
|
1703
1787
|
}
|
|
1704
1788
|
exports.EntityManager = EntityManager;
|
|
@@ -1782,14 +1866,14 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1782
1866
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1783
1867
|
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1784
1868
|
// Find each statically-declared reactive rule for the given entity type
|
|
1785
|
-
const rules =
|
|
1869
|
+
const rules = todo.metadata.reactiveRules;
|
|
1786
1870
|
return rules.map((rule) => {
|
|
1787
1871
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1788
1872
|
const triggered = entities.filter((e) => {
|
|
1789
1873
|
// If the rule is for a different subtype, skip it
|
|
1790
1874
|
if (!(e instanceof rule.source))
|
|
1791
1875
|
return false;
|
|
1792
|
-
// Any new-or-deleted entity fires every rule (
|
|
1876
|
+
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1793
1877
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1794
1878
|
return true;
|
|
1795
1879
|
// Otherwise see if the changed fields overlaps with the rule's fields
|
|
@@ -1807,15 +1891,15 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1807
1891
|
const p2 = Object.values(joinRowTodos).flatMap((todo) => {
|
|
1808
1892
|
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns));
|
|
1809
1893
|
// Do the first side
|
|
1810
|
-
const p1 =
|
|
1811
|
-
.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1894
|
+
const p1 = todo.m2m.meta
|
|
1895
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1812
1896
|
.map((rule) => {
|
|
1813
1897
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1814
1898
|
return followAndQueue(triggered, rule);
|
|
1815
1899
|
});
|
|
1816
1900
|
// And the second side
|
|
1817
|
-
const p2 =
|
|
1818
|
-
.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1901
|
+
const p2 = todo.m2m.otherMeta
|
|
1902
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1819
1903
|
.map((rule) => {
|
|
1820
1904
|
const triggered = entities.filter((e) => e instanceof todo.m2m.otherMeta.cstr);
|
|
1821
1905
|
return followAndQueue(triggered, rule);
|
|
@@ -2044,7 +2128,7 @@ function maybeBumpUpdatedAt(rm, todos, now) {
|
|
|
2044
2128
|
// it has changed. This is technically true, but this will break the oplock SQL generation,
|
|
2045
2129
|
// so force the field to be dirty.
|
|
2046
2130
|
const orm = (0, BaseEntity_1.getInstanceData)(e);
|
|
2047
|
-
orm.
|
|
2131
|
+
orm.markFieldDirty(updatedAt, (0, fields_1.getField)(e, updatedAt));
|
|
2048
2132
|
const serde = todo.metadata.fields[updatedAt].serde;
|
|
2049
2133
|
orm.data[updatedAt] = serde.mapFromNow(now);
|
|
2050
2134
|
rm.queueDownstreamReactables(e, updatedAt);
|
|
@@ -2081,17 +2165,13 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2081
2165
|
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, row.id)} must be instantiated via a subtype`);
|
|
2082
2166
|
}
|
|
2083
2167
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2084
|
-
return maybeBaseMeta.
|
|
2168
|
+
return maybeBaseMeta.subTypesByType.get(row.__class) ?? maybeBaseMeta;
|
|
2085
2169
|
}
|
|
2086
2170
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2087
2171
|
// Look for the STI discriminator value
|
|
2088
2172
|
const baseMeta = (0, index_1.getBaseMeta)(maybeBaseMeta);
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
throw new Error("Discriminator field must be an enum");
|
|
2092
|
-
const columnName = field.serde.columns[0].columnName;
|
|
2093
|
-
const value = row[columnName];
|
|
2094
|
-
return baseMeta.subTypes.find((st) => st.stiDiscriminatorValue === value) ?? baseMeta;
|
|
2173
|
+
const value = row[baseMeta.stiDiscriminatorColumnName];
|
|
2174
|
+
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2095
2175
|
}
|
|
2096
2176
|
else {
|
|
2097
2177
|
throw new Error("Unknown inheritance type");
|
|
@@ -2100,7 +2180,7 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2100
2180
|
/** Sets the `Animal.type` enum to the right subtype value. */
|
|
2101
2181
|
function setStiDiscriminatorValue(baseMeta, entity) {
|
|
2102
2182
|
const typeName = entity.constructor.name;
|
|
2103
|
-
const st = baseMeta.
|
|
2183
|
+
const st = baseMeta.subTypesByType.get(typeName);
|
|
2104
2184
|
if (st) {
|
|
2105
2185
|
const field = baseMeta.fields[baseMeta.stiDiscriminatorField];
|
|
2106
2186
|
const code = field.enumDetailType.findById(st.stiDiscriminatorValue).code;
|
|
@@ -2110,17 +2190,19 @@ function setStiDiscriminatorValue(baseMeta, entity) {
|
|
|
2110
2190
|
entity[baseMeta.stiDiscriminatorField] = undefined;
|
|
2111
2191
|
}
|
|
2112
2192
|
}
|
|
2113
|
-
function findPendingFlushEntities(
|
|
2114
|
-
for (const e of
|
|
2115
|
-
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2193
|
+
function findPendingFlushEntities(maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks) {
|
|
2194
|
+
for (const e of maybePendingFlushEntities) {
|
|
2195
|
+
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation === "none") {
|
|
2196
|
+
maybePendingFlushEntities.delete(e);
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (!hooksInvoked.has(e)) {
|
|
2200
|
+
pendingHooks.add(e);
|
|
2201
|
+
}
|
|
2202
|
+
else {
|
|
2203
|
+
alreadyRanHooks.add(e);
|
|
2123
2204
|
}
|
|
2205
|
+
pendingFlush.add(e);
|
|
2124
2206
|
}
|
|
2125
2207
|
}
|
|
2126
2208
|
/** Returns true if the caller explicitly asked `find` to use SQL pagination. */
|