@rvoh/dream 2.35.0 → 2.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/src/Dream.js +260 -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 +35 -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 +77 -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 +260 -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 +35 -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 +77 -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 +208 -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 +29 -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,49 @@ 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
+ * writes (freshly encrypted, or the ciphertext as loaded or last saved,
3023
+ * which it keeps when that already holds the assigned plaintext). Every
3024
+ * caller-supplied value goes through setAttribute.
3025
+ */
3026
+ setAttributeUnchecked(column, val) {
2913
3027
  this.currentAttributes[column] = val;
2914
3028
  }
2915
3029
  /**
@@ -2977,43 +3091,55 @@ export default class Dream {
2977
3091
  * changed since last persisting, along with their current
2978
3092
  * and previously persisted values.
2979
3093
  *
3094
+ * For a column with an unsaved change, `was` is its value
3095
+ * as of the most recent save. For every other column, `was`
3096
+ * is its value from before that save, so the changes the
3097
+ * save made keep appearing, beside any later unsaved change,
3098
+ * until the next save.
3099
+ *
3100
+ * On a create, that earlier value is `undefined` for every
3101
+ * column of an instance from `new` or `create`, assigned or
3102
+ * not, and the copied value for an instance from
3103
+ * {@link Dream.dup | dup}. So after a create from `new` or
3104
+ * `create`, every column the insert returned counts as
3105
+ * changed: the primary key, timestamps, database defaults,
3106
+ * and `null` for each nullable column nobody assigned. After
3107
+ * a create from `dup`, the columns `dup` cleared (the primary
3108
+ * key, timestamps and sortable positions) count, and a copied
3109
+ * column counts only if the insert returned a different value.
3110
+ *
2980
3111
  * ```ts
2981
3112
  * const pet = Pet.new({ species: 'dog' })
2982
3113
  * pet.changes()
2983
3114
  * // {
2984
- * // species: {
2985
- * // was: undefined,
2986
- * // now: 'dog',
2987
- * // }
3115
+ * // species: { was: undefined, now: 'dog' },
2988
3116
  * // }
2989
3117
  *
2990
3118
  * await pet.save()
2991
3119
  * pet.changes()
2992
3120
  * // {
2993
- * // species: {
2994
- * // was: undefined,
2995
- * // now: 'dog',
2996
- * // }
3121
+ * // id: { was: undefined, now: 1 },
3122
+ * // species: { was: undefined, now: 'dog' },
3123
+ * // name: { was: undefined, now: null },
3124
+ * // ...every other column the insert returned
2997
3125
  * // }
2998
3126
  *
2999
3127
  * pet.species = 'cat'
3000
3128
  * pet.species = 'frog'
3001
3129
  * pet.changes()
3002
3130
  * // {
3003
- * // species: {
3004
- * // was: 'dog',
3005
- * // now: 'frog',
3006
- * // }
3131
+ * // id: { was: undefined, now: 1 },
3132
+ * // species: { was: 'dog', now: 'frog' },
3133
+ * // name: { was: undefined, now: null },
3134
+ * // ...every other change the create made
3007
3135
  * // }
3008
3136
  *
3009
3137
  * await pet.save()
3010
3138
  * pet.changes()
3011
3139
  * // {
3012
- * // species: {
3013
- * // was: 'dog',
3014
- * // now: 'frog',
3015
- * // }
3140
+ * // species: { was: 'dog', now: 'frog' },
3016
3141
  * // }
3142
+ * // (and `updatedAt`, on a model with that column)
3017
3143
  * ```
3018
3144
  *
3019
3145
  * @returns An object containing changed attributes
@@ -3029,8 +3155,22 @@ export default class Dream {
3029
3155
  return obj;
3030
3156
  }
3031
3157
  /**
3032
- * Returns the value most recently persisted
3033
- * to the database.
3158
+ * Returns the value the column held before the most
3159
+ * recent save. For a column with an unsaved change, it
3160
+ * returns the column's value as of that save instead.
3161
+ *
3162
+ * On a create, the value before the save is `undefined`
3163
+ * for every column of an instance from `new` or `create`,
3164
+ * assigned or not, and the copied value for an instance
3165
+ * from {@link Dream.dup | dup} (`undefined` for the
3166
+ * columns `dup` cleared). So after a create from `new` or
3167
+ * `create`, this returns `undefined` for every column
3168
+ * without an unsaved change, not the value the insert
3169
+ * wrote. After an update, it returns the column's value
3170
+ * from before that update, which for a column the update
3171
+ * did not change is its current value. On a record loaded
3172
+ * from the database and not saved since, it returns the
3173
+ * loaded value.
3034
3174
  *
3035
3175
  * ```ts
3036
3176
  * const pet = Pet.new({ species: 'cat' })
@@ -3065,16 +3205,46 @@ export default class Dream {
3065
3205
  /**
3066
3206
  * Returns true if the columnName provided has
3067
3207
  * changes that were persisted during the most
3068
- * recent save.
3208
+ * recent save. An unsaved change does not count,
3209
+ * and an unsaved edit made after that save does
3210
+ * not undo a change the save made. On a record
3211
+ * that has not been persisted, this returns false.
3212
+ *
3213
+ * A create compares the value the insert returned
3214
+ * with `undefined` for every column of an instance
3215
+ * from `new` or `create`, assigned or not, and with
3216
+ * the copied value for an instance from
3217
+ * {@link Dream.dup | dup}. So after a create from
3218
+ * `new` or `create`, this returns true for every
3219
+ * column the insert returned: the primary key,
3220
+ * timestamps, database defaults, and a nullable
3221
+ * column nobody assigned, which comes back `null`.
3222
+ * To react only to the columns a create assigned,
3223
+ * use the `ifChanged` option of an after hook.
3224
+ *
3225
+ * ```ts
3226
+ * const pet = await Pet.create({ species: 'cat' })
3227
+ * pet.savedChangeToAttribute('species')
3228
+ * // true
3229
+ * pet.savedChangeToAttribute('name')
3230
+ * // true (unassigned, so the insert returned null)
3231
+ *
3232
+ * await pet.update({ name: 'Snoopy' })
3233
+ * pet.savedChangeToAttribute('species')
3234
+ * // false
3235
+ *
3236
+ * pet.species = 'dog'
3237
+ * pet.savedChangeToAttribute('species')
3238
+ * // false (not saved yet)
3239
+ * ```
3069
3240
  *
3070
3241
  * @param columnName - the column name to check
3071
3242
  * @returns A boolean
3072
3243
  */
