Compare commits

...

23 Commits

Author SHA1 Message Date
Kendell Joseph
f9677872b5 chore: adds generated types for collections-db and globals-db 2024-09-20 13:43:01 -04:00
Kendell Joseph
358e979c9e chore: linting variable name 2024-09-20 13:32:15 -04:00
Kendell Joseph
b691c6c9a7 chore: adds global type 2024-09-20 11:48:31 -04:00
Kendell Joseph
0c4547b890 chore: updates docs 2024-09-20 11:13:28 -04:00
Kendell Joseph
83a060b3f7 chore: uses strings for id and creates a duplicate collection with db 2024-09-20 10:14:26 -04:00
Kendell Joseph
e20248489a chore: updates collections-db test to include skips 2024-09-20 10:01:09 -04:00
Kendell Joseph
9f788b192d chore: init and connect to global and collection DBs 2024-09-18 12:19:42 -04:00
Kendell Joseph
ab7845d5bc chore: adds connect option to db 2024-09-18 12:18:11 -04:00
Kendell Joseph
9a9ff781bf chore: skips initialization for collections and globals with their own init defined 2024-09-18 12:17:34 -04:00
Kendell Joseph
118287097f chore: skips db initialization for collections and globals with their own init defined 2024-09-18 12:16:43 -04:00
Kendell Joseph
6b48734375 chore: adds test collection logic for init and connect 2024-09-18 12:14:20 -04:00
Kendell Joseph
087a30df86 chore: adds init and connection tests 2024-09-18 12:13:40 -04:00
Kendell Joseph
7baf43aa0f chore: adds init and connect tests 2024-09-18 12:13:31 -04:00
Kendell Joseph
d9cbd446ff chore: updates docs to include global db operations 2024-09-17 16:25:44 -04:00
Kendell Joseph
7da131a93e chore: correctly retrieves global config 2024-09-17 16:04:05 -04:00
Kendell Joseph
f5575049f7 chore: linting 2024-09-17 16:03:00 -04:00
Kendell Joseph
9b11a75ef8 chore: checks for custom global db before transactions 2024-09-17 16:02:37 -04:00
Kendell Joseph
f3b809193d chore: adds guard clause to skip init on collections with init defined 2024-09-17 15:59:44 -04:00
Kendell Joseph
25d83c3d06 chore: adds test for global db 2024-09-17 15:58:05 -04:00
Kendell Joseph
b693de557b chore: updates globals types 2024-09-12 15:34:39 -04:00
Kendell Joseph
7beb4db796 chore: updates collection types 2024-09-12 15:32:05 -04:00
Kendell Joseph
e2d4a2a59f chore: wip 2024-09-12 15:01:15 -04:00
Kendell Joseph
a5444d0e7c chore: wip 2024-09-12 10:50:09 -04:00
39 changed files with 974 additions and 136 deletions

View File

@@ -78,6 +78,8 @@ To configure Collection database operations in your Payload application, your Co
The override methods receive arguments useful for augmenting operations such as Field data, the collection slug, and the req.
Relations and Migrations are not supported at this time on collections with custom database operations.
Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'
@@ -172,3 +174,47 @@ export const Collection: CollectionConfig => {
}
```
## Global Operations
You can also configure database operations on globals.
Like collection operations, the override methods receive arguments useful for augmenting operations such as the global slug, and the req.
Relations and Migrations are not supported at this time on globals with custom database operations.
Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'
export const Collection: CollectionConfig => {
return {
globals: [
{
slug: 'global-db-operations',
db: {
createGlobal: () => {
// ...
},
updateGlobal: () => {
// ...
},
findGlobal: () => {
// ...
},
findGlobalVersions: () => {
// ...
},
},
fields: [
{
name: 'name',
type: 'text',
},
],
},
],
}
}
```

View File

