@lenne.tech/nest-server 11.22.0 → 11.23.0

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.
Files changed (30) hide show
  1. package/.claude/rules/configurable-features.md +1 -0
  2. package/.claude/rules/framework-compatibility.md +79 -0
  3. package/CLAUDE.md +60 -0
  4. package/FRAMEWORK-API.md +235 -0
  5. package/dist/core/common/decorators/restricted.decorator.js +21 -4
  6. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  7. package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
  8. package/dist/core/common/services/crud.service.d.ts +4 -1
  9. package/dist/core/common/services/crud.service.js +24 -2
  10. package/dist/core/common/services/crud.service.js.map +1 -1
  11. package/dist/core/common/services/module.service.d.ts +3 -2
  12. package/dist/core/common/services/module.service.js +43 -20
  13. package/dist/core/common/services/module.service.js.map +1 -1
  14. package/dist/core/common/services/request-context.service.d.ts +3 -0
  15. package/dist/core/common/services/request-context.service.js +12 -0
  16. package/dist/core/common/services/request-context.service.js.map +1 -1
  17. package/dist/server/modules/file/file-info.model.d.ts +1 -5
  18. package/dist/server/modules/user/user.model.d.ts +1 -5
  19. package/dist/tsconfig.build.tsbuildinfo +1 -1
  20. package/docs/REQUEST-LIFECYCLE.md +25 -2
  21. package/docs/native-driver-security.md +153 -0
  22. package/docs/process-performance-optimization.md +493 -0
  23. package/migration-guides/11.22.0-to-11.22.1.md +105 -0
  24. package/migration-guides/11.22.x-to-11.23.0.md +235 -0
  25. package/package.json +33 -31
  26. package/src/core/common/decorators/restricted.decorator.ts +44 -4
  27. package/src/core/common/interfaces/server-options.interface.ts +8 -0
  28. package/src/core/common/services/crud.service.ts +77 -5
  29. package/src/core/common/services/module.service.ts +96 -35
  30. package/src/core/common/services/request-context.service.ts +47 -0
