@rvoh/dream 2.21.1 → 2.22.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 (227) hide show
  1. package/dist/cjs/src/Dream.js +3 -0
  2. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +3 -0
  3. package/dist/cjs/src/dream/Query.js +203 -4
  4. package/dist/cjs/src/dream/QueryDriver/Base.js +26 -0
  5. package/dist/cjs/src/dream/QueryDriver/Kysely.js +46 -4
  6. package/dist/cjs/src/dream/QueryDriver/Postgres.js +21 -0
  7. package/dist/cjs/src/dream/internal/destroyAssociatedRecords.js +76 -4
  8. package/dist/cjs/src/dream/internal/destroyDream.js +6 -7
  9. package/dist/cjs/src/errors/InvalidBatchSize.js +19 -0
  10. package/dist/cjs/src/errors/RowLockIncompatibleWithDistinct.js +14 -0
  11. package/dist/cjs/src/errors/RowLockIncompatibleWithJoinLoad.js +14 -0
  12. package/dist/cjs/src/errors/ops/ArrayEqualityRequiresArrayColumn.js +23 -0
  13. package/dist/cjs/src/ops/index.js +19 -0
  14. package/dist/esm/src/Dream.js +3 -0
  15. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +3 -0
  16. package/dist/esm/src/dream/Query.js +203 -4
  17. package/dist/esm/src/dream/QueryDriver/Base.js +26 -0
  18. package/dist/esm/src/dream/QueryDriver/Kysely.js +46 -4
  19. package/dist/esm/src/dream/QueryDriver/Postgres.js +21 -0
  20. package/dist/esm/src/dream/internal/destroyAssociatedRecords.js +76 -4
  21. package/dist/esm/src/dream/internal/destroyDream.js +6 -7
  22. package/dist/esm/src/errors/InvalidBatchSize.js +19 -0
  23. package/dist/esm/src/errors/RowLockIncompatibleWithDistinct.js +14 -0
  24. package/dist/esm/src/errors/RowLockIncompatibleWithJoinLoad.js +14 -0
  25. package/dist/esm/src/errors/ops/ArrayEqualityRequiresArrayColumn.js +23 -0
  26. package/dist/esm/src/ops/index.js +19 -0
  27. package/dist/types/src/Dream.d.ts +3 -0
  28. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +3 -0
  29. package/dist/types/src/dream/Query.d.ts +140 -3
  30. package/dist/types/src/dream/QueryDriver/Base.d.ts +21 -0
  31. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
  32. package/dist/types/src/dream/QueryDriver/Postgres.d.ts +20 -0
  33. package/dist/types/src/dream/internal/destroyAssociatedRecords.d.ts +22 -3
  34. package/dist/types/src/errors/InvalidBatchSize.d.ts +5 -0
  35. package/dist/types/src/errors/RowLockIncompatibleWithDistinct.d.ts +3 -0
  36. package/dist/types/src/errors/RowLockIncompatibleWithJoinLoad.d.ts +3 -0
  37. package/dist/types/src/errors/ops/ArrayEqualityRequiresArrayColumn.d.ts +8 -0
  38. package/dist/types/src/ops/index.d.ts +19 -0
  39. package/dist/types/src/types/associations/shared.d.ts +2 -2
  40. package/dist/types/src/types/dream.d.ts +8 -0
  41. package/dist/types/src/types/types/associations/shared.ts +84 -55
  42. package/dist/types/src/types/types/dream.ts +30 -0
  43. package/docs/assets/hierarchy.js +1 -1
  44. package/docs/assets/search.js +1 -1
  45. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  46. package/docs/classes/db.KyselyQueryDriver.html +55 -41
  47. package/docs/classes/db.PostgresQueryDriver.html +55 -42
  48. package/docs/classes/db.QueryDriverBase.html +54 -40
  49. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  50. package/docs/classes/errors.ColumnOverflow.html +3 -3
  51. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  52. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  53. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  54. package/docs/classes/errors.DecryptionError.html +2 -2
  55. package/docs/classes/errors.DecryptionParseError.html +2 -2
  56. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  57. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  58. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  59. package/docs/classes/errors.InvalidClockTime.html +2 -2
  60. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  61. package/docs/classes/errors.InvalidDateTime.html +2 -2
  62. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  63. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  64. package/docs/classes/errors.NotNullViolation.html +3 -3
  65. package/docs/classes/errors.RecordNotFound.html +3 -3
  66. package/docs/classes/errors.ValidationError.html +3 -3
  67. package/docs/classes/index.CalendarDate.html +33 -33
  68. package/docs/classes/index.ClockTime.html +32 -32
  69. package/docs/classes/index.ClockTimeTz.html +35 -35
  70. package/docs/classes/index.DateTime.html +86 -86
  71. package/docs/classes/index.Decorators.html +19 -19
  72. package/docs/classes/index.Dream.html +127 -125
  73. package/docs/classes/index.DreamApp.html +10 -10
  74. package/docs/classes/index.DreamTransaction.html +2 -2
  75. package/docs/classes/index.Env.html +2 -2
  76. package/docs/classes/index.Query.html +161 -71
  77. package/docs/classes/system.CliFileWriter.html +4 -4
  78. package/docs/classes/system.DreamBin.html +2 -2
  79. package/docs/classes/system.DreamCLI.html +7 -7
  80. package/docs/classes/system.DreamImporter.html +2 -2
  81. package/docs/classes/system.DreamLogos.html +2 -2
  82. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  83. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  84. package/docs/classes/system.PathHelpers.html +3 -3
  85. package/docs/classes/utils.Encrypt.html +3 -3
  86. package/docs/classes/utils.Range.html +2 -2
  87. package/docs/functions/db.closeAllDbConnections.html +1 -1
  88. package/docs/functions/db.dreamDbConnections.html +1 -1
  89. package/docs/functions/db.untypedDb.html +1 -1
  90. package/docs/functions/db.validateColumn.html +1 -1
  91. package/docs/functions/db.validateTable.html +1 -1
  92. package/docs/functions/errors.pgErrorType.html +1 -1
  93. package/docs/functions/index.DreamSerializer.html +1 -1
  94. package/docs/functions/index.ObjectSerializer.html +1 -1
  95. package/docs/functions/index.ReplicaSafe.html +1 -1
  96. package/docs/functions/index.STI.html +1 -1
  97. package/docs/functions/index.SoftDelete.html +1 -1
  98. package/docs/functions/utils.camelize.html +1 -1
  99. package/docs/functions/utils.capitalize.html +1 -1
  100. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  101. package/docs/functions/utils.compact.html +1 -1
  102. package/docs/functions/utils.groupBy.html +1 -1
  103. package/docs/functions/utils.hyphenize.html +1 -1
  104. package/docs/functions/utils.intersection.html +1 -1
  105. package/docs/functions/utils.isEmpty.html +1 -1
  106. package/docs/functions/utils.normalizeUnicode.html +1 -1
  107. package/docs/functions/utils.pascalize.html +1 -1
  108. package/docs/functions/utils.percent.html +1 -1
  109. package/docs/functions/utils.range.html +1 -1
  110. package/docs/functions/utils.round.html +1 -1
  111. package/docs/functions/utils.sanitizeString.html +1 -1
  112. package/docs/functions/utils.snakeify.html +1 -1
  113. package/docs/functions/utils.sort.html +1 -1
  114. package/docs/functions/utils.sortBy.html +1 -1
  115. package/docs/functions/utils.sortObjectByKey.html +1 -1
  116. package/docs/functions/utils.sortObjectByValue.html +1 -1
  117. package/docs/functions/utils.uncapitalize.html +1 -1
  118. package/docs/functions/utils.uniq.html +1 -1
  119. package/docs/hierarchy.html +1 -1
  120. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  121. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  122. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  123. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  124. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  125. package/docs/interfaces/types.DecoratorContext.html +2 -2
  126. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  127. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  128. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  129. package/docs/interfaces/types.DurationObject.html +2 -2
  130. package/docs/interfaces/types.EncryptOptions.html +2 -2
  131. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  132. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  133. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  134. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  135. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  136. package/docs/types/openapi.OpenapiFormats.html +1 -1
  137. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  138. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  139. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  142. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  145. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  146. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  147. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  148. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  149. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  150. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  151. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  153. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  165. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  166. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  167. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  168. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  169. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  170. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  171. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  172. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  173. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  174. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  175. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  176. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  177. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  178. package/docs/types/types.CalendarDateObject.html +1 -1
  179. package/docs/types/types.Camelized.html +1 -1
  180. package/docs/types/types.ClockTimeObject.html +1 -1
  181. package/docs/types/types.DbConnectionType.html +1 -1
  182. package/docs/types/types.DbTypes.html +1 -1
  183. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  184. package/docs/types/types.DreamAttributes.html +1 -1
  185. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  186. package/docs/types/types.DreamClassColumn.html +1 -1
  187. package/docs/types/types.DreamColumn.html +1 -1
  188. package/docs/types/types.DreamColumnNames.html +1 -1
  189. package/docs/types/types.DreamLogLevel.html +1 -1
  190. package/docs/types/types.DreamLogger.html +2 -2
  191. package/docs/types/types.DreamModelSerializerType.html +1 -1
  192. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  193. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  194. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  195. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  196. package/docs/types/types.DreamSerializable.html +1 -1
  197. package/docs/types/types.DreamSerializableArray.html +1 -1
  198. package/docs/types/types.DreamSerializerKey.html +1 -1
  199. package/docs/types/types.DreamSerializers.html +1 -1
  200. package/docs/types/types.DreamVirtualColumns.html +1 -1
  201. package/docs/types/types.DurationUnit.html +1 -1
  202. package/docs/types/types.EncryptAlgorithm.html +1 -1
  203. package/docs/types/types.HasManyStatement.html +1 -1
  204. package/docs/types/types.HasOneStatement.html +1 -1
  205. package/docs/types/types.Hyphenized.html +1 -1
  206. package/docs/types/types.Pascalized.html +1 -1
  207. package/docs/types/types.PrimaryKeyType.html +1 -1
  208. package/docs/types/types.RoundingPrecision.html +1 -1
  209. package/docs/types/types.SerializerCasing.html +1 -1
  210. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  211. package/docs/types/types.Snakeified.html +1 -1
  212. package/docs/types/types.StrictInterface.html +1 -1
  213. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  214. package/docs/types/types.UpdateableProperties.html +1 -1
  215. package/docs/types/types.ValidationType.html +1 -1
  216. package/docs/types/types.ViewModel.html +2 -2
  217. package/docs/types/types.ViewModelClass.html +1 -1
  218. package/docs/types/types.WeekdayName.html +1 -1
  219. package/docs/types/types.WhereStatementForDream.html +1 -1
  220. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  221. package/docs/variables/index.DreamConst.html +1 -1
  222. package/docs/variables/index.ops.html +38 -23
  223. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  224. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  225. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  226. package/docs/variables/system.primaryKeyTypes.html +1 -1
  227. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ export default class RowLockIncompatibleWithJoinLoad extends Error {
2
+ get message() {
3
+ // A join load builds its own select statement (one row per joined
4
+ // association row, later collapsed in memory), which cannot carry the
5
+ // Query's row lock. Rather than silently performing an unlocked read and
6
+ // dropping the compare-and-set guarantee the caller asked for, refuse.
7
+ return `
8
+ An exclusive row lock was requested on a query that also uses leftJoinPreload.
9
+
10
+ A join-loaded query cannot be locked. Use \`preload\` instead of
11
+ \`leftJoinPreload\`, or drop the lock.
12
+ `;
13
+ }
14
+ }
@@ -0,0 +1,23 @@
1
+ export default class ArrayEqualityRequiresArrayColumn extends Error {
2
+ dreamClass;
3
+ column;
4
+ operator;
5
+ constructor(dreamClass, column, operator) {
6
+ super();
7
+ this.dreamClass = dreamClass;
8
+ this.column = column;
9
+ this.operator = operator;
10
+ }
11
+ get message() {
12
+ return `
13
+ Attempting to compare an array for equality against a column that is not an
14
+ array in the database:
15
+
16
+ ${this.dreamClass.sanitizedName}#${this.column} (operator: ${this.operator})
17
+
18
+ Whole-array equality (\`=\`, \`!=\`, \`<>\`) is only meaningful for array columns.
19
+ If you meant "is this column one of these values", use \`ops.in([...])\`
20
+ (or a bare array) instead.
21
+ `;
22
+ }
23
+ }
@@ -117,11 +117,27 @@ const ops = {
117
117
  /**
118
118
  * Creates an `OpsStatement` using the `=` operator for strict equality.
119
119
  *
120
+ * **Array columns:** passing an array means literal array equality — the
121
+ * whole array is bound as a single parameter, so the column must match
122
+ * element-for-element, in order. This is the opt-in counterpart to a bare
123
+ * array in a where clause (`Post.where({ tags: ['a', 'b'] })`), which means
124
+ * `IN`, and to `ops.any`, which tests for containment of a single element.
125
+ * Element types are constrained by the column, so an enum array column
126
+ * rejects a string that is not a member of its enum.
127
+ *
128
+ * Note that PostgreSQL defines no equality operator for `json` (as opposed
129
+ * to `jsonb`), so `ops.equal` against a `json[]` column raises at the
130
+ * database.
131
+ *
120
132
  * @param equal - The value the column must equal.
121
133
  * @returns An `OpsStatement` using the `=` operator.
122
134
  *
123
135
  * @example
124
136
  * User.where({ role: ops.equal('admin') })
137
+ *
138
+ * @example
139
+ * // matches only rows whose tags column is exactly ['a', 'b'], in that order
140
+ * Post.where({ tags: ops.equal(['a', 'b']) })
125
141
  */
126
142
  equal: (equal) => new OpsStatement('=', equal),
127
143
  /**
@@ -260,6 +276,9 @@ const ops = {
260
276
  /**
261
277
  * Creates an `OpsStatement` using the `!=` operator for inequality.
262
278
  *
279
+ * **Array columns:** as with `ops.equal`, passing an array compares against
280
+ * the whole array rather than against its elements.
281
+ *
263
282
  * @param equal - The value the column must not equal.
264
283
  * @returns An `OpsStatement` using the `!=` operator.
265
284
  *
@@ -1011,6 +1011,9 @@ export default class Dream {
1011
1011
  * // User{email: 'goodbye@world'}
1012
1012
  * ```
1013
1013
  *
1014
+ * Records are always visited in ascending primary key order; any `order`
1015
+ * applied to the query is discarded. See {@link Query.findEach} for why.
1016
+ *
1014
1017
  * @param cb - The callback to call for each found record
1015
1018
  * @param opts - Optional parameters for batch processing
1016
1019
  * @param opts.batchSize - the batch size you wish to collect records in. If not provided, it will default to 1000
@@ -384,6 +384,9 @@ export default class DreamClassTransactionBuilder<DreamClass extends typeof Drea
384
384
  * // User{email: 'goodbye@world'}
385
385
  * ```
386
386
  *
387
+ * Records are always visited in ascending primary key order; any `order`
388
+ * applied to the query is discarded. See {@link Query.findEach} for why.
389
+ *
387
390
  * @param cb - The callback to call for each found record
388
391
  * @param options.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
389
392
  * @returns void
@@ -16,6 +16,7 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
16
16
  */
17
17
  static readonly BATCH_SIZES: {
18
18
  FIND_EACH: number;
19
+ LOCKED_DESTROY: number;
19
20
  PLUCK_EACH: number;
20
21
  PLUCK_EACH_THROUGH: number;
21
22
  };
@@ -282,13 +283,30 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
282
283
  * records matching the Query have been fetched.
283
284
  *
284
285
  * ```ts
285
- * await User.order('id').findEach(user => {
286
+ * await User.where({ deletedAt: null }).findEach(user => {
286
287
  * DreamApp.log(user)
287
288
  * })
288
289
  * // User{id: 1}
289
290
  * // User{id: 2}
290
291
  * ```
291
292
  *
293
+ * IMPORTANT: `findEach` always iterates in ascending primary key order.
294
+ * Any `order` applied to the Query is discarded; passing one has no effect
295
+ * on the order records are visited in.
296
+ *
297
+ * This is not a limitation that can be lifted. `findEach` guarantees that
298
+ * every matching record is visited exactly once, and it delivers that by
299
+ * paginating on a keyset: each batch selects the next `batchSize` records
300
+ * with a primary key greater than the last record of the previous batch.
301
+ * That guarantee only holds over a stable, unique key. Ordering by a
302
+ * mutable, non-unique column would mean a record whose sort value changed
303
+ * partway through the iteration could be skipped entirely or visited twice.
304
+ * ({@link Query.cursorPaginate} can honor an arbitrary order precisely
305
+ * because a paginated UI tolerates that anomaly; a full traversal cannot.)
306
+ *
307
+ * If you need records processed in a particular order, load them with a
308
+ * regular ordered query instead of `findEach`.
309
+ *
292
310
  * @param cb - The callback to call for each found record
293
311
  * @param __namedParameters - Options for batch processing
294
312
  * @param __namedParameters.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
@@ -1209,17 +1227,131 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
1209
1227
  * // 12
1210
1228
  * ```
1211
1229
  *
1230
+ * ## Guarded (compare-and-set) destroy
1231
+ *
1232
+ * By default, `destroy` reads the matching records and then destroys each of
1233
+ * them, so a concurrent writer can change a record in between — the record is
1234
+ * destroyed even though it no longer matches the Query that selected it.
1235
+ * Passing `lock: true` closes that window, making `destroy` a compare-and-set
1236
+ * the way `update(..., { skipHooks: true })` already is:
1237
+ *
1238
+ * ```ts
1239
+ * const destroyed = await Order.where({ status: 'pending' }).destroy({ lock: true })
1240
+ * // 0 — someone else moved every one of them out of 'pending' first
1241
+ * ```
1242
+ *
1243
+ * With `lock: true`, records are processed in batches, and each batch runs in
1244
+ * its own transaction which first re-selects that batch's records with an
1245
+ * exclusive row lock and then destroys them before committing. Because the
1246
+ * database re-checks the Query's conditions after acquiring each row lock,
1247
+ * a record a concurrent transaction has already moved out of the Query drops
1248
+ * out of the locked read and is never destroyed. The returned count is the
1249
+ * number of records actually claimed, so a caller can detect that it lost a
1250
+ * race by comparing the count against what it expected.
1251
+ *
1252
+ * Things to know before relying on it:
1253
+ *
1254
+ * - **The guarantee is per batch, not set-wide.** A run spanning more than one
1255
+ * batch can win a race in batch 1 and lose one in batch 2; nothing holds the
1256
+ * whole matched set still for the duration of the run. Sharing one
1257
+ * transaction across every batch — by threading a transaction onto the Query
1258
+ * itself, `Order.query().txn(txn).where({ ... }).destroy({ lock: true })` —
1259
+ * buys two things: the destroys become all-or-nothing (a rollback undoes the
1260
+ * whole run), and the row locks taken by every batch are held until you
1261
+ * commit. It does **not** extend the compare-and-set to the part of the set
1262
+ * the run has not reached yet: under READ COMMITTED each statement takes a
1263
+ * fresh snapshot, so a record moved out of the Query between batch 1 and
1264
+ * batch 2 still drops out of batch 2's locked read. A genuinely set-wide
1265
+ * guarantee needs REPEATABLE READ / SERIALIZABLE, or a single locking
1266
+ * statement over the whole set.
1267
+ * - **Only a transaction carried by the Query is shared.** Merely wrapping the
1268
+ * call in `Model.transaction(...)` does not thread that transaction onto the
1269
+ * Query — `Dream.transaction` establishes no ambient transaction, it only
1270
+ * hands a `txn` to your callback — so each batch would open its own
1271
+ * transaction on a different pooled connection. Worse, if the surrounding
1272
+ * transaction has already written to any of the matched rows, each batch's
1273
+ * locked read blocks on locks the surrounding transaction holds while that
1274
+ * transaction awaits the destroy: a cycle across two connections, which
1275
+ * Postgres's deadlock detector cannot see, so it hangs rather than aborting.
1276
+ * Always pass the transaction with `.txn(txn)`.
1277
+ * - **Behavior above READ COMMITTED differs.** The drop-out-of-the-result
1278
+ * behavior described above is READ COMMITTED (Postgres's default). Under
1279
+ * REPEATABLE READ or SERIALIZABLE, the same race raises a serialization
1280
+ * failure instead of silently returning a lower count. Both are safe; only
1281
+ * one of them is quiet.
1282
+ * - **`batchSize` defaults to 10 here**, not `findEach`'s 1000, and the
1283
+ * default is deliberately small. Every row in a batch stays locked for the
1284
+ * whole batch, including the time spent running each record's hooks and
1285
+ * `dependent: 'destroy'` cascade, so an oversized batch blocks unrelated
1286
+ * writers touching those rows and surfaces as timeouts somewhere else in the
1287
+ * application, which is very hard to trace back to this destroy. Too small a
1288
+ * batch, by contrast, only slows down this call, which you can see and fix.
1289
+ * Lower it further for models with deep `dependent: 'destroy'` cascades or
1290
+ * expensive destroy hooks.
1291
+ * - **A batch waits, without bound, for whoever holds the row.** The locked
1292
+ * read is a plain `FOR UPDATE`: if another transaction already holds a lock
1293
+ * on one of the batch's rows, this call blocks until that transaction ends,
1294
+ * holding its own transaction and its pooled connection open the whole
1295
+ * time. Dream sets no `lock_timeout` or `statement_timeout`, so a
1296
+ * long-running writer on the other side stalls the run indefinitely and, at
1297
+ * scale, ties up connections. If that is a risk in your application, set a
1298
+ * `lock_timeout` on the connection so a stuck batch fails instead of
1299
+ * hanging.
1300
+ * - **If you are destroying a large set and do not need compare-and-set, do
1301
+ * not pass `lock`.** Plain `destroy()` (hooks and cascade, no locking) or
1302
+ * {@link Query.delete | delete} (a single statement, no hooks or cascade)
1303
+ * are the right tools for bulk removal.
1304
+ *
1212
1305
  * @param __namedParameters - Options for destroying the instance
1213
1306
  * @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1214
1307
  * @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1308
+ * @param __namedParameters.lock - If true, each batch is re-selected with an exclusive row lock inside its own transaction before being destroyed, making the destroy a compare-and-set. Defaults to false
1309
+ * @param __namedParameters.batchSize - The number of records to process per batch. Must be a positive integer. Defaults to 10 when `lock` is true, and to 1000 otherwise
1215
1310
  * @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1216
1311
  * @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1217
1312
  * @returns The number of records that were removed
1313
+ * @throws InvalidBatchSize if `batchSize` is not a positive integer
1218
1314
  */
1219
- destroy({ skipHooks, cascade, }?: {
1315
+ destroy({ skipHooks, cascade, lock, batchSize, }?: {
1220
1316
  skipHooks?: boolean | undefined;
1221
1317
  cascade?: boolean | undefined;
1318
+ lock?: boolean | undefined;
1319
+ batchSize?: number | undefined;
1222
1320
  }): Promise<number>;
1321
+ /**
1322
+ * @internal
1323
+ *
1324
+ * The compare-and-set implementation behind `destroy({ lock: true })`.
1325
+ *
1326
+ * Each batch is handled in two steps inside one transaction:
1327
+ *
1328
+ * 1. an unlocked pluck of the next `batchSize` primary keys, in ascending
1329
+ * primary key order, which establishes the batch's window (its keyset
1330
+ * cursor) and tells us how many records were *attempted*. Only the keys are
1331
+ * read: hydrating and preloading these records would duplicate the work
1332
+ * step 2 does over the same rows; then
1333
+ * 2. a locked re-read restricted to exactly those primary keys, which
1334
+ * re-applies every condition on the Query while acquiring an exclusive row
1335
+ * lock on each row it returns. Records a concurrent transaction has moved
1336
+ * out of the Query since step 1 do not come back, and are the records this
1337
+ * run lost the race for.
1338
+ *
1339
+ * The count comes from step 2's result set, which is why it reports records
1340
+ * actually claimed rather than records attempted. (One thing it does not
1341
+ * account for, because it never has: a `beforeDestroy` hook that sets
1342
+ * `_preventDeletion` leaves the record in place but still counts.)
1343
+ *
1344
+ * The two-step shape is what keeps iteration correct. If the locked read both
1345
+ * claimed rows and advanced the cursor, a batch whose records all lost the
1346
+ * race would come back empty and end the run early, silently skipping every
1347
+ * record after it. Advancing the cursor from step 1 instead means losing a
1348
+ * race skips only the records that were lost.
1349
+ *
1350
+ * When the Query already carries a transaction, that transaction is used for
1351
+ * every batch rather than opening one per batch, so the caller's transaction
1352
+ * boundary wins and the locks are held until the caller commits.
1353
+ */
1354
+ private lockedDestroy;
1223
1355
  /**
1224
1356
  * Destroys all records matching the Query,
1225
1357
  * ignoring the SoftDelete decorator and
@@ -1241,13 +1373,18 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
1241
1373
  * @param __namedParameters - Options for destroying the instance
1242
1374
  * @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
1243
1375
  * @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
1376
+ * @param __namedParameters.lock - If true, each batch is re-selected with an exclusive row lock inside its own transaction before being destroyed, making the destroy a compare-and-set. See {@link Query.destroy} for the full semantics. Defaults to false
1377
+ * @param __namedParameters.batchSize - The number of records to process per batch. Must be a positive integer. Defaults to 10 when `lock` is true, and to 1000 otherwise
1244
1378
  * @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
1245
1379
  * @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
1246
1380
  * @returns The number of records that were removed
1381
+ * @throws InvalidBatchSize if `batchSize` is not a positive integer
1247
1382
  */
1248
- reallyDestroy({ skipHooks, cascade, }?: {
1383
+ reallyDestroy({ skipHooks, cascade, lock, batchSize, }?: {
1249
1384
  skipHooks?: boolean;
1250
1385
  cascade?: boolean;
1386
+ lock?: boolean;
1387
+ batchSize?: number;
1251
1388
  }): Promise<number>;
1252
1389
  /**
1253
1390
  * Undestroys a SoftDelete model, unsetting
@@ -190,7 +190,28 @@ export default class QueryDriverBase<DreamInstance extends Dream> {
190
190
  */
191
191
  takeAll(options?: {
192
192
  columns?: DreamColumnNames<DreamInstance>[];
193
+ lock?: boolean;
193
194
  }): Promise<DreamInstance[]>;
195
+ /**
196
+ * @internal
197
+ *
198
+ * Adapter seam for exclusive row locking. Given the select statement about to
199
+ * be executed, return a statement that additionally acquires an exclusive
200
+ * lock on each row it returns, held until the enclosing transaction commits
201
+ * or rolls back (Postgres: `SELECT ... FOR UPDATE OF <table>`).
202
+ *
203
+ * This is only ever reached when a caller opts into a locked read (today,
204
+ * `Query#destroy({ lock: true })`), and only from inside a transaction. The
205
+ * base implementation throws so that an adapter which has not implemented row
206
+ * locking fails loud rather than silently performing an unlocked read and
207
+ * quietly dropping the compare-and-set guarantee the caller asked for.
208
+ *
209
+ * @param kyselyQuery - the select statement to lock
210
+ * @param tableAlias - the alias of the Query's base table; only rows of that
211
+ * table are locked, so a joined query does not lock unrelated rows and does
212
+ * not fail on the nullable side of an outer join
213
+ */
214
+ applyRowLock(kyselyQuery: SelectQueryBuilder<any, any, any>, tableAlias: string): SelectQueryBuilder<any, any, any>;
194
215
  /**
195
216
  * Retrieves the max value of the specified column
196
217
  * for this Query
@@ -142,6 +142,7 @@ export default class KyselyQueryDriver<DreamInstance extends Dream> extends Quer
142
142
  */
143
143
  takeAll(options?: {
144
144
  columns?: DreamColumnNames<DreamInstance>[];
145
+ lock?: boolean;
145
146
  }): Promise<DreamInstance[]>;
146
147
  /**
147
148
  * Retrieves the max value of the specified column
@@ -1,3 +1,4 @@
1
+ import { type SelectQueryBuilder } from 'kysely';
1
2
  import Dream from '../../Dream.js';
2
3
  import { SchemaBuilderAssociationData, SchemaBuilderColumnData } from '../../helpers/cli/ASTBuilder.js';
3
4
  import KyselyQueryDriver from './Kysely.js';
@@ -34,6 +35,25 @@ export default class PostgresQueryDriver<DreamInstance extends Dream> extends Ky
34
35
  [key: string]: SchemaBuilderColumnData;
35
36
  }>;
36
37
  static duplicateDatabase(connectionName: string): Promise<void>;
38
+ /**
39
+ * @internal
40
+ *
41
+ * Postgres implementation of the row-locking seam: appends
42
+ * `FOR UPDATE OF "<tableAlias>"` to the select, so every row the statement
43
+ * returns is exclusively locked until the enclosing transaction ends.
44
+ *
45
+ * Scoping the lock to the Query's own base table (rather than a bare
46
+ * `FOR UPDATE`) means a query that joins other tables neither locks rows the
47
+ * caller never asked about nor raises Postgres's "FOR UPDATE cannot be
48
+ * applied to the nullable side of an outer join".
49
+ *
50
+ * This is a waiting lock: neither `NOWAIT` nor `SKIP LOCKED` is applied, so a
51
+ * statement that collides with another transaction's lock blocks until that
52
+ * transaction ends. Callers that cannot afford an unbounded wait should set a
53
+ * `lock_timeout` on the connection; see the guarded-destroy notes on
54
+ * `Query#destroy`.
55
+ */
56
+ applyRowLock(kyselyQuery: SelectQueryBuilder<any, any, any>, tableAlias: string): SelectQueryBuilder<any, any, any>;
37
57
  static supportsParallelTestDatabases: boolean;
38
58
  /**
39
59
  * @internal
@@ -7,8 +7,27 @@ import { ReallyDestroyOptions } from './destroyDream.js';
7
7
  * Destroys all HasOne/HasMany associations on this
8
8
  * dream that are marked as `dependent: 'destroy'`.
9
9
  *
10
- * Expects associations to be preloaded onto the dream instance
11
- * via `loadDependentDestroyTree`. Iterates loaded associations
12
- * directly instead of querying the database.
10
+ * The whole dependent-destroy tree is loaded ONCE, at the root of the destroy
11
+ * operation, by `loadDependentDestroyTree`. Descendants reached by recursing
12
+ * through this function consume those already-hydrated associations rather
13
+ * than re-querying their own subtrees.
14
+ *
15
+ * A load still happens here when an association is not hydrated. That covers
16
+ * two cases:
17
+ *
18
+ * 1. the root of the operation, whose associations have not been loaded yet
19
+ * 2. a node past `RECURSIVE_DESTROY_PRELOAD_MAX_REPEATS` levels of
20
+ * self-reference, which the root's preload could not reach
21
+ *
22
+ * The second case is why the check is per-association-hydration rather than a
23
+ * "descendants are already loaded" flag: such a flag would truncate cascades
24
+ * deeper than the preload horizon.
25
+ *
26
+ * NOTE: reusing a hydrated subtree is only sound because the same `options`
27
+ * are forwarded unchanged all the way down the cascade (see the recursive
28
+ * `destroy`/`reallyDestroy` calls below), so every node's associations were
29
+ * loaded under the scopes this node would have used. Nothing in the types
30
+ * enforces that; if `options` ever start varying by depth, this reuse has to
31
+ * be revisited.
13
32
  */
14
33
  export default function destroyAssociatedRecords<I extends Dream>(dream: I, txn: DreamTransaction<I>, options: ReallyDestroyOptions<I>): Promise<void>;
@@ -0,0 +1,5 @@
1
+ export default class InvalidBatchSize extends Error {
2
+ private batchSize;
3
+ constructor(batchSize: unknown);
4
+ get message(): string;
5
+ }
@@ -0,0 +1,3 @@
1
+ export default class RowLockIncompatibleWithDistinct extends Error {
2
+ get message(): string;
3
+ }
@@ -0,0 +1,3 @@
1
+ export default class RowLockIncompatibleWithJoinLoad extends Error {
2
+ get message(): string;
3
+ }
@@ -0,0 +1,8 @@
1
+ import Dream from '../../Dream.js';
2
+ export default class ArrayEqualityRequiresArrayColumn extends Error {
3
+ dreamClass: typeof Dream;
4
+ column: string;
5
+ operator: string;
6
+ constructor(dreamClass: typeof Dream, column: string, operator: string);
7
+ get message(): string;
8
+ }
@@ -112,11 +112,27 @@ declare const ops: {
112
112
  /**
113
113
  * Creates an `OpsStatement` using the `=` operator for strict equality.
114
114
  *
115
+ * **Array columns:** passing an array means literal array equality — the
116
+ * whole array is bound as a single parameter, so the column must match
117
+ * element-for-element, in order. This is the opt-in counterpart to a bare
118
+ * array in a where clause (`Post.where({ tags: ['a', 'b'] })`), which means
119
+ * `IN`, and to `ops.any`, which tests for containment of a single element.
120
+ * Element types are constrained by the column, so an enum array column
121
+ * rejects a string that is not a member of its enum.
122
+ *
123
+ * Note that PostgreSQL defines no equality operator for `json` (as opposed
124
+ * to `jsonb`), so `ops.equal` against a `json[]` column raises at the
125
+ * database.
126
+ *
115
127
  * @param equal - The value the column must equal.
116
128
  * @returns An `OpsStatement` using the `=` operator.
117
129
  *
118
130
  * @example
119
131
  * User.where({ role: ops.equal('admin') })
132
+ *
133
+ * @example
134
+ * // matches only rows whose tags column is exactly ['a', 'b'], in that order
135
+ * Post.where({ tags: ops.equal(['a', 'b']) })
120
136
  */
121
137
  equal: <const T>(equal: T) => OpsStatement<"=", T, undefined>;
122
138
  /**
@@ -269,6 +285,9 @@ declare const ops: {
269
285
  /**
270
286
  * Creates an `OpsStatement` using the `!=` operator for inequality.
271
287
  *
288
+ * **Array columns:** as with `ops.equal`, passing an array compares against
289
+ * the whole array rather than against its elements.
290
+ *
272
291
  * @param equal - The value the column must not equal.
273
292
  * @returns An `OpsStatement` using the `!=` operator.
274
293
  *
@@ -8,7 +8,7 @@ import { ClockTime, ClockTimeTz } from '../../package-exports/index.js';
8
8
  import CalendarDate from '../../utils/datetime/CalendarDate.js';
9
9
  import { DateTime } from '../../utils/datetime/DateTime.js';
10
10
  import { AssociationTableNames } from '../db.js';
11
- import { DefaultScopeName, DefaultScopeNameForTable, DreamBelongsToAssociationMetadata, DreamColumnNames, GlobalModelNameTableMap, ModelColumnType, OrderDir, TableColumnEnumTypeArray, TableColumnNames, TableColumnType, TableNameForGlobalModelName, TrigramOperator } from '../dream.js';
11
+ import { DefaultScopeName, DefaultScopeNameForTable, DreamBelongsToAssociationMetadata, DreamColumnNames, GlobalModelNameTableMap, ModelColumnType, OrderDir, TableColumnEnumTypeArray, TableColumnIsArray, TableColumnNames, TableColumnType, TableNameForGlobalModelName, TrigramOperator } from '../dream.js';
12
12
  import { Inc, MergeUnionOfRecordTypes, ReadonlyTail, UnionToIntersection } from '../utils.js';
13
13
  import { JoinedAssociation } from '../variadic.js';
14
14
  import { BelongsToStatement } from './belongsTo.js';
@@ -25,7 +25,7 @@ export type PassthroughOnClause<PassthroughColumns extends string[]> = Partial<R
25
25
  type DreamSelectable<DB, Schema, TableName extends AssociationTableNames<DB, Schema> & keyof DB> = Partial<{
26
26
  [ColumnName in keyof DB[TableName]]: NonKyselySupportedSupplementalWhereClauseValues<DB, Schema, TableName, ColumnName>;
27
27
  }>;
28
- type NonKyselySupportedSupplementalWhereClauseValues<DB, Schema, TableName, Column, ModelPropertyType = ModelColumnType<Schema, TableName, Column>, ColumnType = TableColumnType<Schema, TableName, Column>, EnumTypeArray extends string[] | null = TableColumnEnumTypeArray<Schema, TableName, Column>, PermanentOpsValTypes = null | readonly [], OpsValType = EnumTypeArray extends null ? ColumnType extends 'bigint' ? TypesAllowedForBigintAgainstTheDb | PermanentOpsValTypes : ModelPropertyType extends DateTime | CalendarDate ? DateTime | CalendarDate | null : ModelPropertyType extends ClockTime ? ClockTime | null : ModelPropertyType extends ClockTimeTz ? ClockTimeTz | null : ModelPropertyType extends number | string ? ModelPropertyType | PermanentOpsValTypes : never : EnumTypeArray extends string[] ? EnumTypeArray[number] | PermanentOpsValTypes : never, ResolvedOpsValType = OpsValType | RawBuilder<unknown>, PartialTypes = EnumTypeArray extends null ? ModelPropertyType extends DateTime ? Range<DateTime | CalendarDate> | Range<null, DateTime | CalendarDate> | (() => Range<DateTime | CalendarDate>) | (() => Range<null, DateTime | CalendarDate>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends ClockTime ? Range<ClockTime> | Range<null, ClockTime> | (() => Range<ClockTime>) | (() => Range<null, ClockTime>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends ClockTimeTz ? Range<ClockTimeTz> | Range<null, ClockTimeTz> | (() => Range<ClockTimeTz>) | (() => Range<null, ClockTimeTz>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends CalendarDate ? Range<DateTime | CalendarDate> | Range<null, DateTime | CalendarDate> | (() => Range<DateTime | CalendarDate>) | (() => Range<null, DateTime | CalendarDate>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType> : ColumnType extends 'bigint' ? Range<TypesAllowedForBigintAgainstTheDb> | Range<null, TypesAllowedForBigintAgainstTheDb> | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends number ? Range<ModelPropertyType> | Range<null, ModelPropertyType> | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends string ? Range<string> | Range<null, string> | OpsStatement<KyselyComparisonOperatorExpression, string, any> | OpsStatement<TrigramOperator, ResolvedOpsValType, ExtraSimilarityArgs> : never : EnumTypeArray extends string[] ? EnumTypeArray | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : never> = PartialTypes extends never ? OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> | CurriedOpsStatement<any, any, any, ResolvedOpsValType> | SelectQueryBuilder<DB, keyof DB, any> : PartialTypes | CurriedOpsStatement<any, any, any, ResolvedOpsValType> | SelectQueryBuilder<DB, keyof DB, any>;
28
+ type NonKyselySupportedSupplementalWhereClauseValues<DB, Schema, TableName, Column, ModelPropertyType = ModelColumnType<Schema, TableName, Column>, ColumnType = TableColumnType<Schema, TableName, Column>, EnumTypeArray extends string[] | null = TableColumnEnumTypeArray<Schema, TableName, Column>, IsArrayColumn = TableColumnIsArray<Schema, TableName, Column>, PermanentOpsValTypes = null | readonly [], ArrayElementType = ModelPropertyType extends readonly (infer ElementType)[] ? ElementType : never, ArrayOpsValType = readonly ArrayElementType[] | PermanentOpsValTypes | RawBuilder<unknown>, OpsValType = IsArrayColumn extends true ? ArrayElementType | PermanentOpsValTypes : EnumTypeArray extends null ? ColumnType extends 'bigint' ? TypesAllowedForBigintAgainstTheDb | PermanentOpsValTypes : ModelPropertyType extends DateTime | CalendarDate ? DateTime | CalendarDate | null : ModelPropertyType extends ClockTime ? ClockTime | null : ModelPropertyType extends ClockTimeTz ? ClockTimeTz | null : ModelPropertyType extends number | string ? ModelPropertyType | PermanentOpsValTypes : never : EnumTypeArray extends string[] ? EnumTypeArray[number] | PermanentOpsValTypes : never, ResolvedOpsValType = OpsValType | RawBuilder<unknown>, PartialTypes = IsArrayColumn extends true ? (EnumTypeArray extends string[] ? EnumTypeArray : never) | OpsStatement<'=' | '!=' | '<>', ArrayOpsValType, any> | OpsStatement<KyselyComparisonOperatorExpression, PermanentOpsValTypes | RawBuilder<unknown>, any> : EnumTypeArray extends null ? ModelPropertyType extends DateTime ? Range<DateTime | CalendarDate> | Range<null, DateTime | CalendarDate> | (() => Range<DateTime | CalendarDate>) | (() => Range<null, DateTime | CalendarDate>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends ClockTime ? Range<ClockTime> | Range<null, ClockTime> | (() => Range<ClockTime>) | (() => Range<null, ClockTime>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends ClockTimeTz ? Range<ClockTimeTz> | Range<null, ClockTimeTz> | (() => Range<ClockTimeTz>) | (() => Range<null, ClockTimeTz>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends CalendarDate ? Range<DateTime | CalendarDate> | Range<null, DateTime | CalendarDate> | (() => Range<DateTime | CalendarDate>) | (() => Range<null, DateTime | CalendarDate>) | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType> : ColumnType extends 'bigint' ? Range<TypesAllowedForBigintAgainstTheDb> | Range<null, TypesAllowedForBigintAgainstTheDb> | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends number ? Range<ModelPropertyType> | Range<null, ModelPropertyType> | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : ModelPropertyType extends string ? Range<string> | Range<null, string> | OpsStatement<KyselyComparisonOperatorExpression, string, any> | OpsStatement<TrigramOperator, ResolvedOpsValType, ExtraSimilarityArgs> : never : EnumTypeArray extends string[] ? EnumTypeArray | OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> : never> = PartialTypes extends never ? OpsStatement<KyselyComparisonOperatorExpression, ResolvedOpsValType, any> | CurriedOpsStatement<any, any, any, ResolvedOpsValType> | SelectQueryBuilder<DB, keyof DB, any> : PartialTypes | CurriedOpsStatement<any, any, any, ResolvedOpsValType> | SelectQueryBuilder<DB, keyof DB, any>;
29
29
  export type WhereStatementForDreamClass<DreamClass extends typeof Dream> = WhereStatement<InstanceType<DreamClass>>;
30
30
  export type WhereStatementForDream<DreamInstance extends Dream> = WhereStatement<DreamInstance>;
31
31
  type AssociationNameToDotReferencedColumns<DB, TableName extends keyof DB, AssociationName> = `${AssociationName & string}.${TableColumnNames<DB, TableName>}`;
@@ -103,6 +103,14 @@ export type DreamTableSchema<DreamInstance extends Dream> = Updateable<DreamInst
103
103
  export type ModelColumnType<Schema, TableName, Column, TableSchema extends Schema[TableName & keyof Schema] = Schema[TableName & keyof Schema], TableColumns extends TableSchema['columns' & keyof TableSchema] = TableSchema['columns' & keyof TableSchema], TableColumnMetadata extends TableColumns[Column & keyof TableColumns] = TableColumns[Column & keyof TableColumns], ColumnType extends TableColumnMetadata['coercedType' & keyof TableColumnMetadata] = TableColumnMetadata['coercedType' & keyof TableColumnMetadata]> = ColumnType;
104
104
  export type TableColumnType<Schema, TableName, Column, TableSchema extends Schema[TableName & keyof Schema] = Schema[TableName & keyof Schema], TableColumns extends TableSchema['columns' & keyof TableSchema] = TableSchema['columns' & keyof TableSchema], TableColumnMetadata extends TableColumns[Column & keyof TableColumns] = TableColumns[Column & keyof TableColumns], ColumnType extends TableColumnMetadata['dbType' & keyof TableColumnMetadata] = TableColumnMetadata['dbType' & keyof TableColumnMetadata]> = ColumnType;
105
105
  export type TableColumnEnumTypeArray<Schema, TableName, Column, TableSchema extends Schema[TableName & keyof Schema] = Schema[TableName & keyof Schema], TableColumns extends TableSchema['columns' & keyof TableSchema] = TableSchema['columns' & keyof TableSchema], TableColumnMetadata extends TableColumns[Column & keyof TableColumns] = TableColumns[Column & keyof TableColumns], EnumTypeCandidate = TableColumnMetadata['enumArrayType' & keyof TableColumnMetadata], EnumTypeArray extends string[] | null = EnumTypeCandidate extends null ? null : EnumTypeCandidate extends undefined ? null : EnumTypeCandidate extends string[] ? EnumTypeCandidate : never> = EnumTypeArray;
106
+ /**
107
+ * Resolves to `true` when the schema marks the column as a database array
108
+ * column (e.g. `text[]`, `integer[]`, `balloon_colors_enum[]`), otherwise
109
+ * `false`. Enum array columns carry both `enumArrayType` and `isArray: true`,
110
+ * so any branch keyed off array-ness must be checked before branching on
111
+ * `TableColumnEnumTypeArray`, which is also set for scalar enum columns.
112
+ */
113
+ export type TableColumnIsArray<Schema, TableName, Column, TableSchema extends Schema[TableName & keyof Schema] = Schema[TableName & keyof Schema], TableColumns extends TableSchema['columns' & keyof TableSchema] = TableSchema['columns' & keyof TableSchema], TableColumnMetadata extends TableColumns[Column & keyof TableColumns] = TableColumns[Column & keyof TableColumns], IsArrayCandidate = TableColumnMetadata['isArray' & keyof TableColumnMetadata]> = [IsArrayCandidate] extends [never] ? false : IsArrayCandidate extends true ? true : false;
106
114
  type AssociationMetadataForTable<Schema, TableName extends keyof Schema> = Schema[TableName]['associations' & keyof Schema[TableName]];
107
115
  export type AssociationNamesForTable<Schema, TableName extends keyof Schema> = keyof AssociationMetadataForTable<Schema, TableName> | AliasedSchemaAssociation<Schema, TableName>;
108
116
  type MetadataForAssociation<Schema, TableName extends keyof Schema, AssociationName, AssociationMetadata = AssociationMetadataForTable<Schema, TableName>> = AssociationMetadata[AssociationName & keyof AssociationMetadata];