@rvoh/dream 0.44.8 → 0.45.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 (207) hide show
  1. package/dist/cjs/src/Dream.js +246 -23
  2. package/dist/cjs/src/cli/index.js +16 -0
  3. package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +18 -14
  4. package/dist/cjs/src/decorators/Decorators.js +2 -1
  5. package/dist/cjs/src/dream/Query.js +162 -24
  6. package/dist/cjs/src/dream/internal/extractNestedPaths.js +37 -0
  7. package/dist/cjs/src/helpers/indent.js +18 -0
  8. package/dist/cjs/src/serializer/SerializerRenderer.js +3 -18
  9. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +139 -3
  10. package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +176 -3
  11. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +20 -0
  12. package/dist/esm/src/Dream.js +246 -23
  13. package/dist/esm/src/cli/index.js +16 -0
  14. package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +18 -14
  15. package/dist/esm/src/decorators/Decorators.js +2 -1
  16. package/dist/esm/src/dream/Query.js +162 -24
  17. package/dist/esm/src/dream/internal/extractNestedPaths.js +34 -0
  18. package/dist/esm/src/helpers/indent.js +15 -0
  19. package/dist/esm/src/serializer/SerializerRenderer.js +3 -18
  20. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +139 -3
  21. package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +176 -3
  22. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +17 -0
  23. package/dist/types/src/Dream.d.ts +218 -24
  24. package/dist/types/src/db/migration-helpers/DreamMigrationHelpers.d.ts +18 -14
  25. package/dist/types/src/decorators/Decorators.d.ts +2 -1
  26. package/dist/types/src/dream/Query.d.ts +154 -25
  27. package/dist/types/src/dream/internal/extractNestedPaths.d.ts +21 -0
  28. package/dist/types/src/helpers/indent.d.ts +3 -0
  29. package/dist/types/src/index.d.ts +1 -1
  30. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +176 -5
  31. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +176 -3
  32. package/dist/types/src/serializer/helpers/serializerForAssociatedClass.d.ts +9 -0
  33. package/dist/types/src/types/dream.d.ts +2 -1
  34. package/dist/types/src/types/dream.ts +26 -1
  35. package/docs/assets/highlight.css +7 -0
  36. package/docs/assets/navigation.js +1 -1
  37. package/docs/assets/search.js +1 -1
  38. package/docs/classes/Benchmark.html +2 -2
  39. package/docs/classes/CalendarDate.html +2 -2
  40. package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
  41. package/docs/classes/Decorators.html +21 -20
  42. package/docs/classes/Dream.html +288 -209
  43. package/docs/classes/DreamApp.html +4 -4
  44. package/docs/classes/DreamBin.html +2 -2
  45. package/docs/classes/DreamCLI.html +4 -4
  46. package/docs/classes/DreamImporter.html +2 -2
  47. package/docs/classes/DreamLogos.html +2 -2
  48. package/docs/classes/DreamMigrationHelpers.html +21 -8
  49. package/docs/classes/DreamSerializerBuilder.html +66 -2
  50. package/docs/classes/DreamTransaction.html +2 -2
  51. package/docs/classes/Encrypt.html +2 -2
  52. package/docs/classes/Env.html +2 -2
  53. package/docs/classes/GlobalNameNotSet.html +3 -3
  54. package/docs/classes/NonLoadedAssociation.html +3 -3
  55. package/docs/classes/ObjectSerializerBuilder.html +66 -2
  56. package/docs/classes/Query.html +118 -78
  57. package/docs/classes/Range.html +2 -2
  58. package/docs/classes/RecordNotFound.html +3 -3
  59. package/docs/classes/ValidationError.html +3 -3
  60. package/docs/functions/DreamSerializer.html +1 -1
  61. package/docs/functions/ObjectSerializer.html +1 -1
  62. package/docs/functions/ReplicaSafe.html +1 -1
  63. package/docs/functions/STI.html +1 -1
  64. package/docs/functions/SoftDelete.html +1 -1
  65. package/docs/functions/camelize.html +1 -1
  66. package/docs/functions/capitalize.html +1 -1
  67. package/docs/functions/cloneDeepSafe.html +1 -1
  68. package/docs/functions/closeAllDbConnections.html +1 -1
  69. package/docs/functions/compact.html +1 -1
  70. package/docs/functions/dreamDbConnections.html +1 -1
  71. package/docs/functions/dreamPath.html +1 -1
  72. package/docs/functions/expandStiClasses.html +1 -1
  73. package/docs/functions/generateDream.html +1 -1
  74. package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
  75. package/docs/functions/groupBy.html +1 -1
  76. package/docs/functions/hyphenize.html +1 -1
  77. package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
  78. package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
  79. package/docs/functions/intersection.html +1 -1
  80. package/docs/functions/isDreamSerializer.html +1 -1
  81. package/docs/functions/isEmpty.html +1 -1
  82. package/docs/functions/loadRepl.html +1 -1
  83. package/docs/functions/lookupClassByGlobalName.html +1 -1
  84. package/docs/functions/normalizeUnicode.html +1 -1
  85. package/docs/functions/pascalize.html +1 -1
  86. package/docs/functions/pgErrorType.html +1 -1
  87. package/docs/functions/range-1.html +1 -1
  88. package/docs/functions/relativeDreamPath.html +1 -1
  89. package/docs/functions/round.html +1 -1
  90. package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
  91. package/docs/functions/sharedPathPrefix.html +1 -1
  92. package/docs/functions/snakeify.html +1 -1
  93. package/docs/functions/sort.html +1 -1
  94. package/docs/functions/sortBy.html +1 -1
  95. package/docs/functions/sortObjectByKey.html +1 -1
  96. package/docs/functions/sortObjectByValue.html +1 -1
  97. package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
  98. package/docs/functions/uncapitalize.html +1 -1
  99. package/docs/functions/uniq.html +1 -1
  100. package/docs/functions/untypedDb.html +1 -1
  101. package/docs/functions/validateColumn.html +1 -1
  102. package/docs/functions/validateTable.html +1 -1
  103. package/docs/interfaces/BelongsToStatement.html +2 -2
  104. package/docs/interfaces/DecoratorContext.html +2 -2
  105. package/docs/interfaces/DreamAppInitOptions.html +2 -2
  106. package/docs/interfaces/DreamAppOpts.html +2 -2
  107. package/docs/interfaces/EncryptOptions.html +2 -2
  108. package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
  109. package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
  110. package/docs/interfaces/OpenapiDescription.html +2 -2
  111. package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
  112. package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
  113. package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
  114. package/docs/interfaces/SerializerRendererOpts.html +2 -2
  115. package/docs/modules.html +1 -0
  116. package/docs/types/Camelized.html +1 -1
  117. package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
  118. package/docs/types/DateTime.html +1 -1
  119. package/docs/types/DbConnectionType.html +1 -1
  120. package/docs/types/DbTypes.html +1 -1
  121. package/docs/types/DreamAssociationMetadata.html +1 -1
  122. package/docs/types/DreamAttributes.html +1 -1
  123. package/docs/types/DreamClassAssociationAndStatement.html +1 -0
  124. package/docs/types/DreamClassColumn.html +1 -1
  125. package/docs/types/DreamColumn.html +1 -1
  126. package/docs/types/DreamColumnNames.html +1 -1
  127. package/docs/types/DreamLogLevel.html +1 -1
  128. package/docs/types/DreamLogger.html +1 -1
  129. package/docs/types/DreamModelSerializerType.html +1 -1
  130. package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
  131. package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
  132. package/docs/types/DreamParamSafeAttributes.html +1 -1
  133. package/docs/types/DreamParamSafeColumnNames.html +1 -1
  134. package/docs/types/DreamSerializable.html +1 -1
  135. package/docs/types/DreamSerializableArray.html +1 -1
  136. package/docs/types/DreamSerializerKey.html +1 -1
  137. package/docs/types/DreamSerializers.html +1 -1
  138. package/docs/types/DreamTableSchema.html +1 -1
  139. package/docs/types/DreamVirtualColumns.html +1 -1
  140. package/docs/types/EncryptAlgorithm.html +1 -1
  141. package/docs/types/HasManyStatement.html +1 -1
  142. package/docs/types/HasOneStatement.html +1 -1
  143. package/docs/types/Hyphenized.html +1 -1
  144. package/docs/types/IdType.html +1 -1
  145. package/docs/types/OpenapiAllTypes.html +1 -1
  146. package/docs/types/OpenapiFormats.html +1 -1
  147. package/docs/types/OpenapiNumberFormats.html +1 -1
  148. package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
  149. package/docs/types/OpenapiPrimitiveTypes.html +1 -1
  150. package/docs/types/OpenapiSchemaArray.html +1 -1
  151. package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
  152. package/docs/types/OpenapiSchemaBase.html +1 -1
  153. package/docs/types/OpenapiSchemaBody.html +1 -1
  154. package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
  155. package/docs/types/OpenapiSchemaCommonFields.html +1 -1
  156. package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
  157. package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
  158. package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
  159. package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
  160. package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  161. package/docs/types/OpenapiSchemaInteger.html +1 -1
  162. package/docs/types/OpenapiSchemaNull.html +1 -1
  163. package/docs/types/OpenapiSchemaNumber.html +1 -1
  164. package/docs/types/OpenapiSchemaObject.html +1 -1
  165. package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
  166. package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
  167. package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
  168. package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  169. package/docs/types/OpenapiSchemaObjectBase.html +1 -1
  170. package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
  171. package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
  172. package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
  173. package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
  174. package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
  175. package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  176. package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  177. package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  178. package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  179. package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  180. package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  181. package/docs/types/OpenapiSchemaString.html +1 -1
  182. package/docs/types/OpenapiShorthandAllTypes.html +1 -1
  183. package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  184. package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
  185. package/docs/types/OpenapiTypeField.html +1 -1
  186. package/docs/types/Pascalized.html +1 -1
  187. package/docs/types/PrimaryKeyType.html +1 -1
  188. package/docs/types/RoundingPrecision.html +1 -1
  189. package/docs/types/SerializerCasing.html +1 -1
  190. package/docs/types/SimpleObjectSerializerType.html +1 -1
  191. package/docs/types/Snakeified.html +1 -1
  192. package/docs/types/Timestamp.html +1 -1
  193. package/docs/types/UpdateableAssociationProperties.html +1 -1
  194. package/docs/types/UpdateableProperties.html +1 -1
  195. package/docs/types/ValidationType.html +1 -1
  196. package/docs/types/ViewModel.html +1 -1
  197. package/docs/types/ViewModelClass.html +1 -1
  198. package/docs/types/WhereStatementForDream.html +1 -1
  199. package/docs/types/WhereStatementForDreamClass.html +1 -1
  200. package/docs/variables/DateTime-1.html +1 -1
  201. package/docs/variables/DreamConst.html +1 -1
  202. package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
  203. package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
  204. package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
  205. package/docs/variables/ops.html +1 -1
  206. package/docs/variables/primaryKeyTypes.html +1 -1
  207. package/package.json +1 -1