@@ -2,6 +2,7 @@
import type { PaginateOptions } from 'mongoose'
import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import type { SanitizedGlobalConfig } from 'payload/types'
import mongoose from 'mongoose'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
@@ -19,6 +20,9 @@ import { getDBName } from './utilities/getDBName'
export const init: Init = async function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
// Skip collections that have an .init() method
if ('function' === typeof collection?.db?.init) return
const schema = buildCollectionSchema(collection, this)
if (collection.versions) {
@@ -74,7 +78,10 @@ export const init: Init = async function init(this: MongooseAdapter) {
const model = buildGlobalModel(this)
this.globals = model
this.payload.config.globals.forEach((global) => {
this.payload.config.globals.forEach((global: SanitizedGlobalConfig) => {
// Skip globals that have an .init() method
if ('function' === typeof global?.db?.init) return
if (global.versions) {
const versionModelName = getDBName({ config: global, versions: true })

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import type { SanitizedGlobalConfig } from 'payload/types'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
@@ -25,6 +26,9 @@ export const init: Init = async function init(this: PostgresAdapter) {
}
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
// Skip collections that have an .init() method
if ('function' === typeof collection?.db?.init) return
const tableName = createTableName({
adapter: this,
config: collection,
@@ -67,7 +71,10 @@ export const init: Init = async function init(this: PostgresAdapter) {
}
})
this.payload.config.globals.forEach((global) => {
this.payload.config.globals.forEach((global: SanitizedGlobalConfig) => {
// Skip globals that have an .init() method
if ('function' === typeof global?.db?.init) return
const tableName = createTableName({ adapter: this, config: global })
buildTable({

View File

@@ -49,11 +49,21 @@ const getExecuteStaticAccess =
})
}
const doc = await req.payload.db.findOne({
let doc: Record<string, unknown> | undefined
if (config?.db?.findOne) {
doc = await config.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
} else {
doc = await req.payload.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
}
if (!doc) {
throw new Forbidden(req.t)

View File

@@ -79,11 +79,18 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
throw new APIError('Missing email.')
}
let user = await payload.db.findOne<UserDoc>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: { email: { equals: data.email.toLowerCase() } },
})
}
let user: UserDoc
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<UserDoc>(userDbArgs)
} else {
user = await payload.db.findOne<UserDoc>(userDbArgs)
}
if (!user) return null

View File

@@ -3,10 +3,20 @@ import type { PayloadRequest } from '../../express/types'
async function init(args: { collection: string; req: PayloadRequest }): Promise<boolean> {
const { collection: slug, req } = args
const doc = await req.payload.db.findOne({
const collectionConfig = req.payload.config.collections[slug]
let doc: Record<string, unknown> | undefined
if (collectionConfig?.db?.findOne) {
doc = await collectionConfig.db.findOne({
collection: slug,
req,
})
} else {
doc = await req.payload.db.findOne({
collection: slug,
req,
})
}
return !!doc
}

View File

@@ -95,11 +95,18 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
const email = unsanitizedEmail ? unsanitizedEmail.toLowerCase().trim() : null
let user = await payload.db.findOne<any>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: { email: { equals: email.toLowerCase() } },
})
}
let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}
if (!user || (args.collection.config.auth.verify && user._verified === false)) {
throw new AuthenticationError(req.t)

View File

@@ -32,8 +32,8 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
collection: {
config,
config: {
auth: { verify },
slug,
auth: { verify },
},
},
data,
@@ -44,10 +44,18 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
try {
const shouldCommit = await initTransaction(req)
const doc = await payload.db.findOne({
let doc
if (config?.db?.findOne) {
doc = await config.db.findOne({
collection: config.slug,
req,
})
} else {
doc = await payload.db.findOne({
collection: config.slug,
req,
})
}
if (doc) throw new Forbidden(req.t)

View File

@@ -58,14 +58,21 @@ async function resetPassword(args: Arguments): Promise<Result> {
// Reset Password
// /////////////////////////////////////
const user = await payload.db.findOne<any>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: {
resetPasswordExpiration: { greater_than: new Date() },
resetPasswordToken: { equals: data.token },
},
})
}
let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<any>(userDbArgs)
} else {
user = await payload.db.findOne<any>(userDbArgs)
}
if (!user) throw new APIError('Token is either invalid or has expired.')
@@ -81,12 +88,18 @@ async function resetPassword(args: Arguments): Promise<Result> {
user._verified = true
}
const doc = await payload.db.updateOne({
const updateDbArgs = {
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
}
let doc: any
if (collectionConfig?.db?.updateOne) {
doc = await collectionConfig.db.updateOne(updateDbArgs)
} else {
doc = await payload.db.updateOne(updateDbArgs)
}
await authenticateLocalStrategy({ doc, password: data.password })

View File

@@ -52,12 +52,19 @@ async function unlock(args: Args): Promise<boolean> {
throw new APIError('Missing email.')
}
const user = await req.payload.db.findOne({
const userDbArgs = {
collection: collectionConfig.slug,
locale,
req,
where: { email: { equals: data.email.toLowerCase() } },
})
}
let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<any>(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}
let result

View File

@@ -23,19 +23,25 @@ async function verifyEmail(args: Args): Promise<boolean> {
try {
const shouldCommit = await initTransaction(req)
const user = await req.payload.db.findOne<any>({
const userDbArgs = {
collection: collection.config.slug,
req,
where: {
_verificationToken: { equals: token },
},
})
}
let user: any
if (collection.config?.db?.findOne) {
user = await collection.config.db.findOne<any>(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}
if (!user) throw new APIError('Verification token is invalid.', httpStatus.BAD_REQUEST)
if (user && user._verified === true)
throw new APIError('This account has already been activated.', httpStatus.ACCEPTED)
await req.payload.db.updateOne({
const updateDbArgs = {
id: user.id,
collection: collection.config.slug,
data: {
@@ -44,7 +50,13 @@ async function verifyEmail(args: Args): Promise<boolean> {
_verified: true,
},
req,
})
}
if (collection.config?.db?.updateOne) {
await collection.config.db.updateOne(updateDbArgs)
} else {
await req.payload.db.updateOne(updateDbArgs)
}
if (shouldCommit) await commitTransaction(req)

View File

@@ -387,9 +387,30 @@ export type CollectionConfig = {
/**
* Add a custom database adapter to this collection.
*/
db?: Pick<
db?:
| DatabaseAdapter
| Pick<
DatabaseAdapter,
'create' | 'deleteMany' | 'deleteOne' | 'find' | 'findOne' | 'updateOne'
| 'connect'
| 'count'
| 'create'
| 'createGlobal'
| 'createGlobalVersion'
| 'createVersion'
| 'deleteMany'
| 'deleteOne'
| 'deleteVersions'
| 'find'
| 'findGlobal'
| 'findGlobalVersions'
| 'findOne'
| 'findVersions'
| 'init'
| 'queryDrafts'
| 'updateGlobal'
| 'updateGlobalVersion'
| 'updateOne'
| 'updateVersion'
>
/**
* Used to override the default naming of the database table or collection with your using a function or string

View File

@@ -80,11 +80,16 @@ async function count<T extends TypeWithID & Record<string, unknown>>(
where,
})
result = await payload.db.count({
const countDbArgs = {
collection: collectionConfig.slug,
req,
where: fullWhere,
})
}
if (collectionConfig?.db?.count) {
result = await collectionConfig.db.count(countDbArgs)
} else {
result = await payload.db.count(countDbArgs)
}
// /////////////////////////////////////
// afterOperation - Collection

View File

@@ -124,7 +124,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
where: fullWhere,
})
result = await payload.db.queryDrafts<T>({
const queryDraftArgs = {
collection: collectionConfig.slug,
limit: sanitizedLimit,
locale,
@@ -133,7 +133,12 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
req,
sort: getQueryDraftsSort(sort),
where: fullWhere,
})
}
if (collectionConfig?.db?.queryDrafts) {
result = await collectionConfig.db.queryDrafts<T>(queryDraftArgs)
} else {
result = await payload.db.queryDrafts<T>(queryDraftArgs)
}
} else {
await validateQueryPaths({
collectionConfig,

View File

@@ -65,14 +65,21 @@ async function findVersionByID<T extends TypeWithID = any>(
// Find by ID
// /////////////////////////////////////
const versionsQuery = await payload.db.findVersions<T>({
const findVersionsDbArgs = {
collection: collectionConfig.slug,
limit: 1,
locale,
pagination: false,
req,
where: fullWhere,
})
}
let versionsQuery
if (collectionConfig?.db?.findVersions) {
versionsQuery = await collectionConfig.db.findVersions<T>(findVersionsDbArgs)
} else {
versionsQuery = await payload.db.findVersions<T>(findVersionsDbArgs)
}
const result = versionsQuery.docs[0]

View File

@@ -73,7 +73,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
// Find
// /////////////////////////////////////
const paginatedDocs = await payload.db.findVersions<T>({
const findVersionsDbArgs = {
collection: collectionConfig.slug,
limit: limit ?? 10,
locale,
@@ -82,7 +82,14 @@ async function findVersions<T extends TypeWithVersion<T>>(
req,
sort,
where: fullWhere,
})
}
let paginatedDocs
if (collectionConfig?.db?.findVersions) {
paginatedDocs = await collectionConfig.db.findVersions<T>(findVersionsDbArgs)
} else {
paginatedDocs = await payload.db.findVersions<T>(findVersionsDbArgs)
}
// /////////////////////////////////////
// beforeRead - Collection

View File

@@ -49,13 +49,21 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Retrieve original raw version
// /////////////////////////////////////
const { docs: versionDocs } = await req.payload.db.findVersions({
const findVersionDbArgs = {
collection: collectionConfig.slug,
limit: 1,
locale,
req,
where: { id: { equals: id } },
})
}
let queryResult: any
if (collectionConfig?.db?.findVersions) {
queryResult = await collectionConfig.db.findVersions(findVersionDbArgs)
} else {
queryResult = await req.payload.db.findVersions(findVersionDbArgs)
}
const versionDocs = queryResult.docs
const [rawVersion] = versionDocs
@@ -132,7 +140,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
delete prevVersion.id
await payload.db.createVersion({
const createVersionDbArgs = {
autosave: false,
collectionSlug: collectionConfig.slug,
createdAt: prevVersion.createdAt,
@@ -140,7 +148,12 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
req,
updatedAt: new Date().toISOString(),
versionData: rawVersion.version,
})
}
if (collectionConfig?.db?.createVersion) {
await collectionConfig.db.createVersion(createVersionDbArgs)
} else {
await payload.db.createVersion(createVersionDbArgs)
}
// /////////////////////////////////////
// afterRead - Fields

View File

@@ -128,12 +128,22 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
where: versionsWhere,
})
const query = await payload.db.queryDrafts<GeneratedTypes['collections'][TSlug]>({
const queryDraftsDbArgs = {
collection: collectionConfig.slug,
locale,
req,
where: versionsWhere,
})
}
let query: any
if (collection.config?.db?.queryDrafts) {
query =
await collection.config.db.queryDrafts<GeneratedTypes['collections'][TSlug]>(
queryDraftsDbArgs,
)
} else {
query =
await payload.db.queryDrafts<GeneratedTypes['collections'][TSlug]>(queryDraftsDbArgs)
}
docs = query.docs
} else {

View File

@@ -1,6 +1,7 @@
import type { GraphQLNonNull, GraphQLObjectType } from 'graphql'
import type { DeepRequired } from 'ts-essentials'
import type { DatabaseAdapter } from '../../'
import type {
CustomPreviewButtonProps,
CustomPublishButtonType,
@@ -170,6 +171,34 @@ export type GlobalConfig = {
admin?: GlobalAdminOptions
/** Extension point to add your custom data. */
custom?: Record<string, any>
/**
* Add a custom database adapter to this global.
*/
db?:
| DatabaseAdapter
| Pick<
DatabaseAdapter,
| 'connect'
| 'count'
| 'create'
| 'createGlobal'
| 'createGlobalVersion'
| 'createVersion'
| 'deleteMany'
| 'deleteOne'
| 'deleteVersions'
| 'find'
| 'findGlobal'
| 'findGlobalVersions'
| 'findOne'
| 'findVersions'
| 'init'
| 'queryDrafts'
| 'updateGlobal'
| 'updateGlobalVersion'
| 'updateOne'
| 'updateVersion'
>
/**
* Customize the SQL table name
*/

View File

@@ -50,12 +50,18 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
// Perform database operation
// /////////////////////////////////////
let doc = await req.payload.db.findGlobal({
const findGlobalArgs = {
slug,
locale,
req,
where: overrideAccess ? undefined : (accessResult as Where),
})
}
let doc
if (globalConfig?.db?.findGlobal) {
doc = await globalConfig.db.findGlobal(findGlobalArgs)
} else {
doc = await req.payload.db.findGlobal(findGlobalArgs)
}
if (!doc) {
doc = {}
}

View File

@@ -66,7 +66,13 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
if (!findGlobalVersionsArgs.where.and[0].id) throw new NotFound(t)
const { docs: results } = await payload.db.findGlobalVersions(findGlobalVersionsArgs)
let globalVersions: any
if (globalConfig?.db?.findGlobalVersions) {
globalVersions = await globalConfig.db.findGlobalVersions(findGlobalVersionsArgs)
} else {
globalVersions = await payload.db.findGlobalVersions(findGlobalVersionsArgs)
}
const results = globalVersions.docs
if (!results || results?.length === 0) {
if (!disableErrors) {
if (!hasWhereAccess) throw new NotFound(t)

View File

@@ -69,7 +69,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
// Find
// /////////////////////////////////////
const paginatedDocs = await payload.db.findGlobalVersions<T>({
const paginatedDocsArgs = {
global: globalConfig.slug,
limit: limit ?? 10,
locale,
@@ -77,7 +77,13 @@ async function findVersions<T extends TypeWithVersion<T>>(
req,
sort,
where: fullWhere,
})
}
let paginatedDocs: any
if (globalConfig?.db?.findGlobalVersions) {
paginatedDocs = await globalConfig.db.findGlobalVersions<T>(paginatedDocsArgs)
} else {
paginatedDocs = await payload.db.findGlobalVersions<T>(paginatedDocsArgs)
}
// /////////////////////////////////////
// afterRead - Fields

View File

@@ -45,12 +45,19 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
// Retrieve original raw version
// /////////////////////////////////////
const { docs: versionDocs } = await payload.db.findGlobalVersions<any>({
const findGlobalDbArgs = {
global: globalConfig.slug,
limit: 1,
req,
where: { id: { equals: id } },
})
}
let findGlobalResult: any
if (globalConfig?.db?.findGlobalVersions) {
findGlobalResult = await globalConfig.db.findGlobalVersions<any>(findGlobalDbArgs)
} else {
findGlobalResult = await payload.db.findGlobalVersions<any>(findGlobalDbArgs)
}
const versionDocs = findGlobalResult.docs
if (!versionDocs || versionDocs.length === 0) {
throw new NotFound(t)
@@ -75,25 +82,38 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
// Update global
// /////////////////////////////////////
const global = await payload.db.findGlobal({
let global: any
if (globalConfig?.db?.findGlobal) {
global = await globalConfig.db.findGlobal({
slug: globalConfig.slug,
req,
})
let result = rawVersion.version
if (global) {
result = await payload.db.updateGlobal({
slug: globalConfig.slug,
data: result,
req,
})
} else {
result = await payload.db.createGlobal({
global = await payload.db.findGlobal({
slug: globalConfig.slug,
req,
})
}
let result = rawVersion.version
const globalDbArgs = {
slug: globalConfig.slug,
data: result,
req,
})
}
if (global) {
if (globalConfig?.db?.updateGlobal) {
result = await globalConfig.db.updateGlobal(globalDbArgs)
} else {
result = await payload.db.updateGlobal(globalDbArgs)
}
} else {
if (globalConfig?.db?.createGlobal) {
result = await globalConfig.db.createGlobal(globalDbArgs)
} else {
result = await payload.db.createGlobal(globalDbArgs)
}
}
// /////////////////////////////////////

View File

@@ -44,7 +44,11 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
} = args
try {
const shouldCommit = await initTransaction(req)
const isCustomGlobalDb = Boolean(
globalConfig?.db?.updateGlobal || globalConfig?.db?.createGlobal,
)
const shouldCommit = !isCustomGlobalDb && (await initTransaction(req))
let { data } = args
@@ -177,18 +181,23 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
// /////////////////////////////////////
if (!shouldSaveDraft) {
const globalDbArgs = {
slug,
data: result,
req,
}
if (globalExists) {
result = await payload.db.updateGlobal({
slug,
data: result,
req,
})
if (globalConfig?.db?.updateGlobal) {
result = await globalConfig.db.updateGlobal(globalDbArgs)
} else {
result = await payload.db.createGlobal({
slug,
data: result,
req,
})
result = await payload.db.updateGlobal(globalDbArgs)
}
} else {
if (globalConfig?.db?.createGlobal) {
result = await globalConfig.db.createGlobal(globalDbArgs)
} else {
result = await payload.db.createGlobal(globalDbArgs)
}
}
}

View File

@@ -352,13 +352,34 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
this.config = await loadConfig(this.logger)
}
const globalDbConnections = {}
const globalDbInitializations = {}
const collectionDbConnections = {}
const collectionDbInitializations = {}
this.globals = {
config: this.config.globals,
}
this.config.globals.forEach((global) => {
if ('function' === typeof global.db?.connect) {
globalDbConnections[global.slug] = global.db.connect
}
if ('function' === typeof global.db?.init) {
globalDbInitializations[global.slug] = global.db.init
}
})
this.config.collections.forEach((collection) => {
this.collections[collection.slug] = {
config: collection,
}
if ('function' === typeof collection.db?.connect) {
collectionDbConnections[collection.slug] = collection.db.connect
}
if ('function' === typeof collection.db?.init) {
collectionDbInitializations[collection.slug] = collection.db.init
}
})
this.db = this.config.db({ payload: this })
@@ -368,8 +389,22 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
await this.db.init(this)
}
Object.keys(globalDbInitializations).forEach(async (slug) => {
await globalDbInitializations[slug](this)
})
Object.keys(collectionDbInitializations).forEach(async (slug) => {
await collectionDbInitializations[slug](this)
})
if (!options.disableDBConnect && this.db.connect) {
await this.db.connect(this)
Object.keys(globalDbConnections).forEach(async (slug) => {
await globalDbConnections[slug](this)
})
Object.keys(collectionDbConnections).forEach(async (slug) => {
await collectionDbConnections[slug](this)
})
}
this.logger.info('Starting Payload...')

View File

@@ -13,7 +13,7 @@ type Args = {
}
export const deleteUserPreferences = async ({ collectionConfig, ids, payload, req }: Args) => {
if (collectionConfig.auth) {
await payload.db.deleteMany({
const deleteManyAuthDbArgs = {
collection: 'payload-preferences',
req,
where: {
@@ -26,13 +26,24 @@ export const deleteUserPreferences = async ({ collectionConfig, ids, payload, re
},
],
},
})
}
await payload.db.deleteMany({
if (collectionConfig?.db?.deleteMany) {
await collectionConfig.db.deleteMany(deleteManyAuthDbArgs)
} else {
await payload.db.deleteMany(deleteManyAuthDbArgs)
}
}
const deleteManyDbArgs = {
collection: 'payload-preferences',
req,
where: {
key: { in: ids.map((id) => `collection-${collectionConfig.slug}-${id}`) },
},
})
}
if (collectionConfig?.db?.deleteMany) {
await collectionConfig.db.deleteMany(deleteManyDbArgs)
} else {
await payload.db.deleteMany(deleteManyDbArgs)
}
}

View File

@@ -8,7 +8,8 @@ type Args = {
}
const docWithFilenameExists = async ({ collectionSlug, filename, req }: Args): Promise<boolean> => {
const doc = await req.payload.db.findOne({
const collectionConfig = req.payload.config.collections[collectionSlug]
const dbArgs = {
collection: collectionSlug,
req,
where: {
@@ -16,7 +17,13 @@ const docWithFilenameExists = async ({ collectionSlug, filename, req }: Args): P
equals: filename,
},
},
})
}
let doc: any
if (collectionConfig?.db?.findOne) {
doc = await collectionConfig.db.findOne(dbArgs)
} else {
doc = await req.payload.db.findOne(dbArgs)
}
if (doc) return true
return false

View File

@@ -8,9 +8,9 @@ type Args = {
slug: string
}
export const deleteCollectionVersions = async ({ id, payload, req, slug }: Args): Promise<void> => {
export const deleteCollectionVersions = async ({ id, slug, payload, req }: Args): Promise<void> => {
try {
await payload.db.deleteVersions({
const dbArgs = {
collection: slug,
req,
where: {
@@ -18,7 +18,13 @@ export const deleteCollectionVersions = async ({ id, payload, req, slug }: Args)
equals: id,
},
},
})
}
const collectionConfig = payload.config.collections[slug]
if (collectionConfig?.db?.deleteVersions) {
await collectionConfig.db.deleteVersions(dbArgs)
} else {
await payload.db.deleteVersions(dbArgs)
}
} catch (err) {
payload.logger.error(
`There was an error removing versions for the deleted ${slug} document with ID ${id}.`,

View File

@@ -72,10 +72,18 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
let versionDocs
if (entityType === 'global') {
if (entity?.db?.findGlobalVersions) {
versionDocs = (await entity.db.findGlobalVersions<T>(findVersionsArgs)).docs
} else {
versionDocs = (await req.payload.db.findGlobalVersions<T>(findVersionsArgs)).docs
}
} else {
if (entity?.db?.findVersions) {
versionDocs = (await entity.db.findVersions<T>(findVersionsArgs)).docs
} else {
versionDocs = (await req.payload.db.findVersions<T>(findVersionsArgs)).docs
}
}
let draft = versionDocs[0]

View File

@@ -33,24 +33,36 @@ export const enforceMaxVersions = async ({
equals: id,
}
const query = await payload.db.findVersions({
const findVersionsDbArgs = {
collection: collection.slug,
pagination: false,
req,
skip: max,
sort: '-updatedAt',
where,
})
}
let query: any
if (collection?.db?.findVersions) {
query = await collection.db.findVersions(findVersionsDbArgs)
} else {
query = await payload.db.findVersions(findVersionsDbArgs)
}
;[oldestAllowedDoc] = query.docs
} else if (global) {
const query = await payload.db.findGlobalVersions({
const findGlobalVersionsDbArgs = {
global: global.slug,
req,
skip: max,
sort: '-updatedAt',
where,
})
}
let query: any
if (global?.db?.findGlobalVersions) {
query = await global.db.findGlobalVersions(findGlobalVersionsDbArgs)
} else {
query = await payload.db.findGlobalVersions(findGlobalVersionsDbArgs)
}
;[oldestAllowedDoc] = query.docs
}
@@ -68,11 +80,17 @@ export const enforceMaxVersions = async ({
}
}
await payload.db.deleteVersions({
const deleteDbArgs = {
collection: collection?.slug,
req,
where: deleteQuery,
})
}
if (collection?.db?.deleteVersions) {
await collection.db.deleteVersions(deleteDbArgs)
} else {
await payload.db.deleteVersions(deleteDbArgs)
}
}
} catch (err) {
payload.logger.error(

View File

@@ -26,14 +26,21 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
const hasConfigDb = Object.keys(config?.db ? config?.db : {}).length > 0
if (config.versions?.drafts && !hasConfigDb) {
const { docs } = await payload.db.findVersions<T>({
const findVersionsDbArgs = {
collection: config.slug,
limit: 1,
pagination: false,
req,
sort: '-updatedAt',
where: { parent: { equals: id } },
})
}
let result: any
if (config?.db?.findVersions) {
result = await config.db.findVersions<T>(findVersionsDbArgs)
} else {
result = await payload.db.findVersions<T>(findVersionsDbArgs)
}
const docs = result.docs
;[latestVersion] = docs
}

View File

@@ -14,34 +14,45 @@ type Args = {
}
export const getLatestGlobalVersion = async ({
slug,
config,
locale,
payload,
req,
slug,
where,
}: Args): Promise<{ global: Document; globalExists: boolean }> => {
let latestVersion
const globalConfig = payload.config.globals?.find((cfg) => cfg.slug === slug)
if (config.versions?.drafts) {
// eslint-disable-next-line prefer-destructuring
latestVersion = (
await payload.db.findGlobalVersions({
const findVersionsDbArgs = {
global: slug,
limit: 1,
locale,
req,
sort: '-updatedAt',
})
).docs[0]
}
const global = await payload.db.findGlobal({
if (globalConfig?.db?.findGlobalVersions) {
latestVersion = (await globalConfig.db.findGlobalVersions(findVersionsDbArgs)).docs[0]
} else {
latestVersion = (await payload.db.findGlobalVersions(findVersionsDbArgs)).docs[0]
}
}
const findGlobalArgs = {
slug,
locale,
req,
slug,
where,
})
}
let global
if (globalConfig?.db?.findGlobal) {
global = await globalConfig.db.findGlobal(findGlobalArgs)
} else {
global = await payload.db.findGlobal(findGlobalArgs)
}
const globalExists = Boolean(global)
if (!latestVersion || (docHasTimestamps(global) && latestVersion.updatedAt < global.updatedAt)) {

View File

@@ -44,7 +44,7 @@ export const saveVersion = async ({
sort: '-updatedAt',
}
if (collection) {
;({ docs } = await payload.db.findVersions({
const findVersionsDbArgs = {
...findVersionArgs,
collection: collection.slug,
req,
@@ -53,13 +53,24 @@ export const saveVersion = async ({
equals: id,
},
},
}))
}
if (collection?.db?.findVersions) {
;({ docs } = await collection.db.findVersions(findVersionsDbArgs))
} else {
;({ docs } = await payload.db.findGlobalVersions({
;({ docs } = await payload.db.findVersions(findVersionsDbArgs))
}
} else {
const findGlobalVersionsDbArgs = {
...findVersionArgs,
global: global.slug,
req,
}))
}
if (global?.db?.findGlobalVersions) {
;({ docs } = await global.db.findGlobalVersions(findGlobalVersionsDbArgs))
} else {
;({ docs } = await payload.db.findGlobalVersions(findGlobalVersionsDbArgs))
}
}
const [latestVersion] = docs
@@ -80,24 +91,34 @@ export const saveVersion = async ({
}
if (collection) {
result = await payload.db.updateVersion({
const updateVersionDbArgs = {
...updateVersionArgs,
collection: collection.slug,
req,
})
}
if (collection?.db?.updateVersion) {
result = await collection.db.updateVersion(updateVersionDbArgs)
} else {
result = await payload.db.updateGlobalVersion({
result = await payload.db.updateVersion(updateVersionDbArgs)
}
} else {
const updateGlobalVersionDbArgs = {
...updateVersionArgs,
global: global.slug,
req,
})
}
if (global?.db?.updateGlobalVersion) {
result = await global.db.updateGlobalVersion(updateGlobalVersionDbArgs)
} else {
result = await payload.db.updateGlobalVersion(updateGlobalVersionDbArgs)
}
}
}
}
if (createNewVersion) {
if (collection) {
result = await payload.db.createVersion({
const createVersionDbArgs = {
autosave: Boolean(autosave),
collectionSlug: collection.slug,
createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now,
@@ -105,11 +126,16 @@ export const saveVersion = async ({
req,
updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(),
versionData,
})
}
if (collection?.db?.createVersion) {
result = await collection.db.createVersion(createVersionDbArgs)
} else {
result = await payload.db.createVersion(createVersionDbArgs)
}
}
if (global) {
result = await payload.db.createGlobalVersion({
const createGlobalVersionDbArgs = {
autosave: Boolean(autosave),
createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now,
globalSlug: global.slug,
@@ -117,7 +143,12 @@ export const saveVersion = async ({
req,
updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(),
versionData,
})
}
if (global?.db?.createGlobalVersion) {
result = await global.db.createGlobalVersion(createGlobalVersionDbArgs)
} else {
result = await payload.db.createGlobalVersion(createGlobalVersionDbArgs)
}
}
}
} catch (err) {

View File

@@ -4,35 +4,64 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
export const doc = {
id: -1,
id: 'abc123',
name: 'Collection 1',
customData: true,
}
export const doc2 = {
id: 'abc456',
name: 'Collection 2',
customData: true,
}
export const docs = [doc]
export const docs2 = [doc2]
const collectionWithDb = (collectionSlug: string): CollectionConfig => {
export let isInit = false
export const updateIsInit = (val: boolean) => {
isInit = val
return isInit
}
export let isConnected = false
export const updateIsConnect = (val: boolean) => {
isConnected = val
return isConnected
}
export const collectionSlug = 'collection-db'
export const collectionSlugRelated = 'collection-related-db'
const collectionWithDb = (slug: string, relatedCollection: string): CollectionConfig => {
return {
slug: collectionSlug,
slug,
db: {
init: async () => {
updateIsInit(true)
return Promise.resolve()
},
connect: async () => {
updateIsConnect(true)
return Promise.resolve()
},
// @ts-expect-error
create: () => {
return doc
return slug === collectionSlug ? doc : doc2
},
// @ts-expect-error
deleteOne: () => {
return docs
return slug === collectionSlug ? doc : doc2
},
// Only used in deleteUserPreferences on user collections
// @ts-expect-error
deleteMany: () => {
return docs
// Only used in deleteUserPreferences on user collections
return slug === collectionSlug ? doc : doc2
},
// @ts-expect-error
find: () => {
return { docs }
return { docs: slug === collectionSlug ? docs : docs2 }
},
// @ts-expect-error
findOne: () => {
return doc
return slug === collectionSlug ? doc : doc2
},
// @ts-expect-error
updateOne: () => {
@@ -44,14 +73,21 @@ const collectionWithDb = (collectionSlug: string): CollectionConfig => {
name: 'name',
type: 'text',
},
{
name: 'otherCollection',
type: 'relationship',
relationTo: [relatedCollection],
},
],
}
}
export const collectionSlug = 'collection-db'
export default buildConfigWithDefaults({
// @ts-expect-error
collections: [collectionWithDb(collectionSlug)],
collections: [
collectionWithDb(collectionSlug, collectionSlugRelated),
collectionWithDb(collectionSlugRelated, collectionSlug),
],
graphQL: {
schemaOutputFile: './test/collections-db/schema.graphql',
},

View File

@@ -1,7 +1,7 @@
import payload from '../../packages/payload/src'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { collectionSlug } from './config'
import { collectionSlug, isConnected, isInit } from './config'
import { doc } from './config'
require('isomorphic-fetch')
@@ -42,9 +42,17 @@ describe('Collection Database Operations', () => {
})
// --__--__--__--__--__--__--__--__--__
// Local API
// Custom Collection DB Operations
// --__--__--__--__--__--__--__--__--__
it('collection DB Init', () => {
expect(isInit).toEqual(true)
})
it('collection DB Connect', () => {
expect(isConnected).toEqual(true)
})
it('collection DB Create', async () => {
const result = await payload.create({
collection: collectionSlug,
@@ -107,4 +115,21 @@ describe('Collection Database Operations', () => {
expect(result.docs[0].customData).toEqual(doc.customData)
expect(result.errors).toHaveLength(0)
})
it.todo('support Relationships in Collection DB Operations')
it.skip('collection Relationship', async () => {
const result = await payload.create({
collection: collectionSlug,
data: {
id: doc.id,
otherCollection: {
id: doc.id,
},
},
})
expect(result.id).toEqual(doc.id)
expect(result.customData).toEqual(doc.customData)
expect(result.related.id).toEqual(doc.id)
})
})

View File

@@ -0,0 +1,101 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
collections: {
'collection-db': CollectionDb
'collection-related-db': CollectionRelatedDb
users: User
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
globals: {}
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collection-db".
*/
export interface CollectionDb {
id: string
name?: string | null
otherCollection?: {
relationTo: 'collection-related-db'
value: string | CollectionRelatedDb
} | null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collection-related-db".
*/
export interface CollectionRelatedDb {
id: string
name?: string | null
otherCollection?: {
relationTo: 'collection-db'
value: string | CollectionDb
} | null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string
updatedAt: string
createdAt: string
email: string
resetPasswordToken?: string | null
resetPasswordExpiration?: string | null
salt?: string | null
hash?: string | null
loginAttempts?: number | null
lockUntil?: string | null
password: string | null
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string
user: {
relationTo: 'users'
value: string | User
}
key?: string | null
value?:
| {
[k: string]: unknown
}
| unknown[]
| string
| number
| boolean
| null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string
name?: string | null
batch?: number | null
updatedAt: string
createdAt: string
}
declare module 'payload' {
export interface GeneratedTypes extends Config {}
}

78
test/globals-db/config.ts Normal file
View File

@@ -0,0 +1,78 @@
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
export const doc = {
id: -1,
customData: true,
}
export const docs = [doc]
export const globalSlug = 'global-db'
let noGlobal = false
export const updateNoGlobal = (val: boolean) => {
noGlobal = val
return noGlobal
}
export let isInit = false
export const updateIsInit = (val: boolean) => {
isInit = val
return isInit
}
export let isConnected = false
export const updateIsConnect = (val: boolean) => {
isConnected = val
return isConnected
}
export default buildConfigWithDefaults({
globals: [
{
slug: globalSlug,
db: {
init: async () => {
updateIsInit(true)
return Promise.resolve()
},
connect: async () => {
updateIsConnect(true)
return Promise.resolve()
},
// @ts-expect-error
createGlobal: ({ slug, data, req }) => {
return { ...doc, created: true }
},
// @ts-expect-error
updateGlobal: ({ slug, data, req }) => {
return { ...doc, updated: true }
},
// @ts-expect-error
findGlobal: () => {
if (noGlobal) return false
return doc
},
// @ts-expect-error
findGlobalVersions: () => {
return { docs }
},
},
fields: [
{
name: 'name',
type: 'text',
},
],
},
],
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
},
})

103
test/globals-db/int.spec.ts Normal file
View File

@@ -0,0 +1,103 @@
import payload from '../../packages/payload/src'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { globalSlug, isConnected, isInit, updateNoGlobal } from './config'
import { doc } from './config'
require('isomorphic-fetch')
let apiUrl
let jwt
const headers = {
'Content-Type': 'application/json',
}
const { email, password } = devUser
describe('Global Database Operations', () => {
// --__--__--__--__--__--__--__--__--__
// Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
apiUrl = `${serverURL}/api`
const response = await fetch(`${apiUrl}/users/login`, {
body: JSON.stringify({
email,
password,
}),
headers,
method: 'POST',
})
const data = await response.json()
jwt = data.token
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy(payload)
}
})
afterEach(() => {
updateNoGlobal(false)
})
// --__--__--__--__--__--__--__--__--__
// Custom Global DB Operations
// --__--__--__--__--__--__--__--__--__
it('global DB Init', () => {
expect(isInit).toEqual(true)
})
it('global DB Connect', () => {
expect(isConnected).toEqual(true)
})
it('global DB Create', async () => {
updateNoGlobal(true)
const result = await payload.updateGlobal({
slug: globalSlug,
data: {
id: doc.id,
},
})
expect(result.id).toEqual(doc.id)
expect(result.customData).toEqual(doc.customData)
})
it('global DB Update', async () => {
const result = await payload.updateGlobal({
slug: globalSlug,
data: {
id: doc.id,
},
})
expect(result.id).toEqual(doc.id)
expect(result.customData).toEqual(doc.customData)
expect(result.updated).toEqual(true)
})
it('global DB Find', async () => {
const result = await payload.findGlobal({
slug: globalSlug,
})
expect(result.id).toEqual(doc.id)
expect(result.customData).toEqual(doc.customData)
})
it('global DB find versions', async () => {
const result = await payload.findGlobalVersions({
slug: globalSlug,
})
expect(result.docs[0].id).toEqual(doc.id)
// @ts-expect-error
expect(result.docs[0].customData).toEqual(doc.customData)
})
})

View File

@@ -0,0 +1,83 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
collections: {
users: User
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
globals: {
'global-db': GlobalDb
}
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string
updatedAt: string
createdAt: string
email: string
resetPasswordToken?: string | null
resetPasswordExpiration?: string | null
salt?: string | null
hash?: string | null
loginAttempts?: number | null
lockUntil?: string | null
password: string | null
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string
user: {
relationTo: 'users'
value: string | User
}
key?: string | null
value?:
| {
[k: string]: unknown
}
| unknown[]
| string
| number
| boolean
| null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string
name?: string | null
batch?: number | null
updatedAt: string
createdAt: string
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global-db".
*/
export interface GlobalDb {
id: string
name?: string | null
updatedAt?: string | null
createdAt?: string | null
}
declare module 'payload' {
export interface GeneratedTypes extends Config {}
}