From e5c66db0797b7b58267bc08e30265e5fa47cb5b6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 30 Jun 2026 14:02:39 -0400 Subject: [PATCH 1/4] fix(drizzle): key the file tag per query instead of per session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build-time caller was stored in a WeakMap keyed by the drizzle session, which is shared across every query on a `drizzle()` instance. Two queries built before either executed collided on that single entry: the first to reach `prepareQuery` emitted both call sites joined and deleted the entry, the second emitted no `file` tag at all. This dropped/mis-attributed the tag under any real concurrency (two in-flight requests, `Promise.all`). `then -> execute -> _prepare -> session.prepareQuery` runs synchronously, so instead tag each built query object with its own caller and publish it only for that synchronous window. Concurrent queries can't interleave inside it, so each reads exactly its own caller — and it's robust to building and awaiting queries in different orders, which an ordered queue would not be. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sqlcommenter-drizzle/src/index.ts | 202 +++++++++++------- .../test/driver-integration.spec.ts | 75 +++++++ 2 files changed, 205 insertions(+), 72 deletions(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts index b01d8948..d12557c5 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/index.ts @@ -5,16 +5,27 @@ import { resolveFilePath } from "./path.js"; const LIBRARY_NAME = "sqlcommenter-drizzle"; -type QueryContext = { - queryStack: string[]; -}; - type DriverSession = { prepareQuery: (query: unknown) => unknown }; -// We don't own the Session object here so using a WeakMap to prevent memory leaks -// An alternative could be to set a Symbol in the Session to store the context -// but this approach seems a little bit safer as we avoid interfacing with the object at all -const contexts = new WeakMap(); +// The caller's source location has to be captured when a query is *built* (e.g. `db.select()`), +// because by the time the query actually executes the build-time stack frame is long gone. +// That caller then has to reach the `prepareQuery` patch that writes the comment. +// +// A single `drizzle()` instance shares ONE session object across every query it builds, so the +// caller cannot be keyed by the session: two queries built before either executes would collide +// on the same entry, mis-attributing one `file` tag and dropping the other under any real +// concurrency (e.g. two in-flight HTTP requests, or `Promise.all`). +// +// Instead we tag each built query object with its own caller, and publish that caller into +// `currentCaller` for the *synchronous* window in which that specific query reaches +// `prepareQuery` (`then`/`execute`/`prepare` -> `_prepare` -> `session.prepareQuery` runs with +// no `await` in between). Because the window is synchronous, concurrent queries can never +// interleave inside it, so each query reads exactly its own caller. +let currentCaller: string | undefined; + +// Marks a query object whose `then`/`execute`/`prepare` have already been wrapped, so chained +// rebuilds returning the same object aren't wrapped twice. +const TAGGED = Symbol("sqlcommenter-drizzle.tagged"); function isValidCaller(line: string): boolean { if (line.includes("node_modules")) { @@ -59,22 +70,91 @@ export function traceCaller(): string | undefined { } } -function patchMethod( - target: Function, - thisArg: unknown, - args: any[], - session: DriverSession, -) { - const caller = traceCaller(); - if (caller) { - const ctx = contexts.get(session); - if (ctx) { - ctx.queryStack.push(caller); - } else { - contexts.set(session, { queryStack: [caller] }); +/** + * Wraps `then`/`execute`/`prepare` on a built query so that, while the query synchronously + * reaches `prepareQuery`, its own build-time caller is the one published in `currentCaller`. + */ +function tagExecutable(executable: any, caller: string) { + if (!executable || typeof executable !== "object" || executable[TAGGED]) { + return; + } + executable[TAGGED] = true; + for (const method of ["then", "execute", "prepare"] as const) { + const original = executable[method]; + if (typeof original !== "function") { + continue; } + executable[method] = function (this: unknown, ...args: unknown[]) { + const previous = currentCaller; + currentCaller = caller; + try { + return original.apply(this, args); + } finally { + currentCaller = previous; + } + }; + } +} + +/** + * `db.select()`/`db.insert()`/`db.update()` return an intermediate *builder*; the executable + * query is produced one call later by `.from()`/`.values()`/`.set()`. We wrap the builder in a + * Proxy so that whatever its methods return is run back through `handleResult` and the eventual + * executable gets tagged with the caller captured at build time. + */ +function wrapBuilder(builder: any, caller: string): unknown { + return new Proxy(builder, { + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + if (typeof value !== "function") { + return value; + } + return function (this: unknown, ...args: unknown[]) { + return handleResult(value.apply(target, args), caller); + }; + }, + }); +} + +function handleResult(result: any, caller: string): unknown { + if (!result || typeof result !== "object") { + return result; + } + // A built, executable query (a drizzle QueryPromise) is thenable — tag it directly. + if (typeof result.then === "function") { + tagExecutable(result, caller); + return result; + } + // Otherwise it's still a builder; keep wrapping until the executable shows up. + return wrapBuilder(result, caller); +} + +/** + * Patches a builder-returning method (`select`/`insert`/`update`/`delete`/relational queries). + * The result is lazy, so we tag it and let `currentCaller` be set when it executes. + */ +function patchBuilderMethod(target: Function, thisArg: unknown, args: any[]) { + const caller = traceCaller(); + const result = Reflect.apply(target, thisArg, args); + return caller ? handleResult(result, caller) : result; +} + +/** + * Patches an eager method (`db.execute()`), which builds *and* reaches `prepareQuery` + * synchronously within this call, so the caller is published around the call itself. + */ +function patchImmediateMethod(target: Function, thisArg: unknown, args: any[]) { + const caller = traceCaller(); + if (!caller) { + return Reflect.apply(target, thisArg, args); + } + const previous = currentCaller; + currentCaller = caller; + try { + return Reflect.apply(target, thisArg, args); + } finally { + currentCaller = previous; } - return Reflect.apply(target, thisArg, args); } const DRIZZLE_ORM_MODE_METHODS = ["findFirst", "findMany"] as const; @@ -112,10 +192,8 @@ export function patchDrizzle( ] as const; if (typeof drizzle.execute === "function") { drizzle.execute = new Proxy(drizzle.execute, { - apply(target, thisArg, args) { - const session = thisArg.session; - return patchMethod(target, thisArg, args, session); - }, + apply: (target, thisArg, args) => + patchImmediateMethod(target, thisArg, args), }); } if (drizzle && "query" in drizzle && drizzle.query) { @@ -126,10 +204,8 @@ export function patchDrizzle( continue; } schema[func] = new Proxy(schema[func], { - apply(target, thisArg, args) { - const session = thisArg.session; - return patchMethod(target, thisArg, args, session); - }, + apply: (target, thisArg, args) => + patchBuilderMethod(target, thisArg, args), }); } } @@ -139,18 +215,13 @@ export function patchDrizzle( if (!drizzle[method] || typeof drizzle[method] !== "function") { continue; } - // patching the CRUD functions. - // the correct function to patch here is QueryPromise.prototype.then - // but because of the way microtasks work, by the time `then` fires, - // the stack is already clear and the caller name is no longer available - // so we have to forcibly get it earlier when the query is built. - // TODO: This isn't 100% reliable as the user could build a query and not run it until much later - // which could throw off this process completely. + // Patching the CRUD entrypoints. The caller is captured here, when the query is built, + // because the build-time stack is the only place the user's call site is still visible — + // by the time the query executes (a microtask later) it's gone. `patchBuilderMethod` tags + // the built query so the caller is reattached for its own synchronous `prepareQuery` window. drizzle[method] = new Proxy(drizzle[method], { - apply(target, thisArg, args) { - const session = thisArg._.session; - return patchMethod(target, thisArg, args, session); - }, + apply: (target, thisArg, args) => + patchBuilderMethod(target, thisArg, args), }); } return drizzle; @@ -162,11 +233,6 @@ const WellKnownFields = { route: "route", } as const; -// This is very non-standard. If `file` is to be a semantic convention this probably -// needs to be discussed with the community. It's what we use at query-doctor so -// sticking with it f -const SQLCOMMENTER_ARRAY_ELEM_DELIMITER = ";"; - /** * Drizzle session is responsible for serializing the query and sending it downstream to * the driver. We're patching `prepareQuery` to add the SQL comments there instead of @@ -183,35 +249,27 @@ function patchSession(session: DriverSession) { } proto.prepareQuery = new Proxy(proto.prepareQuery, { apply(target, thisArg, args) { - try { - const ctx = contexts.get(thisArg); - const requestContext = als.getStore(); - const tags: [string, string][] = [ - [WellKnownFields.dbDriver, "drizzle"], - ]; - // adding traceparent and tracestate - pushW3CTraceContext(tags); - if (ctx) { - tags.push([ - WellKnownFields.file, - // questionable - ctx.queryStack.join(SQLCOMMENTER_ARRAY_ELEM_DELIMITER), - ]); - } - if (args[0]) { - const query = args[0]; - if (!alreadyHasComment(query.sql)) { - if (requestContext) { - for (const key in requestContext) { - tags.push([key, String(requestContext[key])]); - } + // `currentCaller` is set by the built query whose synchronous execution reached this + // call, so it's exactly the caller of the query being prepared. + const caller = currentCaller; + const requestContext = als.getStore(); + const tags: [string, string][] = [[WellKnownFields.dbDriver, "drizzle"]]; + // adding traceparent and tracestate + pushW3CTraceContext(tags); + if (caller) { + tags.push([WellKnownFields.file, caller]); + } + if (args[0]) { + const query = args[0]; + if (!alreadyHasComment(query.sql)) { + if (requestContext) { + for (const key in requestContext) { + tags.push([key, String(requestContext[key])]); } - const sqlComment = serializeTags(tags); - query.sql += sqlComment; } + const sqlComment = serializeTags(tags); + query.sql += sqlComment; } - } finally { - contexts.delete(thisArg); } return Reflect.apply(target, thisArg, args); }, diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/driver-integration.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/driver-integration.spec.ts index fa29c8d7..d9b36332 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/driver-integration.spec.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/driver-integration.spec.ts @@ -28,3 +28,78 @@ test("pglite integration", async () => { assert.fail("Expected an error to be thrown"); } }); + +const watches = pgTable("watches", { + id: serial("id").primaryKey(), + name: text("name"), +}); +const notifs = pgTable("notifs", { + id: serial("id").primaryKey(), + name: text("name"), +}); + +function fileTag(sql: string): string | undefined { + const match = sql.match(/file='([^']*)'/); + return match ? decodeURIComponent(match[1]) : undefined; +} + +async function setupLoggedDb() { + const logged: string[] = []; + const db = patchDrizzle( + drizzle({ + schema: { watches, notifs }, + logger: { logQuery: (query) => logged.push(query) }, + }), + ); + await db.$client.exec( + "CREATE TABLE watches (id serial primary key, name text); CREATE TABLE notifs (id serial primary key, name text);", + ); + return { db, logged }; +} + +// Regression: a single drizzle() instance shares one session across every query, so keying the +// captured caller by the session dropped/clobbered the `file` tag whenever two queries were built +// before either executed (concurrent requests, `Promise.all`). Each query must carry its own tag. +test("concurrent queries each keep their own file tag", async () => { + const { db, logged } = await setupLoggedDb(); + const queryWatches = () => db.select().from(watches); + const queryNotifs = () => db.select().from(notifs); + + await Promise.all([queryWatches(), queryNotifs()]); + + const watchesSql = logged.find((q) => q.includes('from "watches"')); + const notifsSql = logged.find((q) => q.includes('from "notifs"')); + assert.ok(watchesSql, "expected the watches query to be logged"); + assert.ok(notifsSql, "expected the notifs query to be logged"); + + const watchesFile = fileTag(watchesSql); + const notifsFile = fileTag(notifsSql); + assert.ok(watchesFile, "watches query is missing its file tag"); + assert.ok(notifsFile, "notifs query is missing its file tag"); + // Distinct build sites must produce distinct tags — not one joined tag plus a dropped one. + assert.notStrictEqual( + watchesFile, + notifsFile, + "concurrent queries clobbered each other's file tag", + ); + assert.doesNotMatch( + watchesFile!, + /;/, + "file tag must hold a single caller, not a joined stack", + ); +}); + +// Queries can be built in one order and awaited in another; the tag must follow the query object, +// not the order in which queries reach the driver. +test("file tag follows the query even when awaited out of build order", async () => { + const { db, logged } = await setupLoggedDb(); + const built1 = db.select().from(watches); + const built2 = db.select().from(notifs); + await built2; + await built1; + + const watchesFile = fileTag(logged.find((q) => q.includes('from "watches"'))!); + const notifsFile = fileTag(logged.find((q) => q.includes('from "notifs"'))!); + assert.ok(watchesFile && notifsFile, "both queries must keep a file tag"); + assert.notStrictEqual(watchesFile, notifsFile); +}); From 7f19fa5c3f79ec08771ff3810c72a9581041a729 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 30 Jun 2026 14:02:46 -0400 Subject: [PATCH 2/4] fix(http): widen withRequestContext next param to accept framework callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `next: () => Promise` rejected Fastify's `done: (err?: Error) => void` (and Express's `next`) with TS2345, since `void` isn't assignable to `Promise`. `next` is only handed to `als.run`, whose return value is ignored, so widen it to `() => unknown`. Also corrects the JSDoc, which said the context carries "route and controller" — it's `route` plus optional `method`/`controller` and arbitrary keys. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../packages/sqlcommenter-drizzle/src/http.ts | 15 ++++++++------- .../packages/sqlcommenter-mikroorm/src/http.ts | 15 ++++++++------- .../packages/sqlcommenter-typeorm/src/http.ts | 15 ++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/http.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/http.ts index 2efcbb90..5f3a8d9b 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/http.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/http.ts @@ -2,14 +2,15 @@ import { als } from "./als.js"; import type { RequestContext } from "./request-context.js"; /** - * Wraps the next function in the AsyncLocalStorage with the request context. - * Used to get `route` and `controller` information from the request into the query - * without exposing the underlying AsyncLocalStorage API. + * Runs `next` within an AsyncLocalStorage scope carrying the request context, so any query + * issued during it picks up `route` (and any other provided fields such as `method` and + * `controller`) without exposing the underlying AsyncLocalStorage API. + * + * `next` is invoked for its side effect and its return value is ignored, so the parameter + * accepts any nullary callback — including framework hook callbacks like Fastify's + * `done: (err?: Error) => void` or Express's `next`. */ -export function withRequestContext( - context: RequestContext, - next: () => Promise, -) { +export function withRequestContext(context: RequestContext, next: () => unknown) { als.run(context, next); } diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/http.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/http.ts index 2efcbb90..5f3a8d9b 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/http.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/http.ts @@ -2,14 +2,15 @@ import { als } from "./als.js"; import type { RequestContext } from "./request-context.js"; /** - * Wraps the next function in the AsyncLocalStorage with the request context. - * Used to get `route` and `controller` information from the request into the query - * without exposing the underlying AsyncLocalStorage API. + * Runs `next` within an AsyncLocalStorage scope carrying the request context, so any query + * issued during it picks up `route` (and any other provided fields such as `method` and + * `controller`) without exposing the underlying AsyncLocalStorage API. + * + * `next` is invoked for its side effect and its return value is ignored, so the parameter + * accepts any nullary callback — including framework hook callbacks like Fastify's + * `done: (err?: Error) => void` or Express's `next`. */ -export function withRequestContext( - context: RequestContext, - next: () => Promise, -) { +export function withRequestContext(context: RequestContext, next: () => unknown) { als.run(context, next); } diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/http.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/http.ts index 2efcbb90..5f3a8d9b 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/http.ts +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/http.ts @@ -2,14 +2,15 @@ import { als } from "./als.js"; import type { RequestContext } from "./request-context.js"; /** - * Wraps the next function in the AsyncLocalStorage with the request context. - * Used to get `route` and `controller` information from the request into the query - * without exposing the underlying AsyncLocalStorage API. + * Runs `next` within an AsyncLocalStorage scope carrying the request context, so any query + * issued during it picks up `route` (and any other provided fields such as `method` and + * `controller`) without exposing the underlying AsyncLocalStorage API. + * + * `next` is invoked for its side effect and its return value is ignored, so the parameter + * accepts any nullary callback — including framework hook callbacks like Fastify's + * `done: (err?: Error) => void` or Express's `next`. */ -export function withRequestContext( - context: RequestContext, - next: () => Promise, -) { +export function withRequestContext(context: RequestContext, next: () => unknown) { als.run(context, next); } From db84aa809695f558bd9dc8d7258d230d5a883459 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 30 Jun 2026 14:02:57 -0400 Subject: [PATCH 3/4] feat(fastify): add first-party Fastify plugin and fix the integration docs Wiring sqlcommenter into Fastify by hand has two silent pitfalls: a plain `register()` encapsulates the `onRequest` hook so it never applies to routes in the parent scope (no tags, no error), and wrapping the route handler misses queries issued in earlier `onRequest`/`preHandler` hooks (e.g. an auth plugin's session lookup). Add `@query-doctor/sqlcommenter-/fastify` exporting `sqlcommenterFastify`: a global `onRequest` hook (via the `skip-override` symbol, so no extra dependency) using `request.routeOptions.url` + `request.method`, with an optional `context` callback for extra fields. Rewrite the Fastify README section to recommend the plugin and, for the manual path, document the `fastify-plugin` + `onRequest` + registration-ordering requirements and the `routerPath` -> `routeOptions.url` change for Fastify v5. Adds `fastify` as an optional peer dependency (and a dev dependency for tests). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../packages/sqlcommenter-drizzle/README.md | 55 +- .../sqlcommenter-drizzle/package-lock.json | 673 +++++++++++++++++- .../sqlcommenter-drizzle/package.json | 19 +- .../sqlcommenter-drizzle/src/fastify.ts | 72 ++ .../sqlcommenter-drizzle/test/fastify.spec.ts | 89 +++ .../packages/sqlcommenter-mikroorm/README.md | 55 +- .../sqlcommenter-mikroorm/package-lock.json | 641 ++++++++++++++++- .../sqlcommenter-mikroorm/package.json | 19 +- .../sqlcommenter-mikroorm/src/fastify.ts | 72 ++ .../test/fastify.spec.ts | 56 ++ .../packages/sqlcommenter-typeorm/README.md | 55 +- .../sqlcommenter-typeorm/package-lock.json | 671 +++++++++++++++++ .../sqlcommenter-typeorm/package.json | 17 + .../sqlcommenter-typeorm/src/fastify.ts | 72 ++ .../sqlcommenter-typeorm/test/fastify.spec.ts | 56 ++ 15 files changed, 2592 insertions(+), 30 deletions(-) create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/fastify.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/fastify.spec.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/fastify.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/test/fastify.spec.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/fastify.ts create mode 100644 nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/test/fastify.spec.ts diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/README.md b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/README.md index 586bf531..adc33703 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/README.md +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/README.md @@ -77,16 +77,55 @@ app.use((c, next) => { #### Fastify +The recommended way is the first-party plugin, which wires everything correctly: + +```ts +import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-drizzle/fastify"; + +// Register it BEFORE any plugin whose hooks issue queries (e.g. auth), so the context +// is already open when those hooks run. +await app.register(sqlcommenterFastify); +await app.register(authPlugin); +``` + +It hooks `onRequest`, so it tags queries from the **entire request lifecycle** — including queries +issued in other plugins' `onRequest`/`preHandler` hooks — not just the route handler. Pass +`context` to add extra fields: + +```ts +await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items" }), +}); +``` + +##### Doing it manually + +If you'd rather wire it yourself with `withRequestContext`, two things are easy to get wrong: + +- **Register the hook globally with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).** + A plain `app.register(plugin)` encapsulates the `onRequest` hook, so it silently does **not** + apply to routes registered in the parent scope — you get no `route`/`method` tags and no error. +- **Hook `onRequest` (not the route handler), registered before any plugin whose hooks issue + queries** (e.g. an auth plugin that resolves a session in its own `onRequest`/`preHandler`). + Wrapping only the handler misses those earlier queries. + ```ts +import fp from "fastify-plugin"; import { withRequestContext } from "@query-doctor/sqlcommenter-drizzle/http"; -app.addHook("onRequest", (request, _, done) => { - withRequestContext( - { - route: request.routerPath, - method: request.method, - }, - done - ); +const sqlcommenter = fp((app, _opts, done) => { + app.addHook("onRequest", (request, _reply, next) => { + withRequestContext( + { + // `routerPath` was removed in Fastify v5; `routeOptions.url` is the matched route pattern. + route: request.routeOptions?.url ?? request.url, + method: request.method, + }, + next + ); + }); + done(); }); + +await app.register(sqlcommenter); // before auth, etc. ``` diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json index bc9e2adb..e2fdcad4 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json @@ -14,6 +14,7 @@ "devDependencies": { "@electric-sql/pglite": "^0.3.10", "@types/node": "^20.19.34", + "fastify": "^5.2.0", "hono": "^4.9.8", "postgres": "^3.4.7", "rewiremock": "^3.14.3", @@ -25,7 +26,13 @@ }, "peerDependencies": { "@opentelemetry/core": ">=2.8.0", - "drizzle-orm": ">=0.35.0" + "drizzle-orm": ">=0.35.0", + "fastify": ">=4.0.0" + }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } } }, "node_modules/@electric-sql/pglite": { @@ -477,6 +484,123 @@ "node": ">=18" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -512,6 +636,13 @@ "node": ">=14" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz", @@ -522,6 +653,48 @@ "undici-types": "~6.21.0" } }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -569,6 +742,16 @@ "inherits": "2.0.3" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -585,6 +768,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -828,6 +1032,20 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", @@ -954,6 +1172,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -1255,6 +1483,131 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", + "integrity": "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.9.0.tgz", + "integrity": "sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -1498,6 +1851,16 @@ "dev": true, "license": "ISC" }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -1534,6 +1897,72 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1667,6 +2096,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -1730,6 +2169,46 @@ "node": ">= 0.10" } }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -1771,6 +2250,23 @@ "dev": true, "license": "MIT" }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -1825,6 +2321,13 @@ "node": ">=0.4.x" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1879,6 +2382,16 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", @@ -1886,6 +2399,16 @@ "dev": true, "license": "MIT" }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1896,6 +2419,27 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rewiremock": { "version": "3.14.6", "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.6.tgz", @@ -1911,6 +2455,13 @@ "wipe-webpack-cache": "^2.1.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/ripemd160": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", @@ -1962,6 +2513,76 @@ ], "license": "MIT" }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -2084,6 +2705,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -2119,6 +2760,26 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "dev": true, + "license": "MIT" + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -2161,6 +2822,16 @@ "dev": true, "license": "MIT" }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json index 32a751eb..a0b42f56 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json @@ -28,11 +28,22 @@ "types": "./dist/cjs/http.d.ts", "default": "./dist/cjs/http.js" } + }, + "./fastify": { + "import": { + "types": "./dist/esm/fastify.d.ts", + "default": "./dist/esm/fastify.js" + }, + "require": { + "types": "./dist/cjs/fastify.d.ts", + "default": "./dist/cjs/fastify.js" + } } }, "devDependencies": { "@electric-sql/pglite": "^0.3.10", "@types/node": "^20.19.34", + "fastify": "^5.2.0", "hono": "^4.9.8", "postgres": "^3.4.7", "rewiremock": "^3.14.3", @@ -44,7 +55,13 @@ }, "peerDependencies": { "@opentelemetry/core": ">=2.8.0", - "drizzle-orm": ">=0.35.0" + "drizzle-orm": ">=0.35.0", + "fastify": ">=4.0.0" + }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } }, "engines": { "node": ">=20.0.0" diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/fastify.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/fastify.ts new file mode 100644 index 00000000..3a3b7ce4 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/src/fastify.ts @@ -0,0 +1,72 @@ +import type { + FastifyInstance, + FastifyPluginCallback, + FastifyRequest, +} from "fastify"; +import { als } from "./als.js"; +import type { RequestContext } from "./request-context.js"; + +export type SqlcommenterContextFn = ( + request: FastifyRequest, +) => Record; + +export interface SqlcommenterFastifyOptions { + /** + * Extra fields to add to every query's context, merged over the default `route`/`method` + * (e.g. a tenant id, or the matched controller name). Runs once per request in `onRequest`. + */ + context?: SqlcommenterContextFn; +} + +// fastify-plugin sets this symbol so a plugin's hooks/decorators apply to the *parent* scope +// rather than being encapsulated in the plugin's own scope. Our `onRequest` hook has to be +// global so it covers routes — and other plugins' hooks — registered in the parent scope. +// Without it, a plain `register()` silently encapsulates the hook and it tags nothing. This is +// the same primitive `fastify-plugin` relies on, inlined so the integration needs no dependency +// beyond fastify itself. +const SKIP_OVERRIDE = Symbol.for("skip-override"); + +const plugin: FastifyPluginCallback = ( + fastify: FastifyInstance, + options: SqlcommenterFastifyOptions, + done: (err?: Error) => void, +) => { + fastify.addHook("onRequest", (request, _reply, next) => { + const context: RequestContext = { + // `routeOptions.url` is the matched route pattern (e.g. "/items/:id"). It's resolved by the + // time `onRequest` runs; fall back to the raw url for unmatched requests. + route: request.routeOptions?.url ?? request.url, + method: request.method, + ...options.context?.(request), + }; + // Opening the context here — and registering this plugin before any query-issuing plugin — + // means queries from later `onRequest`/`preHandler` hooks and the handler all inherit it, + // not just the handler body. + als.run(context, next); + }); + done(); +}; + +(plugin as unknown as Record)[SKIP_OVERRIDE] = true; + +/** + * Fastify plugin that tags every query issued during a request with its `route` and `method` + * (plus anything returned by `options.context`). + * + * Register it **before** any other plugin whose hooks issue queries (e.g. an auth plugin that + * resolves a session in its own `onRequest`/`preHandler`), so the context is already open when + * those hooks run. Because it hooks `onRequest`, it covers the whole request lifecycle — not + * just the route handler. + * + * @example + * ```ts + * import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-drizzle/fastify"; + * + * await app.register(sqlcommenterFastify); + * await app.register(authPlugin); // queries issued in auth's hooks are tagged too + * ``` + */ +export const sqlcommenterFastify = plugin; +export default sqlcommenterFastify; + +export type { RequestContext } from "./request-context.js"; diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/fastify.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/fastify.spec.ts new file mode 100644 index 00000000..63235ec7 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/test/fastify.spec.ts @@ -0,0 +1,89 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import Fastify from "fastify"; +import { pgTable, serial, text } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/pglite"; +import { patchDrizzle } from "../src/index.js"; +import { sqlcommenterFastify } from "../src/fastify.js"; + +const sessions = pgTable("sessions", { + id: serial("id").primaryKey(), + name: text("name"), +}); +const items = pgTable("items", { + id: serial("id").primaryKey(), + name: text("name"), +}); + +function tag(sql: string, key: string): string | undefined { + const match = sql.match(new RegExp(`${key}='([^']*)'`)); + return match ? decodeURIComponent(match[1]) : undefined; +} + +async function setup() { + const logged: string[] = []; + const db = patchDrizzle( + drizzle({ + schema: { sessions, items }, + logger: { logQuery: (query) => logged.push(query) }, + }), + ); + await db.$client.exec( + "CREATE TABLE sessions(id serial primary key, name text); CREATE TABLE items(id serial primary key, name text);", + ); + return { db, logged }; +} + +// The plugin must (a) apply globally — covering routes registered in the parent scope, which a +// plain encapsulated `register` would silently miss — and (b) cover the whole request lifecycle, +// so a query issued in an auth plugin's own `onRequest` (registered after it) is tagged too, not +// just queries in the route handler. +test("fastify plugin tags lifecycle-wide queries with route and method", async () => { + const { db, logged } = await setup(); + const app = Fastify(); + await app.register(sqlcommenterFastify); + // Auth-style session lookup in its own onRequest hook, registered after the plugin. + app.addHook("onRequest", async () => { + await db.select().from(sessions); + }); + app.get("/items/:id", async () => { + await db.select().from(items); + return { ok: true }; + }); + + const res = await app.inject({ method: "GET", url: "/items/42" }); + await app.close(); + + assert.strictEqual(res.statusCode, 200); + const sessionSql = logged.find((q) => q.includes('from "sessions"')); + const itemSql = logged.find((q) => q.includes('from "items"')); + assert.ok(sessionSql, "expected the auth onRequest query to run"); + assert.ok(itemSql, "expected the handler query to run"); + + // onRequest-phase query (the case a handler wrap misses). + assert.strictEqual(tag(sessionSql, "route"), "/items/:id"); + assert.strictEqual(tag(sessionSql, "method"), "GET"); + // handler-phase query. + assert.strictEqual(tag(itemSql, "route"), "/items/:id"); + assert.strictEqual(tag(itemSql, "method"), "GET"); +}); + +test("fastify plugin merges extra context fields", async () => { + const { db, logged } = await setup(); + const app = Fastify(); + await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items", host: request.headers.host }), + }); + app.get("/items/:id", async () => { + await db.select().from(items); + return { ok: true }; + }); + + await app.inject({ method: "GET", url: "/items/42", headers: { host: "x" } }); + await app.close(); + + const itemSql = logged.find((q) => q.includes('from "items"'))!; + assert.strictEqual(tag(itemSql, "route"), "/items/:id"); + assert.strictEqual(tag(itemSql, "controller"), "items"); + assert.strictEqual(tag(itemSql, "host"), "x"); +}); diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/README.md b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/README.md index 414ae777..7255660c 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/README.md +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/README.md @@ -82,18 +82,57 @@ app.use((c, next) => { #### Fastify +The recommended way is the first-party plugin, which wires everything correctly: + +```ts +import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-mikroorm/fastify"; + +// Register it BEFORE any plugin whose hooks issue queries (e.g. auth), so the context +// is already open when those hooks run. +await app.register(sqlcommenterFastify); +await app.register(authPlugin); +``` + +It hooks `onRequest`, so it tags queries from the **entire request lifecycle** — including queries +issued in other plugins' `onRequest`/`preHandler` hooks — not just the route handler. Pass +`context` to add extra fields: + +```ts +await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items" }), +}); +``` + +##### Doing it manually + +If you'd rather wire it yourself with `withRequestContext`, two things are easy to get wrong: + +- **Register the hook globally with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).** + A plain `app.register(plugin)` encapsulates the `onRequest` hook, so it silently does **not** + apply to routes registered in the parent scope — you get no `route`/`method` tags and no error. +- **Hook `onRequest` (not the route handler), registered before any plugin whose hooks issue + queries** (e.g. an auth plugin that resolves a session in its own `onRequest`/`preHandler`). + Wrapping only the handler misses those earlier queries. + ```ts +import fp from "fastify-plugin"; import { withRequestContext } from "@query-doctor/sqlcommenter-mikroorm/http"; -app.addHook("onRequest", (request, _, done) => { - withRequestContext( - { - route: request.routerPath, - method: request.method, - }, - done - ); +const sqlcommenter = fp((app, _opts, done) => { + app.addHook("onRequest", (request, _reply, next) => { + withRequestContext( + { + // `routerPath` was removed in Fastify v5; `routeOptions.url` is the matched route pattern. + route: request.routeOptions?.url ?? request.url, + method: request.method, + }, + next + ); + }); + done(); }); + +await app.register(sqlcommenter); // before auth, etc. ``` #### NestJS diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json index d61a516e..05f37a9b 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json @@ -15,6 +15,7 @@ "@mikro-orm/better-sqlite": "^6.6.7", "@types/node": "^20.19.34", "better-sqlite3": "^12.6.2", + "fastify": "^5.2.0", "tsx": "^4.20.5", "typescript": "^5.9.3" }, @@ -23,7 +24,13 @@ }, "peerDependencies": { "@mikro-orm/core": ">=6.4.0", - "@opentelemetry/core": ">=2.8.0" + "@opentelemetry/core": ">=2.8.0", + "fastify": ">=4.0.0" + }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } } }, "node_modules/@esbuild/aix-ppc64": { @@ -468,6 +475,123 @@ "node": ">=18" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, "node_modules/@mikro-orm/better-sqlite": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/@mikro-orm/better-sqlite/-/better-sqlite-6.6.7.tgz", @@ -626,6 +750,13 @@ "node": ">=14" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz", @@ -636,6 +767,48 @@ "undici-types": "~6.21.0" } }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -646,6 +819,37 @@ "node": ">=8" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -766,6 +970,20 @@ "node": ">=14" } }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/dataloader": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", @@ -817,6 +1035,16 @@ "node": ">=4.0.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -949,6 +1177,20 @@ "node": ">=6" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -966,12 +1208,97 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stringify": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", + "integrity": "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.9.0.tgz", + "integrity": "sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", - "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -996,6 +1323,21 @@ "node": ">=8" } }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -1187,6 +1529,16 @@ "node": ">= 0.10" } }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -1236,6 +1588,33 @@ "node": ">=0.12.0" } }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -1300,6 +1679,45 @@ } } }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", @@ -1398,6 +1816,16 @@ "node": ">=10" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1445,6 +1873,46 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -1473,6 +1941,23 @@ "node": ">=10" } }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -1505,6 +1990,13 @@ "license": "MIT", "peer": true }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1536,6 +2028,16 @@ "node": ">= 6" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -1556,6 +2058,16 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -1597,17 +2109,33 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", - "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -1653,6 +2181,56 @@ ], "license": "MIT" }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -1666,6 +2244,13 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -1723,6 +2308,26 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -1816,6 +2421,26 @@ "node": ">=8.0.0" } }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "dev": true, + "license": "MIT" + }, "node_modules/tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", @@ -1839,6 +2464,16 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json index 4aa9d31b..26de7b7c 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json @@ -28,12 +28,23 @@ "types": "./dist/cjs/http.d.ts", "default": "./dist/cjs/http.js" } + }, + "./fastify": { + "import": { + "types": "./dist/esm/fastify.d.ts", + "default": "./dist/esm/fastify.js" + }, + "require": { + "types": "./dist/cjs/fastify.d.ts", + "default": "./dist/cjs/fastify.js" + } } }, "devDependencies": { "@mikro-orm/better-sqlite": "^6.6.7", "@types/node": "^20.19.34", "better-sqlite3": "^12.6.2", + "fastify": "^5.2.0", "tsx": "^4.20.5", "typescript": "^5.9.3" }, @@ -42,7 +53,13 @@ }, "peerDependencies": { "@mikro-orm/core": ">=6.4.0", - "@opentelemetry/core": ">=2.8.0" + "@opentelemetry/core": ">=2.8.0", + "fastify": ">=4.0.0" + }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } }, "engines": { "node": ">=20.0.0" diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/fastify.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/fastify.ts new file mode 100644 index 00000000..94dacca9 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/src/fastify.ts @@ -0,0 +1,72 @@ +import type { + FastifyInstance, + FastifyPluginCallback, + FastifyRequest, +} from "fastify"; +import { als } from "./als.js"; +import type { RequestContext } from "./request-context.js"; + +export type SqlcommenterContextFn = ( + request: FastifyRequest, +) => Record; + +export interface SqlcommenterFastifyOptions { + /** + * Extra fields to add to every query's context, merged over the default `route`/`method` + * (e.g. a tenant id, or the matched controller name). Runs once per request in `onRequest`. + */ + context?: SqlcommenterContextFn; +} + +// fastify-plugin sets this symbol so a plugin's hooks/decorators apply to the *parent* scope +// rather than being encapsulated in the plugin's own scope. Our `onRequest` hook has to be +// global so it covers routes — and other plugins' hooks — registered in the parent scope. +// Without it, a plain `register()` silently encapsulates the hook and it tags nothing. This is +// the same primitive `fastify-plugin` relies on, inlined so the integration needs no dependency +// beyond fastify itself. +const SKIP_OVERRIDE = Symbol.for("skip-override"); + +const plugin: FastifyPluginCallback = ( + fastify: FastifyInstance, + options: SqlcommenterFastifyOptions, + done: (err?: Error) => void, +) => { + fastify.addHook("onRequest", (request, _reply, next) => { + const context: RequestContext = { + // `routeOptions.url` is the matched route pattern (e.g. "/items/:id"). It's resolved by the + // time `onRequest` runs; fall back to the raw url for unmatched requests. + route: request.routeOptions?.url ?? request.url, + method: request.method, + ...options.context?.(request), + }; + // Opening the context here — and registering this plugin before any query-issuing plugin — + // means queries from later `onRequest`/`preHandler` hooks and the handler all inherit it, + // not just the handler body. + als.run(context, next); + }); + done(); +}; + +(plugin as unknown as Record)[SKIP_OVERRIDE] = true; + +/** + * Fastify plugin that tags every query issued during a request with its `route` and `method` + * (plus anything returned by `options.context`). + * + * Register it **before** any other plugin whose hooks issue queries (e.g. an auth plugin that + * resolves a session in its own `onRequest`/`preHandler`), so the context is already open when + * those hooks run. Because it hooks `onRequest`, it covers the whole request lifecycle — not + * just the route handler. + * + * @example + * ```ts + * import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-mikroorm/fastify"; + * + * await app.register(sqlcommenterFastify); + * await app.register(authPlugin); // queries issued in auth's hooks are tagged too + * ``` + */ +export const sqlcommenterFastify = plugin; +export default sqlcommenterFastify; + +export type { RequestContext } from "./request-context.js"; diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/test/fastify.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/test/fastify.spec.ts new file mode 100644 index 00000000..87e80b47 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/test/fastify.spec.ts @@ -0,0 +1,56 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import Fastify from "fastify"; +import { sqlcommenterFastify } from "../src/fastify.js"; +import { als } from "../src/als.js"; + +// The plugin is driver-agnostic — it only opens the shared AsyncLocalStorage context that the +// query patch reads. These tests assert that context directly, so they don't need a real ORM. + +// It must apply globally (covering parent-scope routes a plain encapsulated register would miss) +// and cover the whole lifecycle, so an onRequest hook registered after it still sees the context. +test("fastify plugin exposes route/method context lifecycle-wide", async () => { + const seen: Record[] = []; + const app = Fastify(); + await app.register(sqlcommenterFastify); + app.addHook("onRequest", async () => { + const store = als.getStore(); + if (store) seen.push({ phase: "onRequest", ...store }); + }); + app.get("/items/:id", async () => { + const store = als.getStore(); + if (store) seen.push({ phase: "handler", ...store }); + return { ok: true }; + }); + + const res = await app.inject({ method: "GET", url: "/items/42" }); + await app.close(); + + assert.strictEqual(res.statusCode, 200); + assert.deepStrictEqual(seen, [ + { phase: "onRequest", route: "/items/:id", method: "GET" }, + { phase: "handler", route: "/items/:id", method: "GET" }, + ]); +}); + +test("fastify plugin merges extra context fields", async () => { + let store: Record | undefined; + const app = Fastify(); + await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items", host: request.headers.host }), + }); + app.get("/items/:id", async () => { + store = als.getStore(); + return { ok: true }; + }); + + await app.inject({ method: "GET", url: "/items/42", headers: { host: "x" } }); + await app.close(); + + assert.deepStrictEqual(store, { + route: "/items/:id", + method: "GET", + controller: "items", + host: "x", + }); +}); diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/README.md b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/README.md index 19f0e35c..57a5db01 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/README.md +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/README.md @@ -81,18 +81,57 @@ app.use((c, next) => { #### Fastify +The recommended way is the first-party plugin, which wires everything correctly: + +```ts +import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-typeorm/fastify"; + +// Register it BEFORE any plugin whose hooks issue queries (e.g. auth), so the context +// is already open when those hooks run. +await app.register(sqlcommenterFastify); +await app.register(authPlugin); +``` + +It hooks `onRequest`, so it tags queries from the **entire request lifecycle** — including queries +issued in other plugins' `onRequest`/`preHandler` hooks — not just the route handler. Pass +`context` to add extra fields: + +```ts +await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items" }), +}); +``` + +##### Doing it manually + +If you'd rather wire it yourself with `withRequestContext`, two things are easy to get wrong: + +- **Register the hook globally with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).** + A plain `app.register(plugin)` encapsulates the `onRequest` hook, so it silently does **not** + apply to routes registered in the parent scope — you get no `route`/`method` tags and no error. +- **Hook `onRequest` (not the route handler), registered before any plugin whose hooks issue + queries** (e.g. an auth plugin that resolves a session in its own `onRequest`/`preHandler`). + Wrapping only the handler misses those earlier queries. + ```ts +import fp from "fastify-plugin"; import { withRequestContext } from "@query-doctor/sqlcommenter-typeorm/http"; -app.addHook("onRequest", (request, _, done) => { - withRequestContext( - { - route: request.routerPath, - method: request.method, - }, - done - ); +const sqlcommenter = fp((app, _opts, done) => { + app.addHook("onRequest", (request, _reply, next) => { + withRequestContext( + { + // `routerPath` was removed in Fastify v5; `routeOptions.url` is the matched route pattern. + route: request.routeOptions?.url ?? request.url, + method: request.method, + }, + next + ); + }); + done(); }); + +await app.register(sqlcommenter); // before auth, etc. ``` #### NestJS diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json index 4181132b..e20cdedb 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@types/node": "^20.19.34", + "fastify": "^5.2.0", "reflect-metadata": "^0.2.2", "sql.js": "^1.12.0", "tsx": "^4.20.5", @@ -23,7 +24,13 @@ }, "peerDependencies": { "@opentelemetry/core": ">=2.8.0", + "fastify": ">=4.0.0", "typeorm": ">=0.3.0" + }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } } }, "node_modules/@esbuild/aix-ppc64": { @@ -468,6 +475,123 @@ "node": ">=18" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -521,6 +645,13 @@ "node": ">=14" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -549,6 +680,48 @@ "undici-types": "~6.21.0" } }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -595,6 +768,16 @@ "node": ">= 6.0.0" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -611,6 +794,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -844,6 +1048,20 @@ "license": "MIT", "peer": true }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -917,6 +1135,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -1044,6 +1272,131 @@ "node": ">=6" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", + "integrity": "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.9.0.tgz", + "integrity": "sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -1282,6 +1635,16 @@ "license": "ISC", "peer": true }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -1351,6 +1714,72 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -1401,6 +1830,16 @@ "license": "MIT", "peer": true }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -1435,6 +1874,46 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -1445,6 +1924,40 @@ "node": ">= 0.4" } }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -1461,6 +1974,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1471,6 +1994,34 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1492,6 +2043,76 @@ "license": "MIT", "peer": true }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -1567,6 +2188,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sql-highlight": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", @@ -1695,6 +2336,26 @@ "node": ">=8" } }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "dev": true, + "license": "MIT" + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -1710,6 +2371,16 @@ "node": ">= 0.4" } }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json index c07435e5..f7f16fc5 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json @@ -29,10 +29,21 @@ "types": "./dist/cjs/http.d.ts", "default": "./dist/cjs/http.js" } + }, + "./fastify": { + "import": { + "types": "./dist/esm/fastify.d.ts", + "default": "./dist/esm/fastify.js" + }, + "require": { + "types": "./dist/cjs/fastify.d.ts", + "default": "./dist/cjs/fastify.js" + } } }, "devDependencies": { "@types/node": "^20.19.34", + "fastify": "^5.2.0", "reflect-metadata": "^0.2.2", "sql.js": "^1.12.0", "tsx": "^4.20.5", @@ -43,8 +54,14 @@ }, "peerDependencies": { "@opentelemetry/core": ">=2.8.0", + "fastify": ">=4.0.0", "typeorm": ">=0.3.0" }, + "peerDependenciesMeta": { + "fastify": { + "optional": true + } + }, "engines": { "node": ">=20.0.0" }, diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/fastify.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/fastify.ts new file mode 100644 index 00000000..94f66191 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/src/fastify.ts @@ -0,0 +1,72 @@ +import type { + FastifyInstance, + FastifyPluginCallback, + FastifyRequest, +} from "fastify"; +import { als } from "./als.js"; +import type { RequestContext } from "./request-context.js"; + +export type SqlcommenterContextFn = ( + request: FastifyRequest, +) => Record; + +export interface SqlcommenterFastifyOptions { + /** + * Extra fields to add to every query's context, merged over the default `route`/`method` + * (e.g. a tenant id, or the matched controller name). Runs once per request in `onRequest`. + */ + context?: SqlcommenterContextFn; +} + +// fastify-plugin sets this symbol so a plugin's hooks/decorators apply to the *parent* scope +// rather than being encapsulated in the plugin's own scope. Our `onRequest` hook has to be +// global so it covers routes — and other plugins' hooks — registered in the parent scope. +// Without it, a plain `register()` silently encapsulates the hook and it tags nothing. This is +// the same primitive `fastify-plugin` relies on, inlined so the integration needs no dependency +// beyond fastify itself. +const SKIP_OVERRIDE = Symbol.for("skip-override"); + +const plugin: FastifyPluginCallback = ( + fastify: FastifyInstance, + options: SqlcommenterFastifyOptions, + done: (err?: Error) => void, +) => { + fastify.addHook("onRequest", (request, _reply, next) => { + const context: RequestContext = { + // `routeOptions.url` is the matched route pattern (e.g. "/items/:id"). It's resolved by the + // time `onRequest` runs; fall back to the raw url for unmatched requests. + route: request.routeOptions?.url ?? request.url, + method: request.method, + ...options.context?.(request), + }; + // Opening the context here — and registering this plugin before any query-issuing plugin — + // means queries from later `onRequest`/`preHandler` hooks and the handler all inherit it, + // not just the handler body. + als.run(context, next); + }); + done(); +}; + +(plugin as unknown as Record)[SKIP_OVERRIDE] = true; + +/** + * Fastify plugin that tags every query issued during a request with its `route` and `method` + * (plus anything returned by `options.context`). + * + * Register it **before** any other plugin whose hooks issue queries (e.g. an auth plugin that + * resolves a session in its own `onRequest`/`preHandler`), so the context is already open when + * those hooks run. Because it hooks `onRequest`, it covers the whole request lifecycle — not + * just the route handler. + * + * @example + * ```ts + * import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-typeorm/fastify"; + * + * await app.register(sqlcommenterFastify); + * await app.register(authPlugin); // queries issued in auth's hooks are tagged too + * ``` + */ +export const sqlcommenterFastify = plugin; +export default sqlcommenterFastify; + +export type { RequestContext } from "./request-context.js"; diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/test/fastify.spec.ts b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/test/fastify.spec.ts new file mode 100644 index 00000000..87e80b47 --- /dev/null +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/test/fastify.spec.ts @@ -0,0 +1,56 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import Fastify from "fastify"; +import { sqlcommenterFastify } from "../src/fastify.js"; +import { als } from "../src/als.js"; + +// The plugin is driver-agnostic — it only opens the shared AsyncLocalStorage context that the +// query patch reads. These tests assert that context directly, so they don't need a real ORM. + +// It must apply globally (covering parent-scope routes a plain encapsulated register would miss) +// and cover the whole lifecycle, so an onRequest hook registered after it still sees the context. +test("fastify plugin exposes route/method context lifecycle-wide", async () => { + const seen: Record[] = []; + const app = Fastify(); + await app.register(sqlcommenterFastify); + app.addHook("onRequest", async () => { + const store = als.getStore(); + if (store) seen.push({ phase: "onRequest", ...store }); + }); + app.get("/items/:id", async () => { + const store = als.getStore(); + if (store) seen.push({ phase: "handler", ...store }); + return { ok: true }; + }); + + const res = await app.inject({ method: "GET", url: "/items/42" }); + await app.close(); + + assert.strictEqual(res.statusCode, 200); + assert.deepStrictEqual(seen, [ + { phase: "onRequest", route: "/items/:id", method: "GET" }, + { phase: "handler", route: "/items/:id", method: "GET" }, + ]); +}); + +test("fastify plugin merges extra context fields", async () => { + let store: Record | undefined; + const app = Fastify(); + await app.register(sqlcommenterFastify, { + context: (request) => ({ controller: "items", host: request.headers.host }), + }); + app.get("/items/:id", async () => { + store = als.getStore(); + return { ok: true }; + }); + + await app.inject({ method: "GET", url: "/items/42", headers: { host: "x" } }); + await app.close(); + + assert.deepStrictEqual(store, { + route: "/items/:id", + method: "GET", + controller: "items", + host: "x", + }); +}); From ee6101771237a28cf781cd0151718349dcfee35c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 30 Jun 2026 15:33:27 -0400 Subject: [PATCH 4/4] chore(release): bump minor versions for the fastify entry and fixes - @query-doctor/sqlcommenter-drizzle: 0.3.0 -> 0.4.0 (file-tag fix + /fastify) - @query-doctor/sqlcommenter-mikroorm: 0.2.0 -> 0.3.0 (/fastify) - @query-doctor/sqlcommenter-typeorm: 0.2.0 -> 0.3.0 (/fastify) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../packages/sqlcommenter-drizzle/package-lock.json | 4 ++-- .../packages/sqlcommenter-drizzle/package.json | 2 +- .../packages/sqlcommenter-mikroorm/package-lock.json | 4 ++-- .../packages/sqlcommenter-mikroorm/package.json | 2 +- .../packages/sqlcommenter-typeorm/package-lock.json | 4 ++-- .../packages/sqlcommenter-typeorm/package.json | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json index e2fdcad4..05fdaa36 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package-lock.json @@ -1,12 +1,12 @@ { "name": "@query-doctor/sqlcommenter-drizzle", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@query-doctor/sqlcommenter-drizzle", - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "~1.9.0" diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json index a0b42f56..d63a9454 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@query-doctor/sqlcommenter-drizzle", - "version": "0.3.0", + "version": "0.4.0", "description": "SQLCommenter patch for drizzle-orm", "main": "dist/cjs/index.js", "type": "module", diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json index 05f37a9b..7ba26f54 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@query-doctor/sqlcommenter-mikroorm", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@query-doctor/sqlcommenter-mikroorm", - "version": "0.2.0", + "version": "0.3.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "~1.9.0" diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json index 26de7b7c..fdd78ba3 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-mikroorm/package.json @@ -1,6 +1,6 @@ { "name": "@query-doctor/sqlcommenter-mikroorm", - "version": "0.2.0", + "version": "0.3.0", "description": "SQLCommenter patch for MikroORM", "main": "dist/cjs/index.js", "type": "module", diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json index e20cdedb..d50dc7f9 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@query-doctor/sqlcommenter-typeorm", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@query-doctor/sqlcommenter-typeorm", - "version": "0.2.0", + "version": "0.3.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "~1.9.0" diff --git a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json index f7f16fc5..bd9665a1 100644 --- a/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json +++ b/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@query-doctor/sqlcommenter-typeorm", - "version": "0.2.0", + "version": "0.3.0", "description": "SQLCommenter patch for TypeORM", "main": "dist/cjs/index.js", "type": "module",