chore: merge from 2.0

This commit is contained in:
Elliot DeNolf
2023-09-14 12:10:36 -04:00
2597 changed files with 96505 additions and 105649 deletions

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,15 @@
/** @type {import('prettier').Config} */
module.exports = {
extends: ['@payloadcms'],
overrides: [
{
extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
},
],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
root: true,
}

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": "inline",
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "commonjs"
}
}

View File

@@ -1,30 +1,38 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.0.1",
"description": "The officially supported Postgres database adapter for Payload",
"main": "index.js",
"repository": "https://github.com/payloadcms/payload",
"author": "Payload CMS, Inc.",
"license": "MIT",
"scripts": {
"build": "tsc"
},
"author": "Payload CMS, Inc.",
"peerDependencies": {
"better-sqlite3": "^8.5.0"
},
"dependencies": {
"@libsql/client": "^0.3.1",
"drizzle-kit": "^0.19.13-a511135",
"drizzle-orm": "^0.28.0",
"pg": "^8.11.1",
"prompts": "^2.4.2",
"to-snake-case": "^1.0.0"
"drizzle-kit": "0.19.13-e99bac1",
"drizzle-orm": "0.28.5",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0"
},
"devDependencies": {
"@types/pg": "^8.10.2",
"@types/to-snake-case": "^1.0.0",
"@payloadcms/eslint-config": "workspace:*",
"@types/pg": "8.10.2",
"@types/to-snake-case": "1.0.0",
"better-sqlite3": "^8.5.0",
"payload": "payloadcms/payload#build/chore/update-2.0",
"typescript": "^4.9.4"
}
"payload": "workspace:*"
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"repository": "https://github.com/payloadcms/payload",
"scripts": {
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"builddisabled": "pnpm build:swc && pnpm build:types"
},
"version": "0.0.1"
}

View File

@@ -13,133 +13,133 @@ import { DrizzleDB } from './types';
// Migration table def in order to use query using drizzle
const migrationsSchema = pgTable('payload_migrations', {
name: varchar('name'),
batch: numeric('batch'),
name: varchar('name'),
schema: jsonb('schema'),
});
})
export const connect: Connect = async function connect(
this: PostgresAdapter,
payload,
) {
let db: DrizzleDB;
export const connect: Connect = async function connect(this: PostgresAdapter, payload) {
let db: DrizzleDB
this.schema = {
...this.tables,
...this.relations,
...this.enums,
};
}
try {
if ('pool' in this && this.pool !== false) {
const pool = new Pool(this.pool);
db = drizzle(pool, { schema: this.schema });
await pool.connect();
const pool = new Pool(this.pool)
db = drizzle(pool, { schema: this.schema })
await pool.connect()
}
if ('client' in this && this.client !== false) {
const client = new Client(this.client);
db = drizzle(client, { schema: this.schema });
await client.connect();
const client = new Client(this.client)
db = drizzle(client, { schema: this.schema })
await client.connect()
}
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING TABLES ----');
await db.execute(sql`drop schema public cascade;\ncreate schema public;`);
this.payload.logger.info('---- DROPPED TABLES ----');
this.payload.logger.info('---- DROPPING TABLES ----')
await db.execute(sql`drop schema public cascade;\ncreate schema public;`)
this.payload.logger.info('---- DROPPED TABLES ----')
}
} catch (err) {
payload.logger.error(
`Error: cannot connect to Postgres. Details: ${err.message}`,
err,
);
process.exit(1);
payload.logger.error(`Error: cannot connect to Postgres. Details: ${err.message}`, err)
process.exit(1)
}
this.payload.logger.info('Connected to Postgres successfully');
this.db = db;
this.payload.logger.info('Connected to Postgres successfully')
this.db = db
// Only push schema if not in production
if (process.env.NODE_ENV === 'production') return;
if (process.env.NODE_ENV === 'production') return
// This will prompt if clarifications are needed for Drizzle to push new schema
const { hasDataLoss, warnings, statementsToExecute, apply } = await pushSchema(this.schema, this.db);
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
this.schema,
this.db,
)
this.payload.logger.debug({
msg: 'Schema push results',
hasDataLoss,
warnings,
msg: 'Schema push results',
statementsToExecute,
});
warnings,
})
if (warnings.length) {
this.payload.logger.warn({
msg: `Warnings detected during schema push: ${warnings.join('\n')}`,
warnings,
});
})
if (hasDataLoss) {
this.payload.logger.warn({
msg: 'DATA LOSS WARNING: Possible data loss detected if schema is pushed.',
});
})
}
const { confirm: acceptWarnings } = await prompts(
{
type: 'confirm',
name: 'confirm',
message: 'Accept warnings and push schema to database?',
initial: false,
message: 'Accept warnings and push schema to database?',
name: 'confirm',
type: 'confirm',
},
{
onCancel: () => {
process.exit(0);
process.exit(0)
},
},
);
)
// Exit if user does not accept warnings.
// Q: Is this the right type of exit for this interaction?
if (!acceptWarnings) {
process.exit(0);
process.exit(0)
}
}
this.migrationDir = '.migrations';
this.migrationDir = '.migrations'
// Create drizzle snapshot if it doesn't exist
if (!fs.existsSync(`${this.migrationDir}/drizzle-snapshot.json`)) {
// Ensure migration dir exists
if (!fs.existsSync(this.migrationDir)) {
fs.mkdirSync(this.migrationDir);
fs.mkdirSync(this.migrationDir)
}
const drizzleJSON = generateDrizzleJson(this.schema);
const drizzleJSON = generateDrizzleJson(this.schema)
fs.writeFileSync(`${this.migrationDir}/drizzle-snapshot.json`, JSON.stringify(drizzleJSON, null, 2));
fs.writeFileSync(
`${this.migrationDir}/drizzle-snapshot.json`,
JSON.stringify(drizzleJSON, null, 2),
)
}
const jsonSchema = configToJSONSchema(this.payload.config);
const jsonSchema = configToJSONSchema(this.payload.config)
await apply();
await apply()
const devPush = await this.db
.select()
.from(migrationsSchema)
.where(eq(migrationsSchema.batch, '-1'));
.where(eq(migrationsSchema.batch, '-1'))
if (!devPush.length) {
await this.db.insert(migrationsSchema).values({
name: 'dev',
batch: '-1',
name: 'dev',
schema: JSON.stringify(jsonSchema),
});
})
} else {
await this.db
.update(migrationsSchema)
.set({
schema: JSON.stringify(jsonSchema),
})
.where(eq(migrationsSchema.batch, '-1'));
.where(eq(migrationsSchema.batch, '-1'))
}
};
}

View File

@@ -1,13 +1,11 @@
import { Create } from 'payload/dist/database/types';
import toSnakeCase from 'to-snake-case';
import { upsertRow } from '../upsertRow';
import type { Create } from 'payload/database'
export const create: Create = async function create({
collection: collectionSlug,
data,
req,
}) {
const collection = this.payload.collections[collectionSlug].config;
import toSnakeCase from 'to-snake-case'
import { upsertRow } from '../upsertRow'
export const create: Create = async function create({ collection: collectionSlug, data, req }) {
const collection = this.payload.collections[collectionSlug].config
const result = await upsertRow({
adapter: this,
@@ -15,7 +13,7 @@ export const create: Create = async function create({
fields: collection.fields,
operation: 'create',
tableName: toSnakeCase(collectionSlug),
});
})
return result;
};
return result
}

