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.
Files changed (221) hide show
  1. package/build/{BaseEntity.d.ts → src/BaseEntity.d.ts} +2 -1
  2. package/build/{BaseEntity.js → src/BaseEntity.js} +13 -9
  3. package/build/src/BaseEntity.js.map +1 -0
  4. package/build/{EntityManager.d.ts → src/EntityManager.d.ts} +139 -110
  5. package/build/{EntityManager.js → src/EntityManager.js} +281 -262
  6. package/build/src/EntityManager.js.map +1 -0
  7. package/build/{QueryBuilder.d.ts → src/QueryBuilder.d.ts} +53 -3
  8. package/build/src/QueryBuilder.js +341 -0
  9. package/build/src/QueryBuilder.js.map +1 -0
  10. package/build/src/Todo.d.ts +25 -0
  11. package/build/src/Todo.js +52 -0
  12. package/build/src/Todo.js.map +1 -0
  13. package/build/src/changes.d.ts +34 -0
  14. package/build/src/changes.js +37 -0
  15. package/build/src/changes.js.map +1 -0
  16. package/build/src/config.d.ts +43 -0
  17. package/build/src/config.js +114 -0
  18. package/build/src/config.js.map +1 -0
  19. package/build/{createOrUpdatePartial.d.ts → src/createOrUpdatePartial.d.ts} +2 -1
  20. package/build/{createOrUpdatePartial.js → src/createOrUpdatePartial.js} +42 -10
  21. package/build/src/createOrUpdatePartial.js.map +1 -0
  22. package/build/src/dataloaders/findDataLoader.d.ts +5 -0
  23. package/build/src/dataloaders/findDataLoader.js +28 -0
  24. package/build/src/dataloaders/findDataLoader.js.map +1 -0
  25. package/build/src/dataloaders/loadDataLoader.d.ts +3 -0
  26. package/build/src/dataloaders/loadDataLoader.js +37 -0
  27. package/build/src/dataloaders/loadDataLoader.js.map +1 -0
  28. package/build/src/dataloaders/manyToManyDataLoader.d.ts +5 -0
  29. package/build/src/dataloaders/manyToManyDataLoader.js +78 -0
  30. package/build/src/dataloaders/manyToManyDataLoader.js.map +1 -0
  31. package/build/src/dataloaders/manyToManyFindDataLoader.d.ts +5 -0
  32. package/build/src/dataloaders/manyToManyFindDataLoader.js +33 -0
  33. package/build/src/dataloaders/manyToManyFindDataLoader.js.map +1 -0
  34. package/build/src/dataloaders/oneToManyDataLoader.d.ts +4 -0
  35. package/build/src/dataloaders/oneToManyDataLoader.js +40 -0
  36. package/build/src/dataloaders/oneToManyDataLoader.js.map +1 -0
  37. package/build/src/dataloaders/oneToManyFindDataLoader.d.ts +5 -0
  38. package/build/src/dataloaders/oneToManyFindDataLoader.js +32 -0
  39. package/build/src/dataloaders/oneToManyFindDataLoader.js.map +1 -0
  40. package/build/src/dataloaders/oneToOneDataLoader.d.ts +4 -0
  41. package/build/src/dataloaders/oneToOneDataLoader.js +40 -0
  42. package/build/src/dataloaders/oneToOneDataLoader.js.map +1 -0
  43. package/build/src/drivers/IdAssigner.d.ts +33 -0
  44. package/build/src/drivers/IdAssigner.js +106 -0
  45. package/build/src/drivers/IdAssigner.js.map +1 -0
  46. package/build/src/drivers/InMemoryDriver.d.ts +29 -0
  47. package/build/src/drivers/InMemoryDriver.js +306 -0
  48. package/build/src/drivers/InMemoryDriver.js.map +1 -0
  49. package/build/src/drivers/PostgresDriver.d.ts +40 -0
  50. package/build/src/drivers/PostgresDriver.js +376 -0
  51. package/build/src/drivers/PostgresDriver.js.map +1 -0
  52. package/build/src/drivers/driver.d.ts +23 -0
  53. package/build/src/drivers/driver.js +3 -0
  54. package/build/src/drivers/driver.js.map +1 -0
  55. package/build/src/drivers/index.d.ts +4 -0
  56. package/build/src/drivers/index.js +17 -0
  57. package/build/src/drivers/index.js.map +1 -0
  58. package/build/{getProperties.d.ts → src/getProperties.d.ts} +0 -0
  59. package/build/{getProperties.js → src/getProperties.js} +1 -1
  60. package/build/src/getProperties.js.map +1 -0
  61. package/build/src/index.d.ts +62 -0
  62. package/build/src/index.js +263 -0
  63. package/build/src/index.js.map +1 -0
  64. package/build/src/keys.d.ts +30 -0
  65. package/build/{keys.js → src/keys.js} +48 -16
  66. package/build/src/keys.js.map +1 -0
  67. package/build/{loadLens.d.ts → src/loadLens.d.ts} +2 -2
  68. package/build/{loadLens.js → src/loadLens.js} +1 -1
  69. package/build/src/loadLens.js.map +1 -0
  70. package/build/src/loaded.d.ts +49 -0
  71. package/build/src/loaded.js +9 -0
  72. package/build/src/loaded.js.map +1 -0
  73. package/build/{newTestInstance.d.ts → src/newTestInstance.d.ts} +37 -3
  74. package/build/src/newTestInstance.js +342 -0
  75. package/build/src/newTestInstance.js.map +1 -0
  76. package/build/{collections → src/relations}/AbstractRelationImpl.d.ts +6 -5
  77. package/build/{collections → src/relations}/AbstractRelationImpl.js +0 -0
  78. package/build/src/relations/AbstractRelationImpl.js.map +1 -0
  79. package/build/src/relations/Collection.d.ts +26 -0
  80. package/build/src/relations/Collection.js +19 -0
  81. package/build/src/relations/Collection.js.map +1 -0
  82. package/build/{collections → src/relations}/CustomCollection.d.ts +6 -2
  83. package/build/{collections → src/relations}/CustomCollection.js +17 -9
  84. package/build/src/relations/CustomCollection.js.map +1 -0
  85. package/build/{collections → src/relations}/CustomReference.d.ts +7 -2
  86. package/build/{collections → src/relations}/CustomReference.js +16 -9
  87. package/build/src/relations/CustomReference.js.map +1 -0
  88. package/build/src/relations/LargeCollection.d.ts +17 -0
  89. package/build/src/relations/LargeCollection.js +3 -0
  90. package/build/src/relations/LargeCollection.js.map +1 -0
  91. package/build/{collections → src/relations}/ManyToManyCollection.d.ts +9 -2
  92. package/build/src/relations/ManyToManyCollection.js +249 -0
  93. package/build/src/relations/ManyToManyCollection.js.map +1 -0
  94. package/build/src/relations/ManyToManyLargeCollection.d.ts +25 -0
  95. package/build/src/relations/ManyToManyLargeCollection.js +97 -0
  96. package/build/src/relations/ManyToManyLargeCollection.js.map +1 -0
  97. package/build/src/relations/ManyToOneReference.d.ts +77 -0
  98. package/build/{collections → src/relations}/ManyToOneReference.js +101 -48
  99. package/build/src/relations/ManyToOneReference.js.map +1 -0
  100. package/build/{collections → src/relations}/OneToManyCollection.d.ts +10 -2
  101. package/build/{collections → src/relations}/OneToManyCollection.js +54 -59
  102. package/build/src/relations/OneToManyCollection.js.map +1 -0
  103. package/build/src/relations/OneToManyLargeCollection.d.ts +25 -0
  104. package/build/src/relations/OneToManyLargeCollection.js +83 -0
  105. package/build/src/relations/OneToManyLargeCollection.js.map +1 -0
  106. package/build/src/relations/OneToOneReference.d.ts +82 -0
  107. package/build/src/relations/OneToOneReference.js +168 -0
  108. package/build/src/relations/OneToOneReference.js.map +1 -0
  109. package/build/src/relations/PolymorphicReference.d.ts +69 -0
  110. package/build/src/relations/PolymorphicReference.js +210 -0
  111. package/build/src/relations/PolymorphicReference.js.map +1 -0
  112. package/build/src/relations/Reference.d.ts +29 -0
  113. package/build/src/relations/Reference.js +23 -0
  114. package/build/src/relations/Reference.js.map +1 -0
  115. package/build/src/relations/Relation.d.ts +10 -0
  116. package/build/src/relations/Relation.js +13 -0
  117. package/build/src/relations/Relation.js.map +1 -0
  118. package/build/src/relations/hasAsyncProperty.d.ts +36 -0
  119. package/build/src/relations/hasAsyncProperty.js +55 -0
  120. package/build/src/relations/hasAsyncProperty.js.map +1 -0
  121. package/build/{collections → src/relations}/hasManyDerived.d.ts +2 -1
  122. package/build/{collections → src/relations}/hasManyDerived.js +1 -1
  123. package/build/src/relations/hasManyDerived.js.map +1 -0
  124. package/build/{collections → src/relations}/hasManyThrough.d.ts +0 -0
  125. package/build/{collections → src/relations}/hasManyThrough.js +2 -2
  126. package/build/src/relations/hasManyThrough.js.map +1 -0
  127. package/build/{collections → src/relations}/hasOneDerived.d.ts +3 -2
  128. package/build/{collections → src/relations}/hasOneDerived.js +1 -1
  129. package/build/src/relations/hasOneDerived.js.map +1 -0
  130. package/build/{collections → src/relations}/hasOneThrough.d.ts +0 -0
  131. package/build/{collections → src/relations}/hasOneThrough.js +2 -2
  132. package/build/src/relations/hasOneThrough.js.map +1 -0
  133. package/build/src/relations/index.d.ts +18 -0
  134. package/build/src/relations/index.js +53 -0
  135. package/build/src/relations/index.js.map +1 -0
  136. package/build/{reverseHint.d.ts → src/reverseHint.d.ts} +2 -1
  137. package/build/{reverseHint.js → src/reverseHint.js} +13 -9
  138. package/build/src/reverseHint.js.map +1 -0
  139. package/build/src/rules.d.ts +23 -0
  140. package/build/src/rules.js +23 -0
  141. package/build/src/rules.js.map +1 -0
  142. package/build/src/serde.d.ts +121 -0
  143. package/build/src/serde.js +190 -0
  144. package/build/src/serde.js.map +1 -0
  145. package/build/{utils.d.ts → src/utils.d.ts} +2 -0
  146. package/build/{utils.js → src/utils.js} +10 -1
  147. package/build/src/utils.js.map +1 -0
  148. package/build/tsconfig.tsbuildinfo +1 -0
  149. package/package.json +30 -15
  150. package/build/BaseEntity.js.map +0 -1
  151. package/build/EntityManager.js.map +0 -1
  152. package/build/EntityPersister.d.ts +0 -30
  153. package/build/EntityPersister.js +0 -197
  154. package/build/EntityPersister.js.map +0 -1
  155. package/build/QueryBuilder.js +0 -195
  156. package/build/QueryBuilder.js.map +0 -1
  157. package/build/changes.d.ts +0 -23
  158. package/build/changes.js +0 -14
  159. package/build/changes.js.map +0 -1
  160. package/build/collections/AbstractRelationImpl.js.map +0 -1
  161. package/build/collections/CustomCollection.js.map +0 -1
  162. package/build/collections/CustomReference.js.map +0 -1
  163. package/build/collections/ManyToManyCollection.js +0 -288
  164. package/build/collections/ManyToManyCollection.js.map +0 -1
  165. package/build/collections/ManyToOneReference.d.ts +0 -50
  166. package/build/collections/ManyToOneReference.js.map +0 -1
  167. package/build/collections/OneToManyCollection.js.map +0 -1
  168. package/build/collections/OneToOneReference.d.ts +0 -51
  169. package/build/collections/OneToOneReference.js +0 -132
  170. package/build/collections/OneToOneReference.js.map +0 -1
  171. package/build/collections/hasManyDerived.js.map +0 -1
  172. package/build/collections/hasManyThrough.js.map +0 -1
  173. package/build/collections/hasOneDerived.js.map +0 -1
  174. package/build/collections/hasOneThrough.js.map +0 -1
  175. package/build/collections/index.d.ts +0 -19
  176. package/build/collections/index.js +0 -49
  177. package/build/collections/index.js.map +0 -1
  178. package/build/createOrUpdatePartial.js.map +0 -1
  179. package/build/getProperties.js.map +0 -1
  180. package/build/index.d.ts +0 -140
  181. package/build/index.js +0 -278
  182. package/build/index.js.map +0 -1
  183. package/build/keys.d.ts +0 -21
  184. package/build/keys.js.map +0 -1
  185. package/build/loadLens.js.map +0 -1
  186. package/build/newTestInstance.js +0 -153
  187. package/build/newTestInstance.js.map +0 -1
  188. package/build/reverseHint.js.map +0 -1
  189. package/build/serde.d.ts +0 -47
  190. package/build/serde.js +0 -93
  191. package/build/serde.js.map +0 -1
  192. package/build/utils.js.map +0 -1
  193. package/package.json.bak +0 -27
  194. package/src/BaseEntity.ts +0 -104
  195. package/src/EntityManager.ts +0 -1263
  196. package/src/EntityPersister.ts +0 -240
  197. package/src/QueryBuilder.ts +0 -289
  198. package/src/changes.ts +0 -40
  199. package/src/collections/AbstractRelationImpl.ts +0 -28
  200. package/src/collections/CustomCollection.ts +0 -152
  201. package/src/collections/CustomReference.ts +0 -138
  202. package/src/collections/ManyToManyCollection.ts +0 -346
  203. package/src/collections/ManyToOneReference.ts +0 -215
  204. package/src/collections/OneToManyCollection.ts +0 -254
  205. package/src/collections/OneToOneReference.ts +0 -153
  206. package/src/collections/hasManyDerived.ts +0 -29
  207. package/src/collections/hasManyThrough.ts +0 -20
  208. package/src/collections/hasOneDerived.ts +0 -26
  209. package/src/collections/hasOneThrough.ts +0 -20
  210. package/src/collections/index.ts +0 -74
  211. package/src/createOrUpdatePartial.ts +0 -144
  212. package/src/getProperties.ts +0 -27
  213. package/src/index.ts +0 -400
  214. package/src/keys.ts +0 -75
  215. package/src/loadLens.ts +0 -126
  216. package/src/newTestInstance.ts +0 -205
  217. package/src/reverseHint.ts +0 -43
  218. package/src/serde.ts +0 -97
  219. package/src/utils.ts +0 -63
  220. package/tsconfig.json +0 -21
  221. 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 EntityPersister_1 = require("./EntityPersister");
