diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 347a4e695ba..e303cc04bbd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -108,11 +108,18 @@ jobs: POSTGRES_DB: test_db ports: - 5432:5432 + mongodb: + image: mongo + env: + MONGO_INITDB_ROOT_USERNAME: keystone5 + MONGO_INITDB_ROOT_PASSWORD: k3yst0n3 + ports: + - 27017:27017 strategy: fail-fast: false matrix: index: [0, 1, 2, 3, 4, 5, 6, 7, 8] - adapter: ['postgresql', 'sqlite'] + adapter: ['postgresql', 'sqlite', 'mongodb'] steps: - name: Checkout Repo uses: actions/checkout@v2 @@ -153,7 +160,7 @@ jobs: CI_NODE_TOTAL: 9 CI_NODE_INDEX: ${{ matrix.index }} TEST_ADAPTER: ${{ matrix.adapter }} - DATABASE_URL: ${{ matrix.adapter == 'sqlite' && 'file:./dev.db' || 'postgres://keystone5:k3yst0n3@localhost:5432/test_db' }} + DATABASE_URL: ${{ matrix.adapter == 'sqlite' && 'file:./dev.db' || matrix.adapter == 'postgresql' && 'postgres://keystone5:k3yst0n3@localhost:5432/test_db' || 'mongodb://keystone5:k3yst0n3@localhost:27017/test_db' }} non-api-tests: name: Package Unit Tests diff --git a/packages/keystone/src/lib/config/initConfig.ts b/packages/keystone/src/lib/config/initConfig.ts index d22725854ca..db6f432c973 100644 --- a/packages/keystone/src/lib/config/initConfig.ts +++ b/packages/keystone/src/lib/config/initConfig.ts @@ -8,9 +8,9 @@ import { applyIdFieldDefaults } from './applyIdFieldDefaults'; */ export function initConfig(config: KeystoneConfig) { - if (!['postgresql', 'sqlite'].includes(config.db.provider)) { + if (!['postgresql', 'sqlite', 'mongodb'].includes(config.db.provider)) { throw new Error( - 'Invalid db configuration. Please specify db.provider as either "sqlite" or "postgresql"' + 'Invalid db configuration. Please specify db.provider as either "sqlite", "postgresql", or "mongodb"' ); } diff --git a/packages/keystone/src/lib/core/prisma-schema.ts b/packages/keystone/src/lib/core/prisma-schema.ts index 6bc58072dd9..ee968d35ff1 100644 --- a/packages/keystone/src/lib/core/prisma-schema.ts +++ b/packages/keystone/src/lib/core/prisma-schema.ts @@ -207,14 +207,24 @@ export function printPrismaSchema( datasource ${provider} { url = env("DATABASE_URL") provider = "${provider}" -} +}\n\n`; -generator client { + if (provider === 'mongodb') { + prismaSchema += `generator client { provider = "prisma-client-js" + previewFeatures = ["mongoDb"] output = "${clientDir}"${prismaFlags} engineType = "binary" } \n`; + } else { + prismaSchema += `generator client { + provider = "prisma-client-js" + output = "${clientDir}"${prismaFlags} + engineType = "binary" + } + \n`; + } for (const [listKey, { resolvedDbFields }] of Object.entries(lists)) { prismaSchema += `model ${listKey} {`; for (const [fieldPath, field] of Object.entries(resolvedDbFields)) { @@ -224,6 +234,9 @@ generator client { if (fieldPath === 'id') { assertDbFieldIsValidForIdField(listKey, field); prismaSchema += ' @id'; + if (provider === 'mongodb') { + prismaSchema += ' @map("_id")'; + } } } prismaSchema += `\n}\n`; diff --git a/packages/keystone/src/types/config/index.ts b/packages/keystone/src/types/config/index.ts index 4b4a340dffe..8826e7bc396 100644 --- a/packages/keystone/src/types/config/index.ts +++ b/packages/keystone/src/types/config/index.ts @@ -57,7 +57,7 @@ export type DatabaseConfig = { useMigrations?: boolean; enableLogging?: boolean; idField?: IdFieldConfig; - provider: 'postgresql' | 'sqlite'; + provider: 'postgresql' | 'sqlite' | 'mongodb'; prismaPreviewFeatures?: string[]; // https://www.prisma.io/docs/concepts/components/preview-features }; diff --git a/packages/keystone/src/types/core.ts b/packages/keystone/src/types/core.ts index ed16cb9443c..57066f8de99 100644 --- a/packages/keystone/src/types/core.ts +++ b/packages/keystone/src/types/core.ts @@ -3,7 +3,7 @@ import type { GraphQLResolveInfo } from 'graphql'; import type { GqlNames } from './utils'; import type { KeystoneContext, SessionContext } from './context'; -export type DatabaseProvider = 'sqlite' | 'postgresql'; +export type DatabaseProvider = 'sqlite' | 'postgresql' | 'mongodb'; export type CreateRequestContext = ( req: IncomingMessage, diff --git a/packages/keystone/src/types/filters/index.ts b/packages/keystone/src/types/filters/index.ts index a83f16933d1..367720515cd 100644 --- a/packages/keystone/src/types/filters/index.ts +++ b/packages/keystone/src/types/filters/index.ts @@ -1,5 +1,6 @@ export * as postgresql from './providers/postgresql'; export * as sqlite from './providers/sqlite'; +export * as mongodb from './providers/mongodb'; type EntriesAssumingNoExtraProps = { [Key in keyof T]-?: [Key, T[Key]]; diff --git a/packages/keystone/src/types/filters/providers/mongodb.ts b/packages/keystone/src/types/filters/providers/mongodb.ts new file mode 100644 index 00000000000..a35f1f8abd1 --- /dev/null +++ b/packages/keystone/src/types/filters/providers/mongodb.ts @@ -0,0 +1,444 @@ +// 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 { graphql } from '../../schema'; + +import { QueryMode } from '../../next-fields'; + +type StringNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + contains: graphql.Arg; + startsWith: graphql.Arg; + endsWith: graphql.Arg; + mode: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const StringNullableFilter: StringNullableFilterType = graphql.inputObject({ + name: 'StringNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.String }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + lt: graphql.arg({ type: graphql.String }), + lte: graphql.arg({ type: graphql.String }), + gt: graphql.arg({ type: graphql.String }), + gte: graphql.arg({ type: graphql.String }), + contains: graphql.arg({ type: graphql.String }), + startsWith: graphql.arg({ type: graphql.String }), + endsWith: graphql.arg({ type: graphql.String }), + mode: graphql.arg({ type: QueryMode }), + // can be null + not: graphql.arg({ type: NestedStringNullableFilter }), + }), +}); + +type NestedStringNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + contains: graphql.Arg; + startsWith: graphql.Arg; + endsWith: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const NestedStringNullableFilter: NestedStringNullableFilterType = graphql.inputObject({ + name: 'NestedStringNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.String }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + lt: graphql.arg({ type: graphql.String }), + lte: graphql.arg({ type: graphql.String }), + gt: graphql.arg({ type: graphql.String }), + gte: graphql.arg({ type: graphql.String }), + contains: graphql.arg({ type: graphql.String }), + startsWith: graphql.arg({ type: graphql.String }), + endsWith: graphql.arg({ type: graphql.String }), + // can be null + not: graphql.arg({ type: NestedStringNullableFilter }), + }), +}); + +type StringFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + contains: graphql.Arg; + startsWith: graphql.Arg; + endsWith: graphql.Arg; + mode: graphql.Arg; + not: graphql.Arg; +}>; + +const StringFilter: StringFilterType = graphql.inputObject({ + name: 'StringFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.String }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + lt: graphql.arg({ type: graphql.String }), + lte: graphql.arg({ type: graphql.String }), + gt: graphql.arg({ type: graphql.String }), + gte: graphql.arg({ type: graphql.String }), + contains: graphql.arg({ type: graphql.String }), + startsWith: graphql.arg({ type: graphql.String }), + endsWith: graphql.arg({ type: graphql.String }), + mode: graphql.arg({ type: QueryMode }), + not: graphql.arg({ type: NestedStringFilter }), + }), +}); + +type NestedStringFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + contains: graphql.Arg; + startsWith: graphql.Arg; + endsWith: graphql.Arg; + not: graphql.Arg; +}>; + +const NestedStringFilter: NestedStringFilterType = graphql.inputObject({ + name: 'NestedStringFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.String }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.String)) }), + lt: graphql.arg({ type: graphql.String }), + lte: graphql.arg({ type: graphql.String }), + gt: graphql.arg({ type: graphql.String }), + gte: graphql.arg({ type: graphql.String }), + contains: graphql.arg({ type: graphql.String }), + startsWith: graphql.arg({ type: graphql.String }), + endsWith: graphql.arg({ type: graphql.String }), + not: graphql.arg({ type: NestedStringFilter }), + }), +}); + +type BoolNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const BoolNullableFilter: BoolNullableFilterType = graphql.inputObject({ + name: 'BooleanNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.Boolean }), + // can be null + not: graphql.arg({ type: BoolNullableFilter }), + }), +}); + +type BoolFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + not: graphql.Arg; +}>; + +const BoolFilter: BoolFilterType = graphql.inputObject({ + name: 'BooleanFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.Boolean }), + not: graphql.arg({ type: BoolFilter }), + }), +}); + +type IntNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const IntNullableFilter: IntNullableFilterType = graphql.inputObject({ + name: 'IntNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.Int }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Int)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Int)) }), + lt: graphql.arg({ type: graphql.Int }), + lte: graphql.arg({ type: graphql.Int }), + gt: graphql.arg({ type: graphql.Int }), + gte: graphql.arg({ type: graphql.Int }), + // can be null + not: graphql.arg({ type: IntNullableFilter }), + }), +}); + +type IntFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + not: graphql.Arg; +}>; + +const IntFilter: IntFilterType = graphql.inputObject({ + name: 'IntFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.Int }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Int)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Int)) }), + lt: graphql.arg({ type: graphql.Int }), + lte: graphql.arg({ type: graphql.Int }), + gt: graphql.arg({ type: graphql.Int }), + gte: graphql.arg({ type: graphql.Int }), + not: graphql.arg({ type: IntFilter }), + }), +}); + +type FloatNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const FloatNullableFilter: FloatNullableFilterType = graphql.inputObject({ + name: 'FloatNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.Float }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Float)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Float)) }), + lt: graphql.arg({ type: graphql.Float }), + lte: graphql.arg({ type: graphql.Float }), + gt: graphql.arg({ type: graphql.Float }), + gte: graphql.arg({ type: graphql.Float }), + // can be null + not: graphql.arg({ type: FloatNullableFilter }), + }), +}); + +type FloatFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + not: graphql.Arg; +}>; + +const FloatFilter: FloatFilterType = graphql.inputObject({ + name: 'FloatFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.Float }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Float)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Float)) }), + lt: graphql.arg({ type: graphql.Float }), + lte: graphql.arg({ type: graphql.Float }), + gt: graphql.arg({ type: graphql.Float }), + gte: graphql.arg({ type: graphql.Float }), + not: graphql.arg({ type: FloatFilter }), + }), +}); + +type DateTimeNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const DateTimeNullableFilter: DateTimeNullableFilterType = graphql.inputObject({ + name: 'DateTimeNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.DateTime }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.DateTime)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.DateTime)) }), + lt: graphql.arg({ type: graphql.DateTime }), + lte: graphql.arg({ type: graphql.DateTime }), + gt: graphql.arg({ type: graphql.DateTime }), + gte: graphql.arg({ type: graphql.DateTime }), + // can be null + not: graphql.arg({ type: DateTimeNullableFilter }), + }), +}); + +type DateTimeFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + not: graphql.Arg; +}>; + +const DateTimeFilter: DateTimeFilterType = graphql.inputObject({ + name: 'DateTimeFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.DateTime }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.DateTime)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.DateTime)) }), + lt: graphql.arg({ type: graphql.DateTime }), + lte: graphql.arg({ type: graphql.DateTime }), + gt: graphql.arg({ type: graphql.DateTime }), + gte: graphql.arg({ type: graphql.DateTime }), + not: graphql.arg({ type: DateTimeFilter }), + }), +}); + +type DecimalNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + in: graphql.Arg>>; + // can be null + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const DecimalNullableFilter: DecimalNullableFilterType = graphql.inputObject({ + name: 'DecimalNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.Decimal }), + // can be null + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Decimal)) }), + // can be null + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Decimal)) }), + lt: graphql.arg({ type: graphql.Decimal }), + lte: graphql.arg({ type: graphql.Decimal }), + gt: graphql.arg({ type: graphql.Decimal }), + gte: graphql.arg({ type: graphql.Decimal }), + // can be null + not: graphql.arg({ type: DecimalNullableFilter }), + }), +}); + +type DecimalFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + in: graphql.Arg>>; + notIn: graphql.Arg>>; + lt: graphql.Arg; + lte: graphql.Arg; + gt: graphql.Arg; + gte: graphql.Arg; + not: graphql.Arg; +}>; + +const DecimalFilter: DecimalFilterType = graphql.inputObject({ + name: 'DecimalFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.Decimal }), + in: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Decimal)) }), + notIn: graphql.arg({ type: graphql.list(graphql.nonNull(graphql.Decimal)) }), + lt: graphql.arg({ type: graphql.Decimal }), + lte: graphql.arg({ type: graphql.Decimal }), + gt: graphql.arg({ type: graphql.Decimal }), + gte: graphql.arg({ type: graphql.Decimal }), + not: graphql.arg({ type: DecimalFilter }), + }), +}); + +export const String = { + optional: StringNullableFilter, + required: StringFilter, +}; + +export const Boolean = { + optional: BoolNullableFilter, + required: BoolFilter, +}; + +export const Int = { + optional: IntNullableFilter, + required: IntFilter, +}; + +export const Float = { + optional: FloatNullableFilter, + required: FloatFilter, +}; + +export const DateTime = { + optional: DateTimeNullableFilter, + required: DateTimeFilter, +}; + +export const Decimal = { + optional: DecimalNullableFilter, + required: DecimalFilter, +}; + +export { enumFilters as enum } from '../enum-filter'; diff --git a/prisma-utils/src/index.ts b/prisma-utils/src/index.ts index 2a93da9630c..01b3142a2b5 100644 --- a/prisma-utils/src/index.ts +++ b/prisma-utils/src/index.ts @@ -9,7 +9,7 @@ import { DMMF } from '@prisma/generator-helper'; import { getDMMF } from '@prisma/sdk'; import { format, resolveConfig } from 'prettier'; -const providers = ['postgresql', 'sqlite'] as const; +const providers = ['postgresql', 'sqlite', 'mongodb'] as const; type Provider = typeof providers[number]; @@ -34,12 +34,12 @@ generator client { } model Optional { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) ${provider === 'mongodb' ? '@map("_id")' : ''} ${scalarTypes.map(scalarType => `${scalarType} ${scalarType}?`).join('\n')} } model Required { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) ${provider === 'mongodb' ? '@map("_id")' : ''} ${scalarTypes.map(scalarType => `${scalarType} ${scalarType}`).join('\n')} } @@ -110,7 +110,7 @@ ${ const newContent = format( `// 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 { graphql } from '../../schema'; @@ -281,7 +281,7 @@ function printInputTypeForGraphQLTS( }) .join(',\n')} }> - + const ${inputTypeName}: ${nameOfInputObjectTypeType} = graphql.inputObject({ name: '${ // we want to use Boolean instead of Bool because GraphQL calls it Boolean