Skip to content

Commit

Permalink
feat: allows mongoose schemaOptions to be configured (#7099)
Browse files Browse the repository at this point in the history
## Description

This PR adds the ability to configure Mongoose's `schemaOptions`, which exposes more control about how Mongoose operates internally. For example, you can now disable `strict` mode in Mongoose to be able to preserve / surface data in MongoDB that is not reflected in Payload schemas.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
  • Loading branch information
jmikrut authored Jul 16, 2024
1 parent d475b16 commit 51474fa
Show file tree
Hide file tree
Showing 30 changed files with 702 additions and 270 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
"typescript.tsdk": "node_modules/typescript/lib",
// Load .git-blame-ignore-revs file
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"],
"jestrunner.jestCommand": "pnpm exec cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-deprecation\" node 'node_modules/jest/bin/jest.js'",
"jestrunner.debugOptions": {
"runtimeArgs": ["--experimental-vm-modules", "--no-deprecation"]
}
}
27 changes: 27 additions & 0 deletions docs/database/mongodb.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export default buildConfig({
| Option | Description |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `schemaOptions` | Customize schema options for all Mongoose schemas created internally. |
| `collections` | Options on a collection-by-collection basis. [More](#collections-options) |
| `globals` | Options for the Globals collection created by Payload. [More](#globals-options) |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
Expand All @@ -48,3 +51,27 @@ You can access Mongoose models as follows:
- Collection models - `payload.db.collections[myCollectionSlug]`
- Globals model - `payload.db.globals`
- Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]`

### Collections Options

You can configure the way the MongoDB adapter works on a collection-by-collection basis, including customizing Mongoose `schemaOptions` for each collection schema created.

Example:

```ts
const db = mongooseAdapter({
url: 'your-url-here',
collections: {
users: {
//
schemaOptions: {
strict: false,
}
}
}
})
```

### Global Options

Payload automatically creates a single `globals` collection that correspond with any Payload globals that you define. When you initialize the `mongooseAdapter`, you can specify settings here for your globals in a similar manner to how you can for collections above. Right now, the only property available is `schemaOptions` but more may be added in the future.
2 changes: 1 addition & 1 deletion packages/db-mongodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"bson-objectid": "2.0.4",
"deepmerge": "4.3.1",
"get-port": "5.1.1",
"http-status": "1.6.2",
"mongoose": "6.12.3",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",
"http-status": "1.6.2",
"uuid": "9.0.0"
},
"devDependencies": {
Expand Down
47 changes: 45 additions & 2 deletions packages/db-mongodb/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { TransactionOptions } from 'mongodb'
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
import type { ClientSession, ConnectOptions, Connection, SchemaOptions } from 'mongoose'
import type { Payload } from 'payload'
import type { BaseDatabaseAdapter } from 'payload/database'

Expand Down Expand Up @@ -42,27 +42,53 @@ export type { MigrateDownArgs, MigrateUpArgs } from './types'
export interface Args {
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
autoPluralization?: boolean
/** Define Mongoose options on a collection-by-collection basis.
*/
collections?: {
[slug: string]: {
/** Define Mongoose schema options for a given collection.
*/
schemaOptions?: SchemaOptions
}
}
/** Extra configuration options */
connectOptions?: ConnectOptions & {
/** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */
useFacet?: boolean
}
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
disableIndexHints?: boolean
/** Define Mongoose options for the globals collection.
*/
globals?: {
schemaOptions?: SchemaOptions
}
migrationDir?: string
/** Define default Mongoose schema options for all schemas created.
*/
schemaOptions?: SchemaOptions
transactionOptions?: TransactionOptions | false
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
url: false | string
}

export type MongooseAdapter = BaseDatabaseAdapter &
Args & {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: any
schemaOptions?: SchemaOptions
sessions: Record<number | string, ClientSession>
versions: {
[slug: string]: CollectionModel
Expand All @@ -74,13 +100,23 @@ type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter
declare module 'payload' {
export interface DatabaseAdapter
extends Omit<BaseDatabaseAdapter, 'sessions'>,
Omit<Args, 'migrationDir'> {
Omit<Args, 'collections' | 'globals' | 'migrationDir'> {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: any
schemaOptions?: SchemaOptions

sessions: Record<number | string, ClientSession>
transactionOptions: TransactionOptions
versions: {
Expand All @@ -91,9 +127,12 @@ declare module 'payload' {

export function mongooseAdapter({
autoPluralization = true,
collections,
connectOptions,
disableIndexHints = false,
globals,
migrationDir: migrationDirArg,
schemaOptions,
transactionOptions = {},
url,
}: Args): MongooseAdapterResult {
Expand All @@ -106,17 +145,21 @@ export function mongooseAdapter({

// Mongoose-specific
autoPluralization,
collectionOptions: collections || {},
collections: {},
connectOptions: connectOptions || {},
connection: undefined,
count,
disableIndexHints,
globals: undefined,
globalsOptions: globals || {},
mongoMemoryServer: undefined,
schemaOptions: schemaOptions || {},
sessions: {},
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
url,
versions: {},

// DatabaseAdapter
beginTransaction: transactionOptions ? beginTransaction : undefined,
commitTransaction,
Expand Down
12 changes: 8 additions & 4 deletions packages/db-mongodb/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ import { getDBName } from './utilities/getDBName'

export const init: Init = async function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config)
const schema = buildCollectionSchema(collection, this)

if (collection.versions) {
const versionModelName = getDBName({ config: collection, versions: true })

const versionCollectionFields = buildVersionCollectionFields(collection)

const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
const versionSchema = buildSchema(this, versionCollectionFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})

Expand Down Expand Up @@ -69,7 +71,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
}
})

const model = buildGlobalModel(this.payload.config)
const model = buildGlobalModel(this)
this.globals = model

this.payload.config.globals.forEach((global) => {
Expand All @@ -78,13 +80,15 @@ export const init: Init = async function init(this: MongooseAdapter) {

const versionGlobalFields = buildVersionGlobalFields(global)

const versionSchema = buildSchema(this.payload.config, versionGlobalFields, {
const versionSchema = buildSchema(this, versionGlobalFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.globalsOptions.schemaOptions || {}),
},
})

Expand Down
15 changes: 8 additions & 7 deletions packages/db-mongodb/src/models/buildCollectionSchema.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import type { PaginateOptions, Schema } from 'mongoose'
import type { SanitizedConfig } from 'payload/config'
import type { SanitizedCollectionConfig } from 'payload/types'

import paginate from 'mongoose-paginate-v2'

import type { MongooseAdapter } from '..'

import getBuildQueryPlugin from '../queries/buildQuery'
import buildSchema from './buildSchema'

const buildCollectionSchema = (
collection: SanitizedCollectionConfig,
config: SanitizedConfig,
schemaOptions = {},
adapter: MongooseAdapter,
): Schema => {
const schema = buildSchema(config, collection.fields, {
const schema = buildSchema(adapter, collection.fields, {
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
indexSortableFields: config.indexSortableFields,
indexSortableFields: adapter.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: collection.timestamps !== false,
...schemaOptions,
...adapter.schemaOptions,
...(adapter.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})

if (config.indexSortableFields && collection.timestamps !== false) {
if (adapter.payload.config.indexSortableFields && collection.timestamps !== false) {
schema.index({ updatedAt: 1 })
schema.index({ createdAt: 1 })
}
Expand Down
21 changes: 14 additions & 7 deletions packages/db-mongodb/src/models/buildGlobalModel.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import type { SanitizedConfig } from 'payload/config'

import mongoose from 'mongoose'

import type { MongooseAdapter } from '..'
import type { GlobalModel } from '../types'

import getBuildQueryPlugin from '../queries/buildQuery'
import buildSchema from './buildSchema'

export const buildGlobalModel = (config: SanitizedConfig): GlobalModel | null => {
if (config.globals && config.globals.length > 0) {
export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => {
if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) {
const globalsSchema = new mongoose.Schema(
{},
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
{
discriminatorKey: 'globalType',
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
timestamps: true,
},
)

globalsSchema.plugin(getBuildQueryPlugin())

const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel

Object.values(config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(config, globalConfig.fields, {
Object.values(adapter.payload.config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(adapter, globalConfig.fields, {
options: {
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
},
})
Globals.discriminator(globalConfig.slug, globalSchema)
Expand Down
Loading

0 comments on commit 51474fa

Please sign in to comment.