Skip to content

Commit 82ba193

Browse files
authored
feat: add upsert to database interface and adapters (#8397)
- Adds the upsert method to the database interface - Adds a mongodb specific option to extend the updateOne to accept mongoDB Query Options (to pass `upsert: true`) - Added upsert method to all database adapters - Uses db.upsert in the payload preferences update operation Includes a test using payload-preferences
1 parent 06ea67a commit 82ba193

File tree

10 files changed

+101
-24
lines changed

10 files changed

+101
-24
lines changed

packages/db-mongodb/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { CollationOptions, TransactionOptions } from 'mongodb'
22
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
3-
import type { ClientSession, Connection, ConnectOptions } from 'mongoose'
4-
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
3+
import type { ClientSession, Connection, ConnectOptions, QueryOptions } from 'mongoose'
4+
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload, UpdateOneArgs } from 'payload'
55

66
import fs from 'fs'
77
import mongoose from 'mongoose'
@@ -36,6 +36,7 @@ import { updateGlobal } from './updateGlobal.js'
3636
import { updateGlobalVersion } from './updateGlobalVersion.js'
3737
import { updateOne } from './updateOne.js'
3838
import { updateVersion } from './updateVersion.js'
39+
import { upsert } from './upsert.js'
3940

4041
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
4142

@@ -124,6 +125,7 @@ declare module 'payload' {
124125
}[]
125126
sessions: Record<number | string, ClientSession>
126127
transactionOptions: TransactionOptions
128+
updateOne: (args: { options?: QueryOptions } & UpdateOneArgs) => Promise<Document>
127129
versions: {
128130
[slug: string]: CollectionModel
129131
}
@@ -191,6 +193,7 @@ export function mongooseAdapter({
191193
updateGlobalVersion,
192194
updateOne,
193195
updateVersion,
196+
upsert,
194197
})
195198
}
196199

packages/db-mongodb/src/updateOne.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { QueryOptions } from 'mongoose'
12
import type { PayloadRequest, UpdateOne } from 'payload'
23

34
import type { MongooseAdapter } from './index.js'
@@ -9,11 +10,20 @@ import { withSession } from './withSession.js'
910

1011
export const updateOne: UpdateOne = async function updateOne(
1112
this: MongooseAdapter,
12-
{ id, collection, data, locale, req = {} as PayloadRequest, where: whereArg },
13+
{
14+
id,
15+
collection,
16+
data,
17+
locale,
18+
options: optionsArgs = {},
19+
req = {} as PayloadRequest,
20+
where: whereArg,
21+
},
1322
) {
1423
const where = id ? { id: { equals: id } } : whereArg
1524
const Model = this.collections[collection]
16-
const options = {
25+
const options: QueryOptions = {
26+
...optionsArgs,
1727
...(await withSession(this, req)),
1828
lean: true,
1929
new: true,

packages/db-mongodb/src/upsert.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { PayloadRequest, Upsert } from 'payload'
2+
3+
import type { MongooseAdapter } from './index.js'
4+
5+
export const upsert: Upsert = async function upsert(
6+
this: MongooseAdapter,
7+
{ collection, data, locale, req = {} as PayloadRequest, where },
8+
) {
9+
return this.updateOne({ collection, data, locale, options: { upsert: true }, req, where })
10+
}

packages/db-postgres/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
150150
updateGlobalVersion,
151151
updateOne,
152152
updateVersion,
153+
upsert: updateOne,
153154
})
154155
}
155156

packages/db-sqlite/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
151151
updateGlobalVersion,
152152
updateOne,
153153
updateVersion,
154+
upsert: updateOne,
154155
})
155156
}
156157

packages/db-vercel-postgres/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
150150
updateGlobalVersion,
151151
updateOne,
152152
updateVersion,
153+
upsert: updateOne,
153154
})
154155
}
155156

