How to form a unique constraint with multiple fields in keystonejs?
const Redemption = list({
access: allowAll,
fields: {
program: relationship({ ref: 'Program', many: false }),
type: text({ label: 'Type', validation: { isRequired: true }, isIndexed: 'unique' }),
name: text({ label: 'name', validation: { isRequired: true }, isIndexed: 'unique' }),
},
//TODO: validation to check that program, type, name form a unique constraint
})
The best way I can think to do this currently is by adding another field to the list and concatenating your other values into it using a hook. This lets you enforces uniqueness across these three values (combine) at the DB-level.
The list config (and hook) might look like this:
Few things to note here:
isIndexed: 'unique'
config for the main three fields. If I understand the problem you're trying to solve correctly, you actually don't want these values (on their own) to be distinct.label
config from your example. The label defaults to the field key so, in your example, that config is redundant.compoundKey
field to store our composite values:ui
settings make the field appear as uneditable in the UIgraphql
settings block updates on the API too (you could do the same thing with access control but I think just omitting the field is a bit cleaner)resolveInput
hook as it lets you modify data before it's saved. To account for bothcreate
andupdate
operations we need to consult both theresolvedData
anditem
arguments -resolvedData
gives us new/updated values (butundefined
for any fields not being updated) anditem
give us the existing values in the DB. By combining values from both we can build the correct compound key each time and add it to the returned object.And it works! When creating a redemption we'll be prompted for the 3 main fields (the compound key is hidden):
And the compound key is correctly set from the values entered:
Editing any of the values also updates the compound key:
Note that the compound key field is read-only for clarity.
And if we check the resultant DB structure, we can see our unique constraint being enforced:
Attempting to violate the constraint will produce an error:
If you wanted to customise this behaviour you could implement a
validateInput
hook and return a customValidationFailureError
message.