Skip to content

Commit

Permalink
prisma client extensions -- may mess up prisma client types
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeKarow committed Apr 10, 2024
1 parent cf3631f commit a71363b
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 94 deletions.
2 changes: 1 addition & 1 deletion apps/app/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions packages/db/client/extensions/auditContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions packages/db/client/extensions/idGenerator.ts
Original file line number Diff line number Diff line change
@@ -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)
},
},
})
133 changes: 66 additions & 67 deletions packages/db/client/extensions/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,78 @@ const deserialize = (data: unknown) => (isSuperJSONResult(data) ? superjson.dese

const processData = <T>(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)
},
},
})
},
})
41 changes: 21 additions & 20 deletions packages/db/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
}
Expand Down
3 changes: 1 addition & 2 deletions packages/db/lib/idGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ export const isIdFor = (table: Uncapitalize<Tables>, 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()
Expand Down

0 comments on commit a71363b

Please sign in to comment.