diff --git a/CHANGELOG.md b/CHANGELOG.md
index a85a9a1..f57c1b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## Unreleased
+
+### Features
+
+- Add automatic Vitest enforcement through the `qguard/vitest/setup` export and the
+ `qguardSetup` path for `test.setupFiles`. Each test receives an isolated query context and fails
+ with `QueryGuardError` when it produces an N+1 pattern.
+
## 0.3.1
### Fixes
diff --git a/README.md b/README.md
index 40b6d65..34e1017 100644
--- a/README.md
+++ b/README.md
@@ -14,13 +14,19 @@ npm install qguard
```
```ts
-import { assertNoNPlusOne } from 'qguard/vitest'
-
-test('listing users does not N+1', async () => {
- await assertNoNPlusOne(() => handler(req, res))
+// vitest.config.ts
+import { defineConfig } from 'vitest/config'
+import { qguardSetup } from 'qguard/vitest'
+
+export default defineConfig({
+ test: {
+ setupFiles: [qguardSetup],
+ },
})
```
+Every test now fails when it produces an N+1 query pattern. No per-test wrapper is required.
+
## Why
Every ORM makes it easy to write a loop that fires one query per row. Load 100 users, each with a profile: that's 101 queries instead of 2. The database barely notices in development, then the page takes 4 seconds in production with real data.
@@ -58,9 +64,39 @@ queryguard monkey-patches `pg.Client.prototype.query`, `pg.Pool.prototype.query`
## API
+### Automatic Vitest enforcement
+
+Add `qguardSetup` to Vitest's setup files to guard the complete suite:
+
+```ts
+// vitest.config.ts
+import { defineConfig } from 'vitest/config'
+import { qguardSetup } from 'qguard/vitest'
+
+export default defineConfig({
+ test: {
+ setupFiles: [qguardSetup],
+ },
+})
+```
+
+`qguardSetup` is the absolute path to the published `qguard/vitest/setup` entry point. Vitest
+resolves string values in `setupFiles` relative to the project root, so use the exported path
+instead of the bare string `'qguard/vitest/setup'`.
+
+The setup module installs the database hooks once and creates a separate `AsyncLocalStorage`
+context for every test. It includes queries from the test's `beforeEach` hooks, test body, and
+`afterEach` hooks, and safely isolates concurrent tests. Any detection is reported as a
+`QueryGuardError` on the test that produced it.
+
+Queries executed while test files are being imported or in `beforeAll`/`afterAll` cannot be
+attributed to an individual test and are not tracked by this integration. Remove `qguardSetup`
+from the config to disable suite-wide enforcement. The explicit APIs below remain useful for
+narrower scopes, query budgets, and scaling checks.
+
### assertNoNPlusOne
-Runs a function and throws if any N+1 pattern is detected. Available from `queryguard/vitest` and `queryguard/jest`.
+Runs a function and throws if any N+1 pattern is detected. Available from `qguard/vitest` and `qguard/jest`.
```ts
import { assertNoNPlusOne } from 'qguard/vitest'
diff --git a/docs/index.html b/docs/index.html
index ee61c91..e12fa02 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -444,7 +444,7 @@
Catch N+1 queries before they hit production
How it works
-
Three lines of code. No config files.
+
One config entry. Every test guarded.
QueryGuard intercepts pg.Client and pg.Pool queries at the driver level. Every query is fingerprinted and tracked per async context. If the same query repeats above a threshold, your test fails.
@@ -452,13 +452,13 @@
Catch N+1 queries before they hit production
01
Install
npm install qguard
-
No plugins to register. No setup files to create. No environment variables to set.
+
No plugin package, generated setup file, or environment variable required.
02
-
Wrap your test
-
await assertNoNPlusOne(() => handler(req, res))
-
Import from queryguard/vitest or queryguard/jest. One function call wraps your handler.
+
Enable the suite
+
setupFiles: [qguardSetup]
+
Import qguardSetup from qguard/vitest. Every test is guarded automatically.
03
diff --git a/docs/plans/2026-08-31-vitest-auto-setup.md b/docs/plans/2026-08-31-vitest-auto-setup.md
new file mode 100644
index 0000000..0fa8c22
--- /dev/null
+++ b/docs/plans/2026-08-31-vitest-auto-setup.md
@@ -0,0 +1,37 @@
+# Vitest automatic setup
+
+Date: 2026-08-31
+Issue: [#9](https://github.com/oniani1/queryguard/issues/9)
+
+## Goal
+
+Allow a Vitest suite to enforce qguard for every test from one configuration entry:
+
+```ts
+import { qguardSetup } from 'qguard/vitest'
+
+export default defineConfig({
+ test: {
+ setupFiles: [qguardSetup],
+ },
+})
+```
+
+The existing `qguard/vitest` assertion helpers remain explicit and side-effect free. Importing
+`qguardSetup` loads the `qguard/vitest/setup` entry point, which installs the database hooks once,
+opens an isolated tracking context for each test, dispatches configured notifications, and fails
+the affected test with `QueryGuardError` when the context contains an N+1 detection.
+
+## Plan
+
+- [x] Add a runner-level failing test proving setup-file enforcement and clean-test behavior.
+- [x] Implement the Vitest lifecycle adapter with per-test `AsyncLocalStorage` isolation.
+- [x] Publish the `qguard/vitest/setup` package export and verify the packed artifact.
+- [x] Document automatic setup, configuration, opt-out, and interaction with explicit assertions.
+- [x] Run formatting, type checking, linting, unit tests, build, and relevant integration tests.
+
+## Non-goals
+
+- Baseline or acknowledgement files.
+- Jest, Mocha, or other runner adapters.
+- Changes to the N+1 detection algorithm.
diff --git a/package.json b/package.json
index f4dd12c..e787055 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,9 @@
"typescript"
],
"types": "./dist/index.d.ts",
- "sideEffects": false,
+ "sideEffects": [
+ "./dist/integrations/vitest-setup.js"
+ ],
"exports": {
".": {
"import": "./dist/index.js",
@@ -41,6 +43,11 @@
"require": "./dist/integrations/vitest.js",
"types": "./dist/integrations/vitest.d.ts"
},
+ "./vitest/setup": {
+ "import": "./dist/integrations/vitest-setup.js",
+ "require": "./dist/integrations/vitest-setup.js",
+ "types": "./dist/integrations/vitest-setup.d.ts"
+ },
"./jest": {
"import": "./dist/integrations/jest.js",
"require": "./dist/integrations/jest.js",
diff --git a/site/index.html b/site/index.html
index ee61c91..e12fa02 100644
--- a/site/index.html
+++ b/site/index.html
@@ -444,7 +444,7 @@
Catch N+1 queries before they hit production
How it works
-
Three lines of code. No config files.
+
One config entry. Every test guarded.
QueryGuard intercepts pg.Client and pg.Pool queries at the driver level. Every query is fingerprinted and tracked per async context. If the same query repeats above a threshold, your test fails.
@@ -452,13 +452,13 @@
Catch N+1 queries before they hit production
01
Install
npm install qguard
-
No plugins to register. No setup files to create. No environment variables to set.
+
No plugin package, generated setup file, or environment variable required.
02
-
Wrap your test
-
await assertNoNPlusOne(() => handler(req, res))
-
Import from queryguard/vitest or queryguard/jest. One function call wraps your handler.
+
Enable the suite
+
setupFiles: [qguardSetup]
+
Import qguardSetup from qguard/vitest. Every test is guarded automatically.
03
diff --git a/src/integrations/vitest-setup.ts b/src/integrations/vitest-setup.ts
new file mode 100644
index 0000000..5f68906
--- /dev/null
+++ b/src/integrations/vitest-setup.ts
@@ -0,0 +1,38 @@
+import { afterEach, beforeEach } from 'vitest'
+import { detect } from '../core/detector.js'
+import { dispatchNotifications } from '../core/notify.js'
+import { createContext, trackingAls } from '../core/tracker.js'
+import type { TrackingContext } from '../core/tracker.js'
+import { install } from '../drivers/install.js'
+import { QueryGuardError } from './shared.js'
+
+const contexts = new WeakMap