From 1005de82950afa9e74662d707527064c1e54db92 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 23 Apr 2024 12:14:01 -0400 Subject: [PATCH 1/3] fix(db-postgres): shortens relation names --- packages/db-postgres/src/schema/build.ts | 110 +++++++++++++----- .../db-postgres/src/schema/traverseFields.ts | 50 ++++---- packages/next/src/exports/utilities.ts | 2 +- packages/next/src/utilities/getPayloadHMR.ts | 49 ++++---- test/buildConfigWithDefaults.ts | 2 +- test/fields/int.spec.ts | 18 +++ 6 files changed, 153 insertions(+), 78 deletions(-) diff --git a/packages/db-postgres/src/schema/build.ts b/packages/db-postgres/src/schema/build.ts index bfa8421fd1..e489c7325b 100644 --- a/packages/db-postgres/src/schema/build.ts +++ b/packages/db-postgres/src/schema/build.ts @@ -1,6 +1,7 @@ /* eslint-disable no-param-reassign */ import type { Relation } from 'drizzle-orm' import type { + ForeignKeyBuilder, IndexBuilder, PgColumnBuilder, PgTableWithColumns, @@ -9,8 +10,17 @@ import type { import type { Field } from 'payload/types' import { relations } from 'drizzle-orm' -import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core' -import { fieldAffectsData } from 'payload/types' +import { + foreignKey, + index, + integer, + numeric, + serial, + timestamp, + unique, + varchar, +} from 'drizzle-orm/pg-core' +import toSnakeCase from 'to-snake-case' import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js' @@ -19,10 +29,15 @@ import { parentIDColumnMap } from './parentIDColumnMap.js' import { setColumnID } from './setColumnID.js' import { traverseFields } from './traverseFields.js' +export type BaseExtraConfig = Record< + string, + (cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder +> + type Args = { adapter: PostgresAdapter baseColumns?: Record - baseExtraConfig?: Record IndexBuilder | UniqueConstraintBuilder> + baseExtraConfig?: BaseExtraConfig buildNumbers?: boolean buildRelationships?: boolean buildTexts?: boolean @@ -134,10 +149,12 @@ export const buildTable = ({ return config }, {}) - return Object.entries(indexes).reduce((acc, [colName, func]) => { + const result = Object.entries(indexes).reduce((acc, [colName, func]) => { acc[colName] = func(cols) return acc }, extraConfig) + + return result }) adapter.tables[tableName] = table @@ -146,9 +163,7 @@ export const buildTable = ({ const localeTableName = `${tableName}${adapter.localesSuffix}` localesColumns.id = serial('id').primaryKey() localesColumns._locale = adapter.enums.enum__locales('_locale').notNull() - localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id') - .references(() => table.id, { onDelete: 'cascade' }) - .notNull() + localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id').notNull() localesTable = adapter.pgSchema.table(localeTableName, localesColumns, (cols) => { return Object.entries(localesIndexes).reduce( @@ -161,6 +176,11 @@ export const buildTable = ({ cols._locale, cols._parentID, ), + _parentIdFk: foreignKey({ + name: `${localeTableName}_parent_id_fk`, + columns: [cols._parentID], + foreignColumns: [table.id], + }).onDelete('cascade'), }, ) }) @@ -182,9 +202,7 @@ export const buildTable = ({ const columns: Record = { id: serial('id').primaryKey(), order: integer('order').notNull(), - parent: parentIDColumnMap[idColType]('parent_id') - .references(() => table.id, { onDelete: 'cascade' }) - .notNull(), + parent: parentIDColumnMap[idColType]('parent_id').notNull(), path: varchar('path').notNull(), text: varchar('text'), } @@ -194,19 +212,24 @@ export const buildTable = ({ } textsTable = adapter.pgSchema.table(textsTableName, columns, (cols) => { - const indexes: Record = { + const config: Record = { orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent), + parentFk: foreignKey({ + name: `${textsTableName}_parent_fk`, + columns: [cols.parent], + foreignColumns: [table.id], + }).onDelete('cascade'), } if (hasManyTextField === 'index') { - indexes.text_idx = index(`${textsTableName}_text_idx`).on(cols.text) + config.text_idx = index(`${textsTableName}_text_idx`).on(cols.text) } if (hasLocalizedManyTextField) { - indexes.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent) + config.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent) } - return indexes + return config }) adapter.tables[textsTableName] = textsTable @@ -227,9 +250,7 @@ export const buildTable = ({ id: serial('id').primaryKey(), number: numeric('number'), order: integer('order').notNull(), - parent: parentIDColumnMap[idColType]('parent_id') - .references(() => table.id, { onDelete: 'cascade' }) - .notNull(), + parent: parentIDColumnMap[idColType]('parent_id').notNull(), path: varchar('path').notNull(), } @@ -238,22 +259,27 @@ export const buildTable = ({ } numbersTable = adapter.pgSchema.table(numbersTableName, columns, (cols) => { - const indexes: Record = { + const config: Record = { orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent), + parentFk: foreignKey({ + name: `${numbersTableName}_parent_fk`, + columns: [cols.parent], + foreignColumns: [table.id], + }).onDelete('cascade'), } if (hasManyNumberField === 'index') { - indexes.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number) + config.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number) } if (hasLocalizedManyNumberField) { - indexes.localeParent = index(`${numbersTableName}_locale_parent`).on( + config.localeParent = index(`${numbersTableName}_locale_parent`).on( cols.locale, cols.parent, ) } - return indexes + return config }) adapter.tables[numbersTableName] = numbersTable @@ -273,9 +299,7 @@ export const buildTable = ({ const relationshipColumns: Record = { id: serial('id').primaryKey(), order: integer('order'), - parent: parentIDColumnMap[idColType]('parent_id') - .references(() => table.id, { onDelete: 'cascade' }) - .notNull(), + parent: parentIDColumnMap[idColType]('parent_id').notNull(), path: varchar('path').notNull(), } @@ -283,6 +307,10 @@ export const buildTable = ({ relationshipColumns.locale = adapter.enums.enum__locales('locale') } + const relationExtraConfig: BaseExtraConfig = {} + + const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}` + relationships.forEach((relationTo) => { const relationshipConfig = adapter.payload.collections[relationTo].config const formattedRelationTo = getTableName({ @@ -300,20 +328,38 @@ export const buildTable = ({ relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType]( `${formattedRelationTo}_id`, - ).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' }) - }) + ) - const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}` + relationExtraConfig[`${relationTo}IdFk`] = (cols) => + foreignKey({ + name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`, + columns: [cols[`${relationTo}ID`]], + foreignColumns: [adapter.tables[formattedRelationTo].id], + }).onDelete('cascade') + }) relationshipsTable = adapter.pgSchema.table( relationshipsTableName, relationshipColumns, (cols) => { - const result: Record = { - order: index(`${relationshipsTableName}_order_idx`).on(cols.order), - parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent), - pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path), - } + const result: Record = Object.entries( + relationExtraConfig, + ).reduce( + (config, [key, func]) => { + config[key] = func(cols) + return config + }, + { + order: index(`${relationshipsTableName}_order_idx`).on(cols.order), + parentFk: foreignKey({ + name: `${relationshipsTableName}_parent_fk`, + columns: [cols.parent], + foreignColumns: [table.id], + }).onDelete('cascade'), + parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent), + pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path), + }, + ) if (hasLocalizedRelationshipField) { result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale) diff --git a/packages/db-postgres/src/schema/traverseFields.ts b/packages/db-postgres/src/schema/traverseFields.ts index da04979b46..9b5d995f31 100644 --- a/packages/db-postgres/src/schema/traverseFields.ts +++ b/packages/db-postgres/src/schema/traverseFields.ts @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ import type { Relation } from 'drizzle-orm' -import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core' +import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core' import type { Field, TabAsField } from 'payload/types' import { relations } from 'drizzle-orm' @@ -9,6 +9,7 @@ import { PgUUIDBuilder, PgVarcharBuilder, boolean, + foreignKey, index, integer, jsonb, @@ -23,6 +24,7 @@ import { fieldAffectsData, optionIsObject } from 'payload/types' import toSnakeCase from 'to-snake-case' import type { GenericColumns, IDType, PostgresAdapter } from '../types.js' +import type { BaseExtraConfig } from './build.js' import { hasLocalesTable } from '../utilities/hasLocalesTable.js' import { buildTable } from './build.js' @@ -251,17 +253,18 @@ export const traverseFields = ({ }) const baseColumns: Record = { order: integer('order').notNull(), - parent: parentIDColumnMap[parentIDColType]('parent_id') - .references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }) - .notNull(), + parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(), value: adapter.enums[enumName]('value'), } - const baseExtraConfig: Record< - string, - (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder - > = { + const baseExtraConfig: BaseExtraConfig = { orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order), + parentFk: (cols) => + foreignKey({ + name: `${selectTableName}_parent_fk`, + columns: [cols.parent], + foreignColumns: [adapter.tables[parentTableName].id], + }), parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent), } @@ -321,18 +324,20 @@ export const traverseFields = ({ prefix: `${newTableName}_`, throwValidationError, }) + const baseColumns: Record = { _order: integer('_order').notNull(), - _parentID: parentIDColumnMap[parentIDColType]('_parent_id') - .references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }) - .notNull(), + _parentID: parentIDColumnMap[parentIDColType]('_parent_id').notNull(), } - const baseExtraConfig: Record< - string, - (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder - > = { + const baseExtraConfig: BaseExtraConfig = { _orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order), + _parentIDFk: (cols) => + foreignKey({ + name: `${arrayTableName}_parent_id_fk`, + columns: [cols['_parentID']], + foreignColumns: [adapter.tables[parentTableName].id], + }).onDelete('cascade'), _parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID), } @@ -410,18 +415,19 @@ export const traverseFields = ({ if (!adapter.tables[blockTableName]) { const baseColumns: Record = { _order: integer('_order').notNull(), - _parentID: parentIDColumnMap[rootTableIDColType]('_parent_id') - .references(() => adapter.tables[rootTableName].id, { onDelete: 'cascade' }) - .notNull(), + _parentID: parentIDColumnMap[rootTableIDColType]('_parent_id').notNull(), _path: text('_path').notNull(), } - const baseExtraConfig: Record< - string, - (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder - > = { + const baseExtraConfig: BaseExtraConfig = { _orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order), _parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID), + _parentIdFk: (cols) => + foreignKey({ + name: `${blockTableName}_parent_id_fk`, + columns: [cols._parentID], + foreignColumns: [adapter.tables[rootTableName].id], + }).onDelete('cascade'), _pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path), } diff --git a/packages/next/src/exports/utilities.ts b/packages/next/src/exports/utilities.ts index 1f6e2d6451..b8700d9bb5 100644 --- a/packages/next/src/exports/utilities.ts +++ b/packages/next/src/exports/utilities.ts @@ -1,5 +1,5 @@ export { traverseFields } from '../utilities/buildFieldSchemaMap/traverseFields.js' export { createPayloadRequest } from '../utilities/createPayloadRequest.js' export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js' -export { getPayloadHMR } from '../utilities/getPayloadHMR.js' +export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js' export { headersWithCors } from '../utilities/headersWithCors.js' diff --git a/packages/next/src/utilities/getPayloadHMR.ts b/packages/next/src/utilities/getPayloadHMR.ts index dc00b54338..e074fe805c 100644 --- a/packages/next/src/utilities/getPayloadHMR.ts +++ b/packages/next/src/utilities/getPayloadHMR.ts @@ -1,5 +1,5 @@ import type { GeneratedTypes, Payload } from 'payload' -import type { InitOptions } from 'payload/config' +import type { InitOptions, SanitizedConfig } from 'payload/config' import { BasePayload } from 'payload' import WebSocket from 'ws' @@ -15,6 +15,31 @@ if (!cached) { cached = global._payload = { payload: null, promise: null, reload: false } } +export const reload = async (config: SanitizedConfig, payload: Payload): Promise => { + if (typeof payload.db.destroy === 'function') { + await payload.db.destroy() + } + + payload.config = config + + payload.collections = config.collections.reduce((collections, collection) => { + collections[collection.slug] = { + config: collection, + customIDType: payload.collections[collection.slug]?.customIDType, + } + return collections + }, {}) + + payload.globals = { + config: config.globals, + } + + // TODO: support HMR for other props in the future (see payload/src/index init()) hat may change on Payload singleton + + await payload.db.init() + await payload.db.connect({ hotReload: true }) +} + export const getPayloadHMR = async (options: InitOptions): Promise => { if (!options?.config) { throw new Error('Error: the payload config is required for getPayload to work.') @@ -28,28 +53,8 @@ export const getPayloadHMR = async (options: InitOptions): Promise => { cached.reload = new Promise((res) => (resolve = res)) - if (typeof cached.payload.db.destroy === 'function') { - await cached.payload.db.destroy() - } + await reload(config, cached.payload) - cached.payload.config = config - - cached.payload.collections = config.collections.reduce((collections, collection) => { - collections[collection.slug] = { - config: collection, - customIDType: cached.payload.collections[collection.slug]?.customIDType, - } - return collections - }, {}) - - cached.payload.globals = { - config: config.globals, - } - - // TODO: support HMR for other props in the future (see payload/src/index init()) hat may change on Payload singleton - - await cached.payload.db.init() - await cached.payload.db.connect({ hotReload: true }) resolve() } diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index dfd9a630e7..e99fbe90ac 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -34,7 +34,7 @@ import sharp from 'sharp' import { reInitEndpoint } from './helpers/reInit.js' import { localAPIEndpoint } from './helpers/sdk/endpoint.js' -// process.env.PAYLOAD_DATABASE = 'postgres' +process.env.PAYLOAD_DATABASE = 'postgres' export async function buildConfigWithDefaults( testConfig?: Partial, diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index ac069e503a..a3c0927c88 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -3,6 +3,8 @@ import type { IndexDirection, IndexOptions } from 'mongoose' import type { Payload } from 'payload' import type { PaginatedDocs } from 'payload/database' +import { reload } from '@payloadcms/next/utilities' + import type { NextRESTClient } from '../helpers/NextRESTClient.js' import type { GroupField, RichTextField } from './payload-types.js' @@ -916,6 +918,22 @@ describe('Fields', () => { }) }) + it('should hot module reload and still be able to create', async () => { + const testDoc1 = await payload.findByID({ + collection: tabsFieldsSlug, + id: document.id, + }) + + await reload(payload.config, payload) + + const testDoc2 = await payload.findByID({ + collection: tabsFieldsSlug, + id: document.id, + }) + + expect(testDoc1.id).toStrictEqual(testDoc2.id) + }) + it('should create with fields inside a named tab', () => { expect(document.tab.text).toStrictEqual(namedTabText) }) From e4d024cd0d2a754cbb94ba1f68d7e9259b5c2a96 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 23 Apr 2024 12:43:25 -0400 Subject: [PATCH 2/3] chore: properly destroys db in postgres --- packages/db-postgres/src/destroy.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/db-postgres/src/destroy.ts b/packages/db-postgres/src/destroy.ts index 3ec3a4bcef..7fd89095a0 100644 --- a/packages/db-postgres/src/destroy.ts +++ b/packages/db-postgres/src/destroy.ts @@ -2,13 +2,13 @@ import type { Destroy } from 'payload/database' import type { PostgresAdapter } from './types.js' -import { pushDevSchema } from './utilities/pushDevSchema.js' - +// eslint-disable-next-line @typescript-eslint/require-await export const destroy: Destroy = async function destroy(this: PostgresAdapter) { - if (process.env.NODE_ENV !== 'production') { - await pushDevSchema(this) - } else { - // TODO: this hangs test suite for some reason - // await this.pool.end() - } + this.enums = {} + this.schema = {} + this.tables = {} + this.relations = {} + this.blockTableNames = {} + this.fieldConstraints = {} + this.drizzle = undefined } From 16f97ad7c32ffd841141abba00cc28356a0fd68c Mon Sep 17 00:00:00 2001 From: James Date: Tue, 23 Apr 2024 13:13:59 -0400 Subject: [PATCH 3/3] chore: disables forced pg for tests --- test/buildConfigWithDefaults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index e99fbe90ac..dfd9a630e7 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -34,7 +34,7 @@ import sharp from 'sharp' import { reInitEndpoint } from './helpers/reInit.js' import { localAPIEndpoint } from './helpers/sdk/endpoint.js' -process.env.PAYLOAD_DATABASE = 'postgres' +// process.env.PAYLOAD_DATABASE = 'postgres' export async function buildConfigWithDefaults( testConfig?: Partial,