@@ -0,0 +1,105 @@
1
+ # Migration Guide: 11.22.0 → 11.22.1
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **New Features** | `FRAMEWORK-API.md` — auto-generated compact API reference shipped with npm package |
9
+ | **Bugfixes** | Security override updates (minimatch, undici, srvx, path-to-regexp) |
10
+ | **Dependency Updates** | NestJS 11.1.18, mongoose 9.4.1 |
11
+ | **Migration Effort** | ~2 minutes (optional, backward compatible) |
12
+
13
+ ---
14
+
15
+ ## Quick Migration (No Breaking Changes)
16
+
17
+ ```bash
18
+ # Update package
19
+ pnpm add @lenne.tech/nest-server@11.22.1
20
+
21
+ # Verify build
22
+ pnpm run build
23
+
24
+ # Run tests
25
+ pnpm test
26
+ ```
27
+
28
+ No code changes required. All existing patterns continue to work.
29
+
30
+ ---
31
+
32
+ ## What's New in 11.22.1
33
+
34
+ ### 1. FRAMEWORK-API.md — Auto-Generated API Reference
35
+
36
+ The npm package now ships a compact, machine-readable API reference (`FRAMEWORK-API.md`) that is
37
+ auto-generated from source code during `pnpm run build`. It contains:
38
+
39
+ - `CoreModule.forRoot()` overload signatures
40
+ - All configuration interfaces (`IServerOptions`, `IBetterAuth`, `IMultiTenancy`, etc.) with types and defaults
41
+ - `ServiceOptions` interface
42
+ - `CrudService` public method signatures
43
+ - Core module listing with documentation status
44
+
45
+ **For AI-assisted development:** Update your project's `CLAUDE.md` to reference this file:
46
+
47
+ ```markdown
48
+ ## Framework: @lenne.tech/nest-server
49
+ When working with framework features, read source and docs from:
50
+ node_modules/@lenne.tech/nest-server/CLAUDE.md
51
+ node_modules/@lenne.tech/nest-server/FRAMEWORK-API.md
52
+ node_modules/@lenne.tech/nest-server/src/core/modules/*/INTEGRATION-CHECKLIST.md
53
+ ```
54
+
55
+ This enables AI tools to quickly understand the framework's complete API surface without
56
+ reading every source file individually.
57
+
58
+ ### 2. Dependency Updates
59
+
60
+ | Package | From | To | Type |
61
+ |---------|------|----|------|
62
+ | `@nestjs/common` | 11.1.17 | 11.1.18 | patch |
63
+ | `@nestjs/core` | 11.1.17 | 11.1.18 | patch |
64
+ | `@nestjs/platform-express` | 11.1.17 | 11.1.18 | patch |
65
+ | `@nestjs/websockets` | 11.1.17 | 11.1.18 | patch |
66
+ | `@nestjs/testing` (dev) | 11.1.17 | 11.1.18 | patch |
67
+ | `mongoose` | 9.3.3 | 9.4.1 | minor |
68
+
69
+ ### 3. Security Override Updates
70
+
71
+ Updated security overrides to latest patched versions:
72
+
73
+ | Override | From | To |
74
+ |----------|------|----|
75
+ | `minimatch` | 3.1.4 / 9.0.7 / 10.2.4 | 3.1.5 / 9.0.9 / 10.2.5 |
76
+ | `undici` | 7.24.3 | 7.24.7 |
77
+ | `srvx` | 0.11.13 | 0.11.15 |
78
+ | `path-to-regexp` | 8.4.1 | 8.4.2 |
79
+
80
+ ---
81
+
82
+ ## Compatibility Notes
83
+
84
+ | Pattern | Status |
85
+ |---------|--------|
86
+ | All existing `CoreModule.forRoot()` patterns | Works unchanged |
87
+ | `ICoreModuleOverrides` from 11.22.0 | Works unchanged |
88
+ | mongoose queries and schemas | Compatible (9.4.1 is backward compatible) |
89
+
90
+ ---
91
+
92
+ ## Troubleshooting
93
+
94
+ | Issue | Solution |
95
+ |-------|----------|
96
+ | `FRAMEWORK-API.md` not found in `node_modules` | Ensure `@lenne.tech/nest-server@11.22.1` is installed |
97
+ | Override conflicts after update | Run `pnpm install` to apply updated lockfile |
98
+
99
+ ---
100
+
101
+ ## References
102
+
103
+ - [FRAMEWORK-API.md](../FRAMEWORK-API.md) — The new API reference
104
+ - [Framework Compatibility Rules](../.claude/rules/framework-compatibility.md) — Maintenance obligations
105
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
@@ -0,0 +1,235 @@
1
+ # Migration Guide: 11.22.x → 11.23.0
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | `mainDbModel.collection` and `mainDbModel.db` blocked via TypeScript (`SafeModel`) |
8
+ | **New Features** | `process()` depth-based optimization, `debugProcessInput` config, `getNativeCollection(reason)`, `getNativeConnection(reason)`, restricted metadata cache |
9
+ | **Performance** | ~70% less memory on nested service cascades, eliminated redundant `JSON.stringify` and recursive `process()` calls |
10
+ | **Migration Effort** | ~5 minutes if using `mainDbModel.db`; 0 minutes otherwise |
11
+
12
+ ---
13
+
14
+ ## Quick Migration (No `mainDbModel.db` Usage)
15
+
16
+ If your project does **not** access `this.mainDbModel.collection` or `this.mainDbModel.db` directly:
17
+
18
+ ```bash
19
+ # Update package
20
+ pnpm add @lenne.tech/nest-server@11.23.0
21
+
22
+ # Verify build
23
+ pnpm run build
24
+
25
+ # Run tests
26
+ pnpm test
27
+ ```
28
+
29
+ No code changes required. All performance optimizations activate automatically.
30
+
31
+ ---
32
+
33
+ ## Breaking Changes
34
+
35
+ ### `mainDbModel` Type Changed to `SafeModel` (TypeScript Only)
36
+
37
+ `mainDbModel` is now typed as `SafeModel<T>` which is `Omit<Model<T>, 'collection' | 'db'>`. This is a **compile-time-only** change — runtime behavior is identical.
38
+
39
+ **What breaks:** Direct access to `.collection` or `.db` on `this.mainDbModel` produces a TypeScript error.
40
+
41
+ **What does NOT break:** All Mongoose Model methods (`find`, `findById`, `insertMany`, `updateOne`, `aggregate`, `bulkWrite`, etc.) continue to work unchanged.
42
+
43
+ #### If You Use `mainDbModel.collection`
44
+
45
+ **Before:**
46
+ ```typescript
47
+ // Direct native collection access — bypasses ALL Mongoose plugins
48
+ await this.mainDbModel.collection.insertOne(doc);
49
+ ```
50
+
51
+ **After (Option A — use Mongoose Model method):**
52
+ ```typescript
53
+ // Mongoose method — plugins (Tenant, Audit, RoleGuard, Password) fire correctly
54
+ await this.mainDbModel.insertMany([doc]);
55
+ ```
56
+
57
+ **After (Option B — intentional native access with logging):**
58
+ ```typescript
59
+ // Logged escape hatch — requires justification
60
+ const col = this.getNativeCollection('Migration: bulk-import historical data without tenant context');
61
+ await col.insertOne(doc);
62
+ ```
63
+
64
+ #### If You Use `mainDbModel.db`
65
+
66
+ **Before:**
67
+ ```typescript
68
+ // Native DB access via model — bypasses ALL Mongoose plugins
69
+ const count = await this.mainDbModel.db.db.collection('chatmessages').countDocuments({ ... });
70
+ ```
71
+
72
+ **After (Option A — inject the target model):**
73
+ ```typescript
74
+ // Inject the model via @InjectModel and use Mongoose methods
75
+ constructor(@InjectModel('ChatMessage') private chatMessageModel: Model<ChatMessageDocument>) {}
76
+
77
+ const count = await this.chatMessageModel.countDocuments({ ... });
78
+ ```
79
+
80
+ **After (Option B — intentional native access with logging):**
81
+ ```typescript
82
+ // Logged escape hatch — requires justification
83
+ const conn = this.getNativeConnection('Statistics: count chatmessages across all tenants');
84
+ const count = await conn.db.collection('chatmessages').countDocuments({ ... });
85
+ ```
86
+
87
+ #### If You Use `getModel()`
88
+
89
+ `getModel()` continues to return the full `Model` type (including `.collection` and `.db`). No change needed.
90
+
91
+ ---
92
+
93
+ ## What's New in 11.23.0
94
+
95
+ ### 1. process() Pipeline Performance Optimization
96
+
97
+ The `process()` method in `ModuleService` now tracks nesting depth via `RequestContext`. On nested calls (service cascades like A.create → B.create → C.create), redundant pipeline steps are skipped:
98
+
99
+ | Step | Outermost Call | Nested Calls |
100
+ |------|---------------|--------------|
101
+ | prepareInput | Runs | Runs |
102
+ | checkRights (input) | Runs | Runs |
103
+ | serviceFunc | Runs | Runs |
104
+ | processFieldSelection (populate) | Runs | **Skipped** (unless explicit `populate` is set) |
105
+ | prepareOutput (model mapping) | Runs | **Skipped** (secret removal still active) |
106
+ | checkRights (output) | Runs | **Skipped** |
107
+
108
+ **Security is maintained** through three layers:
109
+ 1. Input checkRights always runs at every depth
110
+ 2. Output checkRights runs at depth 0 (outermost call)
111
+ 3. `CheckSecurityInterceptor` (Safety Net) runs on the final HTTP response
112
+
113
+ **Estimated savings for an 8-level service cascade:** ~70% less memory (~120 KB instead of ~400 KB).
114
+
115
+ No configuration needed — this activates automatically.
116
+
117
+ ### 2. Lean Query for Rights Checking
118
+
119
+ The `process()` pipeline previously called `this.get()` to resolve `dbObject` for authorization checks, which triggered a **recursive** `process()` call. It now uses a direct lean query:
120
+
121
+ ```typescript
122
+ // Before: recursive process() call with full pipeline
123
+ const dbObject = await this.get(id);
124
+
125
+ // After: single lean query + map
126
+ const rawDoc = await this.mainDbModel.findById(id).lean().exec();
127
+ const dbObject = mainModelConstructor.map(rawDoc);
128
+ ```
129
+
130
+ This preserves ALL fields (including `createdBy`) needed for `S_CREATOR` and `S_SELF` checks, while avoiding the overhead of a full pipeline pass.
131
+
132
+ ### 3. `debugProcessInput` Configuration Option
133
+
134
+ The previous `JSON.stringify` debug comparison that ran on **every** `process()` call is now behind a config flag:
135
+
136
+ ```typescript
137
+ // config.env.ts — enable only for debugging
138
+ {
139
+ debugProcessInput: true, // default: false
140
+ }
141
+ ```
142
+
143
+ When disabled (default), two `JSON.stringify` calls per `process()` invocation are eliminated.
144
+
145
+ ### 4. Restricted Metadata Cache
146
+
147
+ `getRestricted()` results (from `@Restricted()` decorators) are now cached per class + property. Since decorator metadata is static (set at compile time and never changes), this eliminates hundreds of `Reflect.getMetadata()` lookups per request for objects with many properties.
148
+
149
+ ### 5. Native Driver Security (`SafeModel`, `getNativeCollection`, `getNativeConnection`)
150
+
151
+ Two new protected methods in `CrudService` provide logged escape hatches for legitimate native MongoDB driver access:
152
+
153
+ ```typescript
154
+ // Get the native MongoDB Collection for this model's collection
155
+ protected getNativeCollection(reason: string): Collection
156
+
157
+ // Get the Mongoose Connection (for cross-collection access, schema-less collections)
158
+ protected getNativeConnection(reason: string): Connection
159
+ ```
160
+
161
+ Both methods:
162
+ - Require a non-empty justification string
163
+ - Log a `[SECURITY]` warning on every call
164
+ - Throw an `Error` if no reason is provided
165
+
166
+ The `SafeModel<T>` type is exported for use in custom service implementations.
167
+
168
+ ### 6. RequestContext.processDepth API
169
+
170
+ New methods for tracking `process()` nesting depth:
171
+
172
+ ```typescript
173
+ // Get current depth (0 = outermost or no process() call)
174
+ RequestContext.getProcessDepth(): number
175
+
176
+ // Run a function with incremented depth
177
+ RequestContext.runWithIncrementedProcessDepth<T>(fn: () => T): T
178
+ ```
179
+
180
+ These are used internally by `process()` but are also available for custom pipeline implementations.
181
+
182
+ ---
183
+
184
+ ## Compatibility Notes
185
+
186
+ | Pattern | Status |
187
+ |---------|--------|
188
+ | All `CoreModule.forRoot()` patterns | Works unchanged |
189
+ | `CrudService` subclasses | Works unchanged (SafeModel is transparent for Mongoose methods) |
190
+ | Custom `process()` calls with `populate` option | Works unchanged (explicit populate overrides nested skip) |
191
+ | `getModel()` callers | Works unchanged (returns full Model type) |
192
+ | `@Restricted()` decorators | Works unchanged (cache is transparent) |
193
+ | Services using `this.mainDbModel.find/save/update/etc.` | Works unchanged |
194
+ | Services using `this.mainDbModel.collection` | **TypeScript error** — use `getNativeCollection(reason)` or Mongoose methods |
195
+ | Services using `this.mainDbModel.db` | **TypeScript error** — use `getNativeConnection(reason)` or inject target model |
196
+ | Services using `@InjectConnection` | Works unchanged (Connection is not affected by SafeModel) |
197
+
198
+ ---
199
+
200
+ ## Troubleshooting
201
+
202
+ | Issue | Solution |
203
+ |-------|----------|
204
+ | TypeScript error on `mainDbModel.collection` | Replace with Mongoose Model method or `this.getNativeCollection('reason')` |
205
+ | TypeScript error on `mainDbModel.db` | Replace with `this.getNativeConnection('reason')` or inject the target model via `@InjectModel` |
206
+ | `getModel()` return type mismatch | `getModel()` returns full `Model` — no change needed |
207
+ | `processFieldSelection` type error in custom override | Update parameter type to accept `SafeModel` (see `ModuleService.processFieldSelection`) |
208
+ | Debug logs from `prepareInput` disappeared | Set `debugProcessInput: true` in config to re-enable |
209
+
210
+ ---
211
+
212
+ ## Module Documentation
213
+
214
+ ### Native Driver Security
215
+
216
+ - **Security Policy:** [`docs/native-driver-security.md`](../docs/native-driver-security.md)
217
+ - **Performance Optimization:** [`docs/process-performance-optimization.md`](../docs/process-performance-optimization.md)
218
+
219
+ ### Affected Source Files
220
+
221
+ | File | Changes |
222
+ |------|---------|
223
+ | `src/core/common/services/module.service.ts` | `SafeModel` type, `mainDbModel` type change, `process()` depth optimization, lean query, debug config |
224
+ | `src/core/common/services/crud.service.ts` | `getNativeCollection(reason)`, `getNativeConnection(reason)`, `getModel()` cast |
225
+ | `src/core/common/services/request-context.service.ts` | `processDepth` field, `getProcessDepth()`, `runWithIncrementedProcessDepth()` |
226
+ | `src/core/common/decorators/restricted.decorator.ts` | Metadata cache, `_.uniq()` optimization |
227
+ | `src/core/common/interfaces/server-options.interface.ts` | `debugProcessInput` config option |
228
+
229
+ ---
230
+
231
+ ## References
232
+
233
+ - [Native Driver Security Policy](../docs/native-driver-security.md)
234
+ - [process() Performance Optimization](../docs/process-performance-optimization.md)
235
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.22.0",
3
+ "version": "11.23.0",
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",
@@ -14,7 +14,8 @@
14
14
  "homepage": "https://github.com/lenneTech/nest-server",
