chore: fix up fresh, refresh, and reset migration operations

This commit is contained in:
Elliot DeNolf
2023-10-06 17:15:38 -04:00
parent fb07308dca
commit e55ec6329c
13 changed files with 505 additions and 19 deletions

1
packages/db-mongodb/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/migrations

View File

@@ -26,6 +26,7 @@
"mongoose": "6.11.4",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",
"uuid": "9.0.0"
},
"devDependencies": {

View File

@@ -3,6 +3,7 @@ import type { Payload } from 'payload'
import type { BaseDatabaseAdapter } from 'payload/database'
import mongoose from 'mongoose'
import path from 'path'
import { createDatabaseAdapter } from 'payload/database'
import { createMigration } from 'payload/database'
@@ -27,6 +28,7 @@ import { findGlobalVersions } from './findGlobalVersions'
import { findOne } from './findOne'
import { findVersions } from './findVersions'
import { init } from './init'
import { migrateFresh } from './migrateFresh'
import { queryDrafts } from './queryDrafts'
import { beginTransaction } from './transactions/beginTransaction'
import { commitTransaction } from './transactions/commitTransaction'
@@ -83,23 +85,34 @@ declare module 'payload' {
export function mongooseAdapter({
autoPluralization = true,
connectOptions,
migrationDir,
migrationDir: migrationDirArg,
url,
}: Args): MongooseAdapterResult {
function adapter({ payload }: { payload: Payload }) {
const migrationDir = migrationDirArg || path.resolve(process.cwd(), 'src/migrations')
mongoose.set('strictQuery', false)
extendWebpackConfig(payload.config)
extendViteConfig(payload.config)
return createDatabaseAdapter<MongooseAdapter>({
name: 'mongoose',
// Mongoose-specific
autoPluralization,
beginTransaction,
collections: {},
commitTransaction,
connect,
connectOptions: connectOptions || {},
connection: undefined,
globals: undefined,
mongoMemoryServer: undefined,
sessions: {},
url,
versions: {},
// DatabaseAdapter
beginTransaction,
commitTransaction,
connect,
create,
createGlobal,
createGlobalVersion,
@@ -115,21 +128,16 @@ export function mongooseAdapter({
findGlobalVersions,
findOne,
findVersions,
globals: undefined,
init,
...(migrationDir && { migrationDir }),
name: 'mongoose',
mongoMemoryServer: undefined,
migrateFresh,
migrationDir,
payload,
queryDrafts,
rollbackTransaction,
sessions: {},
updateGlobal,
updateGlobalVersion,
updateOne,
updateVersion,
url,
versions: {},
})
}

View File

@@ -0,0 +1,73 @@
import type { PayloadRequest } from 'payload/types'
import { readMigrationFiles } from 'payload/database'
import prompts from 'prompts'
import type { MongooseAdapter } from '.'
/**
* Drop the current database and run all migrate up functions
*/
export async function migrateFresh(this: MongooseAdapter): Promise<void> {
const { payload } = this
const { confirm: acceptWarning } = await prompts(
{
name: 'confirm',
initial: false,
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
type: 'confirm',
},
{
onCancel: () => {
process.exit(0)
},
},
)
if (!acceptWarning) {
process.exit(0)
}
payload.logger.info({
msg: `Dropping database.`,
})
await this.connection.dropDatabase()
const migrationFiles = await readMigrationFiles({ payload })
payload.logger.debug({
msg: `Found ${migrationFiles.length} migration files.`,
})
let transactionID
// Run all migrate up
for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` })
try {
const start = Date.now()
transactionID = await this.beginTransaction()
await migration.up({ payload })
await payload.create({
collection: 'payload-migrations',
data: {
name: migration.name,
batch: 1,
},
req: {
transactionID,
} as PayloadRequest,
})
await this.commitTransaction(transactionID)
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migration.name}. Rolling back.`,
})
throw err
}
}
}

View File

@@ -24,6 +24,10 @@ import { findOne } from './findOne'
import { findVersions } from './findVersions'
import { init } from './init'
import { migrate } from './migrate'
import { migrateDown } from './migrateDown'
import { migrateFresh } from './migrateFresh'
import { migrateRefresh } from './migrateRefresh'
import { migrateReset } from './migrateReset'
import { migrateStatus } from './migrateStatus'
import { queryDrafts } from './queryDrafts'
import { beginTransaction } from './transactions/beginTransaction'
@@ -77,6 +81,10 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
findVersions,
init,
migrate,
migrateDown,
migrateFresh,
migrateRefresh,
migrateReset,
migrateStatus,
migrationDir,
payload,

View File

@@ -0,0 +1,69 @@
/* eslint-disable no-restricted-syntax, no-await-in-loop */
import type { PayloadRequest } from 'payload/types'
import { getMigrations, readMigrationFiles } from 'payload/database'
import type { PostgresAdapter } from './types'
import { migrationTableExists } from './utilities/migrationTableExists'
export async function migrateDown(this: PostgresAdapter): Promise<void> {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
const { existingMigrations, latestBatch } = await getMigrations({
payload,
})
const migrationsToRollback = existingMigrations.filter(
(migration) => migration.batch === latestBatch && migration.batch !== -1,
)
if (!migrationsToRollback?.length) {
payload.logger.info({ msg: 'No migrations to rollback.' })
return
}
payload.logger.info({
msg: `Rolling back batch ${latestBatch} consisting of ${migrationsToRollback.length} migration(s).`,
})
for (const migration of migrationsToRollback) {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
if (!migrationFile) {
throw new Error(`Migration ${migration.name} not found locally.`)
}
const start = Date.now()
let transactionID
try {
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
transactionID = await this.beginTransaction()
await migrationFile.down({ payload })
payload.logger.info({
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
})
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
await payload.delete({
id: migration.id,
collection: 'payload-migrations',
req: {
transactionID,
} as PayloadRequest,
})
}
await this.commitTransaction(transactionID)
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migrationFile.name}`,
})
throw err
}
}
}

View File

@@ -0,0 +1,74 @@
import type { PayloadRequest } from 'payload/types'
import { sql } from 'drizzle-orm'
import { readMigrationFiles } from 'payload/database'
import prompts from 'prompts'
import type { PostgresAdapter } from './types'
/**
* Drop the current database and run all migrate up functions
*/
export async function migrateFresh(this: PostgresAdapter): Promise<void> {
const { payload } = this
const { confirm: acceptWarning } = await prompts(
{
name: 'confirm',
initial: false,
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
type: 'confirm',
},
{
onCancel: () => {
process.exit(0)
},
},
)
if (!acceptWarning) {
process.exit(0)
}
payload.logger.info({
msg: `Dropping database.`,
})
await this.drizzle.execute(sql`drop schema public cascade;\ncreate schema public;`)
const migrationFiles = await readMigrationFiles({ payload })
payload.logger.debug({
msg: `Found ${migrationFiles.length} migration files.`,
})
let transactionID
// Run all migrate up
for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` })
try {
const start = Date.now()
transactionID = await this.beginTransaction()
await migration.up({ payload })
await payload.create({
collection: 'payload-migrations',
data: {
name: migration.name,
batch: 1,
},
req: {
transactionID,
} as PayloadRequest,
})
await this.commitTransaction(transactionID)
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migration.name}. Rolling back.`,
})
throw err
}
}
}

View File

@@ -0,0 +1,111 @@
/* eslint-disable no-restricted-syntax, no-await-in-loop */
import type { PayloadRequest } from 'payload/types'
import { getMigrations, readMigrationFiles } from 'payload/database'
import type { PostgresAdapter } from './types'
import { migrationTableExists } from './utilities/migrationTableExists'
/**
* Run all migration down functions before running up
*/
export async function migrateRefresh(this: PostgresAdapter) {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
const { existingMigrations, latestBatch } = await getMigrations({
payload,
})
const migrationsToRollback = existingMigrations.filter(
(migration) => migration.batch === latestBatch && migration.batch !== -1,
)
if (!migrationsToRollback?.length) {
payload.logger.info({ msg: 'No migrations to rollback.' })
return
}
payload.logger.info({
msg: `Rolling back batch ${latestBatch} consisting of ${migrationsToRollback.length} migration(s).`,
})
let transactionID
// Reverse order of migrations to rollback
migrationsToRollback.reverse()
for (const migration of migrationsToRollback) {
try {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
if (!migrationFile) {
throw new Error(`Migration ${migration.name} not found locally.`)
}
payload.logger.info({ msg: `Migrating down: ${migration.name}` })
const start = Date.now()
transactionID = await this.beginTransaction()
await migrationFile.down({ payload })
payload.logger.info({
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
})
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
await payload.delete({
collection: 'payload-migrations',
req: {
transactionID,
} as PayloadRequest,
where: {
name: {
equals: migration.name,
},
},
})
}
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
let msg = `Error running migration ${migration.name}. Rolling back.`
if (err instanceof Error) {
msg += ` ${err.message}`
}
payload.logger.error({
err,
msg,
})
throw err
}
}
// Run all migrate up
for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` })
try {
const start = Date.now()
transactionID = await this.beginTransaction()
await migration.up({ payload })
await payload.create({
collection: 'payload-migrations',
data: {
name: migration.name,
executed: true,
},
req: {
transactionID,
} as PayloadRequest,
})
await this.commitTransaction(transactionID)
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migration.name}. Rolling back.`,
})
throw err
}
}
}

View File

@@ -0,0 +1,81 @@
/* eslint-disable no-restricted-syntax, no-await-in-loop */
import type { PayloadRequest } from 'payload/types'
import { getMigrations, readMigrationFiles } from 'payload/database'
import type { PostgresAdapter } from './types'
import { migrationTableExists } from './utilities/migrationTableExists'
/**
* Run all migrate down functions
*/
export async function migrateReset(this: PostgresAdapter): Promise<void> {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
const { existingMigrations } = await getMigrations({ payload })
if (!existingMigrations?.length) {
payload.logger.info({ msg: 'No migrations to reset.' })
return
}
// Rollback all migrations in order
for (const migration of existingMigrations) {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
if (!migrationFile) {
throw new Error(`Migration ${migration.name} not found locally.`)
}
const start = Date.now()
let transactionID
try {
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
transactionID = await this.beginTransaction()
await migrationFile.down({ payload })
payload.logger.info({
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
})
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
await payload.delete({
id: migration.id,
collection: 'payload-migrations',
req: {
transactionID,
} as PayloadRequest,
})
}
await this.commitTransaction(transactionID)
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migrationFile.name}`,
})
throw err
}
}
// Delete dev migration
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
try {
await payload.delete({
collection: 'payload-migrations',
where: {
batch: {
equals: -1,
},
},
})
} catch (err: unknown) {
payload.logger.error({ error: err, msg: 'Error deleting dev migration' })
}
}
}

