@rvoh/dream 2.5.8 → 2.6.1-alpha.1

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 (227) hide show
  1. package/dist/cjs/src/Dream.js +12 -56
  2. package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +15 -11
  3. package/dist/cjs/src/dream/LoadBuilder.js +55 -0
  4. package/dist/cjs/src/dream/Query.js +4 -5
  5. package/dist/cjs/src/dream/constants.js +2 -0
  6. package/dist/cjs/src/dream/internal/buildAssociationPaths.js +33 -0
  7. package/dist/cjs/src/dream/internal/buildDependentDestroyPreloadPaths.js +43 -0
  8. package/dist/cjs/src/dream/internal/buildSerializerPreloadPaths.js +38 -0
  9. package/dist/cjs/src/dream/internal/destroyAssociatedRecords.js +14 -6
  10. package/dist/cjs/src/dream/internal/destroyDream.js +7 -1
  11. package/dist/cjs/src/dream/internal/loadDependentDestroyTree.js +35 -0
  12. package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +53 -0
  13. package/dist/cjs/src/serializer/SerializerRenderer.js +1 -1
  14. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +4 -0
  15. package/dist/cjs/src/types/recursiveSerialization.js +1 -0
  16. package/dist/esm/src/Dream.js +12 -56
  17. package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +15 -11
  18. package/dist/esm/src/dream/LoadBuilder.js +55 -0
  19. package/dist/esm/src/dream/Query.js +4 -5
  20. package/dist/esm/src/dream/constants.js +2 -0
  21. package/dist/esm/src/dream/internal/buildAssociationPaths.js +33 -0
  22. package/dist/esm/src/dream/internal/buildDependentDestroyPreloadPaths.js +43 -0
  23. package/dist/esm/src/dream/internal/buildSerializerPreloadPaths.js +38 -0
  24. package/dist/esm/src/dream/internal/destroyAssociatedRecords.js +14 -6
  25. package/dist/esm/src/dream/internal/destroyDream.js +7 -1
  26. package/dist/esm/src/dream/internal/loadDependentDestroyTree.js +35 -0
  27. package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +53 -0
  28. package/dist/esm/src/serializer/SerializerRenderer.js +1 -1
  29. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +4 -0
  30. package/dist/esm/src/types/recursiveSerialization.js +1 -0
  31. package/dist/types/src/db/migration-helpers/DreamMigrationHelpers.d.ts +6 -7
  32. package/dist/types/src/dream/LoadBuilder.d.ts +48 -1
  33. package/dist/types/src/dream/constants.d.ts +2 -0
  34. package/dist/types/src/dream/internal/buildAssociationPaths.d.ts +12 -0
  35. package/dist/types/src/dream/internal/buildDependentDestroyPreloadPaths.d.ts +17 -0
  36. package/dist/types/src/dream/internal/buildSerializerPreloadPaths.d.ts +3 -0
  37. package/dist/types/src/dream/internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.d.ts +1 -1
  38. package/dist/types/src/dream/internal/destroyAssociatedRecords.d.ts +5 -1
  39. package/dist/types/src/dream/internal/loadDependentDestroyTree.d.ts +17 -0
  40. package/dist/types/src/dream/internal/resolveSerializerAssociationEdges.d.ts +13 -0
  41. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +13 -4
  42. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +3 -2
  43. package/dist/types/src/types/recursiveSerialization.d.ts +8 -0
  44. package/dist/types/src/types/recursiveSerialization.ts +10 -0
  45. package/dist/types/src/types/serializer.d.ts +8 -1
  46. package/dist/types/src/types/serializer.ts +8 -1
  47. package/docs/classes/db.DreamMigrationHelpers.html +14 -15
  48. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  49. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  50. package/docs/classes/db.QueryDriverBase.html +31 -31
  51. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  52. package/docs/classes/errors.ColumnOverflow.html +3 -3
  53. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  54. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  55. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  56. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  57. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  58. package/docs/classes/errors.InvalidClockTime.html +2 -2
  59. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  60. package/docs/classes/errors.InvalidDateTime.html +2 -2
  61. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  62. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  63. package/docs/classes/errors.NotNullViolation.html +3 -3
  64. package/docs/classes/errors.RecordNotFound.html +3 -3
  65. package/docs/classes/errors.ValidationError.html +3 -3
  66. package/docs/classes/index.CalendarDate.html +33 -33
  67. package/docs/classes/index.ClockTime.html +32 -32
  68. package/docs/classes/index.ClockTimeTz.html +35 -35
  69. package/docs/classes/index.DateTime.html +86 -86
  70. package/docs/classes/index.Decorators.html +19 -19
  71. package/docs/classes/index.Dream.html +118 -118
  72. package/docs/classes/index.DreamApp.html +5 -5
  73. package/docs/classes/index.DreamTransaction.html +2 -2
  74. package/docs/classes/index.Env.html +2 -2
  75. package/docs/classes/index.Query.html +56 -56
  76. package/docs/classes/system.CliFileWriter.html +4 -4
  77. package/docs/classes/system.DreamBin.html +2 -2
  78. package/docs/classes/system.DreamCLI.html +6 -6
  79. package/docs/classes/system.DreamImporter.html +2 -2
  80. package/docs/classes/system.DreamLogos.html +2 -2
  81. package/docs/classes/system.DreamSerializerBuilder.html +19 -15
  82. package/docs/classes/system.ObjectSerializerBuilder.html +10 -10
  83. package/docs/classes/system.PathHelpers.html +3 -3
  84. package/docs/classes/utils.Encrypt.html +2 -2
  85. package/docs/classes/utils.Range.html +2 -2
  86. package/docs/functions/db.closeAllDbConnections.html +1 -1
  87. package/docs/functions/db.dreamDbConnections.html +1 -1
  88. package/docs/functions/db.untypedDb.html +1 -1
  89. package/docs/functions/db.validateColumn.html +1 -1
  90. package/docs/functions/db.validateTable.html +1 -1
  91. package/docs/functions/errors.pgErrorType.html +1 -1
  92. package/docs/functions/index.DreamSerializer.html +1 -1
  93. package/docs/functions/index.ObjectSerializer.html +1 -1
  94. package/docs/functions/index.ReplicaSafe.html +1 -1
  95. package/docs/functions/index.STI.html +1 -1
  96. package/docs/functions/index.SoftDelete.html +1 -1
  97. package/docs/functions/utils.camelize.html +1 -1
  98. package/docs/functions/utils.capitalize.html +1 -1
  99. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  100. package/docs/functions/utils.compact.html +1 -1
  101. package/docs/functions/utils.groupBy.html +1 -1
  102. package/docs/functions/utils.hyphenize.html +1 -1
  103. package/docs/functions/utils.intersection.html +1 -1
  104. package/docs/functions/utils.isEmpty.html +1 -1
  105. package/docs/functions/utils.normalizeUnicode.html +1 -1
  106. package/docs/functions/utils.pascalize.html +1 -1
  107. package/docs/functions/utils.percent.html +1 -1
  108. package/docs/functions/utils.range.html +1 -1
  109. package/docs/functions/utils.round.html +1 -1
  110. package/docs/functions/utils.sanitizeString.html +1 -1
  111. package/docs/functions/utils.snakeify.html +1 -1
  112. package/docs/functions/utils.sort.html +1 -1
  113. package/docs/functions/utils.sortBy.html +1 -1
  114. package/docs/functions/utils.sortObjectByKey.html +1 -1
  115. package/docs/functions/utils.sortObjectByValue.html +1 -1
  116. package/docs/functions/utils.uncapitalize.html +1 -1
  117. package/docs/functions/utils.uniq.html +1 -1
  118. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  119. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  120. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  121. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  122. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  123. package/docs/interfaces/types.DecoratorContext.html +2 -2
  124. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  125. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  126. package/docs/interfaces/types.DurationObject.html +2 -2
  127. package/docs/interfaces/types.EncryptOptions.html +2 -2
  128. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  129. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  130. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  131. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  132. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  133. package/docs/types/openapi.OpenapiFormats.html +1 -1
  134. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  135. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  136. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  137. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  138. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  139. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  142. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  144. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  145. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  146. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  147. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  148. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  150. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  163. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  164. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  165. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  166. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  167. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  168. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  169. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  170. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  171. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  172. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  173. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  174. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  175. package/docs/types/types.CalendarDateObject.html +1 -1
  176. package/docs/types/types.Camelized.html +1 -1
  177. package/docs/types/types.ClockTimeObject.html +1 -1
  178. package/docs/types/types.DbConnectionType.html +1 -1
  179. package/docs/types/types.DbTypes.html +1 -1
  180. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  181. package/docs/types/types.DreamAttributes.html +1 -1
  182. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  183. package/docs/types/types.DreamClassColumn.html +1 -1
  184. package/docs/types/types.DreamColumn.html +1 -1
  185. package/docs/types/types.DreamColumnNames.html +1 -1
  186. package/docs/types/types.DreamLogLevel.html +1 -1
  187. package/docs/types/types.DreamLogger.html +2 -2
  188. package/docs/types/types.DreamModelSerializerType.html +1 -1
  189. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  190. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  191. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  192. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  193. package/docs/types/types.DreamSerializable.html +1 -1
  194. package/docs/types/types.DreamSerializableArray.html +1 -1
  195. package/docs/types/types.DreamSerializerKey.html +1 -1
  196. package/docs/types/types.DreamSerializers.html +1 -1
  197. package/docs/types/types.DreamVirtualColumns.html +1 -1
  198. package/docs/types/types.DurationUnit.html +1 -1
  199. package/docs/types/types.EncryptAlgorithm.html +1 -1
  200. package/docs/types/types.HasManyStatement.html +1 -1
  201. package/docs/types/types.HasOneStatement.html +1 -1
  202. package/docs/types/types.Hyphenized.html +1 -1
  203. package/docs/types/types.Pascalized.html +1 -1
  204. package/docs/types/types.PrimaryKeyType.html +1 -1
  205. package/docs/types/types.RoundingPrecision.html +1 -1
  206. package/docs/types/types.SerializerCasing.html +1 -1
  207. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  208. package/docs/types/types.Snakeified.html +1 -1
  209. package/docs/types/types.StrictInterface.html +1 -1
  210. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  211. package/docs/types/types.UpdateableProperties.html +1 -1
  212. package/docs/types/types.ValidationType.html +1 -1
  213. package/docs/types/types.ViewModel.html +2 -2
  214. package/docs/types/types.ViewModelClass.html +1 -1
  215. package/docs/types/types.WeekdayName.html +1 -1
  216. package/docs/types/types.WhereStatementForDream.html +1 -1
  217. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  218. package/docs/variables/index.DreamConst.html +1 -1
  219. package/docs/variables/index.ops.html +1 -1
  220. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  221. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  222. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  223. package/docs/variables/system.primaryKeyTypes.html +1 -1
  224. package/package.json +1 -1
  225. package/dist/cjs/src/dream/internal/extractNestedPaths.js +0 -34
  226. package/dist/esm/src/dream/internal/extractNestedPaths.js +0 -34
  227. 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
  }, {});
