diff --git a/src/migration-builder.ts b/src/migration-builder.ts index 6eddddce..9c94e2a4 100644 --- a/src/migration-builder.ts +++ b/src/migration-builder.ts @@ -197,7 +197,7 @@ export default class MigrationBuilderImpl implements MigrationBuilder { private _useTransaction: boolean - constructor(db: DB, typeShorthands: ColumnDefinitions, shouldDecamelize: boolean) { + constructor(db: DB, typeShorthands: ColumnDefinitions | undefined, shouldDecamelize: boolean) { this._steps = [] this._REVERSE_MODE = false // by default, all migrations are wrapped in a transaction @@ -210,11 +210,15 @@ export default class MigrationBuilderImpl implements MigrationBuilder { // calls the operation or its reverse, and appends the result (array of sql statements) // to the steps array const wrap = (operation: T) => (...args: Parameters) => { - if (this._REVERSE_MODE && typeof operation.reverse !== 'function') { - const name = `pgm.${operation.name}()` - throw new Error(`Impossible to automatically infer down migration for "${name}"`) + if (this._REVERSE_MODE) { + if (typeof operation.reverse !== 'function') { + const name = `pgm.${operation.name}()` + throw new Error(`Impossible to automatically infer down migration for "${name}"`) + } + this._steps = this._steps.concat(operation.reverse(...args)) + } else { + this._steps = this._steps.concat(operation(...args)) } - this._steps = this._steps.concat(this._REVERSE_MODE ? operation.reverse(...args) : operation(...args)) } const options: MigrationOptions = { @@ -325,7 +329,7 @@ export default class MigrationBuilderImpl implements MigrationBuilder { this.func = PgLiteral.create // expose DB so we can access database within transaction - const wrapDB = (operation: (...args: T[]) => R) => (...args: T[]) => { + const wrapDB = (operation: (...args: T) => R) => (...args: T) => { if (this._REVERSE_MODE) { throw new Error('Impossible to automatically infer down migration') } diff --git a/src/migration.ts b/src/migration.ts index 1b170617..ea4f411f 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -30,9 +30,11 @@ export const loadMigrationFiles = async (dir: string, ignorePattern?: string) => return stats.isFile() ? file : null }), ) - ).sort() + ) + .filter((file): file is string => Boolean(file)) + .sort() const filter = new RegExp(`^(${ignorePattern})$`) // eslint-disable-line security/detect-non-literal-regexp - return ignorePattern === undefined ? files : files.filter(i => i && !filter.test(i)) + return ignorePattern === undefined ? files : files.filter(i => !filter.test(i)) } const getLastSuffix = async (dir: string, ignorePattern?: string) => { @@ -87,7 +89,7 @@ export class Migration implements RunMigration { public readonly options: RunnerOption - public readonly typeShorthands: ColumnDefinitions + public readonly typeShorthands?: ColumnDefinitions public readonly log: typeof console.log @@ -153,7 +155,7 @@ export class Migration implements RunMigration { this.log(`${sqlSteps.join('\n')}\n\n`) return sqlSteps.reduce( - (promise, sql) => promise.then((): unknown => this.options.dryRun || this.db.query(sql)), + (promise: Promise, sql) => promise.then((): unknown => this.options.dryRun || this.db.query(sql)), Promise.resolve(), ) } @@ -169,7 +171,7 @@ export class Migration implements RunMigration { } } - const action: MigrationAction | false = this[direction] + const action: MigrationAction | false | undefined = this[direction] if (typeof action !== 'function') { throw new Error( @@ -181,7 +183,7 @@ export class Migration implements RunMigration { } apply(direction: MigrationDirection) { - const pgm = new MigrationBuilder(this.db, this.typeShorthands, this.options.decamelize) + const pgm = new MigrationBuilder(this.db, this.typeShorthands, Boolean(this.options.decamelize)) const action = this._getAction(direction) if (this.down === this.up) { diff --git a/src/operations/domains.ts b/src/operations/domains.ts index 6d2a0e08..fe828e20 100644 --- a/src/operations/domains.ts +++ b/src/operations/domains.ts @@ -5,7 +5,8 @@ import { CreateDomain, DropDomain, AlterDomain, RenameDomain } from './domainsTy export { CreateDomain, DropDomain, AlterDomain, RenameDomain } export function dropDomain(mOptions: MigrationOptions) { - const _drop: DropDomain = (domainName, { ifExists, cascade } = {}) => { + const _drop: DropDomain = (domainName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const domainNameStr = mOptions.literal(domainName) diff --git a/src/operations/extensions.ts b/src/operations/extensions.ts index 3478a200..49953fa0 100644 --- a/src/operations/extensions.ts +++ b/src/operations/extensions.ts @@ -5,7 +5,8 @@ import { CreateExtension, DropExtension } from './extensionsTypes' export { CreateExtension, DropExtension } export function dropExtension(mOptions: MigrationOptions) { - const _drop: DropExtension = (extensions, { ifExists, cascade } = {}) => { + const _drop: DropExtension = (extensions, options = {}) => { + const { ifExists, cascade } = options if (!_.isArray(extensions)) extensions = [extensions] // eslint-disable-line no-param-reassign const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' @@ -18,7 +19,8 @@ export function dropExtension(mOptions: MigrationOptions) { } export function createExtension(mOptions: MigrationOptions) { - const _create: CreateExtension = (extensions, { ifNotExists, schema } = {}) => { + const _create: CreateExtension = (extensions, options = {}) => { + const { ifNotExists, schema } = options if (!_.isArray(extensions)) extensions = [extensions] // eslint-disable-line no-param-reassign const ifNotExistsStr = ifNotExists ? ' IF NOT EXISTS' : '' const schemaStr = schema ? ` SCHEMA ${mOptions.literal(schema)}` : '' diff --git a/src/operations/functions.ts b/src/operations/functions.ts index e361d890..865cf60b 100644 --- a/src/operations/functions.ts +++ b/src/operations/functions.ts @@ -5,7 +5,8 @@ import { CreateFunction, DropFunction, RenameFunction } from './functionsTypes' export { CreateFunction, DropFunction, RenameFunction } export function dropFunction(mOptions: MigrationOptions) { - const _drop: DropFunction = (functionName, functionParams = [], { ifExists, cascade } = {}) => { + const _drop: DropFunction = (functionName, functionParams = [], options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const paramsStr = formatParams(functionParams, mOptions) diff --git a/src/operations/operators.ts b/src/operations/operators.ts index 90b0dd08..d3efaeff 100644 --- a/src/operations/operators.ts +++ b/src/operations/operators.ts @@ -45,7 +45,7 @@ export function dropOperator(mOptions: MigrationOptions) { export function createOperator(mOptions: MigrationOptions) { const _create: CreateOperator = (operatorName, options) => { - const { procedure, left, right, commutator, negator, restrict, join, hashes, merges } = options + const { procedure, left, right, commutator, negator, restrict, join, hashes, merges } = options || {} const defs = [] defs.push(`PROCEDURE = ${mOptions.literal(procedure)}`) @@ -81,7 +81,8 @@ export function createOperator(mOptions: MigrationOptions) { } export function dropOperatorFamily(mOptions: MigrationOptions) { - const _drop: DropOperatorFamily = (operatorFamilyName, indexMethod, { ifExists, cascade } = {}) => { + const _drop: DropOperatorFamily = (operatorFamilyName, indexMethod, options = {}) => { + const { ifExists, cascade } = options const operatorFamilyNameStr = mOptions.literal(operatorFamilyName) const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' @@ -153,7 +154,8 @@ export function renameOperatorFamily(mOptions: MigrationOptions) { } export function dropOperatorClass(mOptions: MigrationOptions) { - const _drop: DropOperatorClass = (operatorClassName, indexMethod, { ifExists, cascade } = {}) => { + const _drop: DropOperatorClass = (operatorClassName, indexMethod, options = {}) => { + const { ifExists, cascade } = options const operatorClassNameStr = mOptions.literal(operatorClassName) const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' diff --git a/src/operations/operatorsTypes.ts b/src/operations/operatorsTypes.ts index 35bad846..6a8a60e1 100644 --- a/src/operations/operatorsTypes.ts +++ b/src/operations/operatorsTypes.ts @@ -30,7 +30,7 @@ export interface OperatorListDefinition { params?: FunctionParam[] } -type CreateOperatorFn = (operatorName: Name, options?: CreateOperatorOptions & DropOperatorOptions) => string | string[] +type CreateOperatorFn = (operatorName: Name, options: CreateOperatorOptions & DropOperatorOptions) => string | string[] export type CreateOperator = CreateOperatorFn & { reverse: CreateOperatorFn } export type DropOperator = (operatorName: Name, dropOptions?: DropOperatorOptions) => string | string[] type CreateOperatorClassFn = ( diff --git a/src/operations/policies.ts b/src/operations/policies.ts index bb3790f9..be5ca145 100644 --- a/src/operations/policies.ts +++ b/src/operations/policies.ts @@ -19,7 +19,8 @@ const makeClauses = ({ role, using, check }: PolicyOptions) => { } export function dropPolicy(mOptions: MigrationOptions) { - const _drop: DropPolicy = (tableName, policyName, { ifExists } = {}) => { + const _drop: DropPolicy = (tableName, policyName, options = {}) => { + const { ifExists } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const policyNameStr = mOptions.literal(policyName) const tableNameStr = mOptions.literal(tableName) diff --git a/src/operations/roles.ts b/src/operations/roles.ts index bfede10e..8429139d 100644 --- a/src/operations/roles.ts +++ b/src/operations/roles.ts @@ -56,7 +56,8 @@ const formatRoleOptions = (roleOptions: RoleOptions = {}) => { } export function dropRole(mOptions: MigrationOptions) { - const _drop: DropRole = (roleName, { ifExists } = {}) => { + const _drop: DropRole = (roleName, options = {}) => { + const { ifExists } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const roleNameStr = mOptions.literal(roleName) return `DROP ROLE${ifExistsStr} ${roleNameStr};` diff --git a/src/operations/schemas.ts b/src/operations/schemas.ts index e81cb16a..df68f239 100644 --- a/src/operations/schemas.ts +++ b/src/operations/schemas.ts @@ -4,7 +4,8 @@ import { CreateSchema, DropSchema, RenameSchema } from './schemasTypes' export { CreateSchema, DropSchema, RenameSchema } export function dropSchema(mOptions: MigrationOptions) { - const _drop: DropSchema = (schemaName, { ifExists, cascade } = {}) => { + const _drop: DropSchema = (schemaName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const schemaNameStr = mOptions.literal(schemaName) @@ -14,7 +15,8 @@ export function dropSchema(mOptions: MigrationOptions) { } export function createSchema(mOptions: MigrationOptions) { - const _create: CreateSchema = (schemaName: string, { ifNotExists, authorization } = {}) => { + const _create: CreateSchema = (schemaName: string, options = {}) => { + const { ifNotExists, authorization } = options const ifNotExistsStr = ifNotExists ? ' IF NOT EXISTS' : '' const schemaNameStr = mOptions.literal(schemaName) const authorizationStr = authorization ? ` AUTHORIZATION ${authorization}` : '' diff --git a/src/operations/sequences.ts b/src/operations/sequences.ts index 0265c7f7..b9a4e772 100644 --- a/src/operations/sequences.ts +++ b/src/operations/sequences.ts @@ -5,7 +5,7 @@ import { ColumnDefinitions } from './tablesTypes' export { CreateSequence, DropSequence, AlterSequence, RenameSequence } -export const parseSequenceOptions = (typeShorthands: ColumnDefinitions, options: SequenceOptions) => { +export const parseSequenceOptions = (typeShorthands: ColumnDefinitions | undefined, options: SequenceOptions) => { const { type, increment, minvalue, maxvalue, start, cache, cycle, owner } = options const clauses: string[] = [] if (type) { @@ -44,7 +44,8 @@ export const parseSequenceOptions = (typeShorthands: ColumnDefinitions, options: } export function dropSequence(mOptions: MigrationOptions) { - const _drop: DropSequence = (sequenceName, { ifExists, cascade } = {}) => { + const _drop: DropSequence = (sequenceName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const sequenceNameStr = mOptions.literal(sequenceName) diff --git a/src/operations/tables.ts b/src/operations/tables.ts index 805f7c56..b826f36e 100644 --- a/src/operations/tables.ts +++ b/src/operations/tables.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import { MigrationOptions, Literal } from '../types' -import { applyType, applyTypeAdapters, comment, escapeValue, formatLines } from '../utils' +import { applyType, applyTypeAdapters, makeComment, escapeValue, formatLines } from '../utils' import { parseSequenceOptions } from './sequences' import { CreateTable, @@ -74,7 +74,7 @@ const parseColumns = ( const primaryColumns = _.chain(columnsWithOptions) .map((options: ColumnDefinition, columnName) => (options.primaryKey ? columnName : null)) - .filter() + .filter((columnName): columnName is string => Boolean(columnName)) .value() const multiplePrimaryColumns = primaryColumns.length > 1 @@ -85,13 +85,13 @@ const parseColumns = ( })) } - const comments: string[] = _.chain(columnsWithOptions) + const comments = _.chain(columnsWithOptions) .map( (options: ColumnDefinition, columnName) => typeof options.comment !== 'undefined' && - comment('COLUMN', `${mOptions.literal(tableName)}.${mOptions.literal(columnName)}`, options.comment), + makeComment('COLUMN', `${mOptions.literal(tableName)}.${mOptions.literal(columnName)}`, options.comment), ) - .filter() + .filter((comment): comment is string => Boolean(comment)) .value() return { @@ -132,10 +132,10 @@ const parseColumns = ( if (references) { const name = referencesConstraintName || (referencesConstraintComment ? `${tableName}_fk_${columnName}` : '') const constraintName = name ? `CONSTRAINT ${mOptions.literal(name)} ` : '' - constraints.push(`${constraintName}${parseReferences(options, mOptions.literal)}`) + constraints.push(`${constraintName}${parseReferences(options as ReferencesOptions, mOptions.literal)}`) if (referencesConstraintComment) { comments.push( - comment( + makeComment( `CONSTRAINT ${mOptions.literal(name)} ON`, mOptions.literal(tableName), referencesConstraintComment, @@ -164,16 +164,8 @@ const parseColumns = ( } } -const parseConstraints = (table: Name, options: ConstraintOptions, optionName: string, literal: Literal) => { - const { - check, - unique, - primaryKey, - foreignKeys, - exclude, - deferrable, - comment: optionComment, - }: ConstraintOptions = options +const parseConstraints = (table: Name, options: ConstraintOptions, optionName: string | null, literal: Literal) => { + const { check, unique, primaryKey, foreignKeys, exclude, deferrable, comment }: ConstraintOptions = options const tableName = typeof table === 'object' ? table.name : table let constraints = [] const comments = [] @@ -211,7 +203,7 @@ const parseConstraints = (table: Name, options: ConstraintOptions, optionName: s const referencesStr = parseReferences(fk, literal) constraints.push(`CONSTRAINT ${name} FOREIGN KEY (${key}) ${referencesStr}`) if (referencesConstraintComment) { - comments.push(comment(`CONSTRAINT ${name} ON`, literal(tableName), referencesConstraintComment)) + comments.push(makeComment(`CONSTRAINT ${name} ON`, literal(tableName), referencesConstraintComment)) } }) } @@ -223,9 +215,9 @@ const parseConstraints = (table: Name, options: ConstraintOptions, optionName: s if (deferrable) { constraints = constraints.map(constraint => `${constraint} ${parseDeferrable(options)}`) } - if (optionComment) { + if (comment) { if (!optionName) throw new Error('cannot comment on unspecified constraints') - comments.push(comment(`CONSTRAINT ${literal(optionName)} ON`, literal(tableName), optionComment)) + comments.push(makeComment(`CONSTRAINT ${literal(optionName)} ON`, literal(tableName), comment)) } return { constraints, @@ -234,12 +226,15 @@ const parseConstraints = (table: Name, options: ConstraintOptions, optionName: s } const parseLike = (like: Name | { table: Name; options?: LikeOptions }, literal: Literal) => { - const formatOptions = (name: 'INCLUDING' | 'EXCLUDING', options: Like | Like[]) => - (_.isArray(options) ? options : [options]).map(option => ` ${name} ${option}`).join('') + const formatOptions = (name: 'INCLUDING' | 'EXCLUDING', options?: Like | Like[]) => + (_.isArray(options) ? options : [options]) + .filter((option): option is Like => option !== undefined) + .map(option => ` ${name} ${option}`) + .join('') const table = typeof like === 'string' || !('table' in like) ? like : like.table const options = - typeof like === 'string' || !('options' in like) + typeof like === 'string' || !('options' in like) || like.options === undefined ? '' : [formatOptions('INCLUDING', like.options.including), formatOptions('EXCLUDING', like.options.excluding)].join( '', @@ -249,7 +244,8 @@ const parseLike = (like: Name | { table: Name; options?: LikeOptions }, literal: // TABLE export function dropTable(mOptions: MigrationOptions) { - const _drop: DropTable = (tableName, { ifExists, cascade } = {}) => { + const _drop: DropTable = (tableName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const tableNameStr = mOptions.literal(tableName) @@ -260,14 +256,7 @@ export function dropTable(mOptions: MigrationOptions) { export function createTable(mOptions: MigrationOptions) { const _create: CreateTable = (tableName, columns, options = {}) => { - const { - temporary, - ifNotExists, - inherits, - like, - constraints: optionsConstraints = {}, - comment: tableComment, - } = options + const { temporary, ifNotExists, inherits, like, constraints: optionsConstraints = {}, comment } = options const { columns: columnLines, constraints: crossColumnConstraints, comments: columnComments = [] } = parseColumns( tableName, columns, @@ -300,8 +289,8 @@ export function createTable(mOptions: MigrationOptions) { ${formatLines(tableDefinition)} )${inheritsStr};` const comments = [...columnComments, ...constraintComments] - if (typeof tableComment !== 'undefined') { - comments.push(comment('TABLE', mOptions.literal(tableName), tableComment)) + if (typeof comment !== 'undefined') { + comments.push(makeComment('TABLE', mOptions.literal(tableName), comment)) } return `${createTableQuery}${comments.length > 0 ? `\n${comments.join('\n')}` : ''}` } @@ -324,7 +313,8 @@ export function alterTable(mOptions: MigrationOptions) { // COLUMNS export function dropColumns(mOptions: MigrationOptions) { - const _drop: DropColumns = (tableName, columns, { ifExists, cascade } = {}) => { + const _drop: DropColumns = (tableName, columns, options = {}) => { + const { ifExists, cascade } = options if (typeof columns === 'string') { columns = [columns] // eslint-disable-line no-param-reassign } else if (!_.isArray(columns) && typeof columns === 'object') { @@ -342,7 +332,8 @@ ${columnsStr};` } export function addColumns(mOptions: MigrationOptions) { - const _add: AddColumns = (tableName, columns, { ifNotExists } = {}) => { + const _add: AddColumns = (tableName, columns, options = {}) => { + const { ifNotExists } = options const { columns: columnLines, comments: columnComments = [] } = parseColumns(tableName, columns, mOptions) const columnsStr = formatLines(columnLines, ` ADD ${ifNotExists ? 'IF NOT EXISTS ' : ''}`) const tableNameStr = mOptions.literal(tableName) @@ -356,16 +347,7 @@ export function addColumns(mOptions: MigrationOptions) { export function alterColumn(mOptions: MigrationOptions): AlterColumn { return (tableName, columnName, options) => { - const { - default: defaultValue, - type, - collation, - using, - notNull, - allowNull, - comment: columnComment, - generated, - } = options + const { default: defaultValue, type, collation, using, notNull, allowNull, comment, generated } = options const actions: string[] = [] if (defaultValue === null) { actions.push('DROP DEFAULT') @@ -399,8 +381,8 @@ export function alterColumn(mOptions: MigrationOptions): AlterColumn { const columnsStr = formatLines(actions, ` ALTER ${mOptions.literal(columnName)} `) queries.push(`ALTER TABLE ${mOptions.literal(tableName)}\n${columnsStr};`) } - if (typeof columnComment !== 'undefined') { - queries.push(comment('COLUMN', `${mOptions.literal(tableName)}.${mOptions.literal(columnName)}`, columnComment)) + if (typeof comment !== 'undefined') { + queries.push(makeComment('COLUMN', `${mOptions.literal(tableName)}.${mOptions.literal(columnName)}`, comment)) } return queries.join('\n') } @@ -439,7 +421,8 @@ export function renameConstraint(mOptions: MigrationOptions) { } export function dropConstraint(mOptions: MigrationOptions) { - const _drop: DropConstraint = (tableName, constraintName, { ifExists, cascade } = {}) => { + const _drop: DropConstraint = (tableName, constraintName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const tableNameStr = mOptions.literal(tableName) @@ -465,6 +448,11 @@ export function addConstraint(mOptions: MigrationOptions) { const constraintStr = formatLines(constraints, ' ADD ') return [`ALTER TABLE ${mOptions.literal(tableName)}\n${constraintStr};`, ...comments].join('\n') } - _add.reverse = dropConstraint(mOptions) + _add.reverse = (tableName, constraintName, options) => { + if (constraintName === null) { + throw new Error(`Impossible to automatically infer down migration for addConstraint without naming constraint`) + } + return dropConstraint(mOptions)(tableName, constraintName, options) + } return _add } diff --git a/src/operations/tablesTypes.ts b/src/operations/tablesTypes.ts index c1a7dc8c..8475d8db 100644 --- a/src/operations/tablesTypes.ts +++ b/src/operations/tablesTypes.ts @@ -6,13 +6,13 @@ export type Action = 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DE export interface ReferencesOptions { referencesConstraintName?: string referencesConstraintComment?: string - references?: Name + references: Name onDelete?: Action onUpdate?: Action match?: 'FULL' | 'SIMPLE' } -export interface ColumnDefinition extends ReferencesOptions { +export interface ColumnDefinition extends Partial { type: string collation?: string unique?: boolean diff --git a/src/operations/triggers.ts b/src/operations/triggers.ts index 43f2119c..e03c3d61 100644 --- a/src/operations/triggers.ts +++ b/src/operations/triggers.ts @@ -9,7 +9,8 @@ import { FunctionOptions } from './functionsTypes' export { CreateTrigger, DropTrigger, RenameTrigger } export function dropTrigger(mOptions: MigrationOptions) { - const _drop: DropTrigger = (tableName, triggerName, { ifExists, cascade } = {}) => { + const _drop: DropTrigger = (tableName, triggerName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const triggerNameStr = mOptions.literal(triggerName) @@ -23,7 +24,7 @@ export function createTrigger(mOptions: MigrationOptions) { const _create: CreateTrigger = ( tableName: Name, triggerName: string, - triggerOptions: TriggerOptions & FunctionOptions & DropOptions, + triggerOptions: (TriggerOptions & DropOptions) | (TriggerOptions & FunctionOptions & DropOptions), definition?: Value, ) => { const { constraint, condition, operation, deferrable, deferred, functionParams = [] } = triggerOptions @@ -32,17 +33,21 @@ export function createTrigger(mOptions: MigrationOptions) { if (constraint) { when = 'AFTER' } + if (!when) { + throw new Error('"when" (BEFORE/AFTER/INSTEAD OF) have to be specified') + } const isInsteadOf = /instead\s+of/i.test(when) if (isInsteadOf) { level = 'ROW' } if (definition) { - functionName = functionName || triggerName + functionName = functionName === undefined ? triggerName : functionName } - if (!when) { - throw new Error('"when" (BEFORE/AFTER/INSTEAD OF) have to be specified') - } else if (isInsteadOf && condition) { + if (!functionName) { + throw new Error("Can't determine function name") + } + if (isInsteadOf && condition) { throw new Error("INSTEAD OF trigger can't have condition specified") } if (!operations) { @@ -65,7 +70,12 @@ export function createTrigger(mOptions: MigrationOptions) { ${conditionClause}EXECUTE PROCEDURE ${functionNameStr}(${paramsStr});` const fnSQL = definition - ? `${createFunction(mOptions)(functionName, [], { ...triggerOptions, returns: 'trigger' }, definition)}\n` + ? `${createFunction(mOptions)( + functionName, + [], + { ...(triggerOptions as FunctionOptions), returns: 'trigger' }, + definition, + )}\n` : '' return `${fnSQL}${triggerSQL}` } @@ -73,7 +83,7 @@ export function createTrigger(mOptions: MigrationOptions) { _create.reverse = ( tableName: Name, triggerName: string, - triggerOptions: TriggerOptions & FunctionOptions & DropOptions, + triggerOptions: TriggerOptions & DropOptions, definition?: Value, ) => { const triggerSQL = dropTrigger(mOptions)(tableName, triggerName, triggerOptions) diff --git a/src/operations/types.ts b/src/operations/types.ts index dbeeec13..54f6cf2e 100644 --- a/src/operations/types.ts +++ b/src/operations/types.ts @@ -26,7 +26,8 @@ export { } export function dropType(mOptions: MigrationOptions) { - const _drop: DropType = (typeName, { ifExists, cascade } = {}) => { + const _drop: DropType = (typeName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const typeNameStr = mOptions.literal(typeName) diff --git a/src/operations/views.ts b/src/operations/views.ts index ae46a375..2406f561 100644 --- a/src/operations/views.ts +++ b/src/operations/views.ts @@ -5,7 +5,8 @@ import { CreateView, DropView, AlterView, AlterViewColumn, RenameView } from './ export { CreateView, DropView, AlterView, AlterViewColumn, RenameView } export function dropView(mOptions: MigrationOptions) { - const _drop: DropView = (viewName, { ifExists, cascade } = {}) => { + const _drop: DropView = (viewName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const viewNameStr = mOptions.literal(viewName) diff --git a/src/operations/viewsMaterialized.ts b/src/operations/viewsMaterialized.ts index ffe99f02..d50384f3 100644 --- a/src/operations/viewsMaterialized.ts +++ b/src/operations/viewsMaterialized.ts @@ -26,7 +26,8 @@ const storageParameterStr = (sto } export function dropMaterializedView(mOptions: MigrationOptions) { - const _drop: DropMaterializedView = (viewName, { ifExists, cascade } = {}) => { + const _drop: DropMaterializedView = (viewName, options = {}) => { + const { ifExists, cascade } = options const ifExistsStr = ifExists ? ' IF EXISTS' : '' const cascadeStr = cascade ? ' CASCADE' : '' const viewNameStr = mOptions.literal(viewName) @@ -113,7 +114,8 @@ export function renameMaterializedViewColumn(mOptions: MigrationOptions) { } export function refreshMaterializedView(mOptions: MigrationOptions) { - const _refresh: RefreshMaterializedView = (viewName, { concurrently, data } = {}) => { + const _refresh: RefreshMaterializedView = (viewName, options = {}) => { + const { concurrently, data } = options const concurrentlyStr = concurrently ? ' CONCURRENTLY' : '' const dataStr = dataClause(data) const viewNameStr = mOptions.literal(viewName) diff --git a/src/runner.ts b/src/runner.ts index 1a9d802a..84421a47 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -57,7 +57,7 @@ const ensureMigrationsTable = async (db: DBConnection, options: RunnerOption): P const schema = getMigrationTableSchema(options) const { migrationsTable } = options const fullTableName = createSchemalize( - options.decamelize, + Boolean(options.decamelize), true, )({ schema, @@ -89,7 +89,7 @@ const getRunMigrations = async (db: DBConnection, options: RunnerOption) => { const schema = getMigrationTableSchema(options) const { migrationsTable } = options const fullTableName = createSchemalize( - options.decamelize, + Boolean(options.decamelize), true, )({ schema, @@ -134,7 +134,10 @@ const checkOrder = (runNames: string[], migrations: Migration[]) => { } const runMigrations = (toRun: Migration[], method: 'markAsRun' | 'apply', direction: MigrationDirection) => - toRun.reduce((promise, migration) => promise.then(() => migration[method](direction)), Promise.resolve()) + toRun.reduce( + (promise: Promise, migration) => promise.then(() => migration[method](direction)), + Promise.resolve(), + ) export default async (options: RunnerOption): Promise => { const log = options.log || console.log diff --git a/src/types.ts b/src/types.ts index f632c346..f50da91e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -113,7 +113,7 @@ export interface MigrationBuilderActions { } export interface MigrationOptions { - typeShorthands: tables.ColumnDefinitions + typeShorthands?: tables.ColumnDefinitions schemalize: Literal literal: Literal } diff --git a/src/utils.ts b/src/utils.ts index 7f7ddd36..6500be44 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,10 +9,9 @@ const identity = (v: T) => v const quote = (str: string) => `"${str}"` export const createSchemalize = (shouldDecamelize: boolean, shouldQuote: boolean) => { - const transform: Literal = [ - shouldDecamelize ? decamelize : identity, - shouldQuote ? quote : identity, - ].reduce((acc, fn) => (fn === identity ? acc : x => acc(fn(x)))) + const transform = [shouldDecamelize ? decamelize : identity, shouldQuote ? quote : identity].reduce((acc, fn) => + fn === identity ? acc : (x: string) => acc(fn(x)), + ) return (v: Name) => { if (typeof v === 'object') { const { schema, name } = v @@ -24,7 +23,8 @@ export const createSchemalize = (shouldDecamelize: boolean, shouldQuote: boolean export const createTransformer = (literal: Literal) => (s: string, d?: { [key: string]: Name }) => Object.keys(d || {}).reduce( - (str: string, p) => str.replace(new RegExp(`{${p}}`, 'g'), literal(d[p])), // eslint-disable-line security/detect-non-literal-regexp + (str: string, p) => + str.replace(new RegExp(`{${p}}`, 'g'), d === undefined || d[p] === undefined ? '' : literal(d[p])), // eslint-disable-line security/detect-non-literal-regexp s, ) @@ -60,8 +60,10 @@ export const escapeValue = (val: Value): string | number => { return '' } -export const getSchemas = (schema: string | string[]): string[] => { - const schemas = (Array.isArray(schema) ? schema : [schema]).filter(s => typeof s === 'string' && s.length > 0) +export const getSchemas = (schema?: string | string[]): string[] => { + const schemas = (Array.isArray(schema) ? schema : [schema]).filter( + (s): s is string => typeof s === 'string' && s.length > 0, + ) return schemas.length > 0 ? schemas : ['public'] } @@ -101,7 +103,7 @@ export const applyType = ( delete ext.type } const t = typeShorthands[types[types.length - 1]] - ext = { ...(typeof t === 'string' ? { type: t } : t), ...ext } + ext = { ...(typeof t === 'string' ? { type: t } : t), ...(ext === null ? {} : ext) } if (types.includes(ext.type)) { throw new Error(`Shorthands contain cyclic dependency: ${types.join(', ')}, ${ext.type}`) } else { @@ -118,8 +120,8 @@ export const applyType = ( } } -const formatParam = (mOptions: MigrationOptions) => (param: FunctionParamType) => { - const { mode, name, type, default: defaultValue }: FunctionParamType = applyType(param, mOptions.typeShorthands) +const formatParam = (mOptions: MigrationOptions) => (param: FunctionParam) => { + const { mode, name, type, default: defaultValue } = applyType(param, mOptions.typeShorthands) const options: string[] = [] if (mode) { options.push(mode) @@ -139,7 +141,7 @@ const formatParam = (mOptions: MigrationOptions) => (param: FunctionParamType) = export const formatParams = (params: FunctionParam[] = [], mOptions: MigrationOptions) => `(${params.map(formatParam(mOptions)).join(', ')})` -export const comment = (object: string, name: string, text?: string) => { +export const makeComment = (object: string, name: string, text?: string | null) => { const cmt = escapeValue(text || null) return `COMMENT ON ${object} ${name} IS ${cmt};` } diff --git a/templates/migration-template.ts b/templates/migration-template.ts index 9fc62380..9f66ce02 100644 --- a/templates/migration-template.ts +++ b/templates/migration-template.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/camelcase */ import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate'; -export const shorthands: ColumnDefinitions = undefined; +export const shorthands: ColumnDefinitions | undefined = undefined; export async function up(pgm: MigrationBuilder): Promise { } diff --git a/test/db-test.ts b/test/db-test.ts index 74367176..9c97ac6d 100644 --- a/test/db-test.ts +++ b/test/db-test.ts @@ -44,7 +44,7 @@ describe('lib/db', () => { }) it('pg.Client should be called with connection string', () => { - const mocked = sandbox.stub(pgMock, 'Client').returns({ end() {} }) + const mocked = sandbox.stub(pgMock, 'Client').returns(client) db = Db('connection_string') expect(mocked).to.be.calledWith('connection_string') }) diff --git a/test/ts/customRunner.ts b/test/ts/customRunner.ts index 11455419..b507846e 100644 --- a/test/ts/customRunner.ts +++ b/test/ts/customRunner.ts @@ -7,7 +7,7 @@ const run = async () => { migrationsTable: 'migrations', dir: resolve(__dirname, 'migrations'), count: Infinity, - databaseUrl: process.env.DATABASE_URL, + databaseUrl: String(process.env.DATABASE_URL), } const upResult = await runner({ ...options, diff --git a/test/ts/migrations/test-migration.ts b/test/ts/migrations/test-migration.ts index d2c4a4d0..e869795c 100644 --- a/test/ts/migrations/test-migration.ts +++ b/test/ts/migrations/test-migration.ts @@ -1,6 +1,6 @@ import { MigrationBuilder, ColumnDefinitions } from '../../../dist' -export const shorthands: ColumnDefinitions = undefined +export const shorthands: ColumnDefinitions | undefined = undefined export async function up(pgm: MigrationBuilder): Promise { pgm.createTable('t1', { diff --git a/tsconfig-test.json b/tsconfig-test.json index 74354254..189ffdf6 100644 --- a/tsconfig-test.json +++ b/tsconfig-test.json @@ -10,7 +10,7 @@ "sourceMap": false, "removeComments": true, "forceConsistentCasingInFileNames": true, - "strict": false, + "strict": true, "noImplicitAny": true, "noUnusedLocals": true }, diff --git a/tsconfig.json b/tsconfig.json index fd41e7ec..fadedf82 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "sourceMap": false, "removeComments": true, "forceConsistentCasingInFileNames": true, - "strict": false, + "strict": true, "noImplicitAny": true, "noUnusedLocals": true },