joist-orm 0.1.538 → 1.0.0
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 → src/BaseEntity.d.ts} +2 -1
- package/build/{BaseEntity.js → src/BaseEntity.js} +13 -9
- package/build/src/BaseEntity.js.map +1 -0
- package/build/{EntityManager.d.ts → src/EntityManager.d.ts} +139 -110
- package/build/{EntityManager.js → src/EntityManager.js} +281 -262
- package/build/src/EntityManager.js.map +1 -0
- package/build/{QueryBuilder.d.ts → src/QueryBuilder.d.ts} +53 -3
- package/build/src/QueryBuilder.js +341 -0
- package/build/src/QueryBuilder.js.map +1 -0
- package/build/src/Todo.d.ts +25 -0
- package/build/src/Todo.js +52 -0
- package/build/src/Todo.js.map +1 -0
- package/build/src/changes.d.ts +34 -0
- package/build/src/changes.js +37 -0
- package/build/src/changes.js.map +1 -0
- package/build/src/config.d.ts +43 -0
- package/build/src/config.js +114 -0
- package/build/src/config.js.map +1 -0
- package/build/{createOrUpdatePartial.d.ts → src/createOrUpdatePartial.d.ts} +2 -1
- package/build/{createOrUpdatePartial.js → src/createOrUpdatePartial.js} +42 -10
- package/build/src/createOrUpdatePartial.js.map +1 -0
- package/build/src/dataloaders/findDataLoader.d.ts +5 -0
- package/build/src/dataloaders/findDataLoader.js +28 -0
- package/build/src/dataloaders/findDataLoader.js.map +1 -0
- package/build/src/dataloaders/loadDataLoader.d.ts +3 -0
- package/build/src/dataloaders/loadDataLoader.js +37 -0
- package/build/src/dataloaders/loadDataLoader.js.map +1 -0
- package/build/src/dataloaders/manyToManyDataLoader.d.ts +5 -0
- package/build/src/dataloaders/manyToManyDataLoader.js +78 -0
- package/build/src/dataloaders/manyToManyDataLoader.js.map +1 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.d.ts +5 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.js +33 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToManyDataLoader.d.ts +4 -0
- package/build/src/dataloaders/oneToManyDataLoader.js +40 -0
- package/build/src/dataloaders/oneToManyDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.d.ts +5 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.js +32 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToOneDataLoader.d.ts +4 -0
- package/build/src/dataloaders/oneToOneDataLoader.js +40 -0
- package/build/src/dataloaders/oneToOneDataLoader.js.map +1 -0
- package/build/src/drivers/IdAssigner.d.ts +33 -0
- package/build/src/drivers/IdAssigner.js +106 -0
- package/build/src/drivers/IdAssigner.js.map +1 -0
- package/build/src/drivers/InMemoryDriver.d.ts +29 -0
- package/build/src/drivers/InMemoryDriver.js +306 -0
- package/build/src/drivers/InMemoryDriver.js.map +1 -0
- package/build/src/drivers/PostgresDriver.d.ts +40 -0
- package/build/src/drivers/PostgresDriver.js +376 -0
- package/build/src/drivers/PostgresDriver.js.map +1 -0
- package/build/src/drivers/driver.d.ts +23 -0
- package/build/src/drivers/driver.js +3 -0
- package/build/src/drivers/driver.js.map +1 -0
- package/build/src/drivers/index.d.ts +4 -0
- package/build/src/drivers/index.js +17 -0
- package/build/src/drivers/index.js.map +1 -0
- package/build/{getProperties.d.ts → src/getProperties.d.ts} +0 -0
- package/build/{getProperties.js → src/getProperties.js} +1 -1
- package/build/src/getProperties.js.map +1 -0
- package/build/src/index.d.ts +62 -0
- package/build/src/index.js +263 -0
- package/build/src/index.js.map +1 -0
- package/build/src/keys.d.ts +30 -0
- package/build/{keys.js → src/keys.js} +48 -16
- package/build/src/keys.js.map +1 -0
- package/build/{loadLens.d.ts → src/loadLens.d.ts} +2 -2
- package/build/{loadLens.js → src/loadLens.js} +1 -1
- package/build/src/loadLens.js.map +1 -0
- package/build/src/loaded.d.ts +49 -0
- package/build/src/loaded.js +9 -0
- package/build/src/loaded.js.map +1 -0
- package/build/{newTestInstance.d.ts → src/newTestInstance.d.ts} +37 -3
- package/build/src/newTestInstance.js +342 -0
- package/build/src/newTestInstance.js.map +1 -0
- package/build/{collections → src/relations}/AbstractRelationImpl.d.ts +6 -5
- package/build/{collections → src/relations}/AbstractRelationImpl.js +0 -0
- package/build/src/relations/AbstractRelationImpl.js.map +1 -0
- package/build/src/relations/Collection.d.ts +26 -0
- package/build/src/relations/Collection.js +19 -0
- package/build/src/relations/Collection.js.map +1 -0
- package/build/{collections → src/relations}/CustomCollection.d.ts +6 -2
- package/build/{collections → src/relations}/CustomCollection.js +17 -9
- package/build/src/relations/CustomCollection.js.map +1 -0
- package/build/{collections → src/relations}/CustomReference.d.ts +7 -2
- package/build/{collections → src/relations}/CustomReference.js +16 -9
- package/build/src/relations/CustomReference.js.map +1 -0
- package/build/src/relations/LargeCollection.d.ts +17 -0
- package/build/src/relations/LargeCollection.js +3 -0
- package/build/src/relations/LargeCollection.js.map +1 -0
- package/build/{collections → src/relations}/ManyToManyCollection.d.ts +9 -2
- package/build/src/relations/ManyToManyCollection.js +249 -0
- package/build/src/relations/ManyToManyCollection.js.map +1 -0
- package/build/src/relations/ManyToManyLargeCollection.d.ts +25 -0
- package/build/src/relations/ManyToManyLargeCollection.js +97 -0
- package/build/src/relations/ManyToManyLargeCollection.js.map +1 -0
- package/build/src/relations/ManyToOneReference.d.ts +77 -0
- package/build/{collections → src/relations}/ManyToOneReference.js +101 -48
- package/build/src/relations/ManyToOneReference.js.map +1 -0
- package/build/{collections → src/relations}/OneToManyCollection.d.ts +10 -2
- package/build/{collections → src/relations}/OneToManyCollection.js +54 -59
- package/build/src/relations/OneToManyCollection.js.map +1 -0
- package/build/src/relations/OneToManyLargeCollection.d.ts +25 -0
- package/build/src/relations/OneToManyLargeCollection.js +83 -0
- package/build/src/relations/OneToManyLargeCollection.js.map +1 -0
- package/build/src/relations/OneToOneReference.d.ts +82 -0
- package/build/src/relations/OneToOneReference.js +168 -0
- package/build/src/relations/OneToOneReference.js.map +1 -0
- package/build/src/relations/PolymorphicReference.d.ts +69 -0
- package/build/src/relations/PolymorphicReference.js +210 -0
- package/build/src/relations/PolymorphicReference.js.map +1 -0
- package/build/src/relations/Reference.d.ts +29 -0
- package/build/src/relations/Reference.js +23 -0
- package/build/src/relations/Reference.js.map +1 -0
- package/build/src/relations/Relation.d.ts +10 -0
- package/build/src/relations/Relation.js +13 -0
- package/build/src/relations/Relation.js.map +1 -0
- package/build/src/relations/hasAsyncProperty.d.ts +36 -0
- package/build/src/relations/hasAsyncProperty.js +55 -0
- package/build/src/relations/hasAsyncProperty.js.map +1 -0
- package/build/{collections → src/relations}/hasManyDerived.d.ts +2 -1
- package/build/{collections → src/relations}/hasManyDerived.js +1 -1
- package/build/src/relations/hasManyDerived.js.map +1 -0
- package/build/{collections → src/relations}/hasManyThrough.d.ts +0 -0
- package/build/{collections → src/relations}/hasManyThrough.js +2 -2
- package/build/src/relations/hasManyThrough.js.map +1 -0
- package/build/{collections → src/relations}/hasOneDerived.d.ts +3 -2
- package/build/{collections → src/relations}/hasOneDerived.js +1 -1
- package/build/src/relations/hasOneDerived.js.map +1 -0
- package/build/{collections → src/relations}/hasOneThrough.d.ts +0 -0
- package/build/{collections → src/relations}/hasOneThrough.js +2 -2
- package/build/src/relations/hasOneThrough.js.map +1 -0
- package/build/src/relations/index.d.ts +18 -0
- package/build/src/relations/index.js +53 -0
- package/build/src/relations/index.js.map +1 -0
- package/build/{reverseHint.d.ts → src/reverseHint.d.ts} +2 -1
- package/build/{reverseHint.js → src/reverseHint.js} +13 -9
- package/build/src/reverseHint.js.map +1 -0
- package/build/src/rules.d.ts +23 -0
- package/build/src/rules.js +23 -0
- package/build/src/rules.js.map +1 -0
- package/build/src/serde.d.ts +121 -0
- package/build/src/serde.js +190 -0
- package/build/src/serde.js.map +1 -0
- package/build/{utils.d.ts → src/utils.d.ts} +2 -0
- package/build/{utils.js → src/utils.js} +10 -1
- package/build/src/utils.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -15
- package/build/BaseEntity.js.map +0 -1
- package/build/EntityManager.js.map +0 -1
- package/build/EntityPersister.d.ts +0 -30
- package/build/EntityPersister.js +0 -197
- package/build/EntityPersister.js.map +0 -1
- package/build/QueryBuilder.js +0 -195
- package/build/QueryBuilder.js.map +0 -1
- package/build/changes.d.ts +0 -23
- package/build/changes.js +0 -14
- package/build/changes.js.map +0 -1
- package/build/collections/AbstractRelationImpl.js.map +0 -1
- package/build/collections/CustomCollection.js.map +0 -1
- package/build/collections/CustomReference.js.map +0 -1
- package/build/collections/ManyToManyCollection.js +0 -288
- package/build/collections/ManyToManyCollection.js.map +0 -1
- package/build/collections/ManyToOneReference.d.ts +0 -50
- package/build/collections/ManyToOneReference.js.map +0 -1
- package/build/collections/OneToManyCollection.js.map +0 -1
- package/build/collections/OneToOneReference.d.ts +0 -51
- package/build/collections/OneToOneReference.js +0 -132
- package/build/collections/OneToOneReference.js.map +0 -1
- package/build/collections/hasManyDerived.js.map +0 -1
- package/build/collections/hasManyThrough.js.map +0 -1
- package/build/collections/hasOneDerived.js.map +0 -1
- package/build/collections/hasOneThrough.js.map +0 -1
- package/build/collections/index.d.ts +0 -19
- package/build/collections/index.js +0 -49
- package/build/collections/index.js.map +0 -1
- package/build/createOrUpdatePartial.js.map +0 -1
- package/build/getProperties.js.map +0 -1
- package/build/index.d.ts +0 -140
- package/build/index.js +0 -278
- package/build/index.js.map +0 -1
- package/build/keys.d.ts +0 -21
- package/build/keys.js.map +0 -1
- package/build/loadLens.js.map +0 -1
- package/build/newTestInstance.js +0 -153
- package/build/newTestInstance.js.map +0 -1
- package/build/reverseHint.js.map +0 -1
- package/build/serde.d.ts +0 -47
- package/build/serde.js +0 -93
- package/build/serde.js.map +0 -1
- package/build/utils.js.map +0 -1
- package/package.json.bak +0 -27
- package/src/BaseEntity.ts +0 -104
- package/src/EntityManager.ts +0 -1263
- package/src/EntityPersister.ts +0 -240
- package/src/QueryBuilder.ts +0 -289
- package/src/changes.ts +0 -40
- package/src/collections/AbstractRelationImpl.ts +0 -28
- package/src/collections/CustomCollection.ts +0 -152
- package/src/collections/CustomReference.ts +0 -138
- package/src/collections/ManyToManyCollection.ts +0 -346
- package/src/collections/ManyToOneReference.ts +0 -215
- package/src/collections/OneToManyCollection.ts +0 -254
- package/src/collections/OneToOneReference.ts +0 -153
- package/src/collections/hasManyDerived.ts +0 -29
- package/src/collections/hasManyThrough.ts +0 -20
- package/src/collections/hasOneDerived.ts +0 -26
- package/src/collections/hasOneThrough.ts +0 -20
- package/src/collections/index.ts +0 -74
- package/src/createOrUpdatePartial.ts +0 -144
- package/src/getProperties.ts +0 -27
- package/src/index.ts +0 -400
- package/src/keys.ts +0 -75
- package/src/loadLens.ts +0 -126
- package/src/newTestInstance.ts +0 -205
- package/src/reverseHint.ts +0 -43
- package/src/serde.ts +0 -97
- package/src/utils.ts +0 -63
- package/tsconfig.json +0 -21
- package/tsconfig.tsbuildinfo +0 -2646
|
@@ -1,62 +1,86 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TooManyError = exports.NotFoundError = exports.getMetadata = exports.sameEntity = exports.isKey = exports.isEntity = exports.setDefaultEntityLimit = exports.setEntityLimit = exports.entityLimit = exports.EntityManager = exports.currentFlushSecret = exports.currentlyInstantiatingEntity = void 0;
|
|
3
|
+
exports.afterTransaction = exports.beforeTransaction = exports.TooManyError = exports.NotFoundError = exports.getMetadata = exports.sameEntity = exports.isKey = exports.isEntity = exports.setDefaultEntityLimit = exports.setEntityLimit = exports.entityLimit = exports.EntityManager = exports.currentFlushSecret = exports.currentlyInstantiatingEntity = exports.isId = void 0;
|
|
7
4
|
const async_hooks_1 = require("async_hooks");
|
|
8
|
-
const dataloader_1 = __importDefault(require("dataloader"));
|
|
9
|
-
const object_hash_1 = __importDefault(require("object-hash"));
|
|
10
|
-
const AbstractRelationImpl_1 = require("./collections/AbstractRelationImpl");
|
|
11
5
|
const createOrUpdatePartial_1 = require("./createOrUpdatePartial");
|
|
12
|
-
const
|
|
6
|
+
const findDataLoader_1 = require("./dataloaders/findDataLoader");
|
|
7
|
+
const loadDataLoader_1 = require("./dataloaders/loadDataLoader");
|
|
13
8
|
const index_1 = require("./index");
|
|
14
|
-
const
|
|
9
|
+
const relations_1 = require("./relations");
|
|
10
|
+
const Todo_1 = require("./Todo");
|
|
15
11
|
const utils_1 = require("./utils");
|
|
12
|
+
function isId(value) {
|
|
13
|
+
return value && typeof value === "string";
|
|
14
|
+
}
|
|
15
|
+
exports.isId = isId;
|
|
16
|
+
/**
|
|
17
|
+
* A marker to prevent setter calls during `flush` calls.
|
|
18
|
+
*
|
|
19
|
+
* The `flush` process does a dirty check + SQL flush and generally doesn't want
|
|
20
|
+
* entities to re-dirtied after it's done the initial dirty check. So we'd like
|
|
21
|
+
* to prevent all setter calls while `flush` is running.
|
|
22
|
+
*
|
|
23
|
+
* That said, lifecycle code like hooks actually can make setter calls b/c `flush`
|
|
24
|
+
* invokes them at a specific point in its process.
|
|
25
|
+
*
|
|
26
|
+
* We solve this by using node's `AsyncLocalStorage` to mark certain callbacks (promise
|
|
27
|
+
* handlers) as blessed / invoked-from-`flush`-itself, and they are allowed to call setters,
|
|
28
|
+
* but any external callers (i.e. application code) will be rejected.
|
|
29
|
+
*/
|
|
16
30
|
exports.currentFlushSecret = new async_hooks_1.AsyncLocalStorage();
|
|
17
31
|
class EntityManager {
|
|
18
|
-
constructor(emOrCtx) {
|
|
32
|
+
constructor(emOrCtx, driver) {
|
|
19
33
|
this._entities = [];
|
|
20
34
|
// Indexes the currently loaded entities by their tagged ids. This fixes a real-world
|
|
21
35
|
// performance issue where `findExistingInstance` scanning `_entities` was an `O(n^2)`.
|
|
22
36
|
this._entityIndex = new Map();
|
|
23
|
-
this.findLoaders = {};
|
|
24
37
|
this.flushSecret = 0;
|
|
25
38
|
this._isFlushing = false;
|
|
39
|
+
// TODO Make these private
|
|
40
|
+
this.loadLoaders = {};
|
|
41
|
+
this.findLoaders = {};
|
|
26
42
|
// This is attempting to be internal/module private
|
|
27
43
|
this.__data = {
|
|
28
|
-
loaders: {},
|
|
29
44
|
joinRows: {},
|
|
30
45
|
};
|
|
31
46
|
this.hooks = {
|
|
32
47
|
beforeTransaction: [],
|
|
48
|
+
afterTransaction: [],
|
|
33
49
|
};
|
|
34
50
|
if (emOrCtx instanceof EntityManager) {
|
|
35
51
|
const em = emOrCtx;
|
|
36
|
-
this.
|
|
37
|
-
this.hooks = {
|
|
52
|
+
this.driver = em.driver;
|
|
53
|
+
this.hooks = {
|
|
54
|
+
beforeTransaction: [...em.hooks.beforeTransaction],
|
|
55
|
+
afterTransaction: [...em.hooks.afterTransaction],
|
|
56
|
+
};
|
|
38
57
|
this.ctx = em.ctx;
|
|
39
58
|
}
|
|
40
59
|
else {
|
|
41
60
|
this.ctx = emOrCtx;
|
|
42
|
-
this.
|
|
61
|
+
this.driver = driver;
|
|
43
62
|
}
|
|
44
63
|
}
|
|
64
|
+
/** Returns a read-only shallow copy of the currently-loaded entities. */
|
|
45
65
|
get entities() {
|
|
46
66
|
return [...this._entities];
|
|
47
67
|
}
|
|
68
|
+
/** Looks up `id` in the list of already-loaded entities. */
|
|
69
|
+
getEntity(id) {
|
|
70
|
+
return this._entityIndex.get(id);
|
|
71
|
+
}
|
|
48
72
|
async find(type, where, options) {
|
|
49
|
-
const rows = await
|
|
73
|
+
const rows = await (0, findDataLoader_1.findDataLoader)(this, type).load({ where, ...options });
|
|
50
74
|
const result = rows.map((row) => this.hydrate(type, row, { overwriteExisting: false }));
|
|
51
|
-
if (options
|
|
75
|
+
if (options?.populate) {
|
|
52
76
|
await this.populate(result, options.populate);
|
|
53
77
|
}
|
|
54
78
|
return result;
|
|
55
79
|
}
|
|
56
80
|
async findGql(type, where, options) {
|
|
57
|
-
const rows = await
|
|
81
|
+
const rows = await (0, findDataLoader_1.findDataLoader)(this, type).load({ where, ...options });
|
|
58
82
|
const result = rows.map((row) => this.hydrate(type, row, { overwriteExisting: false }));
|
|
59
|
-
if (options
|
|
83
|
+
if (options?.populate) {
|
|
60
84
|
await this.populate(result, options.populate);
|
|
61
85
|
}
|
|
62
86
|
return result;
|
|
@@ -115,20 +139,99 @@ class EntityManager {
|
|
|
115
139
|
// `calledFromConstructor` because this is _basically_ like calling `new`.
|
|
116
140
|
const entity = new type(this, undefined);
|
|
117
141
|
// Could remove the `as OptsOf<T>` by adding a method overload on `partial: true`
|
|
118
|
-
index_1.setOpts(entity, opts, { partial: true, calledFromConstructor: true });
|
|
142
|
+
(0, index_1.setOpts)(entity, opts, { partial: true, calledFromConstructor: true });
|
|
119
143
|
return entity;
|
|
120
144
|
}
|
|
121
145
|
/** Creates a new `type` but with `opts` that are nullable, to accept partial-update-style input. */
|
|
122
146
|
createOrUpdatePartial(type, opts) {
|
|
123
|
-
return createOrUpdatePartial_1.createOrUpdatePartial(this, type, opts);
|
|
147
|
+
return (0, createOrUpdatePartial_1.createOrUpdatePartial)(this, type, opts);
|
|
124
148
|
}
|
|
125
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Utility to clone an entity and its nested relations, as determined by a populate hint
|
|
151
|
+
*
|
|
152
|
+
* @param entity - Any entity
|
|
153
|
+
* @param hint - A populate hint
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // This will duplicate the author
|
|
157
|
+
* const duplicatedAuthor = await em.clone(author)
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* // This will duplicate the author and all their related book entities
|
|
161
|
+
* const duplicatedAuthorAndBooks = await em.clone(author, "books")
|
|
162
|
+
*
|
|
163
|
+
* @exmaple
|
|
164
|
+
* // This will duplicate the author, all their books, and the images for those books
|
|
165
|
+
* const duplicatedAuthorAndBooksAndImages = await em.clone(author, {books: "image"})
|
|
166
|
+
*/
|
|
167
|
+
async clone(entity, hint) {
|
|
168
|
+
const meta = getMetadata(entity);
|
|
169
|
+
const { id, ...data } = entity.__orm.data;
|
|
170
|
+
const clone = this.create(meta.cstr, {});
|
|
171
|
+
clone.__orm.data = data;
|
|
172
|
+
if (hint) {
|
|
173
|
+
if (typeof hint === "string") {
|
|
174
|
+
hint = { [hint]: {} };
|
|
175
|
+
}
|
|
176
|
+
else if (Array.isArray(hint)) {
|
|
177
|
+
hint = Object.fromEntries(hint.map((relation) => [relation, {}]));
|
|
178
|
+
}
|
|
179
|
+
await Promise.all(Object.entries(hint).map(async ([relationName, nested]) => {
|
|
180
|
+
const relation = entity[relationName];
|
|
181
|
+
if (relation instanceof index_1.OneToManyCollection) {
|
|
182
|
+
const relatedEntities = await relation.load();
|
|
183
|
+
await Promise.all(relatedEntities.map(async (related) => {
|
|
184
|
+
const clonedRelated = await this.clone(related, nested);
|
|
185
|
+
// Clear to avoid `set` mutating the original/source entity's relationship
|
|
186
|
+
clonedRelated.__orm.data[relation.otherFieldName] = undefined;
|
|
187
|
+
const relationToClone = clonedRelated[relation.otherFieldName];
|
|
188
|
+
relationToClone.set(clone);
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
else if (relation instanceof relations_1.OneToOneReferenceImpl) {
|
|
192
|
+
const related = await relation.load();
|
|
193
|
+
if (related) {
|
|
194
|
+
const clonedRelated = await this.clone(related, nested);
|
|
195
|
+
// Clear to avoid `set` mutating the original/source entity's relationship
|
|
196
|
+
clonedRelated.__orm.data[relation.otherFieldName] = undefined;
|
|
197
|
+
const relationToClone = clone[relation.fieldName];
|
|
198
|
+
relationToClone.set(clonedRelated);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (relation instanceof relations_1.ManyToOneReferenceImpl) {
|
|
202
|
+
const related = await relation.load();
|
|
203
|
+
if (related) {
|
|
204
|
+
const clonedRelated = await this.clone(related, nested);
|
|
205
|
+
// Clear to avoid `set` mutating the original/source entity's relationship
|
|
206
|
+
clone.__orm.data[relationName] = undefined;
|
|
207
|
+
const relationToClone = clone[relationName];
|
|
208
|
+
relationToClone.set(clonedRelated);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
(0, utils_1.fail)(`Uncloneable relation: ${relationName}`);
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
return clone;
|
|
217
|
+
}
|
|
218
|
+
async load(typeOrId, id, hint) {
|
|
219
|
+
// Handle the `typeOrId` overload
|
|
220
|
+
let type;
|
|
221
|
+
if (typeof typeOrId === "string") {
|
|
222
|
+
type = (0, index_1.getConstructorFromTaggedId)(typeOrId);
|
|
223
|
+
id = typeOrId;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
type = typeOrId;
|
|
227
|
+
id = id || (0, utils_1.fail)();
|
|
228
|
+
}
|
|
126
229
|
if (typeof id !== "string") {
|
|
127
230
|
throw new Error(`Expected ${id} to be a string`);
|
|
128
231
|
}
|
|
129
232
|
const meta = getMetadata(type);
|
|
130
|
-
const tagged = index_1.
|
|
131
|
-
const entity = this.findExistingInstance(tagged) || (await
|
|
233
|
+
const tagged = (0, index_1.tagId)(meta, id);
|
|
234
|
+
const entity = this.findExistingInstance(tagged) || (await (0, loadDataLoader_1.loadDataLoader)(this, meta).load(tagged));
|
|
132
235
|
if (!entity) {
|
|
133
236
|
throw new Error(`${tagged} was not found`);
|
|
134
237
|
}
|
|
@@ -139,9 +242,9 @@ class EntityManager {
|
|
|
139
242
|
}
|
|
140
243
|
async loadAll(type, _ids, hint) {
|
|
141
244
|
const meta = getMetadata(type);
|
|
142
|
-
const ids = _ids.map((id) => index_1.
|
|
245
|
+
const ids = _ids.map((id) => (0, index_1.tagId)(meta, id));
|
|
143
246
|
const entities = await Promise.all(ids.map((id) => {
|
|
144
|
-
return this.findExistingInstance(id) ||
|
|
247
|
+
return this.findExistingInstance(id) || (0, loadDataLoader_1.loadDataLoader)(this, meta).load(id);
|
|
145
248
|
}));
|
|
146
249
|
const idsNotFound = ids.filter((id, i) => entities[i] === undefined);
|
|
147
250
|
if (idsNotFound.length > 0) {
|
|
@@ -152,6 +255,17 @@ class EntityManager {
|
|
|
152
255
|
}
|
|
153
256
|
return entities;
|
|
154
257
|
}
|
|
258
|
+
async loadAllIfExists(type, _ids, hint) {
|
|
259
|
+
const meta = getMetadata(type);
|
|
260
|
+
const ids = _ids.map((id) => (0, index_1.tagId)(meta, id));
|
|
261
|
+
const entities = (await Promise.all(ids.map((id) => {
|
|
262
|
+
return this.findExistingInstance(id) || (0, loadDataLoader_1.loadDataLoader)(this, meta).load(id);
|
|
263
|
+
}))).filter(Boolean);
|
|
264
|
+
if (hint) {
|
|
265
|
+
await this.populate(entities, hint);
|
|
266
|
+
}
|
|
267
|
+
return entities;
|
|
268
|
+
}
|
|
155
269
|
async loadFromQuery(type, query, populate) {
|
|
156
270
|
const rows = await query;
|
|
157
271
|
const entities = rows.map((row) => this.hydrate(type, row, { overwriteExisting: false }));
|
|
@@ -161,7 +275,7 @@ class EntityManager {
|
|
|
161
275
|
return entities;
|
|
162
276
|
}
|
|
163
277
|
async populate(entityOrList, hint) {
|
|
164
|
-
const list =
|
|
278
|
+
const list = (0, utils_1.toArray)(entityOrList);
|
|
165
279
|
const promises = list
|
|
166
280
|
.filter((e) => e !== undefined && (e.isPendingDelete || !e.isDeletedEntity))
|
|
167
281
|
.flatMap((entity) => {
|
|
@@ -198,28 +312,18 @@ class EntityManager {
|
|
|
198
312
|
* cannot be enforced with database-level constraints.
|
|
199
313
|
*/
|
|
200
314
|
async transaction(fn) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.knex = txn;
|
|
204
|
-
try {
|
|
205
|
-
await txn.raw("set transaction isolation level serializable;");
|
|
206
|
-
await beforeTransaction(this, txn);
|
|
207
|
-
const result = await fn(txn);
|
|
315
|
+
return this.driver.transaction(this, async (knex) => {
|
|
316
|
+
const result = await fn(knex);
|
|
208
317
|
// The lambda may have done some interstitial flushes (that would not
|
|
209
318
|
// have committed the transaction), but go ahead and do a final one
|
|
210
319
|
// in case they didn't explicitly call flush.
|
|
211
320
|
await this.flush();
|
|
212
|
-
await txn.commit();
|
|
213
321
|
return result;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
this.knex = originalKnex;
|
|
222
|
-
}
|
|
322
|
+
},
|
|
323
|
+
// Application-enforced unique constraints (i.e. custom find + conditional insert) can be
|
|
324
|
+
// serialization anomalies, so we use the highest isolation level b/c it prevents this.
|
|
325
|
+
// See the EntityManager.txns.test.ts file.
|
|
326
|
+
"serializable");
|
|
223
327
|
}
|
|
224
328
|
/** Registers a newly-instantiated entity with our EntityManager; only called by entity constructors. */
|
|
225
329
|
register(meta, entity) {
|
|
@@ -231,7 +335,7 @@ class EntityManager {
|
|
|
231
335
|
entity.__orm.data["updatedAt"] = new Date();
|
|
232
336
|
this._entities.push(entity);
|
|
233
337
|
if (entity.id) {
|
|
234
|
-
index_1.assertIdsAreTagged([entity.id]);
|
|
338
|
+
(0, index_1.assertIdsAreTagged)([entity.id]);
|
|
235
339
|
this._entityIndex.set(entity.id, entity);
|
|
236
340
|
}
|
|
237
341
|
if (this._entities.length >= exports.entityLimit) {
|
|
@@ -256,11 +360,7 @@ class EntityManager {
|
|
|
256
360
|
return;
|
|
257
361
|
}
|
|
258
362
|
deletedEntity.__orm.deleted = "pending";
|
|
259
|
-
|
|
260
|
-
.filter((v) => v instanceof AbstractRelationImpl_1.AbstractRelationImpl)
|
|
261
|
-
.map((relation) => {
|
|
262
|
-
relation.onEntityDelete();
|
|
263
|
-
});
|
|
363
|
+
(0, index_1.getRelations)(deletedEntity).forEach((relation) => relation.maybeCascadeDelete());
|
|
264
364
|
}
|
|
265
365
|
/**
|
|
266
366
|
* Flushes the SQL for any changed entities to the database.
|
|
@@ -272,8 +372,11 @@ class EntityManager {
|
|
|
272
372
|
* If this is run within an existing transaction, i.e. `EntityManager.transaction`,
|
|
273
373
|
* then it will only issue `INSERT`s/etc. and defer to the caller to `COMMIT`
|
|
274
374
|
* the transaction.
|
|
375
|
+
*
|
|
376
|
+
* It returns entities that have changed (an entity is considered changed if it has been deleted, inserted, or updated)
|
|
275
377
|
*/
|
|
276
|
-
async flush() {
|
|
378
|
+
async flush(flushOptions = {}) {
|
|
379
|
+
const { skipValidation = false } = flushOptions;
|
|
277
380
|
if (this.isFlushing) {
|
|
278
381
|
throw new Error("Cannot flush while another flush is already in progress");
|
|
279
382
|
}
|
|
@@ -282,61 +385,64 @@ class EntityManager {
|
|
|
282
385
|
let pendingEntities = this.entities.filter((e) => e.isPendingFlush);
|
|
283
386
|
try {
|
|
284
387
|
while (pendingEntities.length > 0) {
|
|
285
|
-
await
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
388
|
+
await exports.currentFlushSecret.run({ flushSecret: this.flushSecret }, async () => {
|
|
389
|
+
const todos = (0, Todo_1.createTodos)(pendingEntities);
|
|
390
|
+
// Add objects to todos that have reactive hooks.
|
|
391
|
+
// Note that, if we're on the 2nd loop, we might actually re-add entities that were already-validated
|
|
392
|
+
// and already-flushed on the 1st loop, if those entities happen to be marked as derived from the
|
|
393
|
+
// current loop's entities. In theory this is a good thing, b/c the current loop's entities have
|
|
394
|
+
// been changed since/during the 1st loop, so we want the derived validation rules + derived values
|
|
395
|
+
// to run again to see the latest & greatest data.
|
|
396
|
+
await addReactiveAsyncDerivedValues(todos);
|
|
397
|
+
await addReactiveValidations(todos);
|
|
398
|
+
// run our hooks
|
|
399
|
+
await beforeDelete(this.ctx, todos);
|
|
400
|
+
// We defer doing this cascade logic until flush() so that delete() can remain synchronous.
|
|
401
|
+
await cleanupDeletedRelations(todos);
|
|
402
|
+
await beforeFlush(this.ctx, todos);
|
|
403
|
+
await beforeCreate(this.ctx, todos);
|
|
404
|
+
await beforeUpdate(this.ctx, todos);
|
|
405
|
+
recalcDerivedFields(todos);
|
|
406
|
+
await recalcAsyncDerivedFields(this, todos);
|
|
407
|
+
if (!skipValidation) {
|
|
408
|
+
await validate(todos);
|
|
409
|
+
await afterValidation(this.ctx, todos);
|
|
410
|
+
}
|
|
411
|
+
entitiesToFlush.push(...pendingEntities);
|
|
412
|
+
pendingEntities = this.entities.filter((e) => e.isPendingFlush && !entitiesToFlush.includes(e));
|
|
413
|
+
this.flushSecret += 1;
|
|
310
414
|
});
|
|
311
415
|
}
|
|
312
|
-
const entityTodos =
|
|
313
|
-
const joinRowTodos =
|
|
416
|
+
const entityTodos = (0, Todo_1.createTodos)(entitiesToFlush);
|
|
417
|
+
const joinRowTodos = (0, Todo_1.combineJoinRows)(this.__data.joinRows);
|
|
314
418
|
if (Object.keys(entityTodos).length > 0 || Object.keys(joinRowTodos).length > 0) {
|
|
315
|
-
|
|
316
|
-
if
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
await EntityPersister_1.flushEntities(this.knex, entityTodos);
|
|
327
|
-
await EntityPersister_1.flushJoinTables(this.knex, joinRowTodos);
|
|
328
|
-
// Defer to the caller to commit the transaction
|
|
329
|
-
}
|
|
419
|
+
// The driver will handle the right thing if we're already in an existing transaction.
|
|
420
|
+
// We also purposefully don't pass an isolation level b/c if we're only doing
|
|
421
|
+
// INSERTs and UPDATEs, then we don't really care about overlapping SELECT-then-INSERT
|
|
422
|
+
// serialization anomalies. (Although should we? Maybe we should run the flush hooks
|
|
423
|
+
// in this same transaction just as a matter of principle / safest default.)
|
|
424
|
+
await this.driver.transaction(this, async () => {
|
|
425
|
+
await this.driver.flushEntities(this, entityTodos);
|
|
426
|
+
await this.driver.flushJoinTables(this, joinRowTodos);
|
|
427
|
+
});
|
|
330
428
|
// TODO: This is really "after flush" if we're being called from a transaction that
|
|
331
429
|
// is going to make multiple `em.flush()` calls?
|
|
332
430
|
await afterCommit(this.ctx, entityTodos);
|
|
333
431
|
Object.values(entityTodos).forEach((todo) => {
|
|
334
|
-
todo.inserts.forEach((e) =>
|
|
432
|
+
todo.inserts.forEach((e) => {
|
|
433
|
+
this._entityIndex.set(e.id, e);
|
|
434
|
+
e.__orm.isNew = false;
|
|
435
|
+
});
|
|
436
|
+
[todo.inserts, todo.updates, todo.deletes].flat().forEach((e) => {
|
|
437
|
+
e.__orm.originalData = {};
|
|
438
|
+
e.__orm.isTouched = false;
|
|
439
|
+
});
|
|
335
440
|
});
|
|
336
441
|
// Reset the find caches b/c data will have changed in the db
|
|
442
|
+
this.loadLoaders = {};
|
|
337
443
|
this.findLoaders = {};
|
|
338
|
-
this.__data.loaders = {};
|
|
339
444
|
}
|
|
445
|
+
return entitiesToFlush;
|
|
340
446
|
}
|
|
341
447
|
finally {
|
|
342
448
|
this._isFlushing = false;
|
|
@@ -356,6 +462,7 @@ class EntityManager {
|
|
|
356
462
|
return `<EntityManager ${this.entities.length}>`;
|
|
357
463
|
}
|
|
358
464
|
async refresh(entityOrListOrUndefined) {
|
|
465
|
+
this.loadLoaders = {};
|
|
359
466
|
this.findLoaders = {};
|
|
360
467
|
const list = entityOrListOrUndefined === undefined
|
|
361
468
|
? this._entities
|
|
@@ -365,17 +472,10 @@ class EntityManager {
|
|
|
365
472
|
await Promise.all(list.map(async (entity) => {
|
|
366
473
|
if (entity.id) {
|
|
367
474
|
// Clear the original cached loader result and fetch the new primitives
|
|
368
|
-
|
|
369
|
-
loader.clear(entity.id);
|
|
370
|
-
await loader.load(entity.id);
|
|
475
|
+
await (0, loadDataLoader_1.loadDataLoader)(this, getMetadata(entity)).load(entity.id);
|
|
371
476
|
if (entity.__orm.deleted === undefined) {
|
|
372
477
|
// Then refresh any loaded collections
|
|
373
|
-
await Promise.all(
|
|
374
|
-
if (c instanceof AbstractRelationImpl_1.AbstractRelationImpl) {
|
|
375
|
-
return c.refreshIfLoaded();
|
|
376
|
-
}
|
|
377
|
-
return undefined;
|
|
378
|
-
}));
|
|
478
|
+
await Promise.all((0, index_1.getRelations)(entity).map((r) => r.refreshIfLoaded()));
|
|
379
479
|
}
|
|
380
480
|
}
|
|
381
481
|
}));
|
|
@@ -383,118 +483,10 @@ class EntityManager {
|
|
|
383
483
|
get numberOfEntities() {
|
|
384
484
|
return this.entities.length;
|
|
385
485
|
}
|
|
386
|
-
loaderForFind(type) {
|
|
387
|
-
return utils_1.getOrSet(this.findLoaders, type.name, () => {
|
|
388
|
-
return new dataloader_1.default(async (queries) => {
|
|
389
|
-
function ensureUnderLimit(rows) {
|
|
390
|
-
if (rows.length >= exports.entityLimit) {
|
|
391
|
-
throw new Error(`Query returned more than ${exports.entityLimit} rows`);
|
|
392
|
-
}
|
|
393
|
-
return rows;
|
|
394
|
-
}
|
|
395
|
-
// If there is only 1 query, we can skip the tagging step.
|
|
396
|
-
if (queries.length === 1) {
|
|
397
|
-
return [ensureUnderLimit(await QueryBuilder_1.buildQuery(this.knex, type, queries[0]))];
|
|
398
|
-
}
|
|
399
|
-
const { knex } = this;
|
|
400
|
-
// Map each incoming query[i] to itself or a previous dup
|
|
401
|
-
const uniqueQueries = [];
|
|
402
|
-
const queryToUnique = {};
|
|
403
|
-
queries.forEach((q, i) => {
|
|
404
|
-
let j = uniqueQueries.findIndex((uq) => whereFilterHash(uq) === whereFilterHash(q));
|
|
405
|
-
if (j === -1) {
|
|
406
|
-
uniqueQueries.push(q);
|
|
407
|
-
j = uniqueQueries.length - 1;
|
|
408
|
-
}
|
|
409
|
-
queryToUnique[i] = j;
|
|
410
|
-
});
|
|
411
|
-
// There are duplicate queries, but only one unique query, so we can execute just it w/o tagging.
|
|
412
|
-
if (uniqueQueries.length === 1) {
|
|
413
|
-
const rows = ensureUnderLimit(await QueryBuilder_1.buildQuery(this.knex, type, queries[0]));
|
|
414
|
-
// Reuse this same result for however many callers asked for it.
|
|
415
|
-
return queries.map(() => rows);
|
|
416
|
-
}
|
|
417
|
-
// TODO: Instead of this tagged approach, we could probably check if the each
|
|
418
|
-
// where cause: a) has the same structure for joins, and b) has conditions that
|
|
419
|
-
// we can evaluate client-side, and then combine it into a query like:
|
|
420
|
-
//
|
|
421
|
-
// SELECT entity.*, t1.foo as condition1, t2.bar as condition2 FROM ...
|
|
422
|
-
// WHERE t1.foo (union of each queries condition)
|
|
423
|
-
//
|
|
424
|
-
// And then use the `condition1` and `condition2` to tease the combined result set
|
|
425
|
-
// back apart into each condition's result list.
|
|
426
|
-
// For each query, add an additional `__tag` column that will identify that query's
|
|
427
|
-
// corresponding rows in the combined/UNION ALL'd result set.
|
|
428
|
-
//
|
|
429
|
-
// We also add a `__row` column with that queries order, so that after we `UNION ALL`,
|
|
430
|
-
// we can order by `__tag` + `__row` and ensure we're getting back the combined rows
|
|
431
|
-
// exactly as they would be in done individually (i.e. per the docs `UNION ALL` does
|
|
432
|
-
// not gaurantee order).
|
|
433
|
-
const tagged = uniqueQueries.map((queryAndSettings, i) => {
|
|
434
|
-
const query = QueryBuilder_1.buildQuery(this.knex, type, queryAndSettings);
|
|
435
|
-
return query.select(knex.raw(`${i} as __tag`), knex.raw("row_number() over () as __row"));
|
|
436
|
-
});
|
|
437
|
-
const meta = getMetadata(type);
|
|
438
|
-
// Kind of dumb, but make a dummy row to start our query with
|
|
439
|
-
let query = knex
|
|
440
|
-
.select("*", knex.raw("-1 as __tag"), knex.raw("-1 as __row"))
|
|
441
|
-
.from(meta.tableName)
|
|
442
|
-
.orderBy("__tag", "__row")
|
|
443
|
-
.where({ id: -1 });
|
|
444
|
-
// Use the dummy query as a base, then `UNION ALL` in all the rest
|
|
445
|
-
tagged.forEach((add) => {
|
|
446
|
-
query = query.unionAll(add, true);
|
|
447
|
-
});
|
|
448
|
-
// Issue a single SQL statement for all of them
|
|
449
|
-
const rows = ensureUnderLimit(await query);
|
|
450
|
-
const resultForUniques = [];
|
|
451
|
-
uniqueQueries.forEach((q, i) => {
|
|
452
|
-
resultForUniques[i] = [];
|
|
453
|
-
});
|
|
454
|
-
rows.forEach((row) => {
|
|
455
|
-
resultForUniques[row["__tag"]].push(row);
|
|
456
|
-
});
|
|
457
|
-
// We return an array-of-arrays, where result[i] is the rows for queries[i]
|
|
458
|
-
const result = [];
|
|
459
|
-
queries.forEach((q, i) => {
|
|
460
|
-
result[i] = resultForUniques[queryToUnique[i]];
|
|
461
|
-
});
|
|
462
|
-
return result;
|
|
463
|
-
}, {
|
|
464
|
-
// Our filter/order tuple is a complex object, so object-hash it to ensure caching works
|
|
465
|
-
cacheKeyFn: whereFilterHash,
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
loaderForEntity(meta) {
|
|
470
|
-
return utils_1.getOrSet(this.__data.loaders, meta.type, () => {
|
|
471
|
-
return new dataloader_1.default(async (_keys) => {
|
|
472
|
-
index_1.assertIdsAreTagged(_keys);
|
|
473
|
-
const keys = index_1.deTagIds(meta, _keys);
|
|
474
|
-
const rows = await this.knex.select("*").from(meta.tableName).whereIn("id", keys);
|
|
475
|
-
// Pass overwriteExisting (which is the default anyway) because it might be EntityManager.refresh calling us.
|
|
476
|
-
const entities = rows.map((row) => this.hydrate(meta.cstr, row, { overwriteExisting: true }));
|
|
477
|
-
const entitiesById = utils_1.indexBy(entities, (e) => e.id);
|
|
478
|
-
// Return the results back in the same order as the keys
|
|
479
|
-
return _keys.map((k) => {
|
|
480
|
-
const entity = entitiesById.get(k);
|
|
481
|
-
// We generally expect all of our entities to be found, but they may not for API calls like
|
|
482
|
-
// `findOneOrFail` or for `EntityManager.refresh` when the entity has been deleted out from
|
|
483
|
-
// under us.
|
|
484
|
-
if (entity === undefined) {
|
|
485
|
-
const existingEntity = this.findExistingInstance(k);
|
|
486
|
-
if (existingEntity) {
|
|
487
|
-
existingEntity.__orm.deleted = "deleted";
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return entity;
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
486
|
// Handles our Unit of Work-style look up / deduplication of entity instances.
|
|
487
|
+
// Currently only public for the driver impls
|
|
496
488
|
findExistingInstance(id) {
|
|
497
|
-
index_1.assertIdsAreTagged([id]);
|
|
489
|
+
(0, index_1.assertIdsAreTagged)([id]);
|
|
498
490
|
return this._entityIndex.get(id);
|
|
499
491
|
}
|
|
500
492
|
/**
|
|
@@ -508,25 +500,34 @@ class EntityManager {
|
|
|
508
500
|
*/
|
|
509
501
|
hydrate(type, row, options) {
|
|
510
502
|
const meta = getMetadata(type);
|
|
511
|
-
const id = index_1.keyToString(meta, row["id"]) || utils_1.fail("No id column was available");
|
|
503
|
+
const id = (0, index_1.keyToString)(meta, row["id"]) || (0, utils_1.fail)("No id column was available");
|
|
512
504
|
// See if this is already in our UoW
|
|
513
505
|
let entity = this.findExistingInstance(id);
|
|
514
506
|
if (!entity) {
|
|
515
507
|
// Pass id as a hint that we're in hydrate mode
|
|
516
508
|
entity = new type(this, id);
|
|
517
|
-
meta.
|
|
509
|
+
Object.values(meta.fields).forEach((f) => f.serde?.setOnEntity(entity.__orm.data, row));
|
|
518
510
|
}
|
|
519
|
-
else if (
|
|
520
|
-
// Usually if the entity
|
|
511
|
+
else if (options?.overwriteExisting !== false) {
|
|
512
|
+
// Usually if the entity already exists, we don't write over it, but in this case
|
|
521
513
|
// we assume that `EntityManager.refresh` is telling us to explicitly load the
|
|
522
514
|
// latest data.
|
|
523
|
-
meta.
|
|
515
|
+
Object.values(meta.fields).forEach((f) => f.serde?.setOnEntity(entity.__orm.data, row));
|
|
524
516
|
}
|
|
525
517
|
return entity;
|
|
526
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Mark an entity as needing to be flushed regardless of its state
|
|
521
|
+
*/
|
|
522
|
+
touch(entity) {
|
|
523
|
+
entity.__orm.isTouched = true;
|
|
524
|
+
}
|
|
527
525
|
beforeTransaction(fn) {
|
|
528
526
|
this.hooks.beforeTransaction.push(fn);
|
|
529
527
|
}
|
|
528
|
+
afterTransaction(fn) {
|
|
529
|
+
this.hooks.afterTransaction.push(fn);
|
|
530
|
+
}
|
|
530
531
|
toString() {
|
|
531
532
|
return "EntityManager";
|
|
532
533
|
}
|
|
@@ -550,17 +551,16 @@ function isKey(k) {
|
|
|
550
551
|
}
|
|
551
552
|
exports.isKey = isKey;
|
|
552
553
|
/** Compares `a` to `b`, where `b` might be an id. B/c ids can overlap, we need to know `b`'s metadata type. */
|
|
553
|
-
function sameEntity(a,
|
|
554
|
-
if (a === undefined ||
|
|
555
|
-
return
|
|
554
|
+
function sameEntity(a, meta, b) {
|
|
555
|
+
if (a === undefined || b === undefined) {
|
|
556
|
+
return a === undefined && b === undefined;
|
|
556
557
|
}
|
|
557
|
-
return (a ===
|
|
558
|
+
return (a === b ||
|
|
559
|
+
(!a.isNewEntity && getMetadata(a) === meta && (0, index_1.maybeResolveReferenceToId)(a) === (0, index_1.maybeResolveReferenceToId)(b)));
|
|
558
560
|
}
|
|
559
561
|
exports.sameEntity = sameEntity;
|
|
560
|
-
function getMetadata(
|
|
561
|
-
return (typeof
|
|
562
|
-
? entityOrType.metadata
|
|
563
|
-
: entityOrType.__orm.metadata);
|
|
562
|
+
function getMetadata(param) {
|
|
563
|
+
return (typeof param === "function" ? param.metadata : "cstr" in param ? param : param.__orm.metadata);
|
|
564
564
|
}
|
|
565
565
|
exports.getMetadata = getMetadata;
|
|
566
566
|
/** Thrown by `findOneOrFail` if an entity is not found. */
|
|
@@ -575,6 +575,10 @@ exports.TooManyError = TooManyError;
|
|
|
575
575
|
* For the entities currently in `todos`, find any reactive validation rules that point
|
|
576
576
|
* from the currently-changed entities back to each rule's originally-defined-in entity,
|
|
577
577
|
* and ensure those entities are added to `todos`.
|
|
578
|
+
*
|
|
579
|
+
* Note that we don't check whether `entitiesToFlush` already the entities we're adding,
|
|
580
|
+
* because rules should be side effect free, so invoking them twice, if that does happen
|
|
581
|
+
* to occur, should be fine (and desirable given something about the entity has changed).
|
|
578
582
|
*/
|
|
579
583
|
async function addReactiveValidations(todos) {
|
|
580
584
|
const p = Object.values(todos).flatMap((todo) => {
|
|
@@ -583,8 +587,11 @@ async function addReactiveValidations(todos) {
|
|
|
583
587
|
return todo.metadata.config.__data.reactiveRules.map(async (reverseHint) => {
|
|
584
588
|
// Add the resulting "found" entities to the right todos to be validated
|
|
585
589
|
(await followReverseHint(entities, reverseHint)).forEach((entity) => {
|
|
586
|
-
const todo =
|
|
587
|
-
if (!todo.inserts.includes(entity) &&
|
|
590
|
+
const todo = (0, Todo_1.getTodo)(todos, entity);
|
|
591
|
+
if (!todo.inserts.includes(entity) &&
|
|
592
|
+
!todo.updates.includes(entity) &&
|
|
593
|
+
!todo.validates.includes(entity) &&
|
|
594
|
+
!entity.isDeletedEntity) {
|
|
588
595
|
todo.validates.push(entity);
|
|
589
596
|
}
|
|
590
597
|
});
|
|
@@ -601,7 +608,7 @@ async function addReactiveAsyncDerivedValues(todos) {
|
|
|
601
608
|
const entities = [...todo.inserts, ...todo.updates];
|
|
602
609
|
return todo.metadata.config.__data.reactiveDerivedValues.map(async (reverseHint) => {
|
|
603
610
|
(await followReverseHint(entities, reverseHint)).forEach((entity) => {
|
|
604
|
-
const todo =
|
|
611
|
+
const todo = (0, Todo_1.getTodo)(todos, entity);
|
|
605
612
|
if (!todo.inserts.includes(entity) && !todo.updates.includes(entity) && !entity.isDeletedEntity) {
|
|
606
613
|
todo.updates.push(entity);
|
|
607
614
|
}
|
|
@@ -611,14 +618,9 @@ async function addReactiveAsyncDerivedValues(todos) {
|
|
|
611
618
|
await Promise.all(p);
|
|
612
619
|
}
|
|
613
620
|
/** Find all deleted entities and ensure their references all know about their deleted-ness. */
|
|
614
|
-
async function
|
|
621
|
+
async function cleanupDeletedRelations(todos) {
|
|
615
622
|
const entities = Object.values(todos).flatMap((todo) => todo.deletes);
|
|
616
|
-
await Promise.all(entities
|
|
617
|
-
.flatMap((e) => Object.values(e))
|
|
618
|
-
.filter((v) => v instanceof AbstractRelationImpl_1.AbstractRelationImpl)
|
|
619
|
-
.map((relation) => {
|
|
620
|
-
return relation.onEntityDeletedAndFlushing();
|
|
621
|
-
}));
|
|
623
|
+
await Promise.all(entities.flatMap(index_1.getRelations).map((relation) => relation.cleanupOnEntityDeleted()));
|
|
622
624
|
}
|
|
623
625
|
async function validate(todos) {
|
|
624
626
|
const p = Object.values(todos).flatMap((todo) => {
|
|
@@ -634,31 +636,49 @@ async function validate(todos) {
|
|
|
634
636
|
throw new index_1.ValidationErrors(errors);
|
|
635
637
|
}
|
|
636
638
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
+
function beforeTransaction(em, knex) {
|
|
640
|
+
return Promise.all(em["hooks"].beforeTransaction.map((fn) => fn(em, knex)));
|
|
641
|
+
}
|
|
642
|
+
exports.beforeTransaction = beforeTransaction;
|
|
643
|
+
function afterTransaction(em, knex) {
|
|
644
|
+
return Promise.all(em["hooks"].afterTransaction.map((fn) => fn(em, knex)));
|
|
639
645
|
}
|
|
646
|
+
exports.afterTransaction = afterTransaction;
|
|
640
647
|
async function runHook(ctx, hook, todos, keys) {
|
|
641
648
|
const p = Object.values(todos).flatMap((todo) => {
|
|
642
649
|
const hookFns = todo.metadata.config.__data.hooks[hook];
|
|
643
650
|
return keys
|
|
644
651
|
.flatMap((k) => todo[k].filter((e) => k === "deletes" || !e.isDeletedEntity))
|
|
645
652
|
.flatMap((entity) => {
|
|
653
|
+
// Use an explicit `async` here to ensure all hooks are promises, i.e. so that a non-promise
|
|
654
|
+
// hook blowing up doesn't orphan the others .
|
|
646
655
|
return hookFns.map(async (fn) => fn(entity, ctx));
|
|
647
656
|
});
|
|
648
657
|
});
|
|
649
|
-
|
|
658
|
+
// Use `allSettled` so that even if 1 hook blows up, we don't orphan other hooks mid-flush
|
|
659
|
+
const rejects = (await Promise.allSettled(p)).filter((r) => r.status === "rejected");
|
|
660
|
+
// For now just throw the 1st rejection; this should be pretty rare
|
|
661
|
+
if (rejects.length > 0 && rejects[0].status === "rejected") {
|
|
662
|
+
throw rejects[0].reason;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function beforeDelete(ctx, todos) {
|
|
666
|
+
return runHook(ctx, "beforeDelete", todos, ["deletes"]);
|
|
650
667
|
}
|
|
651
|
-
|
|
652
|
-
|
|
668
|
+
function beforeFlush(ctx, todos) {
|
|
669
|
+
return runHook(ctx, "beforeFlush", todos, ["inserts", "updates"]);
|
|
653
670
|
}
|
|
654
|
-
|
|
655
|
-
|
|
671
|
+
function beforeCreate(ctx, todos) {
|
|
672
|
+
return runHook(ctx, "beforeCreate", todos, ["inserts"]);
|
|
656
673
|
}
|
|
657
|
-
|
|
658
|
-
|
|
674
|
+
function beforeUpdate(ctx, todos) {
|
|
675
|
+
return runHook(ctx, "beforeUpdate", todos, ["updates"]);
|
|
659
676
|
}
|
|
660
|
-
|
|
661
|
-
|
|
677
|
+
function afterValidation(ctx, todos) {
|
|
678
|
+
return runHook(ctx, "afterValidation", todos, ["inserts", "updates"]);
|
|
679
|
+
}
|
|
680
|
+
function afterCommit(ctx, todos) {
|
|
681
|
+
return runHook(ctx, "afterCommit", todos, ["inserts", "updates"]);
|
|
662
682
|
}
|
|
663
683
|
function coerceError(entity, maybeError) {
|
|
664
684
|
if (maybeError === undefined) {
|
|
@@ -668,10 +688,10 @@ function coerceError(entity, maybeError) {
|
|
|
668
688
|
return [{ entity, message: maybeError }];
|
|
669
689
|
}
|
|
670
690
|
else if (Array.isArray(maybeError)) {
|
|
671
|
-
return maybeError;
|
|
691
|
+
return maybeError.map((ve) => ({ entity, ...ve }));
|
|
672
692
|
}
|
|
673
693
|
else {
|
|
674
|
-
return [maybeError];
|
|
694
|
+
return [{ entity, ...maybeError }];
|
|
675
695
|
}
|
|
676
696
|
}
|
|
677
697
|
/**
|
|
@@ -686,13 +706,18 @@ function recalcDerivedFields(todos) {
|
|
|
686
706
|
.flatMap((todo) => [...todo.inserts, ...todo.updates])
|
|
687
707
|
.filter((e) => !e.isDeletedEntity);
|
|
688
708
|
const derivedFieldsByMeta = new Map([...new Set(entities.map(getMetadata))].map((m) => {
|
|
689
|
-
return [
|
|
709
|
+
return [
|
|
710
|
+
m,
|
|
711
|
+
Object.values(m.fields)
|
|
712
|
+
.filter((f) => f.kind === "primitive" && f.derived === "sync")
|
|
713
|
+
.map((f) => f.fieldName),
|
|
714
|
+
];
|
|
690
715
|
}));
|
|
691
716
|
for (const entity of entities) {
|
|
692
717
|
const derivedFields = derivedFieldsByMeta.get(entity.__orm.metadata);
|
|
693
|
-
derivedFields
|
|
718
|
+
derivedFields?.forEach((fieldName) => {
|
|
694
719
|
// setField will intelligently mark/not mark the field as dirty.
|
|
695
|
-
index_1.setField(entity, fieldName, entity[fieldName]);
|
|
720
|
+
(0, index_1.setField)(entity, fieldName, entity[fieldName]);
|
|
696
721
|
});
|
|
697
722
|
}
|
|
698
723
|
}
|
|
@@ -710,18 +735,13 @@ async function recalcAsyncDerivedFields(em, todos) {
|
|
|
710
735
|
if (entry) {
|
|
711
736
|
const [hint, fn] = entry;
|
|
712
737
|
await em.populate(changed, hint);
|
|
713
|
-
await Promise.all(changed.map((entity) => index_1.setField(entity, key, fn(entity))));
|
|
738
|
+
await Promise.all(changed.map((entity) => (0, index_1.setField)(entity, key, fn(entity))));
|
|
714
739
|
}
|
|
715
740
|
});
|
|
716
741
|
await Promise.all(p);
|
|
717
742
|
});
|
|
718
743
|
await Promise.all(p);
|
|
719
744
|
}
|
|
720
|
-
// If a where clause includes an entity, object-hash cannot hash it, so just use the id.
|
|
721
|
-
const replacer = (v) => (isEntity(v) ? v.id : v);
|
|
722
|
-
function whereFilterHash(where) {
|
|
723
|
-
return object_hash_1.default(where, { replacer });
|
|
724
|
-
}
|
|
725
745
|
/**
|
|
726
746
|
* Walks `reverseHint` for every entity in `entities`.
|
|
727
747
|
*
|
|
@@ -736,18 +756,17 @@ async function followReverseHint(entities, reverseHint) {
|
|
|
736
756
|
const fieldName = paths.shift();
|
|
737
757
|
// The path might touch either a reference or a collection
|
|
738
758
|
const entitiesOrLists = await Promise.all(current.flatMap((c) => {
|
|
739
|
-
var _a;
|
|
740
759
|
const currentValuePromise = c[fieldName].load();
|
|
741
760
|
// If we're going from Book.author back to Author to re-validate the Author.books collection,
|
|
742
761
|
// see if Book.author has changed so we can re-validate both the old author's books and the
|
|
743
762
|
// new author's books.
|
|
744
|
-
const isReference =
|
|
763
|
+
const isReference = getMetadata(c).fields[fieldName]?.kind === "m2o";
|
|
745
764
|
const hasChanged = isReference && c.changes[fieldName].hasChanged;
|
|
746
765
|
const originalValue = c.changes[fieldName].originalValue;
|
|
747
766
|
if (hasChanged && originalValue) {
|
|
748
767
|
const originalEntityMaybePromise = isEntity(originalValue)
|
|
749
768
|
? originalValue
|
|
750
|
-
:
|
|
769
|
+
: c.em.load(c[fieldName].otherMeta.cstr, originalValue);
|
|
751
770
|
return [currentValuePromise, originalEntityMaybePromise];
|
|
752
771
|
}
|
|
753
772
|
return [currentValuePromise];
|