@rvoh/dream 2.18.0 → 2.19.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 (238) hide show
  1. package/dist/cjs/src/Dream.js +103 -0
  2. package/dist/cjs/src/db/DreamDbConnection.js +6 -0
  3. package/dist/cjs/src/decorators/class/SoftDelete.js +12 -0
  4. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +113 -0
  5. package/dist/cjs/src/dream/Query.js +137 -0
  6. package/dist/cjs/src/dream/QueryDriver/Base.js +84 -0
  7. package/dist/cjs/src/dream/QueryDriver/Kysely.js +358 -67
  8. package/dist/cjs/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
  9. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +21 -13
  10. package/dist/esm/src/Dream.js +103 -0
  11. package/dist/esm/src/db/DreamDbConnection.js +6 -0
  12. package/dist/esm/src/decorators/class/SoftDelete.js +12 -0
  13. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +113 -0
  14. package/dist/esm/src/dream/Query.js +137 -0
  15. package/dist/esm/src/dream/QueryDriver/Base.js +84 -0
  16. package/dist/esm/src/dream/QueryDriver/Kysely.js +358 -67
  17. package/dist/esm/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
  18. package/dist/esm/src/helpers/cli/generateMigrationContent.js +21 -13
  19. package/dist/types/src/Dream.d.ts +97 -4
  20. package/dist/types/src/decorators/class/SoftDelete.d.ts +12 -0
  21. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +103 -0
  22. package/dist/types/src/dream/Query.d.ts +127 -0
  23. package/dist/types/src/dream/QueryDriver/Base.d.ts +69 -0
  24. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +166 -8
  25. package/dist/types/src/errors/CannotNamespaceAssociationFilterToAnotherTable.d.ts +8 -0
  26. package/dist/types/src/types/associations/shared.d.ts +4 -1
  27. package/dist/types/src/types/dream.d.ts +17 -0
  28. package/dist/types/src/types/{associations → types/associations}/shared.ts +10 -1
  29. package/dist/types/src/types/{dream.ts → types/dream.ts} +53 -0
  30. package/dist/types/src/types/{variadic.ts → types/variadic.ts} +179 -140
  31. package/dist/types/src/types/variadic.d.ts +15 -10
  32. package/docs/assets/hierarchy.js +1 -1
  33. package/docs/assets/search.js +1 -1
  34. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  35. package/docs/classes/db.KyselyQueryDriver.html +78 -34
  36. package/docs/classes/db.PostgresQueryDriver.html +79 -35
  37. package/docs/classes/db.QueryDriverBase.html +77 -33
  38. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  39. package/docs/classes/errors.ColumnOverflow.html +3 -3
  40. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  41. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  42. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  43. package/docs/classes/errors.DecryptionError.html +2 -2
  44. package/docs/classes/errors.DecryptionParseError.html +2 -2
  45. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  46. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  47. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  48. package/docs/classes/errors.InvalidClockTime.html +2 -2
  49. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  50. package/docs/classes/errors.InvalidDateTime.html +2 -2
  51. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  52. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  53. package/docs/classes/errors.NotNullViolation.html +3 -3
  54. package/docs/classes/errors.RecordNotFound.html +3 -3
  55. package/docs/classes/errors.ValidationError.html +3 -3
  56. package/docs/classes/index.CalendarDate.html +33 -33
  57. package/docs/classes/index.ClockTime.html +32 -32
  58. package/docs/classes/index.ClockTimeTz.html +35 -35
  59. package/docs/classes/index.DateTime.html +86 -86
  60. package/docs/classes/index.Decorators.html +19 -19
  61. package/docs/classes/index.Dream.html +182 -119
  62. package/docs/classes/index.DreamApp.html +10 -10
  63. package/docs/classes/index.DreamTransaction.html +2 -2
  64. package/docs/classes/index.Env.html +2 -2
  65. package/docs/classes/index.Query.html +144 -57
  66. package/docs/classes/system.CliFileWriter.html +4 -4
  67. package/docs/classes/system.DreamBin.html +2 -2
  68. package/docs/classes/system.DreamCLI.html +7 -7
  69. package/docs/classes/system.DreamImporter.html +2 -2
  70. package/docs/classes/system.DreamLogos.html +2 -2
  71. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  72. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  73. package/docs/classes/system.PathHelpers.html +3 -3
  74. package/docs/classes/utils.Encrypt.html +3 -3
  75. package/docs/classes/utils.Range.html +2 -2
  76. package/docs/functions/db.closeAllDbConnections.html +1 -1
  77. package/docs/functions/db.dreamDbConnections.html +1 -1
  78. package/docs/functions/db.untypedDb.html +1 -1
  79. package/docs/functions/db.validateColumn.html +1 -1
  80. package/docs/functions/db.validateTable.html +1 -1
  81. package/docs/functions/errors.pgErrorType.html +1 -1
  82. package/docs/functions/index.DreamSerializer.html +1 -1
  83. package/docs/functions/index.ObjectSerializer.html +1 -1
  84. package/docs/functions/index.ReplicaSafe.html +1 -1
  85. package/docs/functions/index.STI.html +1 -1
  86. package/docs/functions/index.SoftDelete.html +12 -1
  87. package/docs/functions/utils.camelize.html +1 -1
  88. package/docs/functions/utils.capitalize.html +1 -1
  89. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  90. package/docs/functions/utils.compact.html +1 -1
  91. package/docs/functions/utils.groupBy.html +1 -1
  92. package/docs/functions/utils.hyphenize.html +1 -1
  93. package/docs/functions/utils.intersection.html +1 -1
  94. package/docs/functions/utils.isEmpty.html +1 -1
  95. package/docs/functions/utils.normalizeUnicode.html +1 -1
  96. package/docs/functions/utils.pascalize.html +1 -1
  97. package/docs/functions/utils.percent.html +1 -1
  98. package/docs/functions/utils.range.html +1 -1
  99. package/docs/functions/utils.round.html +1 -1
  100. package/docs/functions/utils.sanitizeString.html +1 -1
  101. package/docs/functions/utils.snakeify.html +1 -1
  102. package/docs/functions/utils.sort.html +1 -1
  103. package/docs/functions/utils.sortBy.html +1 -1
  104. package/docs/functions/utils.sortObjectByKey.html +1 -1
  105. package/docs/functions/utils.sortObjectByValue.html +1 -1
  106. package/docs/functions/utils.uncapitalize.html +1 -1
  107. package/docs/functions/utils.uniq.html +1 -1
  108. package/docs/hierarchy.html +1 -1
  109. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  110. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  111. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  112. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  113. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  114. package/docs/interfaces/types.DecoratorContext.html +2 -2
  115. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  116. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  117. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  118. package/docs/interfaces/types.DurationObject.html +2 -2
  119. package/docs/interfaces/types.EncryptOptions.html +2 -2
  120. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  121. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  122. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  123. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  124. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  125. package/docs/types/openapi.OpenapiFormats.html +1 -1
  126. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  127. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  128. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  130. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  131. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  132. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  133. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  134. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  135. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  136. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  137. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  138. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  139. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  140. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  142. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  145. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  146. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  147. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  148. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  150. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  155. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  156. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  157. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  158. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  159. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  161. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  162. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  163. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  164. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  165. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  166. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  167. package/docs/types/types.CalendarDateObject.html +1 -1
  168. package/docs/types/types.Camelized.html +1 -1
  169. package/docs/types/types.ClockTimeObject.html +1 -1
  170. package/docs/types/types.DbConnectionType.html +1 -1
  171. package/docs/types/types.DbTypes.html +1 -1
  172. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  173. package/docs/types/types.DreamAttributes.html +1 -1
  174. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  175. package/docs/types/types.DreamClassColumn.html +1 -1
  176. package/docs/types/types.DreamColumn.html +1 -1
  177. package/docs/types/types.DreamColumnNames.html +1 -1
  178. package/docs/types/types.DreamLogLevel.html +1 -1
  179. package/docs/types/types.DreamLogger.html +2 -2
  180. package/docs/types/types.DreamModelSerializerType.html +1 -1
  181. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  182. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  183. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  184. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  185. package/docs/types/types.DreamSerializable.html +1 -1
  186. package/docs/types/types.DreamSerializableArray.html +1 -1
  187. package/docs/types/types.DreamSerializerKey.html +1 -1
  188. package/docs/types/types.DreamSerializers.html +1 -1
  189. package/docs/types/types.DreamVirtualColumns.html +1 -1
  190. package/docs/types/types.DurationUnit.html +1 -1
  191. package/docs/types/types.EncryptAlgorithm.html +1 -1
  192. package/docs/types/types.HasManyStatement.html +1 -1
  193. package/docs/types/types.HasOneStatement.html +1 -1
  194. package/docs/types/types.Hyphenized.html +1 -1
  195. package/docs/types/types.Pascalized.html +1 -1
  196. package/docs/types/types.PrimaryKeyType.html +1 -1
  197. package/docs/types/types.RoundingPrecision.html +1 -1
  198. package/docs/types/types.SerializerCasing.html +1 -1
  199. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  200. package/docs/types/types.Snakeified.html +1 -1
  201. package/docs/types/types.StrictInterface.html +1 -1
  202. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  203. package/docs/types/types.UpdateableProperties.html +1 -1
  204. package/docs/types/types.ValidationType.html +1 -1
  205. package/docs/types/types.ViewModel.html +2 -2
  206. package/docs/types/types.ViewModelClass.html +1 -1
  207. package/docs/types/types.WeekdayName.html +1 -1
  208. package/docs/types/types.WhereStatementForDream.html +1 -1
  209. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  210. package/docs/variables/index.DreamConst.html +1 -1
  211. package/docs/variables/index.ops.html +1 -1
  212. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  213. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  214. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  215. package/docs/variables/system.primaryKeyTypes.html +1 -1
  216. package/package.json +3 -3
  217. package/dist/cjs/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
  218. package/dist/cjs/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
  219. package/dist/esm/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
  220. package/dist/esm/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
  221. package/dist/types/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.d.ts +0 -13
  222. package/dist/types/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.d.ts +0 -12
  223. /package/dist/types/src/types/{associations → types/associations}/belongsTo.ts +0 -0
  224. /package/dist/types/src/types/{associations → types/associations}/hasMany.ts +0 -0
  225. /package/dist/types/src/types/{associations → types/associations}/hasOne.ts +0 -0
  226. /package/dist/types/src/types/{calendardate.ts → types/calendardate.ts} +0 -0
  227. /package/dist/types/src/types/{clocktime.ts → types/clocktime.ts} +0 -0
  228. /package/dist/types/src/types/{datetime.ts → types/datetime.ts} +0 -0
  229. /package/dist/types/src/types/{db.ts → types/db.ts} +0 -0
  230. /package/dist/types/src/types/{lifecycle.ts → types/lifecycle.ts} +0 -0
  231. /package/dist/types/src/types/{logger.ts → types/logger.ts} +0 -0
  232. /package/dist/types/src/types/{moduleDeclarations → types/moduleDeclarations}/luxon.d.ts +0 -0
  233. /package/dist/types/src/types/{openapi.ts → types/openapi.ts} +0 -0
  234. /package/dist/types/src/types/{query.ts → types/query.ts} +0 -0
  235. /package/dist/types/src/types/{recursiveSerialization.ts → types/recursiveSerialization.ts} +0 -0
  236. /package/dist/types/src/types/{serializer.ts → types/serializer.ts} +0 -0
  237. /package/dist/types/src/types/{utils.ts → types/utils.ts} +0 -0
  238. /package/dist/types/src/types/{validation.ts → types/validation.ts} +0 -0