View File

@@ -1,16 +1,18 @@
/* eslint-disable no-restricted-syntax, no-await-in-loop */
import fs from 'fs';
import { CreateMigration } from 'payload/dist/database/types';
import type { CreateMigration } from 'payload/database'
import type { DatabaseAdapter, Init } from 'payload/database'
import { generateDrizzleJson, generateMigration } from 'drizzle-kit/utils';
import { eq } from 'drizzle-orm';
import { jsonb, numeric, pgEnum, pgTable, varchar } from 'drizzle-orm/pg-core';
import { SanitizedCollectionConfig } from 'payload/dist/collections/config/types';
import type { DatabaseAdapter, Init } from 'payload/dist/database/types';
import { configToJSONSchema } from 'payload/dist/utilities/configToJSONSchema';
import prompts from 'prompts';
import { buildTable } from './schema/build';
import type { GenericEnum, GenericRelation, GenericTable, PostgresAdapter } from './types';
import { generateDrizzleJson, generateMigration } from 'drizzle-kit/utils'
import { eq } from 'drizzle-orm'
import { jsonb, numeric, pgEnum, pgTable, varchar } from 'drizzle-orm/pg-core'
import fs from 'fs'
import { SanitizedCollectionConfig } from 'payload/types'
import { configToJSONSchema } from 'payload/utilities'
import prompts from 'prompts'
import type { GenericEnum, GenericRelation, GenericTable, PostgresAdapter } from './types'
import { buildTable } from './schema/build'
const migrationTemplate = (upSQL?: string) => `
import payload, { Payload } from 'payload';
@@ -22,7 +24,7 @@ export async function up(payload: Payload): Promise<void> {
export async function down(payload: Payload): Promise<void> {
// Migration code
};
`;
`
export const createMigration: CreateMigration = async function createMigration(
this: PostgresAdapter,
@@ -30,27 +32,30 @@ export const createMigration: CreateMigration = async function createMigration(
migrationDir,
migrationName,
) {
payload.logger.info({ msg: 'Creating migration from postgres adapter...' });
const dir = migrationDir || '.migrations'; // TODO: Verify path after linking
payload.logger.info({ msg: 'Creating migration from postgres adapter...' })
const dir = migrationDir || '.migrations' // TODO: Verify path after linking
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
fs.mkdirSync(dir)
}
const [yyymmdd, hhmmss] = new Date().toISOString().split('T');
const formattedDate = yyymmdd.replace(/\D/g, '');
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '');
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '')
const timestamp = `${formattedDate}_${formattedTime}`;
const timestamp = `${formattedDate}_${formattedTime}`
const formattedName = migrationName.replace(/\W/g, '_');
const fileName = `${timestamp}_${formattedName}.ts`;
const filePath = `${dir}/${fileName}`;
const formattedName = migrationName.replace(/\W/g, '_')
const fileName = `${timestamp}_${formattedName}.ts`
const filePath = `${dir}/${fileName}`
const snapshotJSON = fs.readFileSync(`${dir}/drizzle-snapshot.json`, 'utf8');
const drizzleJsonBefore = generateDrizzleJson(JSON.parse(snapshotJSON));
const drizzleJsonAfter = generateDrizzleJson(this.schema, drizzleJsonBefore.id);
const sqlStatements = await generateMigration(drizzleJsonBefore, drizzleJsonAfter);
fs.writeFileSync(filePath, migrationTemplate(sqlStatements.length ? sqlStatements?.join('\n') : undefined));
const snapshotJSON = fs.readFileSync(`${dir}/drizzle-snapshot.json`, 'utf8')
const drizzleJsonBefore = generateDrizzleJson(JSON.parse(snapshotJSON))
const drizzleJsonAfter = generateDrizzleJson(this.schema, drizzleJsonBefore.id)
const sqlStatements = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
fs.writeFileSync(
filePath,
migrationTemplate(sqlStatements.length ? sqlStatements?.join('\n') : undefined),
)
// TODO:
// Get the most recent migration schema from the file system
@@ -61,4 +66,4 @@ export const createMigration: CreateMigration = async function createMigration(
// and then inject them each into the `migrationTemplate` above,
// outputting the file into the migrations folder accordingly
// also make sure to output the JSON schema snapshot into a `./migrationsDir/meta` folder like Drizzle does
};
}

View File

@@ -22,14 +22,15 @@ export const buildFindManyArgs = ({
}: BuildFindQueryArgs): Record<string, unknown> => {
const result: Result = {
with: {},
};
}
const _locales: Result = {
columns: {
id: false,
_parentID: false,
id: false,
},
};
where: createLocaleWhereQuery({ fallbackLocale, locale }),
}
if (adapter.tables[`${tableName}_relationships`]) {
result.with._relationships = {
@@ -42,13 +43,14 @@ export const buildFindManyArgs = ({
}
if (adapter.tables[`${tableName}_locales`]) {
result.with._locales = _locales;
result.with._locales = _locales
}
const locatedBlocks: Block[] = [];
const locatedArrays: { [path: string]: ArrayField } = {};
const locatedBlocks: Block[] = []
const locatedArrays: { [path: string]: ArrayField } = {}
traverseFields({
_locales,
adapter,
currentArgs: result,
currentTableName: tableName,
@@ -60,7 +62,7 @@ export const buildFindManyArgs = ({
path: '',
topLevelArgs: result,
topLevelTableName: tableName,
});
})
return result;
};
return result
}

View File

@@ -1,13 +1,15 @@
/* eslint-disable no-param-reassign */
import { SanitizedConfig } from 'payload/config';
import { buildFindManyArgs } from './buildFindManyArgs';
import { PostgresAdapter } from '../types';
import type { SanitizedConfig } from 'payload/config'
import type { PostgresAdapter } from '../types'
import { buildFindManyArgs } from './buildFindManyArgs'
type BuildWithFromDepthArgs = {
adapter: PostgresAdapter
config: SanitizedConfig
depth: number
fallbackLocale?: string | false
fallbackLocale?: false | string
locale?: string
}
@@ -19,23 +21,23 @@ export const buildWithFromDepth = ({
locale,
}: BuildWithFromDepthArgs): Record<string, unknown> | undefined => {
const result = config.collections.reduce((slugs, coll) => {
const { slug } = coll;
const { slug } = coll
if (depth >= 1) {
const args = buildFindManyArgs({
adapter,
config,
collection: coll,
config,
depth: depth - 1,
fallbackLocale,
locale,
});
})
slugs[`${slug}ID`] = args;
slugs[`${slug}ID`] = args
}
return slugs;
}, {});
return slugs
}, {})
return result;
};
return result
}

View File

@@ -12,20 +12,26 @@ type TraverseFieldArgs = {
depth?: number,
fields: Field[]
_locales: Record<string, unknown>
locatedArrays: { [path: string]: ArrayField },
locatedBlocks: Block[],
path: string,
topLevelArgs: Record<string, unknown>,
adapter: PostgresAdapter
config: SanitizedConfig
currentArgs: Record<string, unknown>
currentTableName: string
depth?: number
fields: Field[]
locatedArrays: { [path: string]: ArrayField }
locatedBlocks: Block[]
path: string
topLevelArgs: Record<string, unknown>
topLevelTableName: string
}
export const traverseFields = ({
_locales,
adapter,
currentArgs,
currentTableName,
depth,
fields,
_locales,
locatedArrays,
locatedBlocks,
path,
@@ -37,39 +43,39 @@ export const traverseFields = ({
switch (field.type) {
case 'array': {
const withArray: Result = {
orderBy: ({ _order }, { asc }) => [asc(_order)],
columns: {
_parentID: false,
_order: false,
_parentID: false,
},
orderBy: ({ _order }, { asc }) => [asc(_order)],
with: {},
};
}
const arrayTableName = `${currentTableName}_${toSnakeCase(field.name)}`;
const arrayTableName = `${currentTableName}_${toSnakeCase(field.name)}`
if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales;
currentArgs.with[`${path}${field.name}`] = withArray;
if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales
currentArgs.with[`${path}${field.name}`] = withArray
traverseFields({
_locales,
adapter,
currentArgs: withArray,
currentTableName: arrayTableName,
depth,
fields: field.fields,
_locales,
locatedArrays,
locatedBlocks,
path: '',
topLevelArgs,
topLevelTableName,
});
})
break;
break
}
case 'blocks':
field.blocks.forEach((block) => {
const blockKey = `_blocks_${block.slug}`;
const blockKey = `_blocks_${block.slug}`
if (!topLevelArgs[blockKey]) {
const withBlock: Result = {
@@ -78,52 +84,52 @@ export const traverseFields = ({
},
orderBy: ({ _order }, { asc }) => [asc(_order)],
with: {},
};
}
if (adapter.tables[`${topLevelTableName}_${toSnakeCase(block.slug)}_locales`]) withBlock.with._locales = _locales;
topLevelArgs.with[blockKey] = withBlock;
traverseFields({
_locales,
adapter,
currentArgs: withBlock,
currentTableName,
depth,
fields: block.fields,
_locales,
locatedArrays,
locatedBlocks,
path,
topLevelArgs,
topLevelTableName,
});
})
}
});
})
break;
break
case 'group':
traverseFields({
_locales,
adapter,
currentArgs,
currentTableName,
depth,
fields: field.fields,
_locales,
locatedArrays,
locatedBlocks,
path: `${path}${field.name}_`,
topLevelArgs,
topLevelTableName,
});
})
break;
break
default: {
break;
break
}
}
}
});
})
return topLevelArgs;
};
return topLevelArgs
}

View File

@@ -1,19 +1,22 @@
import toSnakeCase from 'to-snake-case';
import type { FindOne } from 'payload/dist/database/types';
import type { PayloadRequest } from 'payload/dist/express/types';
import type { SanitizedCollectionConfig } from 'payload/dist/collections/config/types';
import buildQuery from './queries/buildQuery';
import { buildFindManyArgs } from './find/buildFindManyArgs';
import { transform } from './transform/read';
import type { FindOne } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import type { PayloadRequest } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import { buildFindManyArgs } from './find/buildFindManyArgs'
import buildQuery from './queries/buildQuery'
import { transform } from './transform/read'
export const findOne: FindOne = async function findOne({
collection,
where: incomingWhere,
locale,
req = {} as PayloadRequest,
where,
}) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config;
const tableName = toSnakeCase(collection);
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = toSnakeCase(collection)
const { where } = await buildQuery({
adapter: this,
@@ -32,11 +35,12 @@ export const findOne: FindOne = async function findOne({
findManyArgs.where = where;
const doc = await this.db.query[tableName].findFirst(findManyArgs);
const doc = await this.db.query[tableName].findFirst(findManyArgs)
return transform({
config: this.payload.config,
data: doc,
fallbackLocale: req.fallbackLocale,
fields: collectionConfig.fields,
});
};

View File

@@ -31,16 +31,17 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
// @ts-expect-error
return createDatabaseAdapter<PostgresAdapter>({
...args,
enums: {},
relations: {},
tables: {},
payload,
connect,
create,
createMigration,
db: undefined,
enums: {},
find,
// queryDrafts,
findOne,
// destroy,
init,
webpack,
createMigration,
payload,
// beginTransaction,
// rollbackTransaction,
// commitTransaction,
@@ -59,8 +60,8 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
createVersion,
// updateVersion,
// deleteVersions,
});
})
}
return adapter;
return adapter
}

View File

@@ -1 +1 @@
exports.postgresAdapter = () => ({});
exports.postgresAdapter = () => ({})

View File

@@ -9,7 +9,7 @@ export async function buildAndOrConditions({
joins,
where,
adapter,
locale,
collectionSlug,
fields,
tableName,
selectFields,
@@ -24,7 +24,7 @@ export async function buildAndOrConditions({
tableName: string,
selectFields: Record<string, GenericColumn>
}): Promise<SQL[]> {
const completedConditions = [];
const completedConditions = []
// Loop over all AND / OR operations and add them to the AND / OR query param
// Operations should come through as an array
// eslint-disable-next-line no-restricted-syntax
@@ -36,7 +36,7 @@ export async function buildAndOrConditions({
joins,
where: condition,
adapter,
locale,
collectionSlug,
fields,
tableName,
selectFields,
@@ -46,5 +46,5 @@ export async function buildAndOrConditions({
}
}
}
return completedConditions;
return completedConditions
}

View File

@@ -96,4 +96,4 @@ const buildQuery = async function buildQuery({
};
};
export default buildQuery;
export default buildQuery

View File

@@ -1,10 +1,14 @@
import { and, eq, gt, gte, ilike, inArray, isNotNull, isNull, lt, lte, ne, notInArray, or } from 'drizzle-orm';
export const operatorMap = {
greater_than_equal: gte,
less_than_equal: lte,
less_than: lt,
// near: near,
and,
equals: eq,
// TODO: isNotNull isn't right as it depends on if the query value is true or false
exists: isNotNull,
greater_than: gt,
greater_than_equal: gte,
// TODO:
in: inArray,
like: ilike,
// TODO:
@@ -20,4 +24,4 @@ export const operatorMap = {
// intersects: intersects,
and,
or,
};
}

View File

@@ -1,4 +1,5 @@
/* eslint-disable no-restricted-syntax */
import type { SQL } from 'drizzle-orm'
/* eslint-disable no-await-in-loop */
import { Operator, Where } from 'payload/types';
import { Field } from 'payload/dist/fields/config/types';
@@ -25,7 +26,7 @@ export async function parseParams({
joins,
where,
adapter,
locale,
collectionSlug,
fields,
tableName,
selectFields,
@@ -118,5 +119,5 @@ export async function parseParams({
[result] = constraints;
}
return result;
return result
}

View File

@@ -4,40 +4,37 @@
// type PushDiff = (schema: DrizzleSchemaExports) => Promise<{ warnings: string[], apply: () => Promise<void> }>
// drizzle-kit@utils
import { generateDrizzleJson, generateMigration, pushSchema } from 'drizzle-kit/utils';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import { generateDrizzleJson, generateMigration, pushSchema } from 'drizzle-kit/utils'
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
async function generateUsage() {
const schema = await import('./data/users');
const schemaAfter = await import('./data/users-after');
const schema = await import('./data/users')
const schemaAfter = await import('./data/users-after')
const drizzleJsonBefore = generateDrizzleJson(schema);
const drizzleJsonAfter = generateDrizzleJson(schemaAfter);
const drizzleJsonBefore = generateDrizzleJson(schema)
const drizzleJsonAfter = generateDrizzleJson(schemaAfter)
const sqlStatements = await generateMigration(drizzleJsonBefore, drizzleJsonAfter);
const sqlStatements = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
console.log(sqlStatements);
console.log(sqlStatements)
}
async function pushUsage() {
const schemaAfter = await import('./data/users-after');
const schemaAfter = await import('./data/users-after')
const db = drizzle(
new Pool({ connectionString: '' }),
);
const db = drizzle(new Pool({ connectionString: '' }))
const response = await pushSchema(schemaAfter, db);
const response = await pushSchema(schemaAfter, db)
console.log('\n');
console.log('hasDataLoss: ', response.hasDataLoss);
console.log('warnings: ', response.warnings);
console.log('statements: ', response.statementsToExecute);
console.log('\n')
console.log('hasDataLoss: ', response.hasDataLoss)
console.log('warnings: ', response.warnings)
console.log('statements: ', response.statementsToExecute)
await response.apply();
await response.apply()
process.exit(0);
process.exit(0)
}

View File

@@ -1,14 +1,16 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { AnyPgColumnBuilder, IndexBuilder } from 'drizzle-orm/pg-core'
import type { Field } from 'payload/types'
import { relations } from 'drizzle-orm'
import {
AnyPgColumnBuilder,
index,
integer,
numeric,
pgTable,
serial,
varchar,
index,
numeric,
timestamp,
IndexBuilder,
unique,
UniqueConstraintBuilder,
} from 'drizzle-orm/pg-core';
@@ -46,35 +48,35 @@ export const buildTable = ({
const columns: Record<string, AnyPgColumnBuilder> = baseColumns;
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {};
let hasLocalizedField = false;
let hasLocalizedRelationshipField = false;
const localesColumns: Record<string, AnyPgColumnBuilder> = {};
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {};
let localesTable: GenericTable;
let hasLocalizedField = false
let hasLocalizedRelationshipField = false
const localesColumns: Record<string, AnyPgColumnBuilder> = {}
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
let localesTable: GenericTable
const relationships: Set<string> = new Set();
let relationshipsTable: GenericTable;
const relationships: Set<string> = new Set()
let relationshipsTable: GenericTable
const arrayBlockRelations: Map<string, string> = new Map();
const arrayBlockRelations: Map<string, string> = new Map()
const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id');
let idColType = 'integer';
const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id')
let idColType = 'integer'
if (idField) {
if (idField.type === 'number') {
idColType = 'numeric';
columns.id = numeric('id').primaryKey();
idColType = 'numeric'
columns.id = numeric('id').primaryKey()
}
if (idField.type === 'text') {
idColType = 'varchar';
columns.id = varchar('id').primaryKey();
idColType = 'varchar'
columns.id = varchar('id').primaryKey()
}
} else {
columns.id = serial('id').primaryKey();
columns.id = serial('id').primaryKey()
}
({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({
;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({
adapter,
arrayBlockRelations,
buildRelationships,
@@ -86,11 +88,11 @@ export const buildTable = ({
newTableName: tableName,
parentTableName: tableName,
relationships,
}));
}))
if (timestamps) {
columns.createdAt = timestamp('created_at').defaultNow().notNull();
columns.updatedAt = timestamp('updated_at').defaultNow().notNull();
columns.createdAt = timestamp('created_at').defaultNow().notNull()
columns.updatedAt = timestamp('updated_at').defaultNow().notNull()
}
const table = pgTable(tableName, columns, (cols) => {
@@ -114,24 +116,27 @@ export const buildTable = ({
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id').references(() => table.id, { onDelete: 'cascade' }).notNull();
localesTable = pgTable(localeTableName, localesColumns, (cols) => {
return Object.entries(localesIndexes).reduce((acc, [colName, func]) => {
acc[colName] = func(cols);
return acc;
}, {
_localeParent: unique().on(cols._locale, cols._parentID),
});
});
return Object.entries(localesIndexes).reduce(
(acc, [colName, func]) => {
acc[colName] = func(cols)
return acc
},
{
_localeParent: unique().on(cols._locale, cols._parentID),
},
)
})
adapter.tables[localeTableName] = localesTable;
adapter.tables[localeTableName] = localesTable
const localesTableRelations = relations(localesTable, ({ one }) => ({
_parentID: one(table, {
fields: [localesTable._parentID],
references: [table.id],
}),
}));
}))
adapter.relations[`relations_${localeTableName}`] = localesTableRelations;
adapter.relations[`relations_${localeTableName}`] = localesTableRelations
}
if (buildRelationships) {
@@ -141,21 +146,29 @@ export const buildTable = ({
parent: parentIDColumnMap[idColType]('parent_id').references(() => table.id, { onDelete: 'cascade' }).notNull(),
path: varchar('path').notNull(),
order: integer('order'),
};
parent: parentIDColumnMap[idColType]('parent_id')
.references(() => table.id)
.notNull(),
path: varchar('path').notNull(),
}
if (hasLocalizedRelationshipField) {
relationshipColumns.locale = adapter.enums._locales('locale');
}
relationships.forEach((relationTo) => {
const formattedRelationTo = toSnakeCase(relationTo);
let colType = 'integer';
const relatedCollectionCustomID = adapter.payload.collections[relationTo].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric';
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar';
const formattedRelationTo = toSnakeCase(relationTo)
let colType = 'integer'
const relatedCollectionCustomID = adapter.payload.collections[
relationTo
].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id')
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](`${formattedRelationTo}_id`).references(() => adapter.tables[formattedRelationTo].id);
});
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
`${formattedRelationTo}_id`,
).references(() => adapter.tables[formattedRelationTo].id)
})
const relationshipsTableName = `${tableName}_relationships`;
@@ -172,54 +185,54 @@ export const buildTable = ({
return result;
});
adapter.tables[relationshipsTableName] = relationshipsTable;
adapter.tables[relationshipsTableName] = relationshipsTable
const relationshipsTableRelations = relations(relationshipsTable, ({ one }) => {
const result: Record<string, Relation<string>> = {
parent: one(table, {
relationName: '_relationships',
fields: [relationshipsTable.parent],
references: [table.id],
relationName: '_relationships',
}),
};
}
relationships.forEach((relationTo) => {
const relatedTableName = toSnakeCase(relationTo);
const idColumnName = `${relationTo}ID`;
const relatedTableName = toSnakeCase(relationTo)
const idColumnName = `${relationTo}ID`
result[idColumnName] = one(adapter.tables[relatedTableName], {
fields: [relationshipsTable[idColumnName]],
references: [adapter.tables[relatedTableName].id],
});
});
})
})
return result;
});
return result
})
adapter.relations[`relations_${relationshipsTableName}`] = relationshipsTableRelations;
adapter.relations[`relations_${relationshipsTableName}`] = relationshipsTableRelations
}
}
const tableRelations = relations(table, ({ many }) => {
const result: Record<string, Relation<string>> = {};
const result: Record<string, Relation<string>> = {}
arrayBlockRelations.forEach((val, key) => {
result[key] = many(adapter.tables[val]);
});
result[key] = many(adapter.tables[val])
})
if (hasLocalizedField) {
result._locales = many(localesTable);
result._locales = many(localesTable)
}
if (relationships.size && relationshipsTable) {
result._relationships = many(relationshipsTable, {
relationName: '_relationships',
});
})
}
return result;
});
return result
})
adapter.relations[`relations_${tableName}`] = tableRelations;
return { arrayBlockRelations };
};
return { arrayBlockRelations }
}

View File

@@ -1,16 +1,17 @@
/* eslint-disable no-param-reassign */
import { uniqueIndex, index } from 'drizzle-orm/pg-core';
import { GenericColumn } from '../types';
import { index, uniqueIndex } from 'drizzle-orm/pg-core'
import type { GenericColumn } from '../types'
type CreateIndexArgs = {
name: string
columnName: string
name: string
unique?: boolean
}
export const createIndex = ({ name, columnName, unique }: CreateIndexArgs) => {
export const createIndex = ({ columnName, name, unique }: CreateIndexArgs) => {
return (table: { [x: string]: GenericColumn }) => {
if (unique) return uniqueIndex(`${columnName}_idx`).on(table[name]);
return index(`${columnName}_idx`).on(table[name]);
};
};
if (unique) return uniqueIndex(`${columnName}_idx`).on(table[name])
return index(`${columnName}_idx`).on(table[name])
}
}

View File

@@ -1,7 +1,7 @@
import { integer, numeric, varchar } from 'drizzle-orm/pg-core';
import { integer, numeric, varchar } from 'drizzle-orm/pg-core'
export const parentIDColumnMap = {
integer,
varchar,
numeric,
};
varchar,
}

View File

@@ -26,8 +26,8 @@ type Args = {
adapter: PostgresAdapter
arrayBlockRelations: Map<string, string>
buildRelationships: boolean
columns: Record<string, AnyPgColumnBuilder>
columnPrefix?: string
columns: Record<string, AnyPgColumnBuilder>
fieldPrefix?: string
fields: Field[]
forceLocalized?: boolean
@@ -60,22 +60,22 @@ export const traverseFields = ({
parentTableName,
relationships,
}: Args): Result => {
let hasLocalizedField = false;
let hasLocalizedRelationshipField = false;
let hasLocalizedField = false
let hasLocalizedRelationshipField = false
let parentIDColType = 'integer';
if (columns.id instanceof PgNumericBuilder) parentIDColType = 'numeric';
if (columns.id instanceof PgVarcharBuilder) parentIDColType = 'varchar';
let parentIDColType = 'integer'
if (columns.id instanceof PgNumericBuilder) parentIDColType = 'numeric'
if (columns.id instanceof PgVarcharBuilder) parentIDColType = 'varchar'
fields.forEach((field) => {
if ('name' in field && field.name === 'id') return;
let columnName: string;
if ('name' in field && field.name === 'id') return
let columnName: string
let targetTable = columns;
let targetIndexes = indexes;
let targetTable = columns
let targetIndexes = indexes
if (fieldAffectsData(field)) {
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`;
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`
// If field is localized,
// add the column to the locale table instead of main table
@@ -97,37 +97,37 @@ export const traverseFields = ({
case 'textarea': {
// TODO: handle hasMany
// TODO: handle min / max length
targetTable[`${fieldPrefix || ''}${field.name}`] = varchar(columnName);
break;
targetTable[`${fieldPrefix || ''}${field.name}`] = varchar(columnName)
break
}
case 'number': {
// TODO: handle hasMany
// TODO: handle min / max
targetTable[`${fieldPrefix || ''}${field.name}`] = numeric(columnName);
break;
targetTable[`${fieldPrefix || ''}${field.name}`] = numeric(columnName)
break
}
case 'richText':
case 'json': {
targetTable[`${fieldPrefix || ''}${field.name}`] = jsonb(columnName);
break;
targetTable[`${fieldPrefix || ''}${field.name}`] = jsonb(columnName)
break
}
case 'date': {
break;
break
}
case 'point': {
break;
break
}
case 'radio': {
break;
break
}
case 'select': {
break;
break
}
case 'array': {
@@ -145,7 +145,11 @@ export const traverseFields = ({
baseExtraConfig._parentOrder = (cols) => unique().on(cols._parentID, cols._order);
}
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`;
if (field.localized && adapter.payload.config.localization) {
baseColumns._locale = adapter.enums._locales('_locale').notNull()
}
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
const { arrayBlockRelations: subArrayBlockRelations } = buildTable({
adapter,
@@ -153,9 +157,9 @@ export const traverseFields = ({
baseExtraConfig,
fields: field.fields,
tableName: arrayTableName,
});
})
arrayBlockRelations.set(`${fieldPrefix || ''}${field.name}`, arrayTableName);
arrayBlockRelations.set(`${fieldPrefix || ''}${field.name}`, arrayTableName)
const arrayTableRelations = relations(adapter.tables[arrayTableName], ({ many, one }) => {
const result: Record<string, Relation<string>> = {
@@ -163,22 +167,22 @@ export const traverseFields = ({
fields: [adapter.tables[arrayTableName]._parentID],
references: [adapter.tables[parentTableName].id],
}),
};
}
if (hasLocalesTable(field.fields)) {
result._locales = many(adapter.tables[`${arrayTableName}_locales`]);
result._locales = many(adapter.tables[`${arrayTableName}_locales`])
}
subArrayBlockRelations.forEach((val, key) => {
result[key] = many(adapter.tables[val]);
});
result[key] = many(adapter.tables[val])
})
return result;
});
return result
})
adapter.relations[`relations_${arrayTableName}`] = arrayTableRelations;
adapter.relations[`relations_${arrayTableName}`] = arrayTableRelations
break;
break
}
case 'blocks': {
@@ -206,34 +210,37 @@ export const traverseFields = ({
baseExtraConfig,
fields: block.fields,
tableName: blockTableName,
});
})
const blockTableRelations = relations(adapter.tables[blockTableName], ({ many, one }) => {
const result: Record<string, Relation<string>> = {
_parentID: one(adapter.tables[parentTableName], {
fields: [adapter.tables[blockTableName]._parentID],
references: [adapter.tables[parentTableName].id],
}),
};
const blockTableRelations = relations(
adapter.tables[blockTableName],
({ many, one }) => {
const result: Record<string, Relation<string>> = {
_parentID: one(adapter.tables[parentTableName], {
fields: [adapter.tables[blockTableName]._parentID],
references: [adapter.tables[parentTableName].id],
}),
}
if (hasLocalesTable(block.fields)) {
result._locales = many(adapter.tables[`${blockTableName}_locales`]);
}
if (hasLocalesTable(block.fields)) {
result._locales = many(adapter.tables[`${blockTableName}_locales`])
}
subArrayBlockRelations.forEach((val, key) => {
result[key] = many(adapter.tables[val]);
});
subArrayBlockRelations.forEach((val, key) => {
result[key] = many(adapter.tables[val])
})
return result;
});
return result
},
)
adapter.relations[`relations_${blockTableName}`] = blockTableRelations;
adapter.relations[`relations_${blockTableName}`] = blockTableRelations
}
arrayBlockRelations.set(`_blocks_${block.slug}`, blockTableName);
});
arrayBlockRelations.set(`_blocks_${block.slug}`, blockTableName)
})
break;
break
}
case 'group': {
@@ -255,11 +262,11 @@ export const traverseFields = ({
newTableName: `${parentTableName}_${toSnakeCase(field.name)}`,
parentTableName,
relationships,
});
})
if (groupHasLocalizedField) hasLocalizedField = true;
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
break;
if (groupHasLocalizedField) hasLocalizedField = true
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
break
}
case 'tabs': {
@@ -282,15 +289,12 @@ export const traverseFields = ({
newTableName: `${parentTableName}_${toSnakeCase(tab.name)}`,
parentTableName,
relationships,
});
})
if (tabHasLocalizedField) hasLocalizedField = true;
if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
if (tabHasLocalizedField) hasLocalizedField = true
if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
} else {
({
hasLocalizedField,
hasLocalizedRelationshipField,
} = traverseFields({
;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({
adapter,
arrayBlockRelations,
buildRelationships,
@@ -302,18 +306,15 @@ export const traverseFields = ({
newTableName: parentTableName,
parentTableName,
relationships,
}));
}))
}
});
break;
})
break
}
case 'row':
case 'collapsible': {
({
hasLocalizedField,
hasLocalizedRelationshipField,
} = traverseFields({
;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({
adapter,
arrayBlockRelations,
buildRelationships,
@@ -325,27 +326,27 @@ export const traverseFields = ({
newTableName: parentTableName,
parentTableName,
relationships,
}));
break;
}))
break
}
case 'relationship':
case 'upload':
if (Array.isArray(field.relationTo)) {
field.relationTo.forEach((relation) => relationships.add(relation));
field.relationTo.forEach((relation) => relationships.add(relation))
} else {
relationships.add(field.relationTo);
relationships.add(field.relationTo)
}
if (field.localized && adapter.payload.config.localization) {
hasLocalizedRelationshipField = true;
}
break;
break
default:
break;
break
}
});
})
return { hasLocalizedField, hasLocalizedRelationshipField };
};
return { hasLocalizedField, hasLocalizedRelationshipField }
}

