@rvoh/dream 2.5.7 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/cjs/src/Dream.js +37 -66
  2. package/dist/cjs/src/decorators/class/STI.js +2 -2
  3. package/dist/cjs/src/dream/LeftJoinLoadBuilder.js +16 -10
  4. package/dist/cjs/src/dream/LoadBuilder.js +70 -9
  5. package/dist/cjs/src/dream/Query.js +5 -6
  6. package/dist/cjs/src/dream/constants.js +2 -0
  7. package/dist/cjs/src/dream/internal/buildAssociationPaths.js +33 -0
  8. package/dist/cjs/src/dream/internal/buildDependentDestroyPreloadPaths.js +43 -0
  9. package/dist/cjs/src/dream/internal/buildSerializerPreloadPaths.js +38 -0
  10. package/dist/cjs/src/dream/internal/destroyAssociatedRecords.js +14 -6
  11. package/dist/cjs/src/dream/internal/destroyDream.js +7 -1
  12. package/dist/cjs/src/dream/internal/loadDependentDestroyTree.js +35 -0
  13. package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +53 -0
  14. package/dist/cjs/src/serializer/SerializerRenderer.js +1 -1
  15. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +114 -52
  16. package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +111 -41
  17. package/dist/cjs/src/types/recursiveSerialization.js +1 -0
  18. package/dist/esm/src/Dream.js +37 -66
  19. package/dist/esm/src/decorators/class/STI.js +2 -2
  20. package/dist/esm/src/dream/LeftJoinLoadBuilder.js +16 -10
  21. package/dist/esm/src/dream/LoadBuilder.js +70 -9
  22. package/dist/esm/src/dream/Query.js +5 -6
  23. package/dist/esm/src/dream/constants.js +2 -0
  24. package/dist/esm/src/dream/internal/buildAssociationPaths.js +33 -0
  25. package/dist/esm/src/dream/internal/buildDependentDestroyPreloadPaths.js +43 -0
  26. package/dist/esm/src/dream/internal/buildSerializerPreloadPaths.js +38 -0
  27. package/dist/esm/src/dream/internal/destroyAssociatedRecords.js +14 -6
  28. package/dist/esm/src/dream/internal/destroyDream.js +7 -1
  29. package/dist/esm/src/dream/internal/loadDependentDestroyTree.js +35 -0
  30. package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +53 -0
  31. package/dist/esm/src/serializer/SerializerRenderer.js +1 -1
  32. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +114 -52
  33. package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +111 -41
  34. package/dist/esm/src/types/recursiveSerialization.js +1 -0
  35. package/dist/types/src/Dream.d.ts +25 -10
  36. package/dist/types/src/decorators/Decorators.d.ts +8 -0
  37. package/dist/types/src/decorators/class/STI.d.ts +1 -3
  38. package/dist/types/src/decorators/field/sortable/Sortable.d.ts +9 -0
  39. package/dist/types/src/decorators/field/validation/Validates.d.ts +4 -0
  40. package/dist/types/src/decorators/static-method/Scope.d.ts +4 -0
  41. package/dist/types/src/dream/LeftJoinLoadBuilder.d.ts +16 -10
  42. package/dist/types/src/dream/LoadBuilder.d.ts +63 -10
  43. package/dist/types/src/dream/Query.d.ts +1 -1
  44. package/dist/types/src/dream/constants.d.ts +2 -0
  45. package/dist/types/src/dream/internal/buildAssociationPaths.d.ts +12 -0
  46. package/dist/types/src/dream/internal/buildDependentDestroyPreloadPaths.d.ts +17 -0
  47. package/dist/types/src/dream/internal/buildSerializerPreloadPaths.d.ts +3 -0
  48. package/dist/types/src/dream/internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.d.ts +1 -1
  49. package/dist/types/src/dream/internal/destroyAssociatedRecords.d.ts +5 -1
  50. package/dist/types/src/dream/internal/loadDependentDestroyTree.d.ts +17 -0
  51. package/dist/types/src/dream/internal/resolveSerializerAssociationEdges.d.ts +13 -0
  52. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +164 -63
  53. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +126 -43
  54. package/dist/types/src/types/associations/belongsTo.d.ts +34 -0
  55. package/dist/types/src/types/associations/belongsTo.ts +41 -0
  56. package/dist/types/src/types/associations/hasMany.d.ts +18 -0
  57. package/dist/types/src/types/associations/hasMany.ts +18 -0
  58. package/dist/types/src/types/associations/shared.d.ts +71 -0
  59. package/dist/types/src/types/associations/shared.ts +74 -0
  60. package/dist/types/src/types/dream.d.ts +16 -0
  61. package/dist/types/src/types/dream.ts +18 -0
  62. package/dist/types/src/types/lifecycle.d.ts +18 -0
  63. package/dist/types/src/types/lifecycle.ts +18 -0
  64. package/dist/types/src/types/query.d.ts +3 -0
  65. package/dist/types/src/types/query.ts +3 -0
  66. package/dist/types/src/types/recursiveSerialization.d.ts +8 -0
  67. package/dist/types/src/types/recursiveSerialization.ts +10 -0
  68. package/dist/types/src/types/serializer.d.ts +8 -1
  69. package/dist/types/src/types/serializer.ts +8 -1
  70. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  71. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  72. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  73. package/docs/classes/db.QueryDriverBase.html +31 -31
  74. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  75. package/docs/classes/errors.ColumnOverflow.html +3 -3
  76. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  77. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  78. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  79. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  80. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  81. package/docs/classes/errors.InvalidClockTime.html +2 -2
  82. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  83. package/docs/classes/errors.InvalidDateTime.html +2 -2
  84. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  85. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  86. package/docs/classes/errors.NotNullViolation.html +3 -3
  87. package/docs/classes/errors.RecordNotFound.html +3 -3
  88. package/docs/classes/errors.ValidationError.html +3 -3
  89. package/docs/classes/index.CalendarDate.html +33 -33
  90. package/docs/classes/index.ClockTime.html +32 -32
  91. package/docs/classes/index.ClockTimeTz.html +35 -35
  92. package/docs/classes/index.DateTime.html +86 -86
  93. package/docs/classes/index.Decorators.html +21 -20
  94. package/docs/classes/index.Dream.html +133 -126
  95. package/docs/classes/index.DreamApp.html +5 -5
  96. package/docs/classes/index.DreamTransaction.html +2 -2
  97. package/docs/classes/index.Env.html +2 -2
  98. package/docs/classes/index.Query.html +57 -57
  99. package/docs/classes/system.CliFileWriter.html +4 -4
  100. package/docs/classes/system.DreamBin.html +2 -2
  101. package/docs/classes/system.DreamCLI.html +6 -6
  102. package/docs/classes/system.DreamImporter.html +2 -2
  103. package/docs/classes/system.DreamLogos.html +2 -2
  104. package/docs/classes/system.DreamSerializerBuilder.html +177 -59
  105. package/docs/classes/system.ObjectSerializerBuilder.html +110 -34
  106. package/docs/classes/system.PathHelpers.html +3 -3
  107. package/docs/classes/utils.Encrypt.html +2 -2
  108. package/docs/classes/utils.Range.html +2 -2
  109. package/docs/functions/db.closeAllDbConnections.html +1 -1
  110. package/docs/functions/db.dreamDbConnections.html +1 -1
  111. package/docs/functions/db.untypedDb.html +1 -1
  112. package/docs/functions/db.validateColumn.html +1 -1
  113. package/docs/functions/db.validateTable.html +1 -1
  114. package/docs/functions/errors.pgErrorType.html +1 -1
  115. package/docs/functions/index.DreamSerializer.html +1 -1
  116. package/docs/functions/index.ObjectSerializer.html +1 -1
  117. package/docs/functions/index.ReplicaSafe.html +1 -1
  118. package/docs/functions/index.STI.html +1 -1
  119. package/docs/functions/index.SoftDelete.html +1 -1
  120. package/docs/functions/utils.camelize.html +1 -1
  121. package/docs/functions/utils.capitalize.html +1 -1
  122. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  123. package/docs/functions/utils.compact.html +1 -1
  124. package/docs/functions/utils.groupBy.html +1 -1
  125. package/docs/functions/utils.hyphenize.html +1 -1
  126. package/docs/functions/utils.intersection.html +1 -1
  127. package/docs/functions/utils.isEmpty.html +1 -1
  128. package/docs/functions/utils.normalizeUnicode.html +1 -1
  129. package/docs/functions/utils.pascalize.html +1 -1
  130. package/docs/functions/utils.percent.html +1 -1
  131. package/docs/functions/utils.range.html +1 -1
  132. package/docs/functions/utils.round.html +1 -1
  133. package/docs/functions/utils.sanitizeString.html +1 -1
  134. package/docs/functions/utils.snakeify.html +1 -1
  135. package/docs/functions/utils.sort.html +1 -1
  136. package/docs/functions/utils.sortBy.html +1 -1
  137. package/docs/functions/utils.sortObjectByKey.html +1 -1
  138. package/docs/functions/utils.sortObjectByValue.html +1 -1
  139. package/docs/functions/utils.uncapitalize.html +1 -1
  140. package/docs/functions/utils.uniq.html +1 -1
  141. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  142. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  143. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  144. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  145. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  146. package/docs/interfaces/types.DecoratorContext.html +2 -2
  147. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  148. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  149. package/docs/interfaces/types.DurationObject.html +2 -2
  150. package/docs/interfaces/types.EncryptOptions.html +2 -2
  151. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  152. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  153. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  154. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  155. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  156. package/docs/types/openapi.OpenapiFormats.html +1 -1
  157. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  158. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  159. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  165. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  166. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  167. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  168. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  169. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  170. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  171. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  172. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  173. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  174. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  175. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  176. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  177. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  178. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  179. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  180. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  181. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  182. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  183. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  184. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  185. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  186. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  187. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  188. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  189. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  190. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  191. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  192. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  193. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  194. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  195. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  196. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  197. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  198. package/docs/types/types.CalendarDateObject.html +1 -1
  199. package/docs/types/types.Camelized.html +1 -1
  200. package/docs/types/types.ClockTimeObject.html +1 -1
  201. package/docs/types/types.DbConnectionType.html +1 -1
  202. package/docs/types/types.DbTypes.html +1 -1
  203. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  204. package/docs/types/types.DreamAttributes.html +1 -1
  205. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  206. package/docs/types/types.DreamClassColumn.html +1 -1
  207. package/docs/types/types.DreamColumn.html +1 -1
  208. package/docs/types/types.DreamColumnNames.html +1 -1
  209. package/docs/types/types.DreamLogLevel.html +1 -1
  210. package/docs/types/types.DreamLogger.html +2 -2
  211. package/docs/types/types.DreamModelSerializerType.html +1 -1
  212. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  213. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  214. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  215. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  216. package/docs/types/types.DreamSerializable.html +1 -1
  217. package/docs/types/types.DreamSerializableArray.html +1 -1
  218. package/docs/types/types.DreamSerializerKey.html +1 -1
  219. package/docs/types/types.DreamSerializers.html +1 -1
  220. package/docs/types/types.DreamVirtualColumns.html +1 -1
  221. package/docs/types/types.DurationUnit.html +1 -1
  222. package/docs/types/types.EncryptAlgorithm.html +1 -1
  223. package/docs/types/types.HasManyStatement.html +1 -1
  224. package/docs/types/types.HasOneStatement.html +1 -1
  225. package/docs/types/types.Hyphenized.html +1 -1
  226. package/docs/types/types.Pascalized.html +1 -1
  227. package/docs/types/types.PrimaryKeyType.html +1 -1
  228. package/docs/types/types.RoundingPrecision.html +1 -1
  229. package/docs/types/types.SerializerCasing.html +1 -1
  230. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  231. package/docs/types/types.Snakeified.html +1 -1
  232. package/docs/types/types.StrictInterface.html +1 -1
  233. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  234. package/docs/types/types.UpdateableProperties.html +1 -1
  235. package/docs/types/types.ValidationType.html +1 -1
  236. package/docs/types/types.ViewModel.html +2 -2
  237. package/docs/types/types.ViewModelClass.html +1 -1
  238. package/docs/types/types.WeekdayName.html +1 -1
  239. package/docs/types/types.WhereStatementForDream.html +1 -1
  240. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  241. package/docs/variables/index.DreamConst.html +1 -1
  242. package/docs/variables/index.ops.html +1 -1
  243. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  244. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  245. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  246. package/docs/variables/system.primaryKeyTypes.html +1 -1
  247. package/package.json +1 -1
  248. package/dist/cjs/src/dream/internal/extractNestedPaths.js +0 -34
  249. package/dist/esm/src/dream/internal/extractNestedPaths.js +0 -34
  250. package/dist/types/src/dream/internal/extractNestedPaths.d.ts +0 -21