@@ -1,15 +1,14 @@
1
1
  import { sql } from 'kysely';
2
2
  export default class DreamMigrationHelpers {
3
3
  /**
4
- * Rename a table and its associated primary key sequence.
4
+ * Rename a table and its associated primary key index and sequence.
5
5
  *
6
- * This method renames both the table and its primary key sequence to keep them
7
- * in sync. When PostgreSQL creates a table with a serial or bigserial primary key,
8
- * it automatically creates a sequence named `{tablename}_id_seq`. If you only rename
9
- * the table, the sequence keeps the old name, which can cause confusion and issues.
6
+ * This method renames the table, its primary key index (`{tablename}_pkey`),
7
+ * and its primary key sequence (`{tablename}_id_seq`) to keep them in sync.
10
8
  *
11
- * This method is only suitable for tables that have a serial or bigserial primary
12
- * key column named 'id'.
9
+ * The sequence rename is skipped for tables with UUID primary keys (which have
10
+ * no associated sequence). The primary key index is always renamed since
11
+ * PostgreSQL does not automatically rename it when the table is renamed.
13
12
  *
14
13
  * @param db - The Kysely database object passed into the migration up/down function
15
14
  * @param from - The current name of the table to rename
@@ -17,10 +16,15 @@ export default class DreamMigrationHelpers {
17
16
  */
18
17
  static async renameTable(db, from, to) {
19
18
  await db.schema.alterTable(from).renameTo(to).execute();
20
- const fromSequenceName = `${from}_id_seq`;
21
- const toSequenceName = `${to}_id_seq`;
22
- const sqlStatement = `ALTER SEQUENCE ${fromSequenceName} RENAME TO ${toSequenceName}`;
23
- await sql.raw(sqlStatement).execute(db);
19
+ await sql `ALTER INDEX IF EXISTS ${sql.ref(`${from}_pkey`)} RENAME TO ${sql.ref(`${to}_pkey`)}`.execute(db);
20
+ const sequenceExists = await sql `
21
+ SELECT EXISTS (
22
+ SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = ${`${from}_id_seq`}
23
+ )
24
+ `.execute(db);
25
+ if (sequenceExists.rows[0]?.exists) {
26
+ await sql `ALTER SEQUENCE ${sql.ref(`${from}_id_seq`)} RENAME TO ${sql.ref(`${to}_id_seq`)}`.execute(db);
27
+ }
24
28
  }
25
29
  /**
26
30
  * Unique indexes cannot be populated by the same value even within a transaction,
@@ -24,6 +24,61 @@ export default class LoadBuilder {
24
24
  this.query = this.query.passthrough(passthroughWhereStatement);
25
25
  return this;
26
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
+ }
27
82
  /**
28
83
  * Attaches a load statement to the load builder
29
84
  *
@@ -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;
@@ -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
+ }
@@ -0,0 +1,53 @@
1
+ import MissingSerializersDefinition from '../../errors/serializers/MissingSerializersDefinition.js';
2
+ import compact from '../../helpers/compact.js';
3
+ import { inferSerializersFromDreamClassOrViewModelClass } from '../../serializer/helpers/inferSerializerFromDreamOrViewModel.js';
4
+ export default function resolveSerializerAssociationEdges(dreamClass, serializer) {
5
+ const serializerBuilder = serializer(undefined, undefined);
6
+ const serializerAssociations = serializerBuilder['attributes'].filter(attribute => ['rendersOne', 'rendersMany', 'delegatedAttribute'].includes(attribute.type));
7
+ return compact(serializerAssociations.map(serializerAssociation => {
8
+ const serializerAssociationName = serializerAssociation.targetName ??
9
+ serializerAssociation.name;
10
+ const association = dreamClass['getAssociationMetadata'](serializerAssociationName);
11
+ if (!association)
12
+ return null;
13
+ if (serializerAssociation.type === 'delegatedAttribute') {
14
+ return {
15
+ associationAs: association.as,
16
+ sourceDreamClass: dreamClass,
17
+ type: serializerAssociation.type,
18
+ serializerAssociationName,
19
+ targets: [],
20
+ };
21
+ }
22
+ const maybeAssociatedClasses = association.modelCB();
23
+ if (!maybeAssociatedClasses)
24
+ throw new Error(`No class defined on ${serializerAssociationName} association on ${dreamClass.sanitizedName}`);
25
+ const associatedClasses = Array.isArray(maybeAssociatedClasses)
26
+ ? maybeAssociatedClasses
27
+ : [maybeAssociatedClasses];
28
+ const targets = associatedClasses.flatMap(associatedClass => {
29
+ let serializers = [];
30
+ try {
31
+ serializers = serializerAssociation.options.serializer
32
+ ? compact([serializerAssociation.options.serializer])
33
+ : compact(inferSerializersFromDreamClassOrViewModelClass(associatedClass, serializerAssociation.options.serializerKey));
34
+ }
35
+ catch (error) {
36
+ if (!(error instanceof MissingSerializersDefinition))
37
+ throw error;
38
+ serializers = [];
39
+ }
40
+ return serializers.map(associatedSerializer => ({
41
+ dreamClass: associatedClass,
42
+ serializer: associatedSerializer,
43
+ }));
44
+ });
45
+ return {
46
+ associationAs: association.as,
47
+ sourceDreamClass: dreamClass,
48
+ type: serializerAssociation.type,
49
+ serializerAssociationName,
50
+ targets,
51
+ };
52
+ }));
53
+ }
@@ -158,7 +158,7 @@ export default class SerializerRenderer {
158
158
  // does not pass it into the call to DreamSerializer/ObjectSerializer,
159
159
  // then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
160
160
  // handles passing its passthrough data into those
161
- .render(passthroughData));
161
+ .render(passthroughData, this.renderOpts));
162
162
  });
163
163
  return accumulator;
164
164
  }
@@ -37,6 +37,10 @@ export default class DreamSerializerBuilder {
37
37
  * - `default` - Value to use when the target object or its attribute is null/undefined
38
38
  * - `openapi` - OpenAPI schema definition; required for non-Dream targets and json/jsonb
39
39
  * columns, optional for standard Dream columns (where types are inferred)
40
+ * - `optional` - Set to `true` to indicate the value can be null in the OpenAPI schema
41
+ * (wraps the type in `anyOf: [schema, { type: 'null' }]`). For Dream models, this is
42
+ * auto-inferred from optional BelongsTo associations. Use this when delegating through
43
+ * a HasOne or other nullable association.
40
44
  * - `precision` - Round decimal values to the specified number of decimal places (0–9)
41
45
  * during rendering; does not affect the OpenAPI shape
42
46
  * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
@@ -0,0 +1 @@
1
+ export {};