@rvoh/dream 2.24.1 → 2.26.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 (244) hide show
  1. package/dist/cjs/src/Dream.js +10 -1
  2. package/dist/cjs/src/cli/index.js +31 -5
  3. package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
  4. package/dist/cjs/src/dream/Query.js +247 -67
  5. package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
  6. package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
  7. package/dist/cjs/src/errors/BatchingIncompatibleWithLimitOrOffset.js +25 -0
  8. package/dist/cjs/src/errors/DropEnumValueRetypeFailed.js +208 -0
  9. package/dist/cjs/src/errors/MissingRequiredLockOptionForUpdateCallback.js +16 -0
  10. package/dist/cjs/src/errors/serializers/MissingSerializersDefinition.js +16 -2
  11. package/dist/cjs/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
  12. package/dist/cjs/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
  13. package/dist/cjs/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
  14. package/dist/cjs/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
  15. package/dist/cjs/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
  16. package/dist/cjs/src/helpers/cli/generateFactoryContent.js +7 -1
  17. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +80 -13
  18. package/dist/cjs/src/helpers/cli/syncWithFailureContext.js +41 -0
  19. package/dist/cjs/src/serializer/SerializerRenderer.js +32 -4
  20. package/dist/cjs/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
  21. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
  22. package/dist/cjs/src/serializer/helpers/serializerGlobalName.js +16 -0
  23. package/dist/esm/src/Dream.js +10 -1
  24. package/dist/esm/src/cli/index.js +31 -5
  25. package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
  26. package/dist/esm/src/dream/Query.js +247 -67
  27. package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
  28. package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
  29. package/dist/esm/src/errors/BatchingIncompatibleWithLimitOrOffset.js +25 -0
  30. package/dist/esm/src/errors/DropEnumValueRetypeFailed.js +208 -0
  31. package/dist/esm/src/errors/MissingRequiredLockOptionForUpdateCallback.js +16 -0
  32. package/dist/esm/src/errors/serializers/MissingSerializersDefinition.js +16 -2
  33. package/dist/esm/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
  34. package/dist/esm/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
  35. package/dist/esm/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
  36. package/dist/esm/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
  37. package/dist/esm/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
  38. package/dist/esm/src/helpers/cli/generateFactoryContent.js +7 -1
  39. package/dist/esm/src/helpers/cli/generateMigrationContent.js +80 -13
  40. package/dist/esm/src/helpers/cli/syncWithFailureContext.js +41 -0
  41. package/dist/esm/src/serializer/SerializerRenderer.js +32 -4
  42. package/dist/esm/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
  43. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
  44. package/dist/esm/src/serializer/helpers/serializerGlobalName.js +16 -0
  45. package/dist/types/src/Dream.d.ts +8 -2
  46. package/dist/types/src/cli/index.d.ts +1 -1
  47. package/dist/types/src/dream/Query.d.ts +316 -40
  48. package/dist/types/src/errors/BatchingIncompatibleWithLimitOrOffset.d.ts +3 -0
  49. package/dist/types/src/errors/DropEnumValueRetypeFailed.d.ts +89 -0
  50. package/dist/types/src/errors/MissingRequiredLockOptionForUpdateCallback.d.ts +3 -0
  51. package/dist/types/src/errors/serializers/MissingSerializersDefinition.d.ts +3 -1
  52. package/dist/types/src/errors/serializers/MissingSerializersDefinitionForKey.d.ts +3 -1
  53. package/dist/types/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.d.ts +3 -1
  54. package/dist/types/src/errors/serializers/NoSerializersResolvedForKey.d.ts +3 -1
  55. package/dist/types/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.d.ts +3 -1
  56. package/dist/types/src/errors/serializers/serializerResolutionContextMessage.d.ts +20 -0
  57. package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +58 -0
  58. package/dist/types/src/serializer/helpers/inferSerializerFromDreamOrViewModel.d.ts +11 -4
  59. package/dist/types/src/serializer/helpers/serializerGlobalName.d.ts +15 -0
  60. package/dist/types/src/types/serializer.d.ts +38 -0
  61. package/dist/types/src/types/types/serializer.ts +40 -0
  62. package/docs/assets/search.js +1 -1
  63. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  64. package/docs/classes/db.KyselyQueryDriver.html +40 -40
  65. package/docs/classes/db.PostgresQueryDriver.html +41 -41
  66. package/docs/classes/db.QueryDriverBase.html +39 -39
  67. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  68. package/docs/classes/errors.ColumnOverflow.html +3 -3
  69. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  70. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  71. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  72. package/docs/classes/errors.DecryptionError.html +2 -2
  73. package/docs/classes/errors.DecryptionParseError.html +2 -2
  74. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  75. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  76. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  77. package/docs/classes/errors.InvalidClockTime.html +2 -2
  78. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  79. package/docs/classes/errors.InvalidDateTime.html +2 -2
  80. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  81. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  82. package/docs/classes/errors.NotNullViolation.html +3 -3
  83. package/docs/classes/errors.RecordNotFound.html +3 -3
  84. package/docs/classes/errors.ValidationError.html +3 -3
  85. package/docs/classes/index.CalendarDate.html +33 -33
  86. package/docs/classes/index.ClockTime.html +32 -32
  87. package/docs/classes/index.ClockTimeTz.html +35 -35
  88. package/docs/classes/index.DateTime.html +86 -86
  89. package/docs/classes/index.Decorators.html +19 -19
  90. package/docs/classes/index.Dream.html +123 -137
  91. package/docs/classes/index.DreamApp.html +10 -10
  92. package/docs/classes/index.DreamTransaction.html +2 -2
  93. package/docs/classes/index.Env.html +2 -2
  94. package/docs/classes/index.Query.html +735 -73
  95. package/docs/classes/system.CliFileWriter.html +4 -4
  96. package/docs/classes/system.DreamBin.html +2 -2
  97. package/docs/classes/system.DreamCLI.html +7 -7
  98. package/docs/classes/system.DreamImporter.html +2 -2
  99. package/docs/classes/system.DreamLogos.html +2 -2
  100. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  101. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  102. package/docs/classes/system.PathHelpers.html +3 -3
  103. package/docs/classes/utils.Encrypt.html +3 -3
  104. package/docs/classes/utils.Range.html +2 -2
  105. package/docs/functions/db.closeAllDbConnections.html +1 -1
  106. package/docs/functions/db.dreamDbConnections.html +1 -1
  107. package/docs/functions/db.untypedDb.html +1 -1
  108. package/docs/functions/db.validateColumn.html +1 -1
  109. package/docs/functions/db.validateTable.html +1 -1
  110. package/docs/functions/errors.pgErrorType.html +1 -1
  111. package/docs/functions/index.DreamSerializer.html +1 -1
  112. package/docs/functions/index.ObjectSerializer.html +1 -1
  113. package/docs/functions/index.ReplicaSafe.html +1 -1
  114. package/docs/functions/index.STI.html +1 -1
  115. package/docs/functions/index.SoftDelete.html +1 -1
  116. package/docs/functions/utils.camelize.html +1 -1
  117. package/docs/functions/utils.capitalize.html +1 -1
  118. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  119. package/docs/functions/utils.compact.html +1 -1
  120. package/docs/functions/utils.groupBy.html +1 -1
  121. package/docs/functions/utils.hyphenize.html +1 -1
  122. package/docs/functions/utils.intersection.html +1 -1
  123. package/docs/functions/utils.isEmpty.html +1 -1
  124. package/docs/functions/utils.normalizeUnicode.html +1 -1
  125. package/docs/functions/utils.pascalize.html +1 -1
  126. package/docs/functions/utils.percent.html +1 -1
  127. package/docs/functions/utils.range.html +1 -1
  128. package/docs/functions/utils.round.html +1 -1
  129. package/docs/functions/utils.sanitizeString.html +1 -1
  130. package/docs/functions/utils.snakeify.html +1 -1
  131. package/docs/functions/utils.sort.html +1 -1
  132. package/docs/functions/utils.sortBy.html +1 -1
  133. package/docs/functions/utils.sortObjectByKey.html +1 -1
  134. package/docs/functions/utils.sortObjectByValue.html +1 -1
  135. package/docs/functions/utils.uncapitalize.html +1 -1
  136. package/docs/functions/utils.uniq.html +1 -1
  137. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  138. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  139. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  140. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  141. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  142. package/docs/interfaces/types.DecoratorContext.html +2 -2
  143. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  144. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  145. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  146. package/docs/interfaces/types.DurationObject.html +2 -2
  147. package/docs/interfaces/types.EncryptOptions.html +2 -2
  148. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  149. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  150. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  151. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  152. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  153. package/docs/types/openapi.OpenapiFormats.html +1 -1
  154. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  155. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  156. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  164. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  165. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  166. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  167. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  168. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  169. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  170. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  171. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  172. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  173. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  174. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  175. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  176. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  177. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  178. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  179. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  180. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  181. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  182. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  183. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  184. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  185. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  186. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  187. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  188. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  189. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  190. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  191. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  192. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  193. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  194. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  195. package/docs/types/types.CalendarDateObject.html +1 -1
  196. package/docs/types/types.Camelized.html +1 -1
  197. package/docs/types/types.ClockTimeObject.html +1 -1
  198. package/docs/types/types.DbConnectionType.html +1 -1
  199. package/docs/types/types.DbTypes.html +1 -1
  200. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  201. package/docs/types/types.DreamAttributes.html +1 -1
  202. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  203. package/docs/types/types.DreamClassColumn.html +1 -1
  204. package/docs/types/types.DreamColumn.html +1 -1
  205. package/docs/types/types.DreamColumnNames.html +1 -1
  206. package/docs/types/types.DreamLogLevel.html +1 -1
  207. package/docs/types/types.DreamLogger.html +2 -2
  208. package/docs/types/types.DreamModelSerializerType.html +1 -1
  209. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  210. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  211. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  212. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  213. package/docs/types/types.DreamSerializable.html +1 -1
  214. package/docs/types/types.DreamSerializableArray.html +1 -1
  215. package/docs/types/types.DreamSerializerKey.html +1 -1
  216. package/docs/types/types.DreamSerializers.html +1 -1
  217. package/docs/types/types.DreamVirtualColumns.html +1 -1
  218. package/docs/types/types.DurationUnit.html +1 -1
  219. package/docs/types/types.EncryptAlgorithm.html +1 -1
  220. package/docs/types/types.HasManyStatement.html +1 -1
  221. package/docs/types/types.HasOneStatement.html +1 -1
  222. package/docs/types/types.Hyphenized.html +1 -1
  223. package/docs/types/types.Pascalized.html +1 -1
  224. package/docs/types/types.PrimaryKeyType.html +1 -1
  225. package/docs/types/types.RoundingPrecision.html +1 -1
  226. package/docs/types/types.SerializerCasing.html +1 -1
  227. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  228. package/docs/types/types.Snakeified.html +1 -1
  229. package/docs/types/types.StrictInterface.html +1 -1
  230. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  231. package/docs/types/types.UpdateableProperties.html +1 -1
  232. package/docs/types/types.ValidationType.html +1 -1
  233. package/docs/types/types.ViewModel.html +2 -2
  234. package/docs/types/types.ViewModelClass.html +1 -1
  235. package/docs/types/types.WeekdayName.html +1 -1
  236. package/docs/types/types.WhereStatementForDream.html +1 -1
  237. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  238. package/docs/variables/index.DreamConst.html +1 -1
  239. package/docs/variables/index.ops.html +1 -1
  240. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  241. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  242. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  243. package/docs/variables/system.primaryKeyTypes.html +1 -1
  244. package/package.json +1 -1
