From e5c20f4784965eeea1523d7d9cc17974cf7da374 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Tue, 10 Dec 2024 04:25:46 +0200 Subject: [PATCH] feat: expose `session` / `db` from the curr transaction to up and down migration functions --- docs/database/migrations.mdx | 24 ++++++++ packages/db-mongodb/src/createMigration.ts | 4 +- packages/db-mongodb/src/types.ts | 57 ++++++++++++++++++- packages/db-postgres/src/index.ts | 1 + packages/db-sqlite/src/index.ts | 1 + packages/db-sqlite/src/types.ts | 46 +++++++++++++++ packages/db-vercel-postgres/src/index.ts | 1 + packages/drizzle/src/migrateDown.ts | 3 +- packages/drizzle/src/migrateFresh.ts | 3 +- packages/drizzle/src/migrateRefresh.ts | 3 +- packages/drizzle/src/migrateReset.ts | 3 +- packages/drizzle/src/postgres/types.ts | 57 ++++++++++++++++++- packages/drizzle/src/types.ts | 8 +-- .../src/utilities/buildCreateMigration.ts | 4 +- .../src/utilities/getMigrationTemplate.ts | 4 +- .../src/database/migrations/migrate.ts | 3 +- .../src/database/migrations/migrateDown.ts | 3 +- .../src/database/migrations/migrateRefresh.ts | 3 +- .../src/database/migrations/migrateReset.ts | 3 +- 19 files changed, 206 insertions(+), 25 deletions(-) diff --git a/docs/database/migrations.mdx b/docs/database/migrations.mdx index db8c09cf90a..887fe6e0dec 100644 --- a/docs/database/migrations.mdx +++ b/docs/database/migrations.mdx @@ -57,6 +57,30 @@ you need to do is pass the `req` object to any [local API](/docs/local-api/overv after your `up` or `down` function runs. If the migration errors at any point or fails to commit, it is caught and the transaction gets aborted. This way no change is made to the database if the migration fails. +### Using database directly with the transaction + +Additionally, you can bypass Payload's layer entirely and perform operations directly on your underlying database within the active transaction + +### MongoDB: +```ts +export async function up({ session, payload, req }: MigrateUpArgs): Promise { + const posts = await payload.db.collections.posts.collection.find({ session }).toArray() +} +``` +### Postgres: +```ts +export async function up({ db, payload, req }: MigrateUpArgs): Promise { + const { rows: posts } = await db.execute(sql`SELECT * from posts`) +} +``` + +### SQLite: +```ts +export async function up({ db, payload, req }: MigrateUpArgs): Promise { + const { rows: posts } = await db.run(sql`SELECT * from posts`) +} +``` + ## Migrations Directory Each DB adapter has an optional property `migrationDir` where you can override where you want your migrations to be diff --git a/packages/db-mongodb/src/createMigration.ts b/packages/db-mongodb/src/createMigration.ts index c99eee52cac..ae54ba1a683 100644 --- a/packages/db-mongodb/src/createMigration.ts +++ b/packages/db-mongodb/src/createMigration.ts @@ -10,11 +10,11 @@ const migrationTemplate = ({ downSQL, imports, upSQL }: MigrationTemplateArgs): MigrateUpArgs, } from '@payloadcms/db-mongodb' ${imports ?? ''} -export async function up({ payload, req }: MigrateUpArgs): Promise { +export async function up({ payload, req, session }: MigrateUpArgs): Promise { ${upSQL ?? ` // Migration code`} } -export async function down({ payload, req }: MigrateDownArgs): Promise { +export async function down({ payload, req, session }: MigrateDownArgs): Promise { ${downSQL ?? ` // Migration code`} } ` diff --git a/packages/db-mongodb/src/types.ts b/packages/db-mongodb/src/types.ts index 7945f75664a..503aa43af1e 100644 --- a/packages/db-mongodb/src/types.ts +++ b/packages/db-mongodb/src/types.ts @@ -1,3 +1,4 @@ +import type { ClientSession } from 'mongodb' import type { AggregatePaginateModel, IndexDefinition, @@ -110,5 +111,57 @@ export type FieldToSchemaMap = { upload: FieldGeneratorFunction } -export type MigrateUpArgs = { payload: Payload; req: PayloadRequest } -export type MigrateDownArgs = { payload: Payload; req: PayloadRequest } +export type MigrateUpArgs = { + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function up({ session, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ + payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ + req: PayloadRequest + /** + * The MongoDB client session that you can use to execute MongoDB methods directly within the current transaction. + * @example + * ```ts + * export async function up({ session, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await payload.db.collections.posts.collection.find({ session }).toArray() + * } + * ``` + */ + session?: ClientSession +} +export type MigrateDownArgs = { + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function down({ session, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ + payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ + req: PayloadRequest + /** + * The MongoDB client session that you can use to execute MongoDB methods directly within the current transaction. + * @example + * ```ts + * export async function down({ session, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await payload.db.collections.posts.collection.find({ session }).toArray() + * } + * ``` + */ + session?: ClientSession +} diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts index 08f2a9e2681..fe6e4b2a8b5 100644 --- a/packages/db-postgres/src/index.ts +++ b/packages/db-postgres/src/index.ts @@ -91,6 +91,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj createDatabase, createExtensions, createMigration: buildCreateMigration({ + executeMethod: 'execute', filename, sanitizeStatements({ sqlExecute, statements }) { return `${sqlExecute}\n ${statements.join('\n')}\`)` diff --git a/packages/db-sqlite/src/index.ts b/packages/db-sqlite/src/index.ts index e5cfb67e762..4259d0c8e28 100644 --- a/packages/db-sqlite/src/index.ts +++ b/packages/db-sqlite/src/index.ts @@ -124,6 +124,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj { createGlobalVersion, createJSONQuery, createMigration: buildCreateMigration({ + executeMethod: 'run', filename, sanitizeStatements({ sqlExecute, statements }) { return statements diff --git a/packages/db-sqlite/src/types.ts b/packages/db-sqlite/src/types.ts index 53e4fe9c228..9de2daffbf5 100644 --- a/packages/db-sqlite/src/types.ts +++ b/packages/db-sqlite/src/types.ts @@ -154,11 +154,57 @@ export type SQLiteAdapter = { export type IDType = 'integer' | 'numeric' | 'text' export type MigrateUpArgs = { + /** + * The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction. + * @example + * ```ts + * export async function up({ db, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await db.run(sql`SELECT * FROM posts`) + * } + * ``` + */ + db: LibSQLDatabase + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function up({ db, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ req: PayloadRequest } export type MigrateDownArgs = { + /** + * The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction. + * @example + * ```ts + * export async function down({ db, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await db.run(sql`SELECT * FROM posts`) + * } + * ``` + */ + db: LibSQLDatabase + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function down({ db, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ req: PayloadRequest } diff --git a/packages/db-vercel-postgres/src/index.ts b/packages/db-vercel-postgres/src/index.ts index a88d84b7fd7..e735190932b 100644 --- a/packages/db-vercel-postgres/src/index.ts +++ b/packages/db-vercel-postgres/src/index.ts @@ -135,6 +135,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj { try { payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` }) await initTransaction(req) - await migrationFile.down({ payload, req }) + const db = this.sessions[await req.transactionID]?.db || this.drizzle + await migrationFile.down({ db, payload, req }) payload.logger.info({ msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`, }) diff --git a/packages/drizzle/src/migrateFresh.ts b/packages/drizzle/src/migrateFresh.ts index 8aea223782c..6583098644a 100644 --- a/packages/drizzle/src/migrateFresh.ts +++ b/packages/drizzle/src/migrateFresh.ts @@ -59,8 +59,7 @@ export async function migrateFresh( try { const start = Date.now() await initTransaction(req) - const adapter = payload.db as DrizzleAdapter - const db = adapter?.sessions[await req.transactionID]?.db || adapter.drizzle + const db = this.sessions[await req.transactionID]?.db || this.drizzle await migration.up({ db, payload, req }) await payload.create({ collection: 'payload-migrations', diff --git a/packages/drizzle/src/migrateRefresh.ts b/packages/drizzle/src/migrateRefresh.ts index badcb6686e5..fde077dbb8b 100644 --- a/packages/drizzle/src/migrateRefresh.ts +++ b/packages/drizzle/src/migrateRefresh.ts @@ -48,7 +48,8 @@ export async function migrateRefresh(this: DrizzleAdapter) { payload.logger.info({ msg: `Migrating down: ${migration.name}` }) const start = Date.now() await initTransaction(req) - await migrationFile.down({ payload, req }) + const db = this.sessions[await req.transactionID]?.db || this.drizzle + await migrationFile.down({ db, payload, req }) payload.logger.info({ msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`, }) diff --git a/packages/drizzle/src/migrateReset.ts b/packages/drizzle/src/migrateReset.ts index b6111406983..940429d1be1 100644 --- a/packages/drizzle/src/migrateReset.ts +++ b/packages/drizzle/src/migrateReset.ts @@ -39,7 +39,8 @@ export async function migrateReset(this: DrizzleAdapter): Promise { const start = Date.now() payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` }) await initTransaction(req) - await migrationFile.down({ payload, req }) + const db = this.sessions[await req.transactionID]?.db || this.drizzle + await migrationFile.down({ db, payload, req }) payload.logger.info({ msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`, }) diff --git a/packages/drizzle/src/postgres/types.ts b/packages/drizzle/src/postgres/types.ts index 749bd68a121..9f6434c4c33 100644 --- a/packages/drizzle/src/postgres/types.ts +++ b/packages/drizzle/src/postgres/types.ts @@ -191,5 +191,58 @@ export type PostgresDrizzleAdapter = Omit< export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar' -export type MigrateUpArgs = { payload: Payload; req: PayloadRequest } -export type MigrateDownArgs = { payload: Payload; req: PayloadRequest } +export type MigrateUpArgs = { + /** + * The Postgres Drizzle instance that you can use to execute SQL directly within the current transaction. + * @example + * ```ts + * export async function up({ db, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await db.execute(sql`SELECT * FROM posts`) + * } + * ``` + */ + db: PostgresDB + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function up({ db, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ + payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ + req: PayloadRequest +} + +export type MigrateDownArgs = { + /** + * The Postgres Drizzle instance that you can use to execute SQL directly within the current transaction. + * @example + * ```ts + * export async function down({ db, payload, req }: MigrateUpArgs): Promise { + * const { rows: posts } = await db.execute(sql`SELECT * FROM posts`) + * } + * ``` + */ + db: PostgresDB + /** + * The Payload instance that you can use to execute Local API methods + * To use the current transaction you must pass `req` to arguments + * @example + * ```ts + * export async function down({ db, payload, req }: MigrateUpArgs): Promise { + * const posts = await payload.find({ collection: 'posts', req }) + * } + * ``` + */ + payload: Payload + /** + * The `PayloadRequest` object that contains the current transaction + */ + req: PayloadRequest +} diff --git a/packages/drizzle/src/types.ts b/packages/drizzle/src/types.ts index 97e53f85c0f..dc940b1d37a 100644 --- a/packages/drizzle/src/types.ts +++ b/packages/drizzle/src/types.ts @@ -14,13 +14,7 @@ import type { NodePgDatabase, NodePgQueryResultHKT } from 'drizzle-orm/node-post import type { PgColumn, PgTable, PgTransaction } from 'drizzle-orm/pg-core' import type { SQLiteColumn, SQLiteTable, SQLiteTransaction } from 'drizzle-orm/sqlite-core' import type { Result } from 'drizzle-orm/sqlite-core/session' -import type { - BaseDatabaseAdapter, - MigrationData, - MigrationTemplateArgs, - Payload, - PayloadRequest, -} from 'payload' +import type { BaseDatabaseAdapter, MigrationData, Payload, PayloadRequest } from 'payload' import type { BuildQueryJoinAliases } from './queries/buildQuery.js' diff --git a/packages/drizzle/src/utilities/buildCreateMigration.ts b/packages/drizzle/src/utilities/buildCreateMigration.ts index 56c60b5751d..2b287470acf 100644 --- a/packages/drizzle/src/utilities/buildCreateMigration.ts +++ b/packages/drizzle/src/utilities/buildCreateMigration.ts @@ -11,9 +11,11 @@ import type { DrizzleAdapter } from '../types.js' import { getMigrationTemplate } from './getMigrationTemplate.js' export const buildCreateMigration = ({ + executeMethod, filename, sanitizeStatements, }: { + executeMethod: string filename: string sanitizeStatements: (args: { sqlExecute: string; statements: string[] }) => string }): CreateMigration => { @@ -77,7 +79,7 @@ export const buildCreateMigration = ({ const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter) const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore) - const sqlExecute = 'await payload.db.drizzle.execute(sql`' + const sqlExecute = `await db.${executeMethod}(` + 'sql`' if (sqlStatementsUp?.length) { upSQL = sanitizeStatements({ sqlExecute, statements: sqlStatementsUp }) diff --git a/packages/drizzle/src/utilities/getMigrationTemplate.ts b/packages/drizzle/src/utilities/getMigrationTemplate.ts index 1f2c8ba9a66..3b1b05be36f 100644 --- a/packages/drizzle/src/utilities/getMigrationTemplate.ts +++ b/packages/drizzle/src/utilities/getMigrationTemplate.ts @@ -13,11 +13,11 @@ export const getMigrationTemplate = ({ upSQL, }: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '${packageName}' ${imports ? `${imports}\n` : ''} -export async function up({ payload, req }: MigrateUpArgs): Promise { +export async function up({ db, payload, req }: MigrateUpArgs): Promise { ${indent(upSQL)} } -export async function down({ payload, req }: MigrateDownArgs): Promise { +export async function down({ db, payload, req }: MigrateDownArgs): Promise { ${indent(downSQL)} } ` diff --git a/packages/payload/src/database/migrations/migrate.ts b/packages/payload/src/database/migrations/migrate.ts index 94d105155ca..ecf12340629 100644 --- a/packages/payload/src/database/migrations/migrate.ts +++ b/packages/payload/src/database/migrations/migrate.ts @@ -35,7 +35,8 @@ export const migrate: BaseDatabaseAdapter['migrate'] = async function migrate( try { await initTransaction(req) - await migration.up({ payload, req }) + const session = payload.db.sessions?.[await req.transactionID] + await migration.up({ payload, req, session }) payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` }) await payload.create({ collection: 'payload-migrations', diff --git a/packages/payload/src/database/migrations/migrateDown.ts b/packages/payload/src/database/migrations/migrateDown.ts index 352a765b326..40ae1915e11 100644 --- a/packages/payload/src/database/migrations/migrateDown.ts +++ b/packages/payload/src/database/migrations/migrateDown.ts @@ -38,7 +38,8 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise { try { payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` }) await initTransaction(req) - await migrationFile.down({ payload, req }) + const session = payload.db.sessions?.[await req.transactionID] + await migrationFile.down({ payload, req, session }) payload.logger.info({ msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`, }) diff --git a/packages/payload/src/database/migrations/migrateRefresh.ts b/packages/payload/src/database/migrations/migrateRefresh.ts index 0b53eca724a..f07d464d091 100644 --- a/packages/payload/src/database/migrations/migrateRefresh.ts +++ b/packages/payload/src/database/migrations/migrateRefresh.ts @@ -37,7 +37,8 @@ export async function migrateRefresh(this: BaseDatabaseAdapter) { payload.logger.info({ msg: `Migrating down: ${migration.name}` }) const start = Date.now() await initTransaction(req) - await migrationFile.down({ payload, req }) + const session = payload.db.sessions?.[await req.transactionID] + await migrationFile.down({ payload, req, session }) payload.logger.info({ msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`, }) diff --git a/packages/payload/src/database/migrations/migrateReset.ts b/packages/payload/src/database/migrations/migrateReset.ts index 7cd1b0b0acf..bb63d14ab34 100644 --- a/packages/payload/src/database/migrations/migrateReset.ts +++ b/packages/payload/src/database/migrations/migrateReset.ts @@ -31,7 +31,8 @@ export async function migrateReset(this: BaseDatabaseAdapter): Promise { try { const start = Date.now() await initTransaction(req) - await migration.down({ payload, req }) + const session = payload.db.sessions?.[await req.transactionID] + await migration.down({ payload, req, session }) await payload.delete({ collection: 'payload-migrations', req,