@rvoh/dream 2.20.0 → 2.21.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 (196) hide show
  1. package/dist/cjs/src/cli/index.js +7 -4
  2. package/dist/cjs/src/errors/NoColumnsToAlterMigration.js +17 -0
  3. package/dist/cjs/src/errors/UnparseableMigrationColumn.js +16 -0
  4. package/dist/cjs/src/helpers/cli/generateMigration.js +20 -3
  5. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +74 -9
  6. package/dist/esm/src/cli/index.js +7 -4
  7. package/dist/esm/src/errors/NoColumnsToAlterMigration.js +17 -0
  8. package/dist/esm/src/errors/UnparseableMigrationColumn.js +16 -0
  9. package/dist/esm/src/helpers/cli/generateMigration.js +20 -3
  10. package/dist/esm/src/helpers/cli/generateMigrationContent.js +74 -9
  11. package/dist/types/src/cli/index.d.ts +1 -1
  12. package/dist/types/src/errors/NoColumnsToAlterMigration.d.ts +6 -0
  13. package/dist/types/src/errors/UnparseableMigrationColumn.d.ts +5 -0
  14. package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +11 -1
  15. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  16. package/docs/classes/db.KyselyQueryDriver.html +39 -39
  17. package/docs/classes/db.PostgresQueryDriver.html +40 -40
  18. package/docs/classes/db.QueryDriverBase.html +38 -38
  19. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  20. package/docs/classes/errors.ColumnOverflow.html +3 -3
  21. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  22. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  23. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  24. package/docs/classes/errors.DecryptionError.html +2 -2
  25. package/docs/classes/errors.DecryptionParseError.html +2 -2
  26. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  27. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  28. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  29. package/docs/classes/errors.InvalidClockTime.html +2 -2
  30. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  31. package/docs/classes/errors.InvalidDateTime.html +2 -2
  32. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  33. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  34. package/docs/classes/errors.NotNullViolation.html +3 -3
  35. package/docs/classes/errors.RecordNotFound.html +3 -3
  36. package/docs/classes/errors.ValidationError.html +3 -3
  37. package/docs/classes/index.CalendarDate.html +33 -33
  38. package/docs/classes/index.ClockTime.html +32 -32
  39. package/docs/classes/index.ClockTimeTz.html +35 -35
  40. package/docs/classes/index.DateTime.html +86 -86
  41. package/docs/classes/index.Decorators.html +19 -19
  42. package/docs/classes/index.Dream.html +124 -124
  43. package/docs/classes/index.DreamApp.html +10 -10
  44. package/docs/classes/index.DreamTransaction.html +2 -2
  45. package/docs/classes/index.Env.html +2 -2
  46. package/docs/classes/index.Query.html +62 -62
  47. package/docs/classes/system.CliFileWriter.html +4 -4
  48. package/docs/classes/system.DreamBin.html +2 -2
  49. package/docs/classes/system.DreamCLI.html +7 -7
  50. package/docs/classes/system.DreamImporter.html +2 -2
  51. package/docs/classes/system.DreamLogos.html +2 -2
  52. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  53. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  54. package/docs/classes/system.PathHelpers.html +3 -3
  55. package/docs/classes/utils.Encrypt.html +3 -3
  56. package/docs/classes/utils.Range.html +2 -2
  57. package/docs/functions/db.closeAllDbConnections.html +1 -1
  58. package/docs/functions/db.dreamDbConnections.html +1 -1
  59. package/docs/functions/db.untypedDb.html +1 -1
  60. package/docs/functions/db.validateColumn.html +1 -1
  61. package/docs/functions/db.validateTable.html +1 -1
  62. package/docs/functions/errors.pgErrorType.html +1 -1
  63. package/docs/functions/index.DreamSerializer.html +1 -1
  64. package/docs/functions/index.ObjectSerializer.html +1 -1
  65. package/docs/functions/index.ReplicaSafe.html +1 -1
  66. package/docs/functions/index.STI.html +1 -1
  67. package/docs/functions/index.SoftDelete.html +1 -1
  68. package/docs/functions/utils.camelize.html +1 -1
  69. package/docs/functions/utils.capitalize.html +1 -1
  70. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  71. package/docs/functions/utils.compact.html +1 -1
  72. package/docs/functions/utils.groupBy.html +1 -1
  73. package/docs/functions/utils.hyphenize.html +1 -1
  74. package/docs/functions/utils.intersection.html +1 -1
  75. package/docs/functions/utils.isEmpty.html +1 -1
  76. package/docs/functions/utils.normalizeUnicode.html +1 -1
  77. package/docs/functions/utils.pascalize.html +1 -1
  78. package/docs/functions/utils.percent.html +1 -1
  79. package/docs/functions/utils.range.html +1 -1
  80. package/docs/functions/utils.round.html +1 -1
  81. package/docs/functions/utils.sanitizeString.html +1 -1
  82. package/docs/functions/utils.snakeify.html +1 -1
  83. package/docs/functions/utils.sort.html +1 -1
  84. package/docs/functions/utils.sortBy.html +1 -1
  85. package/docs/functions/utils.sortObjectByKey.html +1 -1
  86. package/docs/functions/utils.sortObjectByValue.html +1 -1
  87. package/docs/functions/utils.uncapitalize.html +1 -1
  88. package/docs/functions/utils.uniq.html +1 -1
  89. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  90. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  91. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  92. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  93. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  94. package/docs/interfaces/types.DecoratorContext.html +2 -2
  95. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  96. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  97. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  98. package/docs/interfaces/types.DurationObject.html +2 -2
  99. package/docs/interfaces/types.EncryptOptions.html +2 -2
  100. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  101. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  102. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  103. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  104. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  105. package/docs/types/openapi.OpenapiFormats.html +1 -1
  106. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  107. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  108. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  109. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  110. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  111. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  112. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  113. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  114. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  115. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  116. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  117. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  118. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  119. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  120. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  121. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  122. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  123. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  124. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  125. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  126. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  127. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  128. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  130. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  131. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  132. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  133. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  134. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  135. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  136. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  137. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  138. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  139. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  141. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  142. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  143. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  144. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  145. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  146. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  147. package/docs/types/types.CalendarDateObject.html +1 -1
  148. package/docs/types/types.Camelized.html +1 -1
  149. package/docs/types/types.ClockTimeObject.html +1 -1
  150. package/docs/types/types.DbConnectionType.html +1 -1
  151. package/docs/types/types.DbTypes.html +1 -1
  152. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  153. package/docs/types/types.DreamAttributes.html +1 -1
  154. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  155. package/docs/types/types.DreamClassColumn.html +1 -1
  156. package/docs/types/types.DreamColumn.html +1 -1
  157. package/docs/types/types.DreamColumnNames.html +1 -1
  158. package/docs/types/types.DreamLogLevel.html +1 -1
  159. package/docs/types/types.DreamLogger.html +2 -2
  160. package/docs/types/types.DreamModelSerializerType.html +1 -1
  161. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  162. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  163. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  164. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  165. package/docs/types/types.DreamSerializable.html +1 -1
  166. package/docs/types/types.DreamSerializableArray.html +1 -1
  167. package/docs/types/types.DreamSerializerKey.html +1 -1
  168. package/docs/types/types.DreamSerializers.html +1 -1
  169. package/docs/types/types.DreamVirtualColumns.html +1 -1
  170. package/docs/types/types.DurationUnit.html +1 -1
  171. package/docs/types/types.EncryptAlgorithm.html +1 -1
  172. package/docs/types/types.HasManyStatement.html +1 -1
  173. package/docs/types/types.HasOneStatement.html +1 -1
  174. package/docs/types/types.Hyphenized.html +1 -1
  175. package/docs/types/types.Pascalized.html +1 -1
  176. package/docs/types/types.PrimaryKeyType.html +1 -1
  177. package/docs/types/types.RoundingPrecision.html +1 -1
  178. package/docs/types/types.SerializerCasing.html +1 -1
  179. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  180. package/docs/types/types.Snakeified.html +1 -1
  181. package/docs/types/types.StrictInterface.html +1 -1
  182. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  183. package/docs/types/types.UpdateableProperties.html +1 -1
  184. package/docs/types/types.ValidationType.html +1 -1
  185. package/docs/types/types.ViewModel.html +2 -2
  186. package/docs/types/types.ViewModelClass.html +1 -1
  187. package/docs/types/types.WeekdayName.html +1 -1
  188. package/docs/types/types.WhereStatementForDream.html +1 -1
  189. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  190. package/docs/variables/index.DreamConst.html +1 -1
  191. package/docs/variables/index.ops.html +1 -1
  192. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  193. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  194. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  195. package/docs/variables/system.primaryKeyTypes.html +1 -1
  196. package/package.json +1 -1
