joist-core 2.3.0-next.98 → 2.3.0-next.99

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 (313) hide show
  1. package/build/BaseEntity.cjs.map +1 -1
  2. package/build/BaseEntity.js.map +1 -1
  3. package/build/Entity.cjs.map +1 -1
  4. package/build/Entity.js.map +1 -1
  5. package/build/EntityManager.cjs.map +1 -1
  6. package/build/EntityManager.d.cts.map +1 -1
  7. package/build/EntityManager.d.mts.map +1 -1
  8. package/build/EntityManager.js.map +1 -1
  9. package/build/EntityMetadata.cjs.map +1 -1
  10. package/build/EntityMetadata.js.map +1 -1
  11. package/build/IndexManager.cjs.map +1 -1
  12. package/build/IndexManager.js.map +1 -1
  13. package/build/InstanceData.cjs.map +1 -1
  14. package/build/InstanceData.js.map +1 -1
  15. package/build/PluginManager.cjs.map +1 -1
  16. package/build/PluginManager.js.map +1 -1
  17. package/build/batchloaders/enumCollectionBatchLoader.cjs.map +1 -1
  18. package/build/batchloaders/enumCollectionBatchLoader.js.map +1 -1
  19. package/build/batchloaders/loadBatchLoader.cjs.map +1 -1
  20. package/build/batchloaders/loadBatchLoader.js.map +1 -1
  21. package/build/batchloaders/manyToManyBatchLoader.cjs.map +1 -1
  22. package/build/batchloaders/manyToManyBatchLoader.js.map +1 -1
  23. package/build/batchloaders/oneToManyBatchLoader.cjs.map +1 -1
  24. package/build/batchloaders/oneToManyBatchLoader.js.map +1 -1
  25. package/build/batchloaders/oneToOneBatchLoader.cjs.map +1 -1
  26. package/build/batchloaders/oneToOneBatchLoader.js.map +1 -1
  27. package/build/batchloaders/populateBatchLoader.cjs.map +1 -1
  28. package/build/batchloaders/populateBatchLoader.js.map +1 -1
  29. package/build/batchloaders/recursiveChildrenBatchLoader.cjs.map +1 -1
  30. package/build/batchloaders/recursiveChildrenBatchLoader.js.map +1 -1
  31. package/build/batchloaders/recursiveM2mBatchLoader.cjs.map +1 -1
  32. package/build/batchloaders/recursiveM2mBatchLoader.js.map +1 -1
  33. package/build/batchloaders/recursiveParentsBatchLoader.cjs.map +1 -1
  34. package/build/batchloaders/recursiveParentsBatchLoader.js.map +1 -1
  35. package/build/changes.cjs.map +1 -1
  36. package/build/changes.js.map +1 -1
  37. package/build/config.cjs.map +1 -1
  38. package/build/config.js.map +1 -1
  39. package/build/configure.cjs.map +1 -1
  40. package/build/configure.js.map +1 -1
  41. package/build/dataloaders/fastWhereFilterHash.cjs.map +1 -1
  42. package/build/dataloaders/fastWhereFilterHash.js.map +1 -1
  43. package/build/dataloaders/findByUniqueDataLoader.cjs.map +1 -1
  44. package/build/dataloaders/findByUniqueDataLoader.d.cts.map +1 -1
  45. package/build/dataloaders/findByUniqueDataLoader.d.mts.map +1 -1
  46. package/build/dataloaders/findByUniqueDataLoader.js.map +1 -1
  47. package/build/dataloaders/findCountDataLoader.cjs.map +1 -1
  48. package/build/dataloaders/findCountDataLoader.js.map +1 -1
  49. package/build/dataloaders/findDataLoader.cjs +1 -1
  50. package/build/dataloaders/findDataLoader.cjs.map +1 -1
  51. package/build/dataloaders/findDataLoader.js +1 -1
  52. package/build/dataloaders/findDataLoader.js.map +1 -1
  53. package/build/dataloaders/findIdsDataLoader.cjs.map +1 -1
  54. package/build/dataloaders/findIdsDataLoader.js.map +1 -1
  55. package/build/dataloaders/findOrCreateDataLoader.cjs.map +1 -1
  56. package/build/dataloaders/findOrCreateDataLoader.d.cts.map +1 -1
  57. package/build/dataloaders/findOrCreateDataLoader.d.mts.map +1 -1
  58. package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
  59. package/build/dataloaders/findPaginatedDataLoader.cjs.map +1 -1
  60. package/build/dataloaders/findPaginatedDataLoader.js.map +1 -1
  61. package/build/dataloaders/lensDataLoader.cjs.map +1 -1
  62. package/build/dataloaders/lensDataLoader.d.cts.map +1 -1
  63. package/build/dataloaders/lensDataLoader.d.mts.map +1 -1
  64. package/build/dataloaders/lensDataLoader.js.map +1 -1
  65. package/build/dataloaders/manyToManyFindDataLoader.cjs.map +1 -1
  66. package/build/dataloaders/manyToManyFindDataLoader.d.cts.map +1 -1
  67. package/build/dataloaders/manyToManyFindDataLoader.d.mts.map +1 -1
  68. package/build/dataloaders/manyToManyFindDataLoader.js.map +1 -1
  69. package/build/dataloaders/oneToManyFindDataLoader.cjs.map +1 -1
  70. package/build/dataloaders/oneToManyFindDataLoader.d.cts.map +1 -1
  71. package/build/dataloaders/oneToManyFindDataLoader.d.mts.map +1 -1
  72. package/build/dataloaders/oneToManyFindDataLoader.js.map +1 -1
  73. package/build/defaults.cjs.map +1 -1
  74. package/build/defaults.d.cts.map +1 -1
  75. package/build/defaults.d.mts.map +1 -1
  76. package/build/defaults.js.map +1 -1
  77. package/build/drivers/EntityWriter.cjs.map +1 -1
  78. package/build/drivers/EntityWriter.js.map +1 -1
  79. package/build/drivers/IdAssigner.cjs.map +1 -1
  80. package/build/drivers/IdAssigner.js.map +1 -1
  81. package/build/drivers/driverApi.cjs.map +1 -1
  82. package/build/drivers/driverApi.js.map +1 -1
  83. package/build/fields.cjs.map +1 -1
  84. package/build/fields.js.map +1 -1
  85. package/build/flush/JoinRows.cjs.map +1 -1
  86. package/build/flush/JoinRows.js.map +1 -1
  87. package/build/flush/Todo.cjs.map +1 -1
  88. package/build/flush/Todo.js.map +1 -1
  89. package/build/getProperties.cjs.map +1 -1
  90. package/build/getProperties.js.map +1 -1
  91. package/build/index.cjs +2 -2
  92. package/build/index.cjs.map +1 -1
  93. package/build/index.js +2 -2
  94. package/build/index.js.map +1 -1
  95. package/build/json.cjs.map +1 -1
  96. package/build/json.js.map +1 -1
  97. package/build/keys.cjs.map +1 -1
  98. package/build/keys.js.map +1 -1
  99. package/build/loading/HintTree.cjs.map +1 -1
  100. package/build/loading/HintTree.js.map +1 -1
  101. package/build/loading/IsLoadedCache.cjs.map +1 -1
  102. package/build/loading/IsLoadedCache.js.map +1 -1
  103. package/build/loading/loadHints.cjs.map +1 -1
  104. package/build/loading/loadHints.js.map +1 -1
  105. package/build/loading/loadLens.cjs.map +1 -1
  106. package/build/loading/loadLens.js.map +1 -1
  107. package/build/loading/withLoaded.cjs.map +1 -1
  108. package/build/loading/withLoaded.js.map +1 -1
  109. package/build/logging/FactoryLogger.cjs.map +1 -1
  110. package/build/logging/FactoryLogger.d.cts.map +1 -1
  111. package/build/logging/FactoryLogger.d.mts.map +1 -1
  112. package/build/logging/FactoryLogger.js.map +1 -1
  113. package/build/logging/FieldLogger.cjs.map +1 -1
  114. package/build/logging/FieldLogger.d.cts.map +1 -1
  115. package/build/logging/FieldLogger.d.mts.map +1 -1
  116. package/build/logging/FieldLogger.js.map +1 -1
  117. package/build/logging/ReactionLogger.cjs.map +1 -1
  118. package/build/logging/ReactionLogger.d.cts.map +1 -1
  119. package/build/logging/ReactionLogger.d.mts.map +1 -1
  120. package/build/logging/ReactionLogger.js.map +1 -1
  121. package/build/newEntity.cjs.map +1 -1
  122. package/build/newEntity.js.map +1 -1
  123. package/build/newTestInstance.cjs.map +1 -1
  124. package/build/newTestInstance.d.cts.map +1 -1
  125. package/build/newTestInstance.d.mts.map +1 -1
  126. package/build/newTestInstance.js.map +1 -1
  127. package/build/normalizeHints.cjs.map +1 -1
  128. package/build/normalizeHints.js.map +1 -1
  129. package/build/nowUTC.cjs.map +1 -1
  130. package/build/nowUTC.js.map +1 -1
  131. package/build/plugins/ImmutableEntitiesPlugin.cjs.map +1 -1
  132. package/build/plugins/ImmutableEntitiesPlugin.js.map +1 -1
  133. package/build/preloading/JsonAggregatePreloader.cjs.map +1 -1
  134. package/build/preloading/JsonAggregatePreloader.d.cts.map +1 -1
  135. package/build/preloading/JsonAggregatePreloader.d.mts.map +1 -1
  136. package/build/preloading/JsonAggregatePreloader.js.map +1 -1
  137. package/build/preloading/canPreload.cjs.map +1 -1
  138. package/build/preloading/canPreload.js.map +1 -1
  139. package/build/preloading/partitionHint.cjs.map +1 -1
  140. package/build/preloading/partitionHint.js.map +1 -1
  141. package/build/queries/ConditionBuilder.cjs.map +1 -1
  142. package/build/queries/ConditionBuilder.js.map +1 -1
  143. package/build/queries/conditions.cjs.map +1 -1
  144. package/build/queries/conditions.js.map +1 -1
  145. package/build/queries/entityQueryUtils.cjs.map +1 -1
  146. package/build/queries/entityQueryUtils.js.map +1 -1
  147. package/build/queries/find/Aliases.cjs.map +1 -1
  148. package/build/queries/find/Aliases.d.cts.map +1 -1
  149. package/build/queries/find/Aliases.d.mts.map +1 -1
  150. package/build/queries/find/Aliases.js.map +1 -1
  151. package/build/queries/find/DeferredAlias.cjs.map +1 -1
  152. package/build/queries/find/DeferredAlias.js.map +1 -1
  153. package/build/queries/find/EntityFilter.cjs.map +1 -1
  154. package/build/queries/find/EntityFilter.js.map +1 -1
  155. package/build/queries/find/EntityGraphQLFilter.cjs.map +1 -1
  156. package/build/queries/find/EntityGraphQLFilter.js.map +1 -1
  157. package/build/queries/find/QueryParser.cjs +1 -1
  158. package/build/queries/find/QueryParser.cjs.map +1 -1
  159. package/build/queries/find/QueryParser.collectionJoins.cjs.map +1 -1
  160. package/build/queries/find/QueryParser.collectionJoins.js.map +1 -1
  161. package/build/queries/find/QueryParser.d.cts.map +1 -1
  162. package/build/queries/find/QueryParser.d.mts.map +1 -1
  163. package/build/queries/find/QueryParser.js +1 -1
  164. package/build/queries/find/QueryParser.js.map +1 -1
  165. package/build/queries/find/QueryParser.pruning.cjs.map +1 -1
  166. package/build/queries/find/QueryParser.pruning.js.map +1 -1
  167. package/build/queries/find/QueryVisitor.cjs.map +1 -1
  168. package/build/queries/find/QueryVisitor.js.map +1 -1
  169. package/build/queries/find/buildFindQuery.cjs.map +1 -1
  170. package/build/queries/find/buildFindQuery.js.map +1 -1
  171. package/build/queries/find/scopes.cjs.map +1 -1
  172. package/build/queries/find/scopes.js.map +1 -1
  173. package/build/queries/parsedConditions.cjs.map +1 -1
  174. package/build/queries/parsedConditions.js.map +1 -1
  175. package/build/queries/renderConditions.cjs.map +1 -1
  176. package/build/queries/renderConditions.js.map +1 -1
  177. package/build/queries/skipCondition.cjs.map +1 -1
  178. package/build/queries/skipCondition.js.map +1 -1
  179. package/build/queries/sql/AliasAssigner.cjs.map +1 -1
  180. package/build/queries/sql/AliasAssigner.js.map +1 -1
  181. package/build/queries/sql/Expr.cjs.map +1 -1
  182. package/build/queries/sql/Expr.js.map +1 -1
  183. package/build/queries/sql/Tables.cjs.map +1 -1
  184. package/build/queries/sql/Tables.d.cts.map +1 -1
  185. package/build/queries/sql/Tables.d.mts.map +1 -1
  186. package/build/queries/sql/Tables.js.map +1 -1
  187. package/build/queries/sql/execute.cjs.map +1 -1
  188. package/build/queries/sql/execute.js.map +1 -1
  189. package/build/queries/sql/expressions/arrayAgg.cjs.map +1 -1
  190. package/build/queries/sql/expressions/arrayAgg.js.map +1 -1
  191. package/build/queries/sql/expressions/case.cjs.map +1 -1
  192. package/build/queries/sql/expressions/case.js.map +1 -1
  193. package/build/queries/sql/expressions/coalesce.cjs.map +1 -1
  194. package/build/queries/sql/expressions/coalesce.js.map +1 -1
  195. package/build/queries/sql/expressions/codecs.cjs.map +1 -1
  196. package/build/queries/sql/expressions/codecs.js.map +1 -1
  197. package/build/queries/sql/expressions/expression.cjs.map +1 -1
  198. package/build/queries/sql/expressions/expression.d.cts.map +1 -1
  199. package/build/queries/sql/expressions/expression.d.mts.map +1 -1
  200. package/build/queries/sql/expressions/expression.js.map +1 -1
  201. package/build/queries/sql/expressions/greatest.cjs.map +1 -1
  202. package/build/queries/sql/expressions/greatest.js.map +1 -1
  203. package/build/queries/sql/expressions/least.cjs.map +1 -1
  204. package/build/queries/sql/expressions/least.js.map +1 -1
  205. package/build/queries/sql/expressions/nullIf.cjs.map +1 -1
  206. package/build/queries/sql/expressions/nullIf.js.map +1 -1
  207. package/build/queries/sql/expressions/parseExpression.cjs.map +1 -1
  208. package/build/queries/sql/expressions/parseExpression.js.map +1 -1
  209. package/build/queries/sql/expressions/types.d.cts.map +1 -1
  210. package/build/queries/sql/expressions/types.d.mts.map +1 -1
  211. package/build/queries/sql/query.cjs +1 -1
  212. package/build/queries/sql/query.cjs.map +1 -1
  213. package/build/queries/sql/query.js +1 -1
  214. package/build/queries/sql/query.js.map +1 -1
  215. package/build/queries/unnest.cjs.map +1 -1
  216. package/build/queries/unnest.js.map +1 -1
  217. package/build/queries/valueFilters.cjs.map +1 -1
  218. package/build/queries/valueFilters.d.cts.map +1 -1
  219. package/build/queries/valueFilters.d.mts.map +1 -1
  220. package/build/queries/valueFilters.js.map +1 -1
  221. package/build/reactivity/ReactionsManager.cjs.map +1 -1
  222. package/build/reactivity/ReactionsManager.js.map +1 -1
  223. package/build/reactivity/reactiveHints.cjs.map +1 -1
  224. package/build/reactivity/reactiveHints.js.map +1 -1
  225. package/build/relations/AbstractRelationImpl.cjs.map +1 -1
  226. package/build/relations/AbstractRelationImpl.js.map +1 -1
  227. package/build/relations/AsyncProperty.cjs.map +1 -1
  228. package/build/relations/AsyncProperty.js.map +1 -1
  229. package/build/relations/AsyncReactiveField.cjs.map +1 -1
  230. package/build/relations/AsyncReactiveField.js.map +1 -1
  231. package/build/relations/Collection.cjs.map +1 -1
  232. package/build/relations/Collection.js.map +1 -1
  233. package/build/relations/CustomCollection.cjs.map +1 -1
  234. package/build/relations/CustomCollection.js.map +1 -1
  235. package/build/relations/CustomReference.cjs.map +1 -1
  236. package/build/relations/CustomReference.js.map +1 -1
  237. package/build/relations/EnumCollection.cjs.map +1 -1
  238. package/build/relations/EnumCollection.js.map +1 -1
  239. package/build/relations/LazyField.cjs.map +1 -1
  240. package/build/relations/LazyField.js.map +1 -1
  241. package/build/relations/ManyToManyCollection.cjs.map +1 -1
  242. package/build/relations/ManyToManyCollection.js.map +1 -1
  243. package/build/relations/ManyToManyLargeCollection.cjs.map +1 -1
  244. package/build/relations/ManyToManyLargeCollection.js.map +1 -1
  245. package/build/relations/ManyToOneReference.cjs.map +1 -1
  246. package/build/relations/ManyToOneReference.js.map +1 -1
  247. package/build/relations/OneToManyCollection.cjs.map +1 -1
  248. package/build/relations/OneToManyCollection.js.map +1 -1
  249. package/build/relations/OneToManyLargeCollection.cjs.map +1 -1
  250. package/build/relations/OneToManyLargeCollection.js.map +1 -1
  251. package/build/relations/OneToOneReference.cjs.map +1 -1
  252. package/build/relations/OneToOneReference.js.map +1 -1
  253. package/build/relations/PolymorphicReference.cjs.map +1 -1
  254. package/build/relations/PolymorphicReference.js.map +1 -1
  255. package/build/relations/ReactiveField.cjs.map +1 -1
  256. package/build/relations/ReactiveField.js.map +1 -1
  257. package/build/relations/ReactiveGetter.cjs.map +1 -1
  258. package/build/relations/ReactiveGetter.js.map +1 -1
  259. package/build/relations/ReactiveManyToMany.cjs.map +1 -1
  260. package/build/relations/ReactiveManyToMany.js.map +1 -1
  261. package/build/relations/ReactiveManyToManyOtherSide.cjs.map +1 -1
  262. package/build/relations/ReactiveManyToManyOtherSide.js.map +1 -1
  263. package/build/relations/ReactiveReference.cjs.map +1 -1
  264. package/build/relations/ReactiveReference.js.map +1 -1
  265. package/build/relations/ReadOnlyCollection.cjs.map +1 -1
  266. package/build/relations/ReadOnlyCollection.js.map +1 -1
  267. package/build/relations/RecursiveCollection.cjs.map +1 -1
  268. package/build/relations/RecursiveCollection.js.map +1 -1
  269. package/build/relations/RecursiveCycleError.cjs.map +1 -1
  270. package/build/relations/RecursiveCycleError.js.map +1 -1
  271. package/build/relations/Reference.cjs.map +1 -1
  272. package/build/relations/Reference.js.map +1 -1
  273. package/build/relations/Relation.cjs.map +1 -1
  274. package/build/relations/Relation.js.map +1 -1
  275. package/build/relations/hasAsyncMethod.cjs.map +1 -1
  276. package/build/relations/hasAsyncMethod.js.map +1 -1
  277. package/build/relations/hasManyDerived.cjs.map +1 -1
  278. package/build/relations/hasManyDerived.js.map +1 -1
  279. package/build/relations/hasManyThrough.cjs.map +1 -1
  280. package/build/relations/hasManyThrough.js.map +1 -1
  281. package/build/relations/hasOneDerived.cjs.map +1 -1
  282. package/build/relations/hasOneDerived.js.map +1 -1
  283. package/build/relations/hasOneThrough.cjs.map +1 -1
  284. package/build/relations/hasOneThrough.js.map +1 -1
  285. package/build/relations/hasProperty.cjs.map +1 -1
  286. package/build/relations/hasProperty.js.map +1 -1
  287. package/build/relations/isCascadeDelete.cjs.map +1 -1
  288. package/build/relations/isCascadeDelete.js.map +1 -1
  289. package/build/resurrection.cjs.map +1 -1
  290. package/build/resurrection.js.map +1 -1
  291. package/build/rules.cjs.map +1 -1
  292. package/build/rules.js.map +1 -1
  293. package/build/runtimeConfig.cjs.map +1 -1
  294. package/build/runtimeConfig.js.map +1 -1
  295. package/build/serde/TypeInfo.cjs.map +1 -1
  296. package/build/serde/TypeInfo.js.map +1 -1
  297. package/build/serde/columns.cjs.map +1 -1
  298. package/build/serde/columns.js.map +1 -1
  299. package/build/serde/fieldSerde.cjs.map +1 -1
  300. package/build/serde/fieldSerde.js.map +1 -1
  301. package/build/serde/serde.cjs.map +1 -1
  302. package/build/serde/serde.d.cts.map +1 -1
  303. package/build/serde/serde.d.mts.map +1 -1
  304. package/build/serde/serde.js.map +1 -1
  305. package/build/serde/temporalMappers.cjs.map +1 -1
  306. package/build/serde/temporalMappers.js.map +1 -1
  307. package/build/upsert.cjs.map +1 -1
  308. package/build/upsert.d.cts.map +1 -1
  309. package/build/upsert.d.mts.map +1 -1
  310. package/build/upsert.js.map +1 -1
  311. package/build/utils.cjs.map +1 -1
  312. package/build/utils.js.map +1 -1
  313. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"recursiveParentsBatchLoader.js","names":[],"sources":["../../src/batchloaders/recursiveParentsBatchLoader.ts"],"sourcesContent":["import { getMetadataForType } from \"../configure.ts\";\nimport { type Entity } from \"../Entity.ts\";\nimport { type EntityManager } from \"../EntityManager.ts\";\nimport {\n type ManyToOneField,\n type ParsedFindQuery,\n addTablePerClassJoinsAndClassTag,\n deTagIds,\n getField,\n kq,\n} from \"../index.ts\";\nimport { type RecursiveParentsCollectionImpl } from \"../relations/RecursiveCollection.ts\";\nimport { abbreviation } from \"../utils.ts\";\nimport { type BatchLoader } from \"./BatchLoader.ts\";\n\nexport const recursiveParentsOperation = \"m2o-recursive\";\n\nexport function recursiveParentsBatchLoader<T extends Entity, U extends Entity>(\n em: EntityManager,\n collection: RecursiveParentsCollectionImpl<T, U>,\n): BatchLoader<Entity> {\n let { meta, fieldName } = collection;\n // This could be called from subtypes to get relations defined on the parent. So we need to make sure we are using the\n // correct meta by walking the inheritance tree until we find the meta that actually has the root m2o field\n // We intentionally don't use getMetadataForField here because CTI-specialized fields stay on the subtype metadata,\n // while these raw recursive CTEs need the metadata/table that physically owns the FK column.\n while (!(collection.m2oFieldName in meta.fields) && meta.baseType) meta = getMetadataForType(meta.baseType);\n const batchKey = `${meta.tableName}-${fieldName}`;\n return em.getBatchLoader(recursiveParentsOperation, batchKey, async (children) => {\n const m2o = meta.allFields[collection.m2oFieldName] as ManyToOneField;\n // ...can `getField` return a new entity? In theory, we shouldn't see new entities because\n // RecursiveParentsCollectionImpl.load/isLoaded will see the `ManyToOneReferenceImpl` as loaded.\n const immediateParentIds = children.map((c) => getField(c, m2o.fieldName)).filter((id: any) => id !== undefined);\n const { columnName } = m2o.serde.columns[0];\n\n const alias = abbreviation(meta.tableName);\n const query: ParsedFindQuery = {\n selects: [`\"${alias}\".*`],\n tables: [\n // We still select directly from our table, and join into the CTE so that we\n // can use `addTablePerClassJoinsAndClassTag` to get inheritance added for free.\n { alias, join: \"primary\", table: meta.tableName },\n { alias: `${alias}_cte`, join: \"inner\", table: `${alias}_cte`, col1: `${alias}.id`, col2: `${alias}_cte.id` },\n ],\n orderBys: [{ alias, column: \"id\", order: \"ASC\" }],\n ctes: [\n {\n alias: `${alias}_cte`,\n query: {\n kind: \"raw\",\n // b is our base case, which is the immediate parents of the children we're loading,\n // and r is the recursive case of finding their parents.\n sql: `\n SELECT b.id, b.${columnName} FROM ${kq(meta.tableName)} b WHERE b.id = ANY(?)\n UNION\n SELECT r.id, r.${columnName} FROM ${kq(meta.tableName)} r JOIN ${alias}_cte ON r.id = ${alias}_cte.${columnName}\n `,\n bindings: [deTagIds(meta, immediateParentIds)],\n },\n recursive: true,\n },\n ],\n };\n\n addTablePerClassJoinsAndClassTag(query, meta, alias, true);\n\n const rowData = await em[\"executeFindRowData\"](meta, recursiveParentsOperation, query, {});\n\n // Since we're preloading m2os up the tree, merely having the entities in the EM is enough\n // for the ManyToOneReferenceImpl to find them, so we don't need to map them back to the\n // keys, or push them into the preloader cache.\n em[\"hydrateAndFinalize\"](meta.cstr, rowData);\n });\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,4BAA4B;AAEzC,SAAgB,4BACd,IACA,YACqB;CACrB,IAAI,EAAE,MAAM,cAAc;CAK1B,OAAO,EAAE,WAAW,gBAAgB,KAAK,WAAW,KAAK,UAAU,OAAO,mBAAmB,KAAK,QAAQ;CAC1G,MAAM,WAAW,GAAG,KAAK,UAAU,GAAG;CACtC,OAAO,GAAG,eAAe,2BAA2B,UAAU,OAAO,aAAa;EAChF,MAAM,MAAM,KAAK,UAAU,WAAW;EAGtC,MAAM,qBAAqB,SAAS,KAAK,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC,QAAQ,OAAY,OAAO,KAAA,CAAS;EAC/G,MAAM,EAAE,eAAe,IAAI,MAAM,QAAQ;EAEzC,MAAM,QAAQ,aAAa,KAAK,SAAS;EACzC,MAAM,QAAyB;GAC7B,SAAS,CAAC,IAAI,MAAM,IAAI;GACxB,QAAQ,CAGN;IAAE;IAAO,MAAM;IAAW,OAAO,KAAK;GAAU,GAChD;IAAE,OAAO,GAAG,MAAM;IAAO,MAAM;IAAS,OAAO,GAAG,MAAM;IAAO,MAAM,GAAG,MAAM;IAAM,MAAM,GAAG,MAAM;GAAS,CAC9G;GACA,UAAU,CAAC;IAAE;IAAO,QAAQ;IAAM,OAAO;GAAM,CAAC;GAChD,MAAM,CACJ;IACE,OAAO,GAAG,MAAM;IAChB,OAAO;KACL,MAAM;KAGN,KAAK;+BACc,WAAW,QAAQ,GAAG,KAAK,SAAS,EAAE;;+BAEtC,WAAW,QAAQ,GAAG,KAAK,SAAS,EAAE,UAAU,MAAM,iBAAiB,MAAM,OAAO,WAAW;;KAElH,UAAU,CAAC,SAAS,MAAM,kBAAkB,CAAC;IAC/C;IACA,WAAW;GACb,CACF;EACF;EAEA,iCAAiC,OAAO,MAAM,OAAO,IAAI;EAEzD,MAAM,UAAU,MAAM,GAAG,qBAAqB,CAAC,MAAM,2BAA2B,OAAO,CAAC,CAAC;EAKzF,GAAG,qBAAqB,CAAC,KAAK,MAAM,OAAO;CAC7C,CAAC;AACH"}