@@ -11,11 +11,13 @@ import CannotPaginateWithLimit from '../errors/pagination/CannotPaginateWithLimi
11
11
  import CannotPaginateWithOffset from '../errors/pagination/CannotPaginateWithOffset.js';
12
12
  import RecordNotFound from '../errors/RecordNotFound.js';
13
13
  import cloneDeepSafe from '../helpers/cloneDeepSafe.js';
14
+ import compact from '../helpers/compact.js';
14
15
  import isObject from '../helpers/isObject.js';
15
16
  import namespaceColumn from '../helpers/namespaceColumn.js';
16
17
  import protectAgainstPollutingAssignment from '../helpers/protectAgainstPollutingAssignment.js';
17
18
  import ops from '../ops/index.js';
18
19
  import computedPaginatePage from './internal/computedPaginatePage.js';
20
+ import extractNestedPaths from './internal/extractNestedPaths.js';
19
21
  import PostgresQueryDriver from './QueryDriver/Postgres.js';
20
22
  export default class Query {
21
23
  /**
@@ -396,8 +398,8 @@ export default class Query {
396
398
  * ```
397
399
  *
398
400
  * @param cb - The callback to call for each found record
399
- * @param options - Options for destroying the instance
400
- * @param options.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
401
+ * @param __namedParameters - Options for batch processing
402
+ * @param __namedParameters.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
401
403
  * @returns void
402
404
  */
403
405
  async findEach(cb, { batchSize = Query.BATCH_SIZES.FIND_EACH } = {}) {
@@ -421,10 +423,10 @@ export default class Query {
421
423
  }
422
424
  /**
423
425
  * Load each specified association using a single SQL query.
424
- * See {@link #preload} for preloading in separate queries.
426
+ * See {@link Query.preload} for preloading in separate queries.
425
427
  *
426
428
  * Note: since leftJoinPreload loads via single query, it has
427
- * some downsides and that may be avoided using {@link #preload}:
429
+ * some downsides and that may be avoided using {@link Query.preload}:
428
430
  * 1. `limit` and `offset` will be automatically removed
429
431
  * 2. `through` associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query
430
432
  * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinPreload('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`. `.preload('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once.
@@ -452,7 +454,7 @@ export default class Query {
452
454
  }
453
455
  /**
454
456
  * Load each specified association using a separate SQL query.
455
- * See {@link #leftJoinPreload} for preloading in a single query.
457
+ * See {@link Query.leftJoinPreload} for preloading in a single query.
456
458
  *
457
459
  * ```ts
458
460
  * const user = await User.query().preload('posts', 'comments', { visibilty: 'public' }, 'replies').first()
@@ -469,6 +471,141 @@ export default class Query {
469
471
  this.fleshOutJoinStatements([], preloadStatements, preloadOnStatements, null, [...args]);
470
472
  return this.clone({ preloadStatements, preloadOnStatements: preloadOnStatements });
471
473
  }
474
+ /**
475
+ * Recursively preloads all Dream associations referenced by `rendersOne` and `rendersMany`
476
+ * in a DreamSerializer. This traverses the entire content tree of serializers to automatically
477
+ * load all necessary associations, eliminating N+1 query problems and removing the need to
478
+ * manually remember which associations to preload for serialization.
479
+ *
480
+ * This method decouples data loading code from data rendering code by having the serializer
481
+ * (rendering code) inform the query (loading code) about which associations are needed.
482
+ * As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or
483
+ * modifying existing ones - the loading code automatically adapts without requiring
484
+ * corresponding modifications to preload statements.
485
+ *
486
+ * This method analyzes the serializer (specified by `serializerKey` or 'default') and
487
+ * automatically preloads all associations that will be needed during serialization.
488
+ *
489
+ * ```ts
490
+ * // Instead of manually specifying all associations:
491
+ * await User.preload('posts', 'comments', 'replies').all()
492
+ *
493
+ * // Automatically preload everything needed for serialization:
494
+ * await User.preloadForSerialization({ serializerKey: 'summary' }).all()
495
+ *
496
+ * // Add where conditions to specific associations during preloading:
497
+ * await User.preloadForSerialization({
498
+ * serializerKey: 'detailed',
499
+ * modifierFn: (dreamClass, associationName) => {
500
+ * if (dreamClass.typeof(Post) && associationName === 'comments') {
501
+ * return { and: { published: true } }
502
+ * }
503
+ * }
504
+ * }).all()
505
+ *
506
+ * // Skip preloading specific associations to handle them manually:
507
+ * await User.preloadForSerialization({
508
+ * serializerKey: 'summary',
509
+ * modifierFn: (dreamClass, associationName) => {
510
+ * if (dreamClass.typeof(User) && associationName === 'posts') {
511
+ * return 'omit' // Handle posts preloading separately with custom logic
512
+ * }
513
+ * }
514
+ * })
515
+ * .preload('posts', { and: { featured: true } }) // Custom preloading
516
+ * .all()
517
+ * ```
518
+ *
519
+ * @param opts - Configuration options for serialization preloading
520
+ * @param opts.serializerKey - The serializer key to use for determining which associations to preload. Defaults to 'default'
521
+ * @param opts.modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading
522
+ * @returns A Query with all serialization associations preloaded
523
+ */
524
+ preloadForSerialization({ serializerKey, modifierFn, } = {}) {
525
+ const preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
526
+ let query = this;
527
+ preloadArgs.forEach(dreamClassAndAssociationNameTupleArray => {
528
+ query = query.preload(...this.convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn));
529
+ });
530
+ return query;
531
+ }
532
+ /**
533
+ * Recursively preloads all Dream associations referenced by `rendersOne` and `rendersMany`
534
+ * in a DreamSerializer using left join preloading. This traverses the entire content tree
535
+ * of serializers to automatically load all necessary associations in a single query,
536
+ * eliminating N+1 query problems and removing the need to manually remember which
537
+ * associations to preload for serialization.
538
+ *
539
+ * This method decouples data loading code from data rendering code by having the serializer
540
+ * (rendering code) inform the query (loading code) about which associations are needed.
541
+ * As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or
542
+ * modifying existing ones - the loading code automatically adapts without requiring
543
+ * corresponding modifications to left join preload statements.
544
+ *
545
+ * This method analyzes the serializer (specified by `serializerKey` or 'default') and
546
+ * automatically left join preloads all associations that will be needed during serialization.
547
+ *
548
+ * Note: Left join preloading loads all data in a single SQL query but has trade-offs compared
549
+ * to regular preloading. See {@link Dream.leftJoinPreload} for details about limitations.
550
+ *
551
+ * ```ts
552
+ * // Instead of manually specifying all associations:
553
+ * await User.leftJoinPreload('posts', 'comments', 'replies').all()
554
+ *
555
+ * // Automatically left join preload everything needed for serialization:
556
+ * await User.leftJoinPreloadForSerialization({ serializerKey: 'summary' }).all()
557
+ *
558
+ * // Add where conditions to specific associations during left join preloading:
559
+ * await User.leftJoinPreloadForSerialization({
560
+ * serializerKey: 'detailed',
561
+ * modifierFn: (dreamClass, associationName) => {
562
+ * if (dreamClass.typeof(Post) && associationName === 'comments') {
563
+ * return { and: { published: true } }
564
+ * }
565
+ * }
566
+ * }).all()
567
+ *
568
+ * // Skip left join preloading specific associations to handle them manually:
569
+ * await User.leftJoinPreloadForSerialization({
570
+ * serializerKey: 'summary',
571
+ * modifierFn: (dreamClass, associationName) => {
572
+ * if (dreamClass.typeof(User) && associationName === 'posts') {
573
+ * return 'omit' // Handle posts preloading separately with custom logic
574
+ * }
575
+ * }
576
+ * })
577
+ * .preload('posts', { and: { featured: true } }) // Custom preloading instead
578
+ * .all()
579
+ * ```
580
+ *
581
+ * @param opts - Configuration options for serialization preloading
582
+ * @param opts.serializerKey - The serializer key to use for determining which associations to preload. Defaults to 'default'
583
+ * @param opts.modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading
584
+ * @returns A Query with all serialization associations left join preloaded
585
+ */
586
+ leftJoinPreloadForSerialization({ serializerKey, modifierFn, } = {}) {
587
+ const preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
588
+ let query = this;
589
+ const counter = { count: 0 };
590
+ preloadArgs.forEach(dreamClassAndAssociationNameTupleArray => {
591
+ query = query.leftJoinPreload(...this.convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn, counter));
592
+ });
593
+ return query;
594
+ }
595
+ convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn, counter) {
596
+ return compact(dreamClassAndAssociationNameTupleArray.flatMap(dreamClassAndAssociationNameTuple => {
597
+ const associationName = dreamClassAndAssociationNameTuple[1];
598
+ const aliasedAssociationName = counter
599
+ ? `${dreamClassAndAssociationNameTuple[1]} as drsz${counter.count++}`
600
+ : dreamClassAndAssociationNameTuple[1];
601
+ if (!modifierFn)
602
+ return aliasedAssociationName;
603
+ const modifier = modifierFn(dreamClassAndAssociationNameTuple[0], associationName);
604
+ if (modifier === 'omit')
605
+ return undefined;
606
+ return [aliasedAssociationName, modifier];
607
+ }));
608
+ }
472
609
  /**
473
610
  * Returns a new Query instance, with the provided
474
611
  * joins statement attached
@@ -783,7 +920,7 @@ export default class Query {
783
920
  * // [User{name: 'a', id: 99}, User{name: 'a', id: 97}, User{ name: 'b', id: 98 } ...]
784
921
  * ```
785
922
  *
786
- * @param orderStatement - Either a string or an object specifying order. If a string, the order is implicitly ascending. If the orderStatement is an object, statements will be provided in the order of the keys set in the object
923
+ * @param arg - Either a string or an object specifying order. If a string, the order is implicitly ascending. If the orderStatement is an object, statements will be provided in the order of the keys set in the object
787
924
  * @returns A cloned Query with the order clause applied
788
925
  */