@@ -49,6 +49,7 @@ import cloneDeepSafe from './helpers/cloneDeepSafe.js';
49
49
  import isJsonColumn from './helpers/db/types/isJsonColumn.js';
50
50
  import notEqual from './helpers/notEqual.js';
51
51
  import { inferSerializersFromDreamClassOrViewModelClassOrFail } from './serializer/helpers/inferSerializerFromDreamOrViewModel.js';
52
+ import serializerGlobalName from './serializer/helpers/serializerGlobalName.js';
52
53
  export default class Dream {
53
54
  DB;
54
55
  /**
@@ -580,8 +581,10 @@ export default class Dream {
580
581
  // One line per serializer: for an STI base the displayed tree is the union across its children,
581
582
  // so naming a single child's serializer under the base's own name would misattribute the tree.
582
583
  serializers.forEach(serializer => {
584
+ // Interpolated rather than defaulted so the printed line is unchanged for a serializer with no
585
+ // globalName; naming inline serializers in this tree is its own change.
583
586
  // eslint-disable-next-line no-console
584
- console.log(yoctocolors.gray(serializer.globalName));
587
+ console.log(yoctocolors.gray(`${serializerGlobalName(serializer)}`));
585
588
  });
586
589
  return this.recursiveSerializationMap(serializers, {
587
590
  forDisplay: true,
@@ -667,6 +670,12 @@ export default class Dream {
667
670
  * * foreign key fields for belongs to associations (these should usually be verified before being set)
668
671
  * * type fields corresponding to polymorphic associations
669
672
  *
673
+ * @internal
674
+ *
675
+ * Not part of the public API. Psychic reaches it through the bracketed
676
+ * back-door (`dreamClass['paramSafeColumnsOrFallback']()`), the same escape
677
+ * hatch used for other Dream internals such as `virtualAttributes`.
678
+ *
670
679
  * @returns A subset of columns for the given dream class
671
680
  */
672
681
  static paramSafeColumnsOrFallback() {
@@ -5,6 +5,7 @@ import DreamApp from '../dream-app/index.js';
5
5
  import Encrypt from '../encrypt/index.js';
6
6
  import SspawnRequiresDevelopmentOrTest from '../errors/SspawnRequiresDevelopmentOrTest.js';
7
7
  import generateDream from '../helpers/cli/generateDream.js';
8
+ import syncWithFailureContext from '../helpers/cli/syncWithFailureContext.js';
8
9
  import EnvInternal from '../helpers/EnvInternal.js';
9
10
  import loadRepl from '../helpers/loadRepl.js';
10
11
  import DreamCliLogger from './logger/DreamCliLogger.js';
@@ -129,7 +130,9 @@ ${INDENT} create-many-to-many-posts-users # WRONG: selects 'many_posts_
129
130
  ${INDENT} # -to- inside "many-to-many" matches: with no columns
130
131
  ${INDENT} # this errors; with columns it silently scaffolds it
131
132
  ${INDENT} create-posts-users-join-table # correct: no marker match
132
- ${INDENT} # scaffolds alterTable('<table-name>') to hand-edit`;
133
+ ${INDENT} # with no columns, generates empty up/down methods to
134
+ ${INDENT} # write by hand; with columns, scaffolds
135
+ ${INDENT} # alterTable('<table-name>') to hand-edit`;
133
136
  export default class DreamCLI {
134
137
  /**
135
138
  * Starts the Dream console
@@ -184,7 +187,9 @@ ${INDENT} # Remove columns (include -from-<table_name>; columns are described w
184
187
  ${INDENT} # their types, same shorthand as -to-, so the rollback (down) can recreate them)
185
188
  ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts legacy_status:string
186
189
  ${INDENT}
187
- ${INDENT} # General schema change (no -to-/-from- marker — scaffolds alterTable('<table-name>') to hand-edit)
190
+ ${INDENT} # General schema change (no -to-/-from- marker — with no columns, generates
191
+ ${INDENT} # empty up/down methods to write by hand; with columns, scaffolds
192
+ ${INDENT} # alterTable('<table-name>') to hand-edit)
188
193
  ${INDENT} pnpm psy g:migration create-unique-index-on-invitations`)
189
194
  .argument('<migrationName>', migrationNameArgumentDescription)
190
195
  .option('--connection-name <connectionName>', 'the database connection to use for this migration. Only needed for multi-database setups; defaults to "default"')
@@ -345,7 +350,11 @@ ${INDENT} pnpm psy db:migrate`)
345
350
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
346
351
  await DreamBin.dbMigrate();
347
352
  if (EnvInternal.isTest && !skipSync) {
348
- await DreamBin.sync(onSync);
353
+ await syncWithFailureContext({
354
+ commandName: 'db:migrate',
355
+ completedWork: 'Every pending migration ran successfully',
356
+ recoveryAdvice: 'run `sync` on its own, or re-run `db:migrate` — the migrations it already applied are not re-applied, so it goes straight back to the sync',
357
+ }, async () => await DreamBin.sync(onSync));
349
358
  }
350
359
  process.exit();
351
360
  });
@@ -362,7 +371,13 @@ ${INDENT} pnpm psy db:rollback --steps=3 # rolls back the last 3 migrations`
362
371
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
363
372
  await DreamBin.dbRollback({ steps });
364
373
  if (EnvInternal.isTest && !skipSync) {
365
- await DreamBin.sync(onSync);
374
+ await syncWithFailureContext({
375
+ commandName: 'db:rollback',
376
+ completedWork: 'The rollback completed successfully and is already committed',
377
+ // never `re-run db:rollback` here: the rollback is already
378
+ // committed, so a second run rolls back a further migration
379
+ recoveryAdvice: 'run `sync` on its own. Do not re-run `db:rollback` — the rollback it would perform is a further one, undoing a migration you did not ask to undo',
380
+ }, async () => await DreamBin.sync(onSync));
366
381
  }
367
382
  process.exit();
368
383
  });
@@ -409,7 +424,18 @@ ${INDENT}Warning: all existing data will be lost. The seed file (db/seed.ts) wil
409
424
  logPrefix: ' ',
410
425
  logPrefixColor: 'green',
411
426
  });
412
- await DreamBin.sync(onSync);
427
+ await syncWithFailureContext({
428
+ commandName: 'db:reset',
429
+ completedWork: 'db:drop, db:create and db:migrate all completed successfully',
430
+ didNotRun: 'db:seed, which db:reset runs after the sync, did not run',
431
+ // the two routes are not interchangeable, so the second one names
432
+ // the env var it needs: db:reset seeds by calling the app's seed
433
+ // hook directly (below), while the `db:seed` *command* refuses to
434
+ // seed under NODE_ENV=test unless DREAM_SEED_DB_IN_TEST=1 — and
435
+ // NODE_ENV=test is the only environment DreamBin.sync does any
436
+ // work in, so it is the only environment this advice is printed in
437
+ recoveryAdvice: 're-run `db:reset`, which seeds directly and so picks up the skipped seeding step, or run `sync` on its own followed by `DREAM_SEED_DB_IN_TEST=1 db:seed` — this sync only runs under `NODE_ENV=test`, and a bare `db:seed` does not seed there',
438
+ }, async () => await DreamBin.sync(onSync));
413
439
  DreamCLI.logger.log(arrows, { logPrefix: ' ' });
414
440
  DreamCLI.logger.log(colorize('db:seed', { color: 'green' }), {
415
441
  logPrefix: ' ',
@@ -1,5 +1,6 @@
1
1
  import { sql } from 'kysely';
2
2
  import InternalEncrypt from '../../encrypt/InternalEncrypt.js';
3
+ import DropEnumValueRetypeFailed, { quotedRelationName, } from '../../errors/DropEnumValueRetypeFailed.js';
3
4
  export default class DreamMigrationHelpers {
4
5
  /**
5
6
  * Rename a table and its associated primary key index and sequence.
@@ -169,14 +170,29 @@ export default class DreamMigrationHelpers {
169
170
  * @param __namedParameters.replacements - Details about which table and column to change and which value to replace the dropped value with (or remove it if the column is an array)
170
171
  */
171
172
  static async dropEnumValue(db, { enumName, value, replacements }) {
173
+ // Read the check constraints on every replacement column up front, before anything is
174
+ // retyped. They are only ever *used* from the catch blocks below, but they cannot be read
175
+ // from there: dropEnumValue runs inside a migration transaction (runMigration.ts routes any
176
+ // migration mentioning it through `newTransaction`), and once a statement in a transaction
177
+ // block errors, Postgres aborts the block and fails every subsequent statement with
178
+ // `25P02 current transaction is aborted`. A pg_constraint SELECT issued from a catch block
179
+ // would therefore throw itself and bury the very error we are trying to explain.
180
+ const checkConstraintsByPosition = await checkConstraintsOnColumns(db, replacements);
172
181
  // temporarily set all table columns depending on this enum to an acceptable alternate type
173
- for (const tableAndColumnToChange of replacements) {
182
+ for (const [position, tableAndColumnToChange] of replacements.entries()) {
174
183
  const tableAndColumnToChangeAsArray = tableAndColumnToChange;
175
184
  const isArray = tableAndColumnToChangeAsArray.array || false;
176
- await db.schema
185
+ await retypeOrExplain(() => db.schema
177
186
  .alterTable(tableAndColumnToChange.table)
178
187
  .alterColumn(tableAndColumnToChange.column, col => col.setDataType(computedTemporaryType(isArray)))
179
- .execute();
188
+ .execute(), {
189
+ enumName,
190
+ value,
191
+ table: tableAndColumnToChange.table,
192
+ column: tableAndColumnToChange.column,
193
+ retypeDirection: 'toText',
194
+ ...constraintContextFor(checkConstraintsByPosition, position),
195
+ });
180
196
  }
181
197
  // collect enum values before dropping type
182
198
  const allEnumValues = await getEnumValues(db, enumName);
@@ -187,15 +203,23 @@ export default class DreamMigrationHelpers {
187
203
  .createType(enumName)
188
204
  .asEnum(allEnumValues.filter(val => val !== value))
189
205
  .execute();
190
- for (const tableAndColumnToChange of replacements) {
206
+ for (const [position, tableAndColumnToChange] of replacements.entries()) {
191
207
  const isArray = tableAndColumnToChange.array || false;
208
+ const retypeContext = {
209
+ enumName,
210
+ value,
211
+ table: tableAndColumnToChange.table,
212
+ column: tableAndColumnToChange.column,
213
+ retypeDirection: 'toEnum',
214
+ ...constraintContextFor(checkConstraintsByPosition, position),
215
+ };
192
216
  if (isArray) {
193
217
  await replaceArrayValues(db, value, tableAndColumnToChange);
194
- await updateTableColumnToNewEnumArrayType(db, enumName, tableAndColumnToChange);
218
+ await retypeOrExplain(() => updateTableColumnToNewEnumArrayType(db, enumName, tableAndColumnToChange), retypeContext);
195
219
  }
196
220
  else {
197
221
  await replaceNonArrayValues(db, value, tableAndColumnToChange);
198
- await updateTableColumnToNewEnumType(db, enumName, tableAndColumnToChange);
222
+ await retypeOrExplain(() => updateTableColumnToNewEnumType(db, enumName, tableAndColumnToChange), retypeContext);
199
223
  }
200
224
  }
201
225
  }
@@ -330,6 +354,103 @@ export default class DreamMigrationHelpers {
330
354
  }
331
355
  }
332
356
  }
357
+ /**
358
+ * Run one of dropEnumValue's internal column retypes, and on failure re-raise it as a
359
+ * {@link DropEnumValueRetypeFailed} naming the table, the column and the check constraints
360
+ * that were read before any retype ran. The original error is kept reachable underneath.
361
+ *
362
+ * A caught throwable is normalized to an `Error` rather than cast to one: the error's
363
+ * message getter dereferences `originalError.message`, so a driver (or a mock) that rejects
364
+ * with a non-Error would otherwise blow up inside the getter and destroy the very
365
+ * diagnostic this wrapping exists to produce.
366
+ */
367
+ async function retypeOrExplain(retype, context) {
368
+ try {
369
+ await retype();
370
+ }
371
+ catch (error) {
372
+ throw new DropEnumValueRetypeFailed({
373
+ ...context,
374
+ originalError: error instanceof Error ? error : new Error(String(error)),
375
+ });
376
+ }
377
+ }
378
+ /**
379
+ * The check constraints defined on each replacement's column, indexed by the replacement's
380
+ * position in `replacements` (both of dropEnumValue's loops walk the same array in order, so
381
+ * the position identifies a replacement exactly and no key string has to be kept in sync).
382
+ *
383
+ * Position, rather than the replacement object, because the same object may legitimately
384
+ * appear at two positions — `const r = {...}; replacements: [r, r]` — and a Map keyed on the
385
+ * object would collapse those two entries onto one, reporting each column's constraints twice
386
+ * and emitting a duplicated dropConstraint line for every one of them.
387
+ *
388
+ * One catalog query covers every replacement: the (relation, column) pairs are passed as two
389
+ * arrays and unnested `WITH ORDINALITY`, so each row carries the 1-based position of the
390
+ * replacement it belongs to. Issuing one query per replacement would cost a round trip per
391
+ * column on every successful `dropEnumValue`, and Kysely serializes them all on the
392
+ * migration's single connection.
393
+ *
394
+ * `relationResolved` is selected separately from the constraints, because "the table has no
395
+ * check constraints" and "the table name did not resolve" both produce zero constraint rows
396
+ * and the error message must not confuse the two.
397
+ *
398
+ * `to_regclass` rather than a `::regclass` cast so that an unknown table yields no rows
399
+ * instead of throwing, leaving the retype itself to report a missing table exactly as it
400
+ * does today. The name handed to it is quoted per segment, because Kysely quotes the
401
+ * identifiers it emits (`alterTable('MixedCase')` -> `"MixedCase"`) while an unquoted
402
+ * `to_regclass` argument case-folds and would silently miss every non-lowercase table.
403
+ *
404
+ * The selected aliases are deliberately single words: a `CamelCasePlugin` on the connection
405
+ * rewrites result keys, and `ord`/`resolved`/`name`/`definition` survive that rewrite
406
+ * unchanged.
407
+ */
408
+ async function checkConstraintsOnColumns(db, replacements) {
409
+ const byPosition = replacements.map(() => ({
410
+ relationResolved: false,
411
+ checkConstraints: [],
412
+ }));
413
+ if (replacements.length === 0)
414
+ return byPosition;
415
+ const relationNames = replacements.map(replacement => quotedRelationName(replacement.table));
416
+ const columnNames = replacements.map(replacement => replacement.column);
417
+ const response = await sql `
418
+ SELECT
419
+ t.ord::int AS ord,
420
+ (to_regclass(t.relname) IS NOT NULL) AS resolved,
421
+ con.name AS name,
422
+ con.definition AS definition
423
+ FROM unnest(${relationNames}::text[], ${columnNames}::text[])
424
+ WITH ORDINALITY AS t(relname, colname, ord)
425
+ LEFT JOIN LATERAL (
426
+ SELECT c.conname AS name, pg_get_constraintdef(c.oid) AS definition
427
+ FROM pg_constraint c
428
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
429
+ WHERE c.contype = 'c'
430
+ AND c.conrelid = to_regclass(t.relname)
431
+ AND a.attname = t.colname
432
+ ) con ON true
433
+ ORDER BY t.ord, con.name
434
+ `.execute(db);
435
+ for (const row of response.rows) {
436
+ const entry = byPosition[Number(row.ord) - 1];
437
+ if (entry === undefined)
438
+ continue;
439
+ entry.relationResolved = row.resolved;
440
+ if (row.name !== null && row.definition !== null) {
441
+ entry.checkConstraints.push({ name: row.name, definition: row.definition });
442
+ }
443
+ }
444
+ return byPosition;
445
+ }
446
+ /**
447
+ * The constraint metadata for the replacement at `position`. A position with no entry (which
448
+ * cannot happen today, but would be a silent lie if it did) reads as "not read", never as
449
+ * "none defined".
450
+ */
451
+ function constraintContextFor(byPosition, position) {
452
+ return byPosition[position] ?? { relationResolved: false, checkConstraints: [] };
453
+ }
333
454
  async function getEnumValues(db, enumName) {
334
455
  const response = await sql `SELECT unnest(enum_range(NULL::${sql.raw(enumName)}))`.execute(db);
335
456
  return response.rows.map(row => row.unnest);