View File

@@ -9,7 +9,7 @@ import { createBlocksMap } from '../../utilities/createBlocksMap';
type TransformArgs = {
config: SanitizedConfig
data: Record<string, unknown>
fallbackLocale?: string | false
fallbackLocale?: false | string
fields: Field[]
locale?: string
}
@@ -21,14 +21,14 @@ export const transform = <T extends TypeWithID>({
data,
fields,
}: TransformArgs): T => {
let relationships: Record<string, Record<string, unknown>[]> = {};
let relationships: Record<string, Record<string, unknown>[]> = {}
if ('_relationships' in data) {
relationships = createRelationshipMap(data._relationships);
delete data._relationships;
relationships = createRelationshipMap(data._relationships)
delete data._relationships
}
const blocks = createBlocksMap(data);
const blocks = createBlocksMap(data)
const result = traverseFields<T>({
blocks,

View File

@@ -55,7 +55,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
siblingData,
table,
}: TraverseFieldsArgs): T => {
const sanitizedPath = path ? `${path}.` : path;
const sanitizedPath = path ? `${path}.` : path
const formatted = fields.reduce((result, field) => {
if (fieldAffectsData(field)) {
@@ -160,8 +160,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
});
}
return {};
});
return {}
})
}
}
@@ -181,7 +181,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
if (!relationsByLocale[row.locale]) relationsByLocale[row.locale] = [];
relationsByLocale[row.locale].push(row);
}
});
})
Object.entries(relationsByLocale).forEach(([locale, relations]) => {
transformRelationship({
@@ -233,7 +233,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
delete table[subFieldKey];
}
}
});
})
if (field.localized) {
Object.entries(ref).forEach(([groupLocale, groupLocaleData]) => {
@@ -310,11 +310,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
result[field.name] = localizedFieldData;
}
return result;
return result
}
return siblingData;
}, siblingData);
return siblingData
}, siblingData)
return formatted as T;
};
return formatted as T
}

