joist-core 2.0.3-next.2 → 2.0.3-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/AliasAssigner.d.ts +3 -0
- package/build/AliasAssigner.d.ts.map +1 -1
- package/build/AliasAssigner.js +12 -3
- package/build/AliasAssigner.js.map +1 -1
- package/build/EntityManager.d.ts +20 -9
- package/build/EntityManager.d.ts.map +1 -1
- package/build/EntityManager.js +94 -58
- package/build/EntityManager.js.map +1 -1
- package/build/ReactionsManager.d.ts +1 -1
- package/build/ReactionsManager.d.ts.map +1 -1
- package/build/ReactionsManager.js +7 -5
- package/build/ReactionsManager.js.map +1 -1
- package/build/batchloaders/BatchLoader.d.ts +14 -0
- package/build/batchloaders/BatchLoader.d.ts.map +1 -0
- package/build/batchloaders/BatchLoader.js +49 -0
- package/build/batchloaders/BatchLoader.js.map +1 -0
- package/build/batchloaders/loadBatchLoader.d.ts +17 -0
- package/build/batchloaders/loadBatchLoader.d.ts.map +1 -0
- package/build/batchloaders/loadBatchLoader.js +55 -0
- package/build/batchloaders/loadBatchLoader.js.map +1 -0
- package/build/batchloaders/manyToManyBatchLoader.d.ts +7 -0
- package/build/batchloaders/manyToManyBatchLoader.d.ts.map +1 -0
- package/build/batchloaders/manyToManyBatchLoader.js +57 -0
- package/build/batchloaders/manyToManyBatchLoader.js.map +1 -0
- package/build/batchloaders/oneToManyBatchLoader.d.ts +7 -0
- package/build/batchloaders/oneToManyBatchLoader.d.ts.map +1 -0
- package/build/{dataloaders/oneToManyDataLoader.js → batchloaders/oneToManyBatchLoader.js} +9 -18
- package/build/batchloaders/oneToManyBatchLoader.js.map +1 -0
- package/build/batchloaders/oneToOneBatchLoader.d.ts +7 -0
- package/build/batchloaders/oneToOneBatchLoader.d.ts.map +1 -0
- package/build/{dataloaders/oneToOneDataLoader.js → batchloaders/oneToOneBatchLoader.js} +10 -16
- package/build/batchloaders/oneToOneBatchLoader.js.map +1 -0
- package/build/{dataloaders/populateDataLoader.d.ts → batchloaders/populateBatchLoader.d.ts} +5 -5
- package/build/batchloaders/populateBatchLoader.d.ts.map +1 -0
- package/build/{dataloaders/populateDataLoader.js → batchloaders/populateBatchLoader.js} +97 -54
- package/build/batchloaders/populateBatchLoader.js.map +1 -0
- package/build/batchloaders/recursiveChildrenBatchLoader.d.ts +7 -0
- package/build/batchloaders/recursiveChildrenBatchLoader.d.ts.map +1 -0
- package/build/{dataloaders/recursiveChildrenDataLoader.js → batchloaders/recursiveChildrenBatchLoader.js} +4 -6
- package/build/batchloaders/recursiveChildrenBatchLoader.js.map +1 -0
- package/build/batchloaders/recursiveParentsBatchLoader.d.ts +7 -0
- package/build/batchloaders/recursiveParentsBatchLoader.d.ts.map +1 -0
- package/build/{dataloaders/recursiveParentsDataLoader.js → batchloaders/recursiveParentsBatchLoader.js} +4 -5
- package/build/batchloaders/recursiveParentsBatchLoader.js.map +1 -0
- package/build/changes.js +2 -2
- package/build/changes.js.map +1 -1
- package/build/config.js +1 -1
- package/build/config.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.js +2 -2
- package/build/dataloaders/findIdsDataLoader.js.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.js +1 -1
- package/build/logging/ReactionLogger.d.ts +8 -4
- package/build/logging/ReactionLogger.d.ts.map +1 -1
- package/build/logging/ReactionLogger.js +11 -4
- package/build/logging/ReactionLogger.js.map +1 -1
- package/build/preloading/JsonAggregatePreloader.js +29 -24
- package/build/preloading/JsonAggregatePreloader.js.map +1 -1
- package/build/relations/Collection.d.ts +6 -1
- package/build/relations/Collection.d.ts.map +1 -1
- package/build/relations/Collection.js.map +1 -1
- package/build/relations/ManyToManyCollection.d.ts +8 -1
- package/build/relations/ManyToManyCollection.d.ts.map +1 -1
- package/build/relations/ManyToManyCollection.js +338 -130
- package/build/relations/ManyToManyCollection.js.map +1 -1
- package/build/relations/ManyToOneReference.d.ts +2 -6
- package/build/relations/ManyToOneReference.d.ts.map +1 -1
- package/build/relations/ManyToOneReference.js +115 -78
- package/build/relations/ManyToOneReference.js.map +1 -1
- package/build/relations/OneToManyCollection.d.ts +8 -4
- package/build/relations/OneToManyCollection.d.ts.map +1 -1
- package/build/relations/OneToManyCollection.js +415 -173
- package/build/relations/OneToManyCollection.js.map +1 -1
- package/build/relations/OneToOneReference.d.ts.map +1 -1
- package/build/relations/OneToOneReference.js +12 -7
- package/build/relations/OneToOneReference.js.map +1 -1
- package/build/relations/ReactiveManyToMany.js +4 -4
- package/build/relations/ReactiveManyToMany.js.map +1 -1
- package/build/relations/ReactiveManyToManyOtherSide.js +3 -3
- package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
- package/build/relations/RecursiveCollection.d.ts.map +1 -1
- package/build/relations/RecursiveCollection.js +22 -8
- package/build/relations/RecursiveCollection.js.map +1 -1
- package/package.json +3 -4
- package/build/dataloaders/loadDataLoader.d.ts +0 -11
- package/build/dataloaders/loadDataLoader.d.ts.map +0 -1
- package/build/dataloaders/loadDataLoader.js +0 -54
- package/build/dataloaders/loadDataLoader.js.map +0 -1
- package/build/dataloaders/manyToManyDataLoader.d.ts +0 -8
- package/build/dataloaders/manyToManyDataLoader.d.ts.map +0 -1
- package/build/dataloaders/manyToManyDataLoader.js +0 -74
- package/build/dataloaders/manyToManyDataLoader.js.map +0 -1
- package/build/dataloaders/oneToManyDataLoader.d.ts +0 -7
- package/build/dataloaders/oneToManyDataLoader.d.ts.map +0 -1
- package/build/dataloaders/oneToManyDataLoader.js.map +0 -1
- package/build/dataloaders/oneToOneDataLoader.d.ts +0 -7
- package/build/dataloaders/oneToOneDataLoader.d.ts.map +0 -1
- package/build/dataloaders/oneToOneDataLoader.js.map +0 -1
- package/build/dataloaders/populateDataLoader.d.ts.map +0 -1
- package/build/dataloaders/populateDataLoader.js.map +0 -1
- package/build/dataloaders/recursiveChildrenDataLoader.d.ts +0 -7
- package/build/dataloaders/recursiveChildrenDataLoader.d.ts.map +0 -1
- package/build/dataloaders/recursiveChildrenDataLoader.js.map +0 -1
- package/build/dataloaders/recursiveParentsDataLoader.d.ts +0 -7
- package/build/dataloaders/recursiveParentsDataLoader.d.ts.map +0 -1
- package/build/dataloaders/recursiveParentsDataLoader.js.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OneToManyCollection = void 0;
|
|
4
4
|
exports.hasMany = hasMany;
|
|
5
|
-
const
|
|
5
|
+
const oneToManyBatchLoader_1 = require("../batchloaders/oneToManyBatchLoader");
|
|
6
6
|
const oneToManyFindDataLoader_1 = require("../dataloaders/oneToManyFindDataLoader");
|
|
7
7
|
const index_1 = require("../index");
|
|
8
8
|
const newEntity_1 = require("../newEntity");
|
|
@@ -18,83 +18,76 @@ function hasMany() {
|
|
|
18
18
|
}
|
|
19
19
|
class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
20
20
|
#field;
|
|
21
|
-
|
|
22
|
-
#
|
|
21
|
+
#state;
|
|
22
|
+
#loadPromise;
|
|
23
23
|
// Constantly filtering+sorting our `.get` values can be surprisingly expensive if called
|
|
24
24
|
// when processing many entities/writing code that calls it repeatedly, so we cache it
|
|
25
25
|
// both the "without deleted" default (`getSorted`) and "all deleted" (`allSorted`).
|
|
26
26
|
#getSorted;
|
|
27
27
|
#allSorted;
|
|
28
|
-
// We _used_ to not track `#removed`, because if a child is removed in our unloaded state,
|
|
29
|
-
// when we load and get back the `child X has parent_id = our id` rows from the db, `loaderForCollection`
|
|
30
|
-
// groups the hydrated rows by their _current parent m2o field value_, which for a removed child will no
|
|
31
|
-
// longer be us, so it will effectively not show up in our post-load `loaded` array.
|
|
32
|
-
// However, now with join preloading, the getPreloadedRelation might still have pre-load removed children.
|
|
33
|
-
#added;
|
|
34
|
-
#removed;
|
|
35
|
-
#hasBeenSet = false;
|
|
36
28
|
constructor(entity, field) {
|
|
37
29
|
super(entity);
|
|
38
30
|
this.#field = field;
|
|
39
31
|
if ((0, index_1.getInstanceData)(entity).isOrWasNew) {
|
|
40
|
-
this.#
|
|
32
|
+
this.#state = new O2MLoadedState(this, [], false);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const { em } = entity;
|
|
36
|
+
// If any m2o.set(ourId) were made before our entity was loaded, pull in those changes
|
|
37
|
+
const pending = (0, index_1.getEmInternalApi)(em).pendingPercolate.get(entity.idTagged)?.get(this.fieldName);
|
|
38
|
+
if (pending) {
|
|
39
|
+
this.#state = new O2MUnloadedAddedRemovedState(this, pending.adds, pending.removes);
|
|
40
|
+
(0, index_1.getEmInternalApi)(em).pendingPercolate.get(entity.idTagged)?.delete(this.fieldName);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.#state = new O2MUnloadedPristineState(this);
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
// opts is an internal parameter
|
|
44
48
|
async load(opts = {}) {
|
|
45
49
|
(0, index_1.ensureNotDeleted)(this.entity, "pending");
|
|
46
|
-
if (this.#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw (0, index_1.appendStack)(err, new Error());
|
|
62
|
-
}));
|
|
63
|
-
// If we're reloading (i.e. `forceReload: true`), then we need to clear our caches
|
|
50
|
+
if (!this.#state.isLoaded || (opts.forceReload && !this.entity.isNewEntity)) {
|
|
51
|
+
const maybePreloaded = this.getPreloaded();
|
|
52
|
+
if (maybePreloaded) {
|
|
53
|
+
this.#state = this.#state.applyLoad(maybePreloaded);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
await (this.#loadPromise ??= (0, oneToManyBatchLoader_1.oneToManyBatchLoader)(this.entity.em, this).load(this.entity.idTagged))
|
|
57
|
+
.then(() => this.preload())
|
|
58
|
+
.catch(function load(err) {
|
|
59
|
+
throw (0, index_1.appendStack)(err, new Error());
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
this.#loadPromise = undefined;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
64
65
|
this.#getSorted = undefined;
|
|
65
66
|
this.#allSorted = undefined;
|
|
66
|
-
this.maybeAppendAddedBeforeLoaded();
|
|
67
67
|
}
|
|
68
68
|
return opts?.withDeleted ? this.getWithDeleted : this.get;
|
|
69
69
|
}
|
|
70
70
|
async find(id) {
|
|
71
71
|
(0, index_1.ensureNotDeleted)(this.entity, "pending");
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.catch(function find(err) {
|
|
84
|
-
throw (0, index_1.appendStack)(err, new Error());
|
|
85
|
-
});
|
|
86
|
-
}
|
|
72
|
+
const inMemory = this.#state.find(id);
|
|
73
|
+
if (inMemory)
|
|
74
|
+
return inMemory;
|
|
75
|
+
if (this.#state.isLoaded)
|
|
76
|
+
return undefined;
|
|
77
|
+
const key = `id=${id},${this.#field.otherColumnName}=${this.entity.id}`;
|
|
78
|
+
return (0, oneToManyFindDataLoader_1.oneToManyFindDataLoader)(this.entity.em, this)
|
|
79
|
+
.load(key)
|
|
80
|
+
.catch(function find(err) {
|
|
81
|
+
throw (0, index_1.appendStack)(err, new Error());
|
|
82
|
+
});
|
|
87
83
|
}
|
|
88
84
|
async includes(other) {
|
|
89
85
|
return (0, index_1.sameEntity)(this.entity, this.getOtherRelation(other).current());
|
|
90
86
|
}
|
|
91
87
|
get isLoaded() {
|
|
92
|
-
return this.#
|
|
88
|
+
return this.#state.isLoaded;
|
|
93
89
|
}
|
|
94
90
|
resetIsLoaded() {
|
|
95
|
-
// Invalidate our .get cache on any mutation; in theory we could do this only if this
|
|
96
|
-
// mutation was from an `other`, i.e. when entities are deleted or something in our
|
|
97
|
-
// `orderBy` changes (although we some RF `orderBy`s, which might be a pain to track).
|
|
98
91
|
this.#getSorted = undefined;
|
|
99
92
|
this.#allSorted = undefined;
|
|
100
93
|
}
|
|
@@ -102,21 +95,16 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
102
95
|
return !!this.getPreloaded();
|
|
103
96
|
}
|
|
104
97
|
preload() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (v === undefined)
|
|
111
|
-
return undefined;
|
|
112
|
-
const result = new Array(v.length);
|
|
113
|
-
for (let i = 0; i < v.length; i++)
|
|
114
|
-
result[i] = findEntity(v[i]);
|
|
115
|
-
return result;
|
|
98
|
+
const preloaded = this.getPreloaded();
|
|
99
|
+
if (preloaded) {
|
|
100
|
+
this.#state = this.#state.applyLoad(preloaded);
|
|
101
|
+
this.#getSorted = undefined;
|
|
102
|
+
this.#allSorted = undefined;
|
|
116
103
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
104
|
+
}
|
|
105
|
+
/** Copy another em's `source` collection into our state. */
|
|
106
|
+
import(source, findEntity) {
|
|
107
|
+
this.#state = source.#state.import(this, findEntity);
|
|
120
108
|
this.#getSorted = undefined;
|
|
121
109
|
this.#allSorted = undefined;
|
|
122
110
|
}
|
|
@@ -125,7 +113,7 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
125
113
|
(0, index_1.ensureNotDeleted)(this.entity, "pending");
|
|
126
114
|
if (this.#getSorted !== undefined)
|
|
127
115
|
return this.#getSorted;
|
|
128
|
-
this.#getSorted = Object.freeze(this.filterDeleted(this.doGet(), { withDeleted: false }));
|
|
116
|
+
this.#getSorted = Object.freeze(this.filterDeleted(this.#state.doGet(), { withDeleted: false }));
|
|
129
117
|
(0, index_1.getEmInternalApi)(this.entity.em).isLoadedCache.addNaive(this);
|
|
130
118
|
return this.#getSorted;
|
|
131
119
|
}
|
|
@@ -133,101 +121,64 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
133
121
|
(0, index_1.ensureNotDeleted)(this.entity, "pending");
|
|
134
122
|
if (this.#allSorted !== undefined)
|
|
135
123
|
return this.#allSorted;
|
|
136
|
-
this.#allSorted = Object.freeze(this.filterDeleted(this.doGet(), { withDeleted: true }));
|
|
124
|
+
this.#allSorted = Object.freeze(this.filterDeleted(this.#state.doGet(), { withDeleted: true }));
|
|
137
125
|
(0, index_1.getEmInternalApi)(this.entity.em).isLoadedCache.addNaive(this);
|
|
138
126
|
return this.#allSorted;
|
|
139
127
|
}
|
|
140
|
-
doGet() {
|
|
141
|
-
// This should only be callable in the type system if we've already resolved this to an instance
|
|
142
|
-
if (this.#loaded === undefined) {
|
|
143
|
-
throw new Error("get was called when not loaded");
|
|
144
|
-
}
|
|
145
|
-
return this.#loaded;
|
|
146
|
-
}
|
|
147
128
|
set(values) {
|
|
148
129
|
(0, index_1.ensureNotDeleted)(this.entity);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.#
|
|
153
|
-
// If we're changing `a1.books = [b1, b2]` to `a1.books = [b2]`, then implicitly delete the old book
|
|
154
|
-
const otherCannotChange = this.otherMeta.allFields[this.otherFieldName].immutable;
|
|
155
|
-
if (this.isCascadeDelete && otherCannotChange) {
|
|
156
|
-
const implicitlyDeleted = this.#loaded.filter((e) => !values.includes(e));
|
|
157
|
-
// The `em.delete` will internally invalidate our `#getSorted` / `#allSorted` caches, which will be dirty now
|
|
158
|
-
implicitlyDeleted.forEach((e) => this.entity.em.delete(e));
|
|
159
|
-
// Keep the implicitlyDeleted values for `getWithDeleted` to return
|
|
160
|
-
values = [...values, ...implicitlyDeleted];
|
|
161
|
-
}
|
|
162
|
-
// Make a copy for safe iteration
|
|
163
|
-
const loaded = [...this.#loaded];
|
|
164
|
-
// Remove old values
|
|
165
|
-
for (const other of loaded) {
|
|
166
|
-
if (!values.includes(other)) {
|
|
167
|
-
this.remove(other);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
for (const other of values) {
|
|
171
|
-
if (!loaded.includes(other)) {
|
|
172
|
-
this.add(other);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
130
|
+
this.#state = this.#state.set(values);
|
|
131
|
+
this.registerAsMutated();
|
|
132
|
+
this.#getSorted = undefined;
|
|
133
|
+
this.#allSorted = undefined;
|
|
175
134
|
}
|
|
176
135
|
add(other) {
|
|
177
136
|
(0, index_1.ensureNotDeleted)(this.entity);
|
|
178
|
-
this.#
|
|
179
|
-
|
|
180
|
-
(0, utils_1.maybeRemove)(this.#removed, other);
|
|
181
|
-
if (this.#loaded !== undefined) {
|
|
182
|
-
(0, utils_1.maybeAdd)(this.#loaded, other);
|
|
183
|
-
this.#getSorted = undefined;
|
|
184
|
-
this.#allSorted = undefined;
|
|
185
|
-
}
|
|
137
|
+
this.#state = this.#state.add(other);
|
|
138
|
+
this.percolateAdd(other);
|
|
186
139
|
this.registerAsMutated();
|
|
187
|
-
|
|
188
|
-
this
|
|
140
|
+
this.#getSorted = undefined;
|
|
141
|
+
this.#allSorted = undefined;
|
|
189
142
|
}
|
|
190
|
-
// We're not supported remove(other) because that might leave other.otherFieldName as undefined,
|
|
191
|
-
// which we don't know if that's valid or not, i.e. depending on whether the field is nullable.
|
|
192
143
|
remove(other, opts = { requireLoaded: true }) {
|
|
193
144
|
(0, index_1.ensureNotDeleted)(this.entity, "pending");
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
this.#removed ??= [];
|
|
198
|
-
(0, utils_1.maybeAdd)(this.#removed, other);
|
|
199
|
-
(0, utils_1.maybeRemove)(this.#added, other);
|
|
200
|
-
if (this.#loaded !== undefined) {
|
|
201
|
-
(0, utils_1.remove)(this.#loaded, other);
|
|
202
|
-
this.#getSorted = undefined;
|
|
203
|
-
this.#allSorted = undefined;
|
|
204
|
-
}
|
|
145
|
+
this.#state = this.#state.remove(other, opts);
|
|
146
|
+
this.percolateRemove(other);
|
|
205
147
|
this.registerAsMutated();
|
|
206
|
-
|
|
207
|
-
this
|
|
148
|
+
this.#getSorted = undefined;
|
|
149
|
+
this.#allSorted = undefined;
|
|
208
150
|
}
|
|
209
151
|
removeAll() {
|
|
210
152
|
(0, index_1.ensureNotDeleted)(this.entity);
|
|
211
|
-
if (this.#
|
|
153
|
+
if (!this.#state.isLoaded) {
|
|
212
154
|
throw new Error("removeAll was called when not loaded");
|
|
213
155
|
}
|
|
214
|
-
for (const other of [...this.#
|
|
156
|
+
for (const other of [...this.#state.doGet()]) {
|
|
215
157
|
this.remove(other);
|
|
216
158
|
}
|
|
217
159
|
}
|
|
218
160
|
// internal impl
|
|
161
|
+
/** @internal */
|
|
162
|
+
percolateAdd(other) {
|
|
163
|
+
this.getOtherRelation(other).set(this.entity);
|
|
164
|
+
}
|
|
165
|
+
/** @internal */
|
|
166
|
+
percolateRemove(other) {
|
|
167
|
+
this.getOtherRelation(other).set(undefined);
|
|
168
|
+
}
|
|
169
|
+
/** @internal */
|
|
170
|
+
registerAsMutated() {
|
|
171
|
+
(0, index_1.getEmInternalApi)(this.entity.em).mutatedCollections.add(this);
|
|
172
|
+
}
|
|
219
173
|
setFromOpts(others) {
|
|
220
|
-
this.#
|
|
174
|
+
this.#state = new O2MLoadedState(this, [], false);
|
|
221
175
|
others.forEach((o) => this.add(o));
|
|
222
176
|
}
|
|
223
177
|
removeIfLoaded(other) {
|
|
224
|
-
this.#
|
|
225
|
-
(0, utils_1.maybeRemove)(this.#added, other);
|
|
226
|
-
(0, utils_1.maybeAdd)(this.#removed, other);
|
|
227
|
-
if (this.#loaded !== undefined) {
|
|
228
|
-
(0, utils_1.remove)(this.#loaded, other);
|
|
229
|
-
}
|
|
178
|
+
this.#state = this.#state.removeIfLoaded(other);
|
|
230
179
|
this.registerAsMutated();
|
|
180
|
+
this.#getSorted = undefined;
|
|
181
|
+
this.#allSorted = undefined;
|
|
231
182
|
}
|
|
232
183
|
maybeCascadeDelete() {
|
|
233
184
|
if (this.isCascadeDelete) {
|
|
@@ -243,43 +194,13 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
243
194
|
current.forEach((other) => {
|
|
244
195
|
const m2o = this.getOtherRelation(other);
|
|
245
196
|
if ((0, index_1.maybeResolveReferenceToId)(m2o.current({ withDeleted: true })) === this.entity.idMaybe) {
|
|
246
|
-
// TODO What if other.otherFieldName is required/not-null?
|
|
247
197
|
m2o.set(undefined);
|
|
248
198
|
}
|
|
249
199
|
});
|
|
250
|
-
this.#
|
|
251
|
-
this.#added = [];
|
|
252
|
-
this.#removed = [];
|
|
200
|
+
this.#state = new O2MLoadedState(this, [], false);
|
|
253
201
|
this.#getSorted = undefined;
|
|
254
202
|
this.#allSorted = undefined;
|
|
255
203
|
}
|
|
256
|
-
maybeAppendAddedBeforeLoaded() {
|
|
257
|
-
// If our entity is not new, then entities in the EM might have been mutated to point
|
|
258
|
-
// to our foreign key (instead of our loaded instance), which means they should be in
|
|
259
|
-
// `addedBeforeLoaded` but are not.
|
|
260
|
-
//
|
|
261
|
-
// (Note that we don't have to handle the case for "removed before loaded" here because
|
|
262
|
-
// the oneToManyDataLoader already handles that; although maybe arguably that logic should
|
|
263
|
-
// be handled here?)
|
|
264
|
-
if (!this.entity.isNewEntity) {
|
|
265
|
-
const { em } = this.entity;
|
|
266
|
-
const pending = (0, index_1.getEmInternalApi)(em).pendingChildren.get(this.entity.idTagged)?.get(this.fieldName);
|
|
267
|
-
if (pending) {
|
|
268
|
-
(this.#added ??= []).push(...pending.adds);
|
|
269
|
-
(this.#removed ??= []).push(...pending.removes);
|
|
270
|
-
(0, utils_1.clear)(pending.adds);
|
|
271
|
-
(0, utils_1.clear)(pending.removes);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (this.#added) {
|
|
275
|
-
const newEntities = this.#added.filter((e) => !this.#loaded?.includes(e));
|
|
276
|
-
// Push on the end to better match the db order of "newer things come last"
|
|
277
|
-
this.#loaded.push(...newEntities);
|
|
278
|
-
}
|
|
279
|
-
if (this.#removed) {
|
|
280
|
-
this.#removed.forEach((e) => (0, utils_1.remove)(this.#loaded, e));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
204
|
// These are public to our internal implementation but not exposed in the Collection API
|
|
284
205
|
get fieldName() {
|
|
285
206
|
return this.#field.fieldName;
|
|
@@ -288,7 +209,7 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
288
209
|
return this.#field.otherFieldName;
|
|
289
210
|
}
|
|
290
211
|
current(opts) {
|
|
291
|
-
return this.filterDeleted(this.#
|
|
212
|
+
return this.filterDeleted(this.#state.current(), opts);
|
|
292
213
|
}
|
|
293
214
|
get meta() {
|
|
294
215
|
return (0, index_1.getMetadata)(this.entity);
|
|
@@ -297,15 +218,14 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
297
218
|
return (0, index_1.getMetadata)(this.entity).allFields[this.fieldName].otherMetadata();
|
|
298
219
|
}
|
|
299
220
|
get hasBeenSet() {
|
|
300
|
-
return this.#hasBeenSet;
|
|
221
|
+
return this.#state.hasBeenSet;
|
|
301
222
|
}
|
|
302
223
|
toString() {
|
|
303
224
|
return `OneToManyCollection(entity: ${this.entity}, fieldName: ${this.fieldName}, otherType: ${this.otherMeta.type}, otherFieldName: ${this.otherFieldName})`;
|
|
304
225
|
}
|
|
305
226
|
/** Called after `em.flush` to reset our dirty tracking. */
|
|
306
227
|
resetAddedRemoved() {
|
|
307
|
-
this.#
|
|
308
|
-
this.#removed = undefined;
|
|
228
|
+
this.#state.resetAddedRemoved();
|
|
309
229
|
}
|
|
310
230
|
/** Removes pending-hard-delete or soft-deleted entities, unless explicitly asked for. */
|
|
311
231
|
filterDeleted(entities, opts) {
|
|
@@ -330,18 +250,340 @@ class OneToManyCollection extends AbstractRelationImpl_1.AbstractRelationImpl {
|
|
|
330
250
|
return undefined;
|
|
331
251
|
return (0, index_1.getEmInternalApi)(this.entity.em).getPreloadedRelation(this.entity.idTagged, this.fieldName);
|
|
332
252
|
}
|
|
333
|
-
registerAsMutated() {
|
|
334
|
-
(0, index_1.getEmInternalApi)(this.entity.em).mutatedCollections.add(this);
|
|
335
|
-
}
|
|
336
253
|
// Exposed for changes
|
|
337
254
|
added() {
|
|
338
|
-
return this.#added
|
|
255
|
+
return this.#state.added();
|
|
339
256
|
}
|
|
340
257
|
removed() {
|
|
341
|
-
return this.#removed
|
|
258
|
+
return this.#state.removed();
|
|
342
259
|
}
|
|
343
260
|
[Relation_1.RelationT] = null;
|
|
344
261
|
[Relation_1.RelationU] = null;
|
|
345
262
|
}
|
|
346
263
|
exports.OneToManyCollection = OneToManyCollection;
|
|
264
|
+
/** Initial state for existing entities - no data loaded yet. */
|
|
265
|
+
class O2MUnloadedPristineState {
|
|
266
|
+
isLoaded = false;
|
|
267
|
+
hasBeenSet = false;
|
|
268
|
+
#o2m;
|
|
269
|
+
constructor(o2m) {
|
|
270
|
+
this.#o2m = o2m;
|
|
271
|
+
}
|
|
272
|
+
add(other) {
|
|
273
|
+
return new O2MUnloadedAddedRemovedState(this.#o2m, [other], []);
|
|
274
|
+
}
|
|
275
|
+
remove(_other, opts) {
|
|
276
|
+
if (opts.requireLoaded) {
|
|
277
|
+
throw new Error("remove was called when not loaded");
|
|
278
|
+
}
|
|
279
|
+
return new O2MUnloadedAddedRemovedState(this.#o2m, [], [_other]);
|
|
280
|
+
}
|
|
281
|
+
removeIfLoaded(other) {
|
|
282
|
+
return new O2MUnloadedAddedRemovedState(this.#o2m, [], [other]);
|
|
283
|
+
}
|
|
284
|
+
set(values) {
|
|
285
|
+
return new O2MPendingSetState(this.#o2m, values);
|
|
286
|
+
}
|
|
287
|
+
doGet() {
|
|
288
|
+
throw new Error("get was called when not loaded");
|
|
289
|
+
}
|
|
290
|
+
find(_id) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
applyLoad(dbEntities) {
|
|
294
|
+
return new O2MLoadedState(this.#o2m, dbEntities, false);
|
|
295
|
+
}
|
|
296
|
+
current() {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
import(target, _findEntity) {
|
|
300
|
+
return new O2MUnloadedPristineState(target);
|
|
301
|
+
}
|
|
302
|
+
added() {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
removed() {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
resetAddedRemoved() { }
|
|
309
|
+
}
|
|
310
|
+
/** State when add/remove called before load - tracks changes to merge later. */
|
|
311
|
+
class O2MUnloadedAddedRemovedState {
|
|
312
|
+
isLoaded = false;
|
|
313
|
+
hasBeenSet = false;
|
|
314
|
+
#o2m;
|
|
315
|
+
#added;
|
|
316
|
+
#removed;
|
|
317
|
+
constructor(o2m, added, removed) {
|
|
318
|
+
this.#o2m = o2m;
|
|
319
|
+
this.#added = new Set(added);
|
|
320
|
+
this.#removed = new Set(removed);
|
|
321
|
+
}
|
|
322
|
+
add(other) {
|
|
323
|
+
this.#added.add(other);
|
|
324
|
+
this.#removed.delete(other);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
remove(other, opts) {
|
|
328
|
+
if (opts.requireLoaded) {
|
|
329
|
+
throw new Error("remove was called when not loaded");
|
|
330
|
+
}
|
|
331
|
+
this.#removed.add(other);
|
|
332
|
+
this.#added.delete(other);
|
|
333
|
+
return this;
|
|
334
|
+
}
|
|
335
|
+
removeIfLoaded(other) {
|
|
336
|
+
this.#added.delete(other);
|
|
337
|
+
this.#removed.add(other);
|
|
338
|
+
return this;
|
|
339
|
+
}
|
|
340
|
+
set(values) {
|
|
341
|
+
// We should do the implicit deletion check?
|
|
342
|
+
// Should we keep #added/#removed?
|
|
343
|
+
return new O2MPendingSetState(this.#o2m, values);
|
|
344
|
+
}
|
|
345
|
+
doGet() {
|
|
346
|
+
throw new Error("get was called when not loaded");
|
|
347
|
+
}
|
|
348
|
+
find(id) {
|
|
349
|
+
for (const u of this.#added) {
|
|
350
|
+
if (!u.isNewEntity && u.id === id)
|
|
351
|
+
return u;
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
applyLoad(dbEntities) {
|
|
356
|
+
// Push added entities on the end to better match the db order of "newer things come last"
|
|
357
|
+
const loaded = new Set([...dbEntities]);
|
|
358
|
+
for (const e of this.#added)
|
|
359
|
+
loaded.add(e);
|
|
360
|
+
for (const e of this.#removed)
|
|
361
|
+
loaded.delete(e);
|
|
362
|
+
return new O2MLoadedState(this.#o2m, [...loaded], false, [...this.#added], [...this.#removed]);
|
|
363
|
+
}
|
|
364
|
+
current() {
|
|
365
|
+
return [...this.#added];
|
|
366
|
+
}
|
|
367
|
+
import(target, findEntity) {
|
|
368
|
+
return new O2MUnloadedAddedRemovedState(target, mapEntities([...this.#added], findEntity), mapEntities([...this.#removed], findEntity));
|
|
369
|
+
}
|
|
370
|
+
added() {
|
|
371
|
+
return [...this.#added];
|
|
372
|
+
}
|
|
373
|
+
removed() {
|
|
374
|
+
return [...this.#removed];
|
|
375
|
+
}
|
|
376
|
+
resetAddedRemoved() {
|
|
377
|
+
// This is called after em.flush, so these changes have been pushed into the db; we don't
|
|
378
|
+
// need to track them anymore b/c any future `o2m.load` will see up.
|
|
379
|
+
this.#added = new Set();
|
|
380
|
+
this.#removed = new Set();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** State when set() called before load - holds pending values to diff on load. */
|
|
384
|
+
class O2MPendingSetState {
|
|
385
|
+
isLoaded = false;
|
|
386
|
+
hasBeenSet = true;
|
|
387
|
+
#o2m;
|
|
388
|
+
#pendingValues;
|
|
389
|
+
constructor(o2m, pendingValues, skipPercolate = false) {
|
|
390
|
+
this.#o2m = o2m;
|
|
391
|
+
this.#pendingValues = new Set(pendingValues);
|
|
392
|
+
// We percolate on normal set() calls to immediately update the other side's m2o references.
|
|
393
|
+
// We skip percolation on import() because those entities already have correct references
|
|
394
|
+
// in the cloned EntityManager.
|
|
395
|
+
if (!skipPercolate)
|
|
396
|
+
this.#percolate();
|
|
397
|
+
(0, index_1.getEmInternalApi)(o2m.entity.em).pendingLoads.add(o2m);
|
|
398
|
+
}
|
|
399
|
+
add(other) {
|
|
400
|
+
this.#pendingValues.add(other);
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
remove(other, _opts) {
|
|
404
|
+
this.#pendingValues.delete(other);
|
|
405
|
+
return this;
|
|
406
|
+
}
|
|
407
|
+
removeIfLoaded(other) {
|
|
408
|
+
this.#pendingValues.delete(other);
|
|
409
|
+
return this;
|
|
410
|
+
}
|
|
411
|
+
set(values) {
|
|
412
|
+
this.#pendingValues = new Set(values);
|
|
413
|
+
this.#percolate();
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
doGet() {
|
|
417
|
+
return [...this.#pendingValues];
|
|
418
|
+
}
|
|
419
|
+
find(id) {
|
|
420
|
+
for (const u of this.#pendingValues) {
|
|
421
|
+
if (!u.isNewEntity && u.id === id)
|
|
422
|
+
return u;
|
|
423
|
+
}
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
applyLoad(dbEntities) {
|
|
427
|
+
const o2m = this.#o2m;
|
|
428
|
+
const entity = o2m.entity;
|
|
429
|
+
// Handle cascade delete + immutable: entities removed from the collection that
|
|
430
|
+
// cannot have their FK changed should be deleted instead
|
|
431
|
+
const otherCannotChange = o2m.otherMeta.allFields[o2m.otherFieldName].immutable;
|
|
432
|
+
const isCascade = (0, AbstractRelationImpl_1.isCascadeDelete)(o2m, o2m.fieldName);
|
|
433
|
+
// Calc our added/removed so that `.changes` in the new loaded state are correct
|
|
434
|
+
const dbSet = new Set(dbEntities);
|
|
435
|
+
const added = [];
|
|
436
|
+
for (const other of this.#pendingValues) {
|
|
437
|
+
if (!dbSet.has(other))
|
|
438
|
+
added.push(other);
|
|
439
|
+
}
|
|
440
|
+
const removed = [];
|
|
441
|
+
for (const other of dbEntities) {
|
|
442
|
+
if (!this.#pendingValues.has(other)) {
|
|
443
|
+
removed.push(other);
|
|
444
|
+
if (isCascade && otherCannotChange) {
|
|
445
|
+
entity.em.delete(other);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
o2m.percolateRemove(other);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return new O2MLoadedState(o2m, [...this.#pendingValues], true, added, removed);
|
|
453
|
+
}
|
|
454
|
+
current() {
|
|
455
|
+
return [...this.#pendingValues];
|
|
456
|
+
}
|
|
457
|
+
import(target, findEntity) {
|
|
458
|
+
return new O2MPendingSetState(target, mapEntities([...this.#pendingValues], findEntity), true);
|
|
459
|
+
}
|
|
460
|
+
added() {
|
|
461
|
+
return [...this.#pendingValues];
|
|
462
|
+
}
|
|
463
|
+
removed() {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
resetAddedRemoved() { }
|
|
467
|
+
#percolate() {
|
|
468
|
+
const o2m = this.#o2m;
|
|
469
|
+
// All new values, we immediately tell them we're their new parent
|
|
470
|
+
for (const other of this.#pendingValues) {
|
|
471
|
+
o2m.percolateAdd(other);
|
|
472
|
+
}
|
|
473
|
+
// If we're an existing entity, we can scan for any in-memory entities that were previously
|
|
474
|
+
// pointing to us, but now should not
|
|
475
|
+
if (!o2m.entity.isNewEntity) {
|
|
476
|
+
for (const other of o2m.entity.em.getEntities(o2m.otherMeta.cstr)) {
|
|
477
|
+
if (this.#pendingValues.has(other))
|
|
478
|
+
continue;
|
|
479
|
+
if ((0, index_1.sameEntity)(other[o2m.otherFieldName].current(), o2m.entity)) {
|
|
480
|
+
o2m.percolateRemove(other);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/** State when collection is loaded - all operations work directly on loaded array. */
|
|
487
|
+
class O2MLoadedState {
|
|
488
|
+
isLoaded = true;
|
|
489
|
+
#o2m;
|
|
490
|
+
#loaded;
|
|
491
|
+
#added;
|
|
492
|
+
#removed;
|
|
493
|
+
#hasBeenSet;
|
|
494
|
+
constructor(o2m, loaded, hasBeenSet, added = [], removed = []) {
|
|
495
|
+
this.#o2m = o2m;
|
|
496
|
+
this.#loaded = new Set(loaded);
|
|
497
|
+
this.#added = new Set(added);
|
|
498
|
+
this.#removed = new Set(removed);
|
|
499
|
+
this.#hasBeenSet = hasBeenSet;
|
|
500
|
+
}
|
|
501
|
+
get hasBeenSet() {
|
|
502
|
+
return this.#hasBeenSet;
|
|
503
|
+
}
|
|
504
|
+
add(other) {
|
|
505
|
+
this.#added.add(other);
|
|
506
|
+
this.#removed.delete(other);
|
|
507
|
+
this.#loaded.add(other);
|
|
508
|
+
return this;
|
|
509
|
+
}
|
|
510
|
+
remove(other, _opts) {
|
|
511
|
+
this.#removed.add(other);
|
|
512
|
+
this.#added.delete(other);
|
|
513
|
+
this.#loaded.delete(other);
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
removeIfLoaded(other) {
|
|
517
|
+
this.#added.delete(other);
|
|
518
|
+
this.#removed.add(other);
|
|
519
|
+
this.#loaded.delete(other);
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
set(values) {
|
|
523
|
+
this.#hasBeenSet = true;
|
|
524
|
+
let valuesSet = new Set(values);
|
|
525
|
+
const o2m = this.#o2m;
|
|
526
|
+
const otherCannotChange = o2m.otherMeta.allFields[o2m.otherFieldName].immutable;
|
|
527
|
+
const isCascade = (0, AbstractRelationImpl_1.isCascadeDelete)(o2m, o2m.fieldName);
|
|
528
|
+
if (isCascade && otherCannotChange) {
|
|
529
|
+
const implicitlyDeleted = [...this.#loaded].filter((e) => !valuesSet.has(e));
|
|
530
|
+
implicitlyDeleted.forEach((e) => o2m.entity.em.delete(e));
|
|
531
|
+
// ...we restore the implicitlyDeleted for getWithDeleted
|
|
532
|
+
values = [...values, ...implicitlyDeleted];
|
|
533
|
+
valuesSet = new Set(values);
|
|
534
|
+
}
|
|
535
|
+
const loaded = new Set(this.#loaded);
|
|
536
|
+
for (const other of loaded) {
|
|
537
|
+
if (!valuesSet.has(other))
|
|
538
|
+
o2m.remove(other);
|
|
539
|
+
}
|
|
540
|
+
for (const other of values) {
|
|
541
|
+
if (!loaded.has(other))
|
|
542
|
+
o2m.add(other);
|
|
543
|
+
}
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
doGet() {
|
|
547
|
+
return [...this.#loaded];
|
|
548
|
+
}
|
|
549
|
+
find(id) {
|
|
550
|
+
for (const other of this.#loaded) {
|
|
551
|
+
if (!other.isNewEntity && other.id === id)
|
|
552
|
+
return other;
|
|
553
|
+
}
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
applyLoad(dbEntities) {
|
|
557
|
+
// On forceReload, replace loaded but preserve pending adds/removes
|
|
558
|
+
const loaded = new Set(dbEntities);
|
|
559
|
+
for (const e of this.#added)
|
|
560
|
+
loaded.add(e);
|
|
561
|
+
for (const e of this.#removed)
|
|
562
|
+
loaded.delete(e);
|
|
563
|
+
this.#loaded = loaded;
|
|
564
|
+
return this;
|
|
565
|
+
}
|
|
566
|
+
current() {
|
|
567
|
+
return [...this.#loaded];
|
|
568
|
+
}
|
|
569
|
+
import(target, findEntity) {
|
|
570
|
+
return new O2MLoadedState(target, mapEntities([...this.#loaded], findEntity), this.#hasBeenSet, mapEntities([...this.#added], findEntity), mapEntities([...this.#removed], findEntity));
|
|
571
|
+
}
|
|
572
|
+
added() {
|
|
573
|
+
return [...this.#added];
|
|
574
|
+
}
|
|
575
|
+
removed() {
|
|
576
|
+
return [...this.#removed];
|
|
577
|
+
}
|
|
578
|
+
resetAddedRemoved() {
|
|
579
|
+
this.#added = new Set();
|
|
580
|
+
this.#removed = new Set();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function mapEntities(entities, findEntity) {
|
|
584
|
+
const result = new Array(entities.length);
|
|
585
|
+
for (let i = 0; i < entities.length; i++)
|
|
586
|
+
result[i] = findEntity(entities[i]);
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
347
589
|
//# sourceMappingURL=OneToManyCollection.js.map
|