joist-core 2.1.0 → 2.2.0-next.2

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 (166) hide show
  1. package/build/ConditionBuilder.d.ts +1 -22
  2. package/build/ConditionBuilder.d.ts.map +1 -1
  3. package/build/ConditionBuilder.js +6 -72
  4. package/build/ConditionBuilder.js.map +1 -1
  5. package/build/EntityFilter.d.ts +4 -2
  6. package/build/EntityFilter.d.ts.map +1 -1
  7. package/build/EntityGraphQLFilter.d.ts +2 -0
  8. package/build/EntityGraphQLFilter.d.ts.map +1 -1
  9. package/build/EntityGraphQLFilter.js.map +1 -1
  10. package/build/EntityManager.d.ts +25 -39
  11. package/build/EntityManager.d.ts.map +1 -1
  12. package/build/EntityManager.js +97 -44
  13. package/build/EntityManager.js.map +1 -1
  14. package/build/EntityMetadata.d.ts +2 -0
  15. package/build/EntityMetadata.d.ts.map +1 -1
  16. package/build/EntityMetadata.js.map +1 -1
  17. package/build/InstanceData.d.ts +7 -1
  18. package/build/InstanceData.d.ts.map +1 -1
  19. package/build/InstanceData.js +36 -7
  20. package/build/InstanceData.js.map +1 -1
  21. package/build/IsLoadedCache.d.ts +1 -1
  22. package/build/IsLoadedCache.d.ts.map +1 -1
  23. package/build/IsLoadedCache.js +6 -1
  24. package/build/IsLoadedCache.js.map +1 -1
  25. package/build/JoinRows.d.ts +1 -1
  26. package/build/JoinRows.js +6 -1
  27. package/build/JoinRows.js.map +1 -1
  28. package/build/PluginManager.d.ts +12 -0
  29. package/build/PluginManager.d.ts.map +1 -1
  30. package/build/PluginManager.js +18 -2
  31. package/build/PluginManager.js.map +1 -1
  32. package/build/QueryParser.collectionJoins.d.ts +27 -0
  33. package/build/QueryParser.collectionJoins.d.ts.map +1 -0
  34. package/build/QueryParser.collectionJoins.js +460 -0
  35. package/build/QueryParser.collectionJoins.js.map +1 -0
  36. package/build/QueryParser.collectionJoins.test.d.ts +2 -0
  37. package/build/QueryParser.collectionJoins.test.d.ts.map +1 -0
  38. package/build/QueryParser.collectionJoins.test.js +772 -0
  39. package/build/QueryParser.collectionJoins.test.js.map +1 -0
  40. package/build/QueryParser.d.ts +67 -9
  41. package/build/QueryParser.d.ts.map +1 -1
  42. package/build/QueryParser.js +39 -33
  43. package/build/QueryParser.js.map +1 -1
  44. package/build/QueryParser.pruning.d.ts +4 -2
  45. package/build/QueryParser.pruning.d.ts.map +1 -1
  46. package/build/QueryParser.pruning.js +80 -10
  47. package/build/QueryParser.pruning.js.map +1 -1
  48. package/build/QueryParser.pruning.test.d.ts +2 -0
  49. package/build/QueryParser.pruning.test.d.ts.map +1 -0
  50. package/build/QueryParser.pruning.test.js +106 -0
  51. package/build/QueryParser.pruning.test.js.map +1 -0
  52. package/build/QueryVisitor.d.ts.map +1 -1
  53. package/build/QueryVisitor.js +22 -0
  54. package/build/QueryVisitor.js.map +1 -1
  55. package/build/ReactionsManager.js +2 -2
  56. package/build/batchloaders/BatchLoader.d.ts.map +1 -1
  57. package/build/batchloaders/BatchLoader.js +4 -1
  58. package/build/batchloaders/BatchLoader.js.map +1 -1
  59. package/build/batchloaders/manyToManyBatchLoader.js +3 -1
  60. package/build/batchloaders/manyToManyBatchLoader.js.map +1 -1
  61. package/build/batchloaders/recursiveM2mBatchLoader.d.ts.map +1 -1
  62. package/build/batchloaders/recursiveM2mBatchLoader.js +3 -1
  63. package/build/batchloaders/recursiveM2mBatchLoader.js.map +1 -1
  64. package/build/changes.d.ts.map +1 -1
  65. package/build/changes.js +1 -4
  66. package/build/changes.js.map +1 -1
  67. package/build/config.d.ts.map +1 -1
  68. package/build/config.js +18 -10
  69. package/build/config.js.map +1 -1
  70. package/build/configure.js +2 -2
  71. package/build/dataloaders/findCountDataLoader.js +2 -2
  72. package/build/dataloaders/findCountDataLoader.js.map +1 -1
  73. package/build/dataloaders/findDataLoader.d.ts +2 -1
  74. package/build/dataloaders/findDataLoader.d.ts.map +1 -1
  75. package/build/dataloaders/findDataLoader.js +26 -23
  76. package/build/dataloaders/findDataLoader.js.map +1 -1
  77. package/build/dataloaders/findIdsDataLoader.js +2 -2
  78. package/build/dataloaders/findIdsDataLoader.js.map +1 -1
  79. package/build/dataloaders/findOrCreateDataLoader.d.ts.map +1 -1
  80. package/build/dataloaders/findOrCreateDataLoader.js +6 -1
  81. package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
  82. package/build/dataloaders/findPaginatedDataLoader.d.ts +8 -0
  83. package/build/dataloaders/findPaginatedDataLoader.d.ts.map +1 -0
  84. package/build/dataloaders/findPaginatedDataLoader.js +76 -0
  85. package/build/dataloaders/findPaginatedDataLoader.js.map +1 -0
  86. package/build/defaults.d.ts.map +1 -1
  87. package/build/defaults.js +49 -42
  88. package/build/defaults.js.map +1 -1
  89. package/build/drivers/buildRawQuery.d.ts +6 -4
  90. package/build/drivers/buildRawQuery.d.ts.map +1 -1
  91. package/build/drivers/buildRawQuery.js +6 -5
  92. package/build/drivers/buildRawQuery.js.map +1 -1
  93. package/build/drivers/buildUtils.d.ts +7 -2
  94. package/build/drivers/buildUtils.d.ts.map +1 -1
  95. package/build/drivers/buildUtils.js +14 -5
  96. package/build/drivers/buildUtils.js.map +1 -1
  97. package/build/fields.d.ts.map +1 -1
  98. package/build/fields.js +16 -7
  99. package/build/fields.js.map +1 -1
  100. package/build/index.d.ts +2 -1
  101. package/build/index.d.ts.map +1 -1
  102. package/build/index.js +7 -5
  103. package/build/index.js.map +1 -1
  104. package/build/json.d.ts +3 -3
  105. package/build/json.d.ts.map +1 -1
  106. package/build/json.js +4 -4
  107. package/build/json.js.map +1 -1
  108. package/build/loadHints.d.ts +4 -4
  109. package/build/loadHints.d.ts.map +1 -1
  110. package/build/loadLens.js +1 -1
  111. package/build/loadLens.js.map +1 -1
  112. package/build/preloading/JsonAggregatePreloader.js +6 -2
  113. package/build/preloading/JsonAggregatePreloader.js.map +1 -1
  114. package/build/reactiveHints.d.ts +24 -7
  115. package/build/reactiveHints.d.ts.map +1 -1
  116. package/build/reactiveHints.js +45 -28
  117. package/build/reactiveHints.js.map +1 -1
  118. package/build/relations/AbstractRelationImpl.d.ts +7 -2
  119. package/build/relations/AbstractRelationImpl.d.ts.map +1 -1
  120. package/build/relations/AbstractRelationImpl.js.map +1 -1
  121. package/build/relations/AsyncProperty.d.ts +36 -0
  122. package/build/relations/AsyncProperty.d.ts.map +1 -0
  123. package/build/relations/AsyncProperty.js +80 -0
  124. package/build/relations/AsyncProperty.js.map +1 -0
  125. package/build/relations/{ReactiveQueryField.d.ts → AsyncReactiveField.d.ts} +10 -10
  126. package/build/relations/AsyncReactiveField.d.ts.map +1 -0
  127. package/build/relations/{ReactiveQueryField.js → AsyncReactiveField.js} +19 -19
  128. package/build/relations/{ReactiveQueryField.js.map → AsyncReactiveField.js.map} +1 -1
  129. package/build/relations/ReactiveField.d.ts +7 -9
  130. package/build/relations/ReactiveField.d.ts.map +1 -1
  131. package/build/relations/ReactiveField.js +5 -10
  132. package/build/relations/ReactiveField.js.map +1 -1
  133. package/build/relations/ReactiveGetter.d.ts +5 -5
  134. package/build/relations/ReactiveGetter.d.ts.map +1 -1
  135. package/build/relations/ReactiveGetter.js +3 -3
  136. package/build/relations/ReactiveGetter.js.map +1 -1
  137. package/build/relations/ReactiveReference.d.ts +2 -2
  138. package/build/relations/ReactiveReference.d.ts.map +1 -1
  139. package/build/relations/ReactiveReference.js +100 -36
  140. package/build/relations/ReactiveReference.js.map +1 -1
  141. package/build/relations/{hasAsyncProperty.d.ts → hasProperty.d.ts} +12 -12
  142. package/build/relations/hasProperty.d.ts.map +1 -0
  143. package/build/relations/{hasAsyncProperty.js → hasProperty.js} +20 -20
  144. package/build/relations/hasProperty.js.map +1 -0
  145. package/build/relations/index.d.ts +3 -2
  146. package/build/relations/index.d.ts.map +1 -1
  147. package/build/relations/index.js +16 -11
  148. package/build/relations/index.js.map +1 -1
  149. package/build/resurrection.d.ts +10 -0
  150. package/build/resurrection.d.ts.map +1 -0
  151. package/build/resurrection.js +93 -0
  152. package/build/resurrection.js.map +1 -0
  153. package/build/rules.js +3 -3
  154. package/build/trusted.d.ts +1 -1
  155. package/build/trusted.d.ts.map +1 -1
  156. package/build/trusted.js +1 -1
  157. package/build/trusted.js.map +1 -1
  158. package/build/upsert.d.ts.map +1 -1
  159. package/build/upsert.js +26 -10
  160. package/build/upsert.js.map +1 -1
  161. package/build/withLoaded.js +5 -5
  162. package/build/withLoaded.js.map +1 -1
  163. package/package.json +9 -9
  164. package/build/relations/ReactiveQueryField.d.ts.map +0 -1
  165. package/build/relations/hasAsyncProperty.d.ts.map +0 -1
  166. package/build/relations/hasAsyncProperty.js.map +0 -1