View File

@@ -1,7 +1,9 @@
/* eslint-disable no-param-reassign */
import { Field } from 'payload/types';
import { traverseFields } from './traverseFields';
import { RowToInsert } from './types';
import type { Field } from 'payload/types'
import type { RowToInsert } from './types'
import { traverseFields } from './traverseFields'
type Args = {
data: Record<string, unknown>
@@ -22,9 +24,8 @@ export const transformForWrite = ({
row: {},
locales: {},
relationships: [],
blocks: {},
arrays: {},
};
row: {},
}
// This function is responsible for building up the
// above rowToInsert
@@ -40,7 +41,7 @@ export const transformForWrite = ({
path,
relationships: rowToInsert.relationships,
row: rowToInsert.row,
});
})
return rowToInsert;
};
return rowToInsert
}

View File

@@ -159,7 +159,7 @@ export const traverseFields = ({
path: `${path || ''}${field.name}.`,
relationships,
row,
});
})
}
}
@@ -320,5 +320,5 @@ export const traverseFields = ({
}
});
}
});
};
})
}

View File

@@ -7,6 +7,9 @@ export type ArrayRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
columnName: string
locale: Record<string, unknown>
row: Record<string, unknown>
}
export type BlockRowToInsert = {
@@ -17,6 +20,8 @@ export type BlockRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
locale: Record<string, unknown>
row: Record<string, unknown>
}
export type RowToInsert = {
@@ -31,4 +36,10 @@ export type RowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
blocks: {
[blockType: string]: BlockRowToInsert[]
}
locale: Record<string, unknown>
relationships: Record<string, unknown>[]
row: Record<string, unknown>
}

