@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.
- package/dist/cjs/src/cli/index.js +7 -4
- package/dist/cjs/src/errors/NoColumnsToAlterMigration.js +17 -0
- package/dist/cjs/src/errors/UnparseableMigrationColumn.js +16 -0
- package/dist/cjs/src/helpers/cli/generateMigration.js +20 -3
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +74 -9
- package/dist/esm/src/cli/index.js +7 -4
- package/dist/esm/src/errors/NoColumnsToAlterMigration.js +17 -0
- package/dist/esm/src/errors/UnparseableMigrationColumn.js +16 -0
- package/dist/esm/src/helpers/cli/generateMigration.js +20 -3
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +74 -9
- package/dist/types/src/cli/index.d.ts +1 -1
- package/dist/types/src/errors/NoColumnsToAlterMigration.d.ts +6 -0
- package/dist/types/src/errors/UnparseableMigrationColumn.d.ts +5 -0
- package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +11 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +39 -39
- package/docs/classes/db.PostgresQueryDriver.html +40 -40
- package/docs/classes/db.QueryDriverBase.html +38 -38
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +2 -2
- package/docs/classes/errors.DecryptionParseError.html +2 -2
- package/docs/classes/errors.DecryptionRotationError.html +3 -3
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +124 -124
- package/docs/classes/index.DreamApp.html +10 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +62 -62
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +5 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- 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}
|
|
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
|
|
165
|
-
${INDENT} remove-status-from-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
|
|
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)) :
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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}
|
|
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
|
|
165
|
-
${INDENT} remove-status-from-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
|
|
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)) :
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { LegacyCompatiblePrimaryKeyType } from '../../types/db.js';
|
|
2
|
-
export
|
|
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`
|