Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(deps): update prisma monorepo to v4 (major) #7671

Merged
merged 9 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-sloths-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': major
---

Updates to Prisma 4.1.0
dcousens marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions .changeset/slow-games-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-6/core': major
---

Changes the return type for the `resolveInput` hook with `json` fields. Previously you may have used `'DbNull'` or `'JsonNull'` as respective null magic values - you can now always use a Javascript `null` value.
Unlike previous behaviour, a null value will now consistently map to a `Prisma.DbNull`.
9 changes: 5 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
"@keystone-ui/tooltip": "^6.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@preconstruct/next": "^4.0.0",
"@prisma/client": "3.14.0",
"@prisma/migrate": "3.14.0",
"@prisma/sdk": "3.14.0",
"@prisma/client": "4.2.1",
"@prisma/internals": "4.2.1",
"@prisma/migrate": "4.2.1",
"@sindresorhus/slugify": "^1.1.2",
"@types/apollo-upload-client": "17.0.1",
"@types/bcryptjs": "^2.4.2",
Expand Down Expand Up @@ -113,13 +113,14 @@
"pirates": "4.0.4",
"pluralize": "^8.0.0",
"prettier": "^2.5.0",
"prisma": "3.14.0",
"prisma": "4.2.1",
"prompts": "^2.4.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"resolve": "^1.20.0",
"source-map-support": "^0.5.20",
"supertest": "^6.1.6",
"ts-toolbelt": "^9.6.0",
"uid-safe": "^2.1.5",
"uuid": "^8.3.2"
},
Expand Down
28 changes: 19 additions & 9 deletions packages/core/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'path';
import { createRequire } from 'module';
import { printSchema, GraphQLSchema } from 'graphql';
import * as fs from 'fs-extra';
import { getGenerator, formatSchema } from '@prisma/sdk';
import { getGenerator, formatSchema } from '@prisma/internals';
import { format } from 'prettier';
import type { KeystoneConfig } from './types';
import { confirmPrompt, shouldPrompt } from './lib/prompts';
Expand Down Expand Up @@ -54,17 +54,17 @@ async function ensurePrismaBinariesExist() {
// ensureBinariesExist does a bunch of slightly expensive things
// so if we can avoid running it a bunch in tests, that's ideal
if (hasEnsuredBinariesExist) return;
// we're resolving @prisma/engines from @prisma/sdk
// we're resolving @prisma/engines from @prisma/internals
// because we don't want to depend on @prisma/engines
// since its version includes a commit hash from https://github.com/prisma/prisma-engines
// and we just want to use whatever version @prisma/sdk is using
// also note we use an exact version of @prisma/sdk
// so if @prisma/sdk suddenly stops depending on @prisma/engines
// and we just want to use whatever version @prisma/internals is using
// also note we use an exact version of @prisma/internals
// so if @prisma/internals suddenly stops depending on @prisma/engines
// that won't break a released version of Keystone
// also, we're not just directly importing @prisma/engines
// since stricter package managers(e.g. pnpm, Yarn Berry)
// don't allow importing packages that aren't explicitly depended on
const requireFromPrismaSdk = createRequire(require.resolve('@prisma/sdk'));
const requireFromPrismaSdk = createRequire(require.resolve('@prisma/internals'));
const prismaEngines = requireFromPrismaSdk('@prisma/engines') as typeof import('@prisma/engines');
await prismaEngines.ensureBinariesExist();
hasEnsuredBinariesExist = true;
Expand Down Expand Up @@ -248,7 +248,10 @@ export async function generateNodeModulesArtifacts(
}

async function generatePrismaClient(cwd: string) {
const generator = await getGenerator({ schemaPath: getSchemaPaths(cwd).prisma });
const generator = await getGenerator({
schemaPath: getSchemaPaths(cwd).prisma,
dataProxy: false,
});
try {
await generator.generate();
} finally {
Expand All @@ -264,6 +267,13 @@ async function generatePrismaClient(cwd: string) {
}
}

export function requirePrismaClient(cwd: string) {
return require(path.join(cwd, 'node_modules/.prisma/client')).PrismaClient;
export type PrismaModule = {
PrismaClient: {
new (args: unknown): any;
};
Prisma: { DbNull: unknown; JsonNull: unknown; [key: string]: unknown };
};

export function requirePrismaClient(cwd: string): PrismaModule {
return require(path.join(cwd, 'node_modules/.prisma/client'));
}
14 changes: 3 additions & 11 deletions packages/core/src/fields/types/json/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ export const json =
throw Error("isIndexed: 'unique' is not a supported option for field type json");
}

const resolve = (val: JSONValue | undefined) =>
val === null && (meta.provider === 'postgresql' || meta.provider === 'mysql')
? 'DbNull'
: val;

return jsonFieldTypePolyfilledForSQLite(
meta.provider,
{
Expand All @@ -37,10 +32,10 @@ export const json =
create: {
arg: graphql.arg({ type: graphql.JSON }),
resolve(val) {
return resolve(val === undefined ? defaultValue : val);
return val === undefined ? defaultValue : val;
},
},
update: { arg: graphql.arg({ type: graphql.JSON }), resolve },
update: { arg: graphql.arg({ type: graphql.JSON }) },
},
output: graphql.field({ type: graphql.JSON }),
views: resolveView('json/views'),
Expand All @@ -50,10 +45,7 @@ export const json =
default:
defaultValue === null
? undefined
: {
kind: 'literal',
value: JSON.stringify(defaultValue),
},
: { kind: 'literal', value: JSON.stringify(defaultValue) },
map: config.db?.map,
}
);
Expand Down
26 changes: 22 additions & 4 deletions packages/core/src/lib/core/mutations/create-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IdType,
runWithPrisma,
getWriteLimit,
getPrismaNamespace,
} from '../utils';
import { InputFilter, resolveUniqueWhereInput, UniqueInputFilter } from '../where-inputs';
import {
Expand Down Expand Up @@ -363,7 +364,7 @@ async function resolveInputForCreateOrUpdate(
// Return the full resolved input (ready for prisma level operation),
// and the afterOperation hook to be applied
return {
data: transformForPrismaClient(list.fields, hookArgs.resolvedData),
data: transformForPrismaClient(list.fields, hookArgs.resolvedData, context),
afterOperation: async (updatedItem: BaseItem) => {
await nestedMutationState.afterOperation();
await runSideEffectOnlyHook(
Expand All @@ -381,19 +382,36 @@ async function resolveInputForCreateOrUpdate(
};
}

function transformInnerDBField(
dbField: Exclude<ResolvedDBField, { kind: 'multi' }>,
context: KeystoneContext,
value: unknown
) {
if (dbField.kind === 'scalar' && dbField.scalar === 'Json' && value === null) {
const Prisma = getPrismaNamespace(context);
return Prisma.DbNull;
}
return value;
}

function transformForPrismaClient(
fields: Record<string, { dbField: ResolvedDBField }>,
data: Record<string, any>
data: Record<string, any>,
context: KeystoneContext
) {
return Object.fromEntries(
Object.entries(data).flatMap(([fieldKey, value]) => {
const { dbField } = fields[fieldKey];
if (dbField.kind === 'multi') {
return Object.entries(value).map(([innerFieldKey, fieldValue]) => {
return [getDBFieldKeyForFieldOnMultiField(fieldKey, innerFieldKey), fieldValue];
return [
getDBFieldKeyForFieldOnMultiField(fieldKey, innerFieldKey),
transformInnerDBField(dbField.fields[innerFieldKey], context, fieldValue),
];
});
}
return [[fieldKey, value]];

return [[fieldKey, transformInnerDBField(dbField, context, value)]];
})
);
}
25 changes: 21 additions & 4 deletions packages/core/src/lib/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Limit } from 'p-limit';
import pluralize from 'pluralize';
import { PrismaModule } from '../../artifacts';
import { BaseItem, KeystoneConfig, KeystoneContext } from '../../types';
import { humanize } from '../utils';
import { prismaError } from './graphql-errors';
Expand Down Expand Up @@ -164,16 +165,32 @@ export function getDBFieldKeyForFieldOnMultiField(fieldKey: string, subField: st
// because even across requests, we want to apply the limit on SQLite
const writeLimits = new WeakMap<object, Limit>();

export const setWriteLimit = (prismaClient: object, limit: Limit) => {
export function setWriteLimit(prismaClient: object, limit: Limit) {
writeLimits.set(prismaClient, limit);
};
}

// this accepts the context instead of the prisma client because the prisma client on context is `any`
// so by accepting the context, it'll be less likely the wrong thing will be passed.
export const getWriteLimit = (context: KeystoneContext) => {
export function getWriteLimit(context: KeystoneContext) {
const limit = writeLimits.get(context.prisma);
if (limit === undefined) {
throw new Error('unexpected write limit not set for prisma client');
}
return limit;
};
}

const prismaNamespaces = new WeakMap<object, PrismaModule['Prisma']>();

export function setPrismaNamespace(prismaClient: object, prismaNamespace: PrismaModule['Prisma']) {
prismaNamespaces.set(prismaClient, prismaNamespace);
}

// this accepts the context instead of the prisma client because the prisma client on context is `any`
// so by accepting the context, it'll be less likely the wrong thing will be passed.
export function getPrismaNamespace(context: KeystoneContext) {
const limit = prismaNamespaces.get(context.prisma);
if (limit === undefined) {
throw new Error('unexpected prisma namespace not set for prisma client');
}
return limit;
}
8 changes: 5 additions & 3 deletions packages/core/src/lib/createSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import pLimit from 'p-limit';
import { FieldData, KeystoneConfig, getGqlNames } from '../types';

import { createAdminMeta } from '../admin-ui/system/createAdminMeta';
import { PrismaModule } from '../artifacts';
import { createGraphQLSchema } from './createGraphQLSchema';
import { makeCreateContext } from './context/createContext';
import { initialiseLists } from './core/types-for-lists';
import { setWriteLimit } from './core/utils';
import { setPrismaNamespace, setWriteLimit } from './core/utils';

function getSudoGraphQLSchema(config: KeystoneConfig) {
// This function creates a GraphQLSchema based on a modified version of the provided config.
Expand Down Expand Up @@ -72,12 +73,13 @@ export function createSystem(config: KeystoneConfig, isLiveReload?: boolean) {
return {
graphQLSchema,
adminMeta,
getKeystone: (PrismaClient: any) => {
const prismaClient = new PrismaClient({
getKeystone: (prismaModule: PrismaModule) => {
const prismaClient = new prismaModule.PrismaClient({
log: config.db.enableLogging ? ['query'] : undefined,
datasources: { [config.db.provider]: { url: config.db.url } },
});
setWriteLimit(prismaClient, pLimit(config.db.provider === 'sqlite' ? 1 : Infinity));
setPrismaNamespace(prismaClient, prismaModule.Prisma);
prismaClient.$on('beforeExit', async () => {
// Prisma is failing to properly clean up its child processes
// https://github.com/keystonejs/keystone/issues/5477
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/lib/migrations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path';
import { createDatabase, uriToCredentials, DatabaseCredentials } from '@prisma/sdk';
import { createDatabase, uriToCredentials, DatabaseCredentials } from '@prisma/internals';
import { Migrate } from '@prisma/migrate';
import chalk from 'chalk';
import slugify from '@sindresorhus/slugify';
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/lib/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @prisma/migrate's types seem to not be right
declare module '@prisma/migrate' {
export * from '@prisma/migrate/dist/migrate/src';
}
35 changes: 23 additions & 12 deletions packages/core/src/scripts/run/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,16 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => {
const p = serializePathForImport(
path.relative(path.join(getAdminPath(cwd), 'pages', 'api'), `${cwd}/keystone`)
);
const { adminMeta, graphQLSchema, createContext, prismaSchema, apolloServer, ...rest } =
await setupInitialKeystone(config, cwd, shouldDropDatabase);

const {
adminMeta,
graphQLSchema,
createContext,
prismaSchema,
apolloServer,
prismaClientModule,
...rest
} = await setupInitialKeystone(config, cwd, shouldDropDatabase);

if (configWithHTTP?.server?.extendHttpServer) {
const createRequestContext = async (req: IncomingMessage, res: ServerResponse) =>
Expand Down Expand Up @@ -195,8 +203,11 @@ exports.default = function (req, res) { return res.send(x.toString()) }

await generateNodeModulesArtifactsWithoutPrismaClient(graphQLSchema, newConfig, cwd);
await generateAdminUI(newConfig, graphQLSchema, adminMeta, getAdminPath(cwd), true);
const keystone = getKeystone(function fakePrismaClientClass() {
return prismaClient;
const keystone = getKeystone({
PrismaClient: function fakePrismaClientClass() {
return prismaClient;
} as unknown as new (args: unknown) => any,
Prisma: prismaClientModule.Prisma,
});
await keystone.connect();
const servers = await createExpressServer(
Expand Down Expand Up @@ -346,10 +357,8 @@ async function setupInitialKeystone(
// Generate the Artifacts
console.log('✨ Generating GraphQL and Prisma schemas');
const prismaSchema = (await generateCommittedArtifacts(graphQLSchema, config, cwd)).prisma;
let keystonePromise = generateNodeModulesArtifacts(graphQLSchema, config, cwd).then(() => {
const prismaClient = requirePrismaClient(cwd);
return getKeystone(prismaClient);
});

let prismaClientGenerationPromise = generateNodeModulesArtifacts(graphQLSchema, config, cwd);

let migrationPromise: Promise<void>;

Expand All @@ -372,8 +381,9 @@ async function setupInitialKeystone(
);
}

const [keystone] = await Promise.all([keystonePromise, migrationPromise]);
const { createContext } = keystone;
await Promise.all([prismaClientGenerationPromise, migrationPromise]);
const prismaClientModule = requirePrismaClient(cwd);
const keystone = getKeystone(prismaClientModule);

// Connect to the Database
console.log('✨ Connecting to the database');
Expand All @@ -384,7 +394,7 @@ async function setupInitialKeystone(
const { apolloServer, expressServer } = await createExpressServer(
config,
graphQLSchema,
createContext
keystone.createContext
);
console.log(`✅ GraphQL API ready`);

Expand All @@ -402,8 +412,9 @@ async function setupInitialKeystone(
expressServer,
apolloServer,
graphQLSchema,
createContext,
createContext: keystone.createContext,
prismaSchema,
prismaClientModule,
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/scripts/tests/migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async function setupAndStopDevServerForMigrations(cwd: string, resetDb: boolean
}

function getPrismaClient(cwd: string) {
const prismaClient = new (requirePrismaClient(cwd))({
const prismaClient = new (requirePrismaClient(cwd).PrismaClient)({
datasources: { sqlite: { url: dbUrl } },
});
return prismaClient;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/scripts/tests/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import fastGlob from 'fast-glob';
import fixturez from 'fixturez';
import outdent from 'outdent';
import { parseArgsStringToArgv } from 'string-argv';
import { IntrospectionEngine, uriToCredentials } from '@prisma/sdk';
import { IntrospectionEngine, uriToCredentials } from '@prisma/internals';
import { KeystoneConfig } from '../../types';
import { cli } from '../cli';
import { mockPrompts } from '../../lib/prompts';
Expand Down
4 changes: 2 additions & 2 deletions prisma-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"private": true,
"main": "dist/keystone-6-prisma-utils.cjs.js",
"dependencies": {
"@prisma/generator-helper": "3.14.0",
"@prisma/sdk": "3.14.0",
"@prisma/generator-helper": "4.2.1",
"@prisma/internals": "4.2.1",
"fs-extra": "^10.0.0",
"prettier": "^2.5.0"
},
Expand Down
2 changes: 1 addition & 1 deletion prisma-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { deepStrictEqual } from 'assert';
import { isDeepStrictEqual } from 'util';
import fs from 'fs-extra';
import { DMMF } from '@prisma/generator-helper';
import { getDMMF } from '@prisma/sdk';
import { getDMMF } from '@prisma/internals';
import { format, resolveConfig } from 'prettier';

const providers = ['postgresql', 'sqlite', 'mysql'] as const;
Expand Down
Loading