15
15
  "license": "MIT",
16
16
  "scripts": {
17
- "build": "rimraf dist && nest build && pnpm run build:copy-types && pnpm run build:copy-templates && pnpm run build:add-type-references",
17
+ "build": "rimraf dist && nest build && pnpm run build:copy-types && pnpm run build:copy-templates && pnpm run build:add-type-references && pnpm run build:framework-api",
18
+ "build:framework-api": "npx tsx scripts/generate-framework-api.ts",
18
19
  "build:copy-types": "mkdir -p dist/types && cp src/types/*.d.ts dist/types/",
19
20
  "build:copy-templates": "mkdir -p dist/core/modules/migrate/templates && cp src/core/modules/migrate/templates/migration-project.template.ts dist/core/modules/migrate/templates/",
20
21
  "build:add-type-references": "node scripts/add-type-references.js",
@@ -78,26 +79,27 @@
78
79
  "@better-auth/passkey": "1.5.5",
79
80
  "@getbrevo/brevo": "3.0.1",
80
81
  "@nestjs/apollo": "13.2.4",
81
- "@nestjs/common": "11.1.17",
82
- "@nestjs/core": "11.1.17",
82
+ "@nestjs/common": "11.1.18",
83
+ "@nestjs/core": "11.1.18",
83
84
  "@nestjs/graphql": "13.2.4",
84
85
  "@nestjs/jwt": "11.0.2",
85
86
  "@nestjs/mongoose": "11.0.4",
86
87
  "@nestjs/passport": "11.0.5",
87
- "@nestjs/platform-express": "11.1.17",
88
+ "@nestjs/platform-express": "11.1.18",
88
89
  "@nestjs/schedule": "6.1.1",
89
90
  "@nestjs/swagger": "11.2.6",
90
91
  "@nestjs/terminus": "11.1.1",
91
- "@nestjs/websockets": "11.1.17",
92
+ "@nestjs/websockets": "11.1.18",
92
93
  "@tus/file-store": "2.0.0",
93
94
  "@tus/server": "2.3.0",
95
+ "@types/supertest": "7.2.0",
94
96
  "bcrypt": "6.0.0",
95
97
  "better-auth": "1.5.5",
96
98
  "class-transformer": "0.5.1",
97
99
  "class-validator": "0.15.1",
98
100
  "compression": "1.8.1",
99
101
  "cookie-parser": "1.4.7",
100
- "dotenv": "17.4.0",
102
+ "dotenv": "17.4.1",
101
103
  "ejs": "5.0.1",
102
104
  "express": "5.2.1",
103
105
  "graphql": "16.13.2",
@@ -108,56 +110,55 @@
108
110
  "json-to-graphql-query": "2.3.0",
109
111
  "lodash": "4.18.1",
110
112
  "mongodb": "7.1.1",
111
- "mongoose": "9.3.3",
113
+ "mongoose": "9.4.1",
112
114
  "multer": "2.1.1",
113
115
  "node-mailjet": "6.0.11",
114
- "nodemailer": "8.0.4",
116
+ "nodemailer": "8.0.5",
115
117
  "passport": "0.7.0",
116
118
  "passport-jwt": "4.0.1",
117
119
  "reflect-metadata": "0.2.2",
118
120
  "rfdc": "1.4.1",
119
121
  "rxjs": "7.8.2",
122
+ "supertest": "7.2.2",
120
123
  "ts-morph": "27.0.2",
121
124
  "yuml-diagram": "1.2.0"
122
125
  },