@@ -37,6 +37,7 @@ const findCountDataLoader_1 = require("./dataloaders/findCountDataLoader");
37
37
  const findDataLoader_1 = require("./dataloaders/findDataLoader");
38
38
  const findIdsDataLoader_1 = require("./dataloaders/findIdsDataLoader");
39
39
  const findOrCreateDataLoader_1 = require("./dataloaders/findOrCreateDataLoader");
40
+ const findPaginatedDataLoader_1 = require("./dataloaders/findPaginatedDataLoader");
40
41
  const Entity_1 = require("./Entity");
41
42
  const FlushLock_1 = require("./FlushLock");
42
43
  const index_1 = require("./index");
@@ -48,6 +49,7 @@ const PluginManager_1 = require("./PluginManager");
48
49
  const ReactionsManager_1 = require("./ReactionsManager");
49
50
  const reactiveHints_1 = require("./reactiveHints");
50
51
  const relations_1 = require("./relations");
52
+ const AsyncProperty_1 = require("./relations/AsyncProperty");
51
53
  const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
52
54
  const RecursiveCollection_1 = require("./relations/RecursiveCollection");
53
55
  const Todo_1 = require("./Todo");
@@ -246,7 +248,10 @@ class EntityManager {
246
248
  async find(type, where, options) {
247
249
  const { populate, ...rest } = options || {};
248
250
  const settings = { where, ...rest };
249
- const result = await (0, findDataLoader_1.findDataLoader)(this, type, settings, populate)
251
+ const loader = hasPaginationSettings(rest)
252
+ ? (0, findPaginatedDataLoader_1.findPaginatedDataLoader)(this, type, settings, populate)
253
+ : (0, findDataLoader_1.findDataLoader)(this, type, settings, populate);
254
+ const result = await loader
250
255
  .load(settings)
251
256
  .catch(function find(err) {
252
257
  throw appendStack(err, new Error());
@@ -256,30 +261,52 @@ class EntityManager {
256
261
  }
257
262
  return result;
258
263
  }
259
- async findPaginated(type, where, options) {
260
- const { populate, limit, offset, ...rest } = options || {};
261
- const meta = (0, index_1.getMetadata)(type);
262
- const query = (0, index_1.parseFindQuery)(meta, where, rest);
263
- const rows = await this.executeFind(meta, "findPaginated", query, { limit, offset });
264
- // check row limit
265
- const result = this.hydrate(type, rows);
266
- if (populate) {
267
- await this.populate(result, populate);
268
- }
269
- return result;
270
- }
264
+ /** Runs the post-parse find pipeline: plugins mutate the logical AST, then Joist optimizes/prunes before SQL. */
271
265
  async executeFind(meta, operation, parsed, settings) {
266
+ const { checkLimit, findSettings } = this.prepareFind(meta, operation, parsed, settings);
267
+ return this.executePreparedFind(meta, operation, parsed, findSettings, checkLimit);
268
+ }
269
+ /** Executes a query that has already had find hooks and optimizations applied. */
270
+ async executePreparedFind(meta, operation, parsed, findSettings, checkLimit) {
272
271
  const { pluginManager } = getEmInternalApi(this);
273
- pluginManager.beforeFind(meta, operation, parsed, settings);
274
- const rows = await this.driver.executeFind(this, parsed, settings);
272
+ const rows = await this.driver.executeFind(this, parsed, findSettings);
273
+ // Check by default unless explicitly disabled or the caller removed the LIMIT via `limit: undefined`
274
+ const shouldCheck = checkLimit ?? !("limit" in findSettings && findSettings.limit === undefined);
275
+ if (shouldCheck && rows.length >= this.entityLimit) {
276
+ throw new Error(`Query returned more than ${this.entityLimit} entityLimit rows`);
277
+ }
275
278
  pluginManager.afterFind(meta, operation, rows);
276
279
  return rows;
277
280
  }
278
- async findGql(type, where, options) {
279
- return this.find(type, where, options);
281
+ /**
282
+ * Runs pre-SQL find hooks and optimizations against a parsed query.
283
+ *
284
+ * This allows plugins to see "pre-batched" / "logical" query ASTs, instead of our
285
+ * more complicated `_find` batched queries. The flow would be:
286
+ *
287
+ * - A loader calls `prepareFind(originalQuery)`
288
+ * - Plugins inspect/modify the query as/if needed
289
+ * - The loader crafts a new, more complicated query that embeds the originalQuery
290
+ * - The loader calls `executePreparedFind` with the 2nd query
291
+ */
292
+ prepareFind(meta, operation, parsed, settings) {
293
+ const { checkLimit, ...findSettings } = settings;
294
+ const { pluginManager } = getEmInternalApi(this);
295
+ // Plugins may mutate the settings object, so return the post-hook version that loaders must reuse.
296
+ pluginManager.beforeFind(meta, operation, parsed, findSettings);
297
+ (0, index_1.optimizeCollectionJoins)(parsed, settings);
298
+ return { checkLimit, findSettings };
280
299
  }
281
- async findGqlPaginated(type, where, options) {
282
- return this.findPaginated(type, where, options);
300
+ async findGql(type, where, options) {
301
+ if (!options) {
302
+ return this.find(type, where);
303
+ }
304
+ const normalized = { ...options };
305
+ if ("limit" in normalized)
306
+ normalized.limit = normalized.limit ?? undefined;
307
+ if ("offset" in normalized)
308
+ normalized.offset = normalized.offset ?? undefined;
309
+ return this.find(type, where, normalized);
283
310
  }
284
311
  async findOne(type, where, options) {
285
312
  const list = await this.find(type, where, options);
@@ -878,11 +905,11 @@ class EntityManager {
878
905
  const createdThenDeleted = new Set();
879
906
  // We'll only invoke hooks once/entity (the 1st time that entity goes through runHooksOnPendingEntities)
880
907
  const hooksInvoked = new Set();
881
- // Make sure two ReactiveQueryFields don't ping-pong each other forever
908
+ // Make sure two AsyncReactiveFields don't ping-pong each other forever
882
909
  let hookLoops = 0;
883
910
  let now = getNow();
884
911
  const suppressedDefaultTypeErrors = [];
885
- // Make a lambda that we can invoke multiple times, if we loop for ReactiveQueryFields
912
+ // Make a lambda that we can invoke multiple times, if we loop for AsyncReactiveFields
886
913
  const runHooksOnPendingEntities = async () => {
887
914
  if (hookLoops++ >= 10)
888
915
  throw new Error("runHooksOnPendingEntities has ran 10 iterations, aborting");
@@ -899,18 +926,18 @@ class EntityManager {
899
926
  // Subset of pendingFlush entities that had hooks invoked in a prior `runHooksOnPendingEntities`
900
927
  const alreadyRanHooks = new Set();
901
928
  findPendingFlushEntities(this.entities, hooksInvoked, pendingFlush, pendingHooks, alreadyRanHooks);
902
- // If we're re-looping for ReactiveQueryField, make sure to bump updatedAt
929
+ // If we're re-looping for AsyncReactiveField, make sure to bump updatedAt
903
930
  // each time, so that for an INSERT-then-UPDATE the triggers don't think the
904
931
  // UPDATE forgot to self-bump updatedAt, and then "helpfully" bump it for us.
905
932
  if (alreadyRanHooks.size > 0) {
906
- maybeBumpUpdatedAt((0, Todo_1.createTodos)([...alreadyRanHooks]), now);
933
+ maybeBumpUpdatedAt(this.#rm, (0, Todo_1.createTodos)([...alreadyRanHooks]), now);
907
934
  }
908
935
  // Run hooks in a series of loops until things "settle down"
909
936
  while (pendingHooks.size > 0) {
910
937
  await this.#fl.allowWrites(async () => {
911
938
  let todos = (0, Todo_1.createTodos)([...pendingHooks]);
912
939
  await (0, defaults_1.setAsyncDefaults)(suppressedDefaultTypeErrors, this.ctx, Todo_1.Todo.groupInsertsByTypeAndSubType(todos));
913
- maybeBumpUpdatedAt(todos, now);
940
+ maybeBumpUpdatedAt(this.#rm, todos, now);
914
941
  // Run our hooks
915
942
  for (const group of maybeSetupHookOrdering(todos)) {
916
943
  await beforeCreate(this.ctx, group);
@@ -955,24 +982,30 @@ class EntityManager {
955
982
  return !createThenDelete;
956
983
  });
957
984
  };
958
- const runValidation = async (entityTodos, joinRowTodos) => {
985
+ const { pluginManager } = getEmInternalApi(this);
986
+ const runValidation = async (entityTodos, joinRowTodos, validate) => {
987
+ const changedEntities = entitiesFromTodos(entityTodos, joinRowTodos);
959
988
  try {
960
989
  this.#isValidating = true;
961
- // Run simple rules first b/c it includes not-null/required rules, so that then when we run
962
- // `validateReactiveRules` next, the app's lambdas won't see fundamentally invalid entities & NPE.
963
- await validateSimpleRules(entityTodos);
964
- // After we've let any "author is not set" simple rules fail before prematurely throwing
965
- // the "of course that caused an NPE" `TypeError`s, if all the authors *were* valid/set,
966
- // and we still have TypeErrors (from derived valeus), they were real, unrelated errors
967
- // that the user should see.
968
- if (suppressedDefaultTypeErrors.length > 0)
969
- throw suppressedDefaultTypeErrors[0];
970
- await validateReactiveRules(this, this.#rm.logger, entityTodos, joinRowTodos);
990
+ await pluginManager.beforeValidate(changedEntities);
991
+ if (validate) {
992
+ // Run simple rules first b/c it includes not-null/required rules, so that then when we run
993
+ // `validateReactiveRules` next, the app's lambdas won't see fundamentally invalid entities & NPE.
994
+ await validateSimpleRules(entityTodos);
995
+ // After we've let any "author is not set" simple rules fail before prematurely throwing
996
+ // the "of course that caused an NPE" `TypeError`s, if all the authors *were* valid/set,
997
+ // and we still have TypeErrors (from derived valeus), they were real, unrelated errors
998
+ // that the user should see.
999
+ if (suppressedDefaultTypeErrors.length > 0)
1000
+ throw suppressedDefaultTypeErrors[0];
1001
+ await validateReactiveRules(this, this.#rm.logger, entityTodos, joinRowTodos);
1002
+ await afterValidation(this.ctx, entityTodos);
1003
+ }
1004
+ await pluginManager.afterValidate(changedEntities);
971
1005
  }
972
1006
  finally {
973
1007
  this.#isValidating = false;
974
1008
  }
975
- await afterValidation(this.ctx, entityTodos);
976
1009
  };
977
1010
  // Run hooks (in iterative loops if hooks mutate new entities) on pending entities
978
1011
  let entitiesToFlush = await runHooksOnPendingEntities();
@@ -982,13 +1015,10 @@ class EntityManager {
982
1015
  // the full set of entities that will be INSERT/UPDATE/DELETE-d in the database.
983
1016
  let entityTodos = (0, Todo_1.createTodos)(entitiesToFlush);
984
1017
  let joinRowTodos = (0, Todo_1.combineJoinRows)(this.#joinRows);
985
- if (!skipValidation) {
986
- await runValidation(entityTodos, joinRowTodos);
987
- }
1018
+ await runValidation(entityTodos, joinRowTodos, !skipValidation);
988
1019
  this.#rm.throwIfAnySuppressedTypeErrors();
989
1020
  if (suppressedDefaultTypeErrors.length > 0)
990
1021
  throw suppressedDefaultTypeErrors[0];
991
- const { pluginManager } = getEmInternalApi(this);
992
1022
  if (Object.keys(entityTodos).length > 0 || Object.keys(joinRowTodos).length > 0) {
993
1023
  // The driver will handle the right thing if we're already in an existing transaction.
994
1024
  await this.driver.transaction(this, async () => {
@@ -1009,7 +1039,7 @@ class EntityManager {
1009
1039
  // Actually do the recalc
1010
1040
  await this.#fl.allowWrites(async () => {
1011
1041
  await this.#rm.recalcPendingReactables("reactiveQueries");
1012
- // If any ReactiveFields depended on ReactiveQueryFields, go ahead and calc those now
1042
+ // If any ReactiveFields depended on AsyncReactiveFields, go ahead and calc those now
1013
1043
  await this.#rm.recalcPendingReactables("reactables");
1014
1044
  });
1015
1045
  // Advance `now` so that our triggers don't think our UPDATEs are forgetting to self-bump
@@ -1022,7 +1052,7 @@ class EntityManager {
1022
1052
  // Recreate `entityTodos` against the only-the-just-changed entities
1023
1053
  entityTodos = (0, Todo_1.createTodos)(entitiesToFlush);
1024
1054
  joinRowTodos = (0, Todo_1.combineJoinRows)(this.#joinRows);
1025
- await runValidation(entityTodos, joinRowTodos);
1055
+ await runValidation(entityTodos, joinRowTodos, true);
1026
1056
  this.#rm.throwIfAnySuppressedTypeErrors();
1027
1057
  }
1028
1058
  else {
@@ -1044,6 +1074,11 @@ class EntityManager {
1044
1074
  if (e.isNewEntity && !e.isDeletedEntity)
1045
1075
  this.#entitiesById.set(e.idTagged, e);
1046
1076
  (0, BaseEntity_1.getInstanceData)(e).resetAfterFlushed();
1077
+ // Reset AsyncQueryProperties since DB state may have changed
1078
+ for (const rel of Object.values((0, BaseEntity_1.getInstanceData)(e).relations)) {
1079
+ if (rel instanceof AsyncProperty_1.AsyncPropertyImpl)
1080
+ rel.resetAfterFlush();
1081
+ }
1047
1082
  }
1048
1083
  // Update the joinRows refs to reflect the new state
1049
1084
  for (const joinRow of Object.values(joinRowTodos)) {
@@ -1251,7 +1286,7 @@ class EntityManager {
1251
1286
  (0, fields_1.setField)(entity, field.fieldName, entity[field.fieldName]);
1252
1287
  }));
1253
1288
  // `.load()` recalculated the immediate relations, go ahead and recalc any downstream reactables.
1254
- // We'll still defer ReactiveQueryFields to the em.flush loop.
1289
+ // We'll still defer AsyncReactiveFields to the em.flush loop.
1255
1290
  await this.#rm.recalcPendingReactables("reactables");
1256
1291
  }
1257
1292
  beforeBegin(fn) {
@@ -1859,6 +1894,19 @@ function beforeUpdate(ctx, todos) {
1859
1894
  function afterValidation(ctx, todos) {
1860
1895
  return runHookOnTodos(ctx, "afterValidation", todos, ["inserts", "updates"]);
1861
1896
  }
1897
+ /** Collects changed entities from flush todos, i.e. m2m endpoint entities. */
1898
+ function entitiesFromTodos(entityTodos, joinRowTodos) {
1899
+ const entities = new Set();
1900
+ for (const todo of Object.values(entityTodos)) {
1901
+ [...todo.inserts, ...todo.updates, ...todo.deletes].forEach((entity) => entities.add(entity));
1902
+ }
1903
+ for (const todo of Object.values(joinRowTodos)) {
1904
+ [...todo.newRows, ...todo.deletedRows].forEach((row) => {
1905
+ Object.values(row.columns).forEach((entity) => entities.add(entity));
1906
+ });
1907
+ }
1908
+ return [...entities];
1909
+ }
1862
1910
  function beforeCommit(ctx, entities) {
1863
1911
  return runHook(ctx, "beforeCommit", [...entities]);
1864
1912
  }
@@ -1986,7 +2034,7 @@ function getCascadeDeleteRelations(entity) {
1986
2034
  function isCustomRelation(r) {
1987
2035
  return r instanceof index_1.CustomCollection || r instanceof index_1.CustomReference || r instanceof relations_1.ReactiveReferenceImpl;
1988
2036
  }
1989
- function maybeBumpUpdatedAt(todos, now) {
2037
+ function maybeBumpUpdatedAt(rm, todos, now) {
1990
2038
  for (const todo of Object.values(todos)) {
1991
2039
  const { updatedAt } = todo.metadata.timestampFields ?? {};
1992
2040
  if (updatedAt) {
@@ -1999,6 +2047,7 @@ function maybeBumpUpdatedAt(todos, now) {
1999
2047
  orm.originalData[updatedAt] = (0, fields_1.getField)(e, updatedAt);
2000
2048
  const serde = todo.metadata.fields[updatedAt].serde;
2001
2049
  orm.data[updatedAt] = serde.mapFromNow(now);
2050
+ rm.queueDownstreamReactables(e, updatedAt);
2002
2051
  }
2003
2052
  }
2004
2053
  }
@@ -2074,6 +2123,10 @@ function findPendingFlushEntities(entities, hooksInvoked, pendingFlush, pendingH
2074
2123
  }
2075
2124
  }
2076
2125
  }
2126
+ /** Returns true if the caller explicitly asked `find` to use SQL pagination. */
2127
+ function hasPaginationSettings(options) {
2128
+ return "limit" in options || "offset" in options;
2129
+ }
2077
2130
  /** An error we throw to get knex to `ROLLBACK`, but then catch. */
2078
2131
  class InMemoryRollbackError extends Error {
2079
2132
  }