joist-core 2.3.0-next.20 → 2.3.0-next.22
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/EntityManager.d.ts +1 -1
- package/build/EntityManager.js +164 -90
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.d.ts +6 -0
- package/build/EntityMetadata.js +4 -2
- package/build/EntityMetadata.js.map +1 -1
- package/build/IndexManager.d.ts +4 -2
- package/build/IndexManager.js +4 -2
- package/build/IndexManager.js.map +1 -1
- package/build/InstanceData.d.ts +8 -7
- package/build/InstanceData.js +57 -25
- package/build/InstanceData.js.map +1 -1
- package/build/ReactionsManager.d.ts +11 -1
- package/build/ReactionsManager.js +61 -34
- package/build/ReactionsManager.js.map +1 -1
- package/build/Todo.d.ts +1 -1
- package/build/Todo.js +16 -5
- package/build/Todo.js.map +1 -1
- package/build/batchloaders/populateBatchLoader.js +4 -2
- package/build/batchloaders/populateBatchLoader.js.map +1 -1
- package/build/configure.js +11 -0
- package/build/configure.js.map +1 -1
- package/build/dataloaders/findDataLoader.js +19 -19
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/drivers/EntityWriter.js +4 -3
- package/build/drivers/EntityWriter.js.map +1 -1
- package/build/newEntity.js +4 -2
- package/build/newEntity.js.map +1 -1
- package/build/relations/ReactiveManyToMany.js +1 -1
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
- package/build/relations/ReactiveReference.js +1 -1
- package/build/relations/ReactiveReference.js.map +1 -1
- package/build/utils.d.ts +2 -0
- package/build/utils.js +7 -0
- package/build/utils.js.map +1 -1
- package/package.json +2 -2
package/build/EntityManager.d.ts
CHANGED
|
@@ -27,8 +27,8 @@ import { PendingChange } from "./PendingChanges";
|
|
|
27
27
|
import { PluginManager } from "./PluginManager";
|
|
28
28
|
import { PreloadPlugin } from "./plugins/PreloadPlugin";
|
|
29
29
|
import { ReactionsManager } from "./ReactionsManager";
|
|
30
|
-
import { lazyColumnLoadOperation } from "./relations/LazyField";
|
|
31
30
|
import { Collection } from "./relations/Collection";
|
|
31
|
+
import { lazyColumnLoadOperation } from "./relations/LazyField";
|
|
32
32
|
import { OptsOf, OrderOf } from "./typeMap";
|
|
33
33
|
import { MaybePromise } from "./utils";
|
|
34
34
|
/**
|
package/build/EntityManager.js
CHANGED
|
@@ -51,8 +51,8 @@ const ReactionsManager_1 = require("./ReactionsManager");
|
|
|
51
51
|
const reactiveHints_1 = require("./reactiveHints");
|
|
52
52
|
const relations_1 = require("./relations");
|
|
53
53
|
const AsyncProperty_1 = require("./relations/AsyncProperty");
|
|
54
|
-
const LazyField_1 = require("./relations/LazyField");
|
|
55
54
|
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
55
|
+
const LazyField_1 = require("./relations/LazyField");
|
|
56
56
|
const RecursiveCollection_1 = require("./relations/RecursiveCollection");
|
|
57
57
|
const scopes_1 = require("./scopes");
|
|
58
58
|
const Todo_1 = require("./Todo");
|
|
@@ -725,14 +725,13 @@ class EntityManager {
|
|
|
725
725
|
}
|
|
726
726
|
async loadAll(type, _ids, hint) {
|
|
727
727
|
const meta = (0, index_1.getMetadata)(type);
|
|
728
|
-
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
729
|
-
|
|
728
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot;
|
|
729
|
+
// misses remember their tagged id + position so the all-hit path skips the `ids` copy entirely
|
|
730
730
|
const entities = new Array(_ids.length);
|
|
731
731
|
let idsToLoad;
|
|
732
732
|
let positionsToLoad;
|
|
733
733
|
for (let i = 0; i < _ids.length; i++) {
|
|
734
734
|
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
735
|
-
ids[i] = id;
|
|
736
735
|
const entity = this.findExistingInstance(id);
|
|
737
736
|
if (entity) {
|
|
738
737
|
entities[i] = entity;
|
|
@@ -742,25 +741,23 @@ class EntityManager {
|
|
|
742
741
|
(positionsToLoad ??= []).push(i);
|
|
743
742
|
}
|
|
744
743
|
}
|
|
745
|
-
if (idsToLoad
|
|
744
|
+
if (idsToLoad) {
|
|
746
745
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
747
746
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
748
747
|
.catch(function loadAll(err) {
|
|
749
748
|
throw appendStack(err, new Error());
|
|
750
749
|
});
|
|
751
|
-
|
|
752
|
-
|
|
750
|
+
let idsNotFound;
|
|
751
|
+
for (let j = 0; j < positionsToLoad.length; j++) {
|
|
752
|
+
const entity = this.findExistingInstance(idsToLoad[j]);
|
|
753
|
+
entities[positionsToLoad[j]] = entity;
|
|
754
|
+
if (entity === undefined)
|
|
755
|
+
(idsNotFound ??= []).push(idsToLoad[j]);
|
|
753
756
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
for (let i = 0; i < entities.length; i++) {
|
|
757
|
-
if (entities[i] === undefined) {
|
|
758
|
-
(idsNotFound ??= []).push(ids[i]);
|
|
757
|
+
if (idsNotFound) {
|
|
758
|
+
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
759
759
|
}
|
|
760
760
|
}
|
|
761
|
-
if (idsNotFound) {
|
|
762
|
-
throw new NotFoundError(`${idsNotFound.join(",")} were not found`);
|
|
763
|
-
}
|
|
764
761
|
const loadedEntities = entities;
|
|
765
762
|
if (hint) {
|
|
766
763
|
await this.populate(loadedEntities, hint);
|
|
@@ -775,36 +772,43 @@ class EntityManager {
|
|
|
775
772
|
}
|
|
776
773
|
async loadAllIfExists(type, _ids, hint) {
|
|
777
774
|
const meta = (0, index_1.getMetadata)(type);
|
|
778
|
-
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot
|
|
779
|
-
|
|
780
|
-
const
|
|
775
|
+
// Use pre-allocated arrays/for loops instead of `.filter`s since this can be a hot spot;
|
|
776
|
+
// the all-hit path returns the single pre-sized array without any id copies or rebuilds
|
|
777
|
+
const maybeEntities = new Array(_ids.length);
|
|
781
778
|
let idsToLoad;
|
|
779
|
+
let positionsToLoad;
|
|
782
780
|
// Ensure the ids are tagged, and find any not-yet-loaded
|
|
783
781
|
for (let i = 0; i < _ids.length; i++) {
|
|
784
782
|
const id = (0, index_1.tagId)(meta, _ids[i]);
|
|
785
|
-
ids[i] = id;
|
|
786
783
|
const entity = this.findExistingInstance(id);
|
|
787
784
|
if (entity) {
|
|
788
|
-
|
|
785
|
+
maybeEntities[i] = entity;
|
|
789
786
|
}
|
|
790
787
|
else {
|
|
791
788
|
(idsToLoad ??= []).push(id);
|
|
789
|
+
(positionsToLoad ??= []).push(i);
|
|
792
790
|
}
|
|
793
791
|
}
|
|
794
|
-
|
|
792
|
+
let entities;
|
|
793
|
+
if (idsToLoad) {
|
|
795
794
|
await (0, loadBatchLoader_1.loadBatchLoader)(this, meta)
|
|
796
795
|
.loadAll(idsToLoad.map((id) => ({ taggedId: id, hint })))
|
|
797
796
|
.catch(function loadAllIfExists(err) {
|
|
798
797
|
throw appendStack(err, new Error());
|
|
799
798
|
});
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
799
|
+
// Fill in whichever missed ids actually exist, then compact out the not-founds
|
|
800
|
+
for (let j = 0; j < positionsToLoad.length; j++) {
|
|
801
|
+
maybeEntities[positionsToLoad[j]] = this.findExistingInstance(idsToLoad[j]);
|
|
802
|
+
}
|
|
803
|
+
entities = [];
|
|
804
|
+
for (const entity of maybeEntities) {
|
|
805
|
+
if (entity !== undefined)
|
|
805
806
|
entities.push(entity);
|
|
806
807
|
}
|
|
807
808
|
}
|
|
809
|
+
else {
|
|
810
|
+
entities = maybeEntities;
|
|
811
|
+
}
|
|
808
812
|
if (hint) {
|
|
809
813
|
await this.populate(entities, hint);
|
|
810
814
|
}
|
|
@@ -1012,12 +1016,12 @@ class EntityManager {
|
|
|
1012
1016
|
// each time, so that for an INSERT-then-UPDATE the triggers don't think the
|
|
1013
1017
|
// UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
|
|
1014
1018
|
if (alreadyRanHooks.size > 0) {
|
|
1015
|
-
maybeBumpUpdatedAt(this.#rm, (0, Todo_1.createTodos)(
|
|
1019
|
+
maybeBumpUpdatedAt(this.#rm, (0, Todo_1.createTodos)(alreadyRanHooks), now);
|
|
1016
1020
|
}
|
|
1017
1021
|
// Run hooks in a series of loops until things "settle down"
|
|
1018
1022
|
while (pendingHooks.size > 0) {
|
|
1019
1023
|
await this.#fl.allowWrites(async () => {
|
|
1020
|
-
let todos = (0, Todo_1.createTodos)(
|
|
1024
|
+
let todos = (0, Todo_1.createTodos)(pendingHooks);
|
|
1021
1025
|
await (0, defaults_1.setAsyncDefaults)(suppressedDefaultTypeErrors, this.ctx, Todo_1.Todo.groupInsertsByTypeAndSubType(todos));
|
|
1022
1026
|
maybeBumpUpdatedAt(this.#rm, todos, now);
|
|
1023
1027
|
// Run our hooks
|
|
@@ -1235,13 +1239,17 @@ class EntityManager {
|
|
|
1235
1239
|
await afterCommit(this.ctx, allFlushedEntities);
|
|
1236
1240
|
// Update the `#orm` field to reflect the new state
|
|
1237
1241
|
for (const e of allFlushedEntities) {
|
|
1242
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(e);
|
|
1238
1243
|
if (e.isNewEntity && !e.isDeletedEntity)
|
|
1239
1244
|
this.#entitiesById.set(e.idTagged, e);
|
|
1240
|
-
|
|
1245
|
+
instanceData.resetAfterFlushed();
|
|
1241
1246
|
// Reset AsyncQueryProperties & LazyFields since DB state may have changed
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1247
|
+
const { relations } = instanceData;
|
|
1248
|
+
if (relations) {
|
|
1249
|
+
for (const rel of Object.values(relations)) {
|
|
1250
|
+
if (rel instanceof AsyncProperty_1.AsyncPropertyImpl || rel instanceof LazyField_1.LazyFieldImpl)
|
|
1251
|
+
rel.resetAfterFlush();
|
|
1252
|
+
}
|
|
1245
1253
|
}
|
|
1246
1254
|
}
|
|
1247
1255
|
// Update the joinRows refs to reflect the new state
|
|
@@ -1421,7 +1429,10 @@ class EntityManager {
|
|
|
1421
1429
|
const meta = findConcreteMeta(maybeBaseMeta, row);
|
|
1422
1430
|
// Pass id as a hint that we're in hydrate mode
|
|
1423
1431
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1424
|
-
(0, BaseEntity_1.getInstanceData)(entity)
|
|
1432
|
+
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1433
|
+
instanceData.row = row;
|
|
1434
|
+
// Seed the id to share the identity-map key string and skip the id serde on first read
|
|
1435
|
+
instanceData.data["id"] = taggedId;
|
|
1425
1436
|
this.#doRegister(entity, taggedId, meta, true);
|
|
1426
1437
|
}
|
|
1427
1438
|
else if (overwriteExisting) {
|
|
@@ -1434,17 +1445,25 @@ class EntityManager {
|
|
|
1434
1445
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1435
1446
|
const { data } = instanceData;
|
|
1436
1447
|
const dataKeys = Object.keys(data);
|
|
1437
|
-
|
|
1448
|
+
// `id` is seeded at hydrate and can never change, so it never needs refreshing
|
|
1449
|
+
if (dataKeys.length > ("id" in data ? 1 : 0)) {
|
|
1438
1450
|
const allFields = (0, index_1.getMetadata)(entity).allFields;
|
|
1439
|
-
|
|
1451
|
+
// For existing entities this is exactly `changes.fieldsWithoutRelations`, minus the proxy allocation
|
|
1452
|
+
const changedFields = instanceData.isNewEntity
|
|
1453
|
+
? entity.changes.fieldsWithoutRelations
|
|
1454
|
+
: Object.keys(instanceData.originalData);
|
|
1440
1455
|
if (changedFields.length === 0) {
|
|
1441
1456
|
for (const fieldName of dataKeys) {
|
|
1457
|
+
if (fieldName === "id")
|
|
1458
|
+
continue;
|
|
1442
1459
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1443
1460
|
serde.setOnEntity(data, row);
|
|
1444
1461
|
}
|
|
1445
1462
|
}
|
|
1446
1463
|
else {
|
|
1447
1464
|
for (const fieldName of dataKeys) {
|
|
1465
|
+
if (fieldName === "id")
|
|
1466
|
+
continue;
|
|
1448
1467
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1449
1468
|
serde.setOnEntity(data, row);
|
|
1450
1469
|
// Make the field look not-dirty
|
|
@@ -1600,15 +1619,18 @@ class EntityManager {
|
|
|
1600
1619
|
const entities = this.#entitiesByTag.get(meta.tagName) ?? [];
|
|
1601
1620
|
// Don't bother filtering if there's no where clause (particularly b/c IndexManager.findMatching
|
|
1602
1621
|
// really expects there to be at least 1 condition)
|
|
1603
|
-
if (
|
|
1622
|
+
if (!(0, utils_1.hasAnyKey)(where)) {
|
|
1604
1623
|
return entities.filter((e) => e instanceof cstr && !e.isDeletedEntity);
|
|
1605
1624
|
}
|
|
1606
1625
|
if (this.#indexManager.shouldIndexType(entities.length)) {
|
|
1607
1626
|
this.#indexManager.enableIndexingForType(meta, entities, where);
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1627
|
+
// Build the final array in one pass, still filtering `instanceof cstr` to handle subtyping
|
|
1628
|
+
const result = [];
|
|
1629
|
+
for (const e of this.#indexManager.findMatching(meta, entities, where)) {
|
|
1630
|
+
if (e instanceof cstr && !e.isDeletedEntity)
|
|
1631
|
+
result.push(e);
|
|
1632
|
+
}
|
|
1633
|
+
return result;
|
|
1612
1634
|
}
|
|
1613
1635
|
else {
|
|
1614
1636
|
return entities
|
|
@@ -1731,6 +1753,8 @@ class EntityManager {
|
|
|
1731
1753
|
continue;
|
|
1732
1754
|
}
|
|
1733
1755
|
const { relations } = oldEntity.__data;
|
|
1756
|
+
if (!relations)
|
|
1757
|
+
continue;
|
|
1734
1758
|
for (const [field, relation] of Object.entries(relations)) {
|
|
1735
1759
|
// With lazyRelation, custom relations are inserted into the `relations` map. Custom relations don't
|
|
1736
1760
|
// store any data, so we can ignore them by checking if the relation implements `import`
|
|
@@ -1982,9 +2006,12 @@ getRules = (meta) => meta.reactiveRules) {
|
|
|
1982
2006
|
});
|
|
1983
2007
|
}
|
|
1984
2008
|
const p1 = Object.values(todos).flatMap((todo) => {
|
|
1985
|
-
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1986
2009
|
// Find each statically-declared reactive rule for the given entity type
|
|
1987
2010
|
const rules = getRules(todo.metadata);
|
|
2011
|
+
// Skip the entities spread for rule-less types
|
|
2012
|
+
if (rules.length === 0)
|
|
2013
|
+
return [];
|
|
2014
|
+
const entities = [...todo.inserts, ...todo.updates, ...todo.deletes];
|
|
1988
2015
|
return rules.map((rule) => {
|
|
1989
2016
|
// Of all changed entities of this type, how many specifically trigger this rule?
|
|
1990
2017
|
const triggered = entities.filter((e) => {
|
|
@@ -1994,10 +2021,12 @@ getRules = (meta) => meta.reactiveRules) {
|
|
|
1994
2021
|
// Any new-or-deleted entity fires every rule (reactiveRules has already filtered out read-only)
|
|
1995
2022
|
if (e.isNewEntity || e.isDeletedEntity)
|
|
1996
2023
|
return true;
|
|
1997
|
-
// Otherwise see if the changed fields
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2024
|
+
// Otherwise see if the changed fields overlap with the rule's fields; for existing entities
|
|
2025
|
+
// `changes.fieldsWithoutRelations` is exactly `Object.keys(originalData)`, so probe originalData
|
|
2026
|
+
// directly instead of allocating a proxy + keys array per entity per rule.
|
|
2027
|
+
const { originalData } = (0, BaseEntity_1.getInstanceData)(e);
|
|
2028
|
+
for (const field of rule.fields) {
|
|
2029
|
+
if (field in originalData)
|
|
2001
2030
|
return true;
|
|
2002
2031
|
}
|
|
2003
2032
|
return false;
|
|
@@ -2042,21 +2071,34 @@ getRules = (meta) => meta.reactiveRules) {
|
|
|
2042
2071
|
async function validateSimpleRules(todos,
|
|
2043
2072
|
// Which config list to pull rules from, i.e. the pre-flush `rules` or the post-flush `commitRules`
|
|
2044
2073
|
getRules = (config) => config.rules) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2074
|
+
let p = undefined;
|
|
2075
|
+
for (const todoKey in todos) {
|
|
2076
|
+
const { inserts, updates } = todos[todoKey];
|
|
2077
|
+
for (const entities of [inserts, updates]) {
|
|
2078
|
+
for (const entity of entities) {
|
|
2079
|
+
if (entity.isDeletedEntity)
|
|
2080
|
+
continue;
|
|
2081
|
+
for (const m of (0, index_1.getBaseAndSelfMetas)((0, index_1.getMetadata)(entity))) {
|
|
2082
|
+
for (const rule of getRules(m.config.__data)) {
|
|
2083
|
+
if (rule.hint !== undefined)
|
|
2084
|
+
continue;
|
|
2085
|
+
(p ??= []).push(invokeRule(entity, rule.fn));
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (!p)
|
|
2092
|
+
return;
|
|
2055
2093
|
const errors = (0, utils_1.failIfAnyRejected)(await Promise.allSettled(p)).flat();
|
|
2056
2094
|
if (errors.length > 0) {
|
|
2057
2095
|
throw new index_1.ValidationErrors(errors);
|
|
2058
2096
|
}
|
|
2059
2097
|
}
|
|
2098
|
+
/** Invokes a single validation rule, coercing its result/sync throws into a promise. */
|
|
2099
|
+
async function invokeRule(entity, fn) {
|
|
2100
|
+
return coerceError(entity, await fn(entity));
|
|
2101
|
+
}
|
|
2060
2102
|
function driverBeforeBegin(em, txn) {
|
|
2061
2103
|
return Promise.all(getEmInternalApi(em).hooks.beforeBegin.map((fn) => fn(em, txn)));
|
|
2062
2104
|
}
|
|
@@ -2070,23 +2112,54 @@ function driverAfterCommit(em, txn) {
|
|
|
2070
2112
|
return Promise.all(getEmInternalApi(em).hooks.afterCommit.map((fn) => fn(em, txn)));
|
|
2071
2113
|
}
|
|
2072
2114
|
async function runHookOnTodos(ctx, hook, todos, keys) {
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2115
|
+
// Only collect entities for todos whose type hierarchy actually has this hook, so that
|
|
2116
|
+
// hook-less bulk flushes skip the per-entity array building entirely.
|
|
2117
|
+
let entities = undefined;
|
|
2118
|
+
for (const todoKey in todos) {
|
|
2119
|
+
const todo = todos[todoKey];
|
|
2120
|
+
if (!metaHasHook(todo.metadata, hook))
|
|
2121
|
+
continue;
|
|
2122
|
+
for (const k of keys) {
|
|
2123
|
+
for (const e of todo[k]) {
|
|
2124
|
+
if (k !== "deletes" && e.isDeletedEntity)
|
|
2125
|
+
continue;
|
|
2126
|
+
(entities ??= []).push(e);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (entities)
|
|
2131
|
+
return runHook(ctx, hook, entities);
|
|
2132
|
+
}
|
|
2133
|
+
/** Returns whether `meta`'s base/self/sub hierarchy has any `hook` functions registered. */
|
|
2134
|
+
function metaHasHook(meta, hook) {
|
|
2135
|
+
for (const m of (0, index_1.getBaseSelfAndSubMetas)(meta)) {
|
|
2136
|
+
if (m.config.__data.hooks[hook].length > 0)
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
return false;
|
|
2077
2140
|
}
|
|
2078
2141
|
async function runHook(ctx, hook, entities) {
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2142
|
+
let p = undefined;
|
|
2143
|
+
for (const entity of entities) {
|
|
2144
|
+
for (const m of (0, index_1.getBaseAndSelfMetas)((0, index_1.getMetadata)(entity))) {
|
|
2145
|
+
for (const fn of m.config.__data.hooks[hook]) {
|
|
2146
|
+
// Use an explicit `async` invoke to ensure all hooks are promises, i.e. so that a
|
|
2147
|
+
// non-promise hook blowing up doesn't orphan the others.
|
|
2148
|
+
(p ??= []).push(invokeHook(fn, entity, ctx));
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
if (!p)
|
|
2153
|
+
return;
|
|
2085
2154
|
// Use `allSettled` so that even if 1 hook blows up, we don't orphan other hooks mid-flush
|
|
2086
2155
|
// (causes weird errors when/if they try to access the EntityManager that has "moved on")
|
|
2087
2156
|
const results = await Promise.allSettled(p);
|
|
2088
2157
|
(0, utils_1.failIfAnyRejected)(results);
|
|
2089
2158
|
}
|
|
2159
|
+
/** Invokes a single hook fn, coercing sync throws into rejections. */
|
|
2160
|
+
async function invokeHook(fn, entity, ctx) {
|
|
2161
|
+
return fn(entity, ctx);
|
|
2162
|
+
}
|
|
2090
2163
|
function beforeDelete(ctx, todos) {
|
|
2091
2164
|
return runHookOnTodos(ctx, "beforeDelete", todos, ["deletes"]);
|
|
2092
2165
|
}
|
|
@@ -2106,16 +2179,21 @@ function afterValidation(ctx, todos) {
|
|
|
2106
2179
|
function entitiesFromTodos(entityTodos, joinRowTodos) {
|
|
2107
2180
|
const entities = new Set();
|
|
2108
2181
|
for (const todo of Object.values(entityTodos)) {
|
|
2109
|
-
[
|
|
2182
|
+
for (const list of [todo.inserts, todo.updates, todo.deletes]) {
|
|
2183
|
+
for (const entity of list)
|
|
2184
|
+
entities.add(entity);
|
|
2185
|
+
}
|
|
2110
2186
|
}
|
|
2111
2187
|
for (const todo of Object.values(joinRowTodos)) {
|
|
2112
|
-
[
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2188
|
+
for (const rows of [todo.newRows, todo.deletedRows]) {
|
|
2189
|
+
for (const row of rows) {
|
|
2190
|
+
// For enum m2ms, a column value is the enum's numeric id rather than an entity.
|
|
2191
|
+
for (const value of Object.values(row.columns)) {
|
|
2192
|
+
if ((0, Entity_1.isEntity)(value))
|
|
2193
|
+
entities.add(value);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2119
2197
|
}
|
|
2120
2198
|
return [...entities];
|
|
2121
2199
|
}
|
|
@@ -2149,23 +2227,19 @@ function coerceError(entity, maybeError) {
|
|
|
2149
2227
|
}
|
|
2150
2228
|
/** Evaluates each (non-async) derived field to see if it's value has changed. */
|
|
2151
2229
|
function recalcSynchronousDerivedFields(todos) {
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
derivedFields.forEach((fieldName) => {
|
|
2166
|
-
// setField will intelligently mark/not mark the field as dirty.
|
|
2167
|
-
(0, fields_1.setField)(entity, fieldName, entity[fieldName]);
|
|
2168
|
-
});
|
|
2230
|
+
for (const todoKey in todos) {
|
|
2231
|
+
const todo = todos[todoKey];
|
|
2232
|
+
for (const entities of [todo.inserts, todo.updates]) {
|
|
2233
|
+
for (const entity of entities) {
|
|
2234
|
+
if (entity.isDeletedEntity)
|
|
2235
|
+
continue;
|
|
2236
|
+
// Use the per-meta cached field list instead of rebuilding a map per hook loop
|
|
2237
|
+
for (const fieldName of (0, index_1.getMetadata)(entity).syncDerivedFields) {
|
|
2238
|
+
// setField will intelligently mark/not mark the field as dirty.
|
|
2239
|
+
(0, fields_1.setField)(entity, fieldName, entity[fieldName]);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2169
2243
|
}
|
|
2170
2244
|
}
|
|
2171
2245
|
/** Recursively crawls through `entity`, with the given populate `deep` hint, and adds anything found to `found`. */
|