@lenne.tech/nest-server 11.36.3 → 11.36.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,6 +107,38 @@ pnpm run test:cleanup
107
107
  `@UnifiedField` deprecation warnings filtered).
108
108
  - Database (E2E only): **one unique database per run** (`nest-server-e2e-run-<ts>-p<pid>`), created by `tests/global-setup.ts` so concurrent runs cannot interfere with each other. Specs needing an extra DB derive it via `deriveTestDbUri('<suffix>')` — never a hardcoded/`Date.now()` name (escapes the cleanup scheme).
109
109
  - DB lifecycle (`tests/db-lifecycle.reporter.ts`): run passes → DB dropped immediately + stale run DBs from crashed/failed runs collected; run fails → DB kept for debugging. Additionally `tests/global-setup.ts` runs a **startup sweep** (shared `isStaleTestDb()` predicate, dead-PID/age guarded) — leftovers are removed when the NEXT run starts, which survives SIGKILL (check watchdog) and `--reporter` CLI overrides. An externally set `MONGODB_URI` (CI) opts out of the scheme.
110
+ - The drop guard (`SAFE_TEST_DB_PATTERN` + `NON_DISPOSABLE_DB_PATTERN`): nothing in the scheme drops
111
+ a database whose name does not carry `e2e`, `ci`, `test` or `acctest` as a **whole segment**
112
+ (`/(^|[-_])(e2e|ci|test|acctest)([-_]|$)/i`). It gates all three drop sites: the externally-set
113
+ `MONGODB_URI` branch, the startup sweep, and the reporter's post-run collection. **The anchoring
114
+ is the point, not decoration.** The original form matched the marker as a substring ANYWHERE, and
115
+ `ci` hides inside soCIal / speCIal / finanCIal / priCIng / muniCIpal, `test` inside laTEST /
116
+ conTEST / TESTimonials. `lt dev up` exports `MONGODB_URI` at the project's DEVELOPMENT database
117
+ (`<slug>-local`), so a developer on a project named e.g. `pricing-portal` who started the e2e
118
+ suite from that shell had their working data dropped BY the guard that exists to prevent exactly
119
+ that. It had never had a test.
120
+
121
+ Three things about it are load-bearing and easy to undo:
122
+
123
+ 1. **`acctest` is a separate alternative BECAUSE of the anchoring** — `test` no longer matches
124
+ inside it, so deleting it as "redundant" silently stops acceptance-test databases from ever
125
+ being collected.
126
+ 2. **The externally-set-URI branch is the only drop site with no second condition.** The other two
127
+ also require `isStaleTestDb()`, i.e. the name must belong to this project's own base. So that
128
+ one branch additionally applies `NON_DISPOSABLE_DB_PATTERN` (`-local`, `-dev`, `-prod`,
129
+ `-staging`, …) via `isDroppableTestDb()`: a project slug may legitimately carry a marker as a
130
+ whole word — `ci` is the German abbreviation for *Corporate Identity*, `test` a product noun
131
+ for an exam — and `ci-portal-local` passes the segment rule. It also refuses a URI that names
132
+ **no** database, because the driver would fall back to its default (`test`) and that name
133
+ passes the guard.
134
+ 3. **The anchoring is a naming contract on the base DB name.** A base like `shope2e` or
135
+ `app-e2edb` satisfies nothing, and its databases would then accumulate with nothing collecting
136
+ them — silently, which is why both sweep loops now COUNT and log the stale candidates the
137
+ guard refused.
138
+
139
+ Pinned by `tests/unit/db-lifecycle-guard.spec.ts` (registered mutation
140
+ `safe-test-db-pattern-unanchored`; reverting the anchoring turns 13 cases red). **The refusal list
141
+ in that spec IS the specification** — extend it rather than loosening the pattern.
110
142
  - Run governor (`tests/e2e-run-slots.ts`): machine-wide slot dir (`<tmpdir>/lt-e2e-run-slots`) caps concurrent e2e runs across ALL lt projects/sessions (default 2 on ≥8 cores). Further runs wait, logging `[e2e-governor] waiting…` every 15s (keeps the check watchdog fed — a queued run is NOT hung). The e2e config counts foreign slots at load time and drops to low-resource mode (reduced forks, raised timeouts) when another run is active — deterministic, unlike the lagging 1-min load average (kept as second signal). Knobs: `LT_E2E_MAX_RUNS` (0 disables), `LT_E2E_SLOT_DIR`, `LT_E2E_SLOT_TIMEOUT` (fail-open).
111
143
  - `retry: 2` (e2e) is deliberate — with `retry: 5`, one spec file with broken app/socket state ground through 6 attempts × 30s timeout × 22 tests ≈ an hour at 0% CPU (looked like a deadlock; the check watchdog killed it). Never raise retry to paper over contention.