123
126
  "devDependencies": {
124
127
  "@compodoc/compodoc": "1.2.1",
125
- "@nestjs/cli": "11.0.17",
128
+ "@nestjs/cli": "11.0.18",
126
129
  "@nestjs/schematics": "11.0.10",
127
- "@nestjs/testing": "11.1.17",
130
+ "@nestjs/testing": "11.1.18",
128
131
  "@swc/cli": "0.8.1",
129
- "@swc/core": "1.15.21",
132
+ "@swc/core": "1.15.24",
130
133
  "@types/compression": "1.8.1",
131
134
  "@types/cookie-parser": "1.4.10",
132
135
  "@types/ejs": "3.1.5",
133
136
  "@types/express": "5.0.6",
134
137
  "@types/lodash": "4.17.24",
135
138
  "@types/multer": "2.1.0",
136
- "@types/node": "25.5.0",
137
- "@types/nodemailer": "7.0.11",
139
+ "@types/node": "25.5.2",
140
+ "@types/nodemailer": "8.0.0",
138
141
  "@types/passport": "1.0.17",
139
- "@types/supertest": "7.2.0",
140
- "@vitest/coverage-v8": "4.1.2",
141
- "@vitest/ui": "4.1.2",
142
+ "@vitest/coverage-v8": "4.1.3",
143
+ "@vitest/ui": "4.1.3",
142
144
  "ansi-colors": "4.1.3",
143
145
  "find-file-up": "2.0.1",
144
146
  "husky": "9.1.7",
145
147
  "nodemon": "3.1.14",
146
148
  "npm-watch": "0.13.0",
147
149
  "otpauth": "9.5.0",
148
- "oxfmt": "0.43.0",
149
- "oxlint": "1.58.0",
150
+ "oxfmt": "0.44.0",
151
+ "oxlint": "1.59.0",
150
152
  "rimraf": "6.1.3",
151
- "supertest": "7.2.2",
152
153
  "ts-node": "10.9.2",
153
154
  "tsconfig-paths": "4.2.0",
154
155
  "tus-js-client": "4.3.1",
155
156
  "typescript": "5.9.3",
156
157
  "unplugin-swc": "1.5.9",
157
- "vite": "7.3.1",
158
+ "vite": "7.3.2",
158
159
  "vite-plugin-node": "7.0.0",
159
160
  "vite-tsconfig-paths": "6.1.1",
160
- "vitest": "4.1.2"
161
+ "vitest": "4.1.3"
161
162
  },
