Release: 5th October 2021
We're nearly at Gold Master status for the Keystone 6 GA release! Here's what's new:
- Fields Overhaul with lots of tweaks and additions 🚀
- Hook Updates bringing consolidation and clearer naming 🪝
- Removal of unused return types and unused values 🚫
- Renaming of options for consistency 📛
- Apollo Server Upgrade to version 3 👩🚀
- Improved Error Messaging 📟
- Performance updates for a faster Admin UI 🏃♀️
- REST API Example using the new
createContext
insideextendExpressApp
👩🏫 - Other Notable Changes to be aware of 🛎️
- Prisma Update from 2.x to 3.x 🗃
We’ve got further improvements to error messaging and performance to come in the next release!
This release contains breaking changes, please see below!
"@keystone-ui/fields": "5.0.0","@keystone-ui/notice": "4.1.0","@keystone-ui/options": "4.0.3","@keystone-ui/popover": "4.0.4","@keystone-ui/segmented-control": "5.0.0","@keystone-next/auth": "33.0.0","@keystone-next/cloudinary": "8.0.0","@keystone-next/fields-document": "10.0.0","@keystone-next/keystone": "26.0.1","@keystone-next/session-store-redis": "5.0.0",
Upgrade Guide 👇
Be sure to read the entire release notes below for everything you need to know about this new release.
The main things to keep in mind are:
defaultValue
config is now static, if you have dynamic defaults, use theresolveInput
hookisRequired
for fields is nowvalidation: { isRequired }
and we have new validation options such asmin
andmax
for some fields- We've made it clearer which fields are nullable in the database and tweaked the defaults, you now have more control but may need to migrate your database (more details below)
- The
hooks
API has new arguments, and we’ve consolidated update and delete events intobeforeOperation
andafterOperation
context.lists
has been renamed tocontext.query
Fields Overhaul 🚀
Keystone’s field types have been given a big overhaul - including several breaking changes, read on to understand what has changed.
Some of these API changes are breaking and you will be required to update your project.
text
defaultValue
is now a static valueisRequired
has moved tovalidation.isRequired
- Now requires that the value has a length of at least one
- New
validation.length.min
,validation.length.max
andvalidation.match
options
float
defaultValue
is now a static numberisRequired
has moved tovalidation.isRequired
- New
validation.min
andvalidation.max
options
integer
defaultValue
is now a static number or{ kind: 'autoincrement' }
isRequired
has moved tovalidation.isRequired
- New
validation.min
andvalidation.max
options
The autoIncrement
field has also been removed, use the integer field with a defaultValue
of { kind: 'autoincrement' }
.
decimal
defaultValue
is now a static number written as a stringisRequired
has moved tovalidation.isRequired
- Now requires the input isn't
NaN
- New
validation.min
andvalidation.max
options
timestamp
defaultValue
is now a static date time value in an ISO8601 string or{ kind: 'now' }
isRequired
has moved tovalidation.isRequired
The field can also be automatically set to the current time on a create/update by setting db.updatedAt: true
, this will add Prisma's @updatedAt
attribute to the field.
The timestamp
field also now uses a custom GraphQL scalar type named DateTime
which requires inputs as full ISO8601 date-time strings such as "2021-01-30T00:00:00.000Z"
. Using new Date().toISOString()
will give you a string in the correct format.
select
dataType
has been renamed totype
defaultValue
is now a static valueisRequired
has moved tovalidation.isRequired
The select
can now also be cleared in the Admin UI when ui.displayMode
is segmented-control
.
password
defaultValue
has been removedisRequired
has moved tovalidation.isRequired
rejectCommon
has moved tovalidation.rejectCommon
minLength
has moved tovalidation.length.min
- New
validation.length.max
andvalidation.match
options
validation.length.min
also must be 1
or above, though it still defaults to 8
.
If workFactor
is outside of the range of 6
to 31
, an error will now be thrown instead of the previous behaviour of clamping the value to 4
to 31
and warning if it's below 6
.
image
- Removed
isRequired
- Removed
defaultValue
If you were using these options, the same behaviour can be re-created with the validateInput
and resolveInput
hooks respectively.
file
- Removed
isRequired
- Removed
defaultValue
If you were using these options, the same behaviour can be re-created with the validateInput
and resolveInput
hooks respectively.
cloudinaryImage
- Removed
isRequired
- Removed
defaultValue
If you were using these options, the same behaviour can be re-created with the validateInput
and resolveInput
hooks respectively.
json
- Removed
isRequired
defaultValue
can no longer be dynamic in thejson
field
If you were using isRequired
, the same behaviour can be re-created with the validateInput
hook.
relationship
- Removed
defaultValue
- Removed undocumented
withMeta
option
To re-create defaultValue
, you can use resolveInput
though note that if you're using autoincrement ids, you need to return the id as number, not a string like you would provide to GraphQL, e.g. { connect: { id: 1 } }
rather than { connect: { id: "1" } }
.
If you were using withMeta: false
, please open an issue with your use case.
checkbox
The checkbox
field is now non-nullable in the database, if you need three states, you should use the select
field.
The field no longer accepts dynamic default values and it will default to false
unless a different defaultValue
is specified.
If you're using SQLite, Prisma will generate a migration that makes the column non-nullable and sets any rows that have null values to the defaultValue
.
If you're using PostgreSQL, Prisma will generate a migration but you'll need to modify it if you have nulls in a checkbox field. Keystone will say that the migration cannot be executed:
✨ Starting Keystone⭐️ Dev Server Ready on http://localhost:3000✨ Generating GraphQL and Prisma schemas✨ There has been a change to your Keystone schema that requires a migration⚠️ We found changes that cannot be executed:• Made the column `isAdmin` on table `User` required, but there are 1 existing NULL values.✔ Name of migration … make-is-admin-non-null✨ A migration has been created at migrations/20210906053141_make_is_admin_non_nullPlease edit the migration and run keystone-next dev again to apply the migration
The generated migration will look like this:
/*Warnings:- Made the column `isAdmin` on table `User` required. This step will fail if there are existing NULL values in that column.*/-- AlterTableALTER TABLE "User" ALTER COLUMN "isAdmin" SET NOT NULL,ALTER COLUMN "isAdmin" SET DEFAULT false;
To make it set any null values to false in your database, you need to modify it so that it looks like this but with the table and column names replaced.
ALTER TABLE "User" ALTER COLUMN "isAdmin" SET DEFAULT false;UPDATE "User" SET "isAdmin" = DEFAULT WHERE "isAdmin" IS NULL;ALTER TABLE "User" ALTER COLUMN "isAdmin" SET NOT NULL;
document
The document
field is now non-nullable in the database. The field no longer has defaultValue
or isRequired
options.
The same behaviour can be re-created with the validateInput
and resolveInput
hooks respectively.
The field will default to [{ "type": "paragraph", "children": [{ "text": "" }] }]
. The output type has also been renamed to ListKey_fieldKey_Document
If you're using SQLite, Prisma will generate a migration that makes the column non-nullable and sets any rows that have null values to an empty paragraph.
If you're using PostgreSQL, Prisma will generate a migration but you'll need to modify it if you have nulls in a document
field.
Keystone will say that the migration cannot be executed:
✨ Starting Keystone⭐️ Dev Server Ready on http://localhost:3000✨ Generating GraphQL and Prisma schemas✨ There has been a change to your Keystone schema that requires a migration⚠️ We found changes that cannot be executed:• Made the column `content` on table `Post` required, but there are 1 existing NULL values.✔ Name of migration … make_document_field_non_null✨ A migration has been created at migrations/20210915050920_make_document_field_non_nullPlease edit the migration and run keystone-next dev again to apply the migration
The generated migration will look like this:
/*Warnings:- Made the column `content` on table `Post` required. This step will fail if there are existing NULL values in that column.*/-- AlterTableALTER TABLE "Post" ALTER COLUMN "content" SET NOT NULL,ALTER COLUMN "content" SET DEFAULT E'[{"type":"paragraph","children":[{"text":""}]}]';
To make it set any null values to an empty paragraph in your database, you need to modify it so that it looks like this but with the table and column names replaced.
ALTER TABLE "Post" ALTER COLUMN "content" SET DEFAULT E'[{"type":"paragraph","children":[{"text":""}]}]';UPDATE "Post" SET "content" = DEFAULT WHERE "content" IS NULL;ALTER TABLE "Post" ALTER COLUMN "content" SET NOT NULL;
general
text
, float
, integer
, decimal
, timestamp
, select
, password
can be made non-nullable at the database-level with the isNullable
option which defaults to true
, except for text
which defaults to false
.
All fields above except password
can also have:
graphql.read.isNonNull
set if the field hasisNullable: false
and you have no read access control and you don't intend to add any in the future, it will make the GraphQL output field non-nullable.graphql.create.isNonNull
set if you have no create access control and you don't intend to add any in the future, it will make the GraphQL create input field non-nullable.
Keep in mind, checkbox
is now always non-nullable.
Hook Updates 🪝
We've consolidated the beforeChange
/beforeDelete
and afterChange
/afterDelete
hooks into beforeOperation
and afterOperation
.
Additionaly, we've renamed:
originalInput
for access control functions toinputData
originalInput
for hook functions toinputData
existingItem
for all hooks (exceptafterOperation
) toitem
existingItem
forafterOperation
tooriginalItem
updatedItem
forafterOperation
toitem
See the Hooks API docs for a complete reference for the updated API!
Removals 🚫
Some unused return types and unused values from enum definitions have been removed:
sendUserPasswordResetLink
andsendUserMagicAuthLink
only ever returnnull
, so now have return types ofBoolean
.UserAuthenticationWithPasswordFailure
no longer has acode
value.MagicLinkRedemptionErrorCode
andPasswordResetRedemptionErrorCode
no longer have the valuesIDENTITY_NOT_FOUND
,MULTIPLE_IDENTITY_MATCHES
,TOKEN_NOT_SET
, orTOKEN_MISMATCH
.
If you were using the createSchema
function, you can also remove the call to createSchema
and pass the lists directly to the lists
property.
Before:
import { config, createSchema, list } from '@keystone-next/keystone';config({lists: createSchema({User: list({ ... }),}),})
After:
import { config, list } from '@keystone-next/keystone';config({lists: {User: list({ ... }),},})
We've removed the deprecated config.db.adapter
option. Please use config.db.provider
to indicate the database provider for your system.
Finally, the internal protectIdentities
variable which was previously hardcoded to true
to protect user data, there are no immediate plans to implement this as a configurable option.
Renames 📇
The API context.lists
has been renamed to context.query
, and context.db.lists
has been renamed to context.db
.
The skipAccessControl
argument to createContext
to sudo
for consistency with context.sudo()
.
When using the experimental option config.experimental.generateNodeAPI
, the api
module now exports query
rather than lists
.
Renamed graphQLReturnFragment
to ui.query
in the virtual field options. The virtual field now checks if ui.query
is required for the GraphQL output type, and throws an error if it is missing. If you don't want want the Admin UI to fetch the field, you can set ui.itemView.fieldMode
and ui.listView.fieldMode
to 'hidden'
instead of providing ui.query
.
Also, we've moved the graphql
export of @keystone-next/keystone/types
to @keystone-next/keystone
.
Improved Error Messaging 📟
Error messages have been improved across the board, in summary:
- On startup, we now output where the GraphQL API is located
- If access is denied errors are thrown, we now state which operation isn't permitted
- Bad relationship field inputs are detected and outputed
- Clearer distinction if a user input is invalid
- If access control functions return invalid values we state what we got and what we expected
- Improved the error messages provided from the GraphQL API when extension code (e.g access control functions, hooks, etc) throws exceptions
- Better formatting of GraphQL error messages resulting from Prisma errors
- Clearer sign-in errors when logging into the Admin UI
- Improved error messages when returning bad filters from filter access control
Performance 🚅
Field Mode
We've optimised the itemView
field mode fetching - we now only fetch the item once to determine the field modes rather than once per field.
The Admin UI will also skip fetching fields that have a statically set itemView.fieldMode: 'hidden'
on the itemView
.
The id
argument to the KeystoneAdminUIFieldMeta.itemView
GraphQL field can now be omitted which will make KeystoneAdminUIFieldMetaItemView.fieldMode
return null when there isn't a static field mode.
The itemView
also no longer uses a sudo context when fetching the item in the KeystoneAdminUIFieldMetaItemView.fieldMode
. Previously, if someone had access to the Admin UI(ui.isAccessAllowed
) and a field had a itemView.fieldMode
function that used the item
argument, someone could bypass access control to determine whether or not an item with a given id exists.
Query Generation Performance
Query generation performance has been improved when querying single relationships without filter-based access control.
When resolving related items, we often have a foreign key available to us that we can use to help optimise the subsequent related item query. If, on top of this, there are no filter-based access control rules in place, then we can use the Prisma findUnique operation, which will group all the operations into a single database query. This solves the GraphQL N+1 query problem in this specific instance.
REST API Example 👩🏫
This release also adds createContext
to the extendExpressApp
function giving you access to the full context API from Keystone.
In a new example we demonstate how to create REST endpoints by extending Keystone's express app and using the Query API to execute queries against the schema.
You can find all of our examples in our examples folder on GitHub.
Other Notable Changes 🛎️
The
KeystoneAdminUIFieldMeta.isOrderable
andKeystoneAdminUIFieldMeta.isFilterable
fields are no longer statically resolvable and will now take into account the context/session. This also meansisOrderable
andisFilterable
are no longer accessible onuseList().fields[fieldKey].isOrderable/isFilterable
, they can be fetched through GraphQL if you need them in the Admin UI.The
sendItemMagicAuthLink
andsendItemPasswordResetLink
mutations now always returntrue
instead of always returningnull
Added support for dynamic
isFilterable
andisOrderable
field config values. If a function is provided for these config option, it will be dynamically evaluated each time the field is used for filtering and ordering, and an error will be returned if the function returnsfalse
.
Apollo Server Upgrade 👩🚀
Apollo Server has had a major upgrade to Version 3.
The Apollo documentation contains a full list of breaking changes introduced by this update.
You can configure the Apollo Server provided by Keystone using the graphql.apolloConfig
configuration option.
The most prominent change for most users will be that the GraphQL Playground has been replaced by the Apollo Sandbox.
If you prefer to keep the GraphQL Playground, you can configure your server by following these instructions.
Prisma Update 🗃
We've updated our Prisma dependency from 2.30.2
to 3.1.1
!
Note that Keystone continues to use the "binary" query engine, rather than the new "node-API" query engine, which is now the Prisma default. We are still performing tests to ensure that the node-API query engine will work well with Keystone.
Check out the Prisma releases page for more details on this major update.
Credits 💫
Exported Field types to help in updating contrib packages. Thanks @gautamsi!
Fixed remaining windows issue where it creates invalid import path. This removes some duplicate code which caused this. Thanks @gautamsi!
Added support for Prisma preview features via the
config.db.prismaPreviewFeatures
configuration option. Thanks @Nikitoring!
Complete Changelog 📜
You can also view the verbose release notes for this release on GitHub.