@@ -45,6 +45,8 @@ ${INDENT} - timetz
45
45
  ${INDENT} - timetz[]
46
46
  ${INDENT} - integer
47
47
  ${INDENT} - integer[]
48
+ ${INDENT} - boolean
49
+ ${INDENT} - boolean[]
48
50
  ${INDENT}
49
51
  ${INDENT} - decimal:
50
52
  ${INDENT} - decimal[]:
@@ -153,16 +155,17 @@ ${INDENT} # Add columns to an existing table (suffix with -to-<table_name> for
153
155
  ${INDENT} pnpm psy g:migration add-timezone-to-users timezone:string
154
156
  ${INDENT} pnpm psy g:migration add-bio-to-users bio:text:optional avatar_url:string:optional
155
157
  ${INDENT}
156
- ${INDENT} # Remove columns (suffix with -from-<table_name>)
157
- ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts
158
+ ${INDENT} # Remove columns (suffix with -from-<table_name>; columns are described with
159
+ ${INDENT} # their types, same shorthand as -to-, so the rollback (down) can recreate them)
160
+ ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts legacy_status:string
158
161
  ${INDENT}
159
162
  ${INDENT} # General schema change (no table suffix — generates empty up/down methods)
160
163
  ${INDENT} pnpm psy g:migration create-unique-index-on-invitations`)
161
164
  .argument('<migrationName>', `Kebab-case name describing the change. End with -to-<table_name> or -from-<table_name> to auto-generate an alterTable scaffold for that table.
162
165
  ${INDENT}
163
166
  ${INDENT}Examples:
164
- ${INDENT} add-phone-to-users # scaffolds alterTable('users', ...)
165
- ${INDENT} remove-status-from-posts # scaffolds alterTable('posts', ...)
167
+ ${INDENT} add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)
168
+ ${INDENT} remove-status-from-posts status:string # scaffolds alterTable('posts', ...)
166
169
  ${INDENT} create-join-table-host-places # empty migration (no table suffix match)`)
167
170
  .option('--connection-name <connectionName>', 'the database connection to use for this migration. Only needed for multi-database setups; defaults to "default"')
168
171
  .argument('[columnsWithTypes...]', columnsWithTypesDescriptionForMigration)
