Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validateUniquess optional prismaClient parameter #5763

Merged
6 changes: 3 additions & 3 deletions docs/docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,14 +645,14 @@ So `validateUniqueness()` first tries to find a record with the given fields, an
> **Why use this when the database can verify uniqueness with a UNIQUE INDEX database constraint?**
>
> You may be in a situation where you can't have a unique index (supporting a legacy schema, perhaps), but still want to make sure the data is unique before proceeding. There is also the belief that you shouldn't have to count on the database to validate your data—that's a core concern of your business logic, and your business logic should live in your Services in a Redwood app.
>
> Another issue is that the error raised by Prisma when a record validates a unique index is swallowed by GraphQL and so you can't report it to the user (there are still ways around this, but it involves catching and re-throwing a different error). The error raised by `validateUniqueness()` is already safe-listed and allowed to be sent to the browser.
>
> Another issue is that the error raised by Prisma when a record validates a unique index is swallowed by GraphQL and so you can't report it to the user (there are still ways around this, but it involves catching and re-throwing a different error). The error raised by `validateUniqueness()` is already safe-listed and allowed to be sent to the browser.

#### Arguments

1. The name of the db table accessor that will be checked (what you would call on `db` in a normal Prisma call). If you'd call `db.user` then this value is `"user"`.
2. An object, containing the db fields/values to check for uniqueness, like `{ email: '[email protected]' }`. Can also include additional options explained below that provide for a narrower scope for uniqueness requirements, and a way for the record to identify itself and not create a false positive for an existing record.
3. [Optional] An object with options.
3. [Optional] An object with options. `message` - custom error message. `db` - custom instance of the PrismaClient to use
cannikin marked this conversation as resolved.
Show resolved Hide resolved
4. Callback to be invoked if record is found to be unique.

In its most basic usage, say you want to make sure that a user's email address is unique before creating the record. `input` is an object containing all the user fields to save to the database, including `email` which must be unique:
Expand Down
25 changes: 25 additions & 0 deletions packages/api/src/validations/__tests__/validations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1144,4 +1144,29 @@ describe('validateUniqueness', () => {
}
expect.assertions(1)
})

it('uses the given prisma client', async () => {
const mockFindFirstOther = jest.fn()
const mockPrismaClient = {
$transaction: async (func) =>
func({
user: {
findFirst: mockFindFirstOther,
},
}),
}
mockFindFirstOther.mockImplementation(() => null)

expect(mockFindFirstOther).not.toBeCalled()
await validateUniqueness(
'user',
{ email: '[email protected]' },
{ db: mockPrismaClient },
() => {
expect(true).toEqual(true)
}
)
expect(mockFindFirstOther).toBeCalled()
expect(mockFindFirst).not.toBeCalled()
})
})
28 changes: 25 additions & 3 deletions packages/api/src/validations/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ interface PresenceValidatorOptions extends WithOptionalMessage {
allowEmptyString?: boolean
}

interface UniquenessValidatorOptions extends WithRequiredMessage {}
interface UniquenessValidatorOptions extends WithOptionalMessage {
cannikin marked this conversation as resolved.
Show resolved Hide resolved
db?: PrismaClient
}
type UniquenessWhere = Record<'AND' | 'NOT', Array<Record<string, unknown>>>

interface ValidationRecipe {
Expand Down Expand Up @@ -612,6 +614,18 @@ export const validateWith = (func: () => void) => {
// }, (db) => {
// return db.create(data: { email })
// })
//
// const myCustomDb = new PrismaClient({
// log: emitLogLevels(['info', 'warn', 'error']),
// datasources: {
// db: {
// url: process.env.DATABASE_URL,
// },
// },
// })
// return validateUniqueness('user', { email: '[email protected]' }, { prismaClient: myCustomDb}, (db) => {
// return db.create(data: { email })
// })
export async function validateUniqueness(
model: string,
fields: Record<string, unknown>,
Expand All @@ -632,10 +646,10 @@ export async function validateUniqueness(
| ((tx: PrismaClient) => Promise<any>),
callback?: (tx: PrismaClient) => Promise<any>
): Promise<any> {
const db = new PrismaClient()
const { $self, $scope, ...rest } = fields
let options = {}
let options: UniquenessValidatorOptions = {}
let validCallback: (tx: PrismaClient) => Promise<any>
let db = null

if (typeof optionsOrCallback === 'function') {
validCallback = optionsOrCallback
Expand All @@ -644,6 +658,14 @@ export async function validateUniqueness(
validCallback = callback as (tx: PrismaClient) => Promise<any>
}

if (options.db) {
const { db: customDb, ...restOptions } = options
options = restOptions
db = customDb
} else {
db = new PrismaClient()
}

const where: UniquenessWhere = {
AND: [rest],
NOT: [],
Expand Down