View File

@@ -1,20 +1,20 @@
import { ColumnBaseConfig, ColumnDataType, Relation, Relations } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { PgColumn, PgEnum, PgTableWithColumns } from 'drizzle-orm/pg-core';
import { Payload } from 'payload';
import { DatabaseAdapter } from 'payload/dist/database/types';
import { ClientConfig, PoolConfig } from 'pg';
import type { ColumnBaseConfig, ColumnDataType, Relation, Relations } from 'drizzle-orm'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
import type { PgColumn, PgEnum, PgTableWithColumns } from 'drizzle-orm/pg-core'
import type { Payload } from 'payload'
import type { DatabaseAdapter } from 'payload/database'
import type { ClientConfig, PoolConfig } from 'pg'
export type DrizzleDB = NodePgDatabase<Record<string, never>>
type BaseArgs = {
migrationDir?: string;
migrationName?: string;
migrationDir?: string
migrationName?: string
}
type ClientArgs = {
/** Client connection options for the Node package `pg` */
client?: ClientConfig | string | false
client?: ClientConfig | false | string
} & BaseArgs
type PoolArgs = {
@@ -24,26 +24,33 @@ type PoolArgs = {
export type Args = ClientArgs | PoolArgs
export type GenericColumn = PgColumn<ColumnBaseConfig<ColumnDataType, string>, Record<string, unknown>>
export type GenericColumn = PgColumn<
ColumnBaseConfig<ColumnDataType, string>,
Record<string, unknown>
>
export type GenericColumns = {
[x: string]: GenericColumn
}
export type GenericTable = PgTableWithColumns<{
name: string, schema: undefined, columns: GenericColumns, dialect: string
columns: GenericColumns
dialect: string
name: string
schema: undefined
}>
export type GenericEnum = PgEnum<[string, ...string[]]>
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
export type PostgresAdapter = DatabaseAdapter & Args & {
db: DrizzleDB
enums: Record<string, GenericEnum>
relations: Record<string, GenericRelation>
tables: Record<string, GenericTable>
schema: Record<string, GenericEnum | GenericTable | GenericRelation>
}
export type PostgresAdapter = DatabaseAdapter &
Args & {
db: DrizzleDB
enums: Record<string, GenericEnum>
relations: Record<string, GenericRelation>
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
tables: Record<string, GenericTable>
}
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter

View File

@@ -1,8 +1,10 @@
import { UpdateOne } from 'payload/dist/database/types';
import toSnakeCase from 'to-snake-case';
import { SQL } from 'drizzle-orm';
import buildQuery from '../queries/buildQuery';
import { upsertRow } from '../upsertRow';
import type { SQL } from 'drizzle-orm'
import type { UpdateOne } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import buildQuery from '../queries/buildQuery'
import { upsertRow } from '../upsertRow'
export const updateOne: UpdateOne = async function updateOne({
collection: collectionSlug,
@@ -13,9 +15,9 @@ export const updateOne: UpdateOne = async function updateOne({
req,
where,
}) {
const collection = this.payload.collections[collectionSlug].config;
const collection = this.payload.collections[collectionSlug].config
let query: SQL<unknown>;
let query: SQL<unknown>
if (where) {
query = await buildQuery({
@@ -23,7 +25,7 @@ export const updateOne: UpdateOne = async function updateOne({
collectionSlug,
locale,
where,
});
})
}
const result = await upsertRow({
@@ -35,7 +37,7 @@ export const updateOne: UpdateOne = async function updateOne({
operation: 'update',
tableName: toSnakeCase(collectionSlug),
where: query,
});
})
return result;
};
return result
}

View File

@@ -27,29 +27,33 @@ export const upsertRow = async ({
fields,
path,
tableName,
});
})
// First, we insert the main row
let insertedRow: Record<string, unknown>;
let insertedRow: Record<string, unknown>
if (operation === 'update') {
const target = upsertTarget || adapter.tables[tableName].id;
const target = upsertTarget || adapter.tables[tableName].id
if (id) {
rowToInsert.row.id = id;
[insertedRow] = await adapter.db.insert(adapter.tables[tableName])
rowToInsert.row.id = id
;[insertedRow] = await adapter.db
.insert(adapter.tables[tableName])
.values(rowToInsert.row)
.onConflictDoUpdate({ target, set: rowToInsert.row })
.returning();
.onConflictDoUpdate({ set: rowToInsert.row, target })
.returning()
} else {
[insertedRow] = await adapter.db.insert(adapter.tables[tableName])
;[insertedRow] = await adapter.db
.insert(adapter.tables[tableName])
.values(rowToInsert.row)
.onConflictDoUpdate({ target, set: rowToInsert.row, where })
.returning();
.onConflictDoUpdate({ set: rowToInsert.row, target, where })
.returning()
}
} else {
[insertedRow] = await adapter.db.insert(adapter.tables[tableName])
.values(rowToInsert.row).returning();
;[insertedRow] = await adapter.db
.insert(adapter.tables[tableName])
.values(rowToInsert.row)
.returning()
}
const localesToInsert: Record<string, unknown>[] = [];
@@ -58,7 +62,7 @@ export const upsertRow = async ({
// Maintain a list of promises to run locale, blocks, and relationships
// all in parallel
const promises = [];
const promises = []
// If there are locale rows with data, add the parent and locale to each
if (Object.keys(rowToInsert.locales).length > 0) {
@@ -72,20 +76,20 @@ export const upsertRow = async ({
// If there are relationships, add parent to each
if (rowToInsert.relationships.length > 0) {
rowToInsert.relationships.forEach((relation) => {
relation.parent = insertedRow.id;
relationsToInsert.push(relation);
});
relation.parent = insertedRow.id
relationsToInsert.push(relation)
})
}
// If there are blocks, add parent to each, and then
// store by table name and rows
Object.keys(rowToInsert.blocks).forEach((blockName) => {
rowToInsert.blocks[blockName].forEach((blockRow) => {
blockRow.row._parentID = insertedRow.id;
if (!blocksToInsert[blockName]) blocksToInsert[blockName] = [];
blocksToInsert[blockName].push(blockRow);
});
});
blockRow.row._parentID = insertedRow.id
if (!blocksToInsert[blockName]) blocksToInsert[blockName] = []
blocksToInsert[blockName].push(blockRow)
})
})
// //////////////////////////////////
// INSERT LOCALES
@@ -131,7 +135,7 @@ export const upsertRow = async ({
// INSERT BLOCKS
// //////////////////////////////////
const insertedBlockRows: Record<string, Record<string, unknown>[]> = {};
const insertedBlockRows: Record<string, Record<string, unknown>[]> = {}
Object.entries(blocksToInsert).forEach(([blockName, blockRows]) => {
// For each block, push insert into promises to run parallel
@@ -153,7 +157,7 @@ export const upsertRow = async ({
blockRows[i].row = row;
});
const blockLocaleIndexMap: number[] = [];
const blockLocaleIndexMap: number[] = []
const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i) => {
if (Object.entries(blockRow.locales).length > 0) {
@@ -167,8 +171,8 @@ export const upsertRow = async ({
});
}
return acc;
}, []);
return acc
}, [])
if (blockLocaleRowsToInsert.length > 0) {
await adapter.db.insert(adapter.tables[`${tableName}_${blockName}_locales`])
@@ -179,9 +183,9 @@ export const upsertRow = async ({
adapter,
arrays: blockRows.map(({ arrays }) => arrays),
parentRows: insertedBlockRows[blockName],
});
});
});
})
})
})
// //////////////////////////////////
// INSERT ARRAYS RECURSIVELY
@@ -202,10 +206,10 @@ export const upsertRow = async ({
adapter,
arrays: [rowToInsert.arrays],
parentRows: [insertedRow],
});
});
})
})
await Promise.all(promises.map((promise) => promise()));
await Promise.all(promises.map((promise) => promise()))
// //////////////////////////////////
// RETRIEVE NEWLY UPDATED ROW
@@ -232,5 +236,5 @@ export const upsertRow = async ({
fields,
});
return result;
};
return result
}