789
926
  order(arg) {
@@ -885,7 +1022,7 @@ export default class Query {
885
1022
  * })
886
1023
  * ```
887
1024
  *
888
- * @param txn - A DreamTransaction instance (usually collected by calling `ApplicationModel.transaction`)
1025
+ * @param dreamTransaction - A DreamTransaction instance (usually collected by calling `ApplicationModel.transaction`)
889
1026
  * @returns A cloned Query with the transaction applied
890
1027
  *
891
1028
  */
@@ -1082,6 +1219,7 @@ export default class Query {
1082
1219
  * // 2
1083
1220
  * ```
1084
1221
  *
1222
+ * @param opts - Pagination options
1085
1223
  * @param opts.page - the page number that you want to fetch results for
1086
1224
  * @param opts.pageSize - the number of results per page (optional)
1087
1225
  * @returns results.recordCount - A number representing the total number of records matching your query
@@ -1245,11 +1383,11 @@ export default class Query {
1245
1383
  * // 12
1246
1384
  * ```
1247
1385
  *
1248
- * @param options - Options for destroying the instance
1249
- * @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1250
- * @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1251
- * @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1252
- * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1386
+ * @param __namedParameters - Options for destroying the instance
1387
+ * @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1388
+ * @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1389
+ * @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1390
+ * @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1253
1391
  * @returns The number of records that were removed
1254
1392
  */
1255
1393
  async destroy({ skipHooks, cascade, } = {}) {
@@ -1285,18 +1423,18 @@ export default class Query {
1285
1423
  * were destroyed.
1286
1424
  *
1287
1425
  * To destroy without bypassing the SoftDelete
1288
- * decorator, use {@link Query.(destroy:instance) | destroy} instead.
1426
+ * decorator, use {@link Query.destroy} instead.
1289
1427
  *
1290
1428
  * ```ts
1291
1429
  * await User.where({ email: ops.ilike('%burpcollaborator%') }).reallyDestroy()
1292
1430
  * // 12
1293
1431
  * ```
1294
1432
  *
1295
- * @param options - Options for destroying the instance
1296
- * @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1297
- * @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1298
- * @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1299
- * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1433
+ * @param __namedParameters - Options for destroying the instance
1434
+ * @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1435
+ * @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1436
+ * @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1437
+ * @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1300
1438
  * @returns The number of records that were removed
1301
1439
  */
1302
1440
  async reallyDestroy({ skipHooks, cascade, } = {}) {
@@ -1314,11 +1452,11 @@ export default class Query {
1314
1452
  * // 12
1315
1453
  * ```
1316
1454
  *
1317
- * @param options - Options for undestroying the instance
1318
- * @param options.skipHooks - If true, skips applying model hooks during the undestroy operation. Defaults to false
1319
- * @param options.cascade - If false, skips undestroying associations marked `dependent: 'destroy'`. Defaults to true
1320
- * @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade undestroying. Defaults to false
1321
- * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array
1455
+ * @param __namedParameters - Options for undestroying the instance
1456
+ * @param __namedParameters.skipHooks - If true, skips applying model hooks during the undestroy operation. Defaults to false
1457
+ * @param __namedParameters.cascade - If false, skips undestroying associations marked `dependent: 'destroy'`. Defaults to true
1458
+ * @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade undestroying. Defaults to false
1459
+ * @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array
1322
1460
  * @returns The number of records that were removed
1323
1461
  */
1324
1462
  async undestroy({ cascade, skipHooks, } = {}) {
@@ -1346,7 +1484,7 @@ export default class Query {
1346
1484
  * though cascading may still happen at the database level.
1347
1485
  *
1348
1486
  * To apply model hooks and association dependent destroy,
1349
- * use {@link Query.(destroy:instance) | destroy} instead.
1487
+ * use {@link Query.destroy} instead.
1350
1488
  *
1351
1489
  * ```ts
1352
1490
  * await User.where({ email: ops.ilike('%burpcollaborator%').delete() })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Turns RecursiveSerializerInfo into an array of arrays of tuples,
3
+ * each tuple including a Dream class and an association name
4
+ *
5
+ * E.g., returns the following:
6
+ * ```ts
7
+ * [
8
+ * [[Post, 'a'], [Pet, 'b']],
9
+ * [[Post, 'a'], [Balloon, 'd'], [BalloonLine, 'e']]
10
+ * ]
11
+ * ```
12
+ */
13
+ export default function extractNestedPaths(obj) {
14
+ const paths = [];
15
+ function traverse(current, currentPath) {
16
+ const associationNames = Object.keys(current);
17
+ if (associationNames.length === 0) {
18
+ paths.push([...currentPath]);
19
+ return;
20
+ }
21
+ for (const associationName of associationNames) {
22
+ const serializerInfo = current[associationName];
23
+ if (serializerInfo === undefined)
24
+ throw new Error('shouldn’t be undefined');
25
+ const dreamClass = serializerInfo.parentDreamClass;
26
+ const nestedSerializerInfo = serializerInfo.nestedSerializerInfo;
27
+ const tuple = [dreamClass, associationName];
28
+ const newPath = [...currentPath, tuple];
29
+ traverse(nestedSerializerInfo, newPath);
30
+ }
31
+ }
32
+ traverse(obj, []);
33
+ return paths;
34
+ }
@@ -0,0 +1,15 @@
1
+ export function indent(number, { tabWidth = 2 } = {}) {
2
+ let spacesString = '';
3
+ const indentationUnit = indentationUnitString(tabWidth);
4
+ for (let i = 0; i < number; i++) {
5
+ spacesString += indentationUnit;
6
+ }
7
+ return spacesString;
8
+ }
9
+ function indentationUnitString(number) {
10
+ let spacesString = '';
11
+ for (let i = 0; i < number; i++) {
12
+ spacesString += ' ';
13
+ }
14
+ return spacesString;
15
+ }
@@ -6,7 +6,8 @@ import round from '../helpers/round.js';
6
6
  import snakeify from '../helpers/snakeify.js';
7
7
  import DreamSerializerBuilder from './builders/DreamSerializerBuilder.js';
8
8
  import ObjectSerializerBuilder from './builders/ObjectSerializerBuilder.js';
9
- import inferSerializerFromDreamOrViewModel, { inferSerializersFromDreamClassOrViewModelClass, } from './helpers/inferSerializerFromDreamOrViewModel.js';
9
+ import inferSerializerFromDreamOrViewModel from './helpers/inferSerializerFromDreamOrViewModel.js';
10
+ import { serializerForAssociatedClass } from './helpers/serializerForAssociatedClass.js';
10
11
  export default class SerializerRenderer {
11
12
  serializerBuilder;
12
13
  passthroughData;
@@ -51,7 +52,7 @@ export default class SerializerRenderer {
51
52
  case 'delegatedAttribute': {
52
53
  const outputAttributeName = this.setCase(attribute.options?.as ?? attribute.name);
53
54
  const target = data[attribute.targetName];
54
- const value = target[attribute.name] ?? attribute.options?.default;
55
+ const value = target?.[attribute.name] ?? attribute.options?.default;
55
56
  accumulator[outputAttributeName] = applyRenderingOptionsToAttribute(value, attribute.options, this.passthroughData, this.renderOpts);
56
57
  return accumulator;
57
58
  }
@@ -201,19 +202,3 @@ function serializerForAssociatedObject(associatedObject, options) {
201
202
  return options.serializer;
202
203
  return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey);
203
204
  }
204
- /**
205
- * Only used when flatten: true, and the associated model is null, in which case,
206
- * we need something to determine the keys that will be flattened into the
207
- * rendering serializer
208
- */
209
- function serializerForAssociatedClass(object, associationName, options) {
210
- if (options.serializer)
211
- return options.serializer;
212
- if (!object.isDreamInstance)
213
- return null;
214
- const dream = object;
215
- const association = dream['getAssociationMetadata'](associationName);
216
- const associatedClasses = association.modelCB();
217
- const associatedClass = Array.isArray(associatedClasses) ? associatedClasses[0] : associatedClasses;
218
- return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
219
- }
@@ -20,15 +20,74 @@ export default class DreamSerializerBuilder {
20
20
  });
21
21
  return this;
22
22
  }
23
+ /**
24
+ * Includes an attribute from a nested object in the serialized output.
25
+ *
26
+ * Serializes an attribute from a target object. If the target object or
27
+ * the delegated attribute is null/undefined, the `default` option value
28
+ * will be used if provided.
29
+ *
30
+ * @param targetName - The property name containing the target object
31
+ * @param name - The attribute name within the target object
32
+ * @param options - Configuration options including OpenAPI schema, default value, and output customization
33
+ * @returns The serializer builder for method chaining
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Delegate to user.email
38
+ * .delegatedAttribute('user', 'email', {
39
+ * openapi: { type: 'string', format: 'email' }
40
+ * })
41
+ *
42
+ * // With default value for null target or attribute
43
+ * .delegatedAttribute('user', 'displayName', {
44
+ * openapi: { type: 'string' },
45
+ * default: 'Unknown User'
46
+ * })
47
+ * ```
48
+ *
49
+ * See: {@link https://your-docs-url.com/docs/serializers/attributes | Serializer Attributes Documentation}
50
+ */
23
51
  delegatedAttribute(targetName, name, options) {
24
52
  this.attributes.push({
25
53
  type: 'delegatedAttribute',
26
- targetName,
54
+ targetName: targetName,
27
55
  name: name,
28
56
  options: options ?? {},
29
57
  });
30
58
  return this;
31
59
  }
60
+ /**
61
+ * Includes a computed value in the serialized output.
62
+ *
63
+ * Executes a callback function to generate a custom attribute value.
64
+ * The `openapi` option is always required since the return type cannot be inferred.
65
+ *
66
+ * @param name - The attribute name for the computed value
67
+ * @param fn - Callback function that returns the computed value
68
+ * @param options - Configuration options including required OpenAPI schema and optional flattening
69
+ * @returns The serializer builder for method chaining
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Simple computed value
74
+ * .customAttribute('initials', () =>
75
+ * `${user.firstName?.[0]}${user.lastName?.[0]}`.toUpperCase(),
76
+ * { openapi: { type: 'string' } }
77
+ * )
78
+ *
79
+ * // Flattened object properties
80
+ * .customAttribute('metadata', () => ({ age: 30, city: 'NYC' }), {
81
+ * flatten: true,
82
+ * openapi: {
83
+ * age: { type: 'integer' },
84
+ * city: { type: 'string' }
85
+ * }
86
+ * })
87
+ * ```
88
+ *
89
+ * See: {@link https://your-docs-url.com/docs/serializers/attributes | Serializer Attributes Documentation}
90
+ */
32
91
  customAttribute(name, fn, options) {
33
92
  this.attributes.push({
34
93
  type: 'customAttribute',
@@ -38,22 +97,99 @@ export default class DreamSerializerBuilder {
38
97
  });
39
98
  return this;
40
99
  }
100
+ /**
101
+ * Includes a single associated object in the serialized output.
102
+ *
103
+ * When rendering a Dream association, the OpenAPI shape is automatically
104
+ * inferred from that associated Dream's serializer.
105
+ *
106
+ * @param name - The association property name
107
+ * @param options - Configuration options for serialization and schema definition
108
+ * @returns The serializer builder for method chaining
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * // DreamSerializer with inference
113
+ * .rendersOne('user') // Infers from Dream association
114
+ *
115
+ * // With specific serializer
116
+ * .rendersOne('user', { serializerKey: 'summary' })
117
+ *
118
+ * // ObjectSerializer (explicit configuration required)
119
+ * .rendersOne('owner', UserSerializer, {
120
+ * openapi: { $ref: '#/components/schemas/User' }
121
+ * })
122
+ * ```
123
+ *
124
+ * See: {@link https://your-docs-url.com/docs/serializers/associations | Serializer Associations Documentation}
125
+ */
41
126
  rendersOne(name, options) {
42
127
  this.attributes.push({
43
128
  type: 'rendersOne',
44
- name,
129
+ name: name,
45
130
  options: options ?? {},
46
131
  });
47
132
  return this;
48
133
  }
134
+ /**
135
+ * Includes an array of associated objects in the serialized output.
136
+ *
137
+ * When rendering a Dream association, the OpenAPI shape is automatically
138
+ * inferred from that associated Dream's serializer.
139
+ *
140
+ * @param name - The association property name (should be an array)
141
+ * @param options - Configuration options for serialization and schema definition
142
+ * @returns The serializer builder for method chaining
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // DreamSerializer with inference
147
+ * .rendersMany('posts') // Infers from Dream association
148
+ *
149
+ * // With specific serializer
150
+ * .rendersMany('posts', { serializerKey: 'summary' })
151
+ *
152
+ * // ObjectSerializer (explicit configuration required)
153
+ * .rendersMany('articles', ArticleSerializer, {
154
+ * openapi: {
155
+ * type: 'array',
156
+ * items: { $ref: '#/components/schemas/Article' }
157
+ * }
158
+ * })
159
+ * ```
160
+ *
161
+ * See: {@link https://your-docs-url.com/docs/serializers/associations | Serializer Associations Documentation}
162
+ */
49
163
  rendersMany(name, options) {
50
164
  this.attributes.push({
51
165
  type: 'rendersMany',
52
- name,
166
+ name: name,
53
167
  options: options ?? {},
54
168
  });
55
169
  return this;
56
170
  }
171
+ /**
172
+ * Executes the serializer and returns the serialized output.
173
+ *
174
+ * This method processes all defined attributes, custom attributes, delegated attributes,
175
+ * and associations to produce the final serialized object. The result is suitable for
176
+ * JSON.stringify() and API responses.
177
+ *
178
+ * @param passthrough - Additional data to pass through to nested serializers
179
+ * @param opts - Rendering options for customizing the serialization process
180
+ * @returns The serialized object
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const result = UserSerializer(user).render()
185
+ * // Returns: { id: 1, email: 'user@example.com', ... }
186
+ *
187
+ * // With passthrough data
188
+ * const result = UserSerializer(user).render({ currentUserId: 123 })
189
+ * ```
190
+ *
191
+ * See: {@link https://your-docs-url.com/docs/serializers/render | Serializer Rendering Documentation}
192
+ */
57
193
  render(passthrough = {}, opts = {}) {
58
194
  return new SerializerRenderer(this, passthrough, opts).render();
59
195
  }