@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.
- package/.claude/rules/testing.md +68 -4
- package/CLAUDE.md +1 -0
- package/FRAMEWORK-API.md +1 -1
- package/migration-guides/11.36.3-to-11.36.4.md +145 -0
- package/package.json +1 -1
package/.claude/rules/testing.md
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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-
|
|
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
|
+
"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",
|