@@ -5,6 +5,7 @@ import { blankAssociationsFactory } from './decorators/field/association/shared.
5
5
  import { blankHooksFactory } from './decorators/field/lifecycle/shared.js';
6
6
  import resortAllRecords from './decorators/field/sortable/helpers/resortAllRecords.js';
7
7
  import DreamApp from './dream-app/index.js';
8
+ import { RECURSIVE_SERIALIZATION_MAX_REPEATS } from './dream/constants.js';
8
9
  import DreamClassTransactionBuilder from './dream/DreamClassTransactionBuilder.js';
9
10
  import DreamInstanceTransactionBuilder from './dream/DreamInstanceTransactionBuilder.js';
10
11
  import associationQuery from './dream/internal/associations/associationQuery.js';
@@ -20,6 +21,7 @@ import ensureSTITypeFieldIsSet from './dream/internal/ensureSTITypeFieldIsSet.js
20
21
  import findOrCreateBy from './dream/internal/findOrCreateBy.js';
21
22
  import printSerializerHierarchyLevel from './dream/internal/printSerializerHierarchyLevel.js';
22
23
  import reload from './dream/internal/reload.js';
24
+ import resolveSerializerAssociationEdges from './dream/internal/resolveSerializerAssociationEdges.js';
23
25
  import runValidations from './dream/internal/runValidations.js';