View File

@@ -26,7 +26,7 @@ export const insertArrays = async ({
parentRows,
}: Args): Promise<void> => {
// Maintain a map of flattened rows by table
const rowsByTable: RowsByTable = {};
const rowsByTable: RowsByTable = {}
arrays.forEach((arraysByTable, parentRowIndex) => {
Object.entries(arraysByTable).forEach(([tableName, arrayRows]) => {
@@ -39,13 +39,13 @@ export const insertArrays = async ({
};
}
const parentID = parentRows[parentRowIndex].id;
const parentID = parentRows[parentRowIndex].id
// Add any sub arrays that need to be created
// We will call this recursively below
arrayRows.forEach((arrayRow) => {
if (Object.keys(arrayRow.arrays).length > 0) {
rowsByTable[tableName].arrays.push(arrayRow.arrays);
rowsByTable[tableName].arrays.push(arrayRow.arrays)
}
// Set up parent IDs for both row and locale row

View File

@@ -1,6 +1,7 @@
import { Field } from 'payload/types';
import { SQL } from 'drizzle-orm';
import { GenericColumn, PostgresAdapter } from '../types';
import type { SQL } from 'drizzle-orm'
import type { Field } from 'payload/types'
import type { GenericColumn, PostgresAdapter } from '../types'
type BaseArgs = {
adapter: PostgresAdapter
@@ -11,17 +12,17 @@ type BaseArgs = {
}
type CreateArgs = BaseArgs & {
upsertTarget?: never
where?: never
id?: never
operation: 'create'
upsertTarget?: never
where?: never
}
type UpdateArgs = BaseArgs & {
upsertTarget?: GenericColumn
id?: number | string
operation: 'update'
upsertTarget?: GenericColumn
where?: SQL<unknown>
id?: string | number
}
export type Args = CreateArgs | UpdateArgs

View File

@@ -4,38 +4,38 @@ export type BlocksMap = {
}
export const createBlocksMap = (data: Record<string, unknown>): BlocksMap => {
const blocksMap: BlocksMap = {};
const blocksMap: BlocksMap = {}
Object.entries(data).forEach(([key, rows]) => {
if (key.startsWith('_blocks_') && Array.isArray(rows)) {
const blockType = key.replace('_blocks_', '');
const blockType = key.replace('_blocks_', '')
rows.forEach((row) => {
if ('_path' in row) {
if (!(row._path in blocksMap)) blocksMap[row._path] = [];
if (!(row._path in blocksMap)) blocksMap[row._path] = []
row.blockType = blockType;
blocksMap[row._path].push(row);
row.blockType = blockType
blocksMap[row._path].push(row)
delete row._path;
}
});
})
delete data[key];
delete data[key]
}
});
})
Object.entries(blocksMap).reduce((sortedBlocksMap, [path, blocks]) => {
sortedBlocksMap[path] = blocks.sort((a, b) => {
if (typeof a._order === 'number' && typeof b._order === 'number') {
return a._order - b._order;
return a._order - b._order
}
return 0;
});
return 0
})
return sortedBlocksMap;
}, {});
return sortedBlocksMap
}, {})
return blocksMap;
};
return blocksMap
}

