From a201b72251e2c36fca72941dddc21400131bd020 Mon Sep 17 00:00:00 2001 From: Tim Leslie Date: Wed, 14 Jul 2021 09:50:51 +1000 Subject: [PATCH] Add support for MongoDB --- .github/workflows/tests.yml | 11 +- .../keystone/src/lib/config/initConfig.ts | 4 +- .../keystone/src/lib/core/prisma-schema.ts | 18 +- packages/keystone/src/types/config/index.ts | 2 +- packages/keystone/src/types/core.ts | 2 +- packages/keystone/src/types/filters/index.ts | 1 + .../src/types/filters/providers/mongodb.ts | 479 ++++++++++++++++++ prisma-utils/src/index.ts | 10 +- 8 files changed, 513 insertions(+), 14 deletions(-) create mode 100644 packages/keystone/src/types/filters/providers/mongodb.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b9ae0f3e2c..44d0c32023b 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 b49caf57fe8..c6ef27df98a 100644 --- a/packages/keystone/src/lib/core/prisma-schema.ts +++ b/packages/keystone/src/lib/core/prisma-schema.ts @@ -206,14 +206,23 @@ 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" - output = "${clientDir}"${prismaFlags} + previewFeatures = ["mongoDb"] + output = "${clientDir}" engineType = "binary" } \n`; + } else { + prismaSchema += `generator client { + provider = "prisma-client-js" + output = "${clientDir}" + } + \n`; + } for (const [listKey, { resolvedDbFields }] of Object.entries(lists)) { prismaSchema += `model ${listKey} {`; for (const [fieldPath, field] of Object.entries(resolvedDbFields)) { @@ -223,6 +232,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 c25caf8478c..b32cca7a7ba 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 4dc9d53e012..7c870468d88 100644 --- a/packages/keystone/src/types/core.ts +++ b/packages/keystone/src/types/core.ts @@ -8,7 +8,7 @@ type FieldDefaultValueArgs = originalInput: TGeneratedListTypes['inputs']['create']; }; -export type DatabaseProvider = 'sqlite' | 'postgresql'; +export type DatabaseProvider = 'sqlite' | 'postgresql' | 'mongodb'; export type FieldDefaultValue = | T 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..ea1e0e73131 --- /dev/null +++ b/packages/keystone/src/types/filters/providers/mongodb.ts @@ -0,0 +1,479 @@ +// 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.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 }), + // 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.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 }), + not: graphql.arg({ type: DateTimeFilter }), + }), +}); + +type JsonNullableFilterType = graphql.InputObjectType<{ + // can be null + equals: graphql.Arg; + // can be null + not: graphql.Arg; +}>; + +const JsonNullableFilter: JsonNullableFilterType = graphql.inputObject({ + name: 'JsonNullableFilter', + fields: () => ({ + // can be null + equals: graphql.arg({ type: graphql.JSON }), + // can be null + not: graphql.arg({ type: graphql.JSON }), + }), +}); + +type JsonFilterType = graphql.InputObjectType<{ + equals: graphql.Arg; + not: graphql.Arg; +}>; + +const JsonFilter: JsonFilterType = graphql.inputObject({ + name: 'JsonFilter', + fields: () => ({ + equals: graphql.arg({ type: graphql.JSON }), + not: graphql.arg({ type: graphql.JSON }), + }), +}); + +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.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 }), + // 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.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 }), + 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 Json = { + optional: JsonNullableFilter, + required: JsonFilter, +}; + +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 5b8e29b4425..23580245e1a 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