1
+ {"version":3,"file":"recursiveParentsBatchLoader.js","names":[],"sources":["../../src/batchloaders/recursiveParentsBatchLoader.ts"],"sourcesContent":["import { type BatchLoader } from \"src/batchloaders/BatchLoader.ts\";\nimport { getMetadataForType } from \"src/configure.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager } from \"src/EntityManager.ts\";\nimport {\n type ManyToOneField,\n type ParsedFindQuery,\n addTablePerClassJoinsAndClassTag,\n deTagIds,\n getField,\n kq,\n} from \"src/index.ts\";\nimport { type RecursiveParentsCollectionImpl } from \"src/relations/RecursiveCollection.ts\";\nimport { abbreviation } from \"src/utils.ts\";\n\nexport const recursiveParentsOperation = \"m2o-recursive\";\n\nexport function recursiveParentsBatchLoader<T extends Entity, U extends Entity>(\n em: EntityManager,\n collection: RecursiveParentsCollectionImpl<T, U>,\n): BatchLoader<Entity> {\n let { meta, fieldName } = collection;\n // This could be called from subtypes to get relations defined on the parent. So we need to make sure we are using the\n // correct meta by walking the inheritance tree until we find the meta that actually has the root m2o field\n // We intentionally don't use getMetadataForField here because CTI-specialized fields stay on the subtype metadata,\n // while these raw recursive CTEs need the metadata/table that physically owns the FK column.\n while (!(collection.m2oFieldName in meta.fields) && meta.baseType) meta = getMetadataForType(meta.baseType);\n const batchKey = `${meta.tableName}-${fieldName}`;\n return em.getBatchLoader(recursiveParentsOperation, batchKey, async (children) => {\n const m2o = meta.allFields[collection.m2oFieldName] as ManyToOneField;\n // ...can `getField` return a new entity? In theory, we shouldn't see new entities because\n // RecursiveParentsCollectionImpl.load/isLoaded will see the `ManyToOneReferenceImpl` as loaded.\n const immediateParentIds = children.map((c) => getField(c, m2o.fieldName)).filter((id: any) => id !== undefined);\n const { columnName } = m2o.serde.columns[0];\n\n const alias = abbreviation(meta.tableName);\n const query: ParsedFindQuery = {\n selects: [`\"${alias}\".*`],\n tables: [\n // We still select directly from our table, and join into the CTE so that we\n // can use `addTablePerClassJoinsAndClassTag` to get inheritance added for free.\n { alias, join: \"primary\", table: meta.tableName },\n { alias: `${alias}_cte`, join: \"inner\", table: `${alias}_cte`, col1: `${alias}.id`, col2: `${alias}_cte.id` },\n ],\n orderBys: [{ alias, column: \"id\", order: \"ASC\" }],\n ctes: [\n {\n alias: `${alias}_cte`,\n query: {\n kind: \"raw\",\n // b is our base case, which is the immediate parents of the children we're loading,\n // and r is the recursive case of finding their parents.\n sql: `\n SELECT b.id, b.${columnName} FROM ${kq(meta.tableName)} b WHERE b.id = ANY(?)\n UNION\n SELECT r.id, r.${columnName} FROM ${kq(meta.tableName)} r JOIN ${alias}_cte ON r.id = ${alias}_cte.${columnName}\n `,\n bindings: [deTagIds(meta, immediateParentIds)],\n },\n recursive: true,\n },\n ],\n };\n\n addTablePerClassJoinsAndClassTag(query, meta, alias, true);\n\n const rowData = await em[\"executeFindRowData\"](meta, recursiveParentsOperation, query, {});\n\n // Since we're preloading m2os up the tree, merely having the entities in the EM is enough\n // for the ManyToOneReferenceImpl to find them, so we don't need to map them back to the\n // keys, or push them into the preloader cache.\n em[\"hydrateAndFinalize\"](meta.cstr, rowData);\n });\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,4BAA4B;AAEzC,SAAgB,4BACd,IACA,YACqB;CACrB,IAAI,EAAE,MAAM,cAAc;CAK1B,OAAO,EAAE,WAAW,gBAAgB,KAAK,WAAW,KAAK,UAAU,OAAO,mBAAmB,KAAK,QAAQ;CAC1G,MAAM,WAAW,GAAG,KAAK,UAAU,GAAG;CACtC,OAAO,GAAG,eAAe,2BAA2B,UAAU,OAAO,aAAa;EAChF,MAAM,MAAM,KAAK,UAAU,WAAW;EAGtC,MAAM,qBAAqB,SAAS,KAAK,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC,QAAQ,OAAY,OAAO,KAAA,CAAS;EAC/G,MAAM,EAAE,eAAe,IAAI,MAAM,QAAQ;EAEzC,MAAM,QAAQ,aAAa,KAAK,SAAS;EACzC,MAAM,QAAyB;GAC7B,SAAS,CAAC,IAAI,MAAM,IAAI;GACxB,QAAQ,CAGN;IAAE;IAAO,MAAM;IAAW,OAAO,KAAK;GAAU,GAChD;IAAE,OAAO,GAAG,MAAM;IAAO,MAAM;IAAS,OAAO,GAAG,MAAM;IAAO,MAAM,GAAG,MAAM;IAAM,MAAM,GAAG,MAAM;GAAS,CAC9G;GACA,UAAU,CAAC;IAAE;IAAO,QAAQ;IAAM,OAAO;GAAM,CAAC;GAChD,MAAM,CACJ;IACE,OAAO,GAAG,MAAM;IAChB,OAAO;KACL,MAAM;KAGN,KAAK;+BACc,WAAW,QAAQ,GAAG,KAAK,SAAS,EAAE;;+BAEtC,WAAW,QAAQ,GAAG,KAAK,SAAS,EAAE,UAAU,MAAM,iBAAiB,MAAM,OAAO,WAAW;;KAElH,UAAU,CAAC,SAAS,MAAM,kBAAkB,CAAC;IAC/C;IACA,WAAW;GACb,CACF;EACF;EAEA,iCAAiC,OAAO,MAAM,OAAO,IAAI;EAEzD,MAAM,UAAU,MAAM,GAAG,qBAAqB,CAAC,MAAM,2BAA2B,OAAO,CAAC,CAAC;EAKzF,GAAG,qBAAqB,CAAC,KAAK,MAAM,OAAO;CAC7C,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"changes.cjs","names":["getInstanceData","getField","notLoadedValue","isEntity","isId","#entity","#m2m","#joinRows","getEmInternalApi","#added","#removed","#collection","#toCodes","#o2m","getMetadata","isChangeableField","assertNever"],"sources":["../src/changes.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type Entity } from \"./Entity.ts\";\nimport { type IdOf } from \"./EntityManager.ts\";\nimport { getField, isChangeableField } from \"./fields.ts\";\nimport type { JoinColumnValue, JoinRows } from \"./flush/JoinRows.ts\";\nimport {\n type Field,\n type ManyToManyCollection,\n type OneToManyCollection,\n type RelationsOf,\n assertNever,\n getEmInternalApi,\n getMetadata,\n isEntity,\n isId,\n} from \"./index.ts\";\nimport { type EnumCollectionImpl } from \"./relations/EnumCollection.ts\";\nimport { notLoadedValue } from \"./relations/LazyField.ts\";\nimport { type FieldsOf, type OptsOf } from \"./typeMap.ts\";\n\n/** Exposes a field's changed/original value in each entity's `this.changes` property. */\nexport interface FieldStatus<T> {\n /** True if the field has been changed on either create or update. */\n hasChanged: boolean;\n /** True only if the field has been updated i.e. not on the initial create. */\n hasUpdated: boolean;\n /** The original value, will be `undefined` if the entity new. */\n originalValue?: T;\n}\n\n/**\n * Provides a base class for field/primitive & m2o status impls.\n *\n * We use a separate base class so that the primitive & m2o subtypes can have their own\n * `kind: ...` consts, that if the m2o just subtyped the primitive directly, wouldn't be\n * allowed.\n */\nabstract class BaseFieldStatusImpl<T> {\n entity: Entity;\n fieldName: string;\n\n constructor(entity: Entity, fieldName: string) {\n this.entity = entity;\n this.fieldName = fieldName;\n }\n\n get originalValue(): T | undefined {\n const { originalData, data } = getInstanceData(this.entity);\n // If `p` is in originalData, always respect that, even if it's undefined\n const value =\n this.fieldName in originalData ? originalData[this.fieldName] : getField(this.entity, this.fieldName as any);\n // An unloaded lazy column's original value is unknown\n return value === notLoadedValue ? undefined : value;\n }\n\n get hasChanged(): boolean {\n const { originalData, data } = getInstanceData(this.entity);\n // Use `__orm.data[p] !== undefined` instead of `p in entity.__orm.data` because if a new (or cloned) entity\n // sets-then-unsets a value, it will return to `undefined` but still be present in `__orm.data`.\n return this.entity.isNewEntity ? data[this.fieldName] !== undefined : this.fieldName in originalData;\n }\n\n get hasUpdated(): boolean {\n const { originalData } = getInstanceData(this.entity);\n return !this.entity.isNewEntity && this.fieldName in originalData;\n }\n}\n\n/** Field status for primitive, enum, & primary key columns. */\nexport interface PrimitiveFieldStatus<T> extends FieldStatus<T> {\n kind: \"field\";\n}\n\n/** Implements the primitive field status, by extend base & just adding our `kind` const. */\nclass PrimitiveFieldStatusImpl<T> extends BaseFieldStatusImpl<T> implements PrimitiveFieldStatus<T> {\n kind = \"field\" as const;\n}\n\n/** Implements the m2o field status, by extend base, & adding `originalEntity`. */\nclass ManyToOneFieldStatusImpl<T extends Entity>\n extends BaseFieldStatusImpl<IdOf<T>>\n implements ManyToOneFieldStatus<T>\n{\n kind = \"m2o\" as const;\n\n /**\n * Returns our original entity's `id`.\n *\n * We can know `originalValue` has an `id` available (i.e. is not new), b/c original values defacto\n * came from the database.\n *\n * We also return the id so that `entity.changes.m2oField.originalValue` has a consistent API,\n * regardless of whether the `entity.m2oField` itself is loaded or unloaded.\n */\n get originalValue() {\n const originalValue = super.originalValue;\n return isEntity(originalValue) ? (originalValue.id as IdOf<T>) : originalValue;\n }\n\n /** Returns the original entity instance, which may not be loaded into memory yet, or undefined. */\n get originalEntity(): Promise<T | undefined> {\n const originalValue = this.originalValue;\n if (isId(originalValue)) {\n return this.entity.em.load(originalValue) as Promise<T>;\n } else {\n return Promise.resolve(undefined);\n }\n }\n}\n\n/** Provides a m2o/reference field's changed/original value in the entity's `this.changes` property. */\nexport interface ManyToOneFieldStatus<T extends Entity> extends FieldStatus<IdOf<T>> {\n kind: \"m2o\";\n /** The original entity, will be `undefined` if the entity new or the m2o was `null`. */\n originalEntity: Promise<T | undefined>;\n}\n\n/** Provides a m2m relation's added/removed/changed/original values. */\nexport interface ManyToManyFieldStatus<U extends Entity> {\n kind: \"m2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\nclass ManyToManyFieldStatusImpl<T extends Entity, U extends Entity> implements ManyToManyFieldStatus<U> {\n readonly kind = \"m2m\";\n readonly #entity: T;\n readonly #m2m: ManyToManyCollection<T, any>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#m2m = entity[fieldName] as ManyToManyCollection<T, any>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#m2m);\n }\n\n // Similar to the o2m.added/removed methods, these don't have to be async because currently\n // any m2m mutation requires having both entities in-memory anyway, i.e. we can't do id-only/unloaded\n // mutation of m2m relations.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.#added, ...this.#removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#m2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n get #added(): U[] {\n return this.#joinRows.addedFor(this.#m2m, this.#entity) as U[];\n }\n\n get #removed(): U[] {\n return this.#joinRows.removedFor(this.#m2m, this.#entity) as U[];\n }\n}\n\n/** Provides an enum m2m's added/removed/changed/original codes (e.g. `Publisher.logoColors`). */\nexport interface EnumCollectionFieldStatus<E> {\n kind: \"m2mEnum\";\n added: E[];\n removed: E[];\n changed: E[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalValues: Promise<readonly E[]>;\n}\n\nclass EnumCollectionFieldStatusImpl<T extends Entity, E> implements EnumCollectionFieldStatus<E> {\n readonly kind = \"m2mEnum\";\n readonly #entity: T;\n readonly #collection: EnumCollectionImpl<T, E>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#collection = entity[fieldName] as EnumCollectionImpl<T, E>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#collection);\n }\n\n get added(): E[] {\n return this.#toCodes(this.#joinRows.addedFor(this.#collection, this.#entity));\n }\n\n get removed(): E[] {\n return this.#toCodes(this.#joinRows.removedFor(this.#collection, this.#entity));\n }\n\n get changed(): E[] {\n return [...this.added, ...this.removed];\n }\n\n get hasChanged(): boolean {\n return this.added.length > 0 || this.removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalValues(): Promise<readonly E[]> {\n // `load()` ensures the persisted (db) rows are in memory, then diff out our pending changes.\n return this.#collection.load().then((codes) => {\n const added = new Set(this.added);\n const enumDetail = this.#collection.otherEnum;\n return [...codes.filter((c) => !added.has(c)), ...this.removed].sort(\n (a, b) => enumDetail.getByCode(a).id - enumDetail.getByCode(b).id,\n );\n });\n }\n\n /** Maps the join rows' enum ids to codes, sorted by enum id for a stable order. */\n #toCodes(ids: JoinColumnValue[]): E[] {\n const enumDetail = this.#collection.otherEnum;\n return (ids as number[]).sort((a, b) => a - b).map((id) => enumDetail.findById(id)!.code as E);\n }\n}\n\nexport interface OneToManyFieldStatus<U extends Entity> {\n kind: \"o2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\n/** Provides access to an o2m relation's added/removed/changed entities. */\nclass OneToManyFieldStatusImpl<T extends Entity, U extends Entity> implements OneToManyFieldStatus<U> {\n readonly kind = \"o2m\";\n readonly #entity: T;\n readonly #o2m: OneToManyCollection<T, U>;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#o2m = entity[fieldName] as OneToManyCollection<T, U>;\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.added, ...this.removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#o2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get #added(): U[] {\n return [\n ...this.#o2m.added(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.adds as U[]) ?? [])),\n ];\n }\n\n get #removed(): U[] {\n return [\n ...this.#o2m.removed(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.removes as U[]) ?? [])),\n ];\n }\n}\n\n/**\n * Creates the `this.changes.firstName` changes API for a given entity `T`.\n *\n * We use `FieldsOf` because that already excludes collection, and then also\n * convert reference fields to `ManyToOneFieldStatus` to be the id type\n * because the reference may not be loaded.\n *\n * @typeParam K The fields of the entity, or potentially the union of the entity and its subtypes,\n * i.e. `Publisher.changes` is typed as `Changes<Publisher, keyof Publisher | keyof SmallPub | keyof LargePub>`\n * @typeParam R An optional list of restrictions, i.e for `Reacted` for to provide `changes` to its subset of fields.\n */\nexport type Changes<T extends Entity, K = keyof (FieldsOf<T> & RelationsOf<T>), R = K> = {\n /** Array of changed field names. */\n fields: NonNullable<K>[];\n /** Array of changed field names w/o o2m & m2m relations (which can be expensive). */\n fieldsWithoutRelations: NonNullable<K>[];\n} & {\n // The o2o branch is `never`: the FK lives on the other entity, so changes never track o2o fields.\n // (The exclusion must stay in the value position: a conditional in the `[P in ...]` clause defers on\n // a generic `T` and collapses `keyof Changes<T>` at inference time, i.e. in `cannotBeUpdated` calls.)\n [P in keyof FieldsOf<T> & R]: FieldsOf<T>[P] extends { kind: \"m2m\"; type: infer U extends Entity }\n ? ManyToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"m2mEnum\"; type: infer E }\n ? EnumCollectionFieldStatus<E>\n : FieldsOf<T>[P] extends { kind: \"o2m\"; type: infer U extends Entity }\n ? OneToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"o2o\" }\n ? never\n : FieldsOf<T>[P] extends { type: (infer U) | undefined }\n ? U extends Entity\n ? ManyToOneFieldStatus<U>\n : PrimitiveFieldStatus<U>\n : never;\n};\n\n// type A1 = never extends string ? 1 : 2;\n// type A2 = Book extends Entity | undefined ? 1 : 2;\n// type A2a = string extends Entity | string | undefined ? 1 : 2;\n// type A3 = Book | undefined extends Entity ? 1 : 2;\n// type A4 = Book | undefined | null extends (infer E extends Entity) | undefined | null ? E : 2;\n\n/**\n * A strongly-typed Entity with its changes field.\n *\n * Ideally this would live on `Entity` directly, but results in circular type issues.\n */\nexport interface EntityChanges<T extends Entity> {\n changes: Changes<T>;\n}\n\nexport function newChangesProxy<T extends Entity>(entity: T): Changes<T> {\n return new Proxy(entity, {\n get(\n target,\n p: PropertyKey & string,\n ):\n | FieldStatus<any>\n | ManyToOneFieldStatus<any>\n | ManyToManyFieldStatus<any>\n | OneToManyFieldStatus<any>\n | (keyof OptsOf<T>)[] {\n if (p === \"fields\") {\n return getChangedFieldNames(entity, true);\n } else if (p === \"fieldsWithoutRelations\") {\n return getChangedFieldNames(entity, false);\n } else if (typeof p === \"symbol\") {\n throw new Error(`Unsupported call to ${String(p)}`);\n }\n const kind = getMetadata(entity).allFields[p]?.kind;\n if (kind === \"m2m\") {\n return new ManyToManyFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"m2mEnum\") {\n return new EnumCollectionFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"o2m\") {\n return new OneToManyFieldStatusImpl(entity, p as keyof T);\n } else if (!isChangeableField(entity, p as any)) {\n throw new Error(`Invalid changes field ${p}`);\n } else if (kind === \"m2o\" || kind === \"poly\") {\n return new ManyToOneFieldStatusImpl(entity, p);\n } else if (kind === \"enum\" || kind === \"primitive\" || kind === \"primaryKey\") {\n return new PrimitiveFieldStatusImpl(entity, p);\n } else if (kind === \"lo2m\" || kind === \"o2o\") {\n throw new Error(`changes are not supported for ${kind} ${p}`);\n } else {\n return assertNever(kind);\n }\n },\n }) as any;\n}\n\nconst addOriginalEntity: Record<Field[\"kind\"], boolean> = {\n m2o: true,\n poly: true,\n m2mEnum: false,\n enum: false,\n lo2m: false,\n m2m: false,\n o2m: false,\n o2o: false,\n primaryKey: false,\n primitive: false,\n};\n\n/** Scans `entity` for changes primitive + m2m/o2m fields. */\nfunction getChangedFieldNames<T extends Entity>(entity: T, includeRelations: boolean): (keyof OptsOf<T>)[] {\n const fieldsChanged = entity.isNewEntity\n ? // Cloning sometimes leaves unset keys in data as undefined, so drop them\n Object.entries(getInstanceData(entity).data)\n .filter(([, value]) => value !== undefined)\n .map(([key]) => key)\n : Object.keys(getInstanceData(entity).originalData);\n\n // Calling `hasChanged` on these can be expensive, and we call this in a loop during em.flush\n if (includeRelations) {\n for (const field of Object.values(getMetadata(entity).allFields)) {\n if (field.kind === \"m2m\") {\n const status = new ManyToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"o2m\") {\n const status = new OneToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"m2mEnum\") {\n const status = new EnumCollectionFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n }\n }\n }\n\n return fieldsChanged as any;\n}\n\nconst entityCompare: (a: Entity, b: Entity) => number = (a, b) => {\n return a.toString().localeCompare(b.toString());\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"changes.cjs","names":["getInstanceData","getField","notLoadedValue","isEntity","isId","#entity","#m2m","#joinRows","getEmInternalApi","#added","#removed","#collection","#toCodes","#o2m","getMetadata","isChangeableField","assertNever"],"sources":["../src/changes.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type IdOf } from \"src/EntityManager.ts\";\nimport { getField, isChangeableField } from \"src/fields.ts\";\nimport type { JoinColumnValue, JoinRows } from \"src/flush/JoinRows.ts\";\nimport {\n type Field,\n type ManyToManyCollection,\n type OneToManyCollection,\n type RelationsOf,\n assertNever,\n getEmInternalApi,\n getMetadata,\n isEntity,\n isId,\n} from \"src/index.ts\";\nimport { type EnumCollectionImpl } from \"src/relations/EnumCollection.ts\";\nimport { notLoadedValue } from \"src/relations/LazyField.ts\";\nimport { type FieldsOf, type OptsOf } from \"src/typeMap.ts\";\n\n/** Exposes a field's changed/original value in each entity's `this.changes` property. */\nexport interface FieldStatus<T> {\n /** True if the field has been changed on either create or update. */\n hasChanged: boolean;\n /** True only if the field has been updated i.e. not on the initial create. */\n hasUpdated: boolean;\n /** The original value, will be `undefined` if the entity new. */\n originalValue?: T;\n}\n\n/**\n * Provides a base class for field/primitive & m2o status impls.\n *\n * We use a separate base class so that the primitive & m2o subtypes can have their own\n * `kind: ...` consts, that if the m2o just subtyped the primitive directly, wouldn't be\n * allowed.\n */\nabstract class BaseFieldStatusImpl<T> {\n entity: Entity;\n fieldName: string;\n\n constructor(entity: Entity, fieldName: string) {\n this.entity = entity;\n this.fieldName = fieldName;\n }\n\n get originalValue(): T | undefined {\n const { originalData, data } = getInstanceData(this.entity);\n // If `p` is in originalData, always respect that, even if it's undefined\n const value =\n this.fieldName in originalData ? originalData[this.fieldName] : getField(this.entity, this.fieldName as any);\n // An unloaded lazy column's original value is unknown\n return value === notLoadedValue ? undefined : value;\n }\n\n get hasChanged(): boolean {\n const { originalData, data } = getInstanceData(this.entity);\n // Use `__orm.data[p] !== undefined` instead of `p in entity.__orm.data` because if a new (or cloned) entity\n // sets-then-unsets a value, it will return to `undefined` but still be present in `__orm.data`.\n return this.entity.isNewEntity ? data[this.fieldName] !== undefined : this.fieldName in originalData;\n }\n\n get hasUpdated(): boolean {\n const { originalData } = getInstanceData(this.entity);\n return !this.entity.isNewEntity && this.fieldName in originalData;\n }\n}\n\n/** Field status for primitive, enum, & primary key columns. */\nexport interface PrimitiveFieldStatus<T> extends FieldStatus<T> {\n kind: \"field\";\n}\n\n/** Implements the primitive field status, by extend base & just adding our `kind` const. */\nclass PrimitiveFieldStatusImpl<T> extends BaseFieldStatusImpl<T> implements PrimitiveFieldStatus<T> {\n kind = \"field\" as const;\n}\n\n/** Implements the m2o field status, by extend base, & adding `originalEntity`. */\nclass ManyToOneFieldStatusImpl<T extends Entity>\n extends BaseFieldStatusImpl<IdOf<T>>\n implements ManyToOneFieldStatus<T>\n{\n kind = \"m2o\" as const;\n\n /**\n * Returns our original entity's `id`.\n *\n * We can know `originalValue` has an `id` available (i.e. is not new), b/c original values defacto\n * came from the database.\n *\n * We also return the id so that `entity.changes.m2oField.originalValue` has a consistent API,\n * regardless of whether the `entity.m2oField` itself is loaded or unloaded.\n */\n get originalValue() {\n const originalValue = super.originalValue;\n return isEntity(originalValue) ? (originalValue.id as IdOf<T>) : originalValue;\n }\n\n /** Returns the original entity instance, which may not be loaded into memory yet, or undefined. */\n get originalEntity(): Promise<T | undefined> {\n const originalValue = this.originalValue;\n if (isId(originalValue)) {\n return this.entity.em.load(originalValue) as Promise<T>;\n } else {\n return Promise.resolve(undefined);\n }\n }\n}\n\n/** Provides a m2o/reference field's changed/original value in the entity's `this.changes` property. */\nexport interface ManyToOneFieldStatus<T extends Entity> extends FieldStatus<IdOf<T>> {\n kind: \"m2o\";\n /** The original entity, will be `undefined` if the entity new or the m2o was `null`. */\n originalEntity: Promise<T | undefined>;\n}\n\n/** Provides a m2m relation's added/removed/changed/original values. */\nexport interface ManyToManyFieldStatus<U extends Entity> {\n kind: \"m2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\nclass ManyToManyFieldStatusImpl<T extends Entity, U extends Entity> implements ManyToManyFieldStatus<U> {\n readonly kind = \"m2m\";\n readonly #entity: T;\n readonly #m2m: ManyToManyCollection<T, any>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#m2m = entity[fieldName] as ManyToManyCollection<T, any>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#m2m);\n }\n\n // Similar to the o2m.added/removed methods, these don't have to be async because currently\n // any m2m mutation requires having both entities in-memory anyway, i.e. we can't do id-only/unloaded\n // mutation of m2m relations.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.#added, ...this.#removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#m2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n get #added(): U[] {\n return this.#joinRows.addedFor(this.#m2m, this.#entity) as U[];\n }\n\n get #removed(): U[] {\n return this.#joinRows.removedFor(this.#m2m, this.#entity) as U[];\n }\n}\n\n/** Provides an enum m2m's added/removed/changed/original codes (e.g. `Publisher.logoColors`). */\nexport interface EnumCollectionFieldStatus<E> {\n kind: \"m2mEnum\";\n added: E[];\n removed: E[];\n changed: E[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalValues: Promise<readonly E[]>;\n}\n\nclass EnumCollectionFieldStatusImpl<T extends Entity, E> implements EnumCollectionFieldStatus<E> {\n readonly kind = \"m2mEnum\";\n readonly #entity: T;\n readonly #collection: EnumCollectionImpl<T, E>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#collection = entity[fieldName] as EnumCollectionImpl<T, E>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#collection);\n }\n\n get added(): E[] {\n return this.#toCodes(this.#joinRows.addedFor(this.#collection, this.#entity));\n }\n\n get removed(): E[] {\n return this.#toCodes(this.#joinRows.removedFor(this.#collection, this.#entity));\n }\n\n get changed(): E[] {\n return [...this.added, ...this.removed];\n }\n\n get hasChanged(): boolean {\n return this.added.length > 0 || this.removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalValues(): Promise<readonly E[]> {\n // `load()` ensures the persisted (db) rows are in memory, then diff out our pending changes.\n return this.#collection.load().then((codes) => {\n const added = new Set(this.added);\n const enumDetail = this.#collection.otherEnum;\n return [...codes.filter((c) => !added.has(c)), ...this.removed].sort(\n (a, b) => enumDetail.getByCode(a).id - enumDetail.getByCode(b).id,\n );\n });\n }\n\n /** Maps the join rows' enum ids to codes, sorted by enum id for a stable order. */\n #toCodes(ids: JoinColumnValue[]): E[] {\n const enumDetail = this.#collection.otherEnum;\n return (ids as number[]).sort((a, b) => a - b).map((id) => enumDetail.findById(id)!.code as E);\n }\n}\n\nexport interface OneToManyFieldStatus<U extends Entity> {\n kind: \"o2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\n/** Provides access to an o2m relation's added/removed/changed entities. */\nclass OneToManyFieldStatusImpl<T extends Entity, U extends Entity> implements OneToManyFieldStatus<U> {\n readonly kind = \"o2m\";\n readonly #entity: T;\n readonly #o2m: OneToManyCollection<T, U>;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#o2m = entity[fieldName] as OneToManyCollection<T, U>;\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.added, ...this.removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#o2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get #added(): U[] {\n return [\n ...this.#o2m.added(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.adds as U[]) ?? [])),\n ];\n }\n\n get #removed(): U[] {\n return [\n ...this.#o2m.removed(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.removes as U[]) ?? [])),\n ];\n }\n}\n\n/**\n * Creates the `this.changes.firstName` changes API for a given entity `T`.\n *\n * We use `FieldsOf` because that already excludes collection, and then also\n * convert reference fields to `ManyToOneFieldStatus` to be the id type\n * because the reference may not be loaded.\n *\n * @typeParam K The fields of the entity, or potentially the union of the entity and its subtypes,\n * i.e. `Publisher.changes` is typed as `Changes<Publisher, keyof Publisher | keyof SmallPub | keyof LargePub>`\n * @typeParam R An optional list of restrictions, i.e for `Reacted` for to provide `changes` to its subset of fields.\n */\nexport type Changes<T extends Entity, K = keyof (FieldsOf<T> & RelationsOf<T>), R = K> = {\n /** Array of changed field names. */\n fields: NonNullable<K>[];\n /** Array of changed field names w/o o2m & m2m relations (which can be expensive). */\n fieldsWithoutRelations: NonNullable<K>[];\n} & {\n // The o2o branch is `never`: the FK lives on the other entity, so changes never track o2o fields.\n // (The exclusion must stay in the value position: a conditional in the `[P in ...]` clause defers on\n // a generic `T` and collapses `keyof Changes<T>` at inference time, i.e. in `cannotBeUpdated` calls.)\n [P in keyof FieldsOf<T> & R]: FieldsOf<T>[P] extends { kind: \"m2m\"; type: infer U extends Entity }\n ? ManyToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"m2mEnum\"; type: infer E }\n ? EnumCollectionFieldStatus<E>\n : FieldsOf<T>[P] extends { kind: \"o2m\"; type: infer U extends Entity }\n ? OneToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"o2o\" }\n ? never\n : FieldsOf<T>[P] extends { type: (infer U) | undefined }\n ? U extends Entity\n ? ManyToOneFieldStatus<U>\n : PrimitiveFieldStatus<U>\n : never;\n};\n\n// type A1 = never extends string ? 1 : 2;\n// type A2 = Book extends Entity | undefined ? 1 : 2;\n// type A2a = string extends Entity | string | undefined ? 1 : 2;\n// type A3 = Book | undefined extends Entity ? 1 : 2;\n// type A4 = Book | undefined | null extends (infer E extends Entity) | undefined | null ? E : 2;\n\n/**\n * A strongly-typed Entity with its changes field.\n *\n * Ideally this would live on `Entity` directly, but results in circular type issues.\n */\nexport interface EntityChanges<T extends Entity> {\n changes: Changes<T>;\n}\n\nexport function newChangesProxy<T extends Entity>(entity: T): Changes<T> {\n return new Proxy(entity, {\n get(\n target,\n p: PropertyKey & string,\n ):\n | FieldStatus<any>\n | ManyToOneFieldStatus<any>\n | ManyToManyFieldStatus<any>\n | OneToManyFieldStatus<any>\n | (keyof OptsOf<T>)[] {\n if (p === \"fields\") {\n return getChangedFieldNames(entity, true);\n } else if (p === \"fieldsWithoutRelations\") {\n return getChangedFieldNames(entity, false);\n } else if (typeof p === \"symbol\") {\n throw new Error(`Unsupported call to ${String(p)}`);\n }\n const kind = getMetadata(entity).allFields[p]?.kind;\n if (kind === \"m2m\") {\n return new ManyToManyFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"m2mEnum\") {\n return new EnumCollectionFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"o2m\") {\n return new OneToManyFieldStatusImpl(entity, p as keyof T);\n } else if (!isChangeableField(entity, p as any)) {\n throw new Error(`Invalid changes field ${p}`);\n } else if (kind === \"m2o\" || kind === \"poly\") {\n return new ManyToOneFieldStatusImpl(entity, p);\n } else if (kind === \"enum\" || kind === \"primitive\" || kind === \"primaryKey\") {\n return new PrimitiveFieldStatusImpl(entity, p);\n } else if (kind === \"lo2m\" || kind === \"o2o\") {\n throw new Error(`changes are not supported for ${kind} ${p}`);\n } else {\n return assertNever(kind);\n }\n },\n }) as any;\n}\n\nconst addOriginalEntity: Record<Field[\"kind\"], boolean> = {\n m2o: true,\n poly: true,\n m2mEnum: false,\n enum: false,\n lo2m: false,\n m2m: false,\n o2m: false,\n o2o: false,\n primaryKey: false,\n primitive: false,\n};\n\n/** Scans `entity` for changes primitive + m2m/o2m fields. */\nfunction getChangedFieldNames<T extends Entity>(entity: T, includeRelations: boolean): (keyof OptsOf<T>)[] {\n const fieldsChanged = entity.isNewEntity\n ? // Cloning sometimes leaves unset keys in data as undefined, so drop them\n Object.entries(getInstanceData(entity).data)\n .filter(([, value]) => value !== undefined)\n .map(([key]) => key)\n : Object.keys(getInstanceData(entity).originalData);\n\n // Calling `hasChanged` on these can be expensive, and we call this in a loop during em.flush\n if (includeRelations) {\n for (const field of Object.values(getMetadata(entity).allFields)) {\n if (field.kind === \"m2m\") {\n const status = new ManyToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"o2m\") {\n const status = new OneToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"m2mEnum\") {\n const status = new EnumCollectionFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n }\n }\n }\n\n return fieldsChanged as any;\n}\n\nconst entityCompare: (a: Entity, b: Entity) => number = (a, b) => {\n return a.toString().localeCompare(b.toString());\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"changes.js","names":["#entity","#m2m","#joinRows","#added","#removed","#collection","#toCodes","#o2m"],"sources":["../src/changes.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type Entity } from \"./Entity.ts\";\nimport { type IdOf } from \"./EntityManager.ts\";\nimport { getField, isChangeableField } from \"./fields.ts\";\nimport type { JoinColumnValue, JoinRows } from \"./flush/JoinRows.ts\";\nimport {\n type Field,\n type ManyToManyCollection,\n type OneToManyCollection,\n type RelationsOf,\n assertNever,\n getEmInternalApi,\n getMetadata,\n isEntity,\n isId,\n} from \"./index.ts\";\nimport { type EnumCollectionImpl } from \"./relations/EnumCollection.ts\";\nimport { notLoadedValue } from \"./relations/LazyField.ts\";\nimport { type FieldsOf, type OptsOf } from \"./typeMap.ts\";\n\n/** Exposes a field's changed/original value in each entity's `this.changes` property. */\nexport interface FieldStatus<T> {\n /** True if the field has been changed on either create or update. */\n hasChanged: boolean;\n /** True only if the field has been updated i.e. not on the initial create. */\n hasUpdated: boolean;\n /** The original value, will be `undefined` if the entity new. */\n originalValue?: T;\n}\n\n/**\n * Provides a base class for field/primitive & m2o status impls.\n *\n * We use a separate base class so that the primitive & m2o subtypes can have their own\n * `kind: ...` consts, that if the m2o just subtyped the primitive directly, wouldn't be\n * allowed.\n */\nabstract class BaseFieldStatusImpl<T> {\n entity: Entity;\n fieldName: string;\n\n constructor(entity: Entity, fieldName: string) {\n this.entity = entity;\n this.fieldName = fieldName;\n }\n\n get originalValue(): T | undefined {\n const { originalData, data } = getInstanceData(this.entity);\n // If `p` is in originalData, always respect that, even if it's undefined\n const value =\n this.fieldName in originalData ? originalData[this.fieldName] : getField(this.entity, this.fieldName as any);\n // An unloaded lazy column's original value is unknown\n return value === notLoadedValue ? undefined : value;\n }\n\n get hasChanged(): boolean {\n const { originalData, data } = getInstanceData(this.entity);\n // Use `__orm.data[p] !== undefined` instead of `p in entity.__orm.data` because if a new (or cloned) entity\n // sets-then-unsets a value, it will return to `undefined` but still be present in `__orm.data`.\n return this.entity.isNewEntity ? data[this.fieldName] !== undefined : this.fieldName in originalData;\n }\n\n get hasUpdated(): boolean {\n const { originalData } = getInstanceData(this.entity);\n return !this.entity.isNewEntity && this.fieldName in originalData;\n }\n}\n\n/** Field status for primitive, enum, & primary key columns. */\nexport interface PrimitiveFieldStatus<T> extends FieldStatus<T> {\n kind: \"field\";\n}\n\n/** Implements the primitive field status, by extend base & just adding our `kind` const. */\nclass PrimitiveFieldStatusImpl<T> extends BaseFieldStatusImpl<T> implements PrimitiveFieldStatus<T> {\n kind = \"field\" as const;\n}\n\n/** Implements the m2o field status, by extend base, & adding `originalEntity`. */\nclass ManyToOneFieldStatusImpl<T extends Entity>\n extends BaseFieldStatusImpl<IdOf<T>>\n implements ManyToOneFieldStatus<T>\n{\n kind = \"m2o\" as const;\n\n /**\n * Returns our original entity's `id`.\n *\n * We can know `originalValue` has an `id` available (i.e. is not new), b/c original values defacto\n * came from the database.\n *\n * We also return the id so that `entity.changes.m2oField.originalValue` has a consistent API,\n * regardless of whether the `entity.m2oField` itself is loaded or unloaded.\n */\n get originalValue() {\n const originalValue = super.originalValue;\n return isEntity(originalValue) ? (originalValue.id as IdOf<T>) : originalValue;\n }\n\n /** Returns the original entity instance, which may not be loaded into memory yet, or undefined. */\n get originalEntity(): Promise<T | undefined> {\n const originalValue = this.originalValue;\n if (isId(originalValue)) {\n return this.entity.em.load(originalValue) as Promise<T>;\n } else {\n return Promise.resolve(undefined);\n }\n }\n}\n\n/** Provides a m2o/reference field's changed/original value in the entity's `this.changes` property. */\nexport interface ManyToOneFieldStatus<T extends Entity> extends FieldStatus<IdOf<T>> {\n kind: \"m2o\";\n /** The original entity, will be `undefined` if the entity new or the m2o was `null`. */\n originalEntity: Promise<T | undefined>;\n}\n\n/** Provides a m2m relation's added/removed/changed/original values. */\nexport interface ManyToManyFieldStatus<U extends Entity> {\n kind: \"m2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\nclass ManyToManyFieldStatusImpl<T extends Entity, U extends Entity> implements ManyToManyFieldStatus<U> {\n readonly kind = \"m2m\";\n readonly #entity: T;\n readonly #m2m: ManyToManyCollection<T, any>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#m2m = entity[fieldName] as ManyToManyCollection<T, any>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#m2m);\n }\n\n // Similar to the o2m.added/removed methods, these don't have to be async because currently\n // any m2m mutation requires having both entities in-memory anyway, i.e. we can't do id-only/unloaded\n // mutation of m2m relations.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.#added, ...this.#removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#m2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n get #added(): U[] {\n return this.#joinRows.addedFor(this.#m2m, this.#entity) as U[];\n }\n\n get #removed(): U[] {\n return this.#joinRows.removedFor(this.#m2m, this.#entity) as U[];\n }\n}\n\n/** Provides an enum m2m's added/removed/changed/original codes (e.g. `Publisher.logoColors`). */\nexport interface EnumCollectionFieldStatus<E> {\n kind: \"m2mEnum\";\n added: E[];\n removed: E[];\n changed: E[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalValues: Promise<readonly E[]>;\n}\n\nclass EnumCollectionFieldStatusImpl<T extends Entity, E> implements EnumCollectionFieldStatus<E> {\n readonly kind = \"m2mEnum\";\n readonly #entity: T;\n readonly #collection: EnumCollectionImpl<T, E>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#collection = entity[fieldName] as EnumCollectionImpl<T, E>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#collection);\n }\n\n get added(): E[] {\n return this.#toCodes(this.#joinRows.addedFor(this.#collection, this.#entity));\n }\n\n get removed(): E[] {\n return this.#toCodes(this.#joinRows.removedFor(this.#collection, this.#entity));\n }\n\n get changed(): E[] {\n return [...this.added, ...this.removed];\n }\n\n get hasChanged(): boolean {\n return this.added.length > 0 || this.removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalValues(): Promise<readonly E[]> {\n // `load()` ensures the persisted (db) rows are in memory, then diff out our pending changes.\n return this.#collection.load().then((codes) => {\n const added = new Set(this.added);\n const enumDetail = this.#collection.otherEnum;\n return [...codes.filter((c) => !added.has(c)), ...this.removed].sort(\n (a, b) => enumDetail.getByCode(a).id - enumDetail.getByCode(b).id,\n );\n });\n }\n\n /** Maps the join rows' enum ids to codes, sorted by enum id for a stable order. */\n #toCodes(ids: JoinColumnValue[]): E[] {\n const enumDetail = this.#collection.otherEnum;\n return (ids as number[]).sort((a, b) => a - b).map((id) => enumDetail.findById(id)!.code as E);\n }\n}\n\nexport interface OneToManyFieldStatus<U extends Entity> {\n kind: \"o2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\n/** Provides access to an o2m relation's added/removed/changed entities. */\nclass OneToManyFieldStatusImpl<T extends Entity, U extends Entity> implements OneToManyFieldStatus<U> {\n readonly kind = \"o2m\";\n readonly #entity: T;\n readonly #o2m: OneToManyCollection<T, U>;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#o2m = entity[fieldName] as OneToManyCollection<T, U>;\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.added, ...this.removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#o2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get #added(): U[] {\n return [\n ...this.#o2m.added(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.adds as U[]) ?? [])),\n ];\n }\n\n get #removed(): U[] {\n return [\n ...this.#o2m.removed(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.removes as U[]) ?? [])),\n ];\n }\n}\n\n/**\n * Creates the `this.changes.firstName` changes API for a given entity `T`.\n *\n * We use `FieldsOf` because that already excludes collection, and then also\n * convert reference fields to `ManyToOneFieldStatus` to be the id type\n * because the reference may not be loaded.\n *\n * @typeParam K The fields of the entity, or potentially the union of the entity and its subtypes,\n * i.e. `Publisher.changes` is typed as `Changes<Publisher, keyof Publisher | keyof SmallPub | keyof LargePub>`\n * @typeParam R An optional list of restrictions, i.e for `Reacted` for to provide `changes` to its subset of fields.\n */\nexport type Changes<T extends Entity, K = keyof (FieldsOf<T> & RelationsOf<T>), R = K> = {\n /** Array of changed field names. */\n fields: NonNullable<K>[];\n /** Array of changed field names w/o o2m & m2m relations (which can be expensive). */\n fieldsWithoutRelations: NonNullable<K>[];\n} & {\n // The o2o branch is `never`: the FK lives on the other entity, so changes never track o2o fields.\n // (The exclusion must stay in the value position: a conditional in the `[P in ...]` clause defers on\n // a generic `T` and collapses `keyof Changes<T>` at inference time, i.e. in `cannotBeUpdated` calls.)\n [P in keyof FieldsOf<T> & R]: FieldsOf<T>[P] extends { kind: \"m2m\"; type: infer U extends Entity }\n ? ManyToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"m2mEnum\"; type: infer E }\n ? EnumCollectionFieldStatus<E>\n : FieldsOf<T>[P] extends { kind: \"o2m\"; type: infer U extends Entity }\n ? OneToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"o2o\" }\n ? never\n : FieldsOf<T>[P] extends { type: (infer U) | undefined }\n ? U extends Entity\n ? ManyToOneFieldStatus<U>\n : PrimitiveFieldStatus<U>\n : never;\n};\n\n// type A1 = never extends string ? 1 : 2;\n// type A2 = Book extends Entity | undefined ? 1 : 2;\n// type A2a = string extends Entity | string | undefined ? 1 : 2;\n// type A3 = Book | undefined extends Entity ? 1 : 2;\n// type A4 = Book | undefined | null extends (infer E extends Entity) | undefined | null ? E : 2;\n\n/**\n * A strongly-typed Entity with its changes field.\n *\n * Ideally this would live on `Entity` directly, but results in circular type issues.\n */\nexport interface EntityChanges<T extends Entity> {\n changes: Changes<T>;\n}\n\nexport function newChangesProxy<T extends Entity>(entity: T): Changes<T> {\n return new Proxy(entity, {\n get(\n target,\n p: PropertyKey & string,\n ):\n | FieldStatus<any>\n | ManyToOneFieldStatus<any>\n | ManyToManyFieldStatus<any>\n | OneToManyFieldStatus<any>\n | (keyof OptsOf<T>)[] {\n if (p === \"fields\") {\n return getChangedFieldNames(entity, true);\n } else if (p === \"fieldsWithoutRelations\") {\n return getChangedFieldNames(entity, false);\n } else if (typeof p === \"symbol\") {\n throw new Error(`Unsupported call to ${String(p)}`);\n }\n const kind = getMetadata(entity).allFields[p]?.kind;\n if (kind === \"m2m\") {\n return new ManyToManyFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"m2mEnum\") {\n return new EnumCollectionFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"o2m\") {\n return new OneToManyFieldStatusImpl(entity, p as keyof T);\n } else if (!isChangeableField(entity, p as any)) {\n throw new Error(`Invalid changes field ${p}`);\n } else if (kind === \"m2o\" || kind === \"poly\") {\n return new ManyToOneFieldStatusImpl(entity, p);\n } else if (kind === \"enum\" || kind === \"primitive\" || kind === \"primaryKey\") {\n return new PrimitiveFieldStatusImpl(entity, p);\n } else if (kind === \"lo2m\" || kind === \"o2o\") {\n throw new Error(`changes are not supported for ${kind} ${p}`);\n } else {\n return assertNever(kind);\n }\n },\n }) as any;\n}\n\nconst addOriginalEntity: Record<Field[\"kind\"], boolean> = {\n m2o: true,\n poly: true,\n m2mEnum: false,\n enum: false,\n lo2m: false,\n m2m: false,\n o2m: false,\n o2o: false,\n primaryKey: false,\n primitive: false,\n};\n\n/** Scans `entity` for changes primitive + m2m/o2m fields. */\nfunction getChangedFieldNames<T extends Entity>(entity: T, includeRelations: boolean): (keyof OptsOf<T>)[] {\n const fieldsChanged = entity.isNewEntity\n ? // Cloning sometimes leaves unset keys in data as undefined, so drop them\n Object.entries(getInstanceData(entity).data)\n .filter(([, value]) => value !== undefined)\n .map(([key]) => key)\n : Object.keys(getInstanceData(entity).originalData);\n\n // Calling `hasChanged` on these can be expensive, and we call this in a loop during em.flush\n if (includeRelations) {\n for (const field of Object.values(getMetadata(entity).allFields)) {\n if (field.kind === \"m2m\") {\n const status = new ManyToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"o2m\") {\n const status = new OneToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"m2mEnum\") {\n const status = new EnumCollectionFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n }\n }\n }\n\n return fieldsChanged as any;\n}\n\nconst entityCompare: (a: Entity, b: Entity) => number = (a, b) => {\n return a.toString().localeCompare(b.toString());\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"changes.js","names":["#entity","#m2m","#joinRows","#added","#removed","#collection","#toCodes","#o2m"],"sources":["../src/changes.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type IdOf } from \"src/EntityManager.ts\";\nimport { getField, isChangeableField } from \"src/fields.ts\";\nimport type { JoinColumnValue, JoinRows } from \"src/flush/JoinRows.ts\";\nimport {\n type Field,\n type ManyToManyCollection,\n type OneToManyCollection,\n type RelationsOf,\n assertNever,\n getEmInternalApi,\n getMetadata,\n isEntity,\n isId,\n} from \"src/index.ts\";\nimport { type EnumCollectionImpl } from \"src/relations/EnumCollection.ts\";\nimport { notLoadedValue } from \"src/relations/LazyField.ts\";\nimport { type FieldsOf, type OptsOf } from \"src/typeMap.ts\";\n\n/** Exposes a field's changed/original value in each entity's `this.changes` property. */\nexport interface FieldStatus<T> {\n /** True if the field has been changed on either create or update. */\n hasChanged: boolean;\n /** True only if the field has been updated i.e. not on the initial create. */\n hasUpdated: boolean;\n /** The original value, will be `undefined` if the entity new. */\n originalValue?: T;\n}\n\n/**\n * Provides a base class for field/primitive & m2o status impls.\n *\n * We use a separate base class so that the primitive & m2o subtypes can have their own\n * `kind: ...` consts, that if the m2o just subtyped the primitive directly, wouldn't be\n * allowed.\n */\nabstract class BaseFieldStatusImpl<T> {\n entity: Entity;\n fieldName: string;\n\n constructor(entity: Entity, fieldName: string) {\n this.entity = entity;\n this.fieldName = fieldName;\n }\n\n get originalValue(): T | undefined {\n const { originalData, data } = getInstanceData(this.entity);\n // If `p` is in originalData, always respect that, even if it's undefined\n const value =\n this.fieldName in originalData ? originalData[this.fieldName] : getField(this.entity, this.fieldName as any);\n // An unloaded lazy column's original value is unknown\n return value === notLoadedValue ? undefined : value;\n }\n\n get hasChanged(): boolean {\n const { originalData, data } = getInstanceData(this.entity);\n // Use `__orm.data[p] !== undefined` instead of `p in entity.__orm.data` because if a new (or cloned) entity\n // sets-then-unsets a value, it will return to `undefined` but still be present in `__orm.data`.\n return this.entity.isNewEntity ? data[this.fieldName] !== undefined : this.fieldName in originalData;\n }\n\n get hasUpdated(): boolean {\n const { originalData } = getInstanceData(this.entity);\n return !this.entity.isNewEntity && this.fieldName in originalData;\n }\n}\n\n/** Field status for primitive, enum, & primary key columns. */\nexport interface PrimitiveFieldStatus<T> extends FieldStatus<T> {\n kind: \"field\";\n}\n\n/** Implements the primitive field status, by extend base & just adding our `kind` const. */\nclass PrimitiveFieldStatusImpl<T> extends BaseFieldStatusImpl<T> implements PrimitiveFieldStatus<T> {\n kind = \"field\" as const;\n}\n\n/** Implements the m2o field status, by extend base, & adding `originalEntity`. */\nclass ManyToOneFieldStatusImpl<T extends Entity>\n extends BaseFieldStatusImpl<IdOf<T>>\n implements ManyToOneFieldStatus<T>\n{\n kind = \"m2o\" as const;\n\n /**\n * Returns our original entity's `id`.\n *\n * We can know `originalValue` has an `id` available (i.e. is not new), b/c original values defacto\n * came from the database.\n *\n * We also return the id so that `entity.changes.m2oField.originalValue` has a consistent API,\n * regardless of whether the `entity.m2oField` itself is loaded or unloaded.\n */\n get originalValue() {\n const originalValue = super.originalValue;\n return isEntity(originalValue) ? (originalValue.id as IdOf<T>) : originalValue;\n }\n\n /** Returns the original entity instance, which may not be loaded into memory yet, or undefined. */\n get originalEntity(): Promise<T | undefined> {\n const originalValue = this.originalValue;\n if (isId(originalValue)) {\n return this.entity.em.load(originalValue) as Promise<T>;\n } else {\n return Promise.resolve(undefined);\n }\n }\n}\n\n/** Provides a m2o/reference field's changed/original value in the entity's `this.changes` property. */\nexport interface ManyToOneFieldStatus<T extends Entity> extends FieldStatus<IdOf<T>> {\n kind: \"m2o\";\n /** The original entity, will be `undefined` if the entity new or the m2o was `null`. */\n originalEntity: Promise<T | undefined>;\n}\n\n/** Provides a m2m relation's added/removed/changed/original values. */\nexport interface ManyToManyFieldStatus<U extends Entity> {\n kind: \"m2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\nclass ManyToManyFieldStatusImpl<T extends Entity, U extends Entity> implements ManyToManyFieldStatus<U> {\n readonly kind = \"m2m\";\n readonly #entity: T;\n readonly #m2m: ManyToManyCollection<T, any>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#m2m = entity[fieldName] as ManyToManyCollection<T, any>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#m2m);\n }\n\n // Similar to the o2m.added/removed methods, these don't have to be async because currently\n // any m2m mutation requires having both entities in-memory anyway, i.e. we can't do id-only/unloaded\n // mutation of m2m relations.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.#added, ...this.#removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#m2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n get #added(): U[] {\n return this.#joinRows.addedFor(this.#m2m, this.#entity) as U[];\n }\n\n get #removed(): U[] {\n return this.#joinRows.removedFor(this.#m2m, this.#entity) as U[];\n }\n}\n\n/** Provides an enum m2m's added/removed/changed/original codes (e.g. `Publisher.logoColors`). */\nexport interface EnumCollectionFieldStatus<E> {\n kind: \"m2mEnum\";\n added: E[];\n removed: E[];\n changed: E[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalValues: Promise<readonly E[]>;\n}\n\nclass EnumCollectionFieldStatusImpl<T extends Entity, E> implements EnumCollectionFieldStatus<E> {\n readonly kind = \"m2mEnum\";\n readonly #entity: T;\n readonly #collection: EnumCollectionImpl<T, E>;\n readonly #joinRows: JoinRows;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#collection = entity[fieldName] as EnumCollectionImpl<T, E>;\n this.#joinRows = getEmInternalApi(entity.em).joinRows(this.#collection);\n }\n\n get added(): E[] {\n return this.#toCodes(this.#joinRows.addedFor(this.#collection, this.#entity));\n }\n\n get removed(): E[] {\n return this.#toCodes(this.#joinRows.removedFor(this.#collection, this.#entity));\n }\n\n get changed(): E[] {\n return [...this.added, ...this.removed];\n }\n\n get hasChanged(): boolean {\n return this.added.length > 0 || this.removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalValues(): Promise<readonly E[]> {\n // `load()` ensures the persisted (db) rows are in memory, then diff out our pending changes.\n return this.#collection.load().then((codes) => {\n const added = new Set(this.added);\n const enumDetail = this.#collection.otherEnum;\n return [...codes.filter((c) => !added.has(c)), ...this.removed].sort(\n (a, b) => enumDetail.getByCode(a).id - enumDetail.getByCode(b).id,\n );\n });\n }\n\n /** Maps the join rows' enum ids to codes, sorted by enum id for a stable order. */\n #toCodes(ids: JoinColumnValue[]): E[] {\n const enumDetail = this.#collection.otherEnum;\n return (ids as number[]).sort((a, b) => a - b).map((id) => enumDetail.findById(id)!.code as E);\n }\n}\n\nexport interface OneToManyFieldStatus<U extends Entity> {\n kind: \"o2m\";\n added: U[];\n removed: U[];\n changed: U[];\n hasChanged: boolean;\n hasUpdated: boolean;\n originalEntities: Promise<readonly U[]>;\n}\n\n/** Provides access to an o2m relation's added/removed/changed entities. */\nclass OneToManyFieldStatusImpl<T extends Entity, U extends Entity> implements OneToManyFieldStatus<U> {\n readonly kind = \"o2m\";\n readonly #entity: T;\n readonly #o2m: OneToManyCollection<T, U>;\n\n constructor(entity: T, fieldName: keyof T) {\n this.#entity = entity;\n this.#o2m = entity[fieldName] as OneToManyCollection<T, U>;\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get added(): U[] {\n return this.#added.sort(entityCompare);\n }\n\n get removed(): U[] {\n return this.#removed.sort(entityCompare);\n }\n\n get changed(): U[] {\n return [...this.added, ...this.removed].sort(entityCompare);\n }\n\n get hasChanged(): boolean {\n // Calculating `changed: U[]` can be expensive if we sort, so just look at added/removed\n return this.#added.length > 0 || this.#removed.length > 0;\n }\n\n get hasUpdated(): boolean {\n return !this.#entity.isNewEntity && this.hasChanged;\n }\n\n get originalEntities(): Promise<readonly U[]> {\n return this.#o2m.load().then((list) => {\n const set = new Set(this.added);\n const copy = list.filter((e) => !set.has(e));\n copy.push(...this.removed);\n return copy.sort(entityCompare);\n });\n }\n\n // This doesn't have to be a promise, b/c even if o2m is unloaded, to mutate\n // the o2m (calling `add(other)` or `remove(other)` or `other.otherField = me`), all\n // require having the \"other entity\" in memory.\n get #added(): U[] {\n return [\n ...this.#o2m.added(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.adds as U[]) ?? [])),\n ];\n }\n\n get #removed(): U[] {\n return [\n ...this.#o2m.removed(),\n ...(this.#entity.isNewEntity\n ? []\n : ((getEmInternalApi(this.#entity.em).pendingPercolate.get(this.#entity.idTagged)?.get(this.#o2m.fieldName)\n ?.removes as U[]) ?? [])),\n ];\n }\n}\n\n/**\n * Creates the `this.changes.firstName` changes API for a given entity `T`.\n *\n * We use `FieldsOf` because that already excludes collection, and then also\n * convert reference fields to `ManyToOneFieldStatus` to be the id type\n * because the reference may not be loaded.\n *\n * @typeParam K The fields of the entity, or potentially the union of the entity and its subtypes,\n * i.e. `Publisher.changes` is typed as `Changes<Publisher, keyof Publisher | keyof SmallPub | keyof LargePub>`\n * @typeParam R An optional list of restrictions, i.e for `Reacted` for to provide `changes` to its subset of fields.\n */\nexport type Changes<T extends Entity, K = keyof (FieldsOf<T> & RelationsOf<T>), R = K> = {\n /** Array of changed field names. */\n fields: NonNullable<K>[];\n /** Array of changed field names w/o o2m & m2m relations (which can be expensive). */\n fieldsWithoutRelations: NonNullable<K>[];\n} & {\n // The o2o branch is `never`: the FK lives on the other entity, so changes never track o2o fields.\n // (The exclusion must stay in the value position: a conditional in the `[P in ...]` clause defers on\n // a generic `T` and collapses `keyof Changes<T>` at inference time, i.e. in `cannotBeUpdated` calls.)\n [P in keyof FieldsOf<T> & R]: FieldsOf<T>[P] extends { kind: \"m2m\"; type: infer U extends Entity }\n ? ManyToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"m2mEnum\"; type: infer E }\n ? EnumCollectionFieldStatus<E>\n : FieldsOf<T>[P] extends { kind: \"o2m\"; type: infer U extends Entity }\n ? OneToManyFieldStatus<U>\n : FieldsOf<T>[P] extends { kind: \"o2o\" }\n ? never\n : FieldsOf<T>[P] extends { type: (infer U) | undefined }\n ? U extends Entity\n ? ManyToOneFieldStatus<U>\n : PrimitiveFieldStatus<U>\n : never;\n};\n\n// type A1 = never extends string ? 1 : 2;\n// type A2 = Book extends Entity | undefined ? 1 : 2;\n// type A2a = string extends Entity | string | undefined ? 1 : 2;\n// type A3 = Book | undefined extends Entity ? 1 : 2;\n// type A4 = Book | undefined | null extends (infer E extends Entity) | undefined | null ? E : 2;\n\n/**\n * A strongly-typed Entity with its changes field.\n *\n * Ideally this would live on `Entity` directly, but results in circular type issues.\n */\nexport interface EntityChanges<T extends Entity> {\n changes: Changes<T>;\n}\n\nexport function newChangesProxy<T extends Entity>(entity: T): Changes<T> {\n return new Proxy(entity, {\n get(\n target,\n p: PropertyKey & string,\n ):\n | FieldStatus<any>\n | ManyToOneFieldStatus<any>\n | ManyToManyFieldStatus<any>\n | OneToManyFieldStatus<any>\n | (keyof OptsOf<T>)[] {\n if (p === \"fields\") {\n return getChangedFieldNames(entity, true);\n } else if (p === \"fieldsWithoutRelations\") {\n return getChangedFieldNames(entity, false);\n } else if (typeof p === \"symbol\") {\n throw new Error(`Unsupported call to ${String(p)}`);\n }\n const kind = getMetadata(entity).allFields[p]?.kind;\n if (kind === \"m2m\") {\n return new ManyToManyFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"m2mEnum\") {\n return new EnumCollectionFieldStatusImpl(entity, p as keyof T);\n } else if (kind === \"o2m\") {\n return new OneToManyFieldStatusImpl(entity, p as keyof T);\n } else if (!isChangeableField(entity, p as any)) {\n throw new Error(`Invalid changes field ${p}`);\n } else if (kind === \"m2o\" || kind === \"poly\") {\n return new ManyToOneFieldStatusImpl(entity, p);\n } else if (kind === \"enum\" || kind === \"primitive\" || kind === \"primaryKey\") {\n return new PrimitiveFieldStatusImpl(entity, p);\n } else if (kind === \"lo2m\" || kind === \"o2o\") {\n throw new Error(`changes are not supported for ${kind} ${p}`);\n } else {\n return assertNever(kind);\n }\n },\n }) as any;\n}\n\nconst addOriginalEntity: Record<Field[\"kind\"], boolean> = {\n m2o: true,\n poly: true,\n m2mEnum: false,\n enum: false,\n lo2m: false,\n m2m: false,\n o2m: false,\n o2o: false,\n primaryKey: false,\n primitive: false,\n};\n\n/** Scans `entity` for changes primitive + m2m/o2m fields. */\nfunction getChangedFieldNames<T extends Entity>(entity: T, includeRelations: boolean): (keyof OptsOf<T>)[] {\n const fieldsChanged = entity.isNewEntity\n ? // Cloning sometimes leaves unset keys in data as undefined, so drop them\n Object.entries(getInstanceData(entity).data)\n .filter(([, value]) => value !== undefined)\n .map(([key]) => key)\n : Object.keys(getInstanceData(entity).originalData);\n\n // Calling `hasChanged` on these can be expensive, and we call this in a loop during em.flush\n if (includeRelations) {\n for (const field of Object.values(getMetadata(entity).allFields)) {\n if (field.kind === \"m2m\") {\n const status = new ManyToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"o2m\") {\n const status = new OneToManyFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n } else if (field.kind === \"m2mEnum\") {\n const status = new EnumCollectionFieldStatusImpl(entity, field.fieldName as keyof T);\n if (status.hasChanged) {\n fieldsChanged.push(field.fieldName);\n }\n }\n }\n }\n\n return fieldsChanged as any;\n}\n\nconst entityCompare: (a: Entity, b: Entity) => number = (a, b) => {\n return a.toString().localeCompare(b.toString());\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":["getMetadata","convertToLoadHint","AsyncDefault","fail"],"sources":["../src/config.ts"],"sourcesContent":["import { resetConstructorMap } from \"./configure.ts\";\nimport { AsyncDefault } from \"./defaults.ts\";\nimport { type Entity } from \"./Entity.ts\";\nimport {\n type EntityConstructor,\n type EntityField,\n type EntityMetadata,\n type FieldsOf,\n type LoadHint,\n type Loaded,\n type MaybeAbstractEntityConstructor,\n type Reacted,\n type ReactiveHint,\n type RelationsIn,\n type SettableFields,\n fail,\n getMetadata,\n} from \"./index.ts\";\nimport { convertToLoadHint } from \"./reactivity/reactiveHints.ts\";\nimport { type ValidationRule, type ValidationRuleInternal } from \"./rules.ts\";\nimport { type MaybePromise } from \"./utils.ts\";\n\nexport type EntityHook =\n | \"beforeFlush\"\n | \"beforeCreate\"\n | \"beforeUpdate\"\n | \"beforeDelete\"\n | \"afterValidation\"\n | \"beforeCommit\"\n | \"afterCommit\";\ntype HookFn<T extends Entity, C> = (entity: T, ctx: C) => MaybePromise<unknown>;\n\ntype AddReactionOpts = { runOnce?: boolean; name?: string };\n\nexport const constraintNameToValidationError: Record<string, string> = {};\n\ntype Settable<T extends Entity> = keyof SettableFields<FieldsOf<T>> & string;\n\nlet booted = false;\nlet afterMetadataLocked = false;\n\n/**\n * Called at the end of `configureMetadata` to indicate the boot process is complete.\n *\n * We use this flag to prevent users from mistakenly calling `config` methods after the boot\n * process is complete, i.e. from hooks or other post-boot invocations, where the calls are\n * not guaranteed to work because Joist's reactivity graph has already been initialized.\n */\nexport function setBooted(): void {\n booted = true;\n}\n\nexport function setAfterMetadataLocked(): void {\n afterMetadataLocked = true;\n}\n\n/** The public API to configure an Entity's hooks & validation rules. */\nexport class ConfigApi<T extends Entity, C> {\n __data = new ConfigData<T, C>();\n\n /**\n * Maps a given `constraintName`, i.e. `authors_publisher_id_unique_index`, to a hard-coded-but-pretty validation errors.\n *\n * Note that the validationError must be hard-coded b/c we cannot tease out \"which entity\" caused a given\n * constraint failure from the SQL database. If you really need pretty-and-custom validation errors, you'll\n * need to check the constraint by hand ahead of time, before calling `em.flush`.\n */\n addConstraintMessage(constraintName: string, validationError: string) {\n this.ensurePreBoot(getCallerName(), \"addConstraintMessage\");\n constraintNameToValidationError[constraintName] = validationError;\n }\n\n /**\n * Maps a `RecursiveCycleError` on the given recursive collection field to a custom validation error.\n *\n * When a cycle is detected in a recursive collection (e.g. `parentsRecursive`), instead of\n * throwing a raw `RecursiveCycleError`, `em.flush` will convert it into a `ValidationErrors`\n * using the provided message function.\n *\n * The `messageFn` receives the entity that detected the cycle and the cycle path (array of\n * entities forming the cycle), and should return a validation error string.\n */\n addCycleRule(fieldName: string & keyof T, messageFn: (entity: T, cyclePath: Entity[]) => string) {\n this.ensurePreBoot(getCallerName(), \"addCycleRule\");\n this.__data.cycleMessages[fieldName] = messageFn;\n // Register a reactive rule that loads the recursive collection and catches cycle errors.\n // The rule uses the recursive collection's underlying relation as a reactive hint so it\n // triggers when the collection changes.\n const hint = fieldName as any;\n const fn = () => {\n // We don't actually need a try/catch/anything here, b/c if there is a cycle, it\n // will cause a CycleError during followReverseHint, and never even get to calling this fn.\n };\n this.__data.rules.push({ name: `addCycleRule(${getCallerName()})`, fn, hint });\n }\n\n /**\n * Adds a validation rule for this entity.\n *\n * If `hint` is passed, then the rule's lambda will be: 1) passed a view of the entity with only\n * the fields included in `hint` marked as accessible, and 2) the rule will be called reactively any\n * time any field in the `hint` changes.\n *\n * If lambdas want to access fields w/o having them marked for reactivity, the rule can either\n * include the field as readonly with a `:ro` suffix, i.e. `firstName:ro`, or the lambda can\n * access the `reacted.entity` property to get a full view of the entity's fields and methods.\n */\n addRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addRule(rule: ValidationRule<T>): void;\n addRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addRule\");\n pushValidationRule(this.__data.rules, name, ruleOrHint, maybeRule);\n }\n\n /**\n * Adds a \"commit rule\" that runs like a regular validation rule (see {@link addRule}), but *after*\n * the entity's `INSERT`/`UPDATE`/`DELETE` has been flushed to the database and *before* the\n * transaction `COMMIT`s, i.e. still within the transaction.\n *\n * The point of running this late is that any `em.find`s the rule makes will query the _changed_\n * state (the just-flushed rows), instead of the pre-flush state that regular `addRule` rules see.\n * This is useful for cross-row invariants that are easiest to express as a query, e.g. \"at most\n * one active `AuthorSchedule` per author\", where the rule wants to `em.find` the sibling rows\n * that were themselves just inserted/updated in this same flush.\n *\n * Like `addRule`, if a `hint` is passed the rule is reactive: it runs whenever any field in the\n * hint changes (walking back to the owning entity), and the lambda gets a `Reacted` view.\n */\n addCommitRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addCommitRule(rule: ValidationRule<T>): void;\n addCommitRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addCommitRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addCommitRule\");\n pushValidationRule(this.__data.commitRules, name, ruleOrHint, maybeRule);\n }\n\n /** If both this entity, and `cstr` entities, are in the same `em.flush`, run us first. */\n runHooksBefore(cstr: EntityConstructor<any>): void {\n this.__data.runHooksBefore.push(cstr);\n }\n\n /** Deletes any entity/entities pointed to by `relation` when this entity is deleted. */\n cascadeDelete(relation: keyof RelationsIn<T> & LoadHint<T>): void {\n this.ensurePreBoot(getCallerName(), \"cascadeDelete\");\n this.__data.cascadeDeleteFields.push(relation);\n }\n\n touchOnChange(relation: keyof RelationsIn<T>): void {\n this.ensurePreBoot(getCallerName(), \"touchOnChange\");\n this.__data.touchOnChange.add(relation);\n }\n\n private addHook(hook: EntityHook, ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>) {\n this.ensurePreBoot(getCallerName(), \"addHook\");\n if (typeof ruleOrHint === \"function\") {\n this.__data.hooks[hook].push(ruleOrHint);\n } else {\n const fn = async (entity: T, ctx: C) => {\n // TODO Use this for reactive beforeFlush\n const loaded = await entity.em.populate(entity, ruleOrHint);\n return maybeFn!(loaded, ctx);\n };\n // Squirrel our hint away where configureMetadata can find it\n (fn as any).hint = ruleOrHint;\n this.__data.hooks[hook].push(fn);\n }\n }\n\n afterMetadata(fn: AfterMetadataCallback<T>): void {\n if (afterMetadataLocked) {\n throw new Error(\n `config.afterMetadata on ${getCallerName()} must only be called on boot, before calling \\`configureMetadata\\` and not from other \\`afterMetadata\\` hooks.`,\n );\n }\n this.__data.afterMetadataCallbacks.push(fn);\n }\n\n beforeDelete<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeDelete(fn: HookFn<T, C>): void;\n beforeDelete(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeDelete\", ruleOrHint, maybeFn);\n }\n\n beforeFlush<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeFlush(fn: HookFn<T, C>): void;\n beforeFlush(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeFlush\", ruleOrHint, maybeFn);\n }\n\n // beforeCreate still needs to take a hint because even though the entity itself is New<T>, we might want to load\n // a nested relation that isn't loaded yet\n beforeCreate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeCreate(fn: HookFn<T, C>): void;\n beforeCreate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeCreate\", ruleOrHint, maybeFn);\n }\n\n beforeUpdate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeUpdate(fn: HookFn<T, C>): void;\n beforeUpdate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeUpdate\", ruleOrHint, maybeFn);\n }\n\n afterValidation<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n afterValidation(fn: HookFn<T, C>): void;\n afterValidation(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"afterValidation\", ruleOrHint, maybeFn);\n }\n\n beforeCommit(fn: HookFn<T, C>): void {\n this.addHook(\"beforeCommit\", fn);\n }\n\n afterCommit(fn: HookFn<T, C>): void {\n this.addHook(\"afterCommit\", fn);\n }\n\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when the provided hint has changes, not on every flush, like an RF/RR\n * 3. Run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a named reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param name A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(name: string, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param opts Options object containing:\n * - runOnce - If true, the reaction will only run once per flush, not every time the hint changes. Optional.\n * default: false.\n * - name - A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(opts: AddReactionOpts, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n addReaction<H extends ReactiveHint<T>>(\n nameOrOptsOrHint: string | AddReactionOpts | H,\n hintOrFn: H | HookFn<Loaded<T, H>, C>,\n maybeFn?: HookFn<Loaded<T, H>, C>,\n ): void {\n // Keep the name so we can uniquely identify this reaction later and also aid debugging/tracing\n const fn = maybeFn ?? (hintOrFn as HookFn<Loaded<T, H>, C>);\n const hint = (maybeFn ? hintOrFn : nameOrOptsOrHint) as H;\n const opts = maybeFn\n ? typeof nameOrOptsOrHint === \"string\"\n ? { name: nameOrOptsOrHint }\n : (nameOrOptsOrHint as AddReactionOpts)\n : {};\n const { name = getCallerName(), runOnce = false } = opts;\n this.ensurePreBoot(name, \"addReaction\");\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const wrappedFn = (entity: T, ctx: C) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then((loaded) => fn(loaded as Loaded<T, H>, ctx));\n }\n return fn(entity as Loaded<T, H>, ctx);\n };\n this.__data.reactions.push({ name, fn: wrappedFn, hint, runOnce });\n }\n\n /** Adds a synchronous default for `fieldName` to a hard-coded `value`. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // Allow returning undefined to mean \"no default\"\n value: F extends EntityField ? F[\"type\"] : never,\n ): void;\n /** Adds a synchronous default for `fieldName` to the result of simple sync lambda. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // ...this doesn't technically declare what other fields of `entity` we depend on,\n // which ideally we want to drive \"which default to set first?\" precedence decisions.\n fn: (entity: T) => F extends EntityField ? F[\"type\"] | undefined : never,\n ): void;\n /** Adds an asynchronous default for `fieldName` to the result of a hinted lambda. */\n setDefault<K extends Settable<T>, const H extends ReactiveHint<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // We use a ReactiveHint so that we get field-level dependencies that means someday\n // we could drive \"which default to set first?\" precedence decisions.\n hint: H,\n fn: (\n entity: Reacted<T, H>,\n ctx: C,\n ) => F extends { kind: \"m2o\"; type: infer T extends Entity }\n ? MaybePromise<T | Reacted<T, {}> | undefined>\n : F extends EntityField\n ? MaybePromise<F[\"type\"] | undefined>\n : never,\n ): void;\n setDefault<K extends keyof SettableFields<FieldsOf<T>> & string>(fieldName: K, hintOrFnOrValue: any, fn?: any): void {\n this.ensurePreBoot(getCallerName(), \"setDefault\");\n if (fn) {\n // If we're called once by the codegen, and again by the user, override the syncDefault\n delete this.__data.syncDefaults[fieldName];\n this.__data.asyncDefaults[fieldName] = new AsyncDefault(fieldName, hintOrFnOrValue, fn);\n } else {\n this.__data.syncDefaults[fieldName] = hintOrFnOrValue;\n }\n }\n\n /**\n * A noop method that exists solely to keep the `config.placeholder()` line in the initial entity file,\n * until the user is ready to use it. */\n placeholder(): void {}\n\n private ensurePreBoot(name: string, op: string): void {\n if (booted) {\n // Detect if this is NextJS and we're in a hot-reload situation\n if (isRunningInNextJs()) {\n // Reset our bag of config data to collect only the new rules/hooks\n this.__data = new ConfigData();\n resetBootFlag();\n } else {\n throw new Error(\n `config.${op} call on ${name} must only be called on boot, before calling \\`configureMetadata\\`.`,\n );\n }\n }\n }\n}\n\nfunction isRunningInNextJs(): boolean {\n return \"__NEXT_HTTP_AGENT\" in globalThis || \"__NEXT_HTTPS_AGENT\" in globalThis;\n}\n\n/**\n * Allows projects to manually reset the internal `booted` flag.\n *\n * This is only necessary if they're using hot-reloading and reloading their entity files\n * without restarting the server.\n *\n * Generally most tools like tsx or ts-node-dev reload the whole process, so don't need\n * to call this, but if Joist is used in a framework that does actual-hot-reloading, this\n * it will be necessary.\n */\nexport function resetBootFlag(): void {\n booted = false;\n afterMetadataLocked = false;\n resetConstructorMap();\n}\n\n/**\n * Stores a path back to a reactive rule.\n *\n * I.e. if `Book` has a `ruleFn` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveRule` with fields `[\"title\"]`, path `books`, and rule `ruleFn`.\n */\nexport interface ReactiveRule {\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream rule's eval. */\n fields: string[];\n /** The constructor of downstream entity that owns the reactive rule. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reactive rule. */\n name: string;\n /** The path from this source entity to the downstream entity that needs evaled. */\n path: string[];\n /** The downstream validation rule to eval. */\n fn: ValidationRule<any>;\n}\n\n/**\n * Stores a path back to a reactive derived field.\n *\n * I.e. if `Book.displayName` is an `asyncField` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveFields` with fields `[\"title\"]`, path `books`, and name `displayName`.\n */\nexport interface ReactiveField {\n kind: \"populate\" | \"query\";\n /** The fields on this source entity that would trigger the downstream field's recalc. */\n fields: string[];\n /** Read-only/immutable fields that are used in the derived field. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the derived field. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The path from this source entity to the downstream entity that needs recalced. */\n path: string[];\n /** The name of the reactive field in the downstream entity to recalc. */\n name: string;\n runOnce: false;\n}\n\nexport interface Reaction {\n kind: \"reaction\";\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream hook's eval. */\n fields: string[];\n /** Read-only/immutable fields that are used in the function. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the reaction. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reaction. */\n name: string;\n /** The path from this source entity to the downstream entity for this reaction. */\n path: string[];\n /** The downstream reaction function. */\n fn: HookFn<any, any>;\n /** If true, the reaction should only run once per flush, not every time the hint changes. */\n runOnce: boolean;\n}\n\nexport type Reactable = ReactiveField | Reaction;\n\ninterface ReactionInternal<T extends Entity, H extends ReactiveHint<T>, C> {\n name: string;\n fn: HookFn<T, C>;\n hint: H;\n runOnce: boolean;\n}\n\ntype AfterMetadataCallback<T extends Entity> = (meta: EntityMetadata<T>) => void;\n\n/** The internal state of an entity's configuration data, i.e. validation rules/hooks. */\nexport class ConfigData<T extends Entity, C> {\n afterMetadataCallbacks: AfterMetadataCallback<T>[] = [];\n runHooksBefore: EntityConstructor<any>[] = [];\n /** The validation rules for this entity type. */\n rules: ValidationRuleInternal<T>[] = [];\n /** The \"commit rules\" for this entity type, i.e. validation rules that run post-flush/pre-commit. */\n commitRules: ValidationRuleInternal<T>[] = [];\n /** The reactions for this entity type. */\n reactions: ReactionInternal<T, any, C>[] = [];\n /** The hooks for this entity type. */\n hooks: Record<EntityHook, HookFn<T, C>[]> = {\n beforeDelete: [],\n beforeFlush: [],\n beforeCreate: [],\n beforeUpdate: [],\n afterValidation: [],\n beforeCommit: [],\n afterCommit: [],\n };\n /** Synchronous defaults for this entity type, invoked on `em.create`. */\n syncDefaults: Record<string, ((entity: T) => void) | unknown> = {};\n /** Asynchronous defaults for this entity type, invoked on `em.flush`. */\n asyncDefaults: Record<string, AsyncDefault<T>> = {};\n\n // An array of the reactive rules that depend on this entity\n reactiveRules: ReactiveRule[] = [];\n // An array of the reactive *commit* rules (post-flush/pre-commit) that depend on this entity\n reactiveCommitRules: ReactiveRule[] = [];\n // An array of the reactive fields and reactions that depend on this entity\n reactables: Reactable[] = [];\n cascadeDeleteFields: Array<keyof RelationsIn<T>> = [];\n touchOnChange: Set<keyof RelationsIn<T>> = new Set();\n // Constantly converting reactive hints to load hints is expense, so cache them here\n cachedReactiveLoadHints: Record<string, any> = {};\n /** Maps recursive collection fieldNames to custom cycle error message functions. */\n cycleMessages: Record<string, (entity: T, cyclePath: Entity[]) => string> = {};\n}\n\nexport function getCallerName(extraFrames: number = 0): string {\n const err = getStack();\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n // (Make sure to drop lines that don't start with 'at' b/c the stack format can differ\n // slightly i.e. if running via tsx/using a node loader (probably?)\n const line = err.stack!.split(\"\\n\").filter((line) => line.includes(\" at \"))[3 + extraFrames];\n const parts = line.split(\"/\");\n // Get the last part, which will be the file name, i.e. Activity.ts:86:8\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\");\n}\n\nexport function getFuzzyCallerName(): string {\n const err = getStack();\n const lines = err\n .stack!.split(\"\\n\")\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n .filter((line) => line.includes(\" at \"));\n const line = findUserCodeLine(lines);\n return getFilePath(line);\n}\n\n/**\n * Given a callstack line return the file name.\n *\n * I.e. `at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)`\n */\nexport function getFilePath(line: string): string {\n const parts = line.split(\"/\");\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\"); // Drop the `:8)`at the end\n}\n\n/** Given a callstack, uses heuristics to find the first line that looks like user code. */\nexport function findUserCodeLine(lines: string[]): string {\n return (\n lines.find((line) => {\n // When running Joist's own integration tests, if we ignore `/joist-orm/`, we'll end up ignoring\n // everything because `joist-orm` is the name of the repository/working copy itself.\n const withinWorkingCopyJoist = line.includes(\"/packages/orm/\") || line.includes(\"/packages/core\");\n // But once we're not in a working copy, assume any `/joist-orm/` in the path === internal orm stack frames\n const withinProductionJoist =\n !withinWorkingCopyJoist && (line.includes(\"/joist-orm/src/\") || line.includes(\"/joist-core/src\"));\n const nodeInternals = line.includes(\"node:internal/\") || line.includes(\"Promise.all\");\n const isUserCode = !withinWorkingCopyJoist && !withinProductionJoist && !nodeInternals;\n // const isRecalc = line.includes(\".recalcPending\");\n const isDefault = line.includes(\"/defaults.ts\");\n // Batched calls like findOrCreate won't have a stack trace back to the true caller, so just stop there\n const isDataloader = line.includes(\"/dataloaders/\");\n // If this is the `newTestInstance` call\n // What about:\n // recalcSynchronousDerivedFields\n return (\n (isDefault || isDataloader || isUserCode) &&\n !line.includes(\"Codegen.ts\") &&\n // I wanted to exclude this, but it was actually our utils/entities.ts maybeSetDefault helper method\n // !line.includes(\"entities.ts\") &&\n // Ignore loops in joist code like setOpts\n !line.includes(\"at Array\") &&\n // Ignore the Author.ts constructor calling setOpts\n !line.includes(\"at new \")\n );\n }) ?? fail(\"Could not find caller name\")\n );\n}\n\nconst getStack: () => { stack?: string } = \"captureStackTrace\" in Error ? getStackFromCapture : getStackFromObject;\n\nfunction getStackFromCapture(): { stack?: string } {\n const obj = {};\n Error.captureStackTrace(obj);\n return obj as any;\n}\n\nfunction getStackFromObject(): { stack?: string } {\n try {\n throw Error(\"\");\n } catch (err) {\n return err as Error;\n }\n}\n\n/** Pushes a validation rule (either the raw or hinted/reactive form) onto `rules`, shared by `addRule`/`addCommitRule`. */\nfunction pushValidationRule<T extends Entity>(\n rules: ValidationRuleInternal<T>[],\n name: string,\n ruleOrHint: ValidationRule<T> | any,\n maybeRule: ValidationRule<any> | undefined,\n): void {\n if (typeof ruleOrHint === \"function\") {\n rules.push({ name, fn: ruleOrHint, hint: undefined });\n } else {\n const hint = ruleOrHint;\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const fn = (entity: T) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then(maybeRule!);\n }\n return maybeRule!(entity);\n };\n rules.push({ name, fn, hint });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.cjs","names":["getMetadata","convertToLoadHint","AsyncDefault","fail"],"sources":["../src/config.ts"],"sourcesContent":["import { resetConstructorMap } from \"src/configure.ts\";\nimport { AsyncDefault } from \"src/defaults.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport {\n type EntityConstructor,\n type EntityField,\n type EntityMetadata,\n type FieldsOf,\n type LoadHint,\n type Loaded,\n type MaybeAbstractEntityConstructor,\n type Reacted,\n type ReactiveHint,\n type RelationsIn,\n type SettableFields,\n fail,\n getMetadata,\n} from \"src/index.ts\";\nimport { convertToLoadHint } from \"src/reactivity/reactiveHints.ts\";\nimport { type ValidationRule, type ValidationRuleInternal } from \"src/rules.ts\";\nimport { type MaybePromise } from \"src/utils.ts\";\n\nexport type EntityHook =\n | \"beforeFlush\"\n | \"beforeCreate\"\n | \"beforeUpdate\"\n | \"beforeDelete\"\n | \"afterValidation\"\n | \"beforeCommit\"\n | \"afterCommit\";\ntype HookFn<T extends Entity, C> = (entity: T, ctx: C) => MaybePromise<unknown>;\n\ntype AddReactionOpts = { runOnce?: boolean; name?: string };\n\nexport const constraintNameToValidationError: Record<string, string> = {};\n\ntype Settable<T extends Entity> = keyof SettableFields<FieldsOf<T>> & string;\n\nlet booted = false;\nlet afterMetadataLocked = false;\n\n/**\n * Called at the end of `configureMetadata` to indicate the boot process is complete.\n *\n * We use this flag to prevent users from mistakenly calling `config` methods after the boot\n * process is complete, i.e. from hooks or other post-boot invocations, where the calls are\n * not guaranteed to work because Joist's reactivity graph has already been initialized.\n */\nexport function setBooted(): void {\n booted = true;\n}\n\nexport function setAfterMetadataLocked(): void {\n afterMetadataLocked = true;\n}\n\n/** The public API to configure an Entity's hooks & validation rules. */\nexport class ConfigApi<T extends Entity, C> {\n __data = new ConfigData<T, C>();\n\n /**\n * Maps a given `constraintName`, i.e. `authors_publisher_id_unique_index`, to a hard-coded-but-pretty validation errors.\n *\n * Note that the validationError must be hard-coded b/c we cannot tease out \"which entity\" caused a given\n * constraint failure from the SQL database. If you really need pretty-and-custom validation errors, you'll\n * need to check the constraint by hand ahead of time, before calling `em.flush`.\n */\n addConstraintMessage(constraintName: string, validationError: string) {\n this.ensurePreBoot(getCallerName(), \"addConstraintMessage\");\n constraintNameToValidationError[constraintName] = validationError;\n }\n\n /**\n * Maps a `RecursiveCycleError` on the given recursive collection field to a custom validation error.\n *\n * When a cycle is detected in a recursive collection (e.g. `parentsRecursive`), instead of\n * throwing a raw `RecursiveCycleError`, `em.flush` will convert it into a `ValidationErrors`\n * using the provided message function.\n *\n * The `messageFn` receives the entity that detected the cycle and the cycle path (array of\n * entities forming the cycle), and should return a validation error string.\n */\n addCycleRule(fieldName: string & keyof T, messageFn: (entity: T, cyclePath: Entity[]) => string) {\n this.ensurePreBoot(getCallerName(), \"addCycleRule\");\n this.__data.cycleMessages[fieldName] = messageFn;\n // Register a reactive rule that loads the recursive collection and catches cycle errors.\n // The rule uses the recursive collection's underlying relation as a reactive hint so it\n // triggers when the collection changes.\n const hint = fieldName as any;\n const fn = () => {\n // We don't actually need a try/catch/anything here, b/c if there is a cycle, it\n // will cause a CycleError during followReverseHint, and never even get to calling this fn.\n };\n this.__data.rules.push({ name: `addCycleRule(${getCallerName()})`, fn, hint });\n }\n\n /**\n * Adds a validation rule for this entity.\n *\n * If `hint` is passed, then the rule's lambda will be: 1) passed a view of the entity with only\n * the fields included in `hint` marked as accessible, and 2) the rule will be called reactively any\n * time any field in the `hint` changes.\n *\n * If lambdas want to access fields w/o having them marked for reactivity, the rule can either\n * include the field as readonly with a `:ro` suffix, i.e. `firstName:ro`, or the lambda can\n * access the `reacted.entity` property to get a full view of the entity's fields and methods.\n */\n addRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addRule(rule: ValidationRule<T>): void;\n addRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addRule\");\n pushValidationRule(this.__data.rules, name, ruleOrHint, maybeRule);\n }\n\n /**\n * Adds a \"commit rule\" that runs like a regular validation rule (see {@link addRule}), but *after*\n * the entity's `INSERT`/`UPDATE`/`DELETE` has been flushed to the database and *before* the\n * transaction `COMMIT`s, i.e. still within the transaction.\n *\n * The point of running this late is that any `em.find`s the rule makes will query the _changed_\n * state (the just-flushed rows), instead of the pre-flush state that regular `addRule` rules see.\n * This is useful for cross-row invariants that are easiest to express as a query, e.g. \"at most\n * one active `AuthorSchedule` per author\", where the rule wants to `em.find` the sibling rows\n * that were themselves just inserted/updated in this same flush.\n *\n * Like `addRule`, if a `hint` is passed the rule is reactive: it runs whenever any field in the\n * hint changes (walking back to the owning entity), and the lambda gets a `Reacted` view.\n */\n addCommitRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addCommitRule(rule: ValidationRule<T>): void;\n addCommitRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addCommitRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addCommitRule\");\n pushValidationRule(this.__data.commitRules, name, ruleOrHint, maybeRule);\n }\n\n /** If both this entity, and `cstr` entities, are in the same `em.flush`, run us first. */\n runHooksBefore(cstr: EntityConstructor<any>): void {\n this.__data.runHooksBefore.push(cstr);\n }\n\n /** Deletes any entity/entities pointed to by `relation` when this entity is deleted. */\n cascadeDelete(relation: keyof RelationsIn<T> & LoadHint<T>): void {\n this.ensurePreBoot(getCallerName(), \"cascadeDelete\");\n this.__data.cascadeDeleteFields.push(relation);\n }\n\n touchOnChange(relation: keyof RelationsIn<T>): void {\n this.ensurePreBoot(getCallerName(), \"touchOnChange\");\n this.__data.touchOnChange.add(relation);\n }\n\n private addHook(hook: EntityHook, ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>) {\n this.ensurePreBoot(getCallerName(), \"addHook\");\n if (typeof ruleOrHint === \"function\") {\n this.__data.hooks[hook].push(ruleOrHint);\n } else {\n const fn = async (entity: T, ctx: C) => {\n // TODO Use this for reactive beforeFlush\n const loaded = await entity.em.populate(entity, ruleOrHint);\n return maybeFn!(loaded, ctx);\n };\n // Squirrel our hint away where configureMetadata can find it\n (fn as any).hint = ruleOrHint;\n this.__data.hooks[hook].push(fn);\n }\n }\n\n afterMetadata(fn: AfterMetadataCallback<T>): void {\n if (afterMetadataLocked) {\n throw new Error(\n `config.afterMetadata on ${getCallerName()} must only be called on boot, before calling \\`configureMetadata\\` and not from other \\`afterMetadata\\` hooks.`,\n );\n }\n this.__data.afterMetadataCallbacks.push(fn);\n }\n\n beforeDelete<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeDelete(fn: HookFn<T, C>): void;\n beforeDelete(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeDelete\", ruleOrHint, maybeFn);\n }\n\n beforeFlush<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeFlush(fn: HookFn<T, C>): void;\n beforeFlush(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeFlush\", ruleOrHint, maybeFn);\n }\n\n // beforeCreate still needs to take a hint because even though the entity itself is New<T>, we might want to load\n // a nested relation that isn't loaded yet\n beforeCreate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeCreate(fn: HookFn<T, C>): void;\n beforeCreate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeCreate\", ruleOrHint, maybeFn);\n }\n\n beforeUpdate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeUpdate(fn: HookFn<T, C>): void;\n beforeUpdate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeUpdate\", ruleOrHint, maybeFn);\n }\n\n afterValidation<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n afterValidation(fn: HookFn<T, C>): void;\n afterValidation(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"afterValidation\", ruleOrHint, maybeFn);\n }\n\n beforeCommit(fn: HookFn<T, C>): void {\n this.addHook(\"beforeCommit\", fn);\n }\n\n afterCommit(fn: HookFn<T, C>): void {\n this.addHook(\"afterCommit\", fn);\n }\n\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when the provided hint has changes, not on every flush, like an RF/RR\n * 3. Run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a named reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param name A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(name: string, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param opts Options object containing:\n * - runOnce - If true, the reaction will only run once per flush, not every time the hint changes. Optional.\n * default: false.\n * - name - A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(opts: AddReactionOpts, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n addReaction<H extends ReactiveHint<T>>(\n nameOrOptsOrHint: string | AddReactionOpts | H,\n hintOrFn: H | HookFn<Loaded<T, H>, C>,\n maybeFn?: HookFn<Loaded<T, H>, C>,\n ): void {\n // Keep the name so we can uniquely identify this reaction later and also aid debugging/tracing\n const fn = maybeFn ?? (hintOrFn as HookFn<Loaded<T, H>, C>);\n const hint = (maybeFn ? hintOrFn : nameOrOptsOrHint) as H;\n const opts = maybeFn\n ? typeof nameOrOptsOrHint === \"string\"\n ? { name: nameOrOptsOrHint }\n : (nameOrOptsOrHint as AddReactionOpts)\n : {};\n const { name = getCallerName(), runOnce = false } = opts;\n this.ensurePreBoot(name, \"addReaction\");\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const wrappedFn = (entity: T, ctx: C) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then((loaded) => fn(loaded as Loaded<T, H>, ctx));\n }\n return fn(entity as Loaded<T, H>, ctx);\n };\n this.__data.reactions.push({ name, fn: wrappedFn, hint, runOnce });\n }\n\n /** Adds a synchronous default for `fieldName` to a hard-coded `value`. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // Allow returning undefined to mean \"no default\"\n value: F extends EntityField ? F[\"type\"] : never,\n ): void;\n /** Adds a synchronous default for `fieldName` to the result of simple sync lambda. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // ...this doesn't technically declare what other fields of `entity` we depend on,\n // which ideally we want to drive \"which default to set first?\" precedence decisions.\n fn: (entity: T) => F extends EntityField ? F[\"type\"] | undefined : never,\n ): void;\n /** Adds an asynchronous default for `fieldName` to the result of a hinted lambda. */\n setDefault<K extends Settable<T>, const H extends ReactiveHint<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // We use a ReactiveHint so that we get field-level dependencies that means someday\n // we could drive \"which default to set first?\" precedence decisions.\n hint: H,\n fn: (\n entity: Reacted<T, H>,\n ctx: C,\n ) => F extends { kind: \"m2o\"; type: infer T extends Entity }\n ? MaybePromise<T | Reacted<T, {}> | undefined>\n : F extends EntityField\n ? MaybePromise<F[\"type\"] | undefined>\n : never,\n ): void;\n setDefault<K extends keyof SettableFields<FieldsOf<T>> & string>(fieldName: K, hintOrFnOrValue: any, fn?: any): void {\n this.ensurePreBoot(getCallerName(), \"setDefault\");\n if (fn) {\n // If we're called once by the codegen, and again by the user, override the syncDefault\n delete this.__data.syncDefaults[fieldName];\n this.__data.asyncDefaults[fieldName] = new AsyncDefault(fieldName, hintOrFnOrValue, fn);\n } else {\n this.__data.syncDefaults[fieldName] = hintOrFnOrValue;\n }\n }\n\n /**\n * A noop method that exists solely to keep the `config.placeholder()` line in the initial entity file,\n * until the user is ready to use it. */\n placeholder(): void {}\n\n private ensurePreBoot(name: string, op: string): void {\n if (booted) {\n // Detect if this is NextJS and we're in a hot-reload situation\n if (isRunningInNextJs()) {\n // Reset our bag of config data to collect only the new rules/hooks\n this.__data = new ConfigData();\n resetBootFlag();\n } else {\n throw new Error(\n `config.${op} call on ${name} must only be called on boot, before calling \\`configureMetadata\\`.`,\n );\n }\n }\n }\n}\n\nfunction isRunningInNextJs(): boolean {\n return \"__NEXT_HTTP_AGENT\" in globalThis || \"__NEXT_HTTPS_AGENT\" in globalThis;\n}\n\n/**\n * Allows projects to manually reset the internal `booted` flag.\n *\n * This is only necessary if they're using hot-reloading and reloading their entity files\n * without restarting the server.\n *\n * Generally most tools like tsx or ts-node-dev reload the whole process, so don't need\n * to call this, but if Joist is used in a framework that does actual-hot-reloading, this\n * it will be necessary.\n */\nexport function resetBootFlag(): void {\n booted = false;\n afterMetadataLocked = false;\n resetConstructorMap();\n}\n\n/**\n * Stores a path back to a reactive rule.\n *\n * I.e. if `Book` has a `ruleFn` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveRule` with fields `[\"title\"]`, path `books`, and rule `ruleFn`.\n */\nexport interface ReactiveRule {\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream rule's eval. */\n fields: string[];\n /** The constructor of downstream entity that owns the reactive rule. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reactive rule. */\n name: string;\n /** The path from this source entity to the downstream entity that needs evaled. */\n path: string[];\n /** The downstream validation rule to eval. */\n fn: ValidationRule<any>;\n}\n\n/**\n * Stores a path back to a reactive derived field.\n *\n * I.e. if `Book.displayName` is an `asyncField` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveFields` with fields `[\"title\"]`, path `books`, and name `displayName`.\n */\nexport interface ReactiveField {\n kind: \"populate\" | \"query\";\n /** The fields on this source entity that would trigger the downstream field's recalc. */\n fields: string[];\n /** Read-only/immutable fields that are used in the derived field. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the derived field. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The path from this source entity to the downstream entity that needs recalced. */\n path: string[];\n /** The name of the reactive field in the downstream entity to recalc. */\n name: string;\n runOnce: false;\n}\n\nexport interface Reaction {\n kind: \"reaction\";\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream hook's eval. */\n fields: string[];\n /** Read-only/immutable fields that are used in the function. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the reaction. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reaction. */\n name: string;\n /** The path from this source entity to the downstream entity for this reaction. */\n path: string[];\n /** The downstream reaction function. */\n fn: HookFn<any, any>;\n /** If true, the reaction should only run once per flush, not every time the hint changes. */\n runOnce: boolean;\n}\n\nexport type Reactable = ReactiveField | Reaction;\n\ninterface ReactionInternal<T extends Entity, H extends ReactiveHint<T>, C> {\n name: string;\n fn: HookFn<T, C>;\n hint: H;\n runOnce: boolean;\n}\n\ntype AfterMetadataCallback<T extends Entity> = (meta: EntityMetadata<T>) => void;\n\n/** The internal state of an entity's configuration data, i.e. validation rules/hooks. */\nexport class ConfigData<T extends Entity, C> {\n afterMetadataCallbacks: AfterMetadataCallback<T>[] = [];\n runHooksBefore: EntityConstructor<any>[] = [];\n /** The validation rules for this entity type. */\n rules: ValidationRuleInternal<T>[] = [];\n /** The \"commit rules\" for this entity type, i.e. validation rules that run post-flush/pre-commit. */\n commitRules: ValidationRuleInternal<T>[] = [];\n /** The reactions for this entity type. */\n reactions: ReactionInternal<T, any, C>[] = [];\n /** The hooks for this entity type. */\n hooks: Record<EntityHook, HookFn<T, C>[]> = {\n beforeDelete: [],\n beforeFlush: [],\n beforeCreate: [],\n beforeUpdate: [],\n afterValidation: [],\n beforeCommit: [],\n afterCommit: [],\n };\n /** Synchronous defaults for this entity type, invoked on `em.create`. */\n syncDefaults: Record<string, ((entity: T) => void) | unknown> = {};\n /** Asynchronous defaults for this entity type, invoked on `em.flush`. */\n asyncDefaults: Record<string, AsyncDefault<T>> = {};\n\n // An array of the reactive rules that depend on this entity\n reactiveRules: ReactiveRule[] = [];\n // An array of the reactive *commit* rules (post-flush/pre-commit) that depend on this entity\n reactiveCommitRules: ReactiveRule[] = [];\n // An array of the reactive fields and reactions that depend on this entity\n reactables: Reactable[] = [];\n cascadeDeleteFields: Array<keyof RelationsIn<T>> = [];\n touchOnChange: Set<keyof RelationsIn<T>> = new Set();\n // Constantly converting reactive hints to load hints is expense, so cache them here\n cachedReactiveLoadHints: Record<string, any> = {};\n /** Maps recursive collection fieldNames to custom cycle error message functions. */\n cycleMessages: Record<string, (entity: T, cyclePath: Entity[]) => string> = {};\n}\n\nexport function getCallerName(extraFrames: number = 0): string {\n const err = getStack();\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n // (Make sure to drop lines that don't start with 'at' b/c the stack format can differ\n // slightly i.e. if running via tsx/using a node loader (probably?)\n const line = err.stack!.split(\"\\n\").filter((line) => line.includes(\" at \"))[3 + extraFrames];\n const parts = line.split(\"/\");\n // Get the last part, which will be the file name, i.e. Activity.ts:86:8\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\");\n}\n\nexport function getFuzzyCallerName(): string {\n const err = getStack();\n const lines = err\n .stack!.split(\"\\n\")\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n .filter((line) => line.includes(\" at \"));\n const line = findUserCodeLine(lines);\n return getFilePath(line);\n}\n\n/**\n * Given a callstack line return the file name.\n *\n * I.e. `at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)`\n */\nexport function getFilePath(line: string): string {\n const parts = line.split(\"/\");\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\"); // Drop the `:8)`at the end\n}\n\n/** Given a callstack, uses heuristics to find the first line that looks like user code. */\nexport function findUserCodeLine(lines: string[]): string {\n return (\n lines.find((line) => {\n // When running Joist's own integration tests, if we ignore `/joist-orm/`, we'll end up ignoring\n // everything because `joist-orm` is the name of the repository/working copy itself.\n const withinWorkingCopyJoist = line.includes(\"/packages/orm/\") || line.includes(\"/packages/core\");\n // But once we're not in a working copy, assume any `/joist-orm/` in the path === internal orm stack frames\n const withinProductionJoist =\n !withinWorkingCopyJoist && (line.includes(\"/joist-orm/src/\") || line.includes(\"/joist-core/src\"));\n const nodeInternals = line.includes(\"node:internal/\") || line.includes(\"Promise.all\");\n const isUserCode = !withinWorkingCopyJoist && !withinProductionJoist && !nodeInternals;\n // const isRecalc = line.includes(\".recalcPending\");\n const isDefault = line.includes(\"/defaults.ts\");\n // Batched calls like findOrCreate won't have a stack trace back to the true caller, so just stop there\n const isDataloader = line.includes(\"/dataloaders/\");\n // If this is the `newTestInstance` call\n // What about:\n // recalcSynchronousDerivedFields\n return (\n (isDefault || isDataloader || isUserCode) &&\n !line.includes(\"Codegen.ts\") &&\n // I wanted to exclude this, but it was actually our utils/entities.ts maybeSetDefault helper method\n // !line.includes(\"entities.ts\") &&\n // Ignore loops in joist code like setOpts\n !line.includes(\"at Array\") &&\n // Ignore the Author.ts constructor calling setOpts\n !line.includes(\"at new \")\n );\n }) ?? fail(\"Could not find caller name\")\n );\n}\n\nconst getStack: () => { stack?: string } = \"captureStackTrace\" in Error ? getStackFromCapture : getStackFromObject;\n\nfunction getStackFromCapture(): { stack?: string } {\n const obj = {};\n Error.captureStackTrace(obj);\n return obj as any;\n}\n\nfunction getStackFromObject(): { stack?: string } {\n try {\n throw Error(\"\");\n } catch (err) {\n return err as Error;\n }\n}\n\n/** Pushes a validation rule (either the raw or hinted/reactive form) onto `rules`, shared by `addRule`/`addCommitRule`. */\nfunction pushValidationRule<T extends Entity>(\n rules: ValidationRuleInternal<T>[],\n name: string,\n ruleOrHint: ValidationRule<T> | any,\n maybeRule: ValidationRule<any> | undefined,\n): void {\n if (typeof ruleOrHint === \"function\") {\n rules.push({ name, fn: ruleOrHint, hint: undefined });\n } else {\n const hint = ruleOrHint;\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const fn = (entity: T) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then(maybeRule!);\n }\n return maybeRule!(entity);\n };\n rules.push({ name, fn, hint });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { resetConstructorMap } from \"./configure.ts\";\nimport { AsyncDefault } from \"./defaults.ts\";\nimport { type Entity } from \"./Entity.ts\";\nimport {\n type EntityConstructor,\n type EntityField,\n type EntityMetadata,\n type FieldsOf,\n type LoadHint,\n type Loaded,\n type MaybeAbstractEntityConstructor,\n type Reacted,\n type ReactiveHint,\n type RelationsIn,\n type SettableFields,\n fail,\n getMetadata,\n} from \"./index.ts\";\nimport { convertToLoadHint } from \"./reactivity/reactiveHints.ts\";\nimport { type ValidationRule, type ValidationRuleInternal } from \"./rules.ts\";\nimport { type MaybePromise } from \"./utils.ts\";\n\nexport type EntityHook =\n | \"beforeFlush\"\n | \"beforeCreate\"\n | \"beforeUpdate\"\n | \"beforeDelete\"\n | \"afterValidation\"\n | \"beforeCommit\"\n | \"afterCommit\";\ntype HookFn<T extends Entity, C> = (entity: T, ctx: C) => MaybePromise<unknown>;\n\ntype AddReactionOpts = { runOnce?: boolean; name?: string };\n\nexport const constraintNameToValidationError: Record<string, string> = {};\n\ntype Settable<T extends Entity> = keyof SettableFields<FieldsOf<T>> & string;\n\nlet booted = false;\nlet afterMetadataLocked = false;\n\n/**\n * Called at the end of `configureMetadata` to indicate the boot process is complete.\n *\n * We use this flag to prevent users from mistakenly calling `config` methods after the boot\n * process is complete, i.e. from hooks or other post-boot invocations, where the calls are\n * not guaranteed to work because Joist's reactivity graph has already been initialized.\n */\nexport function setBooted(): void {\n booted = true;\n}\n\nexport function setAfterMetadataLocked(): void {\n afterMetadataLocked = true;\n}\n\n/** The public API to configure an Entity's hooks & validation rules. */\nexport class ConfigApi<T extends Entity, C> {\n __data = new ConfigData<T, C>();\n\n /**\n * Maps a given `constraintName`, i.e. `authors_publisher_id_unique_index`, to a hard-coded-but-pretty validation errors.\n *\n * Note that the validationError must be hard-coded b/c we cannot tease out \"which entity\" caused a given\n * constraint failure from the SQL database. If you really need pretty-and-custom validation errors, you'll\n * need to check the constraint by hand ahead of time, before calling `em.flush`.\n */\n addConstraintMessage(constraintName: string, validationError: string) {\n this.ensurePreBoot(getCallerName(), \"addConstraintMessage\");\n constraintNameToValidationError[constraintName] = validationError;\n }\n\n /**\n * Maps a `RecursiveCycleError` on the given recursive collection field to a custom validation error.\n *\n * When a cycle is detected in a recursive collection (e.g. `parentsRecursive`), instead of\n * throwing a raw `RecursiveCycleError`, `em.flush` will convert it into a `ValidationErrors`\n * using the provided message function.\n *\n * The `messageFn` receives the entity that detected the cycle and the cycle path (array of\n * entities forming the cycle), and should return a validation error string.\n */\n addCycleRule(fieldName: string & keyof T, messageFn: (entity: T, cyclePath: Entity[]) => string) {\n this.ensurePreBoot(getCallerName(), \"addCycleRule\");\n this.__data.cycleMessages[fieldName] = messageFn;\n // Register a reactive rule that loads the recursive collection and catches cycle errors.\n // The rule uses the recursive collection's underlying relation as a reactive hint so it\n // triggers when the collection changes.\n const hint = fieldName as any;\n const fn = () => {\n // We don't actually need a try/catch/anything here, b/c if there is a cycle, it\n // will cause a CycleError during followReverseHint, and never even get to calling this fn.\n };\n this.__data.rules.push({ name: `addCycleRule(${getCallerName()})`, fn, hint });\n }\n\n /**\n * Adds a validation rule for this entity.\n *\n * If `hint` is passed, then the rule's lambda will be: 1) passed a view of the entity with only\n * the fields included in `hint` marked as accessible, and 2) the rule will be called reactively any\n * time any field in the `hint` changes.\n *\n * If lambdas want to access fields w/o having them marked for reactivity, the rule can either\n * include the field as readonly with a `:ro` suffix, i.e. `firstName:ro`, or the lambda can\n * access the `reacted.entity` property to get a full view of the entity's fields and methods.\n */\n addRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addRule(rule: ValidationRule<T>): void;\n addRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addRule\");\n pushValidationRule(this.__data.rules, name, ruleOrHint, maybeRule);\n }\n\n /**\n * Adds a \"commit rule\" that runs like a regular validation rule (see {@link addRule}), but *after*\n * the entity's `INSERT`/`UPDATE`/`DELETE` has been flushed to the database and *before* the\n * transaction `COMMIT`s, i.e. still within the transaction.\n *\n * The point of running this late is that any `em.find`s the rule makes will query the _changed_\n * state (the just-flushed rows), instead of the pre-flush state that regular `addRule` rules see.\n * This is useful for cross-row invariants that are easiest to express as a query, e.g. \"at most\n * one active `AuthorSchedule` per author\", where the rule wants to `em.find` the sibling rows\n * that were themselves just inserted/updated in this same flush.\n *\n * Like `addRule`, if a `hint` is passed the rule is reactive: it runs whenever any field in the\n * hint changes (walking back to the owning entity), and the lambda gets a `Reacted` view.\n */\n addCommitRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addCommitRule(rule: ValidationRule<T>): void;\n addCommitRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addCommitRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addCommitRule\");\n pushValidationRule(this.__data.commitRules, name, ruleOrHint, maybeRule);\n }\n\n /** If both this entity, and `cstr` entities, are in the same `em.flush`, run us first. */\n runHooksBefore(cstr: EntityConstructor<any>): void {\n this.__data.runHooksBefore.push(cstr);\n }\n\n /** Deletes any entity/entities pointed to by `relation` when this entity is deleted. */\n cascadeDelete(relation: keyof RelationsIn<T> & LoadHint<T>): void {\n this.ensurePreBoot(getCallerName(), \"cascadeDelete\");\n this.__data.cascadeDeleteFields.push(relation);\n }\n\n touchOnChange(relation: keyof RelationsIn<T>): void {\n this.ensurePreBoot(getCallerName(), \"touchOnChange\");\n this.__data.touchOnChange.add(relation);\n }\n\n private addHook(hook: EntityHook, ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>) {\n this.ensurePreBoot(getCallerName(), \"addHook\");\n if (typeof ruleOrHint === \"function\") {\n this.__data.hooks[hook].push(ruleOrHint);\n } else {\n const fn = async (entity: T, ctx: C) => {\n // TODO Use this for reactive beforeFlush\n const loaded = await entity.em.populate(entity, ruleOrHint);\n return maybeFn!(loaded, ctx);\n };\n // Squirrel our hint away where configureMetadata can find it\n (fn as any).hint = ruleOrHint;\n this.__data.hooks[hook].push(fn);\n }\n }\n\n afterMetadata(fn: AfterMetadataCallback<T>): void {\n if (afterMetadataLocked) {\n throw new Error(\n `config.afterMetadata on ${getCallerName()} must only be called on boot, before calling \\`configureMetadata\\` and not from other \\`afterMetadata\\` hooks.`,\n );\n }\n this.__data.afterMetadataCallbacks.push(fn);\n }\n\n beforeDelete<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeDelete(fn: HookFn<T, C>): void;\n beforeDelete(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeDelete\", ruleOrHint, maybeFn);\n }\n\n beforeFlush<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeFlush(fn: HookFn<T, C>): void;\n beforeFlush(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeFlush\", ruleOrHint, maybeFn);\n }\n\n // beforeCreate still needs to take a hint because even though the entity itself is New<T>, we might want to load\n // a nested relation that isn't loaded yet\n beforeCreate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeCreate(fn: HookFn<T, C>): void;\n beforeCreate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeCreate\", ruleOrHint, maybeFn);\n }\n\n beforeUpdate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeUpdate(fn: HookFn<T, C>): void;\n beforeUpdate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeUpdate\", ruleOrHint, maybeFn);\n }\n\n afterValidation<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n afterValidation(fn: HookFn<T, C>): void;\n afterValidation(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"afterValidation\", ruleOrHint, maybeFn);\n }\n\n beforeCommit(fn: HookFn<T, C>): void {\n this.addHook(\"beforeCommit\", fn);\n }\n\n afterCommit(fn: HookFn<T, C>): void {\n this.addHook(\"afterCommit\", fn);\n }\n\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when the provided hint has changes, not on every flush, like an RF/RR\n * 3. Run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a named reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param name A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(name: string, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param opts Options object containing:\n * - runOnce - If true, the reaction will only run once per flush, not every time the hint changes. Optional.\n * default: false.\n * - name - A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(opts: AddReactionOpts, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n addReaction<H extends ReactiveHint<T>>(\n nameOrOptsOrHint: string | AddReactionOpts | H,\n hintOrFn: H | HookFn<Loaded<T, H>, C>,\n maybeFn?: HookFn<Loaded<T, H>, C>,\n ): void {\n // Keep the name so we can uniquely identify this reaction later and also aid debugging/tracing\n const fn = maybeFn ?? (hintOrFn as HookFn<Loaded<T, H>, C>);\n const hint = (maybeFn ? hintOrFn : nameOrOptsOrHint) as H;\n const opts = maybeFn\n ? typeof nameOrOptsOrHint === \"string\"\n ? { name: nameOrOptsOrHint }\n : (nameOrOptsOrHint as AddReactionOpts)\n : {};\n const { name = getCallerName(), runOnce = false } = opts;\n this.ensurePreBoot(name, \"addReaction\");\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const wrappedFn = (entity: T, ctx: C) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then((loaded) => fn(loaded as Loaded<T, H>, ctx));\n }\n return fn(entity as Loaded<T, H>, ctx);\n };\n this.__data.reactions.push({ name, fn: wrappedFn, hint, runOnce });\n }\n\n /** Adds a synchronous default for `fieldName` to a hard-coded `value`. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // Allow returning undefined to mean \"no default\"\n value: F extends EntityField ? F[\"type\"] : never,\n ): void;\n /** Adds a synchronous default for `fieldName` to the result of simple sync lambda. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // ...this doesn't technically declare what other fields of `entity` we depend on,\n // which ideally we want to drive \"which default to set first?\" precedence decisions.\n fn: (entity: T) => F extends EntityField ? F[\"type\"] | undefined : never,\n ): void;\n /** Adds an asynchronous default for `fieldName` to the result of a hinted lambda. */\n setDefault<K extends Settable<T>, const H extends ReactiveHint<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // We use a ReactiveHint so that we get field-level dependencies that means someday\n // we could drive \"which default to set first?\" precedence decisions.\n hint: H,\n fn: (\n entity: Reacted<T, H>,\n ctx: C,\n ) => F extends { kind: \"m2o\"; type: infer T extends Entity }\n ? MaybePromise<T | Reacted<T, {}> | undefined>\n : F extends EntityField\n ? MaybePromise<F[\"type\"] | undefined>\n : never,\n ): void;\n setDefault<K extends keyof SettableFields<FieldsOf<T>> & string>(fieldName: K, hintOrFnOrValue: any, fn?: any): void {\n this.ensurePreBoot(getCallerName(), \"setDefault\");\n if (fn) {\n // If we're called once by the codegen, and again by the user, override the syncDefault\n delete this.__data.syncDefaults[fieldName];\n this.__data.asyncDefaults[fieldName] = new AsyncDefault(fieldName, hintOrFnOrValue, fn);\n } else {\n this.__data.syncDefaults[fieldName] = hintOrFnOrValue;\n }\n }\n\n /**\n * A noop method that exists solely to keep the `config.placeholder()` line in the initial entity file,\n * until the user is ready to use it. */\n placeholder(): void {}\n\n private ensurePreBoot(name: string, op: string): void {\n if (booted) {\n // Detect if this is NextJS and we're in a hot-reload situation\n if (isRunningInNextJs()) {\n // Reset our bag of config data to collect only the new rules/hooks\n this.__data = new ConfigData();\n resetBootFlag();\n } else {\n throw new Error(\n `config.${op} call on ${name} must only be called on boot, before calling \\`configureMetadata\\`.`,\n );\n }\n }\n }\n}\n\nfunction isRunningInNextJs(): boolean {\n return \"__NEXT_HTTP_AGENT\" in globalThis || \"__NEXT_HTTPS_AGENT\" in globalThis;\n}\n\n/**\n * Allows projects to manually reset the internal `booted` flag.\n *\n * This is only necessary if they're using hot-reloading and reloading their entity files\n * without restarting the server.\n *\n * Generally most tools like tsx or ts-node-dev reload the whole process, so don't need\n * to call this, but if Joist is used in a framework that does actual-hot-reloading, this\n * it will be necessary.\n */\nexport function resetBootFlag(): void {\n booted = false;\n afterMetadataLocked = false;\n resetConstructorMap();\n}\n\n/**\n * Stores a path back to a reactive rule.\n *\n * I.e. if `Book` has a `ruleFn` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveRule` with fields `[\"title\"]`, path `books`, and rule `ruleFn`.\n */\nexport interface ReactiveRule {\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream rule's eval. */\n fields: string[];\n /** The constructor of downstream entity that owns the reactive rule. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reactive rule. */\n name: string;\n /** The path from this source entity to the downstream entity that needs evaled. */\n path: string[];\n /** The downstream validation rule to eval. */\n fn: ValidationRule<any>;\n}\n\n/**\n * Stores a path back to a reactive derived field.\n *\n * I.e. if `Book.displayName` is an `asyncField` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveFields` with fields `[\"title\"]`, path `books`, and name `displayName`.\n */\nexport interface ReactiveField {\n kind: \"populate\" | \"query\";\n /** The fields on this source entity that would trigger the downstream field's recalc. */\n fields: string[];\n /** Read-only/immutable fields that are used in the derived field. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the derived field. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The path from this source entity to the downstream entity that needs recalced. */\n path: string[];\n /** The name of the reactive field in the downstream entity to recalc. */\n name: string;\n runOnce: false;\n}\n\nexport interface Reaction {\n kind: \"reaction\";\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream hook's eval. */\n fields: string[];\n /** Read-only/immutable fields that are used in the function. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the reaction. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reaction. */\n name: string;\n /** The path from this source entity to the downstream entity for this reaction. */\n path: string[];\n /** The downstream reaction function. */\n fn: HookFn<any, any>;\n /** If true, the reaction should only run once per flush, not every time the hint changes. */\n runOnce: boolean;\n}\n\nexport type Reactable = ReactiveField | Reaction;\n\ninterface ReactionInternal<T extends Entity, H extends ReactiveHint<T>, C> {\n name: string;\n fn: HookFn<T, C>;\n hint: H;\n runOnce: boolean;\n}\n\ntype AfterMetadataCallback<T extends Entity> = (meta: EntityMetadata<T>) => void;\n\n/** The internal state of an entity's configuration data, i.e. validation rules/hooks. */\nexport class ConfigData<T extends Entity, C> {\n afterMetadataCallbacks: AfterMetadataCallback<T>[] = [];\n runHooksBefore: EntityConstructor<any>[] = [];\n /** The validation rules for this entity type. */\n rules: ValidationRuleInternal<T>[] = [];\n /** The \"commit rules\" for this entity type, i.e. validation rules that run post-flush/pre-commit. */\n commitRules: ValidationRuleInternal<T>[] = [];\n /** The reactions for this entity type. */\n reactions: ReactionInternal<T, any, C>[] = [];\n /** The hooks for this entity type. */\n hooks: Record<EntityHook, HookFn<T, C>[]> = {\n beforeDelete: [],\n beforeFlush: [],\n beforeCreate: [],\n beforeUpdate: [],\n afterValidation: [],\n beforeCommit: [],\n afterCommit: [],\n };\n /** Synchronous defaults for this entity type, invoked on `em.create`. */\n syncDefaults: Record<string, ((entity: T) => void) | unknown> = {};\n /** Asynchronous defaults for this entity type, invoked on `em.flush`. */\n asyncDefaults: Record<string, AsyncDefault<T>> = {};\n\n // An array of the reactive rules that depend on this entity\n reactiveRules: ReactiveRule[] = [];\n // An array of the reactive *commit* rules (post-flush/pre-commit) that depend on this entity\n reactiveCommitRules: ReactiveRule[] = [];\n // An array of the reactive fields and reactions that depend on this entity\n reactables: Reactable[] = [];\n cascadeDeleteFields: Array<keyof RelationsIn<T>> = [];\n touchOnChange: Set<keyof RelationsIn<T>> = new Set();\n // Constantly converting reactive hints to load hints is expense, so cache them here\n cachedReactiveLoadHints: Record<string, any> = {};\n /** Maps recursive collection fieldNames to custom cycle error message functions. */\n cycleMessages: Record<string, (entity: T, cyclePath: Entity[]) => string> = {};\n}\n\nexport function getCallerName(extraFrames: number = 0): string {\n const err = getStack();\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n // (Make sure to drop lines that don't start with 'at' b/c the stack format can differ\n // slightly i.e. if running via tsx/using a node loader (probably?)\n const line = err.stack!.split(\"\\n\").filter((line) => line.includes(\" at \"))[3 + extraFrames];\n const parts = line.split(\"/\");\n // Get the last part, which will be the file name, i.e. Activity.ts:86:8\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\");\n}\n\nexport function getFuzzyCallerName(): string {\n const err = getStack();\n const lines = err\n .stack!.split(\"\\n\")\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n .filter((line) => line.includes(\" at \"));\n const line = findUserCodeLine(lines);\n return getFilePath(line);\n}\n\n/**\n * Given a callstack line return the file name.\n *\n * I.e. `at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)`\n */\nexport function getFilePath(line: string): string {\n const parts = line.split(\"/\");\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\"); // Drop the `:8)`at the end\n}\n\n/** Given a callstack, uses heuristics to find the first line that looks like user code. */\nexport function findUserCodeLine(lines: string[]): string {\n return (\n lines.find((line) => {\n // When running Joist's own integration tests, if we ignore `/joist-orm/`, we'll end up ignoring\n // everything because `joist-orm` is the name of the repository/working copy itself.\n const withinWorkingCopyJoist = line.includes(\"/packages/orm/\") || line.includes(\"/packages/core\");\n // But once we're not in a working copy, assume any `/joist-orm/` in the path === internal orm stack frames\n const withinProductionJoist =\n !withinWorkingCopyJoist && (line.includes(\"/joist-orm/src/\") || line.includes(\"/joist-core/src\"));\n const nodeInternals = line.includes(\"node:internal/\") || line.includes(\"Promise.all\");\n const isUserCode = !withinWorkingCopyJoist && !withinProductionJoist && !nodeInternals;\n // const isRecalc = line.includes(\".recalcPending\");\n const isDefault = line.includes(\"/defaults.ts\");\n // Batched calls like findOrCreate won't have a stack trace back to the true caller, so just stop there\n const isDataloader = line.includes(\"/dataloaders/\");\n // If this is the `newTestInstance` call\n // What about:\n // recalcSynchronousDerivedFields\n return (\n (isDefault || isDataloader || isUserCode) &&\n !line.includes(\"Codegen.ts\") &&\n // I wanted to exclude this, but it was actually our utils/entities.ts maybeSetDefault helper method\n // !line.includes(\"entities.ts\") &&\n // Ignore loops in joist code like setOpts\n !line.includes(\"at Array\") &&\n // Ignore the Author.ts constructor calling setOpts\n !line.includes(\"at new \")\n );\n }) ?? fail(\"Could not find caller name\")\n );\n}\n\nconst getStack: () => { stack?: string } = \"captureStackTrace\" in Error ? getStackFromCapture : getStackFromObject;\n\nfunction getStackFromCapture(): { stack?: string } {\n const obj = {};\n Error.captureStackTrace(obj);\n return obj as any;\n}\n\nfunction getStackFromObject(): { stack?: string } {\n try {\n throw Error(\"\");\n } catch (err) {\n return err as Error;\n }\n}\n\n/** Pushes a validation rule (either the raw or hinted/reactive form) onto `rules`, shared by `addRule`/`addCommitRule`. */\nfunction pushValidationRule<T extends Entity>(\n rules: ValidationRuleInternal<T>[],\n name: string,\n ruleOrHint: ValidationRule<T> | any,\n maybeRule: ValidationRule<any> | undefined,\n): void {\n if (typeof ruleOrHint === \"function\") {\n rules.push({ name, fn: ruleOrHint, hint: undefined });\n } else {\n const hint = ruleOrHint;\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const fn = (entity: T) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then(maybeRule!);\n }\n return maybeRule!(entity);\n };\n rules.push({ name, fn, hint });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { resetConstructorMap } from \"src/configure.ts\";\nimport { AsyncDefault } from \"src/defaults.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport {\n type EntityConstructor,\n type EntityField,\n type EntityMetadata,\n type FieldsOf,\n type LoadHint,\n type Loaded,\n type MaybeAbstractEntityConstructor,\n type Reacted,\n type ReactiveHint,\n type RelationsIn,\n type SettableFields,\n fail,\n getMetadata,\n} from \"src/index.ts\";\nimport { convertToLoadHint } from \"src/reactivity/reactiveHints.ts\";\nimport { type ValidationRule, type ValidationRuleInternal } from \"src/rules.ts\";\nimport { type MaybePromise } from \"src/utils.ts\";\n\nexport type EntityHook =\n | \"beforeFlush\"\n | \"beforeCreate\"\n | \"beforeUpdate\"\n | \"beforeDelete\"\n | \"afterValidation\"\n | \"beforeCommit\"\n | \"afterCommit\";\ntype HookFn<T extends Entity, C> = (entity: T, ctx: C) => MaybePromise<unknown>;\n\ntype AddReactionOpts = { runOnce?: boolean; name?: string };\n\nexport const constraintNameToValidationError: Record<string, string> = {};\n\ntype Settable<T extends Entity> = keyof SettableFields<FieldsOf<T>> & string;\n\nlet booted = false;\nlet afterMetadataLocked = false;\n\n/**\n * Called at the end of `configureMetadata` to indicate the boot process is complete.\n *\n * We use this flag to prevent users from mistakenly calling `config` methods after the boot\n * process is complete, i.e. from hooks or other post-boot invocations, where the calls are\n * not guaranteed to work because Joist's reactivity graph has already been initialized.\n */\nexport function setBooted(): void {\n booted = true;\n}\n\nexport function setAfterMetadataLocked(): void {\n afterMetadataLocked = true;\n}\n\n/** The public API to configure an Entity's hooks & validation rules. */\nexport class ConfigApi<T extends Entity, C> {\n __data = new ConfigData<T, C>();\n\n /**\n * Maps a given `constraintName`, i.e. `authors_publisher_id_unique_index`, to a hard-coded-but-pretty validation errors.\n *\n * Note that the validationError must be hard-coded b/c we cannot tease out \"which entity\" caused a given\n * constraint failure from the SQL database. If you really need pretty-and-custom validation errors, you'll\n * need to check the constraint by hand ahead of time, before calling `em.flush`.\n */\n addConstraintMessage(constraintName: string, validationError: string) {\n this.ensurePreBoot(getCallerName(), \"addConstraintMessage\");\n constraintNameToValidationError[constraintName] = validationError;\n }\n\n /**\n * Maps a `RecursiveCycleError` on the given recursive collection field to a custom validation error.\n *\n * When a cycle is detected in a recursive collection (e.g. `parentsRecursive`), instead of\n * throwing a raw `RecursiveCycleError`, `em.flush` will convert it into a `ValidationErrors`\n * using the provided message function.\n *\n * The `messageFn` receives the entity that detected the cycle and the cycle path (array of\n * entities forming the cycle), and should return a validation error string.\n */\n addCycleRule(fieldName: string & keyof T, messageFn: (entity: T, cyclePath: Entity[]) => string) {\n this.ensurePreBoot(getCallerName(), \"addCycleRule\");\n this.__data.cycleMessages[fieldName] = messageFn;\n // Register a reactive rule that loads the recursive collection and catches cycle errors.\n // The rule uses the recursive collection's underlying relation as a reactive hint so it\n // triggers when the collection changes.\n const hint = fieldName as any;\n const fn = () => {\n // We don't actually need a try/catch/anything here, b/c if there is a cycle, it\n // will cause a CycleError during followReverseHint, and never even get to calling this fn.\n };\n this.__data.rules.push({ name: `addCycleRule(${getCallerName()})`, fn, hint });\n }\n\n /**\n * Adds a validation rule for this entity.\n *\n * If `hint` is passed, then the rule's lambda will be: 1) passed a view of the entity with only\n * the fields included in `hint` marked as accessible, and 2) the rule will be called reactively any\n * time any field in the `hint` changes.\n *\n * If lambdas want to access fields w/o having them marked for reactivity, the rule can either\n * include the field as readonly with a `:ro` suffix, i.e. `firstName:ro`, or the lambda can\n * access the `reacted.entity` property to get a full view of the entity's fields and methods.\n */\n addRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addRule(rule: ValidationRule<T>): void;\n addRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addRule\");\n pushValidationRule(this.__data.rules, name, ruleOrHint, maybeRule);\n }\n\n /**\n * Adds a \"commit rule\" that runs like a regular validation rule (see {@link addRule}), but *after*\n * the entity's `INSERT`/`UPDATE`/`DELETE` has been flushed to the database and *before* the\n * transaction `COMMIT`s, i.e. still within the transaction.\n *\n * The point of running this late is that any `em.find`s the rule makes will query the _changed_\n * state (the just-flushed rows), instead of the pre-flush state that regular `addRule` rules see.\n * This is useful for cross-row invariants that are easiest to express as a query, e.g. \"at most\n * one active `AuthorSchedule` per author\", where the rule wants to `em.find` the sibling rows\n * that were themselves just inserted/updated in this same flush.\n *\n * Like `addRule`, if a `hint` is passed the rule is reactive: it runs whenever any field in the\n * hint changes (walking back to the owning entity), and the lambda gets a `Reacted` view.\n */\n addCommitRule<H extends ReactiveHint<T>>(hint: H, rule: ValidationRule<Reacted<T, H>>): void;\n addCommitRule(rule: ValidationRule<T>): void;\n addCommitRule(ruleOrHint: ValidationRule<T> | any, maybeRule?: ValidationRule<any>): void {\n // Keep the name for easy debugging/tracing later\n const name = `addCommitRule(${getCallerName()})`;\n this.ensurePreBoot(name, \"addCommitRule\");\n pushValidationRule(this.__data.commitRules, name, ruleOrHint, maybeRule);\n }\n\n /** If both this entity, and `cstr` entities, are in the same `em.flush`, run us first. */\n runHooksBefore(cstr: EntityConstructor<any>): void {\n this.__data.runHooksBefore.push(cstr);\n }\n\n /** Deletes any entity/entities pointed to by `relation` when this entity is deleted. */\n cascadeDelete(relation: keyof RelationsIn<T> & LoadHint<T>): void {\n this.ensurePreBoot(getCallerName(), \"cascadeDelete\");\n this.__data.cascadeDeleteFields.push(relation);\n }\n\n touchOnChange(relation: keyof RelationsIn<T>): void {\n this.ensurePreBoot(getCallerName(), \"touchOnChange\");\n this.__data.touchOnChange.add(relation);\n }\n\n private addHook(hook: EntityHook, ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>) {\n this.ensurePreBoot(getCallerName(), \"addHook\");\n if (typeof ruleOrHint === \"function\") {\n this.__data.hooks[hook].push(ruleOrHint);\n } else {\n const fn = async (entity: T, ctx: C) => {\n // TODO Use this for reactive beforeFlush\n const loaded = await entity.em.populate(entity, ruleOrHint);\n return maybeFn!(loaded, ctx);\n };\n // Squirrel our hint away where configureMetadata can find it\n (fn as any).hint = ruleOrHint;\n this.__data.hooks[hook].push(fn);\n }\n }\n\n afterMetadata(fn: AfterMetadataCallback<T>): void {\n if (afterMetadataLocked) {\n throw new Error(\n `config.afterMetadata on ${getCallerName()} must only be called on boot, before calling \\`configureMetadata\\` and not from other \\`afterMetadata\\` hooks.`,\n );\n }\n this.__data.afterMetadataCallbacks.push(fn);\n }\n\n beforeDelete<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeDelete(fn: HookFn<T, C>): void;\n beforeDelete(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeDelete\", ruleOrHint, maybeFn);\n }\n\n beforeFlush<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeFlush(fn: HookFn<T, C>): void;\n beforeFlush(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeFlush\", ruleOrHint, maybeFn);\n }\n\n // beforeCreate still needs to take a hint because even though the entity itself is New<T>, we might want to load\n // a nested relation that isn't loaded yet\n beforeCreate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeCreate(fn: HookFn<T, C>): void;\n beforeCreate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeCreate\", ruleOrHint, maybeFn);\n }\n\n beforeUpdate<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n beforeUpdate(fn: HookFn<T, C>): void;\n beforeUpdate(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"beforeUpdate\", ruleOrHint, maybeFn);\n }\n\n afterValidation<H extends LoadHint<T>>(populate: H, fn: HookFn<Loaded<T, H>, C>): void;\n afterValidation(fn: HookFn<T, C>): void;\n afterValidation(ruleOrHint: HookFn<T, C> | any, maybeFn?: HookFn<Loaded<T, any>, C>): void {\n this.addHook(\"afterValidation\", ruleOrHint, maybeFn);\n }\n\n beforeCommit(fn: HookFn<T, C>): void {\n this.addHook(\"beforeCommit\", fn);\n }\n\n afterCommit(fn: HookFn<T, C>): void {\n this.addHook(\"afterCommit\", fn);\n }\n\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when the provided hint has changes, not on every flush, like an RF/RR\n * 3. Run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a named reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param name A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(name: string, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n /**\n * Adds a reaction that runs during flush whenever fields in the `hint` change.\n *\n * Reactions are somewhere in between hooks and reactive fields/references:\n * 1. Can make arbitrary changes to any entity like a hook\n * 2. Only run when `hint` has changes, not on every flush, like an RF/RR\n * 3. Can run when the entity itself has no changes, like an RF/RF\n * 4. Can run multiple times per flush, like an RF/RF. Be careful to avoid creating\n * circular dependencies in the hint and to make the function idempotent.\n *\n * @param opts Options object containing:\n * - runOnce - If true, the reaction will only run once per flush, not every time the hint changes. Optional.\n * default: false.\n * - name - A name to identify this reaction for debugging\n * @param hint The fields to watch for changes and load before running the reaction\n * @param fn The reaction function to run\n */\n addReaction<H extends ReactiveHint<T>>(opts: AddReactionOpts, hint: H, fn: HookFn<Loaded<T, H>, C>): void;\n addReaction<H extends ReactiveHint<T>>(\n nameOrOptsOrHint: string | AddReactionOpts | H,\n hintOrFn: H | HookFn<Loaded<T, H>, C>,\n maybeFn?: HookFn<Loaded<T, H>, C>,\n ): void {\n // Keep the name so we can uniquely identify this reaction later and also aid debugging/tracing\n const fn = maybeFn ?? (hintOrFn as HookFn<Loaded<T, H>, C>);\n const hint = (maybeFn ? hintOrFn : nameOrOptsOrHint) as H;\n const opts = maybeFn\n ? typeof nameOrOptsOrHint === \"string\"\n ? { name: nameOrOptsOrHint }\n : (nameOrOptsOrHint as AddReactionOpts)\n : {};\n const { name = getCallerName(), runOnce = false } = opts;\n this.ensurePreBoot(name, \"addReaction\");\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const wrappedFn = (entity: T, ctx: C) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then((loaded) => fn(loaded as Loaded<T, H>, ctx));\n }\n return fn(entity as Loaded<T, H>, ctx);\n };\n this.__data.reactions.push({ name, fn: wrappedFn, hint, runOnce });\n }\n\n /** Adds a synchronous default for `fieldName` to a hard-coded `value`. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // Allow returning undefined to mean \"no default\"\n value: F extends EntityField ? F[\"type\"] : never,\n ): void;\n /** Adds a synchronous default for `fieldName` to the result of simple sync lambda. */\n setDefault<K extends Settable<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // ...this doesn't technically declare what other fields of `entity` we depend on,\n // which ideally we want to drive \"which default to set first?\" precedence decisions.\n fn: (entity: T) => F extends EntityField ? F[\"type\"] | undefined : never,\n ): void;\n /** Adds an asynchronous default for `fieldName` to the result of a hinted lambda. */\n setDefault<K extends Settable<T>, const H extends ReactiveHint<T>, F = FieldsOf<T>[K]>(\n fieldName: K,\n // We use a ReactiveHint so that we get field-level dependencies that means someday\n // we could drive \"which default to set first?\" precedence decisions.\n hint: H,\n fn: (\n entity: Reacted<T, H>,\n ctx: C,\n ) => F extends { kind: \"m2o\"; type: infer T extends Entity }\n ? MaybePromise<T | Reacted<T, {}> | undefined>\n : F extends EntityField\n ? MaybePromise<F[\"type\"] | undefined>\n : never,\n ): void;\n setDefault<K extends keyof SettableFields<FieldsOf<T>> & string>(fieldName: K, hintOrFnOrValue: any, fn?: any): void {\n this.ensurePreBoot(getCallerName(), \"setDefault\");\n if (fn) {\n // If we're called once by the codegen, and again by the user, override the syncDefault\n delete this.__data.syncDefaults[fieldName];\n this.__data.asyncDefaults[fieldName] = new AsyncDefault(fieldName, hintOrFnOrValue, fn);\n } else {\n this.__data.syncDefaults[fieldName] = hintOrFnOrValue;\n }\n }\n\n /**\n * A noop method that exists solely to keep the `config.placeholder()` line in the initial entity file,\n * until the user is ready to use it. */\n placeholder(): void {}\n\n private ensurePreBoot(name: string, op: string): void {\n if (booted) {\n // Detect if this is NextJS and we're in a hot-reload situation\n if (isRunningInNextJs()) {\n // Reset our bag of config data to collect only the new rules/hooks\n this.__data = new ConfigData();\n resetBootFlag();\n } else {\n throw new Error(\n `config.${op} call on ${name} must only be called on boot, before calling \\`configureMetadata\\`.`,\n );\n }\n }\n }\n}\n\nfunction isRunningInNextJs(): boolean {\n return \"__NEXT_HTTP_AGENT\" in globalThis || \"__NEXT_HTTPS_AGENT\" in globalThis;\n}\n\n/**\n * Allows projects to manually reset the internal `booted` flag.\n *\n * This is only necessary if they're using hot-reloading and reloading their entity files\n * without restarting the server.\n *\n * Generally most tools like tsx or ts-node-dev reload the whole process, so don't need\n * to call this, but if Joist is used in a framework that does actual-hot-reloading, this\n * it will be necessary.\n */\nexport function resetBootFlag(): void {\n booted = false;\n afterMetadataLocked = false;\n resetConstructorMap();\n}\n\n/**\n * Stores a path back to a reactive rule.\n *\n * I.e. if `Book` has a `ruleFn` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveRule` with fields `[\"title\"]`, path `books`, and rule `ruleFn`.\n */\nexport interface ReactiveRule {\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream rule's eval. */\n fields: string[];\n /** The constructor of downstream entity that owns the reactive rule. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reactive rule. */\n name: string;\n /** The path from this source entity to the downstream entity that needs evaled. */\n path: string[];\n /** The downstream validation rule to eval. */\n fn: ValidationRule<any>;\n}\n\n/**\n * Stores a path back to a reactive derived field.\n *\n * I.e. if `Book.displayName` is an `asyncField` that reacts to `Author.title`, then `Author`'s config will have\n * a `ReactiveFields` with fields `[\"title\"]`, path `books`, and name `displayName`.\n */\nexport interface ReactiveField {\n kind: \"populate\" | \"query\";\n /** The fields on this source entity that would trigger the downstream field's recalc. */\n fields: string[];\n /** Read-only/immutable fields that are used in the derived field. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the derived field. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The path from this source entity to the downstream entity that needs recalced. */\n path: string[];\n /** The name of the reactive field in the downstream entity to recalc. */\n name: string;\n runOnce: false;\n}\n\nexport interface Reaction {\n kind: \"reaction\";\n /** The source we're reacting to, specifically which base/subtype cstr. */\n source: MaybeAbstractEntityConstructor<any>;\n /** The fields on this source entity that would trigger the downstream hook's eval. */\n fields: string[];\n /** Read-only/immutable fields that are used in the function. */\n isReadOnly: boolean;\n /** The constructor of downstream entity that owns the reaction. */\n cstr: MaybeAbstractEntityConstructor<any>;\n /** The name (source location) of the downstream reaction. */\n name: string;\n /** The path from this source entity to the downstream entity for this reaction. */\n path: string[];\n /** The downstream reaction function. */\n fn: HookFn<any, any>;\n /** If true, the reaction should only run once per flush, not every time the hint changes. */\n runOnce: boolean;\n}\n\nexport type Reactable = ReactiveField | Reaction;\n\ninterface ReactionInternal<T extends Entity, H extends ReactiveHint<T>, C> {\n name: string;\n fn: HookFn<T, C>;\n hint: H;\n runOnce: boolean;\n}\n\ntype AfterMetadataCallback<T extends Entity> = (meta: EntityMetadata<T>) => void;\n\n/** The internal state of an entity's configuration data, i.e. validation rules/hooks. */\nexport class ConfigData<T extends Entity, C> {\n afterMetadataCallbacks: AfterMetadataCallback<T>[] = [];\n runHooksBefore: EntityConstructor<any>[] = [];\n /** The validation rules for this entity type. */\n rules: ValidationRuleInternal<T>[] = [];\n /** The \"commit rules\" for this entity type, i.e. validation rules that run post-flush/pre-commit. */\n commitRules: ValidationRuleInternal<T>[] = [];\n /** The reactions for this entity type. */\n reactions: ReactionInternal<T, any, C>[] = [];\n /** The hooks for this entity type. */\n hooks: Record<EntityHook, HookFn<T, C>[]> = {\n beforeDelete: [],\n beforeFlush: [],\n beforeCreate: [],\n beforeUpdate: [],\n afterValidation: [],\n beforeCommit: [],\n afterCommit: [],\n };\n /** Synchronous defaults for this entity type, invoked on `em.create`. */\n syncDefaults: Record<string, ((entity: T) => void) | unknown> = {};\n /** Asynchronous defaults for this entity type, invoked on `em.flush`. */\n asyncDefaults: Record<string, AsyncDefault<T>> = {};\n\n // An array of the reactive rules that depend on this entity\n reactiveRules: ReactiveRule[] = [];\n // An array of the reactive *commit* rules (post-flush/pre-commit) that depend on this entity\n reactiveCommitRules: ReactiveRule[] = [];\n // An array of the reactive fields and reactions that depend on this entity\n reactables: Reactable[] = [];\n cascadeDeleteFields: Array<keyof RelationsIn<T>> = [];\n touchOnChange: Set<keyof RelationsIn<T>> = new Set();\n // Constantly converting reactive hints to load hints is expense, so cache them here\n cachedReactiveLoadHints: Record<string, any> = {};\n /** Maps recursive collection fieldNames to custom cycle error message functions. */\n cycleMessages: Record<string, (entity: T, cyclePath: Entity[]) => string> = {};\n}\n\nexport function getCallerName(extraFrames: number = 0): string {\n const err = getStack();\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n // (Make sure to drop lines that don't start with 'at' b/c the stack format can differ\n // slightly i.e. if running via tsx/using a node loader (probably?)\n const line = err.stack!.split(\"\\n\").filter((line) => line.includes(\" at \"))[3 + extraFrames];\n const parts = line.split(\"/\");\n // Get the last part, which will be the file name, i.e. Activity.ts:86:8\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\");\n}\n\nexport function getFuzzyCallerName(): string {\n const err = getStack();\n const lines = err\n .stack!.split(\"\\n\")\n // E.g. at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)\n .filter((line) => line.includes(\" at \"));\n const line = findUserCodeLine(lines);\n return getFilePath(line);\n}\n\n/**\n * Given a callstack line return the file name.\n *\n * I.e. `at Object.<anonymous> (/home/stephen/homebound/graphql-service/src/entities/Activity.ts:86:8)`\n */\nexport function getFilePath(line: string): string {\n const parts = line.split(\"/\");\n return parts[parts.length - 1].replace(/:\\d+\\)?$/, \"\"); // Drop the `:8)`at the end\n}\n\n/** Given a callstack, uses heuristics to find the first line that looks like user code. */\nexport function findUserCodeLine(lines: string[]): string {\n return (\n lines.find((line) => {\n // When running Joist's own integration tests, if we ignore `/joist-orm/`, we'll end up ignoring\n // everything because `joist-orm` is the name of the repository/working copy itself.\n const withinWorkingCopyJoist = line.includes(\"/packages/orm/\") || line.includes(\"/packages/core\");\n // But once we're not in a working copy, assume any `/joist-orm/` in the path === internal orm stack frames\n const withinProductionJoist =\n !withinWorkingCopyJoist && (line.includes(\"/joist-orm/src/\") || line.includes(\"/joist-core/src\"));\n const nodeInternals = line.includes(\"node:internal/\") || line.includes(\"Promise.all\");\n const isUserCode = !withinWorkingCopyJoist && !withinProductionJoist && !nodeInternals;\n // const isRecalc = line.includes(\".recalcPending\");\n const isDefault = line.includes(\"/defaults.ts\");\n // Batched calls like findOrCreate won't have a stack trace back to the true caller, so just stop there\n const isDataloader = line.includes(\"/dataloaders/\");\n // If this is the `newTestInstance` call\n // What about:\n // recalcSynchronousDerivedFields\n return (\n (isDefault || isDataloader || isUserCode) &&\n !line.includes(\"Codegen.ts\") &&\n // I wanted to exclude this, but it was actually our utils/entities.ts maybeSetDefault helper method\n // !line.includes(\"entities.ts\") &&\n // Ignore loops in joist code like setOpts\n !line.includes(\"at Array\") &&\n // Ignore the Author.ts constructor calling setOpts\n !line.includes(\"at new \")\n );\n }) ?? fail(\"Could not find caller name\")\n );\n}\n\nconst getStack: () => { stack?: string } = \"captureStackTrace\" in Error ? getStackFromCapture : getStackFromObject;\n\nfunction getStackFromCapture(): { stack?: string } {\n const obj = {};\n Error.captureStackTrace(obj);\n return obj as any;\n}\n\nfunction getStackFromObject(): { stack?: string } {\n try {\n throw Error(\"\");\n } catch (err) {\n return err as Error;\n }\n}\n\n/** Pushes a validation rule (either the raw or hinted/reactive form) onto `rules`, shared by `addRule`/`addCommitRule`. */\nfunction pushValidationRule<T extends Entity>(\n rules: ValidationRuleInternal<T>[],\n name: string,\n ruleOrHint: ValidationRule<T> | any,\n maybeRule: ValidationRule<any> | undefined,\n): void {\n if (typeof ruleOrHint === \"function\") {\n rules.push({ name, fn: ruleOrHint, hint: undefined });\n } else {\n const hint = ruleOrHint;\n // Cache load hints per-meta because CTI subtypes may resolve `hint` to different\n // load hints (e.g. an AsyncProperty overridden in the subtype with subtype-only relations).\n const loadHints = new Map<EntityMetadata, LoadHint<T>>();\n const fn = (entity: T) => {\n const meta = getMetadata(entity);\n let loadHint = loadHints.get(meta);\n if (loadHint === undefined) {\n loadHint = convertToLoadHint<T>(meta, hint);\n loadHints.set(meta, loadHint);\n }\n if (Object.keys(loadHint).length > 0) {\n return entity.em.populate(entity, loadHint).then(maybeRule!);\n }\n return maybeRule!(entity);\n };\n rules.push({ name, fn, hint });\n }\n}\n"],"mappings":"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"}