24
26
  import saveDream from './dream/internal/saveDream.js';
25
27
  import { DEFAULT_BYPASS_ALL_DEFAULT_SCOPES, DEFAULT_DEFAULT_SCOPES_TO_BYPASS, DEFAULT_SKIP_HOOKS, } from './dream/internal/scopeHelpers.js';
@@ -43,14 +45,11 @@ import GlobalNameNotSet from './errors/dream-app/GlobalNameNotSet.js';
43
45
  import DreamMissingRequiredOverride from './errors/DreamMissingRequiredOverride.js';
44
46
  import NonExistentScopeProvidedToResort from './errors/NonExistentScopeProvidedToResort.js';
45
47
  import RecordNotFound from './errors/RecordNotFound.js';
46
- import MissingSerializersDefinition from './errors/serializers/MissingSerializersDefinition.js';
47
48
  import areEqual from './helpers/areEqual.js';
48
49
  import cloneDeepSafe from './helpers/cloneDeepSafe.js';
49
- import compact from './helpers/compact.js';
50
50
  import isJsonColumn from './helpers/db/types/isJsonColumn.js';
51
51
  import notEqual from './helpers/notEqual.js';
52
52
  import { inferSerializersFromDreamClassOrViewModelClass } from './serializer/helpers/inferSerializerFromDreamOrViewModel.js';