112
144
  - Infrastructure containers (E2E only, the **seven** specs listed under "Infrastructure containers"
@@ -234,7 +266,7 @@ pnpm run check:mutations -- --id=<id> # one mutation
234
266
  pnpm run check:mutations -- --list # the registry, without running anything
235
267
  pnpm run check:mutations -- --allow-dirty # when the fix and its evidence share a working tree
236
268
  pnpm run check:mutations -- --jobs=4 # N mutations at a time (default: 2, or 4 on >=12 cores)
237
- pnpm run check:mutations -- --no-infra # only the 21 that need no MongoDB
269
+ pnpm run check:mutations -- --no-infra # only the 22 that need no MongoDB
238
270
  pnpm run check:mutations -- --since=<ref> # only mutations touching files changed since <ref>
239
271
  ```
240
272
 
@@ -252,17 +284,49 @@ when the registry, a vitest config or a setup file changed, since those can move
252
284
  **Why the gate does not cache per-mutation verdicts instead.** It runs ONCE PER RELEASE, not per
253
285
  commit, so selective re-running would save ~10 minutes a release. The price is a cache that has to
254
286
  model each spec's full dependency closure correctly, and getting that wrong produces a stale PASS
255
- for a test that has since gone vacuous — exactly what the gate is there to prevent. Bad trade at 51
287
+ for a test that has since gone vacuous — exactly what the gate is there to prevent. Bad trade at 52
256
288
  mutations. Worth revisiting around 100, where the full run approaches half an hour.
257
289
 
258
290
  Not part of `pnpm run check` — it edits source and re-runs whole e2e suites. It belongs in review
259
- and on the publish path.
291
+ and on the publish path. It is also reachable on demand, without cutting a release:
292
+
293
+ ```
294
+ gh workflow run regression-evidence.yml --ref develop # the full registry
295
+ gh workflow run regression-evidence.yml --ref develop -f args="--no-infra" # the unit subset
296
+ ```
297
+
298
+ **Why that matters, and what it cost to learn (11.36.3).** The gate lived only in `publish.yml`,
299
+ which triggers on `release: released`. So the only way to observe it on a CI runner was to cut a
300
+ release — and when it reported `0/51 mutations confirmed`, diagnosing that required cutting another
301
+ one. A gate you cannot run without shipping is a gate you cannot debug.
302
+
303
+ **A verdict must carry its reason.** `INCONCLUSIVE` exists so a crashed, timed-out or starved run is
304
+ not counted as evidence — but the first version reported the verdict and discarded the output that
305
+ explained it. `classifyRun()` now attaches the last 25 lines (`tailOf`) and names the exit code.
306
+
307
+ **The parser must strip ANSI, and this is the load-bearing part.** vitest COLOURS its summary on a CI
308
+ runner, so the line carries escape sequences between `Tests` and the number; `\s+` matches whitespace,
309
+ never an escape. The count therefore did not parse in CI — and it never had. The *previous* verdict
310
+ was `failedCount ?? 'some'` with `ok: true` on any non-zero exit, so it never needed the count: on the
311
+ publish path the gate had been accepting a crash, a timeout or a collection error as "went red".
312
+
313
+ Two consequences worth stating plainly:
314
+
315
+ 1. Requiring a real failure count is what makes the gate a check rather than a rubber stamp. Do not
316
+ relax it back to "non-zero exit is enough" — that is the defect, not a convenience.
317
+ 2. **Mutation confirmations from CI runs before 11.36.3 are not evidence.** They were verified
318
+ locally (where output is colourless) and stamped in CI. The first trustworthy full-registry run on
319
+ a runner is the `51/51` from the 11.36.3 retag.
320
+
321
+ Anything that PARSES a spawned run's output belongs behind `stripAnsi()`: the same command is
322
+ colourless on a pipe and colourised on a runner, so a regex tested locally can be reliably wrong in
323
+ exactly the environment where the answer matters.
260
324
 
261
325
  ### The cost is vitest's cold start, not the tests
262
326
 
263
327
  Worth knowing before optimising the wrong thing: the specs behind all 30 e2e mutations add up to
264
328
  **~40 seconds**. The step takes ~740s. The remaining ~700s is paying vitest's startup — process
265
- spawn, transform, module graph, mongod connect, DB create and drop — once per mutation, 51 times.
329
+ spawn, transform, module graph, mongod connect, DB create and drop — once per mutation, 52 times.
266
330
  That work is largely single-threaded I/O and barely scales with cores: the full registry measures
267
331
  **744s on a 12-core laptop and 777s on a 4-vCPU CI runner**.
268
332
 
package/CLAUDE.md CHANGED
@@ -24,6 +24,7 @@ The following documents must be kept up to date when making changes that affect
24
24
  | `.claude/rules/configurable-features.md` | Adding new configurable features |
25
25
  | `src/templates/*.ejs` + `tests/unit/email-templates.spec.ts` | Changing a mail template — a template is never imported, type-checked or linted, so the only thing binding it to its caller is the variable names inside it. Add the caller's data shape to the spec |
26
26
  | `FRAMEWORK-API.md` | Auto-regenerated by `pnpm run build` — verify after adding interfaces, CrudService methods, or core modules |
27
+ | `.claude/rules/testing.md` | Changing the test-runner split, the per-run DB lifecycle or its **drop guard**, the run governor, the infrastructure containers, or the regression-evidence gate. This row exists because the drop guard — the one thing standing between a test run and a developer's data — went undocumented for its whole life: nothing in this table pointed at the file that describes everything around it |
27
28
 
28
29
  **Rule:** When a code change adds, removes, or modifies a feature listed in `docs/REQUEST-LIFECYCLE.md` (Features Overview, diagrams, decorator reference, configuration, etc.), update the document in the same commit or PR.
29
30
 
package/FRAMEWORK-API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @lenne.tech/nest-server — Framework API Reference
2
2
 
3
- > Auto-generated from source code on 2026-08-22 (v11.36.3)
3
+ > Auto-generated from source code on 2026-08-23 (v11.36.4)
4
4
  > File: `FRAMEWORK-API.md` — compact, machine-readable API surface for Claude Code
5
5
 
6
6
  ## CoreModule.forRoot()
@@ -0,0 +1,145 @@
1
+ # Migration Guide: 11.36.3 → 11.36.4
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **New Features** | None |
9
+ | **Bugfixes** | **The test-database drop guard accepted ordinary project databases** — a name merely CONTAINING `ci`/`test` read as disposable, so an e2e run started from an `lt dev` shell could drop the developer's DEVELOPMENT database (§1). Framework-internal, but every starter-derived project carries a private copy of the same code |
10
+ | **Migration Effort** | **None** for the framework itself — `tests/` is not published. **~1 minute, recommended**, if your project was generated from `nest-server-starter`: apply the one-line fix in §1 to your own `tests/db-lifecycle.reporter.ts` |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ ```bash
17
+ pnpm update @lenne.tech/nest-server@11.36.4
18
+ ```
19
+
20
+ Then read §1 — it is the only thing in this release that can affect you, and `pnpm update` does
21
+ **not** deliver it, because the affected file lives in your repository, not in the package.
22
+
23
+ ---
24
+
25
+ ## 1. Recommended: tighten the test-database drop guard in your own `tests/`
26
+
27
+ **Why this reaches you at all.** `tests/db-lifecycle.reporter.ts` is not part of the npm package
28
+ (`package.json` → `files` does not include `tests/`) and not part of the vendor-mode file set. It is
29
+ a **template** file: `nest-server-starter` ships it, and every project ever generated from the
30
+ starter holds a private copy that no update channel ever touches. So this guide is the only way the
31
+ fix reaches an existing project.
32
+
33
+ **Were you affected?** Only if all three are true:
34
+
35
+ 1. Your project's slug contains `ci`, `test`, `e2e` or `acctest` **as a substring** — including
36
+ inside an ordinary word: `ci` hides in so**ci**al, spe**ci**al, finan**ci**al, invoi**ci**ng,
37
+ pri**ci**ng, muni**ci**pal; `test` hides in la**test**, con**test**, **test**imonials.
38
+ 2. You use `lt dev up`, which exports `MONGODB_URI` pointing at `<slug>-local` — your **development**
39
+ database.
40
+ 3. You started the e2e suite from that same shell.
41
+
42
+ Then `tests/global-setup.ts` dropped that database. The guard meant to prevent exactly this accepted
43
+ it, because it matched the marker anywhere in the name rather than as a delimited segment.
44
+
45
+ ### The fix
46
+
47
+ ```diff
48
+ - export const SAFE_TEST_DB_PATTERN = /(e2e|ci|test|acctest)/i;
49
+ + export const SAFE_TEST_DB_PATTERN = /(^|[-_])(e2e|ci|test|acctest)([-_]|$)/i;
50
+ ```
51
+
52
+ The marker must now be a whole segment, delimited by `-`, `_`, or a string boundary.
53
+
54
+ ### Check your own database names before applying it
55
+
56
+ The change is a strict narrowing — every name the new pattern accepts, the old one accepted too, so
57
+ it cannot start dropping something it previously spared. The risk runs the other way: if your **base**
58
+ test database name does not carry the marker as a delimited segment (`shope2e`, `app-e2edb`,
59
+ `projecttest`), the guard now refuses it and **cleanup silently stops collecting** — the databases
60
+ accumulate with nothing removing them.
61
+
62
+ ```bash
63
+ # What does your suite actually name its databases?
64
+ grep -n "mongodb://" src/config.env.ts
65
+ ```
66
+
67
+ `myproject-e2e` and `myproject_ci` are fine. `myproje2e` is not — rename the database rather than
68
+ loosening the pattern.
69
+
70
+ Since 11.36.4 both sweep loops **count and log** the stale databases the guard refused, so this
71
+ condition announces itself instead of leaking quietly:
72
+
73
+ ```
74
+ Startup sweep: 3 stale database(s) skipped by the safety guard —
75
+ "shope2e" needs a delimited e2e/ci/test/acctest segment for cleanup to work.
76
+ ```
77
+
78
+ ### Two further hardening steps, worth copying
79
+
80
+ Both live in the same file and are optional, but they close the residual cases the pattern alone
81
+ cannot:
82
+
83
+ **A second guard for the one drop site that has no other condition.** The startup sweep and the
84
+ post-run collection both additionally require `isStaleTestDb()` — the name must belong to your own
85
+ base. The externally-supplied-`MONGODB_URI` branch has no such backstop, and a project slug may
86
+ legitimately carry a marker as a *whole word*: `ci` is the ordinary German abbreviation for
87
+ *Corporate Identity*, `test` a product noun for an exam. `ci-portal-local` passes the segment rule.
88
+
89
+ ```typescript
90
+ export const NON_DISPOSABLE_DB_PATTERN
91
+ = /(^|[-_])(local|dev|develop|development|prod|production|staging|stage|live)([-_]|$)/i;
92
+
93
+ export function isDroppableTestDb(name: string): boolean {
94
+ return SAFE_TEST_DB_PATTERN.test(name) && !NON_DISPOSABLE_DB_PATTERN.test(name);
95
+ }
96
+ ```
97
+
98
+ Then use `isDroppableTestDb(db.databaseName)` in place of `SAFE_TEST_DB_PATTERN.test(...)` in
99
+ `tests/global-setup.ts` — **only there**. Applying it to the sweep paths could only turn a collected
100
+ database into a leaked one.
101
+
102
+ **Refuse a URI that names no database.** `mongodb://127.0.0.1` makes the driver fall back to its
103
+ default database, `test` — a name the guard accepts, so a truncated `MONGODB_URI` dropped the
104
+ server's `test` database instead of failing. Note that this needs the `splitMongoUri()` fix too: the
105
+ previous greedy form reported the **host** as the database name for such a URI, so the check could
106
+ never fire.
107
+
108
+ ```typescript
109
+ const match = uri.match(/^(mongodb(?:\+srv)?:\/\/[^/?]*)(?:\/([^/?]*))?(\?.*)?$/i);
110
+ ```
111
+
112
+ This also fixes a second case the greedy form got wrong: an option value containing a slash
113
+ (`?tlsCAFile=/etc/ssl/ca.pem`) previously reported `ca.pem` as the database name.
114
+
115
+ ### Pin it
116
+
117
+ The framework's own spec is `tests/unit/db-lifecycle-guard.spec.ts` — copy it alongside the fix. Its
118
+ refusal list *is* the specification, and reverting the anchoring turns 13 cases red.
119
+
120
+ ---
121
+
122
+ ## Compatibility Notes
123
+
124
+ | Pattern | Status |
125
+ |---------|--------|
126
+ | npm-mode projects | Unaffected by the package update; apply §1 to your own `tests/` |
127
+ | Vendor-mode projects | Same — `tests/` is outside the vendor file set |
128
+ | CI pipelines that pin `MONGODB_URI` | Unaffected, unless the pinned URI names no database or carries an environment suffix. A CI database called `myproject-ci` is fine; one called `myproject-ci-staging` is now refused |
129
+ | Projects not generated from `nest-server-starter` | Nothing to do |
130
+
131
+ ---
132
+
133
+ ## Troubleshooting
134
+
135
+ **`Refusing to dropDatabase("…"): not a recognized disposable test database`**
136
+ Your `MONGODB_URI` points at something the guard will not delete. That is the guard working. Check
137
+ whether the URI is aimed at your development database — an `lt dev` shell exports it that way — and
138
+ point it at a disposable test database instead. Do not loosen the pattern to get past this.
139
+
140
+ **`Refusing to use MONGODB_URI="…": it names no database`**
141
+ The URI has no path (`mongodb://host` rather than `mongodb://host/mydb`). Add the database name.
142
+
143
+ **`N stale database(s) skipped by the safety guard`**
144
+ Your base database name does not carry a delimited marker, so cleanup cannot collect its leftovers.
145
+ Rename the database (see §1).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.36.3",
3
+ "version": "11.36.4",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",