162
163
  "main": "dist/index.js",
163
164
  "types": "dist/index.d.ts",
@@ -170,6 +171,7 @@
170
171
  "src/**/*",
171
172
  "bin/**/*",
172
173
  "CLAUDE.md",
174
+ "FRAMEWORK-API.md",
173
175
  ".claude/rules/**/*",
174
176
  "docs/**/*",
175
177
  "migration-guides/**/*"
@@ -177,26 +179,26 @@
177
179
  "watch": {
178
180
  "build:dev": "src"
179
181
  },
180
- "packageManager": "pnpm@10.29.2",
182
+ "packageManager": "pnpm@10.33.0",
181
183
  "pnpm": {
182
184
  "overrides": {
183
- "minimatch@<3.1.4": "3.1.4",
184
- "minimatch@>=9.0.0 <9.0.7": "9.0.7",
185
- "minimatch@>=10.0.0 <10.2.3": "10.2.4",
186
- "rollup@>=4.0.0 <4.60.1": "4.60.1",
185
+ "minimatch@<3.1.5": "3.1.5",
186
+ "minimatch@>=9.0.0 <9.0.9": "9.0.9",
187
+ "minimatch@>=10.0.0 <10.2.5": "10.2.5",
187
188
  "ajv@<6.14.0": "6.14.0",
188
189
  "ajv@>=7.0.0-alpha.0 <8.18.0": "8.18.0",
189
- "undici@>=7.0.0 <7.24.0": "7.24.3",
190
- "srvx@<0.11.13": "0.11.13",
190
+ "undici@>=7.0.0 <7.24.7": "7.24.7",
191
+ "srvx@<0.11.15": "0.11.15",
191
192
  "handlebars@>=4.0.0 <4.7.9": "4.7.9",
192
193
  "brace-expansion@<1.1.13": "1.1.13",
193
194
  "brace-expansion@>=4.0.0 <5.0.5": "5.0.5",
194
195
  "picomatch@<2.3.2": "2.3.2",
195
196
  "picomatch@>=4.0.0 <4.0.4": "4.0.4",
196
- "path-to-regexp@>=8.0.0 <8.4.0": "8.4.1",
197
+ "path-to-regexp@>=8.0.0 <8.4.2": "8.4.2",
197
198
  "kysely@>=0.26.0 <0.28.15": "0.28.15",
198
199
  "lodash@>=4.0.0 <4.18.0": "4.18.1",
199
- "defu@<=6.1.4": "6.1.6"
200
+ "defu@<=6.1.6": "6.1.7",
201
+ "vite@>=7.0.0 <7.3.2": "7.3.2"
200
202
  },
201
203
  "onlyBuiltDependencies": [
202
204
  "bcrypt",
@@ -44,6 +44,19 @@ export const Restricted = (...rolesOrMember: RestrictedType): ClassDecorator & P
44
44
  return Reflect.metadata(restrictedMetaKey, rolesOrMember);
45
45
  };
46
46
 
47
+ /**
48
+ * Cache for Restricted metadata — decorators are static, metadata never changes at runtime.
49
+ * WeakMap<CacheTarget, Map<propertyKey | '__class__', RestrictedType>>
50
+ *
51
+ * Uses WeakMap so that dynamically-generated or hot-reloaded class constructors can be GC'd
52
+ * when no longer reachable (prevents unbounded growth in test suites and dev watch mode).
53
+ *
54
+ * CacheTarget is the class constructor (for instances) or the class itself (when object IS a constructor).
55
+ * This distinction is critical: getRestricted(data.constructor) passes a class as `object`,
56
+ * and (classFunction).constructor === Function for ALL classes — so we must use the class itself.
57
+ */
58
+ const restrictedMetadataCache = new WeakMap<object, Map<string, RestrictedType>>();
59
+
47
60
  /**
48
61
  * Get restricted data for (property of) object
49
62
  */
@@ -51,10 +64,36 @@ export const getRestricted = (object: unknown, propertyKey?: string): Restricted
51
64
  if (!object) {
52
65
  return null;
53
66
  }
54
- if (!propertyKey) {
55
- return Reflect.getMetadata(restrictedMetaKey, object);
67
+
68
+ // Determine cache target: use the class constructor for instances, the object itself for classes.
69
+ // When object IS a constructor (typeof === 'function'), using object.constructor would give Function
70
+ // for ALL classes, causing cache collisions.
71
+ const cacheTarget: object | undefined =
72
+ typeof object === 'function' ? (object as object) : (object as any).constructor;
73
+ if (!cacheTarget) {
74
+ return propertyKey
75
+ ? Reflect.getMetadata(restrictedMetaKey, object, propertyKey)
76
+ : Reflect.getMetadata(restrictedMetaKey, object);
77
+ }
78
+
79
+ let classCache = restrictedMetadataCache.get(cacheTarget);
80
+ if (!classCache) {
81
+ classCache = new Map();
82
+ restrictedMetadataCache.set(cacheTarget, classCache);
83
+ }
84
+
85
+ const cacheKey = propertyKey || '__class__';
86
+ if (classCache.has(cacheKey)) {
87
+ return classCache.get(cacheKey);
56
88
  }
57
- return Reflect.getMetadata(restrictedMetaKey, object, propertyKey);
89
+
90
+ // Cache miss: perform Reflect lookup and cache the result
91
+ const metadata = propertyKey
92
+ ? Reflect.getMetadata(restrictedMetaKey, object, propertyKey)
93
+ : Reflect.getMetadata(restrictedMetaKey, object);
94
+
95
+ classCache.set(cacheKey, metadata);
96
+ return metadata;
58
97
  };
59
98
 
60
99
  /**
@@ -257,7 +296,8 @@ export const checkRestricted = (
257
296
 
258
297
  // Check restricted
259
298
  const restricted = getRestricted(data, propertyKey) || [];
260
- const concatenatedRestrictions = config.mergeRoles ? _.uniq(objectRestrictions.concat(restricted)) : restricted;
299
+ const concatenatedRestrictions =
300
+ config.mergeRoles && objectRestrictions.length ? _.uniq(objectRestrictions.concat(restricted)) : restricted;
261
301
  const valid = validateRestricted(concatenatedRestrictions);
262
302
 
263
303
  // Check rights
@@ -1109,6 +1109,14 @@ export interface IServerOptions {
1109
1109
  CronExpression | CronJobConfigWithTimeZone | CronJobConfigWithUtcOffset | Date | Falsy | string
1110
1110
  >;
1111
1111
 
1112
+ /**
1113
+ * When true, logs a debug message when prepareInput() changes the input type during process().
1114
+ * Enable only for debugging — has performance cost due to JSON.stringify on every process() call.
1115
+ *
1116
+ * @default false
1117
+ */
1118
+ debugProcessInput?: boolean;
1119
+
1112
1120
  /**
1113
1121
  * SMTP and template configuration for sending emails
1114
1122
  */