View File

@@ -2,22 +2,78 @@
import type { PayloadRequest } from '../../express/types'
import type { BaseDatabaseAdapter } from '../types'
import { getMigrations } from './getMigrations'
import { readMigrationFiles } from './readMigrationFiles'
/**
* Reset and re-run all migrations.
* Run all migration down functions before running up
*/
export async function migrateRefresh(this: BaseDatabaseAdapter) {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
// Clear all migrations
await payload.delete({
collection: 'payload-migrations',
where: {}, // All migrations
const { existingMigrations, latestBatch } = await getMigrations({
payload,
})
const migrationsToRollback = existingMigrations.filter(
(migration) => migration.batch === latestBatch && migration.batch !== -1,
)
if (!migrationsToRollback?.length) {
payload.logger.info({ msg: 'No migrations to rollback.' })
return
}
payload.logger.info({
msg: `Rolling back batch ${latestBatch} consisting of ${migrationsToRollback.length} migration(s).`,
})
let transactionID
// Run all migrations
// Reverse order of migrations to rollback
migrationsToRollback.reverse()
for (const migration of migrationsToRollback) {
try {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
if (!migrationFile) {
throw new Error(`Migration ${migration.name} not found locally.`)
}
payload.logger.info({ msg: `Migrating down: ${migration.name}` })
const start = Date.now()
transactionID = await this.beginTransaction()
await migrationFile.down({ payload })
payload.logger.info({
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
})
await payload.delete({
collection: 'payload-migrations',
req: {
transactionID,
} as PayloadRequest,
where: {
name: {
equals: migration.name,
},
},
})
} catch (err: unknown) {
await this.rollbackTransaction(transactionID)
let msg = `Error running migration ${migration.name}. Rolling back.`
if (err instanceof Error) {
msg += ` ${err.message}`
}
payload.logger.error({
err,
msg,
})
throw err
}
}
// Run all migrate up
for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` })
try {
@@ -41,7 +97,7 @@ export async function migrateRefresh(this: BaseDatabaseAdapter) {
await this.rollbackTransaction(transactionID)
payload.logger.error({
err,
msg: `Error running migration ${migration.name}`,
msg: `Error running migration ${migration.name}. Rolling back.`,
})
throw err
}

View File

@@ -8,7 +8,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
// Migration code
};
export async function down({ payload }: MigrateUpArgs): Promise<void> {
export async function down({ payload }: MigrateDownArgs): Promise<void> {
// Migration code
};
`

3
pnpm-lock.yaml generated
View File

@@ -293,6 +293,9 @@ importers:
mongoose-paginate-v2:
specifier: 1.7.22
version: 1.7.22
prompts:
specifier: 2.4.2
version: 2.4.2
uuid:
specifier: 9.0.0
version: 9.0.0

View File

@@ -18,6 +18,7 @@ const bundlerAdapters = {
const databaseAdapters = {
mongoose: mongooseAdapter({
migrationDir: path.resolve(__dirname, '../packages/db-mongodb/migrations'),
url: 'mongodb://127.0.0.1/payloadtests',
}),
postgres: postgresAdapter({