joist-core 2.2.0-next.2 → 2.2.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/BaseEntity.js +1 -1
- package/build/BaseEntity.js.map +1 -1
- package/build/EntityManager.d.ts +3 -0
- package/build/EntityManager.d.ts.map +1 -1
- package/build/EntityManager.js +164 -87
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +17 -1
- package/build/EntityMetadata.d.ts.map +1 -1
- 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/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.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/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/populateBatchLoader.d.ts.map +1 -1
- package/build/batchloaders/populateBatchLoader.js +26 -8
- package/build/batchloaders/populateBatchLoader.js.map +1 -1
- package/build/configure.d.ts +3 -3
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +64 -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 +16 -13
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.d.ts +9 -4
- package/build/dataloaders/findDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findDataLoader.js +120 -88
- 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/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/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,15 @@ 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
|
+
},
|
|
158
170
|
isMerging(entity) {
|
|
159
171
|
return em.#merging?.has(entity) ?? false;
|
|
160
172
|
},
|
|
@@ -248,12 +260,9 @@ class EntityManager {
|
|
|
248
260
|
async find(type, where, options) {
|
|
249
261
|
const { populate, ...rest } = options || {};
|
|
250
262
|
const settings = { where, ...rest };
|
|
251
|
-
const
|
|
263
|
+
const result = await (hasPaginationSettings(rest)
|
|
252
264
|
? (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) {
|
|
265
|
+
: (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)).catch(function find(err) {
|
|
257
266
|
throw appendStack(err, new Error());
|
|
258
267
|
});
|
|
259
268
|
if (populate) {
|
|
@@ -348,6 +357,9 @@ class EntityManager {
|
|
|
348
357
|
}
|
|
349
358
|
else {
|
|
350
359
|
const [entity] = this.hydrate(type, [row]);
|
|
360
|
+
if (this.#hasAnyDeletes && entity.isDeletedEntity) {
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
351
363
|
if (populate) {
|
|
352
364
|
await this.populate(entity, populate);
|
|
353
365
|
}
|
|
@@ -364,9 +376,7 @@ class EntityManager {
|
|
|
364
376
|
*/
|
|
365
377
|
async findCount(type, where, options = {}) {
|
|
366
378
|
const settings = { where, ...options };
|
|
367
|
-
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings)
|
|
368
|
-
.load(settings)
|
|
369
|
-
.catch(function findCount(err) {
|
|
379
|
+
let count = await (0, findCountDataLoader_1.findCountDataLoader)(this, type, settings).catch(function findCount(err) {
|
|
370
380
|
throw appendStack(err, new Error());
|
|
371
381
|
});
|
|
372
382
|
// If the user is do "count all", we can adjust the number up/down based on
|
|
@@ -398,9 +408,7 @@ class EntityManager {
|
|
|
398
408
|
*/
|
|
399
409
|
async findIds(type, where, options = {}) {
|
|
400
410
|
const settings = { where, ...options };
|
|
401
|
-
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings)
|
|
402
|
-
.load(settings)
|
|
403
|
-
.catch(function findIds(err) {
|
|
411
|
+
return (0, findIdsDataLoader_1.findIdsDataLoader)(this, type, settings).catch(function findIds(err) {
|
|
404
412
|
throw appendStack(err, new Error());
|
|
405
413
|
});
|
|
406
414
|
}
|
|
@@ -689,48 +697,86 @@ class EntityManager {
|
|
|
689
697
|
}
|
|
690
698
|
async loadAll(type, _ids, hint) {
|
|
691
699
|
const meta = (0, index_1.getMetadata)(type);
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
700
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
701
|
+
const ids = new Array(_ids.length);
|
|
702
|
+
const entities = new Array(_ids.length);
|
|
703
|
+
let idsToLoad;
|
|
704
|
+
let positionsToLoad;
|
|
705
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
706
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
707
|
+
ids[i] = id;
|
|
708
|
+
const entity = this.findExistingInstance(id);
|
|
709
|
+
if (entity) {
|
|
710
|
+
entities[i] = entity;
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
(idsToLoad ??= []).push(id);
|
|
714
|
+
(positionsToLoad ??= []).push(i);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
695
718
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
696
719
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
697
720
|
.catch(function loadAll(err) {
|
|
698
721
|
throw appendStack(err, new Error());
|
|
699
722
|
});
|
|
723
|
+
for (const i of positionsToLoad) {
|
|
724
|
+
entities[i] = this.findExistingInstance(ids[i]);
|
|
725
|
+
}
|
|
700
726
|
}
|
|
701
|
-
|
|
702
|
-
for (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
727
|
+
let idsNotFound;
|
|
728
|
+
for (let i = 0; i < entities.length; i++) {
|
|
729
|
+
if (entities[i] === undefined) {
|
|
730
|
+
(idsNotFound ??= []).push(ids[i]);
|
|
731
|
+
}
|
|
706
732
|
}
|
|
707
|
-
if (
|
|
708
|
-
const idsNotFound = ids.filter((_, i) => entities[i] === undefined);
|
|
733
|
+
if (idsNotFound) {
|
|
709
734
|
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
710
735
|
}
|
|
736
|
+
const loadedEntities = entities;
|
|
711
737
|
if (hint) {
|
|
712
|
-
await this.populate(
|
|
738
|
+
await this.populate(loadedEntities, hint);
|
|
713
739
|
}
|
|
714
740
|
if (meta.inheritanceType === "sti" && meta.baseType) {
|
|
715
|
-
const wrongType =
|
|
741
|
+
const wrongType = loadedEntities.filter((e) => !(e instanceof meta.cstr));
|
|
716
742
|
if (wrongType.length > 0) {
|
|
717
743
|
throw new Error(`${wrongType.join(", ")} were not of type ${meta.cstr.name}`);
|
|
718
744
|
}
|
|
719
745
|
}
|
|
720
|
-
return
|
|
746
|
+
return loadedEntities;
|
|
721
747
|
}
|
|
722
748
|
async loadAllIfExists(type, _ids, hint) {
|
|
723
749
|
const meta = (0, index_1.getMetadata)(type);
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
750
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
751
|
+
const ids = new Array(_ids.length);
|
|
752
|
+
const entities = [];
|
|
753
|
+
let idsToLoad;
|
|
754
|
+
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
755
|
+
for (let i = 0; i < _ids.length; i++) {
|
|
756
|
+
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
757
|
+
ids[i] = id;
|
|
758
|
+
const entity = this.findExistingInstance(id);
|
|
759
|
+
if (entity) {
|
|
760
|
+
entities.push(entity);
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
(idsToLoad ??= []).push(id);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (idsToLoad && idsToLoad.length > 0) {
|
|
727
767
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
728
768
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
729
769
|
.catch(function loadAllIfExists(err) {
|
|
730
770
|
throw appendStack(err, new Error());
|
|
731
771
|
});
|
|
772
|
+
// Now that everything is loaded, recalc `entities`
|
|
773
|
+
entities.length = 0;
|
|
774
|
+
for (const id of ids) {
|
|
775
|
+
const entity = this.findExistingInstance(id);
|
|
776
|
+
if (entity)
|
|
777
|
+
entities.push(entity);
|
|
778
|
+
}
|
|
732
779
|
}
|
|
733
|
-
const entities = ids.map((id) => this.findExistingInstance(id)).filter(Boolean);
|
|
734
780
|
if (hint) {
|
|
735
781
|
await this.populate(entities, hint);
|
|
736
782
|
}
|
|
@@ -781,6 +827,12 @@ class EntityManager {
|
|
|
781
827
|
if (list.length === 0) {
|
|
782
828
|
return !fn ? entityOrList : fn(entityOrList);
|
|
783
829
|
}
|
|
830
|
+
// Avoid building a HintTree/batchloader for persisted entities when everything is already loaded; in the phase 9
|
|
831
|
+
// benchmark this moved already-loaded populates from ~1.10ms to ~0.89ms, and nested already-loaded populates from
|
|
832
|
+
// ~2.39ms to ~1.94ms.
|
|
833
|
+
if (!opts.forceReload && list.every((entity) => !entity.isNewEntity && (0, loadHints_1.isLoadedForPopulate)(entity, hintOpt))) {
|
|
834
|
+
return fn ? fn(entityOrList) : entityOrList;
|
|
835
|
+
}
|
|
784
836
|
const meta = (0, index_1.getMetadata)(list[0]);
|
|
785
837
|
if (this.#preloader) {
|
|
786
838
|
// If we can preload, prevent promise deadlocking by one large-batch preload populate (which can't have
|
|
@@ -829,9 +881,11 @@ class EntityManager {
|
|
|
829
881
|
delete(entityOrArray) {
|
|
830
882
|
for (const entity of (0, utils_1.toArray)(entityOrArray)) {
|
|
831
883
|
// Early return if already deleted.
|
|
832
|
-
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted(
|
|
884
|
+
const alreadyMarked = (0, BaseEntity_1.getInstanceData)(entity).markDeleted();
|
|
833
885
|
if (!alreadyMarked)
|
|
834
886
|
continue;
|
|
887
|
+
// This monotonic flag lets find hot paths skip scanning results until a delete has ever happened in this EM.
|
|
888
|
+
this.#hasAnyDeletes = true;
|
|
835
889
|
// Any derived fields that read this entity will need recalc-d
|
|
836
890
|
this.#rm.queueAllDownstreamFields(entity, "deleted");
|
|
837
891
|
// Synchronously unhook the entity if the relations are loaded
|
|
@@ -925,7 +979,7 @@ class EntityManager {
|
|
|
925
979
|
const pendingHooks = new Set();
|
|
926
980
|
// Subset of pendingFlush entities that had hooks invoked in a prior `runHooksOnPendingEntities`
|
|
927
981
|
const alreadyRanHooks = new Set();
|
|
928
|
-
findPendingFlushEntities(this
|
|
982
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
929
983
|
// If we're re-looping for AsyncReactiveField, make sure to bump updatedAt
|
|
930
984
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
931
985
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
@@ -964,7 +1018,7 @@ class EntityManager {
|
|
|
964
1018
|
hooksInvoked.add(e);
|
|
965
1019
|
pendingHooks.clear();
|
|
966
1020
|
// See if the hooks mutated any new, not-yet-hooksInvoked entities
|
|
967
|
-
findPendingFlushEntities(this
|
|
1021
|
+
findPendingFlushEntities(this.#maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
|
|
968
1022
|
// The final run of recalcPendingReactables could have left us with pending type errors and no entities in
|
|
969
1023
|
// pendingHooks. If so, we need to re-run recalcPendingTypeErrors to get those errors to transition into
|
|
970
1024
|
// suppressed errors so that we will fail after simpleValidation.
|
|
@@ -1099,7 +1153,7 @@ class EntityManager {
|
|
|
1099
1153
|
for (const e of createdThenDeleted)
|
|
1100
1154
|
(0, BaseEntity_1.getInstanceData)(e).fixupCreatedThenDeleted();
|
|
1101
1155
|
this.#merging?.clear();
|
|
1102
|
-
return [...allFlushedEntities];
|
|
1156
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1103
1157
|
}
|
|
1104
1158
|
catch (e) {
|
|
1105
1159
|
if (e instanceof RecursiveCollection_1.RecursiveCycleError) {
|
|
@@ -1122,8 +1176,9 @@ class EntityManager {
|
|
|
1122
1176
|
throw new index_1.ValidationErrors(message);
|
|
1123
1177
|
}
|
|
1124
1178
|
}
|
|
1125
|
-
if (e instanceof InMemoryRollbackError)
|
|
1126
|
-
return [...allFlushedEntities];
|
|
1179
|
+
if (e instanceof InMemoryRollbackError) {
|
|
1180
|
+
return [...allFlushedEntities].sort((a, b) => (0, BaseEntity_1.getInstanceData)(a).entityIndex - (0, BaseEntity_1.getInstanceData)(b).entityIndex);
|
|
1181
|
+
}
|
|
1127
1182
|
throw e;
|
|
1128
1183
|
}
|
|
1129
1184
|
finally {
|
|
@@ -1231,10 +1286,13 @@ class EntityManager {
|
|
|
1231
1286
|
*/
|
|
1232
1287
|
hydrate(type, rows, options) {
|
|
1233
1288
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1289
|
+
const taggedIdPrefix = `${maybeBaseMeta.tagName}:`;
|
|
1290
|
+
const overwriteExisting = options?.overwriteExisting === true;
|
|
1234
1291
|
let i = 0;
|
|
1235
1292
|
const entities = new Array(rows.length);
|
|
1236
1293
|
for (const row of rows) {
|
|
1237
|
-
const
|
|
1294
|
+
const id = row["id"];
|
|
1295
|
+
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : `${taggedIdPrefix}${id}`;
|
|
1238
1296
|
// See if this is already in our UoW
|
|
1239
1297
|
let entity = this.findExistingInstance(taggedId);
|
|
1240
1298
|
if (!entity) {
|
|
@@ -1243,23 +1301,36 @@ class EntityManager {
|
|
|
1243
1301
|
// Pass id as a hint that we're in hydrate mode
|
|
1244
1302
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1245
1303
|
(0, BaseEntity_1.getInstanceData)(entity).row = row;
|
|
1246
|
-
this.#doRegister(entity, taggedId);
|
|
1304
|
+
this.#doRegister(entity, taggedId, meta, true);
|
|
1247
1305
|
}
|
|
1248
|
-
else if (
|
|
1306
|
+
else if (overwriteExisting) {
|
|
1249
1307
|
// Usually if the entity already exists, we don't write over it, but in this case we assume that
|
|
1250
1308
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1251
1309
|
// First swap out the old row with the new row
|
|
1252
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1310
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1311
|
+
instanceData.row = row;
|
|
1253
1312
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1254
1313
|
// (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
|
-
|
|
1314
|
+
const { data } = instanceData;
|
|
1315
|
+
const dataKeys = Object.keys(data);
|
|
1316
|
+
if (dataKeys.length > 0) {
|
|
1317
|
+
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1318
|
+
const changedFields = entity.changes.fieldsWithoutRelations;
|
|
1319
|
+
if (changedFields.length === 0) {
|
|
1320
|
+
for (const fieldName of dataKeys) {
|
|
1321
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1322
|
+
serde.setOnEntity(data, row);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
for (const fieldName of dataKeys) {
|
|
1327
|
+
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1328
|
+
serde.setOnEntity(data, row);
|
|
1329
|
+
// Make the field look not-dirty
|
|
1330
|
+
if (changedFields.includes(fieldName)) {
|
|
1331
|
+
instanceData.markFieldClean(fieldName);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1263
1334
|
}
|
|
1264
1335
|
}
|
|
1265
1336
|
}
|
|
@@ -1268,8 +1339,9 @@ class EntityManager {
|
|
|
1268
1339
|
return entities;
|
|
1269
1340
|
}
|
|
1270
1341
|
touch(entityOrEntities) {
|
|
1271
|
-
for (const entity of (0, utils_1.toArray)(entityOrEntities))
|
|
1272
|
-
(0, BaseEntity_1.getInstanceData)(entity).
|
|
1342
|
+
for (const entity of (0, utils_1.toArray)(entityOrEntities)) {
|
|
1343
|
+
(0, BaseEntity_1.getInstanceData)(entity).markTouched();
|
|
1344
|
+
}
|
|
1273
1345
|
}
|
|
1274
1346
|
async recalc(entityOrEntities) {
|
|
1275
1347
|
// Look for async reactive fields
|
|
@@ -1346,9 +1418,16 @@ class EntityManager {
|
|
|
1346
1418
|
// Run the beforeDelete hook before we unhook the entity
|
|
1347
1419
|
const todos = (0, Todo_1.createTodos)(entities);
|
|
1348
1420
|
await beforeDelete(this.ctx, todos);
|
|
1349
|
-
// For all relations, unhook the entity from the other side
|
|
1350
|
-
//
|
|
1351
|
-
|
|
1421
|
+
// For all relations, unhook the entity from the other side; this append path is optimized for
|
|
1422
|
+
// large deletes with ~100k relations by avoiding `flatMap` intermediates and `concat` copies.
|
|
1423
|
+
for (const entity of entities) {
|
|
1424
|
+
const relations = (0, index_1.getRelations)(entity);
|
|
1425
|
+
const start = relationsToCleanup.length;
|
|
1426
|
+
relationsToCleanup.length += relations.length;
|
|
1427
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1428
|
+
relationsToCleanup[start + i] = relations[i];
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1352
1431
|
entities = this.#pendingDeletes;
|
|
1353
1432
|
this.#pendingDeletes = [];
|
|
1354
1433
|
}
|
|
@@ -1404,9 +1483,9 @@ class EntityManager {
|
|
|
1404
1483
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1405
1484
|
}
|
|
1406
1485
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1407
|
-
this.#indexManager.enableIndexingForType(meta, entities);
|
|
1486
|
+
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1408
1487
|
return (this.#indexManager
|
|
1409
|
-
.findMatching(meta, where)
|
|
1488
|
+
.findMatching(meta, entities, where)
|
|
1410
1489
|
// Still filter by `instanceof cstr` to handle subtyping
|
|
1411
1490
|
.filter((e) => e instanceof cstr && !e.isDeletedEntity));
|
|
1412
1491
|
}
|
|
@@ -1506,7 +1585,8 @@ class EntityManager {
|
|
|
1506
1585
|
continue;
|
|
1507
1586
|
const { originalData: oldOriginalData, data: oldData } = oldInstanceData;
|
|
1508
1587
|
const newEntity = mapEntity(oldEntity);
|
|
1509
|
-
const
|
|
1588
|
+
const newInstanceData = newEntity.__data;
|
|
1589
|
+
const { data: newData } = newInstanceData;
|
|
1510
1590
|
// for new entities, anything in `data` is changed and should be copied across. for existing entities, we
|
|
1511
1591
|
// only care about changed fields, which are enumerated by originalData
|
|
1512
1592
|
const maybeEntity = (value) => ((0, Entity_1.isEntity)(value) ? mapEntity(value) : value);
|
|
@@ -1514,7 +1594,7 @@ class EntityManager {
|
|
|
1514
1594
|
for (const field of fields) {
|
|
1515
1595
|
// copy over originalData so .changes is consistent across ems
|
|
1516
1596
|
if (field in oldOriginalData)
|
|
1517
|
-
|
|
1597
|
+
newInstanceData.markFieldDirty(field, maybeEntity(oldOriginalData[field]));
|
|
1518
1598
|
newData[field] = maybeEntity(oldData[field]);
|
|
1519
1599
|
}
|
|
1520
1600
|
}
|
|
@@ -1524,7 +1604,7 @@ class EntityManager {
|
|
|
1524
1604
|
const newEntity = mapEntity(oldEntity);
|
|
1525
1605
|
if (oldEntity.isDeletedEntity) {
|
|
1526
1606
|
// If the old entity was deleted, that should be persisted in the new em
|
|
1527
|
-
newEntity.__data.markDeleted(
|
|
1607
|
+
newEntity.__data.markDeleted();
|
|
1528
1608
|
// deleted entities will fail if you try to `get` their relations, so skip them since they should be cleared
|
|
1529
1609
|
// out regardless
|
|
1530
1610
|
continue;
|
|
@@ -1669,11 +1749,11 @@ class EntityManager {
|
|
|
1669
1749
|
}
|
|
1670
1750
|
}
|
|
1671
1751
|
/** Registers a newly-instantiated entity with our EntityManager; only called by #doCreate and hydrate. */
|
|
1672
|
-
#doRegister(entity, id) {
|
|
1752
|
+
#doRegister(entity, id, meta, skipDuplicateCheck = false) {
|
|
1673
1753
|
// Keep our indexes up to date...
|
|
1674
1754
|
const maybeId = id ?? entity.idTaggedMaybe;
|
|
1675
1755
|
if (maybeId) {
|
|
1676
|
-
if (this.findExistingInstance(maybeId) !== undefined) {
|
|
1756
|
+
if (!skipDuplicateCheck && this.findExistingInstance(maybeId) !== undefined) {
|
|
1677
1757
|
throw new Error(`Entity ${entity} has a duplicate instance already loaded`);
|
|
1678
1758
|
}
|
|
1679
1759
|
this.#entitiesById.set(maybeId, entity);
|
|
@@ -1682,8 +1762,9 @@ class EntityManager {
|
|
|
1682
1762
|
// Also register by the `a#1` style tagged string for new entities
|
|
1683
1763
|
this.#entitiesById.set(entity.toTaggedString(), entity);
|
|
1684
1764
|
}
|
|
1765
|
+
(0, BaseEntity_1.getInstanceData)(entity).entityIndex = this.#entitiesArray.length;
|
|
1685
1766
|
this.#entitiesArray.push(entity);
|
|
1686
|
-
|
|
1767
|
+
meta ??= (0, index_1.getMetadata)(entity);
|
|
1687
1768
|
const set = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1688
1769
|
if (set.length === 0)
|
|
1689
1770
|
this.#entitiesByTag.set(meta.tagName, set);
|
|
@@ -1697,8 +1778,6 @@ class EntityManager {
|
|
|
1697
1778
|
.join(", ");
|
|
1698
1779
|
throw new Error(`More than ${this.entityLimit} entities have been instantiated (top entities: ${topTypes})`);
|
|
1699
1780
|
}
|
|
1700
|
-
// If indexing is enabled for this type, add it...
|
|
1701
|
-
this.#indexManager.maybeIndexEntity(entity);
|
|
1702
1781
|
}
|
|
1703
1782
|
}
|
|
1704
1783
|
exports.EntityManager = EntityManager;
|
|
@@ -1782,14 +1861,14 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1782
1861
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1783
1862
|
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1784
1863
|
// Find each statically-declared reactive rule for the given entity type
|
|
1785
|
-
const rules =
|
|
1864
|
+
const rules = todo.metadata.reactiveRules;
|
|
1786
1865
|
return rules.map((rule) => {
|
|
1787
1866
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1788
1867
|
const triggered = entities.filter((e) => {
|
|
1789
1868
|
// If the rule is for a different subtype, skip it
|
|
1790
1869
|
if (!(e instanceof rule.source))
|
|
1791
1870
|
return false;
|
|
1792
|
-
// Any new-or-deleted entity fires every rule (
|
|
1871
|
+
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1793
1872
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1794
1873
|
return true;
|
|
1795
1874
|
// Otherwise see if the changed fields overlaps with the rule's fields
|
|
@@ -1807,15 +1886,15 @@ async function validateReactiveRules(em, logger, todos, joinRowTodos) {
|
|
|
1807
1886
|
const p2 = Object.values(joinRowTodos).flatMap((todo) => {
|
|
1808
1887
|
const entities = [...todo.newRows, ...todo.deletedRows].flatMap((jr) => Object.values(jr.columns));
|
|
1809
1888
|
// Do the first side
|
|
1810
|
-
const p1 =
|
|
1811
|
-
.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1889
|
+
const p1 = todo.m2m.meta
|
|
1890
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.fieldName))
|
|
1812
1891
|
.map((rule) => {
|
|
1813
1892
|
const triggered = entities.filter((e) => e instanceof todo.m2m.meta.cstr);
|
|
1814
1893
|
return followAndQueue(triggered, rule);
|
|
1815
1894
|
});
|
|
1816
1895
|
// And the second side
|
|
1817
|
-
const p2 =
|
|
1818
|
-
.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1896
|
+
const p2 = todo.m2m.otherMeta
|
|
1897
|
+
.reactiveRules.filter((rule) => rule.fields.includes(todo.m2m.otherFieldName))
|
|
1819
1898
|
.map((rule) => {
|
|
1820
1899
|
const triggered = entities.filter((e) => e instanceof todo.m2m.otherMeta.cstr);
|
|
1821
1900
|
return followAndQueue(triggered, rule);
|
|
@@ -2044,7 +2123,7 @@ function maybeBumpUpdatedAt(rm, todos, now) {
|
|
|
2044
2123
|
// it has changed. This is technically true, but this will break the oplock SQL generation,
|
|
2045
2124
|
// so force the field to be dirty.
|
|
2046
2125
|
const orm = (0, BaseEntity_1.getInstanceData)(e);
|
|
2047
|
-
orm.
|
|
2126
|
+
orm.markFieldDirty(updatedAt, (0, fields_1.getField)(e, updatedAt));
|
|
2048
2127
|
const serde = todo.metadata.fields[updatedAt].serde;
|
|
2049
2128
|
orm.data[updatedAt] = serde.mapFromNow(now);
|
|
2050
2129
|
rm.queueDownstreamReactables(e, updatedAt);
|
|
@@ -2081,17 +2160,13 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2081
2160
|
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, row.id)} must be instantiated via a subtype`);
|
|
2082
2161
|
}
|
|
2083
2162
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2084
|
-
return maybeBaseMeta.
|
|
2163
|
+
return maybeBaseMeta.subTypesByType.get(row.__class) ?? maybeBaseMeta;
|
|
2085
2164
|
}
|
|
2086
2165
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2087
2166
|
// Look for the STI discriminator value
|
|
2088
2167
|
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;
|
|
2168
|
+
const value = row[baseMeta.stiDiscriminatorColumnName];
|
|
2169
|
+
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2095
2170
|
}
|
|
2096
2171
|
else {
|
|
2097
2172
|
throw new Error("Unknown inheritance type");
|
|
@@ -2100,7 +2175,7 @@ function findConcreteMeta(maybeBaseMeta, row) {
|
|
|
2100
2175
|
/** Sets the `Animal.type` enum to the right subtype value. */
|
|
2101
2176
|
function setStiDiscriminatorValue(baseMeta, entity) {
|
|
2102
2177
|
const typeName = entity.constructor.name;
|
|
2103
|
-
const st = baseMeta.
|
|
2178
|
+
const st = baseMeta.subTypesByType.get(typeName);
|
|
2104
2179
|
if (st) {
|
|
2105
2180
|
const field = baseMeta.fields[baseMeta.stiDiscriminatorField];
|
|
2106
2181
|
const code = field.enumDetailType.findById(st.stiDiscriminatorValue).code;
|
|
@@ -2110,17 +2185,19 @@ function setStiDiscriminatorValue(baseMeta, entity) {
|
|
|
2110
2185
|
entity[baseMeta.stiDiscriminatorField] = undefined;
|
|
2111
2186
|
}
|
|
2112
2187
|
}
|
|
2113
|
-
function findPendingFlushEntities(
|
|
2114
|
-
for (const e of
|
|
2115
|
-
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2188
|
+
function findPendingFlushEntities(maybePendingFlushEntities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks) {
|
|
2189
|
+
for (const e of maybePendingFlushEntities) {
|
|
2190
|
+
if ((0, BaseEntity_1.getInstanceData)(e).pendingOperation === "none") {
|
|
2191
|
+
maybePendingFlushEntities.delete(e);
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
if (!hooksInvoked.has(e)) {
|
|
2195
|
+
pendingHooks.add(e);
|
|
2196
|
+
}
|
|
2197
|
+
else {
|
|
2198
|
+
alreadyRanHooks.add(e);
|
|
2123
2199
|
}
|
|
2200
|
+
pendingFlush.add(e);
|
|
2124
2201
|
}
|
|
2125
2202
|
}
|
|
2126
2203
|
/** Returns true if the caller explicitly asked `find` to use SQL pagination. */
|