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": "6.11.4",
"mongoose-aggregate-paginate-v2": "1.0.6", "mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22", "mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",
"uuid": "9.0.0" "uuid": "9.0.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -3,6 +3,7 @@ import type { Payload } from 'payload'
import type { BaseDatabaseAdapter } from 'payload/database' import type { BaseDatabaseAdapter } from 'payload/database'
import mongoose from 'mongoose' import mongoose from 'mongoose'
import path from 'path'
import { createDatabaseAdapter } from 'payload/database' import { createDatabaseAdapter } from 'payload/database'
import { createMigration } from 'payload/database' import { createMigration } from 'payload/database'
@@ -27,6 +28,7 @@ import { findGlobalVersions } from './findGlobalVersions'
import { findOne } from './findOne' import { findOne } from './findOne'
import { findVersions } from './findVersions' import { findVersions } from './findVersions'
import { init } from './init' import { init } from './init'
import { migrateFresh } from './migrateFresh'
import { queryDrafts } from './queryDrafts' import { queryDrafts } from './queryDrafts'
import { beginTransaction } from './transactions/beginTransaction' import { beginTransaction } from './transactions/beginTransaction'
import { commitTransaction } from './transactions/commitTransaction' import { commitTransaction } from './transactions/commitTransaction'
@@ -83,23 +85,34 @@ declare module 'payload' {
export function mongooseAdapter({ export function mongooseAdapter({
autoPluralization = true, autoPluralization = true,
connectOptions, connectOptions,
migrationDir, migrationDir: migrationDirArg,
url, url,
}: Args): MongooseAdapterResult { }: Args): MongooseAdapterResult {
function adapter({ payload }: { payload: Payload }) { function adapter({ payload }: { payload: Payload }) {
const migrationDir = migrationDirArg || path.resolve(process.cwd(), 'src/migrations')
mongoose.set('strictQuery', false) mongoose.set('strictQuery', false)
extendWebpackConfig(payload.config) extendWebpackConfig(payload.config)
extendViteConfig(payload.config) extendViteConfig(payload.config)
return createDatabaseAdapter<MongooseAdapter>({ return createDatabaseAdapter<MongooseAdapter>({
name: 'mongoose',
// Mongoose-specific
autoPluralization, autoPluralization,
beginTransaction,
collections: {}, collections: {},
commitTransaction,
connect,
connectOptions: connectOptions || {}, connectOptions: connectOptions || {},
connection: undefined, connection: undefined,
globals: undefined,
mongoMemoryServer: undefined,
sessions: {},
url,
versions: {},
// DatabaseAdapter
beginTransaction,
commitTransaction,
connect,
create, create,
createGlobal, createGlobal,
createGlobalVersion, createGlobalVersion,
@@ -115,21 +128,16 @@ export function mongooseAdapter({
findGlobalVersions, findGlobalVersions,
findOne, findOne,
findVersions, findVersions,
globals: undefined,
init, init,
...(migrationDir && { migrationDir }), migrateFresh,
name: 'mongoose', migrationDir,
mongoMemoryServer: undefined,
payload, payload,
queryDrafts, queryDrafts,
rollbackTransaction, rollbackTransaction,
sessions: {},
updateGlobal, updateGlobal,
updateGlobalVersion, updateGlobalVersion,
updateOne, updateOne,
updateVersion, 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 { findVersions } from './findVersions'
import { init } from './init' import { init } from './init'
import { migrate } from './migrate' 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 { migrateStatus } from './migrateStatus'
import { queryDrafts } from './queryDrafts' import { queryDrafts } from './queryDrafts'
import { beginTransaction } from './transactions/beginTransaction' import { beginTransaction } from './transactions/beginTransaction'
@@ -77,6 +81,10 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
findVersions, findVersions,
init, init,
migrate, migrate,
migrateDown,
migrateFresh,
migrateRefresh,
migrateReset,
migrateStatus, migrateStatus,
migrationDir, migrationDir,
payload, 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 { PayloadRequest } from '../../express/types'
import type { BaseDatabaseAdapter } from '../types' import type { BaseDatabaseAdapter } from '../types'
import { getMigrations } from './getMigrations'
import { readMigrationFiles } from './readMigrationFiles' import { readMigrationFiles } from './readMigrationFiles'
/** /**
* Reset and re-run all migrations. * Run all migration down functions before running up
*/ */
export async function migrateRefresh(this: BaseDatabaseAdapter) { export async function migrateRefresh(this: BaseDatabaseAdapter) {
const { payload } = this const { payload } = this
const migrationFiles = await readMigrationFiles({ payload }) const migrationFiles = await readMigrationFiles({ payload })
// Clear all migrations const { existingMigrations, latestBatch } = await getMigrations({
await payload.delete({ payload,
collection: 'payload-migrations',
where: {}, // All migrations
}) })
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 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) { for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` }) payload.logger.info({ msg: `Migrating: ${migration.name}` })
try { try {
@@ -41,7 +97,7 @@ export async function migrateRefresh(this: BaseDatabaseAdapter) {
await this.rollbackTransaction(transactionID) await this.rollbackTransaction(transactionID)
payload.logger.error({ payload.logger.error({
err, err,
msg: `Error running migration ${migration.name}`, msg: `Error running migration ${migration.name}. Rolling back.`,
}) })
throw err throw err
} }

View File

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

3
pnpm-lock.yaml generated
View File

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

View File

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