diff --git a/.yarn/cache/@esbuild-linux-x64-npm-0.19.5-26d0ff8fb5-8.zip b/.yarn/cache/@esbuild-linux-x64-npm-0.19.5-26d0ff8fb5-8.zip deleted file mode 100644 index 94ca828b..00000000 Binary files a/.yarn/cache/@esbuild-linux-x64-npm-0.19.5-26d0ff8fb5-8.zip and /dev/null differ diff --git a/.yarn/cache/@esbuild-win32-x64-npm-0.19.5-1e2e5abaa6-8.zip b/.yarn/cache/@esbuild-win32-x64-npm-0.19.5-1e2e5abaa6-8.zip deleted file mode 100644 index 96f5b36c..00000000 Binary files a/.yarn/cache/@esbuild-win32-x64-npm-0.19.5-1e2e5abaa6-8.zip and /dev/null differ diff --git a/.yarn/cache/@next-swc-linux-x64-gnu-npm-14.0.4-816eca9723-8.zip b/.yarn/cache/@next-swc-linux-x64-gnu-npm-14.0.4-816eca9723-8.zip deleted file mode 100644 index 8cd95b8c..00000000 Binary files a/.yarn/cache/@next-swc-linux-x64-gnu-npm-14.0.4-816eca9723-8.zip and /dev/null differ diff --git a/.yarn/cache/@next-swc-win32-x64-msvc-npm-14.0.4-e7cf0df5d6-8.zip b/.yarn/cache/@next-swc-win32-x64-msvc-npm-14.0.4-e7cf0df5d6-8.zip deleted file mode 100644 index e1d90d3a..00000000 Binary files a/.yarn/cache/@next-swc-win32-x64-msvc-npm-14.0.4-e7cf0df5d6-8.zip and /dev/null differ diff --git a/.yarn/cache/@prisma-client-npm-5.9.1-764ac1601e-549e7ec884.zip b/.yarn/cache/@prisma-client-npm-5.9.1-764ac1601e-549e7ec884.zip new file mode 100644 index 00000000..c80d228c Binary files /dev/null and b/.yarn/cache/@prisma-client-npm-5.9.1-764ac1601e-549e7ec884.zip differ diff --git a/.yarn/cache/@prisma-debug-npm-5.9.1-5ead12191a-ef041427aa.zip b/.yarn/cache/@prisma-debug-npm-5.9.1-5ead12191a-ef041427aa.zip new file mode 100644 index 00000000..61746e5b Binary files /dev/null and b/.yarn/cache/@prisma-debug-npm-5.9.1-5ead12191a-ef041427aa.zip differ diff --git a/.yarn/cache/@prisma-engines-npm-5.9.1-4d1998d255-24bcef09c0.zip b/.yarn/cache/@prisma-engines-npm-5.9.1-4d1998d255-24bcef09c0.zip new file mode 100644 index 00000000..896e7505 Binary files /dev/null and b/.yarn/cache/@prisma-engines-npm-5.9.1-4d1998d255-24bcef09c0.zip differ diff --git a/.yarn/cache/@prisma-engines-version-npm-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64-c7d3f36404-aae5655675.zip b/.yarn/cache/@prisma-engines-version-npm-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64-c7d3f36404-aae5655675.zip new file mode 100644 index 00000000..05254fad Binary files /dev/null and b/.yarn/cache/@prisma-engines-version-npm-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64-c7d3f36404-aae5655675.zip differ diff --git a/.yarn/cache/@prisma-fetch-engine-npm-5.9.1-ecdef657e6-359608c8d8.zip b/.yarn/cache/@prisma-fetch-engine-npm-5.9.1-ecdef657e6-359608c8d8.zip new file mode 100644 index 00000000..cd4dfde5 Binary files /dev/null and b/.yarn/cache/@prisma-fetch-engine-npm-5.9.1-ecdef657e6-359608c8d8.zip differ diff --git a/.yarn/cache/@prisma-get-platform-npm-5.9.1-9b99c820ee-bc1ec6984d.zip b/.yarn/cache/@prisma-get-platform-npm-5.9.1-9b99c820ee-bc1ec6984d.zip new file mode 100644 index 00000000..fd836225 Binary files /dev/null and b/.yarn/cache/@prisma-get-platform-npm-5.9.1-9b99c820ee-bc1ec6984d.zip differ diff --git a/.yarn/cache/@rollup-rollup-linux-x64-gnu-npm-4.1.4-f310dd9305-8.zip b/.yarn/cache/@rollup-rollup-linux-x64-gnu-npm-4.1.4-f310dd9305-8.zip deleted file mode 100644 index d85cefc3..00000000 Binary files a/.yarn/cache/@rollup-rollup-linux-x64-gnu-npm-4.1.4-f310dd9305-8.zip and /dev/null differ diff --git a/.yarn/cache/@rollup-rollup-win32-x64-msvc-npm-4.1.4-a9b7b64292-8.zip b/.yarn/cache/@rollup-rollup-win32-x64-msvc-npm-4.1.4-a9b7b64292-8.zip deleted file mode 100644 index 8c542e80..00000000 Binary files a/.yarn/cache/@rollup-rollup-win32-x64-msvc-npm-4.1.4-a9b7b64292-8.zip and /dev/null differ diff --git a/.yarn/cache/@swc-core-linux-x64-gnu-npm-1.3.78-c002456255-8.zip b/.yarn/cache/@swc-core-linux-x64-gnu-npm-1.3.78-c002456255-8.zip deleted file mode 100644 index 07f68dda..00000000 Binary files a/.yarn/cache/@swc-core-linux-x64-gnu-npm-1.3.78-c002456255-8.zip and /dev/null differ diff --git a/.yarn/cache/@swc-core-win32-x64-msvc-npm-1.3.78-3ed6a5ed32-8.zip b/.yarn/cache/@swc-core-win32-x64-msvc-npm-1.3.78-3ed6a5ed32-8.zip deleted file mode 100644 index 8d99b0e6..00000000 Binary files a/.yarn/cache/@swc-core-win32-x64-msvc-npm-1.3.78-3ed6a5ed32-8.zip and /dev/null differ diff --git a/.yarn/cache/prisma-npm-5.9.1-ab453feee9-3f93dc5c70.zip b/.yarn/cache/prisma-npm-5.9.1-ab453feee9-3f93dc5c70.zip new file mode 100644 index 00000000..996f4928 Binary files /dev/null and b/.yarn/cache/prisma-npm-5.9.1-ab453feee9-3f93dc5c70.zip differ diff --git a/package.json b/package.json index 19647c75..07f6dade 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@jest/types": "^29.6.3", + "@prisma/client": "^5.9.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@swc/core": "^1.3.78", @@ -85,6 +86,7 @@ "test/postgres", "test/sqlite", "test/next", - "test/typescript-esm" + "test/typescript-esm", + "test/prisma" ] } diff --git a/src/hooks/prisma.ts b/src/hooks/prisma.ts new file mode 100644 index 00000000..2c0eb8e9 --- /dev/null +++ b/src/hooks/prisma.ts @@ -0,0 +1,124 @@ +import assert from "node:assert"; + +import { ESTree } from "meriyah"; +import type prisma from "@prisma/client"; + +import type AppMap from "../AppMap"; +import { getTime } from "../util/getTime"; +import { fixReturnEventIfPromiseResult, recording } from "../recorder"; +import { FunctionInfo } from "../registry"; + +export default function prismaHook(mod: typeof prisma) { + assert("PrismaClient" in mod); + assert(mod.PrismaClient != null); + const PC = mod.PrismaClient as { prototype: unknown }; + const proto = PC.prototype; + assert(proto != null && typeof proto === "object"); + assert("_request" in proto); + proto._request = createProxy(proto._request as (...args: unknown[]) => unknown); + return mod; +} + +prismaHook.applicable = function (id: string) { + return ["@prisma/client"].includes(id); +}; + +// https://github.com/prisma/prisma/blob/095cba1a1b79d0d950246b07c9fb48d22fd7f229/packages/client/src/runtime/getPrismaClient.ts#L181 +interface QueryEvent { + timestamp: Date; + query: string; + params: string; + duration: number; + target: string; +} + +interface PrismaRequestParamsArgs { + data?: unknown; + include?: unknown; + where?: unknown; +} + +interface PrismaRequestParams { + action?: string; + model?: string; + args?: PrismaRequestParamsArgs; +} + +let hookAttached = false; + +function createProxy unknown>(prismaClientMethod: T) { + return new Proxy(prismaClientMethod, { + apply(target, thisArg: unknown, argArray: Parameters) { + if (!hookAttached) { + hookAttached = true; + assert( + thisArg != null && + typeof thisArg === "object" && + "_engine" in thisArg && + thisArg._engine != null && + typeof thisArg._engine === "object" && + "config" in thisArg._engine && + thisArg._engine.config != null && + typeof thisArg._engine.config === "object" && + "logLevel" in thisArg._engine.config && + "logQueries" in thisArg._engine.config && + "activeProvider" in thisArg._engine.config && + typeof thisArg._engine.config.activeProvider == "string", + ); + + const dbType = thisArg._engine.config.activeProvider; + thisArg._engine.config.logLevel = "query"; + thisArg._engine.config.logQueries = true; + 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); + }); + } + + // Report Prisma query as a function call, if suitable + let prismaCall: AppMap.FunctionCallEvent | undefined; + if (argArray?.length > 0) { + const requestParams = argArray[0] as PrismaRequestParams; + + const params = + requestParams.args != null + ? Object.keys(requestParams.args).map((k) => { + return { type: "Identifier", name: k } as ESTree.Identifier; + }) + : []; + + if (requestParams.action && requestParams.model) { + const info: FunctionInfo = { + async: true, + generator: false, + id: requestParams.action, + params: params, + location: { path: "unknown", lineno: 0 }, + klassOrFile: requestParams.model, + static: true, + }; + prismaCall = recording.functionCall( + info, + undefined, + params.map((p) => requestParams.args?.[p.name as keyof PrismaRequestParamsArgs]), + ); + } + } + + if (prismaCall) { + const start = getTime(); + try { + const result = target.apply(thisArg, argArray); + const ret = recording.functionReturn(prismaCall.id, result, getTime() - start); + return fixReturnEventIfPromiseResult(result, ret, prismaCall, start); + } catch (exn: unknown) { + recording.functionException(prismaCall.id, exn, getTime() - start); + throw exn; + } + } + + return target.apply(thisArg, argArray); + }, + }); +} diff --git a/src/loader.ts b/src/loader.ts index e8156382..8995093a 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -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)) + forceRequire(url); return defaultLoad(url, context, defaultLoad); }; diff --git a/src/requireHook.ts b/src/requireHook.ts index dbd7c0fa..3a295f7b 100644 --- a/src/requireHook.ts +++ b/src/requireHook.ts @@ -1,6 +1,7 @@ import httpHook from "./hooks/http"; import mysqlHook from "./hooks/mysql"; import pgHook from "./hooks/pg"; +import prismaHook from "./hooks/prisma"; import sqliteHook from "./hooks/sqlite"; interface Hook { @@ -9,7 +10,7 @@ interface Hook { applicable(id: string): boolean; } -const hooks: Hook[] = [httpHook, mysqlHook, pgHook, sqliteHook]; +const hooks: Hook[] = [httpHook, mysqlHook, pgHook, sqliteHook, prismaHook]; export default function requireHook( original: NodeJS.Require, diff --git a/test/__snapshots__/prisma.test.ts.snap b/test/__snapshots__/prisma.test.ts.snap new file mode 100644 index 00000000..0e62a1c6 --- /dev/null +++ b/test/__snapshots__/prisma.test.ts.snap @@ -0,0 +1,590 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`mapping Prisma tests 1`] = ` +{ + "classMap": [ + { + "children": [ + { + "children": [ + { + "location": "script.js:6", + "name": "main", + "static": true, + "type": "function", + }, + ], + "name": "script", + "type": "class", + }, + { + "children": [ + { + "location": "unknown:0", + "name": "deleteMany", + "static": true, + "type": "function", + }, + ], + "name": "Post", + "type": "class", + }, + { + "children": [ + { + "location": "unknown:0", + "name": "deleteMany", + "static": true, + "type": "function", + }, + { + "location": "unknown:0", + "name": "create", + "static": true, + "type": "function", + }, + { + "location": "unknown:0", + "name": "create", + "static": true, + "type": "function", + }, + { + "location": "unknown:0", + "name": "findMany", + "static": true, + "type": "function", + }, + ], + "name": "User", + "type": "class", + }, + ], + "name": "prisma-appmap-node-test", + "type": "package", + }, + ], + "eventUpdates": { + "16": { + "elapsed": 31.337, + "event": "return", + "id": 16, + "parent_id": 15, + "return_value": { + "class": "Promise", + "object_id": 5, + "value": "Promise { { id: 1, email: 'alice@prisma.io', name: 'Alice' } }", + }, + "thread_id": 0, + }, + "2": { + "elapsed": 31.337, + "event": "return", + "id": 2, + "parent_id": 1, + "return_value": { + "class": "Promise", + "object_id": 1, + "value": "Promise { undefined }", + }, + "thread_id": 0, + }, + "20": { + "elapsed": 31.337, + "event": "return", + "id": 20, + "parent_id": 19, + "return_value": { + "class": "Promise", + "object_id": 7, + "value": "Promise { { id: 2, email: 'bob@prisma.io', name: 'Bob' } }", + }, + "thread_id": 0, + }, + "32": { + "elapsed": 31.337, + "event": "return", + "id": 32, + "parent_id": 31, + "return_value": { + "class": "Promise", + "object_id": 10, + "value": "Promise { [ [Object] ] }", + }, + "thread_id": 0, + }, + "4": { + "elapsed": 31.337, + "event": "return", + "id": 4, + "parent_id": 3, + "return_value": { + "class": "Promise", + "object_id": 2, + "value": "Promise { { count: 0 } }", + }, + "thread_id": 0, + }, + "8": { + "elapsed": 31.337, + "event": "return", + "id": 8, + "parent_id": 7, + "return_value": { + "class": "Promise", + "object_id": 3, + "value": "Promise { { count: 0 } }", + }, + "thread_id": 0, + }, + }, + "events": [ + { + "defined_class": "script", + "event": "call", + "id": 1, + "lineno": 6, + "method_id": "main", + "parameters": [], + "path": "script.js", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 2, + "parent_id": 1, + "return_value": { + "class": "Promise", + "object_id": 1, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "defined_class": "Post", + "event": "call", + "id": 3, + "lineno": 0, + "method_id": "deleteMany", + "parameters": [], + "path": "unknown", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 4, + "parent_id": 3, + "return_value": { + "class": "Promise", + "object_id": 2, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "event": "call", + "id": 5, + "sql_query": { + "database_type": "sqlite", + "sql": "DELETE FROM \`main\`.\`Post\` WHERE 1=1", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 6, + "parent_id": 5, + "thread_id": 0, + }, + { + "defined_class": "User", + "event": "call", + "id": 7, + "lineno": 0, + "method_id": "deleteMany", + "parameters": [], + "path": "unknown", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 8, + "parent_id": 7, + "return_value": { + "class": "Promise", + "object_id": 3, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "event": "call", + "id": 9, + "sql_query": { + "database_type": "sqlite", + "sql": "DELETE FROM \`main\`.\`User\` WHERE 1=1", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 10, + "parent_id": 9, + "thread_id": 0, + }, + { + "event": "call", + "id": 11, + "sql_query": { + "database_type": "sqlite", + "sql": "delete from sqlite_sequence where name='Post'", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 12, + "parent_id": 11, + "thread_id": 0, + }, + { + "event": "call", + "id": 13, + "sql_query": { + "database_type": "sqlite", + "sql": "delete from sqlite_sequence where name='User'", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 14, + "parent_id": 13, + "thread_id": 0, + }, + { + "defined_class": "User", + "event": "call", + "id": 15, + "lineno": 0, + "method_id": "create", + "parameters": [ + { + "class": "Object", + "name": "data", + "object_id": 4, + "properties": [ + { + "class": "String", + "name": "name", + }, + { + "class": "String", + "name": "email", + }, + ], + "value": "{ name: 'Alice', email: 'alice@prisma.io' }", + }, + ], + "path": "unknown", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 16, + "parent_id": 15, + "return_value": { + "class": "Promise", + "object_id": 5, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "event": "call", + "id": 17, + "sql_query": { + "database_type": "sqlite", + "sql": "INSERT INTO \`main\`.\`User\` (\`email\`, \`name\`) VALUES (?,?) RETURNING \`id\` AS \`id\`, \`email\` AS \`email\`, \`name\` AS \`name\`", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 18, + "parent_id": 17, + "thread_id": 0, + }, + { + "defined_class": "User", + "event": "call", + "id": 19, + "lineno": 0, + "method_id": "create", + "parameters": [ + { + "class": "Object", + "name": "data", + "object_id": 6, + "properties": [ + { + "class": "String", + "name": "name", + }, + { + "class": "String", + "name": "email", + }, + { + "class": "Object", + "name": "posts", + "properties": [ + { + "class": "Object", + "name": "create", + "properties": [ + { + "class": "String", + "name": "title", + }, + ], + }, + ], + }, + ], + "value": "{ name: 'Bob', email: 'bob@prisma.io', posts: { create: [Object] } }", + }, + ], + "path": "unknown", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 20, + "parent_id": 19, + "return_value": { + "class": "Promise", + "object_id": 7, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "event": "call", + "id": 21, + "sql_query": { + "database_type": "sqlite", + "sql": "BEGIN", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 22, + "parent_id": 21, + "thread_id": 0, + }, + { + "event": "call", + "id": 23, + "sql_query": { + "database_type": "sqlite", + "sql": "INSERT INTO \`main\`.\`User\` (\`email\`, \`name\`) VALUES (?,?) RETURNING \`id\` AS \`id\`", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 24, + "parent_id": 23, + "thread_id": 0, + }, + { + "event": "call", + "id": 25, + "sql_query": { + "database_type": "sqlite", + "sql": "INSERT INTO \`main\`.\`Post\` (\`title\`, \`published\`, \`authorId\`) VALUES (?,?,?) RETURNING \`id\` AS \`id\`", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 26, + "parent_id": 25, + "thread_id": 0, + }, + { + "event": "call", + "id": 27, + "sql_query": { + "database_type": "sqlite", + "sql": "SELECT \`main\`.\`User\`.\`id\`, \`main\`.\`User\`.\`email\`, \`main\`.\`User\`.\`name\` FROM \`main\`.\`User\` WHERE \`main\`.\`User\`.\`id\` = ? LIMIT ? OFFSET ?", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 28, + "parent_id": 27, + "thread_id": 0, + }, + { + "event": "call", + "id": 29, + "sql_query": { + "database_type": "sqlite", + "sql": "COMMIT", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 30, + "parent_id": 29, + "thread_id": 0, + }, + { + "defined_class": "User", + "event": "call", + "id": 31, + "lineno": 0, + "method_id": "findMany", + "parameters": [ + { + "class": "Object", + "name": "include", + "object_id": 8, + "properties": [ + { + "class": "Boolean", + "name": "posts", + }, + ], + "value": "{ posts: true }", + }, + { + "class": "Object", + "name": "where", + "object_id": 9, + "properties": [ + { + "class": "Object", + "name": "name", + "properties": [ + { + "class": "String", + "name": "contains", + }, + ], + }, + ], + "value": "{ name: { contains: 'Bob' } }", + }, + ], + "path": "unknown", + "static": true, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 32, + "parent_id": 31, + "return_value": { + "class": "Promise", + "object_id": 10, + "value": "Promise { }", + }, + "thread_id": 0, + }, + { + "event": "call", + "id": 33, + "sql_query": { + "database_type": "sqlite", + "sql": "SELECT \`main\`.\`User\`.\`id\`, \`main\`.\`User\`.\`email\`, \`main\`.\`User\`.\`name\` FROM \`main\`.\`User\` WHERE \`main\`.\`User\`.\`name\` LIKE ? LIMIT ? OFFSET ?", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 34, + "parent_id": 33, + "thread_id": 0, + }, + { + "event": "call", + "id": 35, + "sql_query": { + "database_type": "sqlite", + "sql": "SELECT \`main\`.\`Post\`.\`id\`, \`main\`.\`Post\`.\`title\`, \`main\`.\`Post\`.\`content\`, \`main\`.\`Post\`.\`published\`, \`main\`.\`Post\`.\`authorId\` FROM \`main\`.\`Post\` WHERE \`main\`.\`Post\`.\`authorId\` IN (?) LIMIT ? OFFSET ?", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 36, + "parent_id": 35, + "thread_id": 0, + }, + { + "event": "call", + "id": 37, + "sql_query": { + "database_type": "sqlite", + "sql": "SELECT 1", + }, + "thread_id": 0, + }, + { + "elapsed": 31.337, + "event": "return", + "id": 38, + "parent_id": 37, + "thread_id": 0, + }, + ], + "metadata": { + "app": "prisma-appmap-node-test", + "client": { + "name": "appmap-node", + "url": "https://github.com/getappmap/appmap-node", + "version": "test node-appmap version", + }, + "language": { + "engine": "Node.js", + "name": "javascript", + "version": "test node version", + }, + "name": "test process recording", + "recorder": { + "name": "process", + "type": "process", + }, + }, + "version": "1.12", +} +`; diff --git a/test/prisma.test.ts b/test/prisma.test.ts new file mode 100644 index 00000000..ace3f057 --- /dev/null +++ b/test/prisma.test.ts @@ -0,0 +1,13 @@ +import { cwd } from "node:process"; +import { integrationTest, readAppmap, runAppmapNode } from "./helpers"; +import { copyFileSync, rmSync } from "node:fs"; +import { spawnSync } from "node:child_process"; + +integrationTest("mapping Prisma tests", () => { + const testDir = `${cwd()}/test/prisma`; + copyFileSync(`${testDir}/original.db`, `${testDir}/test.db`); + spawnSync("yarn", ["prisma", "generate"], { cwd: testDir, shell: true }); + expect(runAppmapNode("yarn", "node", "script.js").status).toBe(0); + expect(readAppmap()).toMatchSnapshot(); + rmSync(`${testDir}/test.db`); +}); diff --git a/test/prisma/.gitignore b/test/prisma/.gitignore new file mode 100644 index 00000000..c370cb64 --- /dev/null +++ b/test/prisma/.gitignore @@ -0,0 +1 @@ +test.db diff --git a/test/prisma/appmap.yml b/test/prisma/appmap.yml new file mode 100644 index 00000000..c68877c1 --- /dev/null +++ b/test/prisma/appmap.yml @@ -0,0 +1,2 @@ +name: prisma-appmap-node-test +appmap_dir: tmp/appmap diff --git a/test/prisma/original.db b/test/prisma/original.db new file mode 100644 index 00000000..0ed909d0 Binary files /dev/null and b/test/prisma/original.db differ diff --git a/test/prisma/package.json b/test/prisma/package.json new file mode 100644 index 00000000..17f9109f --- /dev/null +++ b/test/prisma/package.json @@ -0,0 +1,9 @@ +{ + "name": "prisma", + "packageManager": "yarn@3.6.3", + "private": true, + "dependencies": { + "@prisma/client": "5.9.1", + "prisma": "^5.9.1" + } +} diff --git a/test/prisma/schema.prisma b/test/prisma/schema.prisma new file mode 100644 index 00000000..3f14ff0e --- /dev/null +++ b/test/prisma/schema.prisma @@ -0,0 +1,27 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = "file:./test.db" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int +} \ No newline at end of file diff --git a/test/prisma/script.js b/test/prisma/script.js new file mode 100644 index 00000000..e1e46f85 --- /dev/null +++ b/test/prisma/script.js @@ -0,0 +1,58 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { PrismaClient } = require("@prisma/client"); + +const prisma = new PrismaClient(); + +async function main() { + await prisma.post.deleteMany(); + await prisma.user.deleteMany(); + await prisma.$executeRaw`delete from sqlite_sequence where name='Post'`; + await prisma.$executeRaw`delete from sqlite_sequence where name='User'`; + + const alice = await prisma.user.create({ + data: { + name: "Alice", + email: "alice@prisma.io", + }, + }); + + console.log(alice); + + const bob = await prisma.user.create({ + data: { + name: "Bob", + email: "bob@prisma.io", + posts: { + create: { + title: "Hello World", + }, + }, + }, + }); + + console.log(bob); + + const bobsWithPosts = await prisma.user.findMany({ + include: { + posts: true, + }, + where: { + name: { + contains: "Bob", + }, + }, + }); + console.log(bobsWithPosts); + + await prisma.$queryRaw`SELECT 1`; +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/yarn.lock b/yarn.lock index 593e9a1f..3502759d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1510,6 +1510,64 @@ __metadata: languageName: node linkType: hard +"@prisma/client@npm:5.9.1, @prisma/client@npm:^5.9.1": + version: 5.9.1 + resolution: "@prisma/client@npm:5.9.1" + peerDependencies: + prisma: "*" + peerDependenciesMeta: + prisma: + optional: true + checksum: 549e7ec884687b25dcd9f5a195a1e811d7191fc0b8dbd61c5ae126d14c8fa1914d93a8db7b890002a3c06e93a60366d1d60a2aa1218bd4fbb3ba7ec94a240cab + languageName: node + linkType: hard + +"@prisma/debug@npm:5.9.1": + version: 5.9.1 + resolution: "@prisma/debug@npm:5.9.1" + checksum: ef041427aa00772a5734f11ce2db9b285265a3d8cc8136167618877667da54b0973662a8bdcb0e919ad3f7325f9d371c7ed8e9ae58ca6d53ef576efb4eaedc27 + languageName: node + linkType: hard + +"@prisma/engines-version@npm:5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64": + version: 5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64 + resolution: "@prisma/engines-version@npm:5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64" + checksum: aae5655675ceb36daec8f0fb01f48c8b073075f95374b7039b5c0728c78a5df8c4bea0917d408c1f01a70a8c584240bb555d50ae8e7d3e3b6cf487ffbc1611b8 + languageName: node + linkType: hard + +"@prisma/engines@npm:5.9.1": + version: 5.9.1 + resolution: "@prisma/engines@npm:5.9.1" + dependencies: + "@prisma/debug": 5.9.1 + "@prisma/engines-version": 5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64 + "@prisma/fetch-engine": 5.9.1 + "@prisma/get-platform": 5.9.1 + checksum: 24bcef09c0d616a3e5a4b8eefa7fc070e23df0eff31ae57d50d97ad7b50a0b67b69f3c12a518833b6fe891aed4f3b6ca31f300017710232c169489f83b246e41 + languageName: node + linkType: hard + +"@prisma/fetch-engine@npm:5.9.1": + version: 5.9.1 + resolution: "@prisma/fetch-engine@npm:5.9.1" + dependencies: + "@prisma/debug": 5.9.1 + "@prisma/engines-version": 5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64 + "@prisma/get-platform": 5.9.1 + checksum: 359608c8d89ab8b074b431c858b9639f3bad54f4a1d91407106f721f91a63195ed55116413056d8e047a162ad8d7c73d16daa03682e41142ea4fcee17d283a0b + languageName: node + linkType: hard + +"@prisma/get-platform@npm:5.9.1": + version: 5.9.1 + resolution: "@prisma/get-platform@npm:5.9.1" + dependencies: + "@prisma/debug": 5.9.1 + checksum: bc1ec6984deb67e4f04907d0e58dfd50873476f9b010e468e2cbb6377959d7cef1ff945420271a6d638640b62a36552efd36f5f10097eb9ff5616a3111bde176 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.1.4": version: 4.1.4 resolution: "@rollup/rollup-android-arm-eabi@npm:4.1.4" @@ -2649,6 +2707,7 @@ __metadata: resolution: "appmap-node@workspace:." dependencies: "@jest/types": ^29.6.3 + "@prisma/client": ^5.9.1 "@semantic-release/changelog": ^6.0.3 "@semantic-release/git": ^10.0.1 "@swc/core": ^1.3.78 @@ -8246,6 +8305,26 @@ __metadata: languageName: node linkType: hard +"prisma@npm:^5.9.1": + version: 5.9.1 + resolution: "prisma@npm:5.9.1" + dependencies: + "@prisma/engines": 5.9.1 + bin: + prisma: build/index.js + checksum: 3f93dc5c705ae65b867efd40c5d0514ed0a8fb0eace656e7addaaf2def5273f7d41b05a0a8280ca3bd0d87e76d2de4d351fcf8bb61da8ada6df945f3608cb9d3 + languageName: node + linkType: hard + +"prisma@workspace:test/prisma": + version: 0.0.0-use.local + resolution: "prisma@workspace:test/prisma" + dependencies: + "@prisma/client": 5.9.1 + prisma: ^5.9.1 + languageName: unknown + linkType: soft + "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0"