@@ -976,6 +976,25 @@ export default class Dream {
976
976
  static async count() {
977
977
  return await this.query().count();
978
978
  }
979
+ /**
980
+ * Retrieves the number of records in each group, keyed by the
981
+ * value of the provided group column (a SQL `GROUP BY`).
982
+ *
983
+ * ```ts
984
+ * await User.countBy('name')
985
+ * // Map(2) { 'fred' => 2, 'zed' => 1 }
986
+ * ```
987
+ *
988
+ * Only groups with at least one matching row appear in the Map; seed absent
989
+ * groups yourself with `map.get(key) ?? 0`. When the group column is nullable,
990
+ * records with a `null` value are grouped under a real `null` key.
991
+ *
992
+ * @param groupColumn - the column to group by
993
+ * @returns A Map from each present group value to the number of records in that group
994
+ */
995
+ static async countBy(groupColumn) {
996
+ return await this.query().countBy(groupColumn);
997
+ }
979
998
  /**
980
999
  * Retrieves the max value of the specified column
981
1000
  * for this model's records.
@@ -991,6 +1010,27 @@ export default class Dream {
991
1010
  static async max(columnName) {
992
1011
  return await this.query().max(columnName);
993
1012
  }
1013
+ /**
1014
+ * Retrieves the max value of the specified column within each group, keyed by
1015
+ * the value of the provided group column (a SQL `GROUP BY`).
1016
+ *
1017
+ * ```ts
1018
+ * await CompositionAsset.maxBy('name', 'score')
1019
+ * // Map(2) { 'primary' => 9, 'secondary' => 4 }
1020
+ * ```
1021
+ *
1022
+ * Only groups with at least one matching row appear in the Map. When the group
1023
+ * column is nullable, records with a `null` value are grouped under a real
1024
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
1025
+ * value.
1026
+ *
1027
+ * @param groupColumn - the column to group by
1028
+ * @param aggregatedColumn - the column to take the max of within each group
1029
+ * @returns A Map from each present group value to the max of the aggregated column in that group
1030
+ */
1031
+ static async maxBy(groupColumn, aggregatedColumn) {
1032
+ return await this.query().maxBy(groupColumn, aggregatedColumn);
1033
+ }
994
1034
  /**
995
1035
  * Retrieves the min value of the specified column
996
1036
  * for this model's records.
@@ -1007,6 +1047,27 @@ export default class Dream {
1007
1047
  static async min(columnName) {
1008
1048
  return await this.query().min(columnName);
1009
1049
  }
1050
+ /**
1051
+ * Retrieves the min value of the specified column within each group, keyed by
1052
+ * the value of the provided group column (a SQL `GROUP BY`).
1053
+ *
1054
+ * ```ts
1055
+ * await CompositionAsset.minBy('name', 'score')
1056
+ * // Map(2) { 'primary' => 1, 'secondary' => 4 }
1057
+ * ```
1058
+ *
1059
+ * Only groups with at least one matching row appear in the Map. When the group
1060
+ * column is nullable, records with a `null` value are grouped under a real
1061
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
1062
+ * value.
1063
+ *
1064
+ * @param groupColumn - the column to group by
1065
+ * @param aggregatedColumn - the column to take the min of within each group
1066
+ * @returns A Map from each present group value to the min of the aggregated column in that group
1067
+ */
1068
+ static async minBy(groupColumn, aggregatedColumn) {
1069
+ return await this.query().minBy(groupColumn, aggregatedColumn);
1070
+ }
1010
1071
  /**
1011
1072
  * Retrieves the sum for all values of the specified column
1012
1073
  * for this model's records.
@@ -1023,6 +1084,27 @@ export default class Dream {
1023
1084
  static async sum(columnName) {
1024
1085
  return await this.query().sum(columnName);
1025
1086
  }
1087
+ /**
1088
+ * Retrieves the sum of the specified column within each group, keyed by
1089
+ * the value of the provided group column (a SQL `GROUP BY`).
1090
+ *
1091
+ * ```ts
1092
+ * await CompositionAsset.sumBy('name', 'score')
1093
+ * // Map(2) { 'primary' => 10, 'secondary' => 4 }
1094
+ * ```
1095
+ *
1096
+ * Only groups with at least one matching row appear in the Map. When the group
1097
+ * column is nullable, records with a `null` value are grouped under a real
1098
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
1099
+ * value.
1100
+ *
1101
+ * @param groupColumn - the column to group by
1102
+ * @param aggregatedColumn - the column to sum within each group
1103
+ * @returns A Map from each present group value to the sum of the aggregated column in that group
1104
+ */
1105
+ static async sumBy(groupColumn, aggregatedColumn) {
1106
+ return await this.query().sumBy(groupColumn, aggregatedColumn);
1107
+ }
1026
1108
  /**
1027
1109
  * Retrieves the average for all values of the specified column
1028
1110
  * for this model's records.
@@ -1039,6 +1121,27 @@ export default class Dream {
1039
1121
  static async avg(columnName) {
1040
1122
  return await this.query().avg(columnName);
1041
1123
  }
1124
+ /**
1125
+ * Retrieves the average of the specified column within each group, keyed by
1126
+ * the value of the provided group column (a SQL `GROUP BY`).
1127
+ *
1128
+ * ```ts
1129
+ * await CompositionAsset.avgBy('name', 'score')
1130
+ * // Map(2) { 'primary' => 5, 'secondary' => 4 }
1131
+ * ```
1132
+ *
1133
+ * Only groups with at least one matching row appear in the Map. When the group
1134
+ * column is nullable, records with a `null` value are grouped under a real
1135
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
1136
+ * value.
1137
+ *
1138
+ * @param groupColumn - the column to group by
1139
+ * @param aggregatedColumn - the column to average within each group
1140
+ * @returns A Map from each present group value to the average of the aggregated column in that group
1141
+ */
1142
+ static async avgBy(groupColumn, aggregatedColumn) {
1143
+ return await this.query().avgBy(groupColumn, aggregatedColumn);
1144
+ }
1042
1145
  /**
1043
1146
  * Persists a new record, setting the provided attributes.
1044
1147
  * Automatically sets createdAt and updatedAt timestamps.
@@ -103,6 +103,12 @@ async function destroyConnectionWithinTimeout(conn, label) {
103
103
  reportLeakedDbConnections(label);
104
104
  }
105
105
  }
106
+ catch (error) {
107
+ // teardown is best-effort (the registry entry has already been removed,
108
+ // so a subsequent getConnection() builds a fresh pool), but a rejected
109
+ // destroy() must not vanish silently
110
+ DreamApp.logWithLevel('error', `[dream] failed to close db connection "${label}"`, error);
111
+ }
106
112
  finally {
107
113
  if (timer)
108
114
  clearTimeout(timer);
@@ -35,6 +35,18 @@ export const SOFT_DELETE_SCOPE_NAME = 'dream:SoftDelete';
35
35
  * return 'customDatetimeField' as const
36
36
  * }
37
37
  * }
38
+ *
39
+ * Note on indexing: Dream deliberately does not index `deleted_at`.
40
+ * The default scope's `WHERE deleted_at IS NULL` matches nearly every
41
+ * row on a healthy table, so a plain b-tree on the column is rarely
42
+ * chosen by the planner while still costing index size and write
43
+ * amplification, and Dream itself never issues a query such an index
44
+ * could serve. If your app needs one, add it yourself based on your
45
+ * own access patterns — on Postgres, the two useful shapes are a
46
+ * composite partial index on your hot lookup columns with
47
+ * `WHERE deleted_at IS NULL` (fast scoped reads), or a partial index
48
+ * `WHERE deleted_at IS NOT NULL` (purge/GC sweeps over soft-deleted
49
+ * rows). Both are Postgres-specific syntax.
38
50
  */
39
51
  export default function SoftDelete() {
40
52
  return function (target) {
@@ -58,6 +58,27 @@ export default class DreamClassTransactionBuilder {
58
58
  async count() {
59
59
  return this.queryInstance().count();
60
60
  }
61
+ /**
62
+ * Retrieves the number of records in each group, keyed by the
63
+ * value of the provided group column (a SQL `GROUP BY`).
64
+ *
65
+ * ```ts
66
+ * await ApplicationModel.transaction(async txn => {
67
+ * await User.txn(txn).countBy('name')
68
+ * // Map(2) { 'fred' => 2, 'zed' => 1 }
69
+ * })
70
+ * ```
71
+ *
72
+ * Only groups with at least one matching row appear in the Map; seed absent
73
+ * groups yourself with `map.get(key) ?? 0`. When the group column is nullable,
74
+ * records with a `null` value are grouped under a real `null` key.
75
+ *
76
+ * @param groupColumn - the column to group by
77
+ * @returns A Map from each present group value to the number of records in that group
78
+ */
79
+ async countBy(groupColumn) {
80
+ return this.queryInstance().countBy(groupColumn);
81
+ }
61
82
  /**
62
83
  * Returns a new Query instance, specifying a limit
63
84
  *
@@ -107,6 +128,29 @@ export default class DreamClassTransactionBuilder {
107
128
  async max(columnName) {
108
129
  return this.queryInstance().max(columnName);
109
130
  }
131
+ /**
132
+ * Retrieves the max value of the specified column within each group, keyed by
133
+ * the value of the provided group column (a SQL `GROUP BY`).
134
+ *
135
+ * ```ts
136
+ * await ApplicationModel.transaction(async txn => {
137
+ * await CompositionAsset.txn(txn).maxBy('name', 'score')
138
+ * // Map(2) { 'primary' => 9, 'secondary' => 4 }
139
+ * })
140
+ * ```
141
+ *
142
+ * Only groups with at least one matching row appear in the Map. When the group
143
+ * column is nullable, records with a `null` value are grouped under a real
144
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
145
+ * value.
146
+ *
147
+ * @param groupColumn - the column to group by
148
+ * @param aggregatedColumn - the column to take the max of within each group
149
+ * @returns A Map from each present group value to the max of the aggregated column in that group
150
+ */
151
+ async maxBy(groupColumn, aggregatedColumn) {
152
+ return this.queryInstance().maxBy(groupColumn, aggregatedColumn);
153
+ }
110
154
  /**
111
155
  * Retrieves the min value of the specified column
112
156
  * for this model's records.
@@ -124,6 +168,29 @@ export default class DreamClassTransactionBuilder {
124
168
  async min(columnName) {
125
169
  return this.queryInstance().min(columnName);
126
170
  }
171
+ /**
172
+ * Retrieves the min value of the specified column within each group, keyed by
173
+ * the value of the provided group column (a SQL `GROUP BY`).
174
+ *
175
+ * ```ts
176
+ * await ApplicationModel.transaction(async txn => {
177
+ * await CompositionAsset.txn(txn).minBy('name', 'score')
178
+ * // Map(2) { 'primary' => 1, 'secondary' => 4 }
179
+ * })
180
+ * ```
181
+ *
182
+ * Only groups with at least one matching row appear in the Map. When the group
183
+ * column is nullable, records with a `null` value are grouped under a real
184
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
185
+ * value.
186
+ *
187
+ * @param groupColumn - the column to group by
188
+ * @param aggregatedColumn - the column to take the min of within each group
189
+ * @returns A Map from each present group value to the min of the aggregated column in that group
190
+ */
191
+ async minBy(groupColumn, aggregatedColumn) {
192
+ return this.queryInstance().minBy(groupColumn, aggregatedColumn);
193
+ }
127
194
  /**
128
195
  * Retrieves the sum value of the specified column
129
196
  * for this Query
@@ -139,6 +206,29 @@ export default class DreamClassTransactionBuilder {
139
206
  async sum(columnName) {
140
207
  return this.queryInstance().sum(columnName);
141
208
  }
209
+ /**
210
+ * Retrieves the sum of the specified column within each group, keyed by
211
+ * the value of the provided group column (a SQL `GROUP BY`).
212
+ *
213
+ * ```ts
214
+ * await ApplicationModel.transaction(async txn => {
215
+ * await CompositionAsset.txn(txn).sumBy('name', 'score')
216
+ * // Map(2) { 'primary' => 10, 'secondary' => 4 }
217
+ * })
218
+ * ```
219
+ *
220
+ * Only groups with at least one matching row appear in the Map. When the group
221
+ * column is nullable, records with a `null` value are grouped under a real
222
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
223
+ * value.
224
+ *
225
+ * @param groupColumn - the column to group by
226
+ * @param aggregatedColumn - the column to sum within each group
227
+ * @returns A Map from each present group value to the sum of the aggregated column in that group
228
+ */
229
+ async sumBy(groupColumn, aggregatedColumn) {
230
+ return this.queryInstance().sumBy(groupColumn, aggregatedColumn);
231
+ }
142
232
  /**
143
233
  * Retrieves the average value of the specified column
144
234
  * for this Query
@@ -154,6 +244,29 @@ export default class DreamClassTransactionBuilder {
154
244
  async avg(columnName) {
155
245
  return this.queryInstance().avg(columnName);
156
246
  }
247
+ /**
248
+ * Retrieves the average of the specified column within each group, keyed by
249
+ * the value of the provided group column (a SQL `GROUP BY`).
250
+ *
251
+ * ```ts
252
+ * await ApplicationModel.transaction(async txn => {
253
+ * await CompositionAsset.txn(txn).avgBy('name', 'score')
254
+ * // Map(2) { 'primary' => 5, 'secondary' => 4 }
255
+ * })
256
+ * ```
257
+ *
258
+ * Only groups with at least one matching row appear in the Map. When the group
259
+ * column is nullable, records with a `null` value are grouped under a real
260
+ * `null` key; a group whose aggregated values are all `null` yields a `null`
261
+ * value.
262
+ *
263
+ * @param groupColumn - the column to group by
264
+ * @param aggregatedColumn - the column to average within each group
265
+ * @returns A Map from each present group value to the average of the aggregated column in that group
266
+ */
267
+ async avgBy(groupColumn, aggregatedColumn) {
268
+ return this.queryInstance().avgBy(groupColumn, aggregatedColumn);
269
+ }
157
270
  /**
158
271
  * Persists a new record, setting the provided attributes.
159
272
  * Automatically sets createdAt and updatedAt timestamps.
@@ -980,6 +980,39 @@ export default class Query {
980
980
  async count() {
981
981
  return await this.dbDriverInstance().count();
982
982
  }
983
+ /**
984
+ * Retrieves the number of records in each group, keyed by the
985
+ * value of the provided group column (a SQL `GROUP BY`).
986
+ *
987
+ * ```ts
988
+ * await User.query().countBy('name')
989
+ * // Map(2) { 'fred' => 2, 'zed' => 1 }
990
+ *
991
+ * await User.where({ email: ops.ilike('%gmail.com') }).countBy('name')
992
+ * // Map(1) { 'fred' => 3 }
993
+ * ```
994
+ *
995
+ * Any `where` clauses, base scopes (soft-delete / STI), and joined-association
996
+ * `and` clauses on the Query carry through automatically. A joined-association
997
+ * column may be grouped on by passing its namespaced name (e.g.
998
+ * `'compositionAssets.name'`).
999
+ *
1000
+ * Only groups with at least one matching row appear in the Map (inherent to
1001
+ * `GROUP BY`); seed absent groups yourself with `map.get(key) ?? 0`. When the
1002
+ * group column is nullable, records with a `null` value are grouped under a real
1003
+ * `null` key.
1004
+ *
1005
+ * ```ts
1006
+ * await Composition.query().innerJoin('compositionAssets').countBy('compositionAssets.name')
1007
+ * // Map(2) { 'primary' => 3, null => 1 }
1008
+ * ```
1009
+ *
1010
+ * @param groupColumn - the column to group by (base or joined-association-namespaced)
1011
+ * @returns A Map from each present group value to the number of records in that group
1012
+ */
1013
+ async countBy(groupColumn) {
1014
+ return await this.dbDriverInstance().countBy(groupColumn);
1015
+ }
983
1016
  /**
984
1017
  * Returns new Query with distinct clause applied.
985
1018
  * If no column is specified, applies distinct to the primary key.
@@ -1038,6 +1071,32 @@ export default class Query {
1038
1071
  async max(columnName) {
1039
1072
  return await this.dbDriverInstance().max(columnName);
1040
1073
  }
1074
+ /**
1075
+ * Retrieves the max value of the specified column within each group, keyed by
1076
+ * the value of the provided group column (a SQL `GROUP BY`).
1077
+ *
1078
+ * ```ts
1079
+ * await CompositionAsset.query().maxBy('name', 'score')
1080
+ * // Map(2) { 'primary' => 9, 'secondary' => 4 }
1081
+ * ```
1082
+ *
1083
+ * Any `where` clauses, base scopes (soft-delete / STI), and joined-association
1084
+ * `and` clauses on the Query carry through automatically. A joined-association
1085
+ * column may be grouped on or aggregated by passing its namespaced name (e.g.
1086
+ * `'compositionAssets.name'`).
1087
+ *
1088
+ * Only groups with at least one matching row appear in the Map (inherent to
1089
+ * `GROUP BY`). When the group column is nullable, records with a `null` value
1090
+ * are grouped under a real `null` key; a group whose aggregated values are all
1091
+ * `null` yields a `null` value.
1092
+ *
1093
+ * @param groupColumn - the column to group by (base or joined-association-namespaced)
1094
+ * @param aggregatedColumn - the column to take the max of within each group
1095
+ * @returns A Map from each present group value to the max of the aggregated column in that group
1096
+ */
1097
+ async maxBy(groupColumn, aggregatedColumn) {
1098
+ return await this.dbDriverInstance().maxBy(groupColumn, aggregatedColumn);
1099
+ }
1041
1100
  /**
1042
1101
  * Retrieves the min value of the specified column
1043
1102
  * for this Query
@@ -1053,6 +1112,32 @@ export default class Query {
1053
1112
  async min(columnName) {
1054
1113
  return await this.dbDriverInstance().min(columnName);
1055
1114
  }
1115
+ /**
1116
+ * Retrieves the min value of the specified column within each group, keyed by
1117
+ * the value of the provided group column (a SQL `GROUP BY`).
1118
+ *
1119
+ * ```ts
1120
+ * await CompositionAsset.query().minBy('name', 'score')
1121
+ * // Map(2) { 'primary' => 1, 'secondary' => 4 }
1122
+ * ```
1123
+ *
1124
+ * Any `where` clauses, base scopes (soft-delete / STI), and joined-association
1125
+ * `and` clauses on the Query carry through automatically. A joined-association
1126
+ * column may be grouped on or aggregated by passing its namespaced name (e.g.
1127
+ * `'compositionAssets.name'`).
1128
+ *
1129
+ * Only groups with at least one matching row appear in the Map (inherent to
1130
+ * `GROUP BY`). When the group column is nullable, records with a `null` value
1131
+ * are grouped under a real `null` key; a group whose aggregated values are all
1132
+ * `null` yields a `null` value.
1133
+ *
1134
+ * @param groupColumn - the column to group by (base or joined-association-namespaced)
1135
+ * @param aggregatedColumn - the column to take the min of within each group
1136
+ * @returns A Map from each present group value to the min of the aggregated column in that group
1137
+ */
1138
+ async minBy(groupColumn, aggregatedColumn) {
1139
+ return await this.dbDriverInstance().minBy(groupColumn, aggregatedColumn);
1140
+ }
1056
1141
  /**
1057
1142
  * Retrieves the sum value of the specified column
1058
1143
  * for this Query
@@ -1068,6 +1153,32 @@ export default class Query {
1068
1153
  async sum(columnName) {
1069
1154
  return await this.dbDriverInstance().sum(columnName);
1070
1155
  }
1156
+ /**
1157
+ * Retrieves the sum of the specified column within each group, keyed by
1158
+ * the value of the provided group column (a SQL `GROUP BY`).
1159
+ *
1160
+ * ```ts
1161
+ * await CompositionAsset.query().sumBy('name', 'score')
1162
+ * // Map(2) { 'primary' => 10, 'secondary' => 4 }
1163
+ * ```
1164
+ *
1165
+ * Any `where` clauses, base scopes (soft-delete / STI), and joined-association
1166
+ * `and` clauses on the Query carry through automatically. A joined-association
1167
+ * column may be grouped on or aggregated by passing its namespaced name (e.g.
1168
+ * `'compositionAssets.name'`).
1169
+ *
1170
+ * Only groups with at least one matching row appear in the Map (inherent to
1171
+ * `GROUP BY`). When the group column is nullable, records with a `null` value
1172
+ * are grouped under a real `null` key; a group whose aggregated values are all
1173
+ * `null` yields a `null` value.
1174
+ *
1175
+ * @param groupColumn - the column to group by (base or joined-association-namespaced)
1176
+ * @param aggregatedColumn - the column to sum within each group
1177
+ * @returns A Map from each present group value to the sum of the aggregated column in that group
1178
+ */
1179
+ async sumBy(groupColumn, aggregatedColumn) {
1180
+ return await this.dbDriverInstance().sumBy(groupColumn, aggregatedColumn);
1181
+ }
1071
1182
  /**
1072
1183
  * Retrieves the average value of the specified column
1073
1184
  * for this Query
@@ -1083,6 +1194,32 @@ export default class Query {
1083
1194
  async avg(columnName) {
1084
1195
  return await this.dbDriverInstance().avg(columnName);
1085
1196
  }
1197
+ /**
1198
+ * Retrieves the average of the specified column within each group, keyed by
1199
+ * the value of the provided group column (a SQL `GROUP BY`).
1200
+ *
1201
+ * ```ts
1202
+ * await CompositionAsset.query().avgBy('name', 'score')
1203
+ * // Map(2) { 'primary' => 5, 'secondary' => 4 }
1204
+ * ```
1205
+ *
1206
+ * Any `where` clauses, base scopes (soft-delete / STI), and joined-association
1207
+ * `and` clauses on the Query carry through automatically. A joined-association
1208
+ * column may be grouped on or aggregated by passing its namespaced name (e.g.
1209
+ * `'compositionAssets.name'`).
1210
+ *
1211
+ * Only groups with at least one matching row appear in the Map (inherent to
1212
+ * `GROUP BY`). When the group column is nullable, records with a `null` value
1213
+ * are grouped under a real `null` key; a group whose aggregated values are all
1214
+ * `null` yields a `null` value.
1215
+ *
1216
+ * @param groupColumn - the column to group by (base or joined-association-namespaced)
1217
+ * @param aggregatedColumn - the column to average within each group
1218
+ * @returns A Map from each present group value to the average of the aggregated column in that group
1219
+ */
1220
+ async avgBy(groupColumn, aggregatedColumn) {
1221
+ return await this.dbDriverInstance().avgBy(groupColumn, aggregatedColumn);
1222
+ }
1086
1223
  /**
1087
1224
  * Plucks the provided fields from the given dream class table
1088
1225
  *
@@ -273,6 +273,23 @@ export default class QueryDriverBase {
273
273
  async max(columnName) {
274
274
  throw new Error('implement max in child class');
275
275
  }
276
+ /**
277
+ * Retrieves the max value of the specified column within each group,
278
+ * keyed by the value of the provided group column.
279
+ *
280
+ * ```ts
281
+ * await CompositionAsset.query().maxBy('name', 'score')
282
+ * // Map(2) { 'primary' => 9, 'secondary' => 4 }
283
+ * ```
284
+ *
285
+ * @param groupColumn - the column to group by
286
+ * @param aggregatedColumn - the column to take the max of within each group
287
+ * @returns A Map from each present group value to the max of the aggregated column in that group
288
+ */
289
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
290
+ async maxBy(groupColumn, aggregatedColumn) {
291
+ throw new Error('implement maxBy in child class');
292
+ }
276
293
  /**
277
294
  * Retrieves the min value of the specified column
278
295
  * for this Query
@@ -289,6 +306,23 @@ export default class QueryDriverBase {
289
306
  async min(columnName) {
290
307
  throw new Error('implement min in child class');
291
308
  }
309
+ /**
310
+ * Retrieves the min value of the specified column within each group,
311
+ * keyed by the value of the provided group column.
312
+ *
313
+ * ```ts
314
+ * await CompositionAsset.query().minBy('name', 'score')
315
+ * // Map(2) { 'primary' => 1, 'secondary' => 4 }
316
+ * ```
317
+ *
318
+ * @param groupColumn - the column to group by
319
+ * @param aggregatedColumn - the column to take the min of within each group
320
+ * @returns A Map from each present group value to the min of the aggregated column in that group
321
+ */
322
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
323
+ async minBy(groupColumn, aggregatedColumn) {
324
+ throw new Error('implement minBy in child class');
325
+ }
292
326
  /**
293
327
  * Retrieves the sum value of the specified column
294
328
  * for this Query
@@ -305,6 +339,23 @@ export default class QueryDriverBase {
305
339
  async sum(columnName) {
306
340
  throw new Error('implement sum in child class');
307
341
  }
342
+ /**
343
+ * Retrieves the sum of the specified column within each group,
344
+ * keyed by the value of the provided group column.
345
+ *
346
+ * ```ts
347
+ * await CompositionAsset.query().sumBy('name', 'score')
348
+ * // Map(2) { 'primary' => 10, 'secondary' => 4 }
349
+ * ```
350
+ *
351
+ * @param groupColumn - the column to group by
352
+ * @param aggregatedColumn - the column to sum within each group
353
+ * @returns A Map from each present group value to the sum of the aggregated column in that group
354
+ */
355
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
356
+ async sumBy(groupColumn, aggregatedColumn) {
357
+ throw new Error('implement sumBy in child class');
358
+ }
308
359
  /**
309
360
  * Retrieves the average value of the specified column
310
361
  * for this Query
@@ -321,6 +372,23 @@ export default class QueryDriverBase {
321
372
  async avg(columnName) {
322
373
  throw new Error('implement avg in child class');
323
374
  }
375
+ /**
376
+ * Retrieves the average of the specified column within each group,
377
+ * keyed by the value of the provided group column.
378
+ *
379
+ * ```ts
380
+ * await CompositionAsset.query().avgBy('name', 'score')
381
+ * // Map(2) { 'primary' => 5, 'secondary' => 4 }
382
+ * ```
383
+ *
384
+ * @param groupColumn - the column to group by
385
+ * @param aggregatedColumn - the column to average within each group
386
+ * @returns A Map from each present group value to the average of the aggregated column in that group
387
+ */
388
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
389
+ async avgBy(groupColumn, aggregatedColumn) {
390
+ throw new Error('implement avgBy in child class');
391
+ }
324
392
  /**
325
393
  * Retrieves the number of records in the database
326
394
  *
@@ -334,6 +402,22 @@ export default class QueryDriverBase {
334
402
  async count() {
335
403
  throw new Error('implement count in child class');
336
404
  }
405
+ /**
406
+ * Retrieves the number of records in each group, keyed by the
407
+ * value of the provided group column.
408
+ *
409
+ * ```ts
410
+ * await User.query().countBy('name')
411
+ * // Map(2) { 'fred' => 2, 'zed' => 1 }
412
+ * ```
413
+ *
414
+ * @param groupColumn - the column to group by
415
+ * @returns A Map from each present group value to the number of records in that group
416
+ */
417
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
418
+ async countBy(groupColumn) {
419
+ throw new Error('implement countBy in child class');
420
+ }
337
421
  /**
338
422
  * @internal
339
423
  *