@rvoh/dream 2.35.0 → 2.36.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 (302) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/src/Dream.js +258 -55
  3. package/dist/cjs/src/bin/index.js +13 -0
  4. package/dist/cjs/src/cli/index.js +28 -17
  5. package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +23 -21
  6. package/dist/cjs/src/decorators/Decorators.js +26 -4
  7. package/dist/cjs/src/decorators/field/Encrypted.js +30 -6
  8. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +6 -6
  9. package/dist/cjs/src/dream/DreamInstanceTransactionBuilder.js +63 -27
  10. package/dist/cjs/src/dream/LeftJoinLoadBuilder.js +3 -3
  11. package/dist/cjs/src/dream/LoadBuilder.js +1 -1
  12. package/dist/cjs/src/dream/Query.js +542 -167
  13. package/dist/cjs/src/dream/QueryDriver/Base.js +72 -0
  14. package/dist/cjs/src/dream/QueryDriver/Kysely.js +216 -50
  15. package/dist/cjs/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.js +48 -0
  16. package/dist/cjs/src/dream/internal/associations/associationQuery.js +4 -0
  17. package/dist/cjs/src/dream/internal/reload.js +6 -1
  18. package/dist/cjs/src/dream/internal/saveDream.js +8 -2
  19. package/dist/cjs/src/dream/internal/sqlResultToDreamInstance.js +2 -0
  20. package/dist/cjs/src/dream/internal/undestroyDream.js +1 -1
  21. package/dist/cjs/src/dream-app/index.js +38 -0
  22. package/dist/cjs/src/encrypt/InternalEncrypt.js +49 -4
  23. package/dist/cjs/src/encrypt/defaultEncryptedColumnName.js +10 -0
  24. package/dist/cjs/src/encrypt/index.js +76 -9
  25. package/dist/cjs/src/errors/DoNotSetEncryptedFieldsDirectly.js +4 -0
  26. package/dist/cjs/src/errors/associations/UnrecognizedAssociationConditionKeys.js +25 -0
  27. package/dist/cjs/src/helpers/Env.js +28 -0
  28. package/dist/cjs/src/helpers/cli/generateDreamContent.js +2 -1
  29. package/dist/cjs/src/helpers/cli/generateFactoryContent.js +1 -1
  30. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +9 -9
  31. package/dist/cjs/src/helpers/cli/generateSerializerContent.js +21 -8
  32. package/dist/cjs/src/serializer/SerializerRenderer.js +65 -13
  33. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +54 -38
  34. package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +34 -14
  35. package/dist/cjs/src/serializer/helpers/serializersForAssociatedClass.js +43 -0
  36. package/dist/esm/src/Dream.js +258 -55
  37. package/dist/esm/src/bin/index.js +13 -0
  38. package/dist/esm/src/cli/index.js +28 -17
  39. package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +23 -21
  40. package/dist/esm/src/decorators/Decorators.js +26 -4
  41. package/dist/esm/src/decorators/field/Encrypted.js +30 -6
  42. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +6 -6
  43. package/dist/esm/src/dream/DreamInstanceTransactionBuilder.js +63 -27
  44. package/dist/esm/src/dream/LeftJoinLoadBuilder.js +3 -3
  45. package/dist/esm/src/dream/LoadBuilder.js +1 -1
  46. package/dist/esm/src/dream/Query.js +542 -167
  47. package/dist/esm/src/dream/QueryDriver/Base.js +72 -0
  48. package/dist/esm/src/dream/QueryDriver/Kysely.js +216 -50
  49. package/dist/esm/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.js +48 -0
  50. package/dist/esm/src/dream/internal/associations/associationQuery.js +4 -0
  51. package/dist/esm/src/dream/internal/reload.js +6 -1
  52. package/dist/esm/src/dream/internal/saveDream.js +8 -2
  53. package/dist/esm/src/dream/internal/sqlResultToDreamInstance.js +2 -0
  54. package/dist/esm/src/dream/internal/undestroyDream.js +1 -1
  55. package/dist/esm/src/dream-app/index.js +38 -0
  56. package/dist/esm/src/encrypt/InternalEncrypt.js +49 -4
  57. package/dist/esm/src/encrypt/defaultEncryptedColumnName.js +10 -0
  58. package/dist/esm/src/encrypt/index.js +76 -9
  59. package/dist/esm/src/errors/DoNotSetEncryptedFieldsDirectly.js +4 -0
  60. package/dist/esm/src/errors/associations/UnrecognizedAssociationConditionKeys.js +25 -0
  61. package/dist/esm/src/helpers/Env.js +28 -0
  62. package/dist/esm/src/helpers/cli/generateDreamContent.js +2 -1
  63. package/dist/esm/src/helpers/cli/generateFactoryContent.js +1 -1
  64. package/dist/esm/src/helpers/cli/generateMigrationContent.js +9 -9
  65. package/dist/esm/src/helpers/cli/generateSerializerContent.js +21 -8
  66. package/dist/esm/src/serializer/SerializerRenderer.js +65 -13
  67. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +54 -38
  68. package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +34 -14
  69. package/dist/esm/src/serializer/helpers/serializersForAssociatedClass.js +43 -0
  70. package/dist/types/src/Dream.d.ts +206 -42
  71. package/dist/types/src/bin/index.d.ts +7 -1
  72. package/dist/types/src/cli/index.d.ts +13 -0
  73. package/dist/types/src/db/migration-helpers/DreamMigrationHelpers.d.ts +2 -2
  74. package/dist/types/src/decorators/Decorators.d.ts +26 -3
  75. package/dist/types/src/decorators/field/Encrypted.d.ts +6 -1
  76. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +9 -9
  77. package/dist/types/src/dream/DreamInstanceTransactionBuilder.d.ts +52 -25
  78. package/dist/types/src/dream/LeftJoinLoadBuilder.d.ts +4 -4
  79. package/dist/types/src/dream/LoadBuilder.d.ts +1 -1
  80. package/dist/types/src/dream/Query.d.ts +272 -33
  81. package/dist/types/src/dream/QueryDriver/Base.d.ts +63 -1
  82. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +85 -1
  83. package/dist/types/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.d.ts +29 -0
  84. package/dist/types/src/dream-app/index.d.ts +56 -2
  85. package/dist/types/src/encrypt/InternalEncrypt.d.ts +20 -1
  86. package/dist/types/src/encrypt/defaultEncryptedColumnName.d.ts +7 -0
  87. package/dist/types/src/encrypt/index.d.ts +75 -6
  88. package/dist/types/src/errors/associations/UnrecognizedAssociationConditionKeys.d.ts +8 -0
  89. package/dist/types/src/helpers/Env.d.ts +28 -0
  90. package/dist/types/src/package-exports/openapi.d.ts +1 -1
  91. package/dist/types/src/package-exports/types.d.ts +2 -2
  92. package/dist/types/src/serializer/SerializerRenderer.d.ts +13 -0
  93. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +59 -55
  94. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +40 -19
  95. package/dist/types/src/serializer/helpers/serializersForAssociatedClass.d.ts +17 -0
  96. package/dist/types/src/types/lifecycle.d.ts +6 -0
  97. package/dist/types/src/types/openapi.d.ts +52 -29
  98. package/dist/types/src/types/serializer.d.ts +43 -2
  99. package/dist/types/src/types/types/lifecycle.ts +6 -0
  100. package/dist/types/src/types/types/openapi.ts +51 -37
  101. package/dist/types/src/types/types/serializer.ts +62 -2
  102. package/dist/types/src/types/types/variadic.ts +99 -43
  103. package/dist/types/src/types/variadic.d.ts +29 -3
  104. package/docs/assets/hierarchy.js +1 -1
  105. package/docs/assets/highlight.css +4 -11
  106. package/docs/assets/navigation.js +1 -1
  107. package/docs/assets/search.js +1 -1
  108. package/docs/classes/db.DreamMigrationHelpers.html +13 -13
  109. package/docs/classes/db.KyselyQueryDriver.html +85 -46
  110. package/docs/classes/db.PostgresQueryDriver.html +86 -47
  111. package/docs/classes/db.QueryDriverBase.html +84 -45
  112. package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
  113. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  114. package/docs/classes/errors.ColumnOverflow.html +3 -3
  115. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  116. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  117. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  118. package/docs/classes/errors.DecryptionError.html +2 -2
  119. package/docs/classes/errors.DecryptionParseError.html +2 -2
  120. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  121. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  122. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  123. package/docs/classes/errors.InvalidClockTime.html +2 -2
  124. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  125. package/docs/classes/errors.InvalidDateTime.html +2 -2
  126. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  127. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  128. package/docs/classes/errors.NotNullViolation.html +3 -3
  129. package/docs/classes/errors.RecordNotFound.html +3 -3
  130. package/docs/classes/errors.SortableScopeDidNotStabilize.html +3 -3
  131. package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +3 -3
  132. package/docs/classes/errors.ValidationError.html +3 -3
  133. package/docs/classes/index.CalendarDate.html +33 -33
  134. package/docs/classes/index.ClockTime.html +32 -32
  135. package/docs/classes/index.ClockTimeTz.html +35 -35
  136. package/docs/classes/index.DateTime.html +86 -86
  137. package/docs/classes/index.Decorators.html +37 -23
  138. package/docs/classes/index.Dream.html +270 -142
  139. package/docs/classes/index.DreamApp.html +40 -10
  140. package/docs/classes/index.DreamTransaction.html +3 -3
  141. package/docs/classes/index.Env.html +16 -2
  142. package/docs/classes/index.Query.html +331 -129
  143. package/docs/classes/system.CliFileWriter.html +4 -4
  144. package/docs/classes/system.DreamBin.html +6 -2
  145. package/docs/classes/system.DreamCLI.html +7 -7
  146. package/docs/classes/system.DreamImporter.html +2 -2
  147. package/docs/classes/system.DreamLogos.html +2 -2
  148. package/docs/classes/system.DreamSerializerBuilder.html +52 -34
  149. package/docs/classes/system.ObjectSerializerBuilder.html +41 -22
  150. package/docs/classes/system.PathHelpers.html +4 -4
  151. package/docs/classes/utils.Encrypt.html +54 -10
  152. package/docs/classes/utils.Range.html +2 -2
  153. package/docs/functions/db.closeAllDbConnections.html +1 -1
  154. package/docs/functions/db.dreamDbConnections.html +1 -1
  155. package/docs/functions/db.untypedDb.html +1 -1
  156. package/docs/functions/db.validateColumn.html +1 -1
  157. package/docs/functions/db.validateTable.html +1 -1
  158. package/docs/functions/errors.pgErrorType.html +1 -1
  159. package/docs/functions/index.DreamSerializer.html +1 -1
  160. package/docs/functions/index.ObjectSerializer.html +1 -1
  161. package/docs/functions/index.ReplicaSafe.html +1 -1
  162. package/docs/functions/index.STI.html +1 -1
  163. package/docs/functions/index.SoftDelete.html +1 -1
  164. package/docs/functions/utils.camelize.html +1 -1
  165. package/docs/functions/utils.capitalize.html +1 -1
  166. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  167. package/docs/functions/utils.compact.html +1 -1
  168. package/docs/functions/utils.groupBy.html +1 -1
  169. package/docs/functions/utils.hyphenize.html +1 -1
  170. package/docs/functions/utils.intersection.html +1 -1
  171. package/docs/functions/utils.isEmpty.html +1 -1
  172. package/docs/functions/utils.normalizeUnicode.html +1 -1
  173. package/docs/functions/utils.pascalize.html +1 -1
  174. package/docs/functions/utils.percent.html +1 -1
  175. package/docs/functions/utils.range.html +1 -1
  176. package/docs/functions/utils.round.html +1 -1
  177. package/docs/functions/utils.sanitizeString.html +1 -1
  178. package/docs/functions/utils.snakeify.html +1 -1
  179. package/docs/functions/utils.sort.html +1 -1
  180. package/docs/functions/utils.sortBy.html +1 -1
  181. package/docs/functions/utils.sortObjectByKey.html +1 -1
  182. package/docs/functions/utils.sortObjectByValue.html +1 -1
  183. package/docs/functions/utils.uncapitalize.html +1 -1
  184. package/docs/functions/utils.uniq.html +1 -1
  185. package/docs/hierarchy.html +1 -1
  186. package/docs/index.html +1 -1
  187. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  188. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  189. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  190. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  191. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  192. package/docs/interfaces/types.DecoratorContext.html +2 -2
  193. package/docs/interfaces/types.DecryptCallbacks.html +6 -0
  194. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  195. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  196. package/docs/interfaces/types.DreamDbConfig.html +6 -6
  197. package/docs/interfaces/types.DurationObject.html +2 -2
  198. package/docs/interfaces/types.EncryptOptions.html +2 -2
  199. package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +10 -0
  200. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  201. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  202. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  203. package/docs/modules/openapi.html +1 -1
  204. package/docs/modules/types.html +1 -1
  205. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  206. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  207. package/docs/types/openapi.OpenapiFormats.html +1 -1
  208. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  209. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  210. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  211. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  212. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  213. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  214. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  215. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  216. package/docs/types/openapi.OpenapiSchemaCombinatorFields.html +4 -0
  217. package/docs/types/openapi.OpenapiSchemaCommonFields.html +5 -1
  218. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -2
  219. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -2
  220. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -2
  221. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  222. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  223. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  224. package/docs/types/openapi.OpenapiSchemaNull.html +4 -2
  225. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  226. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  227. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +5 -1
  228. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +2 -1
  229. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  230. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  231. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  232. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  233. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -2
  234. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -2
  235. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -2
  236. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  237. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  238. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  239. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  240. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  241. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  242. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  243. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  244. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  245. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  246. package/docs/types/types.CalendarDateObject.html +1 -1
  247. package/docs/types/types.Camelized.html +1 -1
  248. package/docs/types/types.ClockTimeObject.html +1 -1
  249. package/docs/types/types.DbConnectionType.html +1 -1
  250. package/docs/types/types.DbTypes.html +1 -1
  251. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  252. package/docs/types/types.DreamAttributes.html +1 -1
  253. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  254. package/docs/types/types.DreamClassColumn.html +1 -1
  255. package/docs/types/types.DreamColumn.html +1 -1
  256. package/docs/types/types.DreamColumnNames.html +1 -1
  257. package/docs/types/types.DreamLogLevel.html +1 -1
  258. package/docs/types/types.DreamLogger.html +2 -2
  259. package/docs/types/types.DreamModelSerializerType.html +1 -1
  260. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  261. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  262. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  263. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  264. package/docs/types/types.DreamSerializable.html +1 -1
  265. package/docs/types/types.DreamSerializableArray.html +1 -1
  266. package/docs/types/types.DreamSerializerKey.html +1 -1
  267. package/docs/types/types.DreamSerializers.html +1 -1
  268. package/docs/types/types.DreamVirtualColumns.html +1 -1
  269. package/docs/types/types.DurationUnit.html +1 -1
  270. package/docs/types/types.EncryptAlgorithm.html +1 -1
  271. package/docs/types/types.HasManyStatement.html +1 -1
  272. package/docs/types/types.HasOneStatement.html +1 -1
  273. package/docs/types/types.Hyphenized.html +1 -1
  274. package/docs/types/types.Pascalized.html +1 -1
  275. package/docs/types/types.PrimaryKeyType.html +1 -1
  276. package/docs/types/types.RoundingPrecision.html +1 -1
  277. package/docs/types/types.SerializerCasing.html +1 -1
  278. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  279. package/docs/types/types.Snakeified.html +1 -1
  280. package/docs/types/types.StrictInterface.html +1 -1
  281. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  282. package/docs/types/types.UpdateableProperties.html +1 -1
  283. package/docs/types/types.ValidationType.html +1 -1
  284. package/docs/types/types.ViewModel.html +2 -2
  285. package/docs/types/types.ViewModelClass.html +1 -1
  286. package/docs/types/types.WeekdayName.html +1 -1
  287. package/docs/types/types.WhereStatementForDream.html +1 -1
  288. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  289. package/docs/variables/index.DreamConst.html +1 -1
  290. package/docs/variables/index.ops.html +1 -1
  291. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  292. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  293. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  294. package/docs/variables/system.primaryKeyTypes.html +1 -1
  295. package/package.json +1 -1
  296. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +0 -37
  297. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +0 -37
  298. package/dist/types/src/serializer/helpers/serializerForAssociatedClass.d.ts +0 -9
  299. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +0 -1
  300. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +0 -1
  301. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +0 -1
  302. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +0 -1
