From a71363b9a5227dd60eca9c0ca0ccedaf28410da9 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:38:17 -0400 Subject: [PATCH] prisma client extensions -- may mess up prisma client types --- apps/app/next.config.mjs | 2 +- packages/db/client/extensions/auditContext.ts | 8 +- packages/db/client/extensions/idGenerator.ts | 45 ++++++ packages/db/client/extensions/json.ts | 133 +++++++++--------- packages/db/client/index.ts | 41 +++--- packages/db/lib/idGen.ts | 3 +- 6 files changed, 138 insertions(+), 94 deletions(-) create mode 100644 packages/db/client/extensions/idGenerator.ts diff --git a/apps/app/next.config.mjs b/apps/app/next.config.mjs index 3d621882c0..84b9fe34d6 100644 --- a/apps/app/next.config.mjs +++ b/apps/app/next.config.mjs @@ -76,7 +76,7 @@ const nextConfig = { tunnelRoute: '/monitoring', // Hides source maps from generated client bundles - hideSourceMaps: true, + hideSourceMaps: !isLocalDev, // Automatically tree-shake Sentry logger statements to reduce bundle size disableLogger: isVercelProd || isVercelActiveDev, diff --git a/packages/db/client/extensions/auditContext.ts b/packages/db/client/extensions/auditContext.ts index 0b434c0c7f..b76edbaad1 100644 --- a/packages/db/client/extensions/auditContext.ts +++ b/packages/db/client/extensions/auditContext.ts @@ -2,16 +2,16 @@ import { Prisma, prisma } from '~db/client' import { isIdFor } from '~db/lib/idGen' function auditedExtension(actorId: string) { - return Prisma.defineExtension((prisma) => - prisma.$extends({ + return Prisma.defineExtension((client) => + client.$extends({ query: { $allModels: { async $allOperations({ args, query }) { if (!isIdFor('user', actorId)) { throw new Error('Invalid userId') } - const [, result] = await prisma.$transaction([ - prisma.$executeRaw`SELECT set_config('app.actor_id', ${actorId}, TRUE)`, + const [, result] = await client.$transaction([ + client.$executeRaw`SELECT set_config('app.actor_id', ${actorId}, TRUE)`, query(args), ]) return result diff --git a/packages/db/client/extensions/idGenerator.ts b/packages/db/client/extensions/idGenerator.ts new file mode 100644 index 0000000000..3a6464985d --- /dev/null +++ b/packages/db/client/extensions/idGenerator.ts @@ -0,0 +1,45 @@ +import { Prisma } from '~db/client' +import { generateId, idPrefix } from '~db/lib/idGen' + +const applicableModels = Object.keys(idPrefix) as (keyof typeof idPrefix)[] + +const isApplicableModel = (model: string | undefined): model is keyof typeof idPrefix => + Boolean(model) && applicableModels.includes(model as keyof typeof idPrefix) + +export const idGeneratorExtension = Prisma.defineExtension({ + name: 'Id Generator', + query: { + $allOperations({ args, model, operation, query }) { + if (model) { + model = model.charAt(0).toLowerCase() + model.slice(1) + } + if (isApplicableModel(model)) { + switch (operation) { + case 'create': { + if (args.data && !args.data.id) { + args.data.id = generateId(model) + } + break + } + case 'createMany': { + if (Array.isArray(args.data)) { + for (const item of args.data) { + if (!item.id) { + item.id = generateId(model) + } + } + } + break + } + case 'upsert': { + if (args.create && !args.create.id) { + args.create.id = generateId(model) + } + break + } + } + } + return query(args) + }, + }, +}) diff --git a/packages/db/client/extensions/json.ts b/packages/db/client/extensions/json.ts index b463b6a90f..4a7a158cb7 100644 --- a/packages/db/client/extensions/json.ts +++ b/packages/db/client/extensions/json.ts @@ -5,79 +5,78 @@ const deserialize = (data: unknown) => (isSuperJSONResult(data) ? superjson.dese const processData = (data: T) => (data ? superjson.stringify(data) : data) -export const jsonExtension = Prisma.defineExtension((prisma) => { - return prisma.$extends({ - result: { - attributeSupplement: { - data: { - needs: { data: true }, - compute({ data }) { - return deserialize(data) - }, +export const jsonExtension = Prisma.defineExtension({ + name: 'SuperJSON Serializer', + result: { + attributeSupplement: { + data: { + needs: { data: true }, + compute({ data }) { + return deserialize(data) }, }, - suggestion: { - data: { - needs: { data: true }, - compute({ data }) { - return deserialize(data) - }, + }, + suggestion: { + data: { + needs: { data: true }, + compute({ data }) { + return deserialize(data) }, }, }, - query: { - attributeSupplement: { - create({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - createMany({ args, query }) { - args.data = Array.isArray(args.data) ? args.data : [args.data] - for (const item of args.data) { - item.data = processData(item.data) - } - return query(args) - }, - update({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - updateMany({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - upsert({ args, query }) { - args.create.data = processData(args.create.data) - args.update.data = processData(args.update.data) - return query(args) - }, + }, + query: { + attributeSupplement: { + create({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) }, - suggestion: { - create({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - createMany({ args, query }) { - args.data = Array.isArray(args.data) ? args.data : [args.data] - for (const item of args.data) { - item.data = processData(item.data) - } - return query(args) - }, - update({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - updateMany({ args, query }) { - args.data.data = processData(args.data.data) - return query(args) - }, - upsert({ args, query }) { - args.create.data = processData(args.create.data) - args.update.data = processData(args.update.data) - return query(args) - }, + createMany({ args, query }) { + args.data = Array.isArray(args.data) ? args.data : [args.data] + for (const item of args.data) { + item.data = processData(item.data) + } + return query(args) + }, + update({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) + }, + updateMany({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) + }, + upsert({ args, query }) { + args.create.data = processData(args.create.data) + args.update.data = processData(args.update.data) + return query(args) + }, + }, + suggestion: { + create({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) + }, + createMany({ args, query }) { + args.data = Array.isArray(args.data) ? args.data : [args.data] + for (const item of args.data) { + item.data = processData(item.data) + } + return query(args) + }, + update({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) + }, + updateMany({ args, query }) { + args.data.data = processData(args.data.data) + return query(args) + }, + upsert({ args, query }) { + args.create.data = processData(args.create.data) + args.update.data = processData(args.update.data) + return query(args) }, }, - }) + }, }) diff --git a/packages/db/client/index.ts b/packages/db/client/index.ts index 2b295e2e11..9bb2e4f980 100644 --- a/packages/db/client/index.ts +++ b/packages/db/client/index.ts @@ -3,8 +3,8 @@ import { type Prisma, PrismaClient } from '@prisma/client' import { createPrismaQueryEventHandler } from 'prisma-query-log' import { createLoggerInstance } from '@weareinreach/util/logger' -import { idMiddleware } from '~db/lib/idMiddleware' -import { superjsonMiddleware } from '~db/lib/superjsonMiddleware' +import { idGeneratorExtension } from '~db/client/extensions/idGenerator' +import { jsonExtension } from '~db/client/extensions/json' const log = createLoggerInstance('prisma') const verboseLogging = Boolean( @@ -31,29 +31,30 @@ const clientOptions = { errorFormat: 'pretty', } satisfies Prisma.PrismaClientOptions -const prisma = global.prisma || new PrismaClient(clientOptions) +const generateClient = () => { + const client = new PrismaClient(clientOptions) -prisma.$use(idMiddleware) -prisma.$use(superjsonMiddleware) - -const queryLogger = createPrismaQueryEventHandler({ - queryDuration: true, - format: true, - indent: '\t', - // linesBetweenQueries: 2, - language: 'pl/sql', - logger: (data) => log.info(`\n${data}`), -}) - -if (!global.prisma) { if (verboseLogging) { - prisma.$on('query', queryLogger) + const queryLogger = createPrismaQueryEventHandler({ + queryDuration: true, + format: true, + indent: '\t', + language: 'pl/sql', + logger: (data) => log.info(`\n${data}`), + }) + client.$on('query', queryLogger) } else { - prisma.$on('error', (event) => log.error(event)) - prisma.$on('warn', (event) => log.warn(event)) + client.$on('error', (event) => log.error(event)) + client.$on('warn', (event) => log.warn(event)) } + + return client.$extends(jsonExtension).$extends(idGeneratorExtension) as unknown as PrismaClient< + typeof clientOptions + > } -// prisma.$connect() + +const prisma = global.prisma ?? generateClient() + if (process.env.NODE_ENV !== 'production') { global.prisma = prisma } diff --git a/packages/db/lib/idGen.ts b/packages/db/lib/idGen.ts index 0bc575b217..4b7ab136a1 100644 --- a/packages/db/lib/idGen.ts +++ b/packages/db/lib/idGen.ts @@ -91,8 +91,7 @@ export const isIdFor = (table: Uncapitalize, id: string) => { * @returns A table-prefixed ID */ export const generateId = (table: IdPrefix, seedTime?: Date | number) => { - const seedNum = - typeof seedTime === 'undefined' ? undefined : typeof seedTime === 'number' ? seedTime : seedTime.valueOf() + const seedNum = seedTime instanceof Date ? seedTime.valueOf() : seedTime const prefix = idPrefix[table] const id = Ulid.generate({ time: seedNum }).toCanonical()