feat: add disableTransaction to local api (#8697)
This commit is contained in:
@@ -8,7 +8,7 @@ desc: Database transactions are fully supported within Payload.
|
|||||||
|
|
||||||
Database transactions allow your application to make a series of database changes in an all-or-nothing commit. Consider an HTTP request that creates a new **Order** and has an `afterChange` hook to update the stock count of related **Items**. If an error occurs when updating an **Item** and an HTTP error is returned to the user, you would not want the new **Order** to be persisted or any other items to be changed either. This kind of interaction with the database is handled seamlessly with transactions.
|
Database transactions allow your application to make a series of database changes in an all-or-nothing commit. Consider an HTTP request that creates a new **Order** and has an `afterChange` hook to update the stock count of related **Items**. If an error occurs when updating an **Item** and an HTTP error is returned to the user, you would not want the new **Order** to be persisted or any other items to be changed either. This kind of interaction with the database is handled seamlessly with transactions.
|
||||||
|
|
||||||
By default, Payload will use transactions for all operations, as long as it is supported by the configured database. Database changes are contained within all Payload operations and any errors thrown will result in all changes being rolled back without being committed. When transactions are not supported by the database, Payload will continue to operate as expected without them.
|
By default, Payload will use transactions for all data changing operations, as long as it is supported by the configured database. Database changes are contained within all Payload operations and any errors thrown will result in all changes being rolled back without being committed. When transactions are not supported by the database, Payload will continue to operate as expected without them.
|
||||||
|
|
||||||
<Banner type="info">
|
<Banner type="info">
|
||||||
<strong>Note:</strong>
|
<strong>Note:</strong>
|
||||||
@@ -114,3 +114,18 @@ standalonePayloadScript()
|
|||||||
## Disabling Transactions
|
## Disabling Transactions
|
||||||
|
|
||||||
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
|
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
|
||||||
|
|
||||||
|
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the local API by adding `disableTransaction: true` to the args. For example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await payload.update({
|
||||||
|
collection: 'posts',
|
||||||
|
data: {
|
||||||
|
some: 'data',
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
slug: { equals: 'my-slug' }
|
||||||
|
},
|
||||||
|
req: { disableTransaction: true },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|||||||
@@ -78,19 +78,20 @@ Both options function in exactly the same way outside of one having HMR support
|
|||||||
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
|
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
|
||||||
|
|
||||||
| Local Option | Description |
|
| Local Option | Description |
|
||||||
| ------------------ | ------------ |
|
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
|
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
|
||||||
| `data` | The data to use within the operation. Required for `create`, `update`. |
|
| `data` | The data to use within the operation. Required for `create`, `update`. |
|
||||||
| `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. |
|
| `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. |
|
||||||
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
|
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
|
||||||
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
|
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
|
||||||
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
|
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
|
||||||
| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents).|
|
| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents). |
|
||||||
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
|
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
|
||||||
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
|
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
|
||||||
| `pagination` | Set to false to return all documents and avoid querying for document counts. |
|
| `pagination` | Set to false to return all documents and avoid querying for document counts. |
|
||||||
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
|
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
|
||||||
| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
|
| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
|
||||||
|
| `disableTransaction` | When set to `true`, a [database transactions](../database/transactions) will not be initialized. |
|
||||||
|
|
||||||
_There are more options available on an operation by operation basis outlined below._
|
_There are more options available on an operation by operation basis outlined below._
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type Arguments<TSlug extends CollectionSlug> = {
|
|||||||
collection: Collection
|
collection: Collection
|
||||||
data: RequiredDataFromCollectionSlug<TSlug>
|
data: RequiredDataFromCollectionSlug<TSlug>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
disableVerificationEmail?: boolean
|
disableVerificationEmail?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
@@ -48,7 +49,7 @@ export const createOperation = async <TSlug extends CollectionSlug>(
|
|||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
|
|
||||||
ensureUsernameOrEmail<TSlug>({
|
ensureUsernameOrEmail<TSlug>({
|
||||||
authOptions: args.collection.config.auth,
|
authOptions: args.collection.config.auth,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { buildAfterOperation } from './utils.js'
|
|||||||
export type Arguments = {
|
export type Arguments = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
overrideLock?: boolean
|
overrideLock?: boolean
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
@@ -41,7 +42,7 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
|
|||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// beforeOperation - Collection
|
// beforeOperation - Collection
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { buildAfterOperation } from './utils.js'
|
|||||||
export type Arguments = {
|
export type Arguments = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
id: number | string
|
id: number | string
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
overrideLock?: boolean
|
overrideLock?: boolean
|
||||||
@@ -32,7 +33,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
|
|||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// beforeOperation - Collection
|
// beforeOperation - Collection
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { buildAfterOperation } from './utils.js'
|
|||||||
export type Arguments = {
|
export type Arguments = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
id: number | string
|
id: number | string
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
@@ -42,7 +43,7 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
|
|||||||
const operation = 'create'
|
const operation = 'create'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// beforeOperation - Collection
|
// beforeOperation - Collection
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type Options<TSlug extends CollectionSlug> = {
|
|||||||
context?: RequestContext
|
context?: RequestContext
|
||||||
data: RequiredDataFromCollectionSlug<TSlug>
|
data: RequiredDataFromCollectionSlug<TSlug>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
disableVerificationEmail?: boolean
|
disableVerificationEmail?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
fallbackLocale?: TypedLocale
|
fallbackLocale?: TypedLocale
|
||||||
@@ -38,6 +39,7 @@ export default async function createLocal<TSlug extends CollectionSlug>(
|
|||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
disableVerificationEmail,
|
disableVerificationEmail,
|
||||||
draft,
|
draft,
|
||||||
file,
|
file,
|
||||||
@@ -61,6 +63,7 @@ export default async function createLocal<TSlug extends CollectionSlug>(
|
|||||||
collection,
|
collection,
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
disableVerificationEmail,
|
disableVerificationEmail,
|
||||||
draft,
|
draft,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type BaseOptions<TSlug extends CollectionSlug> = {
|
|||||||
*/
|
*/
|
||||||
context?: RequestContext
|
context?: RequestContext
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
fallbackLocale?: TypedLocale
|
fallbackLocale?: TypedLocale
|
||||||
locale?: TypedLocale
|
locale?: TypedLocale
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
@@ -55,6 +56,7 @@ async function deleteLocal<TSlug extends CollectionSlug>(
|
|||||||
id,
|
id,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
overrideLock,
|
overrideLock,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
@@ -73,6 +75,7 @@ async function deleteLocal<TSlug extends CollectionSlug>(
|
|||||||
id,
|
id,
|
||||||
collection,
|
collection,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
overrideLock,
|
overrideLock,
|
||||||
req: await createLocalReq(options, payload),
|
req: await createLocalReq(options, payload),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type Options<TSlug extends CollectionSlug> = {
|
|||||||
*/
|
*/
|
||||||
context?: RequestContext
|
context?: RequestContext
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
fallbackLocale?: TypedLocale
|
fallbackLocale?: TypedLocale
|
||||||
id: number | string
|
id: number | string
|
||||||
@@ -32,6 +33,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
|
|||||||
id,
|
id,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
draft,
|
draft,
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
@@ -57,6 +59,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
|
|||||||
id,
|
id,
|
||||||
collection,
|
collection,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
draft,
|
draft,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
req,
|
req,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type BaseOptions<TSlug extends CollectionSlug> = {
|
|||||||
context?: RequestContext
|
context?: RequestContext
|
||||||
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
fallbackLocale?: TypedLocale
|
fallbackLocale?: TypedLocale
|
||||||
file?: File
|
file?: File
|
||||||
@@ -73,6 +74,7 @@ async function updateLocal<TSlug extends CollectionSlug>(
|
|||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
draft,
|
draft,
|
||||||
file,
|
file,
|
||||||
filePath,
|
filePath,
|
||||||
@@ -101,6 +103,7 @@ async function updateLocal<TSlug extends CollectionSlug>(
|
|||||||
collection,
|
collection,
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
draft,
|
draft,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
overrideLock,
|
overrideLock,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export type Arguments<TSlug extends CollectionSlug> = {
|
|||||||
collection: Collection
|
collection: Collection
|
||||||
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
disableVerificationEmail?: boolean
|
disableVerificationEmail?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -55,7 +56,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
|
|||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// beforeOperation - Collection
|
// beforeOperation - Collection
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export type Arguments<TSlug extends CollectionSlug> = {
|
|||||||
collection: Collection
|
collection: Collection
|
||||||
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
data: DeepPartial<RequiredDataFromCollectionSlug<TSlug>>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
disableVerificationEmail?: boolean
|
disableVerificationEmail?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
id: number | string
|
id: number | string
|
||||||
@@ -56,7 +57,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
|
|||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(args.req)
|
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// beforeOperation - Collection
|
// beforeOperation - Collection
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Args<TSlug extends GlobalSlug> = {
|
|||||||
autosave?: boolean
|
autosave?: boolean
|
||||||
data: DeepPartial<Omit<DataFromGlobalSlug<TSlug>, 'id'>>
|
data: DeepPartial<Omit<DataFromGlobalSlug<TSlug>, 'id'>>
|
||||||
depth?: number
|
depth?: number
|
||||||
|
disableTransaction?: boolean
|
||||||
draft?: boolean
|
draft?: boolean
|
||||||
globalConfig: SanitizedGlobalConfig
|
globalConfig: SanitizedGlobalConfig
|
||||||
overrideAccess?: boolean
|
overrideAccess?: boolean
|
||||||
@@ -42,6 +43,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
|
|||||||
slug,
|
slug,
|
||||||
autosave,
|
autosave,
|
||||||
depth,
|
depth,
|
||||||
|
disableTransaction,
|
||||||
draft: draftArg,
|
draft: draftArg,
|
||||||
globalConfig,
|
globalConfig,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
@@ -53,7 +55,7 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
|
|||||||
} = args
|
} = args
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = !disableTransaction && (await initTransaction(req))
|
||||||
|
|
||||||
let { data } = args
|
let { data } = args
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ export default buildConfigWithDefaults({
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'hasTransaction',
|
||||||
|
type: 'checkbox',
|
||||||
|
hooks: {
|
||||||
|
beforeChange: [({ req }) => !!req.transactionID],
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'throwAfterChange',
|
name: 'throwAfterChange',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
|||||||
@@ -458,6 +458,46 @@ describe('database', () => {
|
|||||||
).rejects.toThrow('Not Found')
|
).rejects.toThrow('Not Found')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('disableTransaction', () => {
|
||||||
|
let disabledTransactionPost
|
||||||
|
beforeAll(async () => {
|
||||||
|
disabledTransactionPost = await payload.create({
|
||||||
|
collection,
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
disableTransaction: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should not use transaction calling create() with disableTransaction', () => {
|
||||||
|
expect(disabledTransactionPost.hasTransaction).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('should not use transaction calling update() with disableTransaction', async () => {
|
||||||
|
const result = await payload.update({
|
||||||
|
collection,
|
||||||
|
id: disabledTransactionPost.id,
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
disableTransaction: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.hasTransaction).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('should not use transaction calling delete() with disableTransaction', async () => {
|
||||||
|
const result = await payload.delete({
|
||||||
|
collection,
|
||||||
|
id: disabledTransactionPost.id,
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
disableTransaction: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.hasTransaction).toBeFalsy()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user