6
+ const findDataLoader_1 = require("./dataloaders/findDataLoader");
7
+ const loadDataLoader_1 = require("./dataloaders/loadDataLoader");
13
8
  const index_1 = require("./index");
14
- const QueryBuilder_1 = require("./QueryBuilder");
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.knex = em.knex;
37
- this.hooks = { beforeTransaction: [...em.hooks.beforeTransaction] };
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.knex = emOrCtx.knex;
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 this.loaderForFind(type).load({ where, ...options });
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 === null || options === void 0 ? void 0 : options.populate) {
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 this.loaderForFind(type).load({ where, ...options });
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 === null || options === void 0 ? void 0 : options.populate) {
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
- async load(type, id, hint) {
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.tagIfNeeded(meta, id);
131
- const entity = this.findExistingInstance(tagged) || (await this.loaderForEntity(meta).load(tagged));
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.tagIfNeeded(meta, id));
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) || this.loaderForEntity(meta).load(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 = Array.isArray(entityOrList) ? entityOrList : [entityOrList];
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
- const originalKnex = this.knex;
202
- const txn = await this.knex.transaction();
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
- finally {
216
- if (!txn.isCompleted()) {
217
- txn.rollback().catch((e) => {
218
- console.error(e, "Error rolling back");
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
- Object.values(deletedEntity)
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 new Promise((resolve, reject) => {
286
- exports.currentFlushSecret.run({ flushSecret: this.flushSecret }, async () => {
287
- try {
288
- const todos = EntityPersister_1.sortEntities(pendingEntities);
289
- // add objects to todos that have reactive hooks
290
- await addReactiveAsyncDerivedValues(todos);
291
- await addReactiveValidations(todos);
292
- // run our hooks
293
- await beforeDelete(this.ctx, todos);
294
- // We defer doing this cascade logic until flush() so that delete() can remain synchronous.
295
- await cascadeDeletesIntoRelations(todos);
296
- await beforeFlush(this.ctx, todos);
297
- recalcDerivedFields(todos);
298
- await recalcAsyncDerivedFields(this, todos);
299
- await validate(todos);
300
- await afterValidation(this.ctx, todos);
301
- entitiesToFlush.push(...pendingEntities);
302
- pendingEntities = this.entities.filter((e) => e.isPendingFlush && !entitiesToFlush.includes(e));
303
- this.flushSecret += 1;
304
- resolve();
305
- }
306
- catch (e) {
307
- reject(e);
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 = EntityPersister_1.sortEntities(entitiesToFlush);
313
- const joinRowTodos = EntityPersister_1.sortJoinRows(this.__data.joinRows);
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
- const alreadyInTxn = "commit" in this.knex;
316
- if (!alreadyInTxn) {
317
- await this.knex.transaction(async (knex) => {
318
- await beforeTransaction(this, knex);
319
- await EntityPersister_1.flushEntities(knex, entityTodos);
320
- await EntityPersister_1.flushJoinTables(knex, joinRowTodos);
321
- // When using `.transaction` with a lambda, we don't explicitly call commit
322
- // await knex.commit();
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) => this._entityIndex.set(e.id, 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
- const loader = this.loaderForEntity(getMetadata(entity));
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(Object.values(entity).map((c) => {
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.columns.forEach((c) => c.serde.setOnEntity(entity.__orm.data, row));
509
+ Object.values(meta.fields).forEach((f) => f.serde?.setOnEntity(entity.__orm.data, row));
518
510
  }
519
- else if ((options === null || options === void 0 ? void 0 : options.overwriteExisting) !== false) {
520
- // Usually if the entity alrady exists, we don't write over it, but in this case
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.columns.forEach((c) => c.serde.setOnEntity(entity.__orm.data, row));
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, bMeta, bCurrent) {
554
- if (a === undefined || bCurrent === undefined) {
555
- return false;
554
+ function sameEntity(a, meta, b) {
555
+ if (a === undefined || b === undefined) {
556
+ return a === undefined && b === undefined;
556
557
  }
557
- return (a === bCurrent || (getMetadata(a) === bMeta && index_1.maybeResolveReferenceToId(a) === index_1.maybeResolveReferenceToId(bCurrent)));
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(entityOrType) {
561
- return (typeof entityOrType === "function"
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 = EntityPersister_1.getTodo(todos, entity);
587
- if (!todo.inserts.includes(entity) && !todo.updates.includes(entity) && !entity.isDeletedEntity) {
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 = EntityPersister_1.getTodo(todos, entity);
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 cascadeDeletesIntoRelations(todos) {
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
- async function beforeTransaction(em, knex) {
638
- await Promise.all(em["hooks"].beforeTransaction.map((fn) => fn(em, knex)));
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
- await Promise.all(p);
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
- async function beforeDelete(ctx, todos) {
652
- await runHook(ctx, "beforeDelete", todos, ["deletes"]);
668
+ function beforeFlush(ctx, todos) {
669
+ return runHook(ctx, "beforeFlush", todos, ["inserts", "updates"]);
653
670
  }
654
- async function beforeFlush(ctx, todos) {
655
- await runHook(ctx, "beforeFlush", todos, ["inserts", "updates"]);
671
+ function beforeCreate(ctx, todos) {
672
+ return runHook(ctx, "beforeCreate", todos, ["inserts"]);
656
673
  }
657
- async function afterValidation(ctx, todos) {
658
- await runHook(ctx, "afterValidation", todos, ["inserts", "updates"]);
674
+ function beforeUpdate(ctx, todos) {
675
+ return runHook(ctx, "beforeUpdate", todos, ["updates"]);
659
676
  }
660
- async function afterCommit(ctx, todos) {
661
- await runHook(ctx, "afterCommit", todos, ["inserts", "updates"]);
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 [m, m.fields.filter((f) => f.kind === "primitive" && f.derived === "sync").map((f) => f.fieldName)];
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 === null || derivedFields === void 0 ? void 0 : derivedFields.forEach((fieldName) => {
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 = ((_a = getMetadata(c).fields.find((f) => f.fieldName === fieldName)) === null || _a === void 0 ? void 0 : _a.kind) === "m2o";
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
- : index_1.getEm(c).load(c[fieldName].otherMeta.cstr, originalValue);
769
+ : c.em.load(c[fieldName].otherMeta.cstr, originalValue);
751
770
  return [currentValuePromise, originalEntityMaybePromise];
752
771
  }
753
772
  return [currentValuePromise];