@@ -0,0 +1,17 @@
1
+ export default class NoColumnsToAlterMigration extends Error {
2
+ table;
3
+ alterDirection;
4
+ constructor(table, alterDirection) {
5
+ super();
6
+ this.table = table;
7
+ this.alterDirection = alterDirection;
8
+ }
9
+ get message() {
10
+ return `
11
+ no valid columns to ${this.alterDirection === 'remove' ? 'drop from' : 'add to'} table '${this.table}' in this alter migration.
12
+
13
+ Pass at least one column declaration with a resolvable type, e.g.:
14
+ name:string
15
+ `;
16
+ }
17
+ }
@@ -0,0 +1,16 @@
1
+ export default class UnparseableMigrationColumn extends Error {
2
+ declaration;
3
+ constructor(declaration) {
4
+ super();
5
+ this.declaration = declaration;
6
+ }
7
+ get message() {
8
+ return `
9
+ could not determine a column type for the following migration column declaration:
10
+ ${this.declaration}
11
+
12
+ Make sure the column includes a type, e.g.:
13
+ ${this.declaration.split(':')[0]}:string
14
+ `;
15
+ }
16
+ }
@@ -1,6 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import pluralize from 'pluralize-esm';
3
- import generateMigrationContent from '../cli/generateMigrationContent.js';
3
+ import generateMigrationContent, { MIGRATION_TABLE_NAME_PLACEHOLDER, } from '../cli/generateMigrationContent.js';
4
4
  import primaryKeyType from '../db/primaryKeyType.js';
5
5
  import hyphenize from '../hyphenize.js';
6
6
  import migrationVersion from '../migrationVersion.js';
@@ -9,6 +9,18 @@ import dreamPath from '../path/dreamPath.js';
9
9
  import snakeify from '../snakeify.js';
10
10
  import generateStiMigrationContent from './generateStiMigrationContent.js';
11
11
  import writeGeneratedFile from './writeGeneratedFile.js';
12
+ /**
13
+ * Equivalent to `migrationName.match(new RegExp(`${marker}(.+)$`))?.[1]`, but
14
+ * without a regex: CodeQL flags `.+` anchored to `$` as a polynomial-ReDoS
15
+ * shape, and a plain `indexOf`/`slice` does the same leftmost-match-to-end
16
+ * job with no backtracking risk.
17
+ */
18
+ function tableNameFromSuffix(migrationName, marker) {
19
+ const markerIndex = migrationName.indexOf(marker);
20
+ if (markerIndex === -1)
21
+ return undefined;
22
+ return migrationName.slice(markerIndex + marker.length) || undefined;
23
+ }
12
24
  export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, tableName: explicitTableName, modelClassName, softDelete = false, }) {
13
25
  const migrationsBasePath = connectionName === 'default'
14
26
  ? path.join(dreamPath('db'), 'migrations')
@@ -33,12 +45,17 @@ export default async function generateMigration({ migrationName, columnsWithType
33
45
  });
34
46
  }
35
47
  else {
36
- const tableName = migrationName.match(/-to-(.+)$/)?.[1];
48
+ const toTableName = tableNameFromSuffix(migrationName, '-to-');
49
+ const fromTableName = toTableName
50
+ ? undefined
51
+ : tableNameFromSuffix(migrationName, '-from-');
52
+ const tableName = toTableName || fromTableName;
37
53
  content = generateMigrationContent({
38
- table: tableName ? pluralize(snakeify(tableName)) : '<table-name>',
54
+ table: tableName ? pluralize(snakeify(tableName)) : MIGRATION_TABLE_NAME_PLACEHOLDER,
39
55
  columnsWithTypes,
40
56
  primaryKeyType: primaryKeyType(connectionName),
41
57
  createOrAlter: 'alter',
58
+ alterDirection: fromTableName ? 'remove' : 'add',
42
59
  });
43
60
  }