3073
3244
  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);
3245
+ if (!this.isPersisted)
3246
+ return false;
3247
+ return notEqual(this.attributesFromBeforeLastSave[columnName], this.frozenAttributes[columnName]);
3078
3248
  }
3079
3249
  /**
3080
3250
  * Returns true if the columnName provided has
@@ -3306,7 +3476,7 @@ export default class Dream {
3306
3476
  * deleting their corresponding records within the database.
3307
3477
  *
3308
3478
  * ```ts
3309
- * await user.destroyAssociation('posts', { body: 'hello world' })
3479
+ * await user.destroyAssociation('posts', { and: { body: 'hello world' } })
3310
3480
  * ```
3311
3481
  *
3312
3482
  * @param associationName - The name of the association to destroy
@@ -3321,6 +3491,7 @@ export default class Dream {
3321
3491
  * @returns The number of records deleted
3322
3492
  */
3323
3493
  async destroyAssociation(associationName, options) {
3494
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'destroyAssociation');
3324
3495
  if (this.isNewRecord)
3325
3496
  throw new CannotDestroyAssociationOnUnpersistedDream(this, associationName);
3326
3497
  return await destroyAssociation(this, null, associationName, {
@@ -3342,7 +3513,7 @@ export default class Dream {
3342
3513
  * causing those records to be deleted from the database.
3343
3514
  *
3344
3515
  * ```ts
3345
- * await user.reallyDestroyAssociation('posts', { body: 'hello world' })
3516
+ * await user.reallyDestroyAssociation('posts', { and: { body: 'hello world' } })
3346
3517
  * ```
3347
3518
  *
3348
3519
  * @param associationName - The name of the association to destroy
@@ -3357,6 +3528,7 @@ export default class Dream {
3357
3528
  * @returns The number of records deleted
3358
3529
  */
3359
3530
  async reallyDestroyAssociation(associationName, options) {
3531
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'reallyDestroyAssociation');
3360
3532
  if (this.isNewRecord)
3361
3533
  throw new CannotDestroyAssociationOnUnpersistedDream(this, associationName);
3362
3534
  return await destroyAssociation(this, null, associationName, {
@@ -3375,7 +3547,7 @@ export default class Dream {
3375
3547
  * will also be undeleted.
3376
3548
  *
3377
3549
  * ```ts
3378
- * await user.undestroyAssociation('posts', { body: 'hello world' })
3550
+ * await user.undestroyAssociation('posts', { and: { body: 'hello world' } })
3379
3551
  * ```
3380
3552
  *
3381
3553
  * @param associationName - The name of the association to undestroy
@@ -3390,6 +3562,7 @@ export default class Dream {
3390
3562
  * @returns The number of records undestroyed
3391
3563
  */
3392
3564
  async undestroyAssociation(associationName, options) {
3565
+ assertRecognizedAssociationConditionKeys(options, DESTROY_ASSOCIATION_OPTION_KEYS, associationName, 'undestroyAssociation');
3393
3566
  return await undestroyAssociation(this, null, associationName, {
3394
3567
  ...undestroyOptions(options),
3395
3568
  joinAndStatements: {
@@ -3422,8 +3595,8 @@ export default class Dream {
3422
3595
  }
3423
3596
  /**
3424
3597
  * 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
3598
+ * the provided attributes. If an and, andNot or andAny
3599
+ * statement is passed, it will be applied to the query
3427
3600
  * before updating.
3428
3601
  *
3429
3602
  * ```ts
@@ -3435,14 +3608,17 @@ export default class Dream {
3435
3608
  *
3436
3609
  * @param associationName - The name of the association to update
3437
3610
  * @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
3611
+ * @param updateAssociationOptions - Options for updating the association
3612
+ * @param updateAssociationOptions.and - Optional and statement to apply to query before updating
3613
+ * @param updateAssociationOptions.andNot - Optional andNot statement to apply to query before updating
3614
+ * @param updateAssociationOptions.andAny - Optional andAny statement to apply to query before updating
3615
+ * @param updateAssociationOptions.skipHooks - If true, skips applying model hooks during the update operation. Defaults to false
3616
+ * @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
3617
+ * @param updateAssociationOptions.defaultScopesToBypass - An array of default scope names to bypass when updating the association. Defaults to an empty array
3443
3618
  * @returns The number of updated records
3444
3619
  */
3445
3620
  async updateAssociation(associationName, attributes, updateAssociationOptions) {
3621
+ assertRecognizedAssociationConditionKeys(updateAssociationOptions, UPDATE_ASSOCIATION_OPTION_KEYS, associationName, 'updateAssociation');
3446
3622
  if (this.isNewRecord)
3447
3623
  throw new CannotUpdateAssociationOnUnpersistedDream(this, associationName);
3448
3624
  return associationUpdateQuery(this, null, associationName, {
@@ -3508,19 +3684,19 @@ export default class Dream {
3508
3684
  * if the association is not already loaded.
3509
3685
  *
3510
3686
  * ```ts
3511
- * const user = await user
3512
- * .load('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3687
+ * const loadedUser = await user
3688
+ * .load('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies')
3513
3689
  * .load('images')
3514
3690
  * .execute()
3515
3691
  *
3516
- * user.posts[0].comments[0].replies[0]
3692
+ * loadedUser.posts[0].comments[0].replies[0]
3517
3693
  * // Reply{}
3518
3694
  *
3519
- * user.images[0]
3695
+ * loadedUser.images[0]
3520
3696
  * // Image{}
3521
3697
  * ```
3522
3698
  *
3523
- * @param args - A list of associations (and optional where clauses) to load
3699
+ * @param args - A chain of association names and and/andNot/andAny clauses
3524
3700
  * @returns A chainable LoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3525
3701
  */
3526
3702
  load(...args) {
@@ -3602,19 +3778,19 @@ export default class Dream {
3602
3778
  * 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
3779
  *
3604
3780
  * ```ts
3605
- * const user = await user
3606
- * .leftJoinLoad('posts', { body: ops.ilike('%hello world%') }, 'comments', 'replies')
3781
+ * const loadedUser = await user
3782
+ * .leftJoinLoad('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies')
3607
3783
  * .leftJoinLoad('images')
3608
3784
  * .execute()
3609
3785
  *
3610
- * user.posts[0].comments[0].replies[0]
3786
+ * loadedUser.posts[0].comments[0].replies[0]
3611
3787
  * // Reply{}
3612
3788
  *
3613
- * user.images[0]
3789
+ * loadedUser.images[0]
3614
3790
  * // Image{}
3615
3791
  * ```
3616
3792
  *
3617
- * @param args - A list of associations (and optional where clauses) to load
3793
+ * @param args - A chain of association names and and/andNot/andAny clauses
3618
3794
  * @returns A chainable LeftJoinLoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded.
3619
3795
  */
3620
3796
  leftJoinLoad(...args) {
@@ -3681,6 +3857,13 @@ export default class Dream {
3681
3857
  * Takes the attributes passed in and sets their values internally,
3682
3858
  * bypassing any custom setters defined for these attributes.
3683
3859
  *
3860
+ * An encrypted property (e.g. `secret`) is still encrypted. Its
3861
+ * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only
3862
+ * ciphertext that decrypts with this app's column encryption keys, or
3863
+ * `null`; anything else throws (see {@link Dream.setAttribute | setAttribute}).
3864
+ * Attributes are set in order, so those before the rejected one have
3865
+ * already been set when it throws.
3866
+ *
3684
3867
  * NOTE:
3685
3868
  * To leverage custom-defined setters, use `#assignAttributes` instead.
3686
3869
  *
@@ -3711,6 +3894,10 @@ export default class Dream {
3711
3894
  * Upon updating an instance, the update timestamp (if defined)
3712
3895
  * will be updated on the model.
3713
3896
  *
3897
+ * A column whose value is `undefined` is skipped, not written: a new
3898
+ * record gets the column's database default, and a persisted record
3899
+ * keeps the value stored for it. Set a column to `null` to clear it.
3900
+ *
3714
3901
  * ```ts
3715
3902
  * const user = User.new({ email: 'how@yadoin' })
3716
3903
  * user.name = 'fred'
@@ -3758,6 +3945,12 @@ export default class Dream {
3758
3945
  * See {@link Dream.save | save} for details on
3759
3946
  * the side effects of saving.
3760
3947
  *
3948
+ * A column whose value is still `undefined` after setters run is
3949
+ * skipped, not written (see {@link Dream.save | save}), with two
3950
+ * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g.
3951
+ * `secret`) is encrypted to `null`, so it clears the column, and
3952
+ * `undefined` for its backing column (e.g. `encryptedSecret`) throws.
3953
+ *
3761
3954
  * NOTE:
3762
3955
  * To bypass custom-defined setters, use {@link Dream.updateAttributes | updateAttributes} instead.
3763
3956
  *
@@ -3785,6 +3978,18 @@ export default class Dream {
3785
3978
  * See {@link Dream.save | save} for details on
3786
3979
  * the side effects of saving.
3787
3980
  *
3981
+ * An encrypted property (e.g. `secret`) is still encrypted. Its
3982
+ * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only
3983
+ * ciphertext that decrypts with this app's column encryption keys, or
3984
+ * `null`; anything else throws before anything is saved (see
3985
+ * {@link Dream.setAttribute | setAttribute}).
3986
+ *
3987
+ * A column whose value is still `undefined` after setters run is
3988
+ * skipped, not written (see {@link Dream.save | save}), with two
3989
+ * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g.
3990
+ * `secret`) is encrypted to `null`, so it clears the column, and
3991
+ * `undefined` for its backing column (e.g. `encryptedSecret`) throws.
3992
+ *
3788
3993
  * NOTE:
3789
3994
  * To update the values without bypassing any custom-defined
3790
3995
  * 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
+ }