53
- const RECURSIVE_SERIALIZATION_MAX_REPEATS = 4;
54
53
  export default class Dream {
55
54
  DB;
56
55
  /**
@@ -568,72 +567,29 @@ export default class Dream {
568
567
  if (repeatedAssociationTracker[depthTrackerId] + 1 > RECURSIVE_SERIALIZATION_MAX_REPEATS)
569
568
  return {};
570
569
  repeatedAssociationTracker[depthTrackerId]++;
571
- const serializerBuilder = serializer(undefined, undefined);
572
- const serializerAssociations = serializerBuilder['attributes'].filter(attribute => ['rendersOne', 'rendersMany', 'delegatedAttribute'].includes(attribute.type));
573
- const mappedAssociations = serializerAssociations.reduce((accumulator, serializerAssociation) => {
574
- const serializerAssociationName = serializerAssociation.targetName ??
575
- serializerAssociation.name;
576
- const serializerAssociationType = serializerAssociation.type;
577
- const association = this['getAssociationMetadata'](serializerAssociationName);
578
- if (!association)
579
- return accumulator;
580
- const maybeAssociatedClasses = association.modelCB();
581
- if (!maybeAssociatedClasses)
582
- throw new Error(`No class defined on ${serializerAssociationName} association on ${this.sanitizedName}`);
583
- const associatedClasses = Array.isArray(maybeAssociatedClasses)
584
- ? maybeAssociatedClasses
585
- : [maybeAssociatedClasses];
586
- /////////////////////////////////////////////////
587
- // map associated classes to their serializers //
588
- /////////////////////////////////////////////////
589
- const associatedClassSerializerTuples = associatedClasses.flatMap(associatedClass => {
590
- /**
591
- * `serializers` is an array because `associatedClass` may be an STI
592
- * base, with each of its STI children having its own serializer
593
- */
594
- let serializers = [];
595
- try {
596
- serializers = serializerAssociation.options.serializer
597
- ? compact([serializerAssociation.options.serializer])
598
- : compact(inferSerializersFromDreamClassOrViewModelClass(associatedClass, serializerAssociation.options.serializerKey));
599
- }
600
- catch (error) {
601
- if (!(error instanceof MissingSerializersDefinition))
602
- throw error;
603
- serializers = [];
604
- }
605
- return serializers.map(serializer => [associatedClass, serializer]);
606
- });
607
- /////////////////////////////////////////////////////
608
- // end:map associated classes to their serializers //
609
- /////////////////////////////////////////////////////
610
- ///////////////////////////////////////////////////////////////////////////////////////////////////
611
- // reduce over all associated serializers, recursively building out their associated serializers //
612
- ///////////////////////////////////////////////////////////////////////////////////////////////////
613
- const innerAssociationSerializerInfo = associatedClassSerializerTuples.reduce((innerAccumulator, [associatedClass, associatedSerializer]) => {
614
- if (forDisplay && serializerAssociationType !== 'delegatedAttribute') {
570
+ const edges = resolveSerializerAssociationEdges(this, serializer);
571
+ const mappedAssociations = edges.reduce((accumulator, edge) => {
572
+ const innerAssociationSerializerInfo = edge.targets.reduce((innerAccumulator, target) => {
573
+ if (forDisplay && edge.type !== 'delegatedAttribute') {
615
574
  printSerializerHierarchyLevel({
616
- serializerAssociationType,
617
- serializerAssociationName,
618
- associationSerializer: associatedSerializer,
575
+ serializerAssociationType: edge.type,
576
+ serializerAssociationName: edge.serializerAssociationName,
577
+ associationSerializer: target.serializer,
619
578
  forDisplayDepth,
620
579
  });
621
580
  }
622
581
  return {
623
582
  ...innerAccumulator,
624
- ...associatedClass['recursiveSerializationMap'](associatedSerializer, {
583
+ ...target.dreamClass['recursiveSerializationMap'](target.serializer, {
625
584
  forDisplay,
626
585
  forDisplayDepth: forDisplayDepth + 1,
627
586
  repeatedAssociationTracker,
628
587
  }),
629
588
  };
630
589
  }, {});
631
- ///////////////////////////////////////////////////////////////////////////////////////////////////////
632
- // end:reduce over all associated serializers, recursively building out their associated serializers //
633
- ///////////////////////////////////////////////////////////////////////////////////////////////////////
634
- accumulator[association.as] = {
590
+ accumulator[edge.associationAs] = {
635
591
  parentDreamClass: this,
636
- nestedSerializerInfo: serializerAssociation.type === 'delegatedAttribute' ? {} : innerAssociationSerializerInfo,
592
+ nestedSerializerInfo: edge.type === 'delegatedAttribute' ? {} : innerAssociationSerializerInfo,
637
593
  };
638
594
  return accumulator;
639
595
  }, {});
@@ -3238,13 +3194,28 @@ export default class Dream {
3238
3194
  return new LoadBuilder(this).passthrough(passthroughWhereStatement);
3239
3195
  }
3240
3196
  /**
3241
- * Loads the requested associations upon execution
3197
+ * Loads the requested associations upon execution.
3198
+ *
3199
+ * **IMPORTANT:** `load().execute()` returns a **new clone** of the model with the
3200
+ * associations loaded. It does NOT modify the original instance. You must use the
3201
+ * returned value:
3202
+ *
3203
+ * ```ts
3204
+ * // CORRECT — use the returned clone:
3205
+ * const loaded = await user.load('posts').execute()
3206
+ * loaded.posts // works
3207
+ *
3208
+ * // WRONG — original is not modified:
3209
+ * await user.load('posts').execute()
3210
+ * user.posts // association not loaded!
3211
+ * ```
3242
3212
  *
3243
3213
  * NOTE: {@link Dream.preload} is often a preferrable way of achieving the
3244
- * same goal.
3214
+ * same goal. Alternatively, `await model.association('posts')` loads only
3215
+ * if the association is not already loaded.
3245
3216
  *
3246
3217
  * ```ts
3247
- * await user
3218
+ * const user = await user
3248
3219
  * .load('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3249
3220
  * .load('images')
3250
3221
  * .execute()
@@ -3257,7 +3228,7 @@ export default class Dream {
3257
3228
  * ```
3258
3229
  *
3259
3230
  * @param args - A list of associations (and optional where clauses) to load
3260
- * @returns A chainable LoadBuilder instance
3231
+ * @returns A chainable LoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3261
3232
  */
3262
3233
  load(...args) {
3263
3234
  return new LoadBuilder(this).load(...args);
@@ -3326,19 +3297,19 @@ export default class Dream {
3326
3297
  * Load each specified association using a single SQL query.
3327
3298
  * See {@link Dream.load} for loading in separate queries.
3328
3299
  *
3300
+ * **IMPORTANT:** Like `load()`, `leftJoinLoad().execute()` returns a **new clone** of the
3301
+ * model with associations loaded. It does NOT modify the original instance.
3302
+ *
3329
3303
  * Note: since leftJoinLoad loads via single query, it has
3330
- * some downsides and that may be avoided using {@link Dream.load}:
3304
+ * some downsides that may be avoided using {@link Dream.load}:
3331
3305
  * 1. `limit` and `offset` will be automatically removed
3332
3306
  * 2. `through` associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query
3333
3307
  * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinLoad('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.load('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once.
3334
3308
  * 4. the individual query becomes more complex the more associations are included
3335
3309
  * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.load(...).findEach(...)` avoids instantiating massive amounts of data at once
3336
3310
  *
3337
- * Note: Left join loading loads all data in a single SQL query but has trade-offs compared
3338
- * to regular preloading. See {@link Dream.leftJoinPreload} for details about limitations.
3339
- *
3340
3311
  * ```ts
3341
- * await user
3312
+ * const user = await user
3342
3313
  * .leftJoinLoad('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3343
3314
  * .leftJoinLoad('images')
3344
3315
  * .execute()
@@ -3351,7 +3322,7 @@ export default class Dream {
3351
3322
  * ```
3352
3323
  *
3353
3324
  * @param args - A list of associations (and optional where clauses) to load
3354
- * @returns A chainable LeftJoinLoadBuilder instance
3325
+ * @returns A chainable LeftJoinLoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3355
3326
  */
3356
3327
  leftJoinLoad(...args) {
3357
3328
  return new LeftJoinLoadBuilder(this).leftJoinLoad(...args);
@@ -3,7 +3,7 @@ import StiChildIncompatibleWithReplicaSafeDecorator from '../../errors/sti/StiCh
3
3
  import StiChildIncompatibleWithSoftDeleteDecorator from '../../errors/sti/StiChildIncompatibleWithSoftDeleteDecorator.js';
4
4
  import { scopeImplementation } from '../static-method/Scope.js';
5
5
  export const STI_SCOPE_NAME = 'dream:STI';
6
- export default function STI(dreamClass, { value } = {}) {
6
+ export default function STI(dreamClass) {
7
7
  return function (target) {
8
8
  const stiChildClass = target;
9
9
  const baseClass = dreamClass['sti'].baseClass || dreamClass;
@@ -21,7 +21,7 @@ export default function STI(dreamClass, { value } = {}) {
21
21
  stiChildClass['sti'] = {
22
22
  active: true,
23
23
  baseClass,
24
- value: value || stiChildClass.sanitizedName,
24
+ value: stiChildClass.sanitizedName,
25
25
  };
26
26
  stiChildClass[STI_SCOPE_NAME] = function (query) {
27
27
  return query.where({ type: stiChildClass['sti'].value });
@@ -5,13 +5,16 @@ export default class LeftJoinLoadBuilder {
5
5
  dream;
6
6
  query;
7
7
  /**
8
- * An intermediate class on the way to executing a load
9
- * query. this can be accessed on an instance of a dream
10
- * model by using the `#load` method:
8
+ * An intermediate builder for loading associations onto a Dream model
9
+ * via a single left-join SQL query. Accessed via `#leftJoinLoad`.
10
+ *
11
+ * **Note:** The LeftJoinLoadBuilder works on a clone of the original model.
12
+ * Call `.execute()` to get the clone with associations loaded.
11
13
  *
12
14
  * ```ts
13
15
  * const user = await User.firstOrFail()
14
- * await user.load('settings').execute()
16
+ * const loaded = await user.leftJoinLoad('settings').execute()
17
+ * loaded.settings // association is loaded on the returned clone
15
18
  * ```
16
19
  */
17
20
  constructor(dream, dreamTransaction) {
@@ -39,17 +42,20 @@ export default class LeftJoinLoadBuilder {
39
42
  return this;
40
43
  }
41
44
  /**
42
- * executes a load builder query, binding
43
- * all associations to their respective model
44
- * instances.
45
+ * Executes the left-join load query, returning a **clone** of the original model
46
+ * with all requested associations loaded. The original instance is not modified.
45
47
  *
46
48
  * ```ts
47
49
  * const user = await User.firstOrFail()
48
- * await user
49
- * .load('settings')
50
- * .load('posts', 'comments', 'replies', ['image', 'localizedText'])
50
+ * const loaded = await user
51
+ * .leftJoinLoad('settings')
52
+ * .leftJoinLoad('posts', 'comments', 'replies')
51
53
  * .execute()
54
+ *
55
+ * loaded.settings // works — associations are on the returned clone
52
56
  * ```
57
+ *
58
+ * @returns A clone of the Dream instance with all requested associations loaded
53
59
  */
54
60
  async execute() {
55
61
  if (this.dreamTransaction) {
@@ -3,13 +3,16 @@ export default class LoadBuilder {
3
3
  dream;
4
4
  query;
5
5
  /**
6
- * An intermediate class on the way to executing a load
7
- * query. this can be accessed on an instance of a dream
8
- * model by using the `#load` method:
6
+ * An intermediate builder for loading associations onto a Dream model.
7
+ * Accessed via the `#load` method on a Dream instance.
8
+ *
9
+ * **Note:** The LoadBuilder works on a clone of the original model.
10
+ * Call `.execute()` to get the clone with associations loaded.
9
11
  *
10
12
  * ```ts
11
13
  * const user = await User.firstOrFail()
12
- * await user.load('settings').execute()
14
+ * const loaded = await user.load('settings').execute()
15
+ * loaded.settings // association is loaded on the returned clone
13
16
  * ```
14
17
  */
15
18
  constructor(dream, dreamTransaction) {
@@ -21,6 +24,61 @@ export default class LoadBuilder {
21
24
  this.query = this.query.passthrough(passthroughWhereStatement);
22
25
  return this;
23
26
  }
27
+ /**
28
+ * Prevents a specific default scope from applying when
29
+ * loading associations
30
+ *
31
+ * ```ts
32
+ * const user = await User.firstOrFail()
33
+ * const loaded = await user
34
+ * .load('posts')
35
+ * .removeDefaultScope('dream:SoftDelete')
36
+ * .execute()
37
+ * ```
38
+ *
39
+ * @param scopeName - The default scope to bypass
40
+ * @returns The LoadBuilder instance for chaining
41
+ */
42
+ removeDefaultScope(scopeName) {
43
+ this.query = this.query.removeDefaultScope(scopeName);
44
+ return this;
45
+ }
46
+ /**
47
+ * Prevents all default scopes from applying when
48
+ * loading associations
49
+ *
50
+ * ```ts
51
+ * const user = await User.firstOrFail()
52
+ * const loaded = await user
53
+ * .load('posts')
54
+ * .removeAllDefaultScopes()
55
+ * .execute()
56
+ * ```
57
+ *
58
+ * @returns The LoadBuilder instance for chaining
59
+ */
60
+ removeAllDefaultScopes() {
61
+ this.query = this.query.removeAllDefaultScopes();
62
+ return this;
63
+ }
64
+ /**
65
+ * Sets the database connection to use when loading associations
66
+ *
67
+ * ```ts
68
+ * const user = await User.firstOrFail()
69
+ * const loaded = await user
70
+ * .load('posts')
71
+ * .connection('replica')
72
+ * .execute()
73
+ * ```
74
+ *
75
+ * @param connection - The connection type ('primary' or 'replica')
76
+ * @returns The LoadBuilder instance for chaining
77
+ */
78
+ connection(connection) {
79
+ this.query = this.query.connection(connection);
80
+ return this;
81
+ }
24
82
  /**
25
83
  * Attaches a load statement to the load builder
26
84
  *
@@ -89,19 +147,22 @@ export default class LoadBuilder {
89
147
  return this;
90
148
  }
91
149
  /**
92
- * Executes a load builder query, binding
93
- * all associations to their respective model
94
- * instances.
150
+ * Executes the load query, binding all associations to a **clone** of the
151
+ * original model instance. The original instance is not modified.
152
+ *
153
+ * You must use the returned value to access the loaded associations:
95
154
  *
96
155
  * ```ts
97
156
  * const user = await User.firstOrFail()
98
- * await user
157
+ * const loaded = await user
99
158
  * .load('settings')
100
159
  * .load('posts', 'comments', 'replies', ['image', 'localizedText'])
101
160
  * .execute()
161
+ *
162
+ * loaded.settings // works — associations are on the returned clone
102
163
  * ```
103
164
  *
104
- * @returns The Dream instance with all associations loaded
165
+ * @returns A clone of the Dream instance with all requested associations loaded
105
166
  */
106
167
  async execute() {
107
168
  if (this.dreamTransaction) {
@@ -17,10 +17,10 @@ import namespaceColumn from '../helpers/namespaceColumn.js';
17
17
  import protectAgainstPollutingAssignment from '../helpers/protectAgainstPollutingAssignment.js';
18
18
  import { toSafeObject } from '../helpers/toSafeObject.js';
19
19
  import ops from '../ops/index.js';
20
+ import buildSerializerPreloadPaths from './internal/buildSerializerPreloadPaths.js';
20
21
  import computedPaginatePage from './internal/computedPaginatePage.js';
21
22
  import convertDreamClassAndAssociationNameTupleArrayToPreloadArgs from './internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.js';
22
- import extractNestedPaths from './internal/extractNestedPaths.js';
23
- // Cache for extractNestedPaths results, keyed by "${sanitizedName}:${serializerKey}"
23
+ // Cache for serializer preload-path results, keyed by "${globalName}:${serializerKey}"
24
24
  const extractedNestedPathsCache = new Map();
25
25
  export default class Query {
26
26
  /**
@@ -526,11 +526,10 @@ export default class Query {
526
526
  * @returns A Query with all serialization associations preloaded
527
527
  */
528
528
  preloadFor(serializerKey, modifierFn) {
529
- const cacheKey = `${this.dreamClass.sanitizedName}:${serializerKey ?? 'default'}`;
529
+ const cacheKey = `${this.dreamClass.globalName}:${serializerKey ?? 'default'}`;
530
530
  let preloadArgs = extractedNestedPathsCache.get(cacheKey);
531
531
  if (!preloadArgs) {
532
- // Cache miss - compute and store
533
- preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
532
+ preloadArgs = buildSerializerPreloadPaths(this.dreamClass, serializerKey);
534
533
  extractedNestedPathsCache.set(cacheKey, preloadArgs);
535
534
  }
536
535
  let query = this;
@@ -1698,7 +1697,7 @@ export default class Query {
1698
1697
  * Updates all records matching the Query
1699
1698
  *
1700
1699
  * ```ts
1701
- * await User.where({ email: ops.ilike('%burpcollaborator%') }).updateAll({ email: null })
1700
+ * await User.where({ email: ops.ilike('%burpcollaborator%') }).update({ email: null })
1702
1701
  * // 12
1703
1702
  * ```
1704
1703
  * @param attributes - The attributes used to update the records
@@ -1,5 +1,7 @@
1
1
  export const primaryKeyTypes = ['uuid7', 'uuid4', 'bigserial', 'bigint', 'integer'];
2
2
  export const TRIGRAM_OPERATORS = ['%', '<%', '<<%'];
3
+ export const RECURSIVE_SERIALIZATION_MAX_REPEATS = 4;
4
+ export const RECURSIVE_DESTROY_PRELOAD_MAX_REPEATS = RECURSIVE_SERIALIZATION_MAX_REPEATS;
3
5
  class RequiredAttribute {
4
6
  constructor() { }
5
7
  }
@@ -0,0 +1,33 @@
1
+ export default function buildAssociationPaths(rootNode, opts) {
2
+ const paths = [];
3
+ traverseAssociationPaths(rootNode, [], {}, paths, opts);
4
+ return paths;
5
+ }
6
+ function traverseAssociationPaths(node, currentPath, depthTracker, paths, opts) {
7
+ const trackerId = opts.getKey(node);
8
+ depthTracker[trackerId] ??= 0;
9
+ if (depthTracker[trackerId] + 1 > opts.maxRepeats) {
10
+ if (currentPath.length > 0) {
11
+ paths.push([...currentPath]);
12
+ }
13
+ return;
14
+ }
15
+ depthTracker[trackerId]++;
16
+ const edges = opts.getEdges(node);
17
+ if (edges.length === 0) {
18
+ if (currentPath.length > 0) {
19
+ paths.push([...currentPath]);
20
+ }
21
+ depthTracker[trackerId]--;
22
+ return;
23
+ }
24
+ for (const edge of edges) {
25
+ const newPath = [...currentPath, edge.tuple];
26
+ if (!edge.nextNode) {
27
+ paths.push(newPath);
28
+ continue;
29
+ }
30
+ traverseAssociationPaths(edge.nextNode, newPath, depthTracker, paths, opts);
31
+ }
32
+ depthTracker[trackerId]--;
33
+ }
@@ -0,0 +1,43 @@
1
+ import { RECURSIVE_DESTROY_PRELOAD_MAX_REPEATS } from '../constants.js';
2
+ import buildAssociationPaths from './buildAssociationPaths.js';
3
+ const dependentDestroyPreloadPathsCache = new Map();
4
+ /**
5
+ * @internal
6
+ *
7
+ * Recursively walks `dependent: 'destroy'` associations starting from the given
8
+ * Dream class, producing an array of preload paths. Each path is an array of
9
+ * [DreamClass, associationName] tuples representing a chain from root to leaf.
10
+ *
11
+ * Allows the same Dream class to appear up to MAX_REPEATS times in a single
12
+ * path to support tree structures (e.g. a Category with `dependent: 'destroy'`
13
+ * on its children, which are also Categories).
14
+ *
15
+ * Used to build a preload tree so that all records in the cascade can be loaded
16
+ * upfront, eliminating N+1 queries during destroy.
17
+ */
18
+ export default function buildDependentDestroyPreloadPaths(dreamClass) {
19
+ const cacheKey = dreamClass.globalName;
20
+ const cached = dependentDestroyPreloadPathsCache.get(cacheKey);
21
+ if (cached)
22
+ return cached;
23
+ const paths = [];
24
+ paths.push(...buildAssociationPaths(dreamClass, {
25
+ getKey: currentDreamClass => currentDreamClass.globalName,
26
+ getEdges: currentDreamClass => {
27
+ const associationMap = currentDreamClass['associationMetadataMap']();
28
+ const dependentDestroyNames = Object.keys(associationMap).filter(key => associationMap[key].dependent === 'destroy');
29
+ return dependentDestroyNames.flatMap(associationName => {
30
+ const association = associationMap[associationName];
31
+ const modelCBResult = association.modelCB();
32
+ const targetClasses = Array.isArray(modelCBResult) ? modelCBResult : [modelCBResult];
33
+ return targetClasses.map(targetClass => ({
34
+ nextNode: targetClass,
35
+ tuple: [currentDreamClass, associationName],
36
+ }));
37
+ });
38
+ },
39
+ maxRepeats: RECURSIVE_DESTROY_PRELOAD_MAX_REPEATS,
40
+ }));
41
+ dependentDestroyPreloadPathsCache.set(cacheKey, paths);
42
+ return paths;
43
+ }
@@ -0,0 +1,38 @@
1
+ import { inferSerializersFromDreamClassOrViewModelClass } from '../../serializer/helpers/inferSerializerFromDreamOrViewModel.js';
2
+ import { RECURSIVE_SERIALIZATION_MAX_REPEATS } from '../constants.js';
3
+ import buildAssociationPaths from './buildAssociationPaths.js';
4
+ import resolveSerializerAssociationEdges from './resolveSerializerAssociationEdges.js';
5
+ export default function buildSerializerPreloadPaths(dreamClass, serializerKey) {
6
+ const key = serializerKey || 'default';
7
+ const serializer = inferSerializersFromDreamClassOrViewModelClass(dreamClass, key)[0] ?? null;
8
+ if (!serializer)
9
+ throw new Error(`unable to find serializer with key: ${key}`);
10
+ const paths = buildAssociationPaths({
11
+ dreamClass,
12
+ serializer,
13
+ }, {
14
+ getKey: node => node.dreamClass.globalName,
15
+ getEdges: serializerNodeToEdges,
16
+ maxRepeats: RECURSIVE_SERIALIZATION_MAX_REPEATS,
17
+ });
18
+ const dedupedPaths = new Map();
19
+ for (const path of paths) {
20
+ dedupedPaths.set(path
21
+ .map(([pathDreamClass, associationName]) => `${pathDreamClass.globalName}:${associationName}`)
22
+ .join('|'), path);
23
+ }
24
+ return [...dedupedPaths.values()];
25
+ }
26
+ function serializerNodeToEdges({ dreamClass, serializer, }) {
27
+ const edges = resolveSerializerAssociationEdges(dreamClass, serializer);
28
+ return edges.flatMap(edge => {
29
+ const tuple = [dreamClass, edge.associationAs];
30
+ if (edge.type === 'delegatedAttribute' || edge.targets.length === 0) {
31
+ return [{ nextNode: null, tuple }];
32
+ }
33
+ return edge.targets.map(target => ({
34
+ nextNode: target,
35
+ tuple,
36
+ }));
37
+ });
38
+ }
@@ -2,17 +2,25 @@
2
2
  * @internal
3
3
  *
4
4
  * Destroys all HasOne/HasMany associations on this
5
- * dream that are marked as `dependent: 'destroy'`
5
+ * dream that are marked as `dependent: 'destroy'`.
6
+ *
7
+ * Expects associations to be preloaded onto the dream instance
8
+ * via `loadDependentDestroyTree`. Iterates loaded associations
9
+ * directly instead of querying the database.
6
10
  */
7
11
  export default async function destroyAssociatedRecords(dream, txn, options) {
8
12
  const dreamClass = dream.constructor;
9
13
  const { reallyDestroy } = options;
10
14
  for (const associationName of dreamClass['dependentDestroyAssociationNames']()) {
11
- if (reallyDestroy) {
12
- await dream.txn(txn).reallyDestroyAssociation(associationName, options);
13
- }
14
- else {
15
- await dream.txn(txn).destroyAssociation(associationName, options);
15
+ const loaded = dream[associationName];
16
+ const records = Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
17
+ for (const record of records) {
18
+ if (reallyDestroy) {
19
+ await record.txn(txn).reallyDestroy(options);
20
+ }
21
+ else {
22
+ await record.txn(txn).destroy(options);
23
+ }
16
24
  }
17
25
  }
18
26
  }
@@ -1,5 +1,6 @@
1
1
  import DreamApp from '../../dream-app/index.js';
2
2
  import destroyAssociatedRecords from './destroyAssociatedRecords.js';
3
+ import loadDependentDestroyTree from './loadDependentDestroyTree.js';
3
4
  import runHooksFor from './runHooksFor.js';
4
5
  /**
5
6
  * @internal
@@ -30,7 +31,12 @@ export default async function destroyDream(dream, txn = null, options) {
30
31
  async function destroyDreamWithTransaction(dream, txn, options) {
31
32
  const { cascade, reallyDestroy, skipHooks } = options;
32
33
  if (cascade) {
33
- await destroyAssociatedRecords(dream, txn, options);
34
+ const dreamWithAssociations = await loadDependentDestroyTree(dream, txn, {
35
+ reallyDestroy,
36
+ bypassAllDefaultScopes: options.bypassAllDefaultScopes ?? false,
37
+ defaultScopesToBypass: options.defaultScopesToBypass ?? [],
38
+ });
39
+ await destroyAssociatedRecords(dreamWithAssociations, txn, options);
34
40
  }
35
41
  if (!skipHooks) {
36
42
  await runHooksFor('beforeDestroy', dream, true, null, txn);
@@ -0,0 +1,35 @@
1
+ import LoadBuilder from '../LoadBuilder.js';
2
+ import buildDependentDestroyPreloadPaths from './buildDependentDestroyPreloadPaths.js';
3
+ import convertDreamClassAndAssociationNameTupleArrayToPreloadArgs from './convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.js';
4
+ /**
5
+ * @internal
6
+ *
7
+ * Loads all `dependent: 'destroy'` associations onto the dream instance
8
+ * upfront, eliminating N+1 queries during cascade destruction.
9
+ *
10
+ * Returns a clone of the dream with all associations loaded.
11
+ * If there are no dependent-destroy associations, returns the original dream.
12
+ */
13
+ export default async function loadDependentDestroyTree(dream, txn, { reallyDestroy, bypassAllDefaultScopes, defaultScopesToBypass, }) {
14
+ const dreamClass = dream.constructor;
15
+ const paths = buildDependentDestroyPreloadPaths(dreamClass);
16
+ if (paths.length === 0)
17
+ return dream;
18
+ let loadBuilder = new LoadBuilder(dream, txn);
19
+ for (const path of paths) {
20
+ const args = convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(path);
21
+ loadBuilder = loadBuilder.load(...args);
22
+ }
23
+ if (bypassAllDefaultScopes) {
24
+ loadBuilder = loadBuilder.removeAllDefaultScopes();
25
+ }
26
+ else {
27
+ for (const scopeName of defaultScopesToBypass) {
28
+ loadBuilder = loadBuilder.removeDefaultScope(scopeName);
29
+ }
30
+ }
31
+ if (reallyDestroy) {
32
+ loadBuilder = loadBuilder.removeDefaultScope('dream:SoftDelete');
33
+ }
34
+ return await loadBuilder.execute();
35
+ }