44
61
  await writeGeneratedFile({
@@ -2,11 +2,19 @@ import pluralize from 'pluralize-esm';
2
2
  import lookupModelByGlobalName from '../../dream-app/helpers/lookupModelByGlobalName.js';
3
3
  import Query from '../../dream/Query.js';
4
4
  import InvalidDecimalFieldPassedToGenerator from '../../errors/InvalidDecimalFieldPassedToGenerator.js';
5
+ import NoColumnsToAlterMigration from '../../errors/NoColumnsToAlterMigration.js';
6
+ import UnparseableMigrationColumn from '../../errors/UnparseableMigrationColumn.js';
5
7
  import camelize from '../camelize.js';
6
8
  import compact from '../compact.js';
7
9
  import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFullyQualifiedModelName.js';
8
10
  import snakeify from '../snakeify.js';
9
11
  import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
12
+ // Sentinel table name used by generateMigration.ts when a standalone
13
+ // migration name matches neither a `-to-<table>` nor `-from-<table>` suffix.
14
+ // That path intentionally produces a stub the user is expected to hand-edit
15
+ // (e.g. an index-only migration with no column operations at all), so it's
16
+ // exempt from the "no valid columns" check below.
17
+ export const MIGRATION_TABLE_NAME_PLACEHOLDER = '<table-name>';
10
18
  const STI_TYPE_COLUMN_NAME = 'type';
11
19
  // deleted_at is deliberately NOT in this list: the SoftDelete default scope's
12
20
  // `WHERE deleted_at IS NULL` is unselective on healthy tables, Dream internals
@@ -15,8 +23,9 @@ const STI_TYPE_COLUMN_NAME = 'type';
15
23
  // default. See spec/unit/cli/generateMigrationContent.spec.ts
16
24
  // ("deleted_at is deliberately NOT indexed") and the CHANGELOG.
17
25
  const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
18
- export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
26
+ export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', alterDirection = 'add', stiChildClassName, softDelete = false, } = {}) {
19
27
  const altering = createOrAlter === 'alter';
28
+ const removingInUp = altering && alterDirection === 'remove';
20
29
  let requireCitextExtension = false;
21
30
  const checkConstraints = [];
22
31
  // When creating a new table, we automatically emit `created_at`,
@@ -65,7 +74,17 @@ export default function generateMigrationContent({ connectionName = 'default', t
65
74
  // when creating a migration for an STI child, we don't want to include notNull;
66
75
  // instead, we'll add a check constraint that uses the STI child class name
67
76
  const sqlAttributeType = getAttributeType(attributeType, descriptors);
68
- if (attributeType === undefined || ['hasone', 'hasmany'].includes(processedAttrType))
77
+ if (attributeType === undefined) {
78
+ // In alter mode (both `-to-`/add and `-from-`/remove), a column whose
79
+ // type can't be resolved (e.g. a mistyped declaration with no `:type`
80
+ // segment) must not silently vanish from the generated migration —
81
+ // that's especially dangerous for `-from-` migrations, whose whole
82
+ // premise is describing removed columns so `down` can restore them.
83
+ if (altering)
84
+ throw new UnparseableMigrationColumn(attributeDeclaration);
85
+ return acc;
86
+ }
87
+ if (['hasone', 'hasmany'].includes(processedAttrType))
69
88
  return acc;
70
89
  if (attributeType === 'citext')
71
90
  requireCitextExtension = true;
@@ -169,6 +188,25 @@ export async function down(db: Kysely<any>): Promise<void> {
169
188
  }\
170
189
  `;
171
190
  }
191
+ // An alter migration (either `-to-`/add or `-from-`/remove) with no valid
192
+ // columns to add/drop would otherwise emit a bare `.alterTable(...).execute()`
193
+ // with no column operation at all, which Postgres rejects at migration-run
194
+ // time rather than generation time. Fail loudly here instead.
195
+ //
196
+ // This guard is scoped to the standalone `g:migration` `-to-`/`-from-` flow
197
+ // only (`stiChildClassName` is unset there). `g:sti-child` legitimately
198
+ // calls this function in alter mode with zero `columnsWithTypes` — a
199
+ // zero-attribute STI child is a documented use case (see `g:sti-child`'s
200
+ // help example in src/cli/index.ts), and `generateStiMigrationContent`
201
+ // handles the type-column/check-constraint machinery separately from the
202
+ // `columnDefs` this check inspects, so an empty `columnDefs` there is
203
+ // expected, not a sign of a mistyped/unparseable column list.
204
+ if (altering &&
205
+ columnDefs.length === 0 &&
206
+ table !== MIGRATION_TABLE_NAME_PLACEHOLDER &&
207
+ !stiChildClassName) {
208
+ throw new NoColumnsToAlterMigration(table, alterDirection);
209
+ }
172
210
  const citextExtension = requireCitextExtension
173
211
  ? ` await DreamMigrationHelpers.createExtension(db, 'citext')\n\n`
174
212
  : '';
@@ -179,7 +217,22 @@ export async function down(db: Kysely<any>): Promise<void> {
179
217
  const newlineIndent = '\n ';
180
218
  const newlineDoubleIndent = '\n ';
181
219
  const doubleNewlineIndent = '\n\n ';
182
- const columnDefLines = columnDefs.length ? newlineDoubleIndent + columnDefs.join(newlineDoubleIndent) : '';
220
+ // For a `-from-` migration (`alterDirection: 'remove'`), `down` re-adds the
221
+ // column(s) that `up` removed. The generator has no way to know the
222
+ // column's original default, so a non-optional (`.notNull()`) column with
223
+ // no default emitted here will fail at migration-run time against a table
224
+ // that already has rows. We can't fix that without a lot more heavy
225
+ // lifting (and the risk that comes with it), so flag it with a comment
226
+ // instead of silently generating a `down` that's likely to break.
227
+ const NO_KNOWN_DEFAULT_COMMENT = "// NOTE: the generator doesn't know this column's original default; this addColumn will fail against a table with existing rows unless you add a default by hand.";
228
+ const finalColumnDefs = removingInUp
229
+ ? columnDefs.map(def => def.includes('.notNull()') && !def.includes('.defaultTo(')
230
+ ? `${NO_KNOWN_DEFAULT_COMMENT}\n ${def}`
231
+ : def)
232
+ : columnDefs;
233
+ const columnDefLines = finalColumnDefs.length
234
+ ? newlineDoubleIndent + finalColumnDefs.join(newlineDoubleIndent)
235
+ : '';
183
236
  const columnDropLines = columnDrops.length
184
237
  ? newlineDoubleIndent + columnDrops.join(newlineDoubleIndent) + newlineDoubleIndent
185
238
  : '';
@@ -190,21 +243,33 @@ export async function down(db: Kysely<any>): Promise<void> {
190
243
  newlineDoubleIndent +
191
244
  ".addColumn('updated_at', 'timestamp', col => col.notNull())" +
192
245
  (emitDeletedAtColumn ? newlineDoubleIndent + ".addColumn('deleted_at', 'timestamp')" : '');
246
+ // For `alterDirection: 'remove'` (a `-from-` migration), the enum *type*
247
+ // itself must never be created or dropped here, even when the column
248
+ // declaration includes inline enum values — enums are frequently reused
249
+ // across columns/tables, so `up` must drop only the column (no `dropType`)
250
+ // and `down` must re-add only the column (no `createType`), identical to
251
+ // referencing an existing enum by name with no values.
252
+ const enumCreateStatements = removingInUp ? '' : generateEnumStatements(columnsWithTypes);
253
+ const enumDropStatements = removingInUp ? '' : generateEnumDropStatements(columnsWithTypes);
254
+ const addColumnsBody = `${citextExtension}${enumCreateStatements} await db.schema
255
+ .${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${timestampColumnLines}
256
+ .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(doubleNewlineIndent)}${checkConstraints.join('')}`;
257
+ const removeColumnsBody = ` ${altering
258
+ ? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
259
+ : `await db.schema.dropTable('${table}').execute()`}${enumDropStatements}`;
260
+ const upBody = removingInUp ? removeColumnsBody : addColumnsBody;
261
+ const downBody = removingInUp ? addColumnsBody : removeColumnsBody;
193
262
  return `\
194
263
  ${dreamDbImports.length ? `import { ${dreamDbImports.join(', ')} } from '@rvoh/dream/db'\n` : ''}import { ${kyselyImports.join(', ')} } from 'kysely'
195
264
 
196
265
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
266
  export async function up(db: Kysely<any>): Promise<void> {
198
- ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
199
- .${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${timestampColumnLines}
200
- .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(doubleNewlineIndent)}${checkConstraints.join('')}
267
+ ${upBody}
201
268
  }
202
269
 
203
270
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
271
  export async function down(db: Kysely<any>): Promise<void> {
205
- ${altering
206
- ? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
207
- : `await db.schema.dropTable('${table}').execute()`}${generateEnumDropStatements(columnsWithTypes)}
272
+ ${downBody}
208
273
  }\
209
274
  `;
210
275
  }
@@ -45,6 +45,8 @@ ${INDENT} - timetz
45
45
  ${INDENT} - timetz[]
46
46
  ${INDENT} - integer
47
47
  ${INDENT} - integer[]
48
+ ${INDENT} - boolean
49
+ ${INDENT} - boolean[]
48
50
  ${INDENT}
49
51
  ${INDENT} - decimal:
50
52
  ${INDENT} - decimal[]:
@@ -153,16 +155,17 @@ ${INDENT} # Add columns to an existing table (suffix with -to-<table_name> for
153
155
  ${INDENT} pnpm psy g:migration add-timezone-to-users timezone:string
154
156
  ${INDENT} pnpm psy g:migration add-bio-to-users bio:text:optional avatar_url:string:optional
155
157
  ${INDENT}
156
- ${INDENT} # Remove columns (suffix with -from-<table_name>)
157
- ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts
158
+ ${INDENT} # Remove columns (suffix with -from-<table_name>; columns are described with
159
+ ${INDENT} # their types, same shorthand as -to-, so the rollback (down) can recreate them)
160
+ ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts legacy_status:string
158
161
  ${INDENT}
159
162
  ${INDENT} # General schema change (no table suffix — generates empty up/down methods)
160
163
  ${INDENT} pnpm psy g:migration create-unique-index-on-invitations`)
161
164
  .argument('<migrationName>', `Kebab-case name describing the change. End with -to-<table_name> or -from-<table_name> to auto-generate an alterTable scaffold for that table.
162
165
  ${INDENT}
163
166
  ${INDENT}Examples:
164
- ${INDENT} add-phone-to-users # scaffolds alterTable('users', ...)
165
- ${INDENT} remove-status-from-posts # scaffolds alterTable('posts', ...)
167
+ ${INDENT} add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)
168
+ ${INDENT} remove-status-from-posts status:string # scaffolds alterTable('posts', ...)
166
169
  ${INDENT} create-join-table-host-places # empty migration (no table suffix match)`)
167
170
  .option('--connection-name <connectionName>', 'the database connection to use for this migration. Only needed for multi-database setups; defaults to "default"')
168
171
  .argument('[columnsWithTypes...]', columnsWithTypesDescriptionForMigration)
@@ -0,0 +1,17 @@
1
+ export default class NoColumnsToAlterMigration extends Error {
2
+ table;
3
+ alterDirection;
4
+ constructor(table, alterDirection) {
5
+ super();
6
+ this.table = table;
7
+ this.alterDirection = alterDirection;
8
+ }
9
+ get message() {
10
+ return `
11
+ no valid columns to ${this.alterDirection === 'remove' ? 'drop from' : 'add to'} table '${this.table}' in this alter migration.
12
+
13
+ Pass at least one column declaration with a resolvable type, e.g.:
14
+ name:string
15
+ `;
16
+ }
17
+ }
@@ -0,0 +1,16 @@
1
+ export default class UnparseableMigrationColumn extends Error {
2
+ declaration;
3
+ constructor(declaration) {
4
+ super();
5
+ this.declaration = declaration;
6
+ }
7
+ get message() {
8
+ return `
9
+ could not determine a column type for the following migration column declaration:
10
+ ${this.declaration}
11
+
12
+ Make sure the column includes a type, e.g.:
13
+ ${this.declaration.split(':')[0]}:string
14
+ `;
15
+ }
16
+ }
@@ -1,6 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import pluralize from 'pluralize-esm';
3
- import generateMigrationContent from '../cli/generateMigrationContent.js';
3
+ import generateMigrationContent, { MIGRATION_TABLE_NAME_PLACEHOLDER, } from '../cli/generateMigrationContent.js';
4
4
  import primaryKeyType from '../db/primaryKeyType.js';
5
5
  import hyphenize from '../hyphenize.js';
6
6
  import migrationVersion from '../migrationVersion.js';
@@ -9,6 +9,18 @@ import dreamPath from '../path/dreamPath.js';
9
9
  import snakeify from '../snakeify.js';
10
10
  import generateStiMigrationContent from './generateStiMigrationContent.js';
11
11
  import writeGeneratedFile from './writeGeneratedFile.js';
12
+ /**
13
+ * Equivalent to `migrationName.match(new RegExp(`${marker}(.+)$`))?.[1]`, but
14
+ * without a regex: CodeQL flags `.+` anchored to `$` as a polynomial-ReDoS
15
+ * shape, and a plain `indexOf`/`slice` does the same leftmost-match-to-end
16
+ * job with no backtracking risk.
17
+ */
18
+ function tableNameFromSuffix(migrationName, marker) {
19
+ const markerIndex = migrationName.indexOf(marker);
20
+ if (markerIndex === -1)
21
+ return undefined;
22
+ return migrationName.slice(markerIndex + marker.length) || undefined;
23
+ }
12
24
  export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, tableName: explicitTableName, modelClassName, softDelete = false, }) {
13
25
  const migrationsBasePath = connectionName === 'default'
14
26
  ? path.join(dreamPath('db'), 'migrations')
@@ -33,12 +45,17 @@ export default async function generateMigration({ migrationName, columnsWithType
33
45
  });
34
46
  }
35
47
  else {
36
- const tableName = migrationName.match(/-to-(.+)$/)?.[1];
48
+ const toTableName = tableNameFromSuffix(migrationName, '-to-');
49
+ const fromTableName = toTableName
50
+ ? undefined
51
+ : tableNameFromSuffix(migrationName, '-from-');
52
+ const tableName = toTableName || fromTableName;
37
53
  content = generateMigrationContent({
38
- table: tableName ? pluralize(snakeify(tableName)) : '<table-name>',
54
+ table: tableName ? pluralize(snakeify(tableName)) : MIGRATION_TABLE_NAME_PLACEHOLDER,
39
55
  columnsWithTypes,
40
56
  primaryKeyType: primaryKeyType(connectionName),
41
57
  createOrAlter: 'alter',
58
+ alterDirection: fromTableName ? 'remove' : 'add',
42
59
  });
43
60
  }
44
61
  await writeGeneratedFile({
@@ -2,11 +2,19 @@ import pluralize from 'pluralize-esm';
2
2
  import lookupModelByGlobalName from '../../dream-app/helpers/lookupModelByGlobalName.js';
3
3
  import Query from '../../dream/Query.js';
4
4
  import InvalidDecimalFieldPassedToGenerator from '../../errors/InvalidDecimalFieldPassedToGenerator.js';
5
+ import NoColumnsToAlterMigration from '../../errors/NoColumnsToAlterMigration.js';
6
+ import UnparseableMigrationColumn from '../../errors/UnparseableMigrationColumn.js';
5
7
  import camelize from '../camelize.js';
6
8
  import compact from '../compact.js';
7
9
  import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFullyQualifiedModelName.js';
8
10
  import snakeify from '../snakeify.js';
9
11
  import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
12
+ // Sentinel table name used by generateMigration.ts when a standalone
13
+ // migration name matches neither a `-to-<table>` nor `-from-<table>` suffix.
14
+ // That path intentionally produces a stub the user is expected to hand-edit
15
+ // (e.g. an index-only migration with no column operations at all), so it's
16
+ // exempt from the "no valid columns" check below.
17
+ export const MIGRATION_TABLE_NAME_PLACEHOLDER = '<table-name>';
10
18
  const STI_TYPE_COLUMN_NAME = 'type';
11
19
  // deleted_at is deliberately NOT in this list: the SoftDelete default scope's
12
20
  // `WHERE deleted_at IS NULL` is unselective on healthy tables, Dream internals
@@ -15,8 +23,9 @@ const STI_TYPE_COLUMN_NAME = 'type';
15
23
  // default. See spec/unit/cli/generateMigrationContent.spec.ts
16
24
  // ("deleted_at is deliberately NOT indexed") and the CHANGELOG.
17
25
  const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
18
- export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
26
+ export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', alterDirection = 'add', stiChildClassName, softDelete = false, } = {}) {
19
27
  const altering = createOrAlter === 'alter';
28
+ const removingInUp = altering && alterDirection === 'remove';
20
29
  let requireCitextExtension = false;
21
30
  const checkConstraints = [];
22
31
  // When creating a new table, we automatically emit `created_at`,
@@ -65,7 +74,17 @@ export default function generateMigrationContent({ connectionName = 'default', t
65
74
  // when creating a migration for an STI child, we don't want to include notNull;
66
75
  // instead, we'll add a check constraint that uses the STI child class name
67
76
  const sqlAttributeType = getAttributeType(attributeType, descriptors);
68
- if (attributeType === undefined || ['hasone', 'hasmany'].includes(processedAttrType))
77
+ if (attributeType === undefined) {
78
+ // In alter mode (both `-to-`/add and `-from-`/remove), a column whose
79
+ // type can't be resolved (e.g. a mistyped declaration with no `:type`
80
+ // segment) must not silently vanish from the generated migration —
81
+ // that's especially dangerous for `-from-` migrations, whose whole
82
+ // premise is describing removed columns so `down` can restore them.
83
+ if (altering)
84
+ throw new UnparseableMigrationColumn(attributeDeclaration);
85
+ return acc;
86
+ }
87
+ if (['hasone', 'hasmany'].includes(processedAttrType))
69
88
  return acc;
70
89
  if (attributeType === 'citext')
71
90
  requireCitextExtension = true;
@@ -169,6 +188,25 @@ export async function down(db: Kysely<any>): Promise<void> {
169
188
  }\
170
189
  `;
171
190
  }
191
+ // An alter migration (either `-to-`/add or `-from-`/remove) with no valid
192
+ // columns to add/drop would otherwise emit a bare `.alterTable(...).execute()`
193
+ // with no column operation at all, which Postgres rejects at migration-run
194
+ // time rather than generation time. Fail loudly here instead.
195
+ //
196
+ // This guard is scoped to the standalone `g:migration` `-to-`/`-from-` flow
197
+ // only (`stiChildClassName` is unset there). `g:sti-child` legitimately
198
+ // calls this function in alter mode with zero `columnsWithTypes` — a
199
+ // zero-attribute STI child is a documented use case (see `g:sti-child`'s
200
+ // help example in src/cli/index.ts), and `generateStiMigrationContent`
201
+ // handles the type-column/check-constraint machinery separately from the
202
+ // `columnDefs` this check inspects, so an empty `columnDefs` there is
203
+ // expected, not a sign of a mistyped/unparseable column list.
204
+ if (altering &&
205
+ columnDefs.length === 0 &&
206
+ table !== MIGRATION_TABLE_NAME_PLACEHOLDER &&
207
+ !stiChildClassName) {
208
+ throw new NoColumnsToAlterMigration(table, alterDirection);
209
+ }
172
210
  const citextExtension = requireCitextExtension
173
211
  ? ` await DreamMigrationHelpers.createExtension(db, 'citext')\n\n`
174
212
  : '';
@@ -179,7 +217,22 @@ export async function down(db: Kysely<any>): Promise<void> {
179
217
  const newlineIndent = '\n ';
180
218
  const newlineDoubleIndent = '\n ';
181
219
  const doubleNewlineIndent = '\n\n ';
182
- const columnDefLines = columnDefs.length ? newlineDoubleIndent + columnDefs.join(newlineDoubleIndent) : '';
220
+ // For a `-from-` migration (`alterDirection: 'remove'`), `down` re-adds the
221
+ // column(s) that `up` removed. The generator has no way to know the
222
+ // column's original default, so a non-optional (`.notNull()`) column with
223
+ // no default emitted here will fail at migration-run time against a table
224
+ // that already has rows. We can't fix that without a lot more heavy
225
+ // lifting (and the risk that comes with it), so flag it with a comment
226
+ // instead of silently generating a `down` that's likely to break.
227
+ const NO_KNOWN_DEFAULT_COMMENT = "// NOTE: the generator doesn't know this column's original default; this addColumn will fail against a table with existing rows unless you add a default by hand.";
228
+ const finalColumnDefs = removingInUp
229
+ ? columnDefs.map(def => def.includes('.notNull()') && !def.includes('.defaultTo(')
230
+ ? `${NO_KNOWN_DEFAULT_COMMENT}\n ${def}`
231
+ : def)
232
+ : columnDefs;
233
+ const columnDefLines = finalColumnDefs.length
234
+ ? newlineDoubleIndent + finalColumnDefs.join(newlineDoubleIndent)
235
+ : '';
183
236
  const columnDropLines = columnDrops.length
184
237
  ? newlineDoubleIndent + columnDrops.join(newlineDoubleIndent) + newlineDoubleIndent
185
238
  : '';
@@ -190,21 +243,33 @@ export async function down(db: Kysely<any>): Promise<void> {
190
243
  newlineDoubleIndent +
191
244
  ".addColumn('updated_at', 'timestamp', col => col.notNull())" +
192
245
  (emitDeletedAtColumn ? newlineDoubleIndent + ".addColumn('deleted_at', 'timestamp')" : '');
246
+ // For `alterDirection: 'remove'` (a `-from-` migration), the enum *type*
247
+ // itself must never be created or dropped here, even when the column
248
+ // declaration includes inline enum values — enums are frequently reused
249
+ // across columns/tables, so `up` must drop only the column (no `dropType`)
250
+ // and `down` must re-add only the column (no `createType`), identical to
251
+ // referencing an existing enum by name with no values.
252
+ const enumCreateStatements = removingInUp ? '' : generateEnumStatements(columnsWithTypes);
253
+ const enumDropStatements = removingInUp ? '' : generateEnumDropStatements(columnsWithTypes);
254
+ const addColumnsBody = `${citextExtension}${enumCreateStatements} await db.schema
255
+ .${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${timestampColumnLines}
256
+ .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(doubleNewlineIndent)}${checkConstraints.join('')}`;
257
+ const removeColumnsBody = ` ${altering
258
+ ? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
259
+ : `await db.schema.dropTable('${table}').execute()`}${enumDropStatements}`;
260
+ const upBody = removingInUp ? removeColumnsBody : addColumnsBody;
261
+ const downBody = removingInUp ? addColumnsBody : removeColumnsBody;
193
262
  return `\
194
263
  ${dreamDbImports.length ? `import { ${dreamDbImports.join(', ')} } from '@rvoh/dream/db'\n` : ''}import { ${kyselyImports.join(', ')} } from 'kysely'
195
264
 
196
265
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
266
  export async function up(db: Kysely<any>): Promise<void> {
198
- ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
199
- .${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${timestampColumnLines}
200
- .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(doubleNewlineIndent)}${checkConstraints.join('')}
267
+ ${upBody}
201
268
  }
202
269
 
203
270
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
271
  export async function down(db: Kysely<any>): Promise<void> {
205
- ${altering
206
- ? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
207
- : `await db.schema.dropTable('${table}').execute()`}${generateEnumDropStatements(columnsWithTypes)}
272
+ ${downBody}
208
273
  }\
209
274
  `;
210
275
  }
@@ -14,7 +14,7 @@ export type SpawnOptions = Omit<NodeSpawnOptions, 'shell'> & {
14
14
  args?: string[];
15
15
  };
16
16
  export declare const CLI_INDENT = " ";
17
- export declare const baseColumnsWithTypesDescription = "space separated snake-case properties like this:\n title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal\n \n all properties default to not nullable; null can be allowed by appending ':optional':\n subtitle:string:optional\n \n supported types:\n - uuid:\n - uuid[]:\n a column optimized for storing UUIDs\n \n - citext:\n - citext[]:\n case insensitive text (indexes and queries are automatically case insensitive)\n \n - encrypted:\n encrypted text (used in conjunction with the @deco.Encrypted decorator)\n \n - string:\n - string[]:\n varchar; allowed length defaults to 255, but may be customized, e.g.: subtitle:string:128 or subtitle:string:128:optional\n \n - text\n - text[]\n - date\n - date[]\n - datetime\n - datetime[]\n - time\n - time[]\n - timetz\n - timetz[]\n - integer\n - integer[]\n \n - decimal:\n - decimal[]:\n precision,scale is required, e.g.: volume:decimal:3,2 or volume:decimal:3,2:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: volume:decimal[]:3,2\n \n - enum:\n - enum[]:\n include the enum name to automatically create the enum:\n type:enum:room_types:bathroom,kitchen,bedroom or type:enum:room_types:bathroom,kitchen,bedroom:optional\n \n omit the enum values to leverage an existing enum (omits the enum type creation):\n type:enum:room_types or type:enum:room_types:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom";
17
+ export declare const baseColumnsWithTypesDescription = "space separated snake-case properties like this:\n title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal\n \n all properties default to not nullable; null can be allowed by appending ':optional':\n subtitle:string:optional\n \n supported types:\n - uuid:\n - uuid[]:\n a column optimized for storing UUIDs\n \n - citext:\n - citext[]:\n case insensitive text (indexes and queries are automatically case insensitive)\n \n - encrypted:\n encrypted text (used in conjunction with the @deco.Encrypted decorator)\n \n - string:\n - string[]:\n varchar; allowed length defaults to 255, but may be customized, e.g.: subtitle:string:128 or subtitle:string:128:optional\n \n - text\n - text[]\n - date\n - date[]\n - datetime\n - datetime[]\n - time\n - time[]\n - timetz\n - timetz[]\n - integer\n - integer[]\n - boolean\n - boolean[]\n \n - decimal:\n - decimal[]:\n precision,scale is required, e.g.: volume:decimal:3,2 or volume:decimal:3,2:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: volume:decimal[]:3,2\n \n - enum:\n - enum[]:\n include the enum name to automatically create the enum:\n type:enum:room_types:bathroom,kitchen,bedroom or type:enum:room_types:bathroom,kitchen,bedroom:optional\n \n omit the enum values to leverage an existing enum (omits the enum type creation):\n type:enum:room_types or type:enum:room_types:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom";
18
18
  export declare const columnsWithTypesDescriptionForStiChild: string;
19
19
  export default class DreamCLI {
20
20
  /**
@@ -0,0 +1,6 @@
1
+ export default class NoColumnsToAlterMigration extends Error {
2
+ table: string;
3
+ alterDirection: 'add' | 'remove';
4
+ constructor(table: string, alterDirection: 'add' | 'remove');
5
+ get message(): string;
6
+ }
@@ -0,0 +1,5 @@
1
+ export default class UnparseableMigrationColumn extends Error {
2
+ declaration: string;
3
+ constructor(declaration: string);
4
+ get message(): string;
5
+ }
@@ -1,10 +1,20 @@
1
1
  import { LegacyCompatiblePrimaryKeyType } from '../../types/db.js';
2
- export default function generateMigrationContent({ connectionName, table, columnsWithTypes, primaryKeyType, createOrAlter, stiChildClassName, softDelete, }?: {
2
+ export declare const MIGRATION_TABLE_NAME_PLACEHOLDER = "<table-name>";
3
+ export default function generateMigrationContent({ connectionName, table, columnsWithTypes, primaryKeyType, createOrAlter, alterDirection, stiChildClassName, softDelete, }?: {
3
4
  connectionName?: string;
4
5
  table?: string | undefined;
5
6
  columnsWithTypes?: string[] | undefined;
6
7
  primaryKeyType?: LegacyCompatiblePrimaryKeyType | undefined;
7
8
  createOrAlter?: 'create' | 'alter' | undefined;
9
+ /**
10
+ * Only meaningful when `createOrAlter: 'alter'`. `'add'` (the default,
11
+ * matching a `-to-<table>`-suffixed migration name) emits column additions
12
+ * in `up` and the matching drops in `down`. `'remove'` (matching a
13
+ * `-from-<table>`-suffixed migration name) inverts this: `up` drops the
14
+ * named columns and `down` re-adds them with their declared types, so
15
+ * `down` is the rollback that restores what `up` removed.
16
+ */
17
+ alterDirection?: 'add' | 'remove' | undefined;
8
18
  stiChildClassName?: string | undefined;
9
19
  /**
10
20
  * When true (and creating a new table), auto-emits a nullable `deleted_at`