+
+
{
// for debugging
false &&
@@ -345,9 +359,11 @@ export function DocumentEditorProvider({
export function DocumentEditorEditable({
autoFocus,
readOnly,
+ className,
}: {
autoFocus?: boolean;
readOnly?: boolean;
+ className?: string;
}) {
const editor = useSlate();
const componentBlocks = useContext(ComponentBlockContext);
@@ -401,6 +417,7 @@ export function DocumentEditorEditable({
readOnly={readOnly}
renderElement={renderElement}
renderLeaf={renderLeaf}
+ className={className}
/>
);
}
@@ -429,7 +446,9 @@ function Debugger() {
const orderedListStyles = ['lower-roman', 'decimal', 'lower-alpha'];
const unorderedListStyles = ['square', 'disc', 'circle'];
-let styles: any = {};
+let styles: any = {
+ flex: 1,
+};
let listDepth = 10;
diff --git a/packages/fields-document/src/relationship-data.tsx b/packages/fields-document/src/relationship-data.tsx
index 553ab970fab..ede96091c09 100644
--- a/packages/fields-document/src/relationship-data.tsx
+++ b/packages/fields-document/src/relationship-data.tsx
@@ -27,7 +27,7 @@ export function addRelationshipData(
let val = await graphQLAPI.run({
query: `query($ids: [ID!]!) {items:${
gqlNames(relationship.listKey).listQueryName
- }(where: {id_in:$ids}) {${idFieldAlias}:id ${labelFieldAlias}:${labelField}\n${
+ }(where: { id: { in: $ids } }) {${idFieldAlias}:id ${labelFieldAlias}:${labelField}\n${
relationship.selection || ''
}}}`,
variables: { ids },
diff --git a/packages/fields/CHANGELOG.md b/packages/fields/CHANGELOG.md
index 22b049863d6..cafa88f5de0 100644
--- a/packages/fields/CHANGELOG.md
+++ b/packages/fields/CHANGELOG.md
@@ -1,5 +1,122 @@
# @keystone-next/fields
+## 14.0.0
+
+### Major Changes
+
+- [#6280](https://github.com/keystonejs/keystone/pull/6280) [`e9f3c42d5`](https://github.com/keystonejs/keystone/commit/e9f3c42d5b9d42872cecbd18fbe9bf9d7d53ed82) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `gqlType` option to `autoIncrement` field type. The field type will now always be represented with an `Int` in GraphQL
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `_ListKeyMeta` and `_toManyRelationshipFieldMeta` fields. You should use `listKeyCount` and `toManyRelationshipFieldCount` instead
+
+- [#6266](https://github.com/keystonejs/keystone/pull/6266) [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Renamed `first` argument in find many queries to `take` to align with Prisma.
+
+ ```graphql
+ type Query {
+ users(
+ where: UserWhereInput! = {}
+ orderBy: [UserOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [User!]
+ # ...
+ }
+
+ type User {
+ # ...
+ posts(
+ where: PostWhereInput! = {}
+ orderBy: [PostOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [Post!]
+ # ...
+ }
+ ```
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `search` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `contains` filters instead.
+
+- [#6095](https://github.com/keystonejs/keystone/pull/6095) [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Updated filters to be nested instead of flattened and add top-level `NOT` operator. See the [Query Filter API docs](https://keystonejs.com/docs/apis/filters) and the upgrade guide for more information.
+
+ ```graphql
+ query {
+ posts(where: { title: { contains: "Something" } }) {
+ title
+ content
+ }
+ }
+ ```
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `sortBy` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `orderBy` instead.
+
+- [#6217](https://github.com/keystonejs/keystone/pull/6217) [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been renamed to `disconnect` in to-one relationship inputs and the old `disconnect` field has been removed. There are also seperate input types for create and update where the input for create doesn't have `disconnect`. It's also now required that if you provide a to-one relationship input, you must provide exactly one field to the input.
+
+ If you have a list called `Item`, the to-one relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToOneForCreateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ }
+ input ItemRelateToOneForUpdateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ disconnect: Boolean
+ }
+ ```
+
+* [#6224](https://github.com/keystonejs/keystone/pull/6224) [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been replaced by `set` in to-many relationship inputs, the equivalent to `disconnectAll: true` is now `set: []`. There are also seperate input types for create and update where the input for create doesn't have `disconnect` or `set`. The inputs in the lists in the input field are now also non-null.
+
+ If you have a list called `Item`, the to-many relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToManyForCreateInput {
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ input ItemRelateToManyForUpdateInput {
+ disconnect: [ItemWhereUniqueInput!]
+ set: [ItemWhereUniqueInput!]
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ ```
+
+- [#6211](https://github.com/keystonejs/keystone/pull/6211) [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The update mutations now accept `where` unique inputs instead of only an `id` and the `where` and `data` arguments are non-null.
+
+ If you have a list called `Item`, the update mutations now look like this:
+
+ ```graphql
+ type Mutation {
+ updateItem(where: ItemWhereUniqueInput!, data: ItemUpdateInput!): Item
+ updateItems(data: [ItemUpdateArgs!]!): [Item]
+ }
+
+ input ItemUpdateArgs {
+ where: ItemWhereUniqueInput!
+ data: ItemUpdateInput!
+ }
+ ```
+
+### Patch Changes
+
+- [#6237](https://github.com/keystonejs/keystone/pull/6237) [`4f4f0351a`](https://github.com/keystonejs/keystone/commit/4f4f0351a056dea9d1614aa2a3a4789d66bb402d) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Updated timestamp field to default time to 00:00 when no time is selected.
+
+* [#6197](https://github.com/keystonejs/keystone/pull/6197) [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The generated CRUD queries, and some of the input types, in the GraphQL API have been renamed.
+
+ If you have a list called `Item`, the query for multiple values, `allItems` will be renamed to `items`. The query for a single value, `Item`, will be renamed to `item`.
+
+ Also, the input type used in the `updateItems` mutation has been renamed from `ItemsUpdateInput` to `ItemUpdateArgs`.
+
+* Updated dependencies [[`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`1cbcf54cb`](https://github.com/keystonejs/keystone/commit/1cbcf54cb1206461866b582865e3b1a8fc728f18), [`a92169d04`](https://github.com/keystonejs/keystone/commit/a92169d04e5a1a98deb8e757b8eae3b06fc66450), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f), [`f3014a627`](https://github.com/keystonejs/keystone/commit/f3014a627060c7cd86440a6937da5caecfd023a0), [`092df6678`](https://github.com/keystonejs/keystone/commit/092df6678cea18d639be16ad250ec4ecc9250f5a), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`c2bb6a9a5`](https://github.com/keystonejs/keystone/commit/c2bb6a9a596fc52a3c61ec5d91c79758e417e61d), [`6da56b80e`](https://github.com/keystonejs/keystone/commit/6da56b80e03c748a621afcca6c1ec2887fef7271), [`697efa354`](https://github.com/keystonejs/keystone/commit/697efa354b1066b3d4b6eb757ca704b458f45e93), [`c7e331d90`](https://github.com/keystonejs/keystone/commit/c7e331d90a28b2ed8236100097cb8d34a11fabe2), [`3a7a06b2c`](https://github.com/keystonejs/keystone/commit/3a7a06b2cc6b5ea157d34d925b15494b471899eb), [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f), [`78dac764e`](https://github.com/keystonejs/keystone/commit/78dac764e1860b33f9e2bd8cee6015abeaaa5ec4), [`399561b27`](https://github.com/keystonejs/keystone/commit/399561b2769ddd8f3d3fdf29838f5784404bb053), [`9d361c1c8`](https://github.com/keystonejs/keystone/commit/9d361c1c8625e1390f837b7318b63547d686a63b), [`0dcb1c95b`](https://github.com/keystonejs/keystone/commit/0dcb1c95b5200750cc8649485425f2ae40d023a3), [`94435ffee`](https://github.com/keystonejs/keystone/commit/94435ffee765824091899242e4a2f73c7356b524), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`56044e2a4`](https://github.com/keystonejs/keystone/commit/56044e2a425f4256b66475fd3b1a6342cd6c3bf9), [`f46fd32b7`](https://github.com/keystonejs/keystone/commit/f46fd32b7047dbb5ea2566859f7ecee8db5b0b15), [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8), [`8ea4eed55`](https://github.com/keystonejs/keystone/commit/8ea4eed55367aaa213f6b4ffb7473087498e39ae), [`e3fe6498d`](https://github.com/keystonejs/keystone/commit/e3fe6498dc36203d8080dff3c2e0c25f6c98733e), [`6cd7ab78e`](https://github.com/keystonejs/keystone/commit/6cd7ab78e018fa0ffaddc1258426d23da19cd854), [`1030296d1`](https://github.com/keystonejs/keystone/commit/1030296d1f304dc44246e895089ac1f992e80590), [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c), [`8b2d179b2`](https://github.com/keystonejs/keystone/commit/8b2d179b2463d78b082182ca9afa8233109e0ba3), [`e3fefafcc`](https://github.com/keystonejs/keystone/commit/e3fefafcce6f8bf836c9bf0f4d931b8200ba41c7), [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda), [`686c0f1c4`](https://github.com/keystonejs/keystone/commit/686c0f1c4a1feb609e1584aa71738709bbbf984e), [`8187ea019`](https://github.com/keystonejs/keystone/commit/8187ea019a212874f3c602573af3382c6f3bd3b2), [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e), [`f5e64af37`](https://github.com/keystonejs/keystone/commit/f5e64af37df2eb460c89d89fa3c8924fb34970ed)]:
+ - @keystone-next/keystone@24.0.0
+ - @keystone-next/types@24.0.0
+ - @keystone-ui/toast@4.0.2
+ - @keystone-ui/segmented-control@4.0.2
+ - @keystone-next/admin-ui-utils@5.0.6
+ - @keystone-next/utils@1.0.4
+
## 13.0.0
### Major Changes
diff --git a/packages/fields/package.json b/packages/fields/package.json
index 3252816db45..27336167293 100644
--- a/packages/fields/package.json
+++ b/packages/fields/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-next/fields",
- "version": "13.0.0",
+ "version": "14.0.0",
"license": "MIT",
"main": "dist/fields.cjs.js",
"module": "dist/fields.esm.js",
@@ -11,11 +11,11 @@
"mime": "^2.5.2"
},
"dependencies": {
- "@babel/runtime": "^7.14.8",
- "@keystone-next/admin-ui-utils": "^5.0.5",
- "@keystone-next/keystone": "^23.0.0",
- "@keystone-next/types": "^23.0.0",
- "@keystone-next/utils": "^1.0.3",
+ "@babel/runtime": "^7.15.3",
+ "@keystone-next/admin-ui-utils": "^5.0.6",
+ "@keystone-next/keystone": "^24.0.0",
+ "@keystone-next/types": "^24.0.0",
+ "@keystone-next/utils": "^1.0.4",
"@keystone-ui/button": "^5.0.0",
"@keystone-ui/core": "^3.1.1",
"@keystone-ui/fields": "^4.1.2",
@@ -23,11 +23,11 @@
"@keystone-ui/loading": "^4.0.0",
"@keystone-ui/modals": "^4.0.0",
"@keystone-ui/pill": "^5.0.0",
- "@keystone-ui/segmented-control": "^4.0.1",
- "@keystone-ui/toast": "^4.0.1",
+ "@keystone-ui/segmented-control": "^4.0.2",
+ "@keystone-ui/toast": "^4.0.2",
"@keystone-ui/tooltip": "^4.0.1",
"@types/bcryptjs": "^2.4.2",
- "@types/react": "^17.0.15",
+ "@types/react": "^17.0.18",
"bcryptjs": "^2.4.3",
"bytes": "^3.1.0",
"copy-to-clipboard": "^3.3.1",
diff --git a/packages/fields/src/tests/test-fixtures.ts b/packages/fields/src/tests/test-fixtures.ts
index b83e5225be1..0c72dadf71c 100644
--- a/packages/fields/src/tests/test-fixtures.ts
+++ b/packages/fields/src/tests/test-fixtures.ts
@@ -63,20 +63,20 @@ export const filterTests = (withKeystone: any) => {
);
test(
- 'Filter: id',
+ 'Filter: equals',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
const id = IDs['person2'];
- return match(context, { id }, [{ id: IDs['person2'], name: 'person2' }]);
+ return match(context, { id: { equals: id } }, [{ id: IDs['person2'], name: 'person2' }]);
})
);
test(
- 'Filter: id_not',
+ 'Filter: not equals',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
const id = IDs['person2'];
- return match(context, { id_not: id }, [
+ return match(context, { id: { not: { equals: id } } }, [
{ id: IDs['person1'], name: 'person1' },
{ id: IDs['person3'], name: 'person3' },
{ id: IDs['person4'], name: 'person4' },
@@ -85,12 +85,12 @@ export const filterTests = (withKeystone: any) => {
);
test(
- 'Filter: id_in',
+ 'Filter: in',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
const id2 = IDs['person2'];
const id3 = IDs['person3'];
- return match(context, { id_in: [id2, id3] }, [
+ return match(context, { id: { in: [id2, id3] } }, [
{ id: IDs['person2'], name: 'person2' },
{ id: IDs['person3'], name: 'person3' },
]);
@@ -98,27 +98,27 @@ export const filterTests = (withKeystone: any) => {
);
test(
- 'Filter: id_in - empty list',
+ 'Filter: in - empty list',
withKeystone(({ context }: { context: KeystoneContext }) => {
- return match(context, { id_in: [] }, []);
+ return match(context, { id: { in: [] } }, []);
})
);
test(
- 'Filter: id_in - missing id',
+ 'Filter: in - missing id',
withKeystone(({ context }: { context: KeystoneContext }) => {
const fakeID = 'cdafasdfasd';
- return match(context, { id_in: [fakeID] }, []);
+ return match(context, { id: { in: [fakeID] } }, []);
})
);
test(
- 'Filter: id_not_in',
+ 'Filter: not in',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
const id2 = IDs['person2'];
const id3 = IDs['person3'];
- return match(context, { id_not_in: [id2, id3] }, [
+ return match(context, { id: { not: { in: [id2, id3] } } }, [
{ id: IDs['person1'], name: 'person1' },
{ id: IDs['person4'], name: 'person4' },
]);
@@ -126,10 +126,10 @@ export const filterTests = (withKeystone: any) => {
);
test(
- 'Filter: id_not_in - empty list',
+ 'Filter: not in - empty list',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
- return match(context, { id_not_in: [] }, [
+ return match(context, { id: { not: { in: [] } } }, [
{ id: IDs['person1'], name: 'person1' },
{ id: IDs['person2'], name: 'person2' },
{ id: IDs['person3'], name: 'person3' },
@@ -139,11 +139,11 @@ export const filterTests = (withKeystone: any) => {
);
test(
- 'Filter: id_not_in - missing id',
+ 'Filter: not in - missing id',
withKeystone(async ({ context }: { context: KeystoneContext }) => {
const IDs = await getIDs(context);
const fakeID = 'cdafasdfasd';
- return match(context, { id_not_in: [fakeID] }, [
+ return match(context, { id: { not: { in: [fakeID] } } }, [
{ id: IDs['person1'], name: 'person1' },
{ id: IDs['person2'], name: 'person2' },
{ id: IDs['person3'], name: 'person3' },
diff --git a/packages/fields/src/types/autoIncrement/index.ts b/packages/fields/src/types/autoIncrement/index.ts
index ceb6ef68e0f..a4484ec6a65 100644
--- a/packages/fields/src/types/autoIncrement/index.ts
+++ b/packages/fields/src/types/autoIncrement/index.ts
@@ -7,6 +7,7 @@ import {
legacyFilters,
orderDirectionEnum,
schema,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -17,7 +18,6 @@ export type AutoIncrementFieldConfig
= {}): FieldTypeFunc =>
meta => {
- const type = meta.fieldKey === 'id' || gqlType === 'ID' ? schema.ID : schema.Int;
- const __legacy = {
- isRequired,
- defaultValue,
- filters: {
- fields: {
- ...legacyFilters.fields.equalityInputFields(meta.fieldKey, type),
- ...legacyFilters.fields.orderingInputFields(meta.fieldKey, type),
- ...legacyFilters.fields.inInputFields(meta.fieldKey, type),
- },
- impls: {
- ...equalityConditions(meta.fieldKey, x => Number(x) || -1),
- ...legacyFilters.impls.orderingConditions(meta.fieldKey, x => Number(x) || -1),
- ...inConditions(meta.fieldKey, x => x.map((xx: any) => Number(xx) || -1)),
- },
- },
- };
- if (meta.fieldKey === 'id') {
- return fieldType({
- kind: 'scalar',
- mode: 'required',
- scalar: 'Int',
- default: { kind: 'autoincrement' },
- })({
- ...config,
- input: {
- // TODO: fix the fact that TS did not catch that a resolver is needed here
- uniqueWhere: {
- arg: schema.arg({ type }),
- resolve(value) {
- return Number(value);
- },
- },
- orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
- },
- output: schema.field({
- type: schema.nonNull(schema.ID),
- resolve({ value }) {
- return value.toString();
- },
- }),
- views: resolveView('integer/views'),
- __legacy,
- });
- }
- const inputResolver = (val: number | string | null | undefined) => {
- if (val == null) {
- return val;
- }
- return Number(val);
- };
return fieldType({
kind: 'scalar',
mode: 'optional',
@@ -90,20 +38,35 @@ export const autoIncrement =
})({
...config,
input: {
- uniqueWhere: isUnique ? { arg: schema.arg({ type }), resolve: x => Number(x) } : undefined,
- create: { arg: schema.arg({ type }), resolve: inputResolver },
- update: { arg: schema.arg({ type }), resolve: inputResolver },
+ where: {
+ arg: schema.arg({
+ type: filters[meta.provider].Int.optional,
+ }),
+ resolve: filters.resolveCommon,
+ },
+ uniqueWhere: isUnique ? { arg: schema.arg({ type: schema.Int }) } : undefined,
+ create: { arg: schema.arg({ type: schema.Int }) },
+ update: { arg: schema.arg({ type: schema.Int }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
},
- output: schema.field({
- type,
- resolve({ value }) {
- if (value === null) return null;
- return type === schema.ID ? value.toString() : value;
- },
- }),
+ output: schema.field({ type: schema.Int }),
views: resolveView('integer/views'),
- __legacy,
+ __legacy: {
+ isRequired,
+ defaultValue,
+ filters: {
+ fields: {
+ ...legacyFilters.fields.equalityInputFields(meta.fieldKey, schema.Int),
+ ...legacyFilters.fields.orderingInputFields(meta.fieldKey, schema.Int),
+ ...legacyFilters.fields.inInputFields(meta.fieldKey, schema.Int),
+ },
+ impls: {
+ ...equalityConditions(meta.fieldKey, x => Number(x) || -1),
+ ...legacyFilters.impls.orderingConditions(meta.fieldKey, x => Number(x) || -1),
+ ...inConditions(meta.fieldKey, x => x.map((xx: any) => Number(xx) || -1)),
+ },
+ },
+ },
});
};
diff --git a/packages/fields/src/types/autoIncrement/tests/test-fixtures.ts b/packages/fields/src/types/autoIncrement/tests/test-fixtures.ts
index 80796704315..c4659c1f360 100644
--- a/packages/fields/src/types/autoIncrement/tests/test-fixtures.ts
+++ b/packages/fields/src/types/autoIncrement/tests/test-fixtures.ts
@@ -2,13 +2,10 @@ import { KeystoneContext } from '@keystone-next/types';
import { text } from '../../text';
import { autoIncrement } from '..';
-type MatrixValue = typeof testMatrix[number];
-
export const name = 'AutoIncrement';
export const typeFunction = autoIncrement;
-export const testMatrix = ['ID', 'Int'] as const;
-export const exampleValue = (matrixValue: MatrixValue) => (matrixValue === 'ID' ? '35' : 35);
-export const exampleValue2 = (matrixValue: MatrixValue) => (matrixValue === 'ID' ? '36' : 36);
+export const exampleValue = () => 35;
+export const exampleValue2 = () => 36;
export const supportsUnique = true;
export const fieldName = 'orderNumber';
export const skipCreateTest = false;
@@ -16,21 +13,9 @@ export const skipUpdateTest = true;
export const unSupportedAdapterList = ['sqlite'];
-// Be default, `AutoIncrement` are read-only. But for `isRequired` test purpose, we need to bypass these restrictions.
-export const fieldConfig = (matrixValue: MatrixValue) => ({
- gqlType: matrixValue,
- access: { create: true, update: true },
-});
-
-export const getTestFields = (matrixValue: MatrixValue) => ({
+export const getTestFields = () => ({
name: text(),
- orderNumber: autoIncrement({
- // The gqlType argument is not currently available on the type.
- // This will be reviewed when we do our full field type API review
- // @ts-ignore
- gqlType: matrixValue,
- access: { create: true },
- }),
+ orderNumber: autoIncrement(),
});
export const initItems = () => {
@@ -45,32 +30,20 @@ export const initItems = () => {
];
};
-export const storedValues = (matrixValue: MatrixValue) =>
- matrixValue === 'ID'
- ? [
- { name: 'product1', orderNumber: '1' },
- { name: 'product2', orderNumber: '2' },
- { name: 'product3', orderNumber: '3' },
- { name: 'product4', orderNumber: '4' },
- { name: 'product5', orderNumber: '5' },
- { name: 'product6', orderNumber: '6' },
- { name: 'product7', orderNumber: '7' },
- ]
- : [
- { name: 'product1', orderNumber: 1 },
- { name: 'product2', orderNumber: 2 },
- { name: 'product3', orderNumber: 3 },
- { name: 'product4', orderNumber: 4 },
- { name: 'product5', orderNumber: 5 },
- { name: 'product6', orderNumber: 6 },
- { name: 'product7', orderNumber: 7 },
- ];
+export const storedValues = () => [
+ { name: 'product1', orderNumber: 1 },
+ { name: 'product2', orderNumber: 2 },
+ { name: 'product3', orderNumber: 3 },
+ { name: 'product4', orderNumber: 4 },
+ { name: 'product5', orderNumber: 5 },
+ { name: 'product6', orderNumber: 6 },
+ { name: 'product7', orderNumber: 7 },
+];
export const supportedFilters = () => [];
-export const filterTests = (withKeystone: (arg: any) => any, matrixValue: MatrixValue) => {
- const _storedValues = storedValues(matrixValue);
- const _f = matrixValue === 'ID' ? (x: any) => x.toString() : (x: any) => x;
+export const filterTests = (withKeystone: (arg: any) => any) => {
+ const _storedValues = storedValues();
const match = async (context: KeystoneContext, where: Record, expected: any[]) =>
expect(
await context.lists.Test.findMany({
@@ -81,93 +54,79 @@ export const filterTests = (withKeystone: (arg: any) => any, matrixValue: Matrix
).toEqual(expected.map(i => _storedValues[i]));
test(
- 'Filter: orderNumber',
- withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber: _f(1) }, [0])
- )
- );
-
- test(
- 'Filter: orderNumber_not',
- withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_not: _f(1) }, [1, 2, 3, 4, 5, 6])
- )
- );
-
- test(
- 'Filter: orderNumber_not null',
+ 'Filter: equals',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_not: null }, [0, 1, 2, 3, 4, 5, 6])
+ match(context, { orderNumber: { equals: 1 } }, [0])
)
);
test(
- 'Filter: orderNumber_lt',
+ 'Filter: not',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_lt: _f(2) }, [0])
+ match(context, { orderNumber: { not: { equals: 1 } } }, [1, 2, 3, 4, 5, 6])
)
);
test(
- 'Filter: orderNumber_lte',
+ 'Filter: not null',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_lte: _f(2) }, [0, 1])
+ match(context, { orderNumber: { not: null } }, [0, 1, 2, 3, 4, 5, 6])
)
);
test(
- 'Filter: orderNumber_gt',
+ 'Filter: lt',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_gt: _f(2) }, [2, 3, 4, 5, 6])
+ match(context, { orderNumber: { lt: 2 } }, [0])
)
);
test(
- 'Filter: orderNumber_gte',
+ 'Filter: lte',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_gte: _f(2) }, [1, 2, 3, 4, 5, 6])
+ match(context, { orderNumber: { lte: 2 } }, [0, 1])
)
);
test(
- 'Filter: orderNumber_in (empty list)',
+ 'Filter: gt',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_in: [] }, [])
+ match(context, { orderNumber: { gt: 2 } }, [2, 3, 4, 5, 6])
)
);
test(
- 'Filter: orderNumber_not_in (empty list)',
+ 'Filter: gte',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_not_in: [] }, [0, 1, 2, 3, 4, 5, 6])
+ match(context, { orderNumber: { gte: 2 } }, [1, 2, 3, 4, 5, 6])
)
);
test(
- 'Filter: orderNumber_in',
+ 'Filter: in (empty list)',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_in: ([1, 2, 3] as const).map(_f) }, [0, 1, 2])
+ match(context, { orderNumber: { in: [] } }, [])
)
);
test(
- 'Filter: orderNumber_not_in',
+ 'Filter: not in (empty list)',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_not_in: [1, 2, 3].map(_f) }, [3, 4, 5, 6])
+ match(context, { orderNumber: { notIn: [] } }, [0, 1, 2, 3, 4, 5, 6])
)
);
test(
- 'Filter: orderNumber_in null',
+ 'Filter: in',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_in: [null] }, [])
+ match(context, { orderNumber: { in: [1, 2, 3] } }, [0, 1, 2])
)
);
test(
- 'Filter: orderNumber_not_in null',
+ 'Filter: not in',
withKeystone(({ context }: { context: KeystoneContext }) =>
- match(context, { orderNumber_not_in: [null] }, [0, 1, 2, 3, 4, 5, 6])
+ match(context, { orderNumber: { notIn: [1, 2, 3] } }, [3, 4, 5, 6])
)
);
};
diff --git a/packages/fields/src/types/checkbox/index.ts b/packages/fields/src/types/checkbox/index.ts
index 1e320de29a6..dd2b36fb891 100644
--- a/packages/fields/src/types/checkbox/index.ts
+++ b/packages/fields/src/types/checkbox/index.ts
@@ -7,6 +7,7 @@ import {
legacyFilters,
orderDirectionEnum,
schema,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
@@ -30,6 +31,10 @@ export const checkbox =
return fieldType({ kind: 'scalar', mode: 'optional', scalar: 'Boolean' })({
...config,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].Boolean.optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: schema.Boolean }) },
update: { arg: schema.arg({ type: schema.Boolean }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
diff --git a/packages/fields/src/types/checkbox/views/index.tsx b/packages/fields/src/types/checkbox/views/index.tsx
index 010f2f4a200..0b40d55766e 100644
--- a/packages/fields/src/types/checkbox/views/index.tsx
+++ b/packages/fields/src/types/checkbox/views/index.tsx
@@ -72,9 +72,8 @@ export const controller = (config: FieldControllerConfig): CheckboxController =>
Filter() {
return null;
},
- graphql({ type, value }) {
- const key = type === 'is' ? `${config.path}` : `${config.path}_${type}`;
- return { [key]: value };
+ graphql({ type }) {
+ return { [config.path]: { equals: type === 'is' } };
},
Label({ label }) {
return label.toLowerCase();
diff --git a/packages/fields/src/types/decimal/index.ts b/packages/fields/src/types/decimal/index.ts
index a4b234cdda0..fdfdcaa1abf 100644
--- a/packages/fields/src/types/decimal/index.ts
+++ b/packages/fields/src/types/decimal/index.ts
@@ -8,6 +8,7 @@ import {
Decimal,
legacyFilters,
FieldDefaultValue,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -66,6 +67,10 @@ export const decimal =
})({
...config,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].Decimal.optional }),
+ resolve: filters.resolveCommon,
+ },
create: {
arg: schema.arg({ type: schema.String }),
resolve(val) {
diff --git a/packages/fields/src/types/decimal/views/index.tsx b/packages/fields/src/types/decimal/views/index.tsx
index 2d135af2d15..7e8b1cea9b8 100644
--- a/packages/fields/src/types/decimal/views/index.tsx
+++ b/packages/fields/src/types/decimal/views/index.tsx
@@ -70,14 +70,16 @@ export const controller = (config: Config): FieldController => {
},
graphql: ({ type, value }) => {
- const key = type === 'is' ? config.path : `${config.path}_${type}`;
const valueWithoutWhitespace = value.replace(/\s/g, '');
-
- return {
- [key]: ['in', 'not_in'].includes(type)
- ? valueWithoutWhitespace.split(',').map(i => i)
- : valueWithoutWhitespace,
- };
+ const parsed =
+ type === 'in' || type === 'not_in'
+ ? valueWithoutWhitespace.split(',')
+ : valueWithoutWhitespace;
+ if (type === 'not') {
+ return { [config.path]: { not: { equals: parsed } } };
+ }
+ const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type;
+ return { [config.path]: { [key]: parsed } };
},
Label({ label, value, type }) {
let renderedValue = value;
diff --git a/packages/fields/src/types/float/index.ts b/packages/fields/src/types/float/index.ts
index c1baa891d85..a7522d3d017 100644
--- a/packages/fields/src/types/float/index.ts
+++ b/packages/fields/src/types/float/index.ts
@@ -7,6 +7,7 @@ import {
orderDirectionEnum,
legacyFilters,
FieldDefaultValue,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -36,6 +37,10 @@ export const float =
})({
...config,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].Float.optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: schema.Float }) },
update: { arg: schema.arg({ type: schema.Float }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
diff --git a/packages/fields/src/types/float/views/index.tsx b/packages/fields/src/types/float/views/index.tsx
index 42c845075e1..8dd79257d11 100644
--- a/packages/fields/src/types/float/views/index.tsx
+++ b/packages/fields/src/types/float/views/index.tsx
@@ -68,14 +68,16 @@ export const controller = (config: FieldControllerConfig): FieldController {
- const key = type === 'is' ? config.path : `${config.path}_${type}`;
const valueWithoutWhitespace = value.replace(/\s/g, '');
-
- return {
- [key]: ['in', 'not_in'].includes(type)
- ? valueWithoutWhitespace.split(',').map(i => parseFloat(i))
- : parseFloat(valueWithoutWhitespace),
- };
+ const parsed =
+ type === 'in' || type === 'not_in'
+ ? valueWithoutWhitespace.split(',').map(x => parseFloat(x))
+ : parseFloat(valueWithoutWhitespace);
+ if (type === 'not') {
+ return { [config.path]: { not: { equals: parsed } } };
+ }
+ const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type;
+ return { [config.path]: { [key]: parsed } };
},
Label({ label, value, type }) {
let renderedValue = value;
diff --git a/packages/fields/src/types/integer/index.ts b/packages/fields/src/types/integer/index.ts
index 744bc9d849a..96c07d9af88 100644
--- a/packages/fields/src/types/integer/index.ts
+++ b/packages/fields/src/types/integer/index.ts
@@ -7,6 +7,7 @@ import {
legacyFilters,
orderDirectionEnum,
schema,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -37,6 +38,10 @@ export const integer =
...config,
input: {
uniqueWhere: isUnique ? { arg: schema.arg({ type: schema.Int }) } : undefined,
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].Int.optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: schema.Int }) },
update: { arg: schema.arg({ type: schema.Int }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
diff --git a/packages/fields/src/types/integer/views/index.tsx b/packages/fields/src/types/integer/views/index.tsx
index f05f3427065..c1de20b4f05 100644
--- a/packages/fields/src/types/integer/views/index.tsx
+++ b/packages/fields/src/types/integer/views/index.tsx
@@ -72,14 +72,16 @@ export const controller = (config: FieldControllerConfig): FieldController {
- const key = type === 'is' ? config.path : `${config.path}_${type}`;
const valueWithoutWhitespace = value.replace(/\s/g, '');
-
- return {
- [key]: ['in', 'not_in'].includes(type)
- ? valueWithoutWhitespace.split(',').map(i => parseInt(i))
- : parseInt(valueWithoutWhitespace),
- };
+ const parsed =
+ type === 'in' || type === 'not_in'
+ ? valueWithoutWhitespace.split(',').map(x => parseInt(x))
+ : parseInt(valueWithoutWhitespace);
+ if (type === 'not') {
+ return { [config.path]: { not: { equals: parsed } } };
+ }
+ const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type;
+ return { [config.path]: { [key]: parsed } };
},
Label({ label, value, type }) {
let renderedValue = value;
diff --git a/packages/fields/src/types/password/index.ts b/packages/fields/src/types/password/index.ts
index 3f3a10017d3..3bfbc35dc9e 100644
--- a/packages/fields/src/types/password/index.ts
+++ b/packages/fields/src/types/password/index.ts
@@ -34,6 +34,13 @@ const PasswordState = schema.object<{ isSet: boolean }>()({
},
});
+const PasswordFilter = schema.inputObject({
+ name: 'PasswordFilter',
+ fields: {
+ isSet: schema.arg({ type: schema.nonNull(schema.Boolean) }),
+ },
+});
+
const bcryptHashRegex = /^\$2[aby]?\$\d{1,2}\$[.\/A-Za-z0-9]{53}$/;
export const password =
@@ -89,6 +96,20 @@ export const password =
})({
...config,
input: {
+ where: {
+ arg: schema.arg({ type: PasswordFilter }),
+ resolve(val) {
+ if (val === null) {
+ throw new Error('Password filters cannot be set to null');
+ }
+ if (val.isSet) {
+ return {
+ not: null,
+ };
+ }
+ return null;
+ },
+ },
create: {
arg: schema.arg({ type: schema.String }),
resolve: inputResolver,
diff --git a/packages/fields/src/types/password/views/index.tsx b/packages/fields/src/types/password/views/index.tsx
index 0c1ecbfdb5a..7b8b29a3aad 100644
--- a/packages/fields/src/types/password/views/index.tsx
+++ b/packages/fields/src/types/password/views/index.tsx
@@ -200,8 +200,8 @@ export const controller = (
/>
);
},
- graphql: ({ type, value }) => {
- return { [`${config.path}_${type}`]: value };
+ graphql: ({ value }) => {
+ return { [config.path]: { isSet: value } };
},
Label({ value }) {
return value ? 'is set' : 'is not set';
diff --git a/packages/fields/src/types/relationship/index.ts b/packages/fields/src/types/relationship/index.ts
index 65014067a40..6d937dcf8f1 100644
--- a/packages/fields/src/types/relationship/index.ts
+++ b/packages/fields/src/types/relationship/index.ts
@@ -153,6 +153,12 @@ export const relationship =
})({
...commonConfig,
input: {
+ where: {
+ arg: schema.arg({ type: listTypes.relateTo.many.where }),
+ resolve(value, context, resolve) {
+ return resolve(value);
+ },
+ },
create: {
arg: schema.arg({
type: listTypes.relateTo.many.create,
@@ -217,6 +223,12 @@ export const relationship =
})({
...commonConfig,
input: {
+ where: {
+ arg: schema.arg({ type: listTypes.where }),
+ resolve(value, context, resolve) {
+ return resolve(value);
+ },
+ },
create: {
arg: schema.arg({ type: listTypes.relateTo.one.create }),
async resolve(value, context, resolve) {
diff --git a/packages/fields/src/types/relationship/tests/implementation.test.ts b/packages/fields/src/types/relationship/tests/implementation.test.ts
index ea93d65957f..54de9fd8d69 100644
--- a/packages/fields/src/types/relationship/tests/implementation.test.ts
+++ b/packages/fields/src/types/relationship/tests/implementation.test.ts
@@ -123,7 +123,7 @@ describe('Type Generation', () => {
expect(printType(schema.getType('Test')!)).toMatchInlineSnapshot(`
"type Test {
id: ID!
- foo(where: ZipWhereInput! = {}, orderBy: [ZipOrderByInput!]! = [], first: Int, skip: Int! = 0): [Zip!]
+ foo(where: ZipWhereInput! = {}, orderBy: [ZipOrderByInput!]! = [], take: Int, skip: Int! = 0): [Zip!]
fooCount(where: ZipWhereInput! = {}): Int
}"
`);
@@ -135,7 +135,7 @@ describe('Type Generation', () => {
expect(printType(schema.getType('Test')!)).toMatchInlineSnapshot(`
"type Test {
id: ID!
- foo(where: ZipWhereInput! = {}, orderBy: [ZipOrderByInput!]! = [], first: Int, skip: Int! = 0): [Zip!]
+ foo(where: ZipWhereInput! = {}, orderBy: [ZipOrderByInput!]! = [], take: Int, skip: Int! = 0): [Zip!]
}"
`);
});
diff --git a/packages/fields/src/types/relationship/views/RelationshipSelect.tsx b/packages/fields/src/types/relationship/views/RelationshipSelect.tsx
index 44446e7bdd1..6f0a6980c26 100644
--- a/packages/fields/src/types/relationship/views/RelationshipSelect.tsx
+++ b/packages/fields/src/types/relationship/views/RelationshipSelect.tsx
@@ -105,10 +105,10 @@ export const RelationshipSelect = ({
const QUERY: TypedDocumentNode<
{ items: { [idField]: string; [labelField]: string | null }[]; count: number },
- { where: Record; first: number; skip: number }
+ { where: Record; take: number; skip: number }
> = gql`
- query RelationshipSelect($where: ${list.gqlNames.whereInputName}!, $first: Int!, $skip: Int!) {
- items: ${list.gqlNames.listQueryName}(where: $where, first: $first, skip: $skip) {
+ query RelationshipSelect($where: ${list.gqlNames.whereInputName}!, $take: Int!, $skip: Int!) {
+ items: ${list.gqlNames.listQueryName}(where: $where, take: $take, skip: $skip) {
${idField}: id
${labelField}: ${list.labelField}
${extraSelection}
@@ -121,7 +121,7 @@ export const RelationshipSelect = ({
const { data, error, loading, fetchMore } = useQuery(QUERY, {
fetchPolicy: 'network-only',
- variables: { where, first: initialItemsToLoad, skip: 0 },
+ variables: { where, take: initialItemsToLoad, skip: 0 },
});
const count = data?.count || 0;
@@ -146,10 +146,10 @@ export const RelationshipSelect = ({
if (!loading && isIntersecting && options.length < count) {
const QUERY: TypedDocumentNode<
{ items: { [idField]: string; [labelField]: string | null }[] },
- { where: Record; first: number; skip: number }
+ { where: Record; take: number; skip: number }
> = gql`
- query RelationshipSelectMore($where: ${list.gqlNames.whereInputName}!, $first: Int!, $skip: Int!) {
- items: ${list.gqlNames.listQueryName}(where: $where, first: $first, skip: $skip) {
+ query RelationshipSelectMore($where: ${list.gqlNames.whereInputName}!, $take: Int!, $skip: Int!) {
+ items: ${list.gqlNames.listQueryName}(where: $where, take: $take, skip: $skip) {
${labelField}: ${list.labelField}
${idField}: id
${extraSelection}
@@ -160,7 +160,7 @@ export const RelationshipSelect = ({
query: QUERY,
variables: {
where,
- first: subsequentItemsToLoad,
+ take: subsequentItemsToLoad,
skip: data!.items.length,
},
updateQuery: (prev, { fetchMoreResult }) => {
diff --git a/packages/fields/src/types/select/index.ts b/packages/fields/src/types/select/index.ts
index 78130da1f6b..8ec53f754e8 100644
--- a/packages/fields/src/types/select/index.ts
+++ b/packages/fields/src/types/select/index.ts
@@ -8,6 +8,7 @@ import {
legacyFilters,
orderDirectionEnum,
schema,
+ filters,
} from '@keystone-next/types';
// @ts-ignore
import inflection from 'inflection';
@@ -68,6 +69,10 @@ export const select =
})({
...commonConfig,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].Int.optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: schema.Int }) },
update: { arg: schema.arg({ type: schema.Int }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
@@ -96,6 +101,10 @@ export const select =
)({
...commonConfig,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].enum(graphQLType).optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: graphQLType }) },
update: { arg: schema.arg({ type: graphQLType }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
@@ -109,6 +118,10 @@ export const select =
return fieldType({ kind: 'scalar', scalar: 'String', mode: 'optional', index })({
...commonConfig,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].String.optional }),
+ resolve: filters.resolveString,
+ },
create: { arg: schema.arg({ type: schema.String }) },
update: { arg: schema.arg({ type: schema.String }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
diff --git a/packages/fields/src/types/select/views/index.tsx b/packages/fields/src/types/select/views/index.tsx
index 6fccd3a0bc0..ca3c939064f 100644
--- a/packages/fields/src/types/select/views/index.tsx
+++ b/packages/fields/src/types/select/views/index.tsx
@@ -116,31 +116,9 @@ export const controller = (
/>
);
},
-
- graphql: ({ type, value: options }) => {
- const inverted = type === 'not_matches';
-
- if (!options.length) {
- return {
- [`${config.path}${inverted ? '_not' : ''}`]: null,
- };
- }
-
- const isMulti = options.length > 1;
-
- let key = config.path;
- if (isMulti && inverted) {
- key = `${config.path}_not_in`;
- } else if (isMulti) {
- key = `${config.path}_in`;
- } else if (inverted) {
- key = `${config.path}_not`;
- }
-
- const value = isMulti ? options.map(x => t(x.value)) : t(options[0].value);
-
- return { [key]: value };
- },
+ graphql: ({ type, value: options }) => ({
+ [config.path]: { [type === 'not_matches' ? 'notIn' : 'in']: options.map(x => t(x.value)) },
+ }),
Label({ type, value }) {
if (!value.length) {
return type === 'not_matches' ? `is set` : `has no value`;
diff --git a/packages/fields/src/types/text/index.ts b/packages/fields/src/types/text/index.ts
index 8fe1e69f746..ecedaf0a5b4 100644
--- a/packages/fields/src/types/text/index.ts
+++ b/packages/fields/src/types/text/index.ts
@@ -7,6 +7,7 @@ import {
orderDirectionEnum,
FieldTypeFunc,
legacyFilters,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -40,6 +41,10 @@ export const text =
...config,
input: {
uniqueWhere: isUnique ? { arg: schema.arg({ type: schema.String }) } : undefined,
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].String.optional }),
+ resolve: filters.resolveString,
+ },
create: { arg: schema.arg({ type: schema.String }) },
update: { arg: schema.arg({ type: schema.String }) },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
@@ -47,7 +52,10 @@ export const text =
output: schema.field({ type: schema.String }),
views: resolveView('text/views'),
getAdminMeta() {
- return { displayMode: config.ui?.displayMode ?? 'input' };
+ return {
+ displayMode: config.ui?.displayMode ?? 'input',
+ shouldUseModeInsensitive: meta.provider === 'postgresql',
+ };
},
__legacy: {
filters: {
diff --git a/packages/fields/src/types/text/views/index.tsx b/packages/fields/src/types/text/views/index.tsx
index c9fd5cc0245..1405b86df73 100644
--- a/packages/fields/src/types/text/views/index.tsx
+++ b/packages/fields/src/types/text/views/index.tsx
@@ -50,7 +50,10 @@ export const CardValue: CardValueComponent = ({ item, field }) => {
);
};
-type Config = FieldControllerConfig<{ displayMode: 'input' | 'textarea' }>;
+type Config = FieldControllerConfig<{
+ displayMode: 'input' | 'textarea';
+ shouldUseModeInsensitive: boolean;
+}>;
export const controller = (
config: Config
@@ -80,13 +83,25 @@ export const controller = (
},
graphql: ({ type, value }) => {
- const key = type === 'is_i' ? `${config.path}_i` : `${config.path}_${type}`;
- return { [key]: value };
+ const isNot = type.startsWith('not_');
+ const key =
+ type === 'is_i' || type === 'not_i'
+ ? 'equals'
+ : type
+ .replace(/_i$/, '')
+ .replace('not_', '')
+ .replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());
+ const filter = { [key]: value };
+ return {
+ [config.path]: {
+ ...(isNot ? { not: filter } : filter),
+ mode: config.fieldMeta.shouldUseModeInsensitive ? 'insensitive' : undefined,
+ },
+ };
},
Label({ label, value }) {
return `${label.toLowerCase()}: "${value}"`;
},
- // FIXME: Not all of these options will work with prisma_sqlite
types: {
contains_i: {
label: 'Contains',
diff --git a/packages/fields/src/types/timestamp/index.ts b/packages/fields/src/types/timestamp/index.ts
index 81df675377d..b5203d908ab 100644
--- a/packages/fields/src/types/timestamp/index.ts
+++ b/packages/fields/src/types/timestamp/index.ts
@@ -7,6 +7,7 @@ import {
orderDirectionEnum,
legacyFilters,
FieldDefaultValue,
+ filters,
} from '@keystone-next/types';
import { resolveView } from '../../resolve-view';
import { getIndexType } from '../../get-index-type';
@@ -42,6 +43,10 @@ export const timestamp =
})({
...config,
input: {
+ where: {
+ arg: schema.arg({ type: filters[meta.provider].DateTime.optional }),
+ resolve: filters.resolveCommon,
+ },
create: { arg: schema.arg({ type: schema.String }), resolve: inputResolver },
update: { arg: schema.arg({ type: schema.String }), resolve: inputResolver },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
diff --git a/packages/keystone/CHANGELOG.md b/packages/keystone/CHANGELOG.md
index eb2919d6459..718e85be1cd 100644
--- a/packages/keystone/CHANGELOG.md
+++ b/packages/keystone/CHANGELOG.md
@@ -1,5 +1,170 @@
# @keystone-next/keystone
+## 24.0.0
+
+### Major Changes
+
+- [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `_ListKeyMeta` and `_toManyRelationshipFieldMeta` fields. You should use `listKeyCount` and `toManyRelationshipFieldCount` instead
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed all arguments from `context.lists.List.count` and `context.db.lists.List.count` except for `where`.
+
+- [#6266](https://github.com/keystonejs/keystone/pull/6266) [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Renamed `first` argument in find many queries to `take` to align with Prisma.
+
+ ```graphql
+ type Query {
+ users(
+ where: UserWhereInput! = {}
+ orderBy: [UserOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [User!]
+ # ...
+ }
+
+ type User {
+ # ...
+ posts(
+ where: PostWhereInput! = {}
+ orderBy: [PostOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [Post!]
+ # ...
+ }
+ ```
+
+* [#6208](https://github.com/keystonejs/keystone/pull/6208) [`092df6678`](https://github.com/keystonejs/keystone/commit/092df6678cea18d639be16ad250ec4ecc9250f5a) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The create one mutation now requires a non-null `data` argument and the create many mutation accepts a list of `ItemCreateInput` directly instead of being nested inside of an object with the `ItemCreateInput` in a `data` field.
+
+ If you have a list called `Item`, `createItem` now looks like `createItem(data: ItemCreateInput!): Item` and `createItems` now looks like `createItems(data: [ItemCreateInput!]!): [Item]`.
+
+- [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `search` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `contains` filters instead.
+
+* [#6095](https://github.com/keystonejs/keystone/pull/6095) [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Updated filters to be nested instead of flattened and add top-level `NOT` operator. See the [Query Filter API docs](https://keystonejs.com/docs/apis/filters) and the upgrade guide for more information.
+
+ ```graphql
+ query {
+ posts(where: { title: { contains: "Something" } }) {
+ title
+ content
+ }
+ }
+ ```
+
+- [#6198](https://github.com/keystonejs/keystone/pull/6198) [`9d361c1c8`](https://github.com/keystonejs/keystone/commit/9d361c1c8625e1390f837b7318b63547d686a63b) Thanks [@timleslie](https://github.com/timleslie)! - Removed the `uid` and `name` properties from the errors returned by the GraphQL API.
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `sortBy` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `orderBy` instead.
+
+- [#6312](https://github.com/keystonejs/keystone/pull/6312) [`56044e2a4`](https://github.com/keystonejs/keystone/commit/56044e2a425f4256b66475fd3b1a6342cd6c3bf9) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Updated `@graphql-ts/schema`. The second type parameter of `schema.Arg` exported from `@keystone-next/types` is now a boolean that defines whether or not the arg has a default value to make it easier to define circular input objects.
+
+* [#6217](https://github.com/keystonejs/keystone/pull/6217) [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been renamed to `disconnect` in to-one relationship inputs and the old `disconnect` field has been removed. There are also seperate input types for create and update where the input for create doesn't have `disconnect`. It's also now required that if you provide a to-one relationship input, you must provide exactly one field to the input.
+
+ If you have a list called `Item`, the to-one relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToOneForCreateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ }
+ input ItemRelateToOneForUpdateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ disconnect: Boolean
+ }
+ ```
+
+- [#6224](https://github.com/keystonejs/keystone/pull/6224) [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been replaced by `set` in to-many relationship inputs, the equivalent to `disconnectAll: true` is now `set: []`. There are also seperate input types for create and update where the input for create doesn't have `disconnect` or `set`. The inputs in the lists in the input field are now also non-null.
+
+ If you have a list called `Item`, the to-many relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToManyForCreateInput {
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ input ItemRelateToManyForUpdateInput {
+ disconnect: [ItemWhereUniqueInput!]
+ set: [ItemWhereUniqueInput!]
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ ```
+
+* [#6197](https://github.com/keystonejs/keystone/pull/6197) [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The generated CRUD queries, and some of the input types, in the GraphQL API have been renamed.
+
+ If you have a list called `Item`, the query for multiple values, `allItems` will be renamed to `items`. The query for a single value, `Item`, will be renamed to `item`.
+
+ Also, the input type used in the `updateItems` mutation has been renamed from `ItemsUpdateInput` to `ItemUpdateArgs`.
+
+- [#6211](https://github.com/keystonejs/keystone/pull/6211) [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The update mutations now accept `where` unique inputs instead of only an `id` and the `where` and `data` arguments are non-null.
+
+ If you have a list called `Item`, the update mutations now look like this:
+
+ ```graphql
+ type Mutation {
+ updateItem(where: ItemWhereUniqueInput!, data: ItemUpdateInput!): Item
+ updateItems(data: [ItemUpdateArgs!]!): [Item]
+ }
+
+ input ItemUpdateArgs {
+ where: ItemWhereUniqueInput!
+ data: ItemUpdateInput!
+ }
+ ```
+
+* [#6206](https://github.com/keystonejs/keystone/pull/6206) [`f5e64af37`](https://github.com/keystonejs/keystone/commit/f5e64af37df2eb460c89d89fa3c8924fb34970ed) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The delete mutations now accept `where` unique inputs instead of only an `id`.
+
+ If you have a list called `Item`, `deleteItem` now looks like `deleteItem(where: ItemWhereUniqueInput!): Item` and `deleteItems` now looks like `deleteItems(where: [ItemWhereUniqueInput!]!): [Item]`
+
+### Minor Changes
+
+- [#6276](https://github.com/keystonejs/keystone/pull/6276) [`3a7a06b2c`](https://github.com/keystonejs/keystone/commit/3a7a06b2cc6b5ea157d34d925b15494b471899eb) Thanks [@gautamsi](https://github.com/gautamsi)! - Added option for `Bearer` token auth when using session.
+
+* [#6267](https://github.com/keystonejs/keystone/pull/6267) [`1030296d1`](https://github.com/keystonejs/keystone/commit/1030296d1f304dc44246e895089ac1f992e80590) Thanks [@timleslie](https://github.com/timleslie)! - Added `config.graphql.debug` option, which can be used to control whether debug information such as stack traces are included in the errors returned by the GraphQL API.
+
+### Patch Changes
+
+- [#6317](https://github.com/keystonejs/keystone/pull/6317) [`1cbcf54cb`](https://github.com/keystonejs/keystone/commit/1cbcf54cb1206461866b582865e3b1a8fc728f18) Thanks [@timleslie](https://github.com/timleslie)! - Separated the resolving of non-relationship field from relationship fields in create/update inputs to allow for better error handling.
+
+* [#6250](https://github.com/keystonejs/keystone/pull/6250) [`a92169d04`](https://github.com/keystonejs/keystone/commit/a92169d04e5a1a98deb8e757b8eae3b06fc66450) Thanks [@timleslie](https://github.com/timleslie)! - Updated internal type definitions.
+
+- [#6334](https://github.com/keystonejs/keystone/pull/6334) [`f3014a627`](https://github.com/keystonejs/keystone/commit/f3014a627060c7cd86440a6937da5caecfd023a0) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Resolved bug with visually hidden elements in ListView checkboxes expanding to fill the whole body on click of elements near the bottom of the screen.
+
+* [#6219](https://github.com/keystonejs/keystone/pull/6219) [`6da56b80e`](https://github.com/keystonejs/keystone/commit/6da56b80e03c748a621afcca6c1ec2887fef7271) Thanks [@timleslie](https://github.com/timleslie)! - Removed unused code path in Admin UI error display.
+
+- [#6269](https://github.com/keystonejs/keystone/pull/6269) [`697efa354`](https://github.com/keystonejs/keystone/commit/697efa354b1066b3d4b6eb757ca704b458f45e93) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Added ignoreBuildErrors flag to next-config.js file, to negate false positive errors in keystone builds with imported components.
+
+* [#6218](https://github.com/keystonejs/keystone/pull/6218) [`c7e331d90`](https://github.com/keystonejs/keystone/commit/c7e331d90a28b2ed8236100097cb8d34a11fabe2) Thanks [@timleslie](https://github.com/timleslie)! - Added more details to validation failure error messages.
+
+- [#6316](https://github.com/keystonejs/keystone/pull/6316) [`78dac764e`](https://github.com/keystonejs/keystone/commit/78dac764e1860b33f9e2bd8cee6015abeaaa5ec4) Thanks [@timleslie](https://github.com/timleslie)! - Updated handling of errors in `resolveInput` hooks to provide developers with appropriate debug information.
+
+* [#6310](https://github.com/keystonejs/keystone/pull/6310) [`399561b27`](https://github.com/keystonejs/keystone/commit/399561b2769ddd8f3d3fdf29838f5784404bb053) Thanks [@timleslie](https://github.com/timleslie)! - Updated dependencies to use `mergeSchemas` from `@graphql-tools/schema`, rather than its old location in `@graphql-tools/merge`. You might see a reordering of the contents of your `graphql.schema` file.
+
+- [#6292](https://github.com/keystonejs/keystone/pull/6292) [`0dcb1c95b`](https://github.com/keystonejs/keystone/commit/0dcb1c95b5200750cc8649485425f2ae40d023a3) Thanks [@renovate](https://github.com/apps/renovate)! - Updated Prisma dependencies to `2.29.1`.
+
+* [#6263](https://github.com/keystonejs/keystone/pull/6263) [`94435ffee`](https://github.com/keystonejs/keystone/commit/94435ffee765824091899242e4a2f73c7356b524) Thanks [@timleslie](https://github.com/timleslie)! - Made the original stacktraces for before/after hooks available on `error.extension.errors`.
+
+- [#6259](https://github.com/keystonejs/keystone/pull/6259) [`f46fd32b7`](https://github.com/keystonejs/keystone/commit/f46fd32b7047dbb5ea2566859f7ecee8db5b0b15) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Bumped @apollo/client dependency to ^3.4.5, the update resolves the following useQuery [issue](https://github.com/keystonejs/keystone/issues/6254).
+
+* [#6239](https://github.com/keystonejs/keystone/pull/6239) [`8ea4eed55`](https://github.com/keystonejs/keystone/commit/8ea4eed55367aaa213f6b4ffb7473087498e39ae) Thanks [@timleslie](https://github.com/timleslie)! - Added more details to before/after change/delete hook error messages.
+
+- [#6248](https://github.com/keystonejs/keystone/pull/6248) [`e3fe6498d`](https://github.com/keystonejs/keystone/commit/e3fe6498dc36203d8080dff3c2e0c25f6c98733e) Thanks [@timleslie](https://github.com/timleslie)! - Removed unused dependency `@graphql-tools/schema`.
+
+* [#6203](https://github.com/keystonejs/keystone/pull/6203) [`8b2d179b2`](https://github.com/keystonejs/keystone/commit/8b2d179b2463d78b082182ca9afa8233109e0ba3) Thanks [@renovate](https://github.com/apps/renovate)! - Updated Prisma dependencies to `2.28.0`.
+
+- [#6296](https://github.com/keystonejs/keystone/pull/6296) [`e3fefafcc`](https://github.com/keystonejs/keystone/commit/e3fefafcce6f8bf836c9bf0f4d931b8200ba41c7) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Fixed delete success notifications in the Admin UI appearing on failed deletes in List view and Item view.
+
+* [#6200](https://github.com/keystonejs/keystone/pull/6200) [`686c0f1c4`](https://github.com/keystonejs/keystone/commit/686c0f1c4a1feb609e1584aa71738709bbbf984e) Thanks [@timleslie](https://github.com/timleslie)! - Updated internal error handling to use the `apollo-server-errors` package instead of `apollo-errors`.
+
+* Updated dependencies [[`e9f3c42d5`](https://github.com/keystonejs/keystone/commit/e9f3c42d5b9d42872cecbd18fbe9bf9d7d53ed82), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f), [`092df6678`](https://github.com/keystonejs/keystone/commit/092df6678cea18d639be16ad250ec4ecc9250f5a), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`c2bb6a9a5`](https://github.com/keystonejs/keystone/commit/c2bb6a9a596fc52a3c61ec5d91c79758e417e61d), [`4f4f0351a`](https://github.com/keystonejs/keystone/commit/4f4f0351a056dea9d1614aa2a3a4789d66bb402d), [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`56044e2a4`](https://github.com/keystonejs/keystone/commit/56044e2a425f4256b66475fd3b1a6342cd6c3bf9), [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8), [`1030296d1`](https://github.com/keystonejs/keystone/commit/1030296d1f304dc44246e895089ac1f992e80590), [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c), [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda), [`8187ea019`](https://github.com/keystonejs/keystone/commit/8187ea019a212874f3c602573af3382c6f3bd3b2), [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e), [`f5e64af37`](https://github.com/keystonejs/keystone/commit/f5e64af37df2eb460c89d89fa3c8924fb34970ed)]:
+ - @keystone-next/fields@14.0.0
+ - @keystone-next/types@24.0.0
+ - @keystone-ui/notice@4.0.1
+ - @keystone-ui/toast@4.0.2
+ - @keystone-next/admin-ui-utils@5.0.6
+ - @keystone-next/utils@1.0.4
+
## 23.0.3
### Patch Changes
diff --git a/packages/keystone/package.json b/packages/keystone/package.json
index 5c70c4c0abc..d2be3d4829e 100644
--- a/packages/keystone/package.json
+++ b/packages/keystone/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-next/keystone",
- "version": "23.0.3",
+ "version": "24.0.0",
"license": "MIT",
"main": "dist/keystone.cjs.js",
"module": "dist/keystone.esm.js",
@@ -22,33 +22,33 @@
"keystone-next": "bin/cli.js"
},
"dependencies": {
- "@apollo/client": "3.3.21",
- "@babel/core": "^7.14.8",
- "@babel/plugin-transform-modules-commonjs": "^7.14.5",
- "@babel/runtime": "^7.14.8",
+ "@apollo/client": "^3.4.8",
+ "@babel/core": "^7.15.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.15.0",
+ "@babel/runtime": "^7.15.3",
"@emotion/hash": "^0.8.0",
- "@graphql-tools/merge": "^6.2.16",
+ "@graphql-tools/schema": "^8.1.1",
"@hapi/iron": "^6.0.0",
- "@keystone-next/admin-ui-utils": "^5.0.5",
- "@keystone-next/fields": "^13.0.0",
- "@keystone-next/types": "^23.0.0",
- "@keystone-next/utils": "^1.0.3",
+ "@keystone-next/admin-ui-utils": "^5.0.6",
+ "@keystone-next/fields": "^14.0.0",
+ "@keystone-next/types": "^24.0.0",
+ "@keystone-next/utils": "^1.0.4",
"@keystone-ui/button": "^5.0.0",
"@keystone-ui/core": "^3.1.1",
"@keystone-ui/fields": "^4.1.2",
"@keystone-ui/icons": "^4.0.0",
"@keystone-ui/loading": "^4.0.0",
"@keystone-ui/modals": "^4.0.0",
- "@keystone-ui/notice": "^4.0.0",
+ "@keystone-ui/notice": "^4.0.1",
"@keystone-ui/options": "^4.0.1",
"@keystone-ui/pill": "^5.0.0",
"@keystone-ui/popover": "^4.0.2",
- "@keystone-ui/toast": "^4.0.1",
+ "@keystone-ui/toast": "^4.0.2",
"@keystone-ui/tooltip": "^4.0.1",
"@preconstruct/next": "^3.0.0",
- "@prisma/client": "2.28.0",
- "@prisma/migrate": "2.28.0",
- "@prisma/sdk": "2.28.0",
+ "@prisma/client": "2.29.1",
+ "@prisma/migrate": "2.29.1",
+ "@prisma/sdk": "2.29.1",
"@sindresorhus/slugify": "^1.1.2",
"@types/apollo-upload-client": "14.1.0",
"@types/babel__core": "^7.1.15",
@@ -94,7 +94,7 @@
"pirates": "^4.0.1",
"pluralize": "^8.0.0",
"prettier": "^2.3.2",
- "prisma": "2.28.0",
+ "prisma": "2.29.1",
"prompts": "^2.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
diff --git a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx
index 53d48f340f5..cba2c258087 100644
--- a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx
+++ b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx
@@ -53,13 +53,18 @@ export const controller = (
},
graphql: ({ type, value }) => {
- const key = type === 'is' ? config.path : `${config.path}_${type}`;
+ if (type === 'not') {
+ return { [config.path]: { not: { equals: value } } };
+ }
const valueWithoutWhitespace = value.replace(/\s/g, '');
+ const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type;
return {
- [key]: ['in', 'not_in'].includes(type)
- ? valueWithoutWhitespace.split(',')
- : valueWithoutWhitespace,
+ [config.path]: {
+ [key]: ['in', 'not_in'].includes(type)
+ ? valueWithoutWhitespace.split(',')
+ : valueWithoutWhitespace,
+ },
};
},
Label({ label, value, type }) {
diff --git a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/next-config.ts b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/next-config.ts
index e7c0984a39f..db51999f8e2 100644
--- a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/next-config.ts
+++ b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/next-config.ts
@@ -3,6 +3,9 @@ import Path from 'path';
import withPreconstruct from '@preconstruct/next';
export const config = withPreconstruct({
+ typescript: {
+ ignoreBuildErrors: true,
+ },
webpack(config: any, { isServer }: any) {
config.resolve.alias = {
...config.resolve.alias,
diff --git a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.tsx b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.tsx
index 5ea383f38be..a49288a34c7 100644
--- a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.tsx
+++ b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.tsx
@@ -253,17 +253,19 @@ function DeleteButton({
confirm: {
label: 'Delete',
action: async () => {
- await deleteItem().catch(err => {
- toasts.addToast({
- title: 'Failed to delete item',
+ try {
+ await deleteItem();
+ } catch (err) {
+ return toasts.addToast({
+ title: `Failed to delete ${list.singular} item: ${itemLabel}`,
message: err.message,
tone: 'negative',
});
- });
+ }
router.push(`/${list.path}`);
- toasts.addToast({
+ return toasts.addToast({
title: itemLabel,
- message: 'Deleted successfully',
+ message: `Deleted ${list.singular} item successfully`,
tone: 'positive',
});
},
diff --git a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx
index e3b977c8737..a3f1e6203ca 100644
--- a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx
+++ b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx
@@ -176,12 +176,12 @@ const ListPage = ({ listKey }: ListPageProps) => {
})
.join('\n');
return gql`
- query ($where: ${list.gqlNames.whereInputName}, $first: Int!, $skip: Int!, $orderBy: [${
+ query ($where: ${list.gqlNames.whereInputName}, $take: Int!, $skip: Int!, $orderBy: [${
list.gqlNames.listOrderName
}!]) {
items: ${
list.gqlNames.listQueryName
- }(where: $where,first: $first, skip: $skip, orderBy: $orderBy) {
+ }(where: $where,take: $take, skip: $skip, orderBy: $orderBy) {
${
// TODO: maybe namespace all the fields instead of doing this
selectedFields.has('id') ? '' : 'id'
@@ -197,7 +197,7 @@ const ListPage = ({ listKey }: ListPageProps) => {
errorPolicy: 'all',
variables: {
where: filters.where,
- first: pageSize,
+ take: pageSize,
skip: (currentPage - 1) * pageSize,
orderBy: sort ? [{ [sort.field]: sort.direction.toLowerCase() }] : undefined,
},
@@ -244,7 +244,6 @@ const ListPage = ({ listKey }: ListPageProps) => {
const theme = useTheme();
const showCreate = !(metaQuery.data?.keystone.adminMeta.list?.hideCreate ?? true) || null;
-
return (
}>
{metaQuery.error ? (
@@ -436,14 +435,18 @@ function DeleteManyButton({
useMemo(
() =>
gql`
- mutation($where: [${list.gqlNames.whereUniqueInputName}!]!) {
- ${list.gqlNames.deleteManyMutationName}(where: $where) {
- id
- }
- }
+ mutation($where: [${list.gqlNames.whereUniqueInputName}!]!) {
+ ${list.gqlNames.deleteManyMutationName}(where: $where) {
+ id
+ ${list.labelField}
+ }
+ }
`,
[list]
- )
+ ),
+ {
+ errorPolicy: 'all',
+ }
);
const [isOpen, setIsOpen] = useState(false);
const toasts = useToasts();
@@ -467,20 +470,75 @@ function DeleteManyButton({
confirm: {
label: 'Delete',
action: async () => {
- await deleteItems({
+ const { data, errors } = await deleteItems({
variables: { where: [...selectedItems].map(id => ({ id })) },
- }).catch(err => {
+ });
+ /*
+ Data returns an array where successful deletions are item objects
+ and unsuccessful deletions are null values.
+ Run a reduce to count success and failure as well as
+ to generate the success message to be passed to the success toast
+ */
+ const { successfulItems, unsuccessfulItems, successMessage } = data[
+ list.gqlNames.deleteManyMutationName
+ ].reduce(
+ (
+ acc: {
+ successfulItems: number;
+ unsuccessfulItems: number;
+ successMessage: string;
+ },
+ curr: any
+ ) => {
+ if (curr) {
+ acc.successfulItems++;
+ acc.successMessage =
+ acc.successMessage === ''
+ ? (acc.successMessage += curr.label)
+ : (acc.successMessage += `, ${curr.label}`);
+ } else {
+ acc.unsuccessfulItems++;
+ }
+ return acc;
+ },
+ { successfulItems: 0, unsuccessfulItems: 0, successMessage: '' } as {
+ successfulItems: number;
+ unsuccessfulItems: number;
+ successMessage: string;
+ }
+ );
+
+ // If there are errors
+ if (errors?.length) {
+ // Find out how many items failed to delete.
+ // Reduce error messages down to unique instances, and append to the toast as a message.
toasts.addToast({
- title: 'Failed to delete items',
- message: err.message,
tone: 'negative',
+ title: `Failed to delete ${unsuccessfulItems} of ${
+ data[list.gqlNames.deleteManyMutationName].length
+ } ${list.plural}`,
+ message: errors
+ .reduce((acc, error) => {
+ if (acc.indexOf(error.message) < 0) {
+ acc.push(error.message);
+ }
+ return acc;
+ }, [] as string[])
+ .join('\n'),
});
- });
- toasts.addToast({
- title: 'Deleted items successfully',
- tone: 'positive',
- });
- refetch();
+ }
+
+ if (successfulItems) {
+ toasts.addToast({
+ tone: 'positive',
+ title: `Deleted ${successfulItems} of ${
+ data[list.gqlNames.deleteManyMutationName].length
+ } ${list.plural} successfully`,
+ message: successMessage,
+ });
+ }
+
+ return refetch();
},
},
cancel: {
@@ -523,7 +581,6 @@ function ListTable({
const { query } = useRouter();
const shouldShowLinkIcon =
!list.fields[selectedFields.keys().next().value].views.Cell.supportsLinkTo;
-
return (
@@ -614,7 +671,7 @@ function ListTable({
minHeight: 38,
alignItems: 'center',
justifyContent: 'start',
- // cursor: 'pointer',
+ position: 'relative',
}}
>
) => {
padding: spacing.small,
textAlign: 'left',
position: 'sticky',
+ zIndex: 1,
top: 0,
}}
{...props}
diff --git a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/next-graphql.ts b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/next-graphql.ts
index 0a032bfe62b..78ec432b4c4 100644
--- a/packages/keystone/src/___internal-do-not-use-will-break-in-patch/next-graphql.ts
+++ b/packages/keystone/src/___internal-do-not-use-will-break-in-patch/next-graphql.ts
@@ -15,7 +15,7 @@ export function nextGraphQLAPIRoute(keystoneConfig: KeystoneConfig, prismaClient
graphQLSchema,
createContext: keystone.createContext,
sessionStrategy: initializedKeystoneConfig.session,
- apolloConfig: initializedKeystoneConfig.graphql?.apolloConfig,
+ graphqlConfig: initializedKeystoneConfig.graphql,
connectionPromise: keystone.connect(),
});
diff --git a/packages/keystone/src/admin-ui/system/getAdminMetaSchema.ts b/packages/keystone/src/admin-ui/system/getAdminMetaSchema.ts
index fcc65b63eda..29bf7753c32 100644
--- a/packages/keystone/src/admin-ui/system/getAdminMetaSchema.ts
+++ b/packages/keystone/src/admin-ui/system/getAdminMetaSchema.ts
@@ -1,4 +1,4 @@
-import { JSONValue, schema as schemaAPIFromTypesPkg } from '@keystone-next/types';
+import { JSONValue, QueryMode, schema as schemaAPIFromTypesPkg } from '@keystone-next/types';
import {
KeystoneContext,
KeystoneConfig,
@@ -6,7 +6,7 @@ import {
ListMetaRootVal,
FieldMetaRootVal,
} from '@keystone-next/types';
-import { GraphQLSchema, GraphQLObjectType, assertScalarType } from 'graphql';
+import { GraphQLSchema, GraphQLObjectType, assertScalarType, assertEnumType } from 'graphql';
import { InitialisedList } from '../../lib/core/types-for-lists';
const schema = {
@@ -35,6 +35,10 @@ export function getAdminMetaSchema({
const jsonScalar = jsonScalarType
? schema.scalar(assertScalarType(jsonScalarType))
: schemaAPIFromTypesPkg.JSON;
+ const queryModeEnumType = graphQLSchema.getType('QueryMode');
+ const queryModeEnum = queryModeEnumType
+ ? { ...QueryMode, graphQLType: assertEnumType(graphQLSchema.getType('QueryMode')) }
+ : QueryMode;
const KeystoneAdminUIFieldMeta = schema.object()({
name: 'KeystoneAdminUIFieldMeta',
@@ -153,10 +157,7 @@ export function getAdminMetaSchema({
}),
}),
search: schema.field({
- type: schema.enum({
- name: 'QueryMode',
- values: schema.enumValues(['default', 'insensitive']),
- }),
+ type: queryModeEnum,
}),
},
});
diff --git a/packages/keystone/src/admin-ui/templates/api.ts b/packages/keystone/src/admin-ui/templates/api.ts
index 01d75117313..c779651e582 100644
--- a/packages/keystone/src/admin-ui/templates/api.ts
+++ b/packages/keystone/src/admin-ui/templates/api.ts
@@ -9,7 +9,7 @@ const apolloServer = createApolloServerMicro({
graphQLSchema,
createContext,
sessionStrategy: initializedKeystoneConfig.session ? initializedKeystoneConfig.session() : undefined,
- apolloConfig: initializedKeystoneConfig.graphql?.apolloConfig,
+ graphqlConfig: initializedKeystoneConfig.graphql,
connectionPromise: keystone.connect(),
});
diff --git a/packages/keystone/src/lib/core/graphql-errors.ts b/packages/keystone/src/lib/core/graphql-errors.ts
index cb760fd554d..6587864b984 100644
--- a/packages/keystone/src/lib/core/graphql-errors.ts
+++ b/packages/keystone/src/lib/core/graphql-errors.ts
@@ -7,9 +7,14 @@ export const validationFailureError = (messages: string[]) => {
return new ApolloError(`You provided invalid data for this operation.\n${s}`);
};
-export const extensionError = (extension: string, messages: string[]) => {
- const s = messages.map(m => ` - ${m}`).join('\n');
- return new ApolloError(`An error occured while running "${extension}".\n${s}`);
+export const extensionError = (extension: string, things: { error: Error; tag: string }[]) => {
+ const s = things.map(t => ` - ${t.tag}: ${t.error.message}`).join('\n');
+ return new ApolloError(
+ `An error occured while running "${extension}".\n${s}`,
+ 'INTERNAL_SERVER_ERROR',
+ // Make the original stack traces available.
+ { debug: things.map(t => ({ stacktrace: t.error.stack, message: t.error.message })) }
+ );
};
// FIXME: In an upcoming PR we will use these args to construct a better
diff --git a/packages/keystone/src/lib/core/mutations/access-control.ts b/packages/keystone/src/lib/core/mutations/access-control.ts
index 51efe776857..0756a934f12 100644
--- a/packages/keystone/src/lib/core/mutations/access-control.ts
+++ b/packages/keystone/src/lib/core/mutations/access-control.ts
@@ -35,7 +35,7 @@ export async function getAccessControlledItemForDelete(
// List access: pass 2
let where: PrismaFilter = mapUniqueWhereToWhere(list, uniqueWhere);
if (typeof access === 'object') {
- where = { AND: [where, await resolveWhereInput(access, list)] };
+ where = { AND: [where, await resolveWhereInput(access, list, context)] };
}
const item = await runWithPrisma(context, list, model => model.findFirst({ where }));
if (item === null) {
@@ -78,7 +78,9 @@ export async function getAccessControlledItemForUpdate(
where:
accessControl === true
? uniqueWhereInWhereForm
- : { AND: [uniqueWhereInWhereForm, await resolveWhereInput(accessControl, list)] },
+ : {
+ AND: [uniqueWhereInWhereForm, await resolveWhereInput(accessControl, list, context)],
+ },
})
);
if (!item) {
diff --git a/packages/keystone/src/lib/core/mutations/create-update.ts b/packages/keystone/src/lib/core/mutations/create-update.ts
index eda831a0393..289d34a9821 100644
--- a/packages/keystone/src/lib/core/mutations/create-update.ts
+++ b/packages/keystone/src/lib/core/mutations/create-update.ts
@@ -9,6 +9,7 @@ import {
runWithPrisma,
} from '../utils';
import { resolveUniqueWhereInput, UniqueInputFilter } from '../where-inputs';
+import { extensionError } from '../graphql-errors';
import {
resolveRelateToManyForCreateInput,
resolveRelateToManyForUpdateInput,
@@ -179,21 +180,32 @@ async function getResolvedData(
);
}
- // Apply field type input resolvers
+ // Apply non-relationship field type input resolvers
resolvedData = Object.fromEntries(
await promiseAllRejectWithAllErrors(
Object.entries(list.fields).map(async ([fieldKey, field]) => {
const inputResolver = field.input?.[operation]?.resolve;
let input = resolvedData[fieldKey];
- if (inputResolver) {
+ if (inputResolver && field.dbField.kind !== 'relation') {
+ input = await inputResolver(input, context, undefined);
+ }
+ return [fieldKey, input] as const;
+ })
+ )
+ );
+
+ // Apply relationship field type input resolvers
+ resolvedData = Object.fromEntries(
+ await promiseAllRejectWithAllErrors(
+ Object.entries(list.fields).map(async ([fieldKey, field]) => {
+ const inputResolver = field.input?.[operation]?.resolve;
+ let input = resolvedData[fieldKey];
+ if (inputResolver && field.dbField.kind === 'relation') {
input = await inputResolver(
input,
context,
+ // This third argument only applies to relationship fields
(() => {
- // This third argument only applies to relationship fields
- if (field.dbField.kind !== 'relation') {
- return undefined;
- }
if (input === undefined) {
// No-op: This is what we want
return () => undefined;
@@ -228,23 +240,37 @@ async function getResolvedData(
);
// Resolve input hooks
- resolvedData = Object.fromEntries(
- await promiseAllRejectWithAllErrors(
- Object.entries(list.fields).map(async ([fieldKey, field]) => {
- if (field.hooks.resolveInput === undefined) {
- return [fieldKey, resolvedData[fieldKey]];
- }
- const value = await field.hooks.resolveInput({
+ const hookName = 'resolveInput';
+ // Field hooks
+ let _resolvedData: Record = {};
+ const fieldsErrors: { error: Error; tag: string }[] = [];
+ for (const [fieldPath, field] of Object.entries(list.fields)) {
+ if (field.hooks.resolveInput === undefined) {
+ _resolvedData[fieldPath] = resolvedData[fieldPath];
+ } else {
+ try {
+ _resolvedData[fieldPath] = await field.hooks.resolveInput({
...hookArgs,
resolvedData,
- fieldPath: fieldKey,
+ fieldPath,
});
- return [fieldKey, value];
- })
- )
- );
+ } catch (error) {
+ fieldsErrors.push({ error, tag: `${list.listKey}.${fieldPath}` });
+ }
+ }
+ }
+ if (fieldsErrors.length) {
+ throw extensionError(hookName, fieldsErrors);
+ }
+ resolvedData = _resolvedData;
+
+ // List hooks
if (list.hooks.resolveInput) {
- resolvedData = (await list.hooks.resolveInput({ ...hookArgs, resolvedData })) as any;
+ try {
+ resolvedData = (await list.hooks.resolveInput({ ...hookArgs, resolvedData })) as any;
+ } catch (error) {
+ throw extensionError(hookName, [{ error, tag: list.listKey }]);
+ }
}
return resolvedData;
diff --git a/packages/keystone/src/lib/core/mutations/hooks.ts b/packages/keystone/src/lib/core/mutations/hooks.ts
index 38cff0a8d7c..eeac512823f 100644
--- a/packages/keystone/src/lib/core/mutations/hooks.ts
+++ b/packages/keystone/src/lib/core/mutations/hooks.ts
@@ -31,14 +31,13 @@ export async function runSideEffectOnlyHook<
}
// Field hooks
- const fieldsErrors = [];
+ const fieldsErrors: { error: Error; tag: string }[] = [];
for (const [fieldPath, field] of Object.entries(list.fields)) {
if (shouldRunFieldLevelHook(fieldPath)) {
try {
- // @ts-ignore
await field.hooks[hookName]?.({ fieldPath, ...args });
- } catch (err) {
- fieldsErrors.push(`${list.listKey}.${fieldPath}: ${err.message}`);
+ } catch (error) {
+ fieldsErrors.push({ error, tag: `${list.listKey}.${fieldPath}` });
}
}
}
@@ -49,7 +48,7 @@ export async function runSideEffectOnlyHook<
// List hooks
try {
await list.hooks[hookName]?.(args);
- } catch (err) {
- throw extensionError(hookName, [`${list.listKey}: ${err.message}`]);
+ } catch (error) {
+ throw extensionError(hookName, [{ error, tag: list.listKey }]);
}
}
diff --git a/packages/keystone/src/lib/core/queries/resolvers.ts b/packages/keystone/src/lib/core/queries/resolvers.ts
index 35e421b4e26..21a9225146c 100644
--- a/packages/keystone/src/lib/core/queries/resolvers.ts
+++ b/packages/keystone/src/lib/core/queries/resolvers.ts
@@ -59,7 +59,7 @@ export async function accessControlledFilter(
// Merge declarative access control
if (typeof access === 'object') {
- resolvedWhere = { AND: [resolvedWhere, await resolveWhereInput(access, list)] };
+ resolvedWhere = { AND: [resolvedWhere, await resolveWhereInput(access, list, context)] };
}
return resolvedWhere;
@@ -86,7 +86,7 @@ export async function findOne(
}
export async function findMany(
- { where, first, skip, orderBy: rawOrderBy }: FindManyArgsValue,
+ { where, take, skip, orderBy: rawOrderBy }: FindManyArgsValue,
list: InitialisedList,
context: KeystoneContext,
info: GraphQLResolveInfo,
@@ -94,16 +94,16 @@ export async function findMany(
): Promise {
const orderBy = await resolveOrderBy(rawOrderBy, list, context);
- applyEarlyMaxResults(first, list);
+ applyEarlyMaxResults(take, list);
- let resolvedWhere = await resolveWhereInput(where || {}, list);
+ let resolvedWhere = await resolveWhereInput(where, list, context);
resolvedWhere = await accessControlledFilter(list, context, resolvedWhere);
const results = await runWithPrisma(context, list, model =>
model.findMany({
where: extraFilter === undefined ? resolvedWhere : { AND: [resolvedWhere, extraFilter] },
orderBy,
- take: first ?? undefined,
+ take: take ?? undefined,
skip,
})
);
@@ -166,7 +166,7 @@ export async function count(
info: GraphQLResolveInfo,
extraFilter?: PrismaFilter
) {
- let resolvedWhere = await resolveWhereInput(where || {}, list);
+ let resolvedWhere = await resolveWhereInput(where, list, context);
resolvedWhere = await accessControlledFilter(list, context, resolvedWhere);
const count = await runWithPrisma(context, list, model =>
@@ -186,16 +186,16 @@ export async function count(
return count;
}
-function applyEarlyMaxResults(_first: number | null | undefined, list: InitialisedList) {
- const first = _first ?? Infinity;
+function applyEarlyMaxResults(_take: number | null | undefined, list: InitialisedList) {
+ const take = _take ?? Infinity;
// We want to help devs by failing fast and noisily if limits are violated.
// Unfortunately, we can't always be sure of intent.
- // E.g., if the query has a "first: 10", is it bad if more results could come back?
+ // E.g., if the query has a "take: 10", is it bad if more results could come back?
// Maybe yes, or maybe the dev is just paginating posts.
// But we can be sure there's a problem in two cases:
- // * The query explicitly has a "first" that exceeds the limit
- // * The query has no "first", and has more results than the limit
- if (first < Infinity && first > list.maxResults) {
+ // * The query explicitly has a "take" that exceeds the limit
+ // * The query has no "take", and has more results than the limit
+ if (take < Infinity && take > list.maxResults) {
throw limitsExceededError({ list: list.listKey, type: 'maxResults', limit: list.maxResults });
}
}
diff --git a/packages/keystone/src/lib/core/types-for-lists.ts b/packages/keystone/src/lib/core/types-for-lists.ts
index 18ab3c6b78d..d4408407ebc 100644
--- a/packages/keystone/src/lib/core/types-for-lists.ts
+++ b/packages/keystone/src/lib/core/types-for-lists.ts
@@ -22,7 +22,6 @@ import {
} from './access-control';
import { getNamesFromList } from './utils';
import { ResolvedDBField, resolveRelationships } from './resolve-relationships';
-import { InputFilter, PrismaFilter, resolveWhereInput } from './where-inputs';
import { outputTypeField } from './queries/output-field';
import { assertFieldsValid } from './field-assertions';
@@ -37,7 +36,6 @@ export type InitialisedList = {
/** This will include the opposites to one-sided relationships */
resolvedDbFields: Record;
pluralGraphQLName: string;
- filterImpls: Record PrismaFilter>;
types: TypesForList;
access: ResolvedListAccessControl;
hooks: ListHooks;
@@ -111,9 +109,12 @@ export function initialiseLists(
{
AND: schema.arg({ type: schema.list(schema.nonNull(where)) }),
OR: schema.arg({ type: schema.list(schema.nonNull(where)) }),
+ NOT: schema.arg({ type: schema.list(schema.nonNull(where)) }),
},
- ...Object.values(fields).map(field =>
- field.access.read === false ? {} : field.__legacy?.filters?.fields ?? {}
+ ...Object.entries(fields).map(
+ ([fieldKey, field]) =>
+ field.input?.where?.arg &&
+ field.access.read !== false && { [fieldKey]: field.input?.where?.arg }
)
);
},
@@ -165,7 +166,7 @@ export function initialiseLists(
defaultValue: [],
}),
// TODO: non-nullable when max results is specified in the list with the default of max results
- first: schema.arg({ type: schema.Int }),
+ take: schema.arg({ type: schema.Int }),
skip: schema.arg({ type: schema.nonNull(schema.Int), defaultValue: 0 }),
};
@@ -234,7 +235,18 @@ export function initialiseLists(
update,
findManyArgs,
relateTo: {
- many: { create: relateToManyForCreate, update: relateToManyForUpdate },
+ many: {
+ where: schema.inputObject({
+ name: `${listKey}ManyRelationFilter`,
+ fields: {
+ every: schema.arg({ type: where }),
+ some: schema.arg({ type: where }),
+ none: schema.arg({ type: where }),
+ },
+ }),
+ create: relateToManyForCreate,
+ update: relateToManyForUpdate,
+ },
one: { create: relateToOneForCreate, update: relateToOneForUpdate },
},
},
@@ -304,27 +316,6 @@ export function initialiseLists(
...listInfos[listKey],
...listsWithResolvedDBFields[listKey],
hooks: list.hooks || {},
- filterImpls: Object.assign(
- {},
- ...Object.values(list.fields).map(field => {
- if (field.dbField.kind === 'relation' && field.__legacy?.filters) {
- const foreignListKey = field.dbField.list;
- return Object.fromEntries(
- Object.entries(field.__legacy.filters.impls).map(([key, resolve]) => {
- return [
- key,
- (val: any) =>
- resolve(val, foreignListWhereInput =>
- resolveWhereInput(foreignListWhereInput, lists[foreignListKey])
- ),
- ];
- })
- );
- } else {
- return field.__legacy?.filters?.impls ?? {};
- }
- })
- ),
cacheHint: (() => {
const cacheHint = listsConfig[listKey].graphql?.cacheHint;
if (cacheHint === undefined) {
diff --git a/packages/keystone/src/lib/core/where-inputs.ts b/packages/keystone/src/lib/core/where-inputs.ts
index 3228f89e657..e6bfab810c6 100644
--- a/packages/keystone/src/lib/core/where-inputs.ts
+++ b/packages/keystone/src/lib/core/where-inputs.ts
@@ -1,5 +1,6 @@
-import { KeystoneContext } from '@keystone-next/types';
+import { DBField, KeystoneContext } from '@keystone-next/types';
import { InitialisedList } from './types-for-lists';
+import { getDBFieldKeyForFieldOnMultiField } from './utils';
export type InputFilter = Record & {
_____?: 'input filter';
@@ -47,20 +48,91 @@ export async function resolveUniqueWhereInput(
export async function resolveWhereInput(
inputFilter: InputFilter,
- list: InitialisedList
+ list: InitialisedList,
+ context: KeystoneContext
): Promise {
return {
AND: await Promise.all(
Object.entries(inputFilter).map(async ([fieldKey, value]) => {
- if (fieldKey === 'OR' || fieldKey === 'AND') {
+ if (fieldKey === 'OR' || fieldKey === 'AND' || fieldKey === 'NOT') {
return {
[fieldKey]: await Promise.all(
- value.map((value: any) => resolveWhereInput(value, list))
+ value.map((value: any) => resolveWhereInput(value, list, context))
),
};
}
- return list.filterImpls[fieldKey](value);
+ const field = list.fields[fieldKey];
+ // we know if there are filters in the input object with the key of a field, the field must have defined a where input so this non null assertion is okay
+ const where = field.input!.where!;
+ const dbField = field.dbField;
+ const ret = where.resolve
+ ? await where.resolve(
+ value,
+ context,
+ (() => {
+ if (field.dbField.kind !== 'relation') {
+ return undefined as any;
+ }
+ const foreignList = field.dbField.list;
+ const whereResolver = (val: any) =>
+ resolveWhereInput(val, list.lists[foreignList], context);
+ if (field.dbField.mode === 'many') {
+ return async () => {
+ if (value === null) {
+ throw new Error('A many relation filter cannot be set to null');
+ }
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(value).map(async ([key, val]) => {
+ if (val === null) {
+ throw new Error(
+ `The key ${key} in a many relation filter cannot be set to null`
+ );
+ }
+ return [key, await whereResolver(val as any)];
+ })
+ )
+ );
+ };
+ }
+ return (val: any) => {
+ if (val === null) {
+ return null;
+ }
+ return whereResolver(val);
+ };
+ })()
+ )
+ : value;
+ if (ret === null) {
+ if (field.dbField.kind === 'multi') {
+ throw new Error('multi db fields cannot return null from where input resolvers');
+ }
+ return { [fieldKey]: null };
+ }
+ return handleOperators(fieldKey, dbField, ret);
})
),
};
}
+
+function handleOperators(fieldKey: string, dbField: DBField, { AND, OR, NOT, ...rest }: any) {
+ return {
+ AND: AND?.map((value: any) => handleOperators(fieldKey, dbField, value)),
+ OR: OR?.map((value: any) => handleOperators(fieldKey, dbField, value)),
+ NOT: NOT?.map((value: any) => handleOperators(fieldKey, dbField, value)),
+ ...nestWithAppropiateField(fieldKey, dbField, rest),
+ };
+}
+
+function nestWithAppropiateField(fieldKey: string, dbField: DBField, value: any) {
+ if (dbField.kind === 'multi') {
+ return Object.fromEntries(
+ Object.entries(value).map(([key, val]) => [
+ getDBFieldKeyForFieldOnMultiField(fieldKey, key),
+ val,
+ ])
+ );
+ }
+ return { [fieldKey]: value };
+}
diff --git a/packages/keystone/src/lib/id-field.ts b/packages/keystone/src/lib/id-field.ts
index 39065785c9d..5f3fd34136f 100644
--- a/packages/keystone/src/lib/id-field.ts
+++ b/packages/keystone/src/lib/id-field.ts
@@ -43,6 +43,63 @@ const idParsers = {
},
};
+const nonCircularFields = {
+ equals: schema.arg({ type: schema.ID }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.ID)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.ID)) }),
+ lt: schema.arg({ type: schema.ID }),
+ lte: schema.arg({ type: schema.ID }),
+ gt: schema.arg({ type: schema.ID }),
+ gte: schema.arg({ type: schema.ID }),
+};
+
+type IDFilterType = schema.InputObjectType<
+ typeof nonCircularFields & {
+ not: schema.Arg;
+ }
+>;
+
+const IDFilter: IDFilterType = schema.inputObject({
+ name: 'IDFilter',
+ fields: () => ({
+ ...nonCircularFields,
+ not: schema.arg({ type: IDFilter }),
+ }),
+});
+
+const filterArg = schema.arg({ type: IDFilter });
+
+function resolveVal(
+ input: Exclude, undefined>,
+ kind: IdFieldConfig['kind']
+): any {
+ if (input === null) {
+ throw new Error('id filter cannot be null');
+ }
+ const idParser = idParsers[kind];
+ const obj: any = {};
+ for (const key of ['equals', 'gt', 'gte', 'lt', 'lte'] as const) {
+ const val = input[key];
+ if (val !== undefined) {
+ const parsed = idParser(val);
+ obj[key] = parsed;
+ }
+ }
+ for (const key of ['in', 'notIn'] as const) {
+ const val = input[key];
+ if (val !== undefined) {
+ if (val === null) {
+ throw new Error(`${key} id filter cannot be null`);
+ }
+ obj[key] = val.map(x => idParser(x));
+ }
+ }
+ if (input.not !== undefined) {
+ obj.not = resolveVal(input.not, kind);
+ }
+ return obj;
+}
+
export const idFieldType =
(config: IdFieldConfig): FieldTypeFunc =>
meta => {
@@ -55,6 +112,12 @@ export const idFieldType =
default: { kind: config.kind },
})({
input: {
+ where: {
+ arg: filterArg,
+ resolve(val) {
+ return resolveVal(val, config.kind);
+ },
+ },
uniqueWhere: { arg: schema.arg({ type: schema.ID }), resolve: parseVal },
orderBy: { arg: schema.arg({ type: orderDirectionEnum }) },
},
diff --git a/packages/keystone/src/lib/server/createApolloServer.ts b/packages/keystone/src/lib/server/createApolloServer.ts
index 70cce699e4d..af4d51b3985 100644
--- a/packages/keystone/src/lib/server/createApolloServer.ts
+++ b/packages/keystone/src/lib/server/createApolloServer.ts
@@ -1,22 +1,22 @@
import type { IncomingMessage, ServerResponse } from 'http';
-import { GraphQLSchema } from 'graphql';
+import { GraphQLError, GraphQLSchema } from 'graphql';
import { ApolloServer as ApolloServerMicro } from 'apollo-server-micro';
import { ApolloServer as ApolloServerExpress } from 'apollo-server-express';
import type { Config } from 'apollo-server-express';
-import type { CreateContext, SessionStrategy } from '@keystone-next/types';
+import type { CreateContext, GraphQLConfig, SessionStrategy } from '@keystone-next/types';
import { createSessionContext } from '../../session';
export const createApolloServerMicro = ({
graphQLSchema,
createContext,
sessionStrategy,
- apolloConfig,
+ graphqlConfig,
connectionPromise,
}: {
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy;
- apolloConfig?: Config;
+ graphqlConfig?: GraphQLConfig;
connectionPromise: Promise;
}) => {
const context = async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
@@ -28,7 +28,7 @@ export const createApolloServerMicro = ({
req,
});
};
- const serverConfig = _createApolloServerConfig({ graphQLSchema, apolloConfig });
+ const serverConfig = _createApolloServerConfig({ graphQLSchema, graphqlConfig });
return new ApolloServerMicro({ ...serverConfig, context });
};
@@ -36,12 +36,12 @@ export const createApolloServerExpress = ({
graphQLSchema,
createContext,
sessionStrategy,
- apolloConfig,
+ graphqlConfig,
}: {
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy;
- apolloConfig?: Config;
+ graphqlConfig?: GraphQLConfig;
}) => {
const context = async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) =>
createContext({
@@ -50,18 +50,19 @@ export const createApolloServerExpress = ({
: undefined,
req,
});
- const serverConfig = _createApolloServerConfig({ graphQLSchema, apolloConfig });
+ const serverConfig = _createApolloServerConfig({ graphQLSchema, graphqlConfig });
return new ApolloServerExpress({ ...serverConfig, context });
};
const _createApolloServerConfig = ({
graphQLSchema,
- apolloConfig,
+ graphqlConfig,
}: {
graphQLSchema: GraphQLSchema;
- apolloConfig?: Config;
+ graphqlConfig?: GraphQLConfig;
}) => {
// Playground config, is /api/graphql available?
+ const apolloConfig = graphqlConfig?.apolloConfig;
const pp = apolloConfig?.playground;
let playground: Config['playground'];
const settings = { 'request.credentials': 'same-origin' };
@@ -86,6 +87,7 @@ const _createApolloServerConfig = ({
return {
uploads: false,
schema: graphQLSchema,
+ debug: graphqlConfig?.debug, // If undefined, use Apollo default of NODE_ENV !== 'production'
// FIXME: support for apollo studio tracing
// ...(process.env.ENGINE_API_KEY || process.env.APOLLO_KEY
// ? { tracing: true }
@@ -97,6 +99,7 @@ const _createApolloServerConfig = ({
// tracing: dev,
// }),
...apolloConfig,
+ formatError: formatError(graphqlConfig),
// Carefully inject the playground
playground,
// FIXME: Support for file handling configuration
@@ -104,3 +107,24 @@ const _createApolloServerConfig = ({
// maxFiles: 5,
};
};
+
+const formatError = (graphqlConfig: GraphQLConfig | undefined) => {
+ return (err: GraphQLError) => {
+ let debug = graphqlConfig?.debug;
+ if (debug === undefined) {
+ debug = process.env.NODE_ENV !== 'production';
+ }
+
+ if (!debug && err.extensions) {
+ // Strip out any `debug` extensions
+ delete err.extensions.debug;
+ delete err.extensions.exception;
+ }
+
+ if (graphqlConfig?.apolloConfig?.formatError) {
+ return graphqlConfig.apolloConfig.formatError(err);
+ } else {
+ return err;
+ }
+ };
+};
diff --git a/packages/keystone/src/lib/server/createExpressServer.ts b/packages/keystone/src/lib/server/createExpressServer.ts
index f4342538093..e94fe7c1297 100644
--- a/packages/keystone/src/lib/server/createExpressServer.ts
+++ b/packages/keystone/src/lib/server/createExpressServer.ts
@@ -1,9 +1,13 @@
-import type { Config } from 'apollo-server-express';
import cors, { CorsOptions } from 'cors';
import express from 'express';
import { GraphQLSchema } from 'graphql';
import { graphqlUploadExpress } from 'graphql-upload';
-import type { KeystoneConfig, CreateContext, SessionStrategy } from '@keystone-next/types';
+import type {
+ KeystoneConfig,
+ CreateContext,
+ SessionStrategy,
+ GraphQLConfig,
+} from '@keystone-next/types';
import { createAdminUIServer } from '../../admin-ui/system';
import { createApolloServerExpress } from './createApolloServer';
import { addHealthCheck } from './addHealthCheck';
@@ -16,20 +20,20 @@ const addApolloServer = ({
graphQLSchema,
createContext,
sessionStrategy,
- apolloConfig,
+ graphqlConfig,
}: {
server: express.Express;
config: KeystoneConfig;
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy;
- apolloConfig?: Config;
+ graphqlConfig?: GraphQLConfig;
}) => {
const apolloServer = createApolloServerExpress({
graphQLSchema,
createContext,
sessionStrategy,
- apolloConfig,
+ graphqlConfig,
});
const maxFileSize = config.server?.maxFileSize || DEFAULT_MAX_FILE_SIZE;
@@ -68,7 +72,7 @@ export const createExpressServer = async (
graphQLSchema,
createContext,
sessionStrategy: config.session,
- apolloConfig: config.graphql?.apolloConfig,
+ graphqlConfig: config.graphql,
});
if (config.ui?.isDisabled) {
diff --git a/packages/keystone/src/schema/schema.ts b/packages/keystone/src/schema/schema.ts
index c3694e83b19..1c84166a776 100644
--- a/packages/keystone/src/schema/schema.ts
+++ b/packages/keystone/src/schema/schema.ts
@@ -1,5 +1,5 @@
import type { GraphQLSchema } from 'graphql';
-import { mergeSchemas } from '@graphql-tools/merge';
+import { mergeSchemas } from '@graphql-tools/schema';
import type {
BaseFields,
diff --git a/packages/keystone/src/scripts/tests/__snapshots__/artifacts.test.ts.snap b/packages/keystone/src/scripts/tests/__snapshots__/artifacts.test.ts.snap
index d82fd130fba..40457901eb3 100644
--- a/packages/keystone/src/scripts/tests/__snapshots__/artifacts.test.ts.snap
+++ b/packages/keystone/src/scripts/tests/__snapshots__/artifacts.test.ts.snap
@@ -19,28 +19,48 @@ type Scalars = {
export type TodoWhereInput = {
readonly AND?: ReadonlyArray | TodoWhereInput | null;
readonly OR?: ReadonlyArray | TodoWhereInput | null;
- readonly id?: Scalars['ID'] | null;
- readonly id_not?: Scalars['ID'] | null;
- readonly id_lt?: Scalars['ID'] | null;
- readonly id_lte?: Scalars['ID'] | null;
- readonly id_gt?: Scalars['ID'] | null;
- readonly id_gte?: Scalars['ID'] | null;
- readonly id_in?: ReadonlyArray | Scalars['ID'] | null;
- readonly id_not_in?: ReadonlyArray | Scalars['ID'] | null;
- readonly title?: Scalars['String'] | null;
- readonly title_not?: Scalars['String'] | null;
- readonly title_contains?: Scalars['String'] | null;
- readonly title_not_contains?: Scalars['String'] | null;
- readonly title_in?:
- | ReadonlyArray
- | Scalars['String']
- | null
- | null;
- readonly title_not_in?:
- | ReadonlyArray
- | Scalars['String']
- | null
- | null;
+ readonly NOT?: ReadonlyArray | TodoWhereInput | null;
+ readonly id?: IDFilter | null;
+ readonly title?: StringNullableFilter | null;
+};
+
+export type IDFilter = {
+ readonly equals?: Scalars['ID'] | null;
+ readonly in?: ReadonlyArray | Scalars['ID'] | null;
+ readonly notIn?: ReadonlyArray | Scalars['ID'] | null;
+ readonly lt?: Scalars['ID'] | null;
+ readonly lte?: Scalars['ID'] | null;
+ readonly gt?: Scalars['ID'] | null;
+ readonly gte?: Scalars['ID'] | null;
+ readonly not?: IDFilter | null;
+};
+
+export type StringNullableFilter = {
+ readonly equals?: Scalars['String'] | null;
+ readonly in?: ReadonlyArray | Scalars['String'] | null;
+ readonly notIn?: ReadonlyArray | Scalars['String'] | null;
+ readonly lt?: Scalars['String'] | null;
+ readonly lte?: Scalars['String'] | null;
+ readonly gt?: Scalars['String'] | null;
+ readonly gte?: Scalars['String'] | null;
+ readonly contains?: Scalars['String'] | null;
+ readonly startsWith?: Scalars['String'] | null;
+ readonly endsWith?: Scalars['String'] | null;
+ readonly not?: NestedStringNullableFilter | null;
+};
+
+export type NestedStringNullableFilter = {
+ readonly equals?: Scalars['String'] | null;
+ readonly in?: ReadonlyArray | Scalars['String'] | null;
+ readonly notIn?: ReadonlyArray | Scalars['String'] | null;
+ readonly lt?: Scalars['String'] | null;
+ readonly lte?: Scalars['String'] | null;
+ readonly gt?: Scalars['String'] | null;
+ readonly gte?: Scalars['String'] | null;
+ readonly contains?: Scalars['String'] | null;
+ readonly startsWith?: Scalars['String'] | null;
+ readonly endsWith?: Scalars['String'] | null;
+ readonly not?: NestedStringNullableFilter | null;
};
export type TodoWhereUniqueInput = {
@@ -94,7 +114,7 @@ export type TodoListTypeInfo = {
listQuery: {
readonly where?: TodoWhereInput;
readonly orderBy?: ReadonlyArray | TodoOrderByInput;
- readonly first?: Scalars['Int'] | null;
+ readonly take?: Scalars['Int'] | null;
readonly skip?: Scalars['Int'];
};
};
diff --git a/packages/keystone/src/scripts/tests/build.test.ts b/packages/keystone/src/scripts/tests/build.test.ts
index 97908231a12..46c5b16a45e 100644
--- a/packages/keystone/src/scripts/tests/build.test.ts
+++ b/packages/keystone/src/scripts/tests/build.test.ts
@@ -77,7 +77,7 @@ test('build works with typescript without the user defining a babel config', asy
✨ Generating Keystone config code
✨ Building Admin UI
info - Using webpack 4. Reason: custom webpack configuration in next.config.js https://nextjs.org/docs/messages/webpack5
- info - Checking validity of types...
+ info - Skipping validation of types...
info - Creating an optimized production build...
info - Compiled successfully
info - Collecting page data...
diff --git a/packages/keystone/src/scripts/tests/fixtures/basic-project/schema.graphql b/packages/keystone/src/scripts/tests/fixtures/basic-project/schema.graphql
index 2f3cfe2ec31..b0e148762fd 100644
--- a/packages/keystone/src/scripts/tests/fixtures/basic-project/schema.graphql
+++ b/packages/keystone/src/scripts/tests/fixtures/basic-project/schema.graphql
@@ -6,20 +6,48 @@ type Todo {
input TodoWhereInput {
AND: [TodoWhereInput!]
OR: [TodoWhereInput!]
- id: ID
- id_not: ID
- id_lt: ID
- id_lte: ID
- id_gt: ID
- id_gte: ID
- id_in: [ID!]
- id_not_in: [ID!]
- title: String
- title_not: String
- title_contains: String
- title_not_contains: String
- title_in: [String]
- title_not_in: [String]
+ NOT: [TodoWhereInput!]
+ id: IDFilter
+ title: StringNullableFilter
+}
+
+input IDFilter {
+ equals: ID
+ in: [ID!]
+ notIn: [ID!]
+ lt: ID
+ lte: ID
+ gt: ID
+ gte: ID
+ not: IDFilter
+}
+
+input StringNullableFilter {
+ equals: String
+ in: [String!]
+ notIn: [String!]
+ lt: String
+ lte: String
+ gt: String
+ gte: String
+ contains: String
+ startsWith: String
+ endsWith: String
+ not: NestedStringNullableFilter
+}
+
+input NestedStringNullableFilter {
+ equals: String
+ in: [String!]
+ notIn: [String!]
+ lt: String
+ lte: String
+ gt: String
+ gte: String
+ contains: String
+ startsWith: String
+ endsWith: String
+ not: NestedStringNullableFilter
}
input TodoWhereUniqueInput {
@@ -70,7 +98,7 @@ type Query {
todos(
where: TodoWhereInput! = {}
orderBy: [TodoOrderByInput!]! = []
- first: Int
+ take: Int
skip: Int! = 0
): [Todo!]
todo(where: TodoWhereUniqueInput!): Todo
diff --git a/packages/keystone/src/session/index.ts b/packages/keystone/src/session/index.ts
index 3f16ecd60f2..9843ebe8c9b 100644
--- a/packages/keystone/src/session/index.ts
+++ b/packages/keystone/src/session/index.ts
@@ -87,11 +87,12 @@ export function statelessSessions({
}
return {
async get({ req }) {
- if (!req.headers.cookie) return;
- let cookies = cookie.parse(req.headers.cookie);
- if (!cookies[TOKEN_NAME]) return;
+ const cookies = cookie.parse(req.headers.cookie || '');
+ const bearer = req.headers.authorization?.replace('Bearer ', '');
+ const token = bearer || cookies[TOKEN_NAME];
+ if (!token) return;
try {
- return await Iron.unseal(cookies[TOKEN_NAME], secret, ironOptions);
+ return await Iron.unseal(token, secret, ironOptions);
} catch (err) {}
},
async end({ res }) {
diff --git a/packages/session-store-redis/package.json b/packages/session-store-redis/package.json
index 201aa83296d..32396318b80 100644
--- a/packages/session-store-redis/package.json
+++ b/packages/session-store-redis/package.json
@@ -8,7 +8,7 @@
"node": "^12.20 || >= 14.13"
},
"dependencies": {
- "@babel/runtime": "^7.14.8",
+ "@babel/runtime": "^7.15.3",
"@types/redis": "^2.8.31"
},
"devDependencies": {
diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md
index 1f2b8df452e..1b73d7bd019 100644
--- a/packages/testing/CHANGELOG.md
+++ b/packages/testing/CHANGELOG.md
@@ -1,5 +1,12 @@
# @keystone-next/testing
+## 1.1.1
+
+### Patch Changes
+
+- Updated dependencies [[`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`1cbcf54cb`](https://github.com/keystonejs/keystone/commit/1cbcf54cb1206461866b582865e3b1a8fc728f18), [`a92169d04`](https://github.com/keystonejs/keystone/commit/a92169d04e5a1a98deb8e757b8eae3b06fc66450), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f), [`f3014a627`](https://github.com/keystonejs/keystone/commit/f3014a627060c7cd86440a6937da5caecfd023a0), [`092df6678`](https://github.com/keystonejs/keystone/commit/092df6678cea18d639be16ad250ec4ecc9250f5a), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`6da56b80e`](https://github.com/keystonejs/keystone/commit/6da56b80e03c748a621afcca6c1ec2887fef7271), [`697efa354`](https://github.com/keystonejs/keystone/commit/697efa354b1066b3d4b6eb757ca704b458f45e93), [`c7e331d90`](https://github.com/keystonejs/keystone/commit/c7e331d90a28b2ed8236100097cb8d34a11fabe2), [`3a7a06b2c`](https://github.com/keystonejs/keystone/commit/3a7a06b2cc6b5ea157d34d925b15494b471899eb), [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f), [`78dac764e`](https://github.com/keystonejs/keystone/commit/78dac764e1860b33f9e2bd8cee6015abeaaa5ec4), [`399561b27`](https://github.com/keystonejs/keystone/commit/399561b2769ddd8f3d3fdf29838f5784404bb053), [`9d361c1c8`](https://github.com/keystonejs/keystone/commit/9d361c1c8625e1390f837b7318b63547d686a63b), [`0dcb1c95b`](https://github.com/keystonejs/keystone/commit/0dcb1c95b5200750cc8649485425f2ae40d023a3), [`94435ffee`](https://github.com/keystonejs/keystone/commit/94435ffee765824091899242e4a2f73c7356b524), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`56044e2a4`](https://github.com/keystonejs/keystone/commit/56044e2a425f4256b66475fd3b1a6342cd6c3bf9), [`f46fd32b7`](https://github.com/keystonejs/keystone/commit/f46fd32b7047dbb5ea2566859f7ecee8db5b0b15), [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8), [`8ea4eed55`](https://github.com/keystonejs/keystone/commit/8ea4eed55367aaa213f6b4ffb7473087498e39ae), [`e3fe6498d`](https://github.com/keystonejs/keystone/commit/e3fe6498dc36203d8080dff3c2e0c25f6c98733e), [`1030296d1`](https://github.com/keystonejs/keystone/commit/1030296d1f304dc44246e895089ac1f992e80590), [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c), [`8b2d179b2`](https://github.com/keystonejs/keystone/commit/8b2d179b2463d78b082182ca9afa8233109e0ba3), [`e3fefafcc`](https://github.com/keystonejs/keystone/commit/e3fefafcce6f8bf836c9bf0f4d931b8200ba41c7), [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda), [`686c0f1c4`](https://github.com/keystonejs/keystone/commit/686c0f1c4a1feb609e1584aa71738709bbbf984e), [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e), [`f5e64af37`](https://github.com/keystonejs/keystone/commit/f5e64af37df2eb460c89d89fa3c8924fb34970ed)]:
+ - @keystone-next/keystone@24.0.0
+
## 1.1.0
### Minor Changes
diff --git a/packages/testing/package.json b/packages/testing/package.json
index fc0d46f2c7f..4e1827333ea 100644
--- a/packages/testing/package.json
+++ b/packages/testing/package.json
@@ -1,7 +1,7 @@
{
"name": "@keystone-next/testing",
"description": "Tools to assist with testing Keystone projects",
- "version": "1.1.0",
+ "version": "1.1.1",
"author": "The KeystoneJS Development Team",
"license": "MIT",
"main": "dist/testing.cjs.js",
@@ -10,11 +10,11 @@
"node": "^12.20 || >= 14.13"
},
"dependencies": {
- "@keystone-next/keystone": "^23.0.0",
+ "@keystone-next/keystone": "^24.0.0",
"@types/supertest": "^2.0.11",
"express": "^4.17.1",
"memoize-one": "^5.2.1",
- "supertest": "^6.1.4"
+ "supertest": "^6.1.5"
},
"repository": "https://github.com/keystonejs/keystone/tree/master/packages/testing"
}
diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md
index b192772357c..152cc92c772 100644
--- a/packages/types/CHANGELOG.md
+++ b/packages/types/CHANGELOG.md
@@ -1,5 +1,131 @@
# @keystone-next/types
+## 24.0.0
+
+### Major Changes
+
+- [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `_ListKeyMeta` and `_toManyRelationshipFieldMeta` fields. You should use `listKeyCount` and `toManyRelationshipFieldCount` instead
+
+* [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed all arguments from `context.lists.List.count` and `context.db.lists.List.count` except for `where`.
+
+- [#6266](https://github.com/keystonejs/keystone/pull/6266) [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Renamed `first` argument in find many queries to `take` to align with Prisma.
+
+ ```graphql
+ type Query {
+ users(
+ where: UserWhereInput! = {}
+ orderBy: [UserOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [User!]
+ # ...
+ }
+
+ type User {
+ # ...
+ posts(
+ where: PostWhereInput! = {}
+ orderBy: [PostOrderByInput!]! = []
+ # previously was first: Int
+ take: Int
+ skip: Int! = 0
+ ): [Post!]
+ # ...
+ }
+ ```
+
+* [#6208](https://github.com/keystonejs/keystone/pull/6208) [`092df6678`](https://github.com/keystonejs/keystone/commit/092df6678cea18d639be16ad250ec4ecc9250f5a) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The create one mutation now requires a non-null `data` argument and the create many mutation accepts a list of `ItemCreateInput` directly instead of being nested inside of an object with the `ItemCreateInput` in a `data` field.
+
+ If you have a list called `Item`, `createItem` now looks like `createItem(data: ItemCreateInput!): Item` and `createItems` now looks like `createItems(data: [ItemCreateInput!]!): [Item]`.
+
+- [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `search` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `contains` filters instead.
+
+* [#6095](https://github.com/keystonejs/keystone/pull/6095) [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Updated filters to be nested instead of flattened and add top-level `NOT` operator. See the [Query Filter API docs](https://keystonejs.com/docs/apis/filters) and the upgrade guide for more information.
+
+ ```graphql
+ query {
+ posts(where: { title: { contains: "Something" } }) {
+ title
+ content
+ }
+ }
+ ```
+
+- [#6196](https://github.com/keystonejs/keystone/pull/6196) [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Removed `sortBy` argument from the GraphQL API for finding many items, Lists/DB API and to-many relationship fields. You should use `orderBy` instead.
+
+* [#6312](https://github.com/keystonejs/keystone/pull/6312) [`56044e2a4`](https://github.com/keystonejs/keystone/commit/56044e2a425f4256b66475fd3b1a6342cd6c3bf9) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - Updated `@graphql-ts/schema`. The second type parameter of `schema.Arg` exported from `@keystone-next/types` is now a boolean that defines whether or not the arg has a default value to make it easier to define circular input objects.
+
+- [#6217](https://github.com/keystonejs/keystone/pull/6217) [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been renamed to `disconnect` in to-one relationship inputs and the old `disconnect` field has been removed. There are also seperate input types for create and update where the input for create doesn't have `disconnect`. It's also now required that if you provide a to-one relationship input, you must provide exactly one field to the input.
+
+ If you have a list called `Item`, the to-one relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToOneForCreateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ }
+ input ItemRelateToOneForUpdateInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ disconnect: Boolean
+ }
+ ```
+
+* [#6224](https://github.com/keystonejs/keystone/pull/6224) [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - `disconnectAll` has been replaced by `set` in to-many relationship inputs, the equivalent to `disconnectAll: true` is now `set: []`. There are also seperate input types for create and update where the input for create doesn't have `disconnect` or `set`. The inputs in the lists in the input field are now also non-null.
+
+ If you have a list called `Item`, the to-many relationship inputs now look like this:
+
+ ```graphql
+ input ItemRelateToManyForCreateInput {
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ input ItemRelateToManyForUpdateInput {
+ disconnect: [ItemWhereUniqueInput!]
+ set: [ItemWhereUniqueInput!]
+ create: [ItemCreateInput!]
+ connect: [ItemWhereUniqueInput!]
+ }
+ ```
+
+- [#6197](https://github.com/keystonejs/keystone/pull/6197) [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The generated CRUD queries, and some of the input types, in the GraphQL API have been renamed.
+
+ If you have a list called `Item`, the query for multiple values, `allItems` will be renamed to `items`. The query for a single value, `Item`, will be renamed to `item`.
+
+ Also, the input type used in the `updateItems` mutation has been renamed from `ItemsUpdateInput` to `ItemUpdateArgs`.
+
+* [#6211](https://github.com/keystonejs/keystone/pull/6211) [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The update mutations now accept `where` unique inputs instead of only an `id` and the `where` and `data` arguments are non-null.
+
+ If you have a list called `Item`, the update mutations now look like this:
+
+ ```graphql
+ type Mutation {
+ updateItem(where: ItemWhereUniqueInput!, data: ItemUpdateInput!): Item
+ updateItems(data: [ItemUpdateArgs!]!): [Item]
+ }
+
+ input ItemUpdateArgs {
+ where: ItemWhereUniqueInput!
+ data: ItemUpdateInput!
+ }
+ ```
+
+- [#6206](https://github.com/keystonejs/keystone/pull/6206) [`f5e64af37`](https://github.com/keystonejs/keystone/commit/f5e64af37df2eb460c89d89fa3c8924fb34970ed) Thanks [@mitchellhamilton](https://github.com/mitchellhamilton)! - The delete mutations now accept `where` unique inputs instead of only an `id`.
+
+ If you have a list called `Item`, `deleteItem` now looks like `deleteItem(where: ItemWhereUniqueInput!): Item` and `deleteItems` now looks like `deleteItems(where: [ItemWhereUniqueInput!]!): [Item]`
+
+### Minor Changes
+
+- [#6267](https://github.com/keystonejs/keystone/pull/6267) [`1030296d1`](https://github.com/keystonejs/keystone/commit/1030296d1f304dc44246e895089ac1f992e80590) Thanks [@timleslie](https://github.com/timleslie)! - Added `config.graphql.debug` option, which can be used to control whether debug information such as stack traces are included in the errors returned by the GraphQL API.
+
+### Patch Changes
+
+- [#6249](https://github.com/keystonejs/keystone/pull/6249) [`8187ea019`](https://github.com/keystonejs/keystone/commit/8187ea019a212874f3c602573af3382c6f3bd3b2) Thanks [@timleslie](https://github.com/timleslie)! - Updated types to allow the `'id'` field in `ui.labelField`, `ui.listView.initialColumns`, and `ui.listView.initialSort`.
+
+- Updated dependencies [[`e9f3c42d5`](https://github.com/keystonejs/keystone/commit/e9f3c42d5b9d42872cecbd18fbe9bf9d7d53ed82), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`b696a9579`](https://github.com/keystonejs/keystone/commit/b696a9579b503db86f42776381e247c4e1a7409f), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`4f4f0351a`](https://github.com/keystonejs/keystone/commit/4f4f0351a056dea9d1614aa2a3a4789d66bb402d), [`272b97b3a`](https://github.com/keystonejs/keystone/commit/272b97b3a10c0dfada782171d55ef7ac6f47c98f), [`5cd8ffd6c`](https://github.com/keystonejs/keystone/commit/5cd8ffd6cb822dbee8555b47846a5019c4d2b1c3), [`874f2c405`](https://github.com/keystonejs/keystone/commit/874f2c4058c9cf006213e84b9ffcf39c5bf144e8), [`3564b342d`](https://github.com/keystonejs/keystone/commit/3564b342d6dc2127ae591d7ac055af9eae90543c), [`4d9f89f88`](https://github.com/keystonejs/keystone/commit/4d9f89f884e2bf984fdd74ca2cbb7874b25b9cda), [`d214e2f72`](https://github.com/keystonejs/keystone/commit/d214e2f72bae1c798e2415a38410d6063c333e2e)]:
+ - @keystone-next/fields@14.0.0
+
## 23.0.0
### Major Changes
diff --git a/packages/types/package.json b/packages/types/package.json
index ec7047c5305..e3fadba83dd 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-next/types",
- "version": "23.0.0",
+ "version": "24.0.0",
"license": "MIT",
"main": "dist/types.cjs.js",
"module": "dist/types.esm.js",
@@ -8,8 +8,9 @@
"node": "^12.20 || >= 14.13"
},
"dependencies": {
- "@graphql-ts/schema": "0.1.2",
- "@keystone-next/fields": "^13.0.0",
+ "@babel/runtime": "^7.15.3",
+ "@graphql-ts/schema": "0.2.0",
+ "@keystone-next/fields": "^14.0.0",
"apollo-server-types": "^0.9.0",
"cors": "^2.8.5",
"decimal.js": "10.3.1",
diff --git a/packages/types/src/config/index.ts b/packages/types/src/config/index.ts
index 0717d6c62aa..c7023c74d30 100644
--- a/packages/types/src/config/index.ts
+++ b/packages/types/src/config/index.ts
@@ -143,6 +143,40 @@ export type GraphQLConfig = {
* @see https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructor
*/
apolloConfig?: Config;
+ /*
+ * When an error is returned from the GraphQL API, Apollo can include a stacktrace
+ * indicating where the error occurred. When Keystone is processing mutations, it
+ * will sometimes captures more than one error at a time, and then group these into
+ * a single error returned from the GraphQL API. Each of these errors will include
+ * a stacktrace.
+ *
+ * In general both categories of stacktrace are useful for debugging while developing,
+ * but should not be exposed in production, and this is the default behaviour of Keystone.
+ *
+ * You can use the `debug` option to change this behaviour. A use case for this
+ * would be if you need to send the stacktraces to a log, but do not want to return them
+ * from the API. In this case you could set `debug: true` and use
+ * `apolloConfig.formatError` to log the stacktraces and then strip them out before
+ * returning the error.
+ *
+ * ```
+ * graphql: {
+ * debug: true,
+ * apolloConfig: {
+ * formatError: err => {
+ * console.error(err);
+ * delete err.extensions?.errors;
+ * delete err.extensions?.exception?.errors;
+ * delete err.extensions?.exception?.stacktrace;
+ * return err;
+ * },
+ * },
+ * }
+ * ```
+ * *
+ * Default: process.env.NODE_ENV !== 'production'
+ */
+ debug?: boolean;
};
// config.extendGraphqlSchema
diff --git a/packages/types/src/filters/enum-filter.ts b/packages/types/src/filters/enum-filter.ts
new file mode 100644
index 00000000000..c6d91228411
--- /dev/null
+++ b/packages/types/src/filters/enum-filter.ts
@@ -0,0 +1,76 @@
+import { schema } from '../schema';
+
+// yes, these two types have the fields but they're semantically different types
+// (even though, yes, having EnumFilter by defined as EnumNullableFilter, would be the same type but names would show up differently in editors for example)
+
+export type EnumNullableFilter> = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ // can be null
+ not: schema.Arg>;
+}>;
+
+export type EnumFilter> = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ not: schema.Arg>;
+}>;
+
+type EnumNullableListFilterType> = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+export function enumFilters>(
+ enumType: Enum
+): {
+ optional: EnumNullableFilter;
+ required: EnumFilter;
+ many: EnumNullableListFilterType;
+} {
+ const optional: EnumNullableFilter = schema.inputObject({
+ name: `${enumType.graphQLType.name}NullableFilter`,
+ fields: () => ({
+ equals: schema.arg({ type: enumType }),
+ in: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ not: schema.arg({ type: optional }),
+ }),
+ });
+ const required: EnumFilter = schema.inputObject({
+ name: `${enumType.graphQLType.name}Filter`,
+ fields: () => ({
+ equals: schema.arg({ type: enumType }),
+ in: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ not: schema.arg({ type: optional }),
+ }),
+ });
+ const many: EnumNullableListFilterType = schema.inputObject({
+ name: `${enumType.graphQLType.name}NullableListFilter`,
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ // can be null
+ has: schema.arg({ type: enumType }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(enumType)) }),
+ isEmpty: schema.arg({ type: enumType }),
+ }),
+ });
+ return {
+ optional,
+ required,
+ many,
+ };
+}
diff --git a/packages/types/src/filters/index.ts b/packages/types/src/filters/index.ts
new file mode 100644
index 00000000000..a83f16933d1
--- /dev/null
+++ b/packages/types/src/filters/index.ts
@@ -0,0 +1,88 @@
+export * as postgresql from './providers/postgresql';
+export * as sqlite from './providers/sqlite';
+
+type EntriesAssumingNoExtraProps = {
+ [Key in keyof T]-?: [Key, T[Key]];
+}[keyof T][];
+
+const objectEntriesButAssumeNoExtraProperties: (obj: T) => EntriesAssumingNoExtraProps =
+ Object.entries as any;
+
+type CommonFilter = {
+ equals?: T | null;
+ in?: readonly T[] | null;
+ notIn?: readonly T[] | null;
+ lt?: T | null;
+ lte?: T | null;
+ gt?: T | null;
+ gte?: T | null;
+ contains?: T | null;
+ startsWith?: T | null;
+ endsWith?: T | null;
+ not?: CommonFilter | null;
+};
+
+function internalResolveFilter(
+ entries: EntriesAssumingNoExtraProps>,
+ mode: 'default' | 'insensitive' | undefined
+): object {
+ const entry = entries.shift();
+ if (entry === undefined) return {};
+ const [key, val] = entry;
+ if (val == null) {
+ return {
+ AND: [{ [key]: val }, internalResolveFilter(entries, mode)],
+ };
+ }
+ switch (key) {
+ case 'equals':
+ case 'lt':
+ case 'lte':
+ case 'gt':
+ case 'gte':
+ case 'in':
+ case 'contains':
+ case 'startsWith':
+ case 'endsWith': {
+ return {
+ AND: [{ [key]: val, mode }, { not: null }, internalResolveFilter(entries, mode)],
+ };
+ }
+
+ case 'notIn': {
+ return {
+ AND: [
+ {
+ NOT: [
+ internalResolveFilter(objectEntriesButAssumeNoExtraProperties({ in: val }), mode),
+ ],
+ },
+ internalResolveFilter(entries, mode),
+ ],
+ };
+ }
+ case 'not': {
+ return {
+ AND: [
+ {
+ NOT: [internalResolveFilter(objectEntriesButAssumeNoExtraProperties(val) as any, mode)],
+ },
+ internalResolveFilter(entries, mode),
+ ],
+ };
+ }
+ }
+}
+
+export function resolveCommon(val: CommonFilter | null) {
+ if (val === null) return null;
+ return internalResolveFilter(objectEntriesButAssumeNoExtraProperties(val), undefined);
+}
+
+export function resolveString(
+ val: (CommonFilter & { mode?: 'default' | 'insensitive' | null }) | null
+) {
+ if (val === null) return null;
+ let { mode, ...rest } = val;
+ return internalResolveFilter(objectEntriesButAssumeNoExtraProperties(rest), mode as any);
+}
diff --git a/packages/types/src/filters/providers/postgresql.ts b/packages/types/src/filters/providers/postgresql.ts
new file mode 100644
index 00000000000..cac24d557da
--- /dev/null
+++ b/packages/types/src/filters/providers/postgresql.ts
@@ -0,0 +1,647 @@
+// Do not manually modify this file, it is automatically generated by the package at /prisma-utils in this repo.
+// Update the script if you need this file to be different
+
+import { schema } from '../../schema';
+
+import { QueryMode } from '../..';
+
+type StringNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ contains: schema.Arg;
+ startsWith: schema.Arg;
+ endsWith: schema.Arg;
+ mode: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const StringNullableFilter: StringNullableFilterType = schema.inputObject({
+ name: 'StringNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.String }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ contains: schema.arg({ type: schema.String }),
+ startsWith: schema.arg({ type: schema.String }),
+ endsWith: schema.arg({ type: schema.String }),
+ mode: schema.arg({ type: QueryMode }),
+ // can be null
+ not: schema.arg({ type: NestedStringNullableFilter }),
+ }),
+});
+
+type NestedStringNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ contains: schema.Arg;
+ startsWith: schema.Arg;
+ endsWith: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const NestedStringNullableFilter: NestedStringNullableFilterType = schema.inputObject({
+ name: 'NestedStringNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.String }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ contains: schema.arg({ type: schema.String }),
+ startsWith: schema.arg({ type: schema.String }),
+ endsWith: schema.arg({ type: schema.String }),
+ // can be null
+ not: schema.arg({ type: NestedStringNullableFilter }),
+ }),
+});
+
+type StringFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ contains: schema.Arg;
+ startsWith: schema.Arg;
+ endsWith: schema.Arg;
+ mode: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const StringFilter: StringFilterType = schema.inputObject({
+ name: 'StringFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.String }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ contains: schema.arg({ type: schema.String }),
+ startsWith: schema.arg({ type: schema.String }),
+ endsWith: schema.arg({ type: schema.String }),
+ mode: schema.arg({ type: QueryMode }),
+ not: schema.arg({ type: NestedStringFilter }),
+ }),
+});
+
+type NestedStringFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ contains: schema.Arg;
+ startsWith: schema.Arg;
+ endsWith: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const NestedStringFilter: NestedStringFilterType = schema.inputObject({
+ name: 'NestedStringFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.String }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ contains: schema.arg({ type: schema.String }),
+ startsWith: schema.arg({ type: schema.String }),
+ endsWith: schema.arg({ type: schema.String }),
+ not: schema.arg({ type: NestedStringFilter }),
+ }),
+});
+
+type StringNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const StringNullableListFilter: StringNullableListFilterType = schema.inputObject({
+ name: 'StringNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ has: schema.arg({ type: schema.String }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type BoolNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const BoolNullableFilter: BoolNullableFilterType = schema.inputObject({
+ name: 'BooleanNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.Boolean }),
+ // can be null
+ not: schema.arg({ type: BoolNullableFilter }),
+ }),
+});
+
+type BoolFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const BoolFilter: BoolFilterType = schema.inputObject({
+ name: 'BooleanFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.Boolean }),
+ not: schema.arg({ type: BoolFilter }),
+ }),
+});
+
+type BoolNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const BoolNullableListFilter: BoolNullableListFilterType = schema.inputObject({
+ name: 'BooleanNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.Boolean)) }),
+ // can be null
+ has: schema.arg({ type: schema.Boolean }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.Boolean)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.Boolean)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type IntNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const IntNullableFilter: IntNullableFilterType = schema.inputObject({
+ name: 'IntNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.Int }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ lt: schema.arg({ type: schema.Int }),
+ lte: schema.arg({ type: schema.Int }),
+ gt: schema.arg({ type: schema.Int }),
+ gte: schema.arg({ type: schema.Int }),
+ // can be null
+ not: schema.arg({ type: IntNullableFilter }),
+ }),
+});
+
+type IntFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const IntFilter: IntFilterType = schema.inputObject({
+ name: 'IntFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.Int }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ lt: schema.arg({ type: schema.Int }),
+ lte: schema.arg({ type: schema.Int }),
+ gt: schema.arg({ type: schema.Int }),
+ gte: schema.arg({ type: schema.Int }),
+ not: schema.arg({ type: IntFilter }),
+ }),
+});
+
+type IntNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const IntNullableListFilter: IntNullableListFilterType = schema.inputObject({
+ name: 'IntNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ // can be null
+ has: schema.arg({ type: schema.Int }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.Int)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type FloatNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const FloatNullableFilter: FloatNullableFilterType = schema.inputObject({
+ name: 'FloatNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.Float }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ lt: schema.arg({ type: schema.Float }),
+ lte: schema.arg({ type: schema.Float }),
+ gt: schema.arg({ type: schema.Float }),
+ gte: schema.arg({ type: schema.Float }),
+ // can be null
+ not: schema.arg({ type: FloatNullableFilter }),
+ }),
+});
+
+type FloatFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const FloatFilter: FloatFilterType = schema.inputObject({
+ name: 'FloatFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.Float }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ lt: schema.arg({ type: schema.Float }),
+ lte: schema.arg({ type: schema.Float }),
+ gt: schema.arg({ type: schema.Float }),
+ gte: schema.arg({ type: schema.Float }),
+ not: schema.arg({ type: FloatFilter }),
+ }),
+});
+
+type FloatNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const FloatNullableListFilter: FloatNullableListFilterType = schema.inputObject({
+ name: 'FloatNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ // can be null
+ has: schema.arg({ type: schema.Float }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.Float)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type DateTimeNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const DateTimeNullableFilter: DateTimeNullableFilterType = schema.inputObject({
+ name: 'DateTimeNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.String }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ // can be null
+ not: schema.arg({ type: DateTimeNullableFilter }),
+ }),
+});
+
+type DateTimeFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const DateTimeFilter: DateTimeFilterType = schema.inputObject({
+ name: 'DateTimeFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.String }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ not: schema.arg({ type: DateTimeFilter }),
+ }),
+});
+
+type DateTimeNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const DateTimeNullableListFilter: DateTimeNullableListFilterType = schema.inputObject({
+ name: 'DateTimeNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ has: schema.arg({ type: schema.String }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type JsonNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const JsonNullableFilter: JsonNullableFilterType = schema.inputObject({
+ name: 'JsonNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.JSON }),
+ // can be null
+ not: schema.arg({ type: schema.JSON }),
+ }),
+});
+
+type JsonFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const JsonFilter: JsonFilterType = schema.inputObject({
+ name: 'JsonFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.JSON }),
+ not: schema.arg({ type: schema.JSON }),
+ }),
+});
+
+type JsonNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const JsonNullableListFilter: JsonNullableListFilterType = schema.inputObject({
+ name: 'JsonNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.JSON)) }),
+ // can be null
+ has: schema.arg({ type: schema.JSON }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.JSON)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.JSON)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+type DecimalNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const DecimalNullableFilter: DecimalNullableFilterType = schema.inputObject({
+ name: 'DecimalNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.String }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ // can be null
+ not: schema.arg({ type: DecimalNullableFilter }),
+ }),
+});
+
+type DecimalFilterType = schema.InputObjectType<{
+ equals: schema.Arg;
+ in: schema.Arg>>;
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ not: schema.Arg;
+}>;
+
+const DecimalFilter: DecimalFilterType = schema.inputObject({
+ name: 'DecimalFilter',
+ fields: () => ({
+ equals: schema.arg({ type: schema.String }),
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ not: schema.arg({ type: DecimalFilter }),
+ }),
+});
+
+type DecimalNullableListFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg>>;
+ // can be null
+ has: schema.Arg;
+ hasEvery: schema.Arg>>;
+ hasSome: schema.Arg>>;
+ isEmpty: schema.Arg;
+}>;
+
+const DecimalNullableListFilter: DecimalNullableListFilterType = schema.inputObject({
+ name: 'DecimalNullableListFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ has: schema.arg({ type: schema.String }),
+ hasEvery: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ hasSome: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ isEmpty: schema.arg({ type: schema.Boolean }),
+ }),
+});
+
+export const String = {
+ optional: StringNullableFilter,
+ required: StringFilter,
+ many: StringNullableListFilter,
+};
+
+export const Boolean = {
+ optional: BoolNullableFilter,
+ required: BoolFilter,
+ many: BoolNullableListFilter,
+};
+
+export const Int = {
+ optional: IntNullableFilter,
+ required: IntFilter,
+ many: IntNullableListFilter,
+};
+
+export const Float = {
+ optional: FloatNullableFilter,
+ required: FloatFilter,
+ many: FloatNullableListFilter,
+};
+
+export const DateTime = {
+ optional: DateTimeNullableFilter,
+ required: DateTimeFilter,
+ many: DateTimeNullableListFilter,
+};
+
+export const Json = {
+ optional: JsonNullableFilter,
+ required: JsonFilter,
+ many: JsonNullableListFilter,
+};
+
+export const Decimal = {
+ optional: DecimalNullableFilter,
+ required: DecimalFilter,
+ many: DecimalNullableListFilter,
+};
+
+export { enumFilters as enum } from '../enum-filter';
diff --git a/packages/types/src/filters/providers/sqlite.ts b/packages/types/src/filters/providers/sqlite.ts
new file mode 100644
index 00000000000..20591155798
--- /dev/null
+++ b/packages/types/src/filters/providers/sqlite.ts
@@ -0,0 +1,438 @@
+// Do not manually modify this file, it is automatically generated by the package at /prisma-utils in this repo.
+// Update the script if you need this file to be different
+
+import { schema } from '../../schema';
+
+type StringNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg;
+ contains: schema.Arg;
+ startsWith: schema.Arg;
+ endsWith: schema.Arg;
+ // can be null
+ not: schema.Arg;
+}>;
+
+const StringNullableFilter: StringNullableFilterType = schema.inputObject({
+ name: 'StringNullableFilter',
+ fields: () => ({
+ // can be null
+ equals: schema.arg({ type: schema.String }),
+ // can be null
+ in: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ // can be null
+ notIn: schema.arg({ type: schema.list(schema.nonNull(schema.String)) }),
+ lt: schema.arg({ type: schema.String }),
+ lte: schema.arg({ type: schema.String }),
+ gt: schema.arg({ type: schema.String }),
+ gte: schema.arg({ type: schema.String }),
+ contains: schema.arg({ type: schema.String }),
+ startsWith: schema.arg({ type: schema.String }),
+ endsWith: schema.arg({ type: schema.String }),
+ // can be null
+ not: schema.arg({ type: NestedStringNullableFilter }),
+ }),
+});
+
+type NestedStringNullableFilterType = schema.InputObjectType<{
+ // can be null
+ equals: schema.Arg;
+ // can be null
+ in: schema.Arg>>;
+ // can be null
+ notIn: schema.Arg>>;
+ lt: schema.Arg;
+ lte: schema.Arg;
+ gt: schema.Arg;
+ gte: schema.Arg