-
Notifications
You must be signed in to change notification settings - Fork 4
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
feat: Prisma support #112
feat: Prisma support #112
Conversation
c392a63
to
17c06a5
Compare
src/hooks/prisma.ts
Outdated
} | ||
|
||
prismaHook.applicable = function (id: string) { | ||
return ["@prisma/client"].includes(id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we configure this? We have internal services which share a pre-configured, pre-built Prisma client, shipped as its own module. Because of this, we don't ever import @prisma/client
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth noting that I've tried this PR with our own service after modifying this line and it does work great. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make the module name (id) a configuration parameter, if I understand correctly.
src/hooks/prisma.ts
Outdated
assert("$on" in thisArg && typeof thisArg.$on === "function"); | ||
thisArg.$on("query", (queryEvent: QueryEvent) => { | ||
const call = recording.sqlQuery(dbType, queryEvent.query); | ||
recording.functionReturn(call.id, undefined, queryEvent.duration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd double check the unit of time here. I tried a request which issued three queries taking 2ms, 0ms, 1ms according to the logs. The AppMap states the duration was 2000ms, 0ms, 1000ms.
ef55baa
to
0a36fa0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job figuring this out! Just a couple of changes I'd suggest before merging. A mention in the README about support for custom prisma client locations would be nice, too.
.yarn/cache/@next-swc-win32-x64-msvc-npm-14.0.4-e7cf0df5d6-8.zip
Outdated
Show resolved
Hide resolved
src/hooks/prisma.ts
Outdated
generator: false, | ||
id: action, | ||
params: params, | ||
location: { path: "unknown", lineno: -1 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's generate location as @prisma/client/${model}
(or custom client name, eg. appmap-prisma-client/${model}
) — this will make it look much nicer in sequence diagrams. Also, the line numbers for different signatures should be different — I believe location is the key by which code objects are identified in the clients.
const call = recording.sqlQuery(dbType, queryEvent.query); | ||
const elapsedMs = queryEvent.duration / 1000.0; | ||
recording.functionReturn(call.id, undefined, elapsedMs); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's pretty clever :) Great job figuring this out!
src/hooks/prisma.ts
Outdated
assert("$on" in thisArg && typeof thisArg.$on === "function"); | ||
thisArg.$on("query", (queryEvent: QueryEvent) => { | ||
const call = recording.sqlQuery(dbType, queryEvent.query); | ||
const elapsedMs = queryEvent.duration / 1000.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should be called elapsedSec
— the doc says duration already is in ms.
const functionInfos = new Map<string, FunctionInfo>(); | ||
|
||
function getFunctionInfo(model: string, action: string, params: ESTree.Parameter[]) { | ||
const key = model + "." + action; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for different calls to the same action to be parametrized differently? If so, this will create just one signature for all of them which will be correct only for the first one. In this case it might make more sense to pass them to the map as a single argument, or explode them all into (data, include, where)
filling in undefined
where one isn't provided, to avoid having multiple signatures for the same thing in the AppMap (note they would also need to differ in location because that's how the client code distinguishes code objects).
src/loader.ts
Outdated
@@ -44,7 +44,8 @@ export const load: NodeLoaderHooksAPI2["load"] = async function load(url, contex | |||
// For these modules, we preempt import with CommonJS require | |||
// to allow our hooks to modify the loaded module in cache | |||
// (which is shared between ESM and CJS for builtins at least). | |||
if (["node:http", "node:https", "http", "https"].includes(url)) forceRequire(url); | |||
if (["node:http", "node:https", "http", "https", "@prisma/client"].includes(url)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should take custom clients into account.
test/prisma/script.js
Outdated
|
||
main() | ||
.then(async () => { | ||
await prisma.$disconnect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't it auto-disconnect? Maybe this then/catch
is not necessary.
1c00d6e
to
13a2735
Compare
src/loader.ts
Outdated
@@ -44,7 +45,8 @@ export const load: NodeLoaderHooksAPI2["load"] = async function load(url, contex | |||
// For these modules, we preempt import with CommonJS require | |||
// to allow our hooks to modify the loaded module in cache | |||
// (which is shared between ESM and CJS for builtins at least). | |||
if (["node:http", "node:https", "http", "https"].includes(url)) forceRequire(url); | |||
if (["node:http", "node:https", "http", "https", config.prismaClientModuleIds].includes(url)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a splat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh exactly, thank you.
|
||
integrationTest("mapping Prisma tests", () => { | ||
const testDir = `${cwd()}/test/prisma`; | ||
copyFileSync(`${testDir}/original.db`, `${testDir}/test.db`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to copy this over instead of creating it in situ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original.db consists of the required schema with empty tables. I didn't want to create it in every test run because creating it is not related to the test. test.db gets modified in every run so I wanted to provide a predefined db state for the test.
test/prisma.test.ts
Outdated
import { spawnSync } from "node:child_process"; | ||
|
||
integrationTest("mapping Prisma tests", () => { | ||
const testDir = `${cwd()}/test/prisma`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not very robust — what if jest is run from a different directory? You can use resolveTarget()
to get it.
src/hooks/prisma.ts
Outdated
requestParams.action, | ||
["data", "include", "where"].map((k) => { | ||
return { type: "Identifier", name: k } as ESTree.Identifier; | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this can be a constant in getFunctionInfo
now.
13a2735
to
1c1f24c
Compare
🎉 This PR is included in version 2.16.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Fixes #79