Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/prisma-next-demo/src/prisma/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import postgres from '@prisma-next/postgres/runtime';
import { budgets, lints } from '@prisma-next/sql-runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
import { slowQueryWarning } from './slow-query-warning';

export const db = postgres<Contract>({
contractJson,
Expand All @@ -21,5 +22,9 @@ export const db = postgres<Contract>({
tableRows: { user: 10_000, post: 10_000 },
maxLatencyMs: 1_000,
}),
// Custom middleware (see `slow-query-warning.ts`): observes every
// execution's latency via `afterExecute` and logs a warning past the
// threshold. Cache hits flow through it too, with `source: 'middleware'`.
slowQueryWarning({ thresholdMs: 250 }),
],
});
39 changes: 39 additions & 0 deletions examples/prisma-next-demo/src/prisma/slow-query-warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Custom middleware example: warn when a query runs longer than a threshold.
*
* `afterExecute` fires once per `execute()` call after the rows have been
* consumed — whether they came from the driver or from an `intercept` hit
* (`result.source` says which) — with the observed `latencyMs` and
* `rowCount` for the run. Registered on the runtime in `src/prisma/db.ts`
* via the `middleware: [...]` option.
*/
import type { SqlMiddleware } from '@prisma-next/sql-runtime';

export interface SlowQueryWarningOptions {
/** Latency above this many milliseconds logs a warning. Default: 250. */
readonly thresholdMs?: number;
}

export function slowQueryWarning(options?: SlowQueryWarningOptions): SqlMiddleware {
const thresholdMs = options?.thresholdMs ?? 250;

return {
name: 'slow-query-warning',
familyId: 'sql',

async afterExecute(plan, result, ctx) {
if (result.latencyMs <= thresholdMs) return;
ctx.log.warn({
code: 'APP.SLOW_QUERY',
message: `Query took ${result.latencyMs}ms (threshold: ${thresholdMs}ms)`,
details: {
sql: plan.sql,
rowCount: result.rowCount,
latencyMs: result.latencyMs,
source: result.source,
planExecutionId: ctx.planExecutionId,
},
});
},
};
}
83 changes: 83 additions & 0 deletions examples/prisma-next-demo/test/slow-query-warning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Offline unit tests for the custom `slowQueryWarning` middleware example.
*
* Drives the middleware's `afterExecute` hook directly with a hand-built
* execution plan and context — no database required. The plan's `ast` and
* `meta` come from a real DSL build so the shapes match what the runtime
* hands to middleware.
*/
import type { AfterExecuteResult, SqlMiddlewareContext } from '@prisma-next/sql-runtime';
import { describe, expect, it } from 'vitest';
import { db } from '../src/prisma/db';
import { slowQueryWarning } from '../src/prisma/slow-query-warning';

function makeContext(warnEvents: unknown[]): SqlMiddlewareContext {
return {
contract: db.contract,
mode: 'strict',
now: () => Date.now(),
log: {
info: () => {},
warn: (event) => {
warnEvents.push(event);
},
error: () => {},
},
contentHash: async () => 'test-content-hash',
scope: 'runtime',
planExecutionId: 'test-plan-execution',
};
}

function makeExecutionPlan() {
const built = db.sql.public.user.select('id', 'email').limit(1).build();
return {
sql: 'SELECT "id", "email" FROM "user" LIMIT 1',
params: [],
ast: built.ast,
meta: built.meta,
};
}

function makeResult(latencyMs: number): AfterExecuteResult {
return { rowCount: 1, latencyMs, completed: true, source: 'driver' };
}

describe('slowQueryWarning middleware', () => {
it('logs a warning when latency exceeds the threshold', async () => {
const warnEvents: unknown[] = [];
const middleware = slowQueryWarning({ thresholdMs: 250 });

await middleware.afterExecute?.(makeExecutionPlan(), makeResult(900), makeContext(warnEvents));

expect(warnEvents).toHaveLength(1);
expect(warnEvents[0]).toMatchObject({
code: 'APP.SLOW_QUERY',
details: {
sql: 'SELECT "id", "email" FROM "user" LIMIT 1',
latencyMs: 900,
rowCount: 1,
source: 'driver',
planExecutionId: 'test-plan-execution',
},
});
});

it('stays silent at or below the threshold', async () => {
const warnEvents: unknown[] = [];
const middleware = slowQueryWarning({ thresholdMs: 250 });

await middleware.afterExecute?.(makeExecutionPlan(), makeResult(250), makeContext(warnEvents));

expect(warnEvents).toHaveLength(0);
});

it('defaults the threshold to 250ms', async () => {
const warnEvents: unknown[] = [];
const middleware = slowQueryWarning();

await middleware.afterExecute?.(makeExecutionPlan(), makeResult(251), makeContext(warnEvents));

expect(warnEvents).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ clean; `contract.json`, `contract.d.ts`, and both hashes unchanged). No user act
required. Incidental substrate diff only.
-->

<!--
Slow-query warning middleware example (PR #912): the `prisma-next-demo` example
gains a `slowQueryWarning` custom middleware (`src/prisma/slow-query-warning.ts`,
wired into the runtime `middleware: [...]` chain in `src/prisma/db.ts`, with
offline unit tests). Documentation-driven example code only — it exercises the
existing public `SqlMiddleware` `afterExecute` hook and changes no framework
surface, contract shape, or emitted artefact. No user action required.
Incidental substrate diff only.
-->

<!--
TML-2953 (this PR): Mongo enum fields now type through a storage value set, the same
way SQL does. Authoring a Mongo enum writes a value set into
Expand Down
Loading