packages/payload/src/database/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ export interface BaseDatabaseAdapter {
135135
updateOne: UpdateOne
136136

137137
updateVersion: UpdateVersion
138+
139+
upsert: Upsert
138140
}
139141

140142
export type Init = () => Promise<void> | void
@@ -380,6 +382,10 @@ export type UpdateOneArgs = {
380382
draft?: boolean
381383
joins?: JoinQuery
382384
locale?: string
385+
/**
386+
* Additional database adapter specific options to pass to the query
387+
*/
388+
options?: Record<string, unknown>
383389
req: PayloadRequest
384390
} & (
385391
| {
@@ -394,6 +400,17 @@ export type UpdateOneArgs = {
394400

395401
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
396402

403+
export type UpsertArgs = {
404+
collection: string
405+
data: Record<string, unknown>
406+
joins?: JoinQuery
407+
locale?: string
408+
req: PayloadRequest
409+
where: Where
410+
}
411+
412+
export type Upsert = (args: UpsertArgs) => Promise<Document>
413+
397414
export type DeleteOneArgs = {
398415
collection: string
399416
joins?: JoinQuery

packages/payload/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ export type {
821821
UpdateOneArgs,
822822
UpdateVersion,
823823
UpdateVersionArgs,
824+
Upsert,
824825
} from './database/types.js'
825826
export type { EmailAdapter as PayloadEmailAdapter, SendEmailOptions } from './email/types.js'
826827
export {

packages/payload/src/preferences/operations/update.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,10 @@ export async function update(args: PreferenceUpdateRequest) {
3535
value,
3636
}
3737

38-
let result
39-
40-
try {
41-
// try/catch because we attempt to update without first reading to check if it exists first to save on db calls
42-
result = await payload.db.updateOne({
43-
collection,
44-
data: preference,
45-
req,
46-
where,
47-
})
48-
} catch (err: unknown) {
49-
result = await payload.db.create({
50-
collection,
51-
data: preference,
52-
req,
53-
})
54-
}
55-
56-
return result
38+
return await payload.db.upsert({
39+
collection,
40+
data: preference,
41+
req,
42+
where,
43+
})
5744
}

test/auth/int.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('Auth', () => {
101101

102102
describe('logged in', () => {
103103
let token: string | undefined
104-
let loggedInUser: User | undefined
104+
let loggedInUser: undefined | User
105105

106106
beforeAll(async () => {
107107
const response = await restClient.POST(`/${slug}/login`, {
@@ -396,6 +396,52 @@ describe('Auth', () => {
396396
expect(result.docs).toHaveLength(1)
397397
})
398398

399+
it('should only have one preference per user per key', async () => {
400+
await restClient.POST(`/payload-preferences/${key}`, {
401+
body: JSON.stringify({
402+
value: { property: 'test', property2: 'test' },
403+
}),
404+
headers: {
405+
Authorization: `JWT ${token}`,
406+
},
407+
})
408+
await restClient.POST(`/payload-preferences/${key}`, {
409+
body: JSON.stringify({
410+
value: { property: 'updated', property2: 'updated' },
411+
}),
412+
headers: {
413+
Authorization: `JWT ${token}`,
414+
},
415+
})
416+
417+
const result = await payload.find({
418+
collection: 'payload-preferences',
419+
depth: 0,
420+
where: {
421+
and: [
422+
{
423+
key: { equals: key },
424+
},
425+
{
426+
'user.relationTo': {
427+
equals: 'users',
428+
},
429+
},
430+
{
431+
'user.value': {
432+
equals: loggedInUser.id,
433+
},
434+
},
435+
],
436+
},
437+
})
438+
439+
expect(result.docs[0].value.property).toStrictEqual('updated')
440+
expect(result.docs[0].value.property2).toStrictEqual('updated')
441+
442+
expect(result.docs).toHaveLength(1)
443+
})
444+
399445
it('should delete', async () => {
400446
const response = await restClient.DELETE(`/payload-preferences/${key}`, {
401447
headers: {

0 commit comments

Comments
 (0)