View File

@@ -1,22 +1,24 @@
// Flatten relationships to object with path keys
// for easier retrieval
export const createRelationshipMap = (rawRelationships: unknown): Record<string, Record<string, unknown>[]> => {
let relationships = {};
export const createRelationshipMap = (
rawRelationships: unknown,
): Record<string, Record<string, unknown>[]> => {
let relationships = {}
if (Array.isArray(rawRelationships)) {
relationships = rawRelationships.reduce((res, relation) => {
const formattedRelation = {
...relation,
};
}
delete formattedRelation.path;
delete formattedRelation.path
if (!res[relation.path]) res[relation.path] = [];
res[relation.path].push(formattedRelation);
if (!res[relation.path]) res[relation.path] = []
res[relation.path].push(formattedRelation)
return res;
}, {});
return res
}, {})
}
return relationships;
};
return relationships
}

View File

@@ -1,11 +1,12 @@
import { fieldAffectsData, fieldHasSubFields } from 'payload/dist/fields/config/types';
import { Field } from 'payload/types';
import type { Field } from 'payload/types'
import { fieldAffectsData, fieldHasSubFields } from 'payload/types'
export const hasLocalesTable = (fields: Field[]): boolean => {
return fields.some((field) => {
if (fieldAffectsData(field) && field.localized) return true;
if (fieldHasSubFields(field) && field.type !== 'array') return hasLocalesTable(field.fields);
if (field.type === 'tabs') return field.tabs.some((tab) => hasLocalesTable(tab.fields));
return false;
});
};
if (fieldAffectsData(field) && field.localized) return true
if (fieldHasSubFields(field) && field.type !== 'array') return hasLocalesTable(field.fields)
if (field.type === 'tabs') return field.tabs.some((tab) => hasLocalesTable(tab.fields))
return false
})
}

View File

@@ -1,3 +1,3 @@
export function isArrayOfRows(data: unknown): data is Record<string, unknown>[] {
return Array.isArray(data);
return Array.isArray(data)
}

View File

@@ -1,15 +1,16 @@
import path from 'path';
import type { Webpack } from 'payload/dist/database/types';
import type { Webpack } from 'payload/database'
import path from 'path'
export const webpack: Webpack = (config) => {
return {
...config,
resolve: {
...config.resolve || {},
...(config.resolve || {}),
alias: {
...config.resolve?.alias || {},
...(config.resolve?.alias || {}),
[path.resolve(__dirname, './index')]: path.resolve(__dirname, 'mock'),
},
},
};
};
}
}

View File

@@ -1,11 +1,24 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true, /* Generates corresponding '.d.ts' file. */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
"composite": true, // Make sure typescript knows that this module depends on their references
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */
},
"exclude": [
"dist",
"build",
"tests",
"test",
"node_modules",
".eslintrc.js",
"src/**/*.spec.js",
"src/**/*.spec.jsx",
"src/**/*.spec.ts",
"src/**/*.spec.tsx"
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
"references": [{ "path": "../payload" }] // db-postgres depends on payload
}

File diff suppressed because it is too large Load Diff