package/README.md CHANGED
@@ -261,11 +261,11 @@ const userIds = await User.where({ name: ops.similarity('steeven hawkins') }).pl
261
261
  const min = await User.where({ name: ops.similarity('steeven hawkins') }).min('id')
262
262
  const max = await User.where({ name: ops.similarity('steeven hawkins') }).max('id')
263
263
  const count = await User.where({ name: ops.similarity('steeven hawkins') }).count()
264
- const petIds = await User.innerJoin('pets', { name: ops.similarity('fido') }).pluck('pets.id')
264
+ const petIds = await User.innerJoin('pets', { and: { name: ops.similarity('fido') } }).pluck('pets.id')
265
265
  const numUpdatedRecords = await User.where({ name: ops.similarity('steeven hawkins') }).update({
266
266
  name: 'Stephen Hawking',
267
267
  })
268
- const users = await User.innerJoin('pets', { name: ops.similarity('fido') }).all()
268
+ const users = await User.innerJoin('pets', { and: { name: ops.similarity('fido') } }).all()
269
269
 
270
270
  const user = await User.first()
271
271
  const pets = await user
@@ -8,6 +8,7 @@ import DreamApp from './dream-app/index.js';
8
8
  import { RECURSIVE_SERIALIZATION_MAX_REPEATS } from './dream/constants.js';
9
9
  import DreamClassTransactionBuilder from './dream/DreamClassTransactionBuilder.js';
10
10
  import DreamInstanceTransactionBuilder from './dream/DreamInstanceTransactionBuilder.js';
11
+ import assertRecognizedAssociationConditionKeys, { DESTROY_ASSOCIATION_OPTION_KEYS, UPDATE_ASSOCIATION_OPTION_KEYS, } from './dream/internal/associations/assertRecognizedAssociationConditionKeys.js';
11
12
  import associationQuery from './dream/internal/associations/associationQuery.js';
12
13
  import associationUpdateQuery from './dream/internal/associations/associationUpdateQuery.js';
13
14
  import createAssociation from './dream/internal/associations/createAssociation.js';
@@ -30,6 +31,7 @@ import updateOrCreateBy from './dream/internal/updateOrCreateBy.js';
30
31
  import LeftJoinLoadBuilder from './dream/LeftJoinLoadBuilder.js';
31
32
  import LoadBuilder from './dream/LoadBuilder.js';
32
33
  import Query from './dream/Query.js';
34
+ import InternalEncrypt from './encrypt/InternalEncrypt.js';
33
35
  import CannotAssociationQueryOnUnpersistedDream from './errors/associations/CannotAssociationQueryOnUnpersistedDream.js';
34
36
  import CannotCreateAssociationOnUnpersistedDream from './errors/associations/CannotCreateAssociationOnUnpersistedDream.js';
35
37
  import CannotDestroyAssociationOnUnpersistedDream from './errors/associations/CannotDestroyAssociationOnUnpersistedDream.js';
@@ -40,6 +42,7 @@ import CannotCallUndestroyOnANonSoftDeleteModel from './errors/CannotCallUndestr
40
42
  import ConstructorOnlyForInternalUse from './errors/ConstructorOnlyForInternalUse.js';
41
43
  import CreateOrFindByFailedToCreateAndFind from './errors/CreateOrFindByFailedToCreateAndFind.js';
42
44
  import CreateOrUpdateByFailedToCreateAndUpdate from './errors/CreateOrUpdateByFailedToCreateAndUpdate.js';
45
+ import DoNotSetEncryptedFieldsDirectly from './errors/DoNotSetEncryptedFieldsDirectly.js';
43
46
  import GlobalNameNotSet from './errors/dream-app/GlobalNameNotSet.js';
44
47
  import DreamMissingRequiredOverride from './errors/DreamMissingRequiredOverride.js';
45
48
  import NonExistentScopeProvidedToResort from './errors/NonExistentScopeProvidedToResort.js';
@@ -1429,6 +1432,67 @@ export default class Dream {
1429
1432
  static async findEach(cb, opts) {
1430
1433
  await this.query().findEach(cb, opts);
1431
1434
  }
1435
+ /**
1436
+ * Encrypts every value of the model's `@deco.Encrypted` properties again
1437
+ * with the current column encryption key, so that reading them no longer
1438
+ * needs the `legacy` key. It is a step of a key rotation (see
1439
+ * `Encrypt.generateKey`): run it in each environment, once that
1440
+ * environment has deployed its rotated keys, from a console or a script,
1441
+ * not from a migration, whose result would then depend on the keys a given
1442
+ * environment has configured when the migration runs.
1443
+ *
1444
+ * ```ts
1445
+ * await User.reencryptEncryptedColumns()
1446
+ * ```
1447
+ *
1448
+ * It walks every row of the model's table, including rows its default
1449
+ * scopes (such as soft delete) hide and an STI model's siblings, in batches in primary key order, and opens
1450
+ * each non-null value with the current key or, failing that, the legacy
1451
+ * one (see `Encrypt.reencrypt`). Each row is written on its own, with
1452
+ * no model hooks and without changing `updatedAt`, and only while the
1453
+ * column still holds the value read, so a row an app saves in between
1454
+ * keeps the newer value. Reading values here does not fire
1455
+ * `dreamApp.on('encryption:legacy-key-used', ...)`.
1456
+ *
1457
+ * **Run it only once every server encrypts with the new key.** It walks
1458
+ * the table once, so a row a server still configured with the old
1459
+ * `current` writes behind it stays encrypted with the old key, and so does
1460
+ * old-key ciphertext later written straight into a backing column
1461
+ * (`setAttribute`, `setAttributes` and `updateAttributes` accept, for a
1462
+ * backing column, ciphertext that either key opens). Once no writer uses
1463
+ * the old key and this has run for every model with encrypted properties,
1464
+ * `legacy` is safe to drop.
1465
+ *
1466
+ * **A value neither key opens stops it**, throwing as reading the property
1467
+ * would (`DecryptionRotationError` while a `legacy` key is configured,
1468
+ * `DecryptionError` without one). The rows rewritten before it stay
1469
+ * rewritten; running it again once the value is fixed rewrites them again
1470
+ * harmlessly.
1471
+ *
1472
+ * @param opts - Optional parameters
1473
+ * @param opts.batchSize - how many rows to read per batch. Defaults to 1000
1474
+ */
1475
+ static async reencryptEncryptedColumns(opts) {
1476
+ const encryptedColumnNames = this.encryptedAttributes.map(attribute => attribute.encryptedColumnName);
1477
+ if (!encryptedColumnNames.length)
1478
+ return;
1479
+ const tableClass = this.stiBaseClassOrOwnClass;
1480
+ await tableClass.removeAllDefaultScopes().findEach(async (record) => {
1481
+ for (const columnName of encryptedColumnNames) {
1482
+ const ciphertext = record.getAttribute(columnName);
1483
+ if (ciphertext === null || ciphertext === undefined)
1484
+ continue;
1485
+ // written through Kysely, since Query#update refuses any value for a
1486
+ // backing column, ciphertext included
1487
+ await tableClass
1488
+ .removeAllDefaultScopes()
1489
+ .where({ [tableClass.primaryKey]: record.primaryKeyValue(), [columnName]: ciphertext })
1490
+ .toKysely('update')
1491
+ .set({ [columnName]: InternalEncrypt.reencryptColumn(ciphertext) })
1492
+ .execute();
1493
+ }
1494
+ }, opts);
1495
+ }
1432
1496
  /**
1433
1497
  * Returns a new instance of Query scoped to the given
1434
1498
  * model class. Especially useful with {@link Query#toKysely}.
@@ -1560,12 +1624,12 @@ export default class Dream {
1560
1624
  * planned drop safe.
1561
1625
  *
1562
1626
  * ```ts
1563
- * const user = await User.leftJoinPreload('posts', 'comments', { visibilty: 'public' }, 'replies').first()
1627
+ * const user = await User.leftJoinPreload('posts', 'comments', { and: { visibility: 'public' } }, 'replies').first()
1564
1628
  * console.log(user.posts[0].comments[0].replies)
1565
1629
  * // [Reply{id: 1}, Reply{id: 2}]
1566
1630
  * ```
1567
1631
  *
1568
- * @param args - A chain of association names and where clauses
1632
+ * @param args - A chain of association names and and/andNot/andAny clauses
1569
1633
  * @returns A query for this model with the include statement applied
1570
1634
  */
1571
1635
  static leftJoinPreload(...args) {
@@ -1582,12 +1646,12 @@ export default class Dream {
1582
1646
  * Preload is useful for avoiding the N+1 query problem
1583
1647
  *
1584
1648
  * ```ts
1585
- * const user = await User.preload('posts', 'comments', { visibilty: 'public' }, 'replies').first()
1649
+ * const user = await User.preload('posts', 'comments', { and: { visibility: 'public' } }, 'replies').first()
1586
1650
  * console.log(user.posts[0].comments[0].replies)
1587
1651
  * // [Reply{id: 1}, Reply{id: 2}]
1588
1652
  * ```
1589
1653
  *
1590
- * @param args - A chain of association names and where clauses
1654
+ * @param args - A chain of association names and and/andNot/andAny clauses
1591
1655
  * @returns A query for this model with the preload statement applied
1592
1656
  */
1593
1657
  static preload(...args) {
@@ -1647,7 +1711,7 @@ export default class Dream {
1647
1711
  * await User.innerJoin('posts').first()
1648
1712
  * ```
1649
1713
  *
1650
- * @param args - A chain of association names and where clauses
1714
+ * @param args - A chain of association names and and/andNot/andAny clauses
1651
1715
  * @returns A Query for this model with the inner join clause applied
1652
1716
  */
1653
1717
  static innerJoin(...args) {
@@ -1661,7 +1725,7 @@ export default class Dream {
1661
1725
  * await user.innerJoin('posts').first()
1662
1726
  * ```
1663
1727
  *
1664
- * @param args - A chain of association names and where clauses
1728
+ * @param args - A chain of association names and and/andNot/andAny clauses
1665
1729
  * @returns A Query for this model with the inner join clause applied
1666
1730
  */
1667
1731
  innerJoin(...args) {
@@ -1675,7 +1739,7 @@ export default class Dream {
1675
1739
  * await User.leftJoin('posts').first()
1676
1740
  * ```
1677
1741
  *
1678
- * @param args - A chain of association names and where clauses
1742
+ * @param args - A chain of association names and and/andNot/andAny clauses
1679
1743
  * @returns A Query for this model with the left join clause applied
1680
1744
  */
1681
1745
  static leftJoin(...args) {
@@ -1689,7 +1753,7 @@ export default class Dream {
1689
1753
  * await user.leftJoin('posts').first()
1690
1754
  * ```
1691
1755
  *
1692
- * @param args - A chain of association names and where clauses
1756
+ * @param args - A chain of association names and and/andNot/andAny clauses
1693
1757
  * @returns A Query for this model with the left join clause applied
1694
1758
  */
1695
1759
  leftJoin(...args) {
@@ -2589,6 +2653,15 @@ export default class Dream {
2589
2653
  * const user = User.new({ email: 'how@yadoin' })
2590
2654
  * ```
2591
2655
  *
2656
+ * @param opts - the attributes to set on the new instance
2657
+ * @param additionalOpts - optional parameters
2658
+ * @param additionalOpts.bypassUserDefinedSetters - if true, sets the attributes
2659
+ * as {@link Dream.setAttributes | setAttributes} does, bypassing any
2660
+ * custom-defined setters. An encrypted property (e.g. `secret`) is still
2661
+ * encrypted, and an `@Encrypted` backing column (e.g. `encryptedSecret`)
2662
+ * accepts only ciphertext that decrypts with this app's column encryption
2663
+ * keys, or `null`; anything else throws (see
2664
+ * {@link Dream.setAttribute | setAttribute}). Defaults to false
2592
2665
  * @returns A new (unpersisted) instance of the provided dream class
2593
2666
  */
2594
2667
  static new(opts, additionalOpts = {}) {
@@ -2658,7 +2731,7 @@ export default class Dream {
2658
2731
  *
2659
2732
  * Used for determining which attributes to update
2660
2733
  */
2661
- static extractAttributesFromUpdateableProperties(attributes, dreamInstance, { bypassUserDefinedSetters = false } = {}) {
2734
+ static extractAttributesFromUpdateableProperties(attributes, dreamInstance, { bypassUserDefinedSetters = false, fromDatabase = false } = {}) {
2662
2735
  const returnValues = {};
2663
2736
  const setAttributeOnDreamInstance = (attr, value) => {
2664
2737
  if (!dreamInstance)
@@ -2672,8 +2745,13 @@ export default class Dream {
2672
2745
  }
2673
2746
  else if (bypassUserDefinedSetters && !isJsonColumn(this, attr)) {
2674
2747
  // bypass user defined setters, and this field is not json, so set
2675
- // the attribute directly and return the attribute
2676
- dreamInstance.setAttribute(attr, value);
2748
+ // the attribute directly and return the attribute. A row read from
2749
+ // the database already holds the ciphertext Dream stored, so it skips
2750
+ // setAttribute's check of @Encrypted backing columns
2751
+ if (fromDatabase)
2752
+ dreamInstance['setAttributeUnchecked'](attr, value);
2753
+ else
2754
+ dreamInstance.setAttribute(attr, value);
2677
2755
  return dreamInstance.getAttribute(attr);
2678
2756
  }
2679
2757
  else {
@@ -2903,13 +2981,47 @@ export default class Dream {
2903
2981
  * bypassing any custom-defined setters. If you would like to set attributes
2904
2982
  * without bypassing custom-defined setters, use #assignAttribute instead
2905
2983
  *
2984
+ * An `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only
2985
+ * a string of ciphertext that decrypts with this app's column encryption
2986
+ * keys (current or legacy), or `null`, which clears it. Anything else,
2987
+ * including plaintext, `undefined`, a non-string value, and ciphertext
2988
+ * made with a key this app does not hold, throws and leaves the column
2989
+ * unchanged. The check covers the backing columns of this model's class,
2990
+ * including those it inherits from an STI parent; a backing column
2991
+ * declared only by another class in the same STI hierarchy is not
2992
+ * checked.
2993
+ *
2994
+ * Given the name of an encrypted property (e.g. `secret`), setAttribute
2995
+ * neither encrypts the value nor stores it: it keeps the value,
2996
+ * unencrypted, under that name in this instance's attributes, which a
2997
+ * save does not write, while the backing column and the property keep
2998
+ * the value they had. To store a new value, assign the property
2999
+ * (`user.secret = 'new secret'`) or pass it to
3000
+ * {@link Dream.setAttributes | setAttributes}, each of which encrypts it.
3001
+ *
2906
3002
  * ```ts
2907
3003
  * const user = new User()
2908
3004
  * user.setAttribute('email', 'sally@gmail.com')
2909
3005
  * ```
2910
3006
  */
2911
3007
  setAttribute(column, val) {
2912
- ;
3008
+ const dreamClass = this.constructor;
3009
+ const encryptedAttribute = dreamClass.encryptedAttributes.find(({ encryptedColumnName }) => encryptedColumnName === column);
3010
+ if (encryptedAttribute &&
3011
+ val !== null &&
3012
+ !(typeof val === 'string' && InternalEncrypt.isDecryptableColumnCiphertext(val)))
3013
+ throw new DoNotSetEncryptedFieldsDirectly(dreamClass, column, encryptedAttribute.property);
3014
+ this.setAttributeUnchecked(column, val);
3015
+ }
3016
+ /**
3017
+ * @internal
3018
+ *
3019
+ * Writes the value into the current attributes as given, with none of
3020
+ * setAttribute's checks. Only for values Dream already trusts: a row read
3021
+ * from the database, and the ciphertext the @Encrypted property setter
3022
+ * has just produced. Every caller-supplied value goes through setAttribute.
3023
+ */
3024
+ setAttributeUnchecked(column, val) {
2913
3025
  this.currentAttributes[column] = val;
2914
3026
  }
2915
3027
  /**
@@ -2977,43 +3089,55 @@ export default class Dream {
2977
3089
  * changed since last persisting, along with their current
2978
3090
  * and previously persisted values.
2979
3091
  *
3092
+ * For a column with an unsaved change, `was` is its value
3093
+ * as of the most recent save. For every other column, `was`
3094
+ * is its value from before that save, so the changes the
3095
+ * save made keep appearing, beside any later unsaved change,
3096
+ * until the next save.
3097
+ *
3098
+ * On a create, that earlier value is `undefined` for every
3099
+ * column of an instance from `new` or `create`, assigned or
3100
+ * not, and the copied value for an instance from
3101
+ * {@link Dream.dup | dup}. So after a create from `new` or
3102
+ * `create`, every column the insert returned counts as
3103
+ * changed: the primary key, timestamps, database defaults,
3104
+ * and `null` for each nullable column nobody assigned. After
3105
+ * a create from `dup`, the columns `dup` cleared (the primary
3106
+ * key, timestamps and sortable positions) count, and a copied
3107
+ * column counts only if the insert returned a different value.
3108
+ *
2980
3109
  * ```ts
2981
3110
  * const pet = Pet.new({ species: 'dog' })
2982
3111
  * pet.changes()
2983
3112
  * // {
2984
- * // species: {
2985
- * // was: undefined,
2986
- * // now: 'dog',
2987
- * // }
3113
+ * // species: { was: undefined, now: 'dog' },
2988
3114
  * // }
2989
3115
  *
2990
3116
  * await pet.save()
2991
3117
  * pet.changes()
2992
3118
  * // {
2993
- * // species: {
2994
- * // was: undefined,
2995
- * // now: 'dog',
2996
- * // }
3119
+ * // id: { was: undefined, now: 1 },
3120
+ * // species: { was: undefined, now: 'dog' },
3121
+ * // name: { was: undefined, now: null },
3122
+ * // ...every other column the insert returned
2997
3123
  * // }
2998
3124
  *
2999
3125
  * pet.species = 'cat'
3000
3126
  * pet.species = 'frog'
3001
3127
  * pet.changes()
3002
3128
  * // {
3003
- * // species: {
3004
- * // was: 'dog',
3005
- * // now: 'frog',
3006
- * // }
3129
+ * // id: { was: undefined, now: 1 },
3130
+ * // species: { was: 'dog', now: 'frog' },
3131
+ * // name: { was: undefined, now: null },
3132
+ * // ...every other change the create made
3007
3133
  * // }
3008
3134
  *
3009
3135
  * await pet.save()
3010
3136
  * pet.changes()
3011
3137
  * // {
3012
- * // species: {
3013
- * // was: 'dog',
3014
- * // now: 'frog',
3015
- * // }
3138
+ * // species: { was: 'dog', now: 'frog' },
3016
3139
  * // }
3140
+ * // (and `updatedAt`, on a model with that column)
3017
3141
  * ```
3018
3142
  *
3019
3143
  * @returns An object containing changed attributes
@@ -3029,8 +3153,22 @@ export default class Dream {
3029
3153
  return obj;
3030
3154
  }
3031
3155
  /**
3032
- * Returns the value most recently persisted
3033
- * to the database.
3156
+ * Returns the value the column held before the most
3157
+ * recent save. For a column with an unsaved change, it
3158
+ * returns the column's value as of that save instead.
3159
+ *
3160
+ * On a create, the value before the save is `undefined`
3161
+ * for every column of an instance from `new` or `create`,
3162
+ * assigned or not, and the copied value for an instance
3163
+ * from {@link Dream.dup | dup} (`undefined` for the
3164
+ * columns `dup` cleared). So after a create from `new` or
3165
+ * `create`, this returns `undefined` for every column
3166
+ * without an unsaved change, not the value the insert
3167
+ * wrote. After an update, it returns the column's value
3168
+ * from before that update, which for a column the update
3169
+ * did not change is its current value. On a record loaded
3170
+ * from the database and not saved since, it returns the
3171
+ * loaded value.
3034
3172
  *
3035
3173
  * ```ts
3036
3174
  * const pet = Pet.new({ species: 'cat' })
@@ -3065,16 +3203,46 @@ export default class Dream {
3065
3203
  /**
3066
3204
  * Returns true if the columnName provided has
3067
3205
  * changes that were persisted during the most
3068
- * recent save.
3206
+ * recent save. An unsaved change does not count,
3207
+ * and an unsaved edit made after that save does
3208
+ * not undo a change the save made. On a record
3209
+ * that has not been persisted, this returns false.
3210
+ *
3211
+ * A create compares the value the insert returned
3212
+ * with `undefined` for every column of an instance
3213
+ * from `new` or `create`, assigned or not, and with
3214
+ * the copied value for an instance from
3215
+ * {@link Dream.dup | dup}. So after a create from
3216
+ * `new` or `create`, this returns true for every
3217
+ * column the insert returned: the primary key,
3218
+ * timestamps, database defaults, and a nullable
3219
+ * column nobody assigned, which comes back `null`.
3220
+ * To react only to the columns a create assigned,
3221
+ * use the `ifChanged` option of an after hook.
3222
+ *
3223
+ * ```ts
3224
+ * const pet = await Pet.create({ species: 'cat' })
3225
+ * pet.savedChangeToAttribute('species')
3226
+ * // true
3227
+ * pet.savedChangeToAttribute('name')
3228
+ * // true (unassigned, so the insert returned null)
3229
+ *
3230
+ * await pet.update({ name: 'Snoopy' })
3231
+ * pet.savedChangeToAttribute('species')
3232
+ * // false
3233
+ *
3234
+ * pet.species = 'dog'
3235
+ * pet.savedChangeToAttribute('species')
3236
+ * // false (not saved yet)
3237
+ * ```
3069
3238
  *
3070
3239
  * @param columnName - the column name to check
3071
3240
  * @returns A boolean
3072
3241
  */
3073
3242
  savedChangeToAttribute(columnName) {
3074
- const changes = this.changes();
3075
- const was = changes?.[columnName]?.was;
3076
- const now = changes?.[columnName]?.now;
3077
- return this.isPersisted && notEqual(now, was);
3243
+ if (!this.isPersisted)
3244
+ return false;
3245
+ return notEqual(this.attributesFromBeforeLastSave[columnName], this.frozenAttributes[columnName]);
3078
3246
  }
3079
3247
  /**
3080
3248
  * Returns true if the columnName provided has
@@ -3306,7 +3474,7 @@ export default class Dream {
3306
3474
  * deleting their corresponding records within the database.
3307
3475
  *
3308
3476
  * ```ts
3309
- * await user.destroyAssociation('posts', { body: 'hello world' })
3477
+ * await user.destroyAssociation('posts', { and: { body: 'hello world' } })
3310
3478
  * ```
3311
3479
  *
3312
3480
  * @param associationName - The name of the association to destroy
@@ -3321,6 +3489,7 @@ export default class Dream {
3321
3489
  * @returns The number of records deleted
3322
3490
  */
3323
3491
  async destroyAssociation(associationName, options) {
3492
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'destroyAssociation');
3324
3493
  if (this.isNewRecord)
3325
3494
  throw new CannotDestroyAssociationOnUnpersistedDream(this, associationName);
3326
3495
  return await destroyAssociation(this, null, associationName, {
@@ -3342,7 +3511,7 @@ export default class Dream {
3342
3511
  * causing those records to be deleted from the database.
3343
3512
  *
3344
3513
  * ```ts
3345
- * await user.reallyDestroyAssociation('posts', { body: 'hello world' })
3514
+ * await user.reallyDestroyAssociation('posts', { and: { body: 'hello world' } })
3346
3515
  * ```
3347
3516
  *
3348
3517
  * @param associationName - The name of the association to destroy
@@ -3357,6 +3526,7 @@ export default class Dream {
3357
3526
  * @returns The number of records deleted
3358
3527
  */
3359
3528
  async reallyDestroyAssociation(associationName, options) {
3529
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'reallyDestroyAssociation');
3360
3530
  if (this.isNewRecord)
3361
3531
  throw new CannotDestroyAssociationOnUnpersistedDream(this, associationName);
3362
3532
  return await destroyAssociation(this, null, associationName, {
@@ -3375,7 +3545,7 @@ export default class Dream {
3375
3545
  * will also be undeleted.
3376
3546
  *
3377
3547
  * ```ts
3378
- * await user.undestroyAssociation('posts', { body: 'hello world' })
3548
+ * await user.undestroyAssociation('posts', { and: { body: 'hello world' } })
3379
3549
  * ```
3380
3550
  *
3381
3551
  * @param associationName - The name of the association to undestroy
@@ -3390,6 +3560,7 @@ export default class Dream {
3390
3560
  * @returns The number of records undestroyed
3391
3561
  */
3392
3562
  async undestroyAssociation(associationName, options) {
3563
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'undestroyAssociation');
3393
3564
  return await undestroyAssociation(this, null, associationName, {
3394
3565
  ...undestroyOptions(options),
3395
3566
  joinAndStatements: {
@@ -3422,8 +3593,8 @@ export default class Dream {
3422
3593
  }
3423
3594
  /**
3424
3595
  * Updates all records matching the association with
3425
- * the provided attributes. If a where statement is passed,
3426
- * The on statement will be applied to the query
3596
+ * the provided attributes. If an and, andNot or andAny
3597
+ * statement is passed, it will be applied to the query
3427
3598
  * before updating.
3428
3599
  *
3429
3600
  * ```ts
@@ -3435,14 +3606,17 @@ export default class Dream {
3435
3606
  *
3436
3607
  * @param associationName - The name of the association to update
3437
3608
  * @param attributes - The attributes to update on the association
3438
- * @param options - Options for updating the association
3439
- * @param options.and - Optional on statement to apply to query before updating
3440
- * @param options.skipHooks - If true, skips applying model hooks during the update operation. Defaults to false
3441
- * @param options.bypassAllDefaultScopes - If true, bypasses user-removable default scopes when updating the association; an STI child's reserved discriminator remains enforced, including when associations are loaded. Defaults to false
3442
- * @param options.defaultScopesToBypass - An array of default scope names to bypass when updating the association. Defaults to an empty array
3609
+ * @param updateAssociationOptions - Options for updating the association
3610
+ * @param updateAssociationOptions.and - Optional and statement to apply to query before updating
3611
+ * @param updateAssociationOptions.andNot - Optional andNot statement to apply to query before updating
3612
+ * @param updateAssociationOptions.andAny - Optional andAny statement to apply to query before updating
3613
+ * @param updateAssociationOptions.skipHooks - If true, skips applying model hooks during the update operation. Defaults to false
3614
+ * @param updateAssociationOptions.bypassAllDefaultScopes - If true, bypasses user-removable default scopes when updating the association; an STI child's reserved discriminator remains enforced, including when associations are loaded. Defaults to false
3615
+ * @param updateAssociationOptions.defaultScopesToBypass - An array of default scope names to bypass when updating the association. Defaults to an empty array
3443
3616
  * @returns The number of updated records
3444
3617
  */
3445
3618
  async updateAssociation(associationName, attributes, updateAssociationOptions) {
3619
+ assertRecognizedAssociationConditionKeys(updateAssociationOptions, UPDATE_ASSOCIATION_OPTION_KEYS, associationName, 'updateAssociation');
3446
3620
  if (this.isNewRecord)
3447
3621
  throw new CannotUpdateAssociationOnUnpersistedDream(this, associationName);
3448
3622
  return associationUpdateQuery(this, null, associationName, {
@@ -3508,19 +3682,19 @@ export default class Dream {
3508
3682
  * if the association is not already loaded.
3509
3683
  *
3510
3684
  * ```ts
3511
- * const user = await user
3512
- * .load('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3685
+ * const loadedUser = await user
3686
+ * .load('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies')
3513
3687
  * .load('images')
3514
3688
  * .execute()
3515
3689
  *
3516
- * user.posts[0].comments[0].replies[0]
3690
+ * loadedUser.posts[0].comments[0].replies[0]
3517
3691
  * // Reply{}
3518
3692
  *
3519
- * user.images[0]
3693
+ * loadedUser.images[0]
3520
3694
  * // Image{}
3521
3695
  * ```
3522
3696
  *
3523
- * @param args - A list of associations (and optional where clauses) to load
3697
+ * @param args - A chain of association names and and/andNot/andAny clauses
3524
3698
  * @returns A chainable LoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3525
3699
  */
3526
3700
  load(...args) {
@@ -3602,19 +3776,19 @@ export default class Dream {
3602
3776
  * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.load(...).findEach(...)` avoids instantiating massive amounts of data at once
3603
3777
  *
3604
3778
  * ```ts
3605
- * const user = await user
3606
- * .leftJoinLoad('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3779
+ * const loadedUser = await user
3780
+ * .leftJoinLoad('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies')
3607
3781
  * .leftJoinLoad('images')
3608
3782
  * .execute()
3609
3783
  *
3610
- * user.posts[0].comments[0].replies[0]
3784
+ * loadedUser.posts[0].comments[0].replies[0]
3611
3785
  * // Reply{}
3612
3786
  *
3613
- * user.images[0]
3787
+ * loadedUser.images[0]
3614
3788
  * // Image{}
3615
3789
  * ```
3616
3790
  *
3617
- * @param args - A list of associations (and optional where clauses) to load
3791
+ * @param args - A chain of association names and and/andNot/andAny clauses
3618
3792
  * @returns A chainable LeftJoinLoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3619
3793
  */
3620
3794
  leftJoinLoad(...args) {
@@ -3681,6 +3855,13 @@ export default class Dream {
3681
3855
  * Takes the attributes passed in and sets their values internally,
3682
3856
  * bypassing any custom setters defined for these attributes.
3683
3857
  *
3858
+ * An encrypted property (e.g. `secret`) is still encrypted. Its
3859
+ * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only
3860
+ * ciphertext that decrypts with this app's column encryption keys, or
3861
+ * `null`; anything else throws (see {@link Dream.setAttribute | setAttribute}).
3862
+ * Attributes are set in order, so those before the rejected one have
3863
+ * already been set when it throws.
3864
+ *
3684
3865
  * NOTE:
3685
3866
  * To leverage custom-defined setters, use `#assignAttributes` instead.
3686
3867
  *
@@ -3711,6 +3892,10 @@ export default class Dream {
3711
3892
  * Upon updating an instance, the update timestamp (if defined)
3712
3893
  * will be updated on the model.
3713
3894
  *
3895
+ * A column whose value is `undefined` is skipped, not written: a new
3896
+ * record gets the column's database default, and a persisted record
3897
+ * keeps the value stored for it. Set a column to `null` to clear it.
3898
+ *
3714
3899
  * ```ts
3715
3900
  * const user = User.new({ email: 'how@yadoin' })
3716
3901
  * user.name = 'fred'
@@ -3758,6 +3943,12 @@ export default class Dream {
3758
3943
  * See {@link Dream.save | save} for details on
3759
3944
  * the side effects of saving.
3760
3945
  *
3946
+ * A column whose value is still `undefined` after setters run is
3947
+ * skipped, not written (see {@link Dream.save | save}), with two
3948
+ * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g.
3949
+ * `secret`) is encrypted to `null`, so it clears the column, and
3950
+ * `undefined` for its backing column (e.g. `encryptedSecret`) throws.
3951
+ *
3761
3952
  * NOTE:
3762
3953
  * To bypass custom-defined setters, use {@link Dream.updateAttributes | updateAttributes} instead.
3763
3954
  *
@@ -3785,6 +3976,18 @@ export default class Dream {
3785
3976
  * See {@link Dream.save | save} for details on
3786
3977
  * the side effects of saving.
3787
3978
  *
3979
+ * An encrypted property (e.g. `secret`) is still encrypted. Its
3980
+ * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only
3981
+ * ciphertext that decrypts with this app's column encryption keys, or
3982
+ * `null`; anything else throws before anything is saved (see
3983
+ * {@link Dream.setAttribute | setAttribute}).
3984
+ *
3985
+ * A column whose value is still `undefined` after setters run is
3986
+ * skipped, not written (see {@link Dream.save | save}), with two
3987
+ * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g.
3988
+ * `secret`) is encrypted to `null`, so it clears the column, and
3989
+ * `undefined` for its backing column (e.g. `encryptedSecret`) throws.
3990
+ *
3788
3991
  * NOTE:
3789
3992
  * To update the values without bypassing any custom-defined
3790
3993
  * setters, use {@link Dream.update | update} instead.
@@ -4,6 +4,7 @@ import Query from '../dream/Query.js';
4
4
  import DBClassDeprecation from '../helpers/cli/DBClassDeprecation.js';
5
5
  import generateDream from '../helpers/cli/generateDream.js';
6
6
  import EnvInternal from '../helpers/EnvInternal.js';
7
+ import standardizeFullyQualifiedModelName from '../helpers/standardizeFullyQualifiedModelName.js';
7
8
  export default class DreamBin {
8
9
  static async sync(onSync, options) {
9
10
  if (!EnvInternal.isTest) {
@@ -58,6 +59,7 @@ export default class DreamBin {
58
59
  });
59
60
  }
60
61
  static async generateStiChild(fullyQualifiedModelName, fullyQualifiedParentName, columnsWithTypes, options) {
62
+ const connectionName = options.connectionName ?? stiParentConnectionName(fullyQualifiedModelName, fullyQualifiedParentName);
61
63
  await generateDream({
62
64
  fullyQualifiedModelName,
63
65
  columnsWithTypes,
@@ -65,6 +67,7 @@ export default class DreamBin {
65
67
  includeAdminSerializers: options.adminSerializers ?? false,
66
68
  includeInternalSerializers: options.internalSerializers ?? false,
67
69
  ...options,
70
+ connectionName,
68
71
  stiBaseSerializer: false,
69
72
  // `@SoftDelete()` is incompatible with STI children — never auto-apply.
70
73
  softDelete: false,
@@ -95,3 +98,13 @@ export default class DreamBin {
95
98
  DreamCLI.logger.logEndProgress();
96
99
  }
97
100
  }
101
+ /**
102
+ * An STI child shares its parent's table, so its migration belongs to the
103
+ * parent model's connection.
104
+ */
105
+ function stiParentConnectionName(fullyQualifiedModelName, fullyQualifiedParentName) {
106
+ const parentModel = DreamApp.getOrFail().models[standardizeFullyQualifiedModelName(fullyQualifiedParentName)];
107
+ if (!parentModel)
108
+ throw new Error(`Cannot generate the STI child ${fullyQualifiedModelName}: its parent, ${fullyQualifiedParentName}, is not one of the app models. The parent must already exist, and its name must match its path under the models directory (e.g. Health/Coach for Health/Coach.ts).`);
109
+ return parentModel.prototype.connectionName;
110
+ }