@lenne.tech/nest-server 11.21.3 → 11.22.1
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/architecture.md +79 -0
- package/.claude/rules/better-auth.md +262 -0
- package/.claude/rules/configurable-features.md +308 -0
- package/.claude/rules/core-modules.md +205 -0
- package/.claude/rules/framework-compatibility.md +79 -0
- package/.claude/rules/migration-guides.md +149 -0
- package/.claude/rules/module-deprecation.md +214 -0
- package/.claude/rules/module-inheritance.md +97 -0
- package/.claude/rules/package-management.md +112 -0
- package/.claude/rules/role-system.md +146 -0
- package/.claude/rules/testing.md +120 -0
- package/.claude/rules/versioning.md +53 -0
- package/CLAUDE.md +174 -0
- package/FRAMEWORK-API.md +231 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
- package/dist/core/modules/error-code/error-code.module.js.map +1 -1
- package/dist/core.module.d.ts +3 -3
- package/dist/core.module.js +17 -4
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/file/file-info.model.d.ts +1 -5
- package/dist/server/modules/user/user.model.d.ts +1 -5
- package/dist/server/server.module.js +6 -6
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1256 -0
- package/docs/error-codes.md +446 -0
- package/migration-guides/11.10.x-to-11.11.x.md +266 -0
- package/migration-guides/11.11.x-to-11.12.x.md +323 -0
- package/migration-guides/11.12.x-to-11.13.0.md +612 -0
- package/migration-guides/11.13.x-to-11.14.0.md +348 -0
- package/migration-guides/11.14.x-to-11.15.0.md +262 -0
- package/migration-guides/11.15.0-to-11.15.3.md +118 -0
- package/migration-guides/11.15.x-to-11.16.0.md +497 -0
- package/migration-guides/11.16.x-to-11.17.0.md +130 -0
- package/migration-guides/11.17.x-to-11.18.0.md +393 -0
- package/migration-guides/11.18.x-to-11.19.0.md +151 -0
- package/migration-guides/11.19.x-to-11.20.0.md +170 -0
- package/migration-guides/11.20.x-to-11.21.0.md +216 -0
- package/migration-guides/11.21.0-to-11.21.1.md +194 -0
- package/migration-guides/11.21.1-to-11.21.2.md +114 -0
- package/migration-guides/11.21.2-to-11.21.3.md +175 -0
- package/migration-guides/11.21.x-to-11.22.0.md +224 -0
- package/migration-guides/11.22.0-to-11.22.1.md +105 -0
- package/migration-guides/11.3.x-to-11.4.x.md +233 -0
- package/migration-guides/11.6.x-to-11.7.x.md +394 -0
- package/migration-guides/11.7.x-to-11.8.x.md +318 -0
- package/migration-guides/11.8.x-to-11.9.x.md +322 -0
- package/migration-guides/11.9.x-to-11.10.x.md +571 -0
- package/migration-guides/TEMPLATE.md +113 -0
- package/package.json +25 -18
- package/src/core/common/interfaces/server-options.interface.ts +83 -16
- package/src/core/modules/better-auth/CUSTOMIZATION.md +24 -17
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +5 -5
- package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +42 -12
- package/src/core/modules/error-code/error-code.module.ts +4 -9
- package/src/core.module.ts +52 -10
- package/src/server/server.module.ts +7 -9
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Module Inheritance Pattern
|
|
2
|
+
|
|
3
|
+
**This is a fundamental architectural pattern that MUST be followed for all new modules in `src/core/modules/`.**
|
|
4
|
+
|
|
5
|
+
## Why Inheritance Over Hooks/Events
|
|
6
|
+
|
|
7
|
+
Core modules (like `AuthModule`, `BetterAuthModule`, `FileModule`) are designed to be **extended through inheritance** in consuming projects, not manipulated through hooks, events, or other workarounds. This provides:
|
|
8
|
+
|
|
9
|
+
1. **Full Control**: Projects can override methods and precisely define what happens before/after `super()` calls
|
|
10
|
+
2. **Flexibility**: Parts of the parent implementation can be skipped entirely for custom logic
|
|
11
|
+
3. **Type Safety**: TypeScript inheritance ensures proper typing and IDE support
|
|
12
|
+
4. **Robustness**: No runtime event systems or hooks that can fail silently
|
|
13
|
+
|
|
14
|
+
## Pattern Structure
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// src/core/modules/example/core-example.resolver.ts (in nest-server)
|
|
18
|
+
@Resolver()
|
|
19
|
+
export class CoreExampleResolver {
|
|
20
|
+
async someMethod() { /* base implementation */ }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/server/modules/example/example.resolver.ts (in consuming project)
|
|
24
|
+
@Resolver()
|
|
25
|
+
export class ExampleResolver extends CoreExampleResolver {
|
|
26
|
+
override async someMethod() {
|
|
27
|
+
// Custom pre-processing
|
|
28
|
+
const result = await super.someMethod(); // Or skip for full override
|
|
29
|
+
// Custom post-processing
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Module Registration Options
|
|
36
|
+
|
|
37
|
+
Core modules should support both:
|
|
38
|
+
|
|
39
|
+
1. **Auto-registration** (`autoRegister: true`) - For simple projects without customization
|
|
40
|
+
2. **Manual registration** (`autoRegister: false`, default) - Projects integrate via extended module
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// config.env.ts - auto-registration for simple projects
|
|
44
|
+
betterAuth: { autoRegister: true }
|
|
45
|
+
|
|
46
|
+
// server.module.ts - manual registration with custom resolver (recommended)
|
|
47
|
+
BetterAuthModule.forRoot({ config, resolver: CustomResolver })
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Examples in Codebase
|
|
51
|
+
|
|
52
|
+
- `CoreAuthService` → extended by project's `AuthService`
|
|
53
|
+
- `CoreBetterAuthResolver` → extended by project's `BetterAuthResolver`
|
|
54
|
+
- `CoreUserService` → extended by project's `UserService`
|
|
55
|
+
|
|
56
|
+
## Critical: Call Protected Helper Methods When Overriding
|
|
57
|
+
|
|
58
|
+
When overriding methods that have **protected helper methods** in the parent class, you MUST call these helpers in your override. Otherwise, important functionality (guards, checks, validation) will be bypassed.
|
|
59
|
+
|
|
60
|
+
### Example: AuthResolver Override Pattern
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// WRONG: Missing checkLegacyGraphQLEnabled call
|
|
64
|
+
@Mutation(() => Auth)
|
|
65
|
+
override async signIn(...): Promise<Auth> {
|
|
66
|
+
// Bug: Legacy endpoint check is bypassed!
|
|
67
|
+
const result = await this.authService.signIn(input, serviceOptions);
|
|
68
|
+
return this.processCookies(ctx, result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// CORRECT: Call the protected check method
|
|
72
|
+
@Mutation(() => Auth)
|
|
73
|
+
override async signIn(...): Promise<Auth> {
|
|
74
|
+
this.checkLegacyGraphQLEnabled('signIn'); // Required!
|
|
75
|
+
const result = await this.authService.signIn(input, serviceOptions);
|
|
76
|
+
return this.processCookies(ctx, result);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Protected Helper Methods to Call
|
|
81
|
+
|
|
82
|
+
| Core Class | Method | Purpose |
|
|
83
|
+
|------------|--------|---------|
|
|
84
|
+
| `CoreAuthResolver` | `checkLegacyGraphQLEnabled(name)` | Throws HTTP 410 if legacy endpoints disabled |
|
|
85
|
+
| `CoreAuthController` | `checkLegacyRESTEnabled(name)` | Throws HTTP 410 if legacy endpoints disabled |
|
|
86
|
+
|
|
87
|
+
**Rule**: When you override a method, check if the parent calls any `protected` helper methods. If so, call them in your override too.
|
|
88
|
+
|
|
89
|
+
## Checklist for New Core Modules
|
|
90
|
+
|
|
91
|
+
When adding new core modules, ensure:
|
|
92
|
+
|
|
93
|
+
- [ ] Base classes in `src/core/modules/` with `Core` prefix
|
|
94
|
+
- [ ] Methods are `protected` or `public` (not `private`) to allow override
|
|
95
|
+
- [ ] `autoRegister` option defaults to `false`
|
|
96
|
+
- [ ] Clear documentation for extension points
|
|
97
|
+
- [ ] Export base classes via `src/index.ts`
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Package Management Rules
|
|
2
|
+
|
|
3
|
+
This document defines the rules for managing dependencies in package.json.
|
|
4
|
+
|
|
5
|
+
## Package Manager: pnpm
|
|
6
|
+
|
|
7
|
+
This project uses **pnpm** for development. The `packageManager` field in `package.json` ensures the correct version is used (via Corepack).
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install dependencies
|
|
11
|
+
pnpm install
|
|
12
|
+
|
|
13
|
+
# Add a new package (fixed version)
|
|
14
|
+
pnpm add express@4.18.2
|
|
15
|
+
|
|
16
|
+
# Add a dev dependency
|
|
17
|
+
pnpm add -D typescript@5.3.3
|
|
18
|
+
|
|
19
|
+
# Remove a package
|
|
20
|
+
pnpm remove package-name
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Fixed Versions Only
|
|
24
|
+
|
|
25
|
+
**All package versions in package.json MUST be exact (fixed) versions.**
|
|
26
|
+
|
|
27
|
+
### Rules
|
|
28
|
+
|
|
29
|
+
| Allowed | Not Allowed |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| `"express": "4.18.2"` | `"express": "^4.18.2"` |
|
|
32
|
+
| `"typescript": "5.3.3"` | `"typescript": "~5.3.3"` |
|
|
33
|
+
| `"lodash": "4.17.21"` | `"lodash": ">=4.0.0"` |
|
|
34
|
+
| | `"lodash": "4.x"` |
|
|
35
|
+
| | `"lodash": "*"` |
|
|
36
|
+
|
|
37
|
+
### Why Fixed Versions?
|
|
38
|
+
|
|
39
|
+
1. **Reproducibility**: Every installation produces identical `node_modules`
|
|
40
|
+
2. **Stability**: No surprise breaking changes from automatic minor/patch updates
|
|
41
|
+
3. **Security Control**: Updates are intentional and reviewed, not automatic
|
|
42
|
+
4. **Debugging**: Easier to identify which version introduced issues
|
|
43
|
+
5. **Framework Responsibility**: As a framework, we must ensure consuming projects get predictable behavior
|
|
44
|
+
|
|
45
|
+
### When Adding New Packages
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# CORRECT: Install with exact version
|
|
49
|
+
pnpm add express@4.18.2
|
|
50
|
+
|
|
51
|
+
# Then verify package.json has exact version (no ^ or ~)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If pnpm adds `^` automatically, manually remove it from package.json.
|
|
55
|
+
|
|
56
|
+
### When Updating Packages
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Use pnpm update or manually update
|
|
60
|
+
pnpm update package-name
|
|
61
|
+
|
|
62
|
+
# Or manually edit package.json with exact version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Always:
|
|
66
|
+
1. Update to exact version
|
|
67
|
+
2. Run `pnpm install`
|
|
68
|
+
3. Run `pnpm test`
|
|
69
|
+
4. Verify no regressions
|
|
70
|
+
|
|
71
|
+
### Checking for Version Ranges
|
|
72
|
+
|
|
73
|
+
To find packages with version ranges:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Find all dependencies with ^ or ~
|
|
77
|
+
grep -E '"\^|"~' package.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Common Mistakes
|
|
81
|
+
|
|
82
|
+
| Mistake | Fix |
|
|
83
|
+
|---------|-----|
|
|
84
|
+
| pnpm adds `^` by default | Remove `^` after install |
|
|
85
|
+
| Copying from other projects | Verify versions are fixed |
|
|
86
|
+
| Using `pnpm add package` without version | Specify exact version: `pnpm add package@1.2.3` |
|
|
87
|
+
|
|
88
|
+
## devDependencies
|
|
89
|
+
|
|
90
|
+
The same rules apply to devDependencies. All versions must be fixed.
|
|
91
|
+
|
|
92
|
+
## peerDependencies
|
|
93
|
+
|
|
94
|
+
peerDependencies may use ranges when necessary for compatibility with consuming projects, but this should be minimized.
|
|
95
|
+
|
|
96
|
+
## Lock File
|
|
97
|
+
|
|
98
|
+
The `pnpm-lock.yaml` file must always be committed. It provides additional reproducibility even if someone accidentally introduces a version range.
|
|
99
|
+
|
|
100
|
+
## Overrides
|
|
101
|
+
|
|
102
|
+
Package overrides are configured in the `pnpm.overrides` section of `package.json`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"pnpm": {
|
|
107
|
+
"overrides": {
|
|
108
|
+
"lodash": "4.17.23"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Role System (RoleEnum)
|
|
2
|
+
|
|
3
|
+
The role system distinguishes between **real roles** and **system roles**.
|
|
4
|
+
|
|
5
|
+
## Real Roles
|
|
6
|
+
|
|
7
|
+
Actual roles stored in `user.roles` array in the database:
|
|
8
|
+
|
|
9
|
+
- `RoleEnum.ADMIN` - Administrator role
|
|
10
|
+
|
|
11
|
+
## System Roles (S_ Prefix)
|
|
12
|
+
|
|
13
|
+
System roles are used for **runtime checks only** and must **NEVER** be stored in `user.roles`:
|
|
14
|
+
|
|
15
|
+
| Role | Purpose | Check Logic |
|
|
16
|
+
|------|---------|-------------|
|
|
17
|
+
| `S_USER` | User is logged in | `currentUser` exists |
|
|
18
|
+
| `S_VERIFIED` | User is verified | `user.verified \|\| user.verifiedAt \|\| user.emailVerified` |
|
|
19
|
+
| `S_CREATOR` | User created the object | `object.createdBy === user.id` |
|
|
20
|
+
| `S_SELF` | User is accessing own data | `object.id === user.id` |
|
|
21
|
+
| `S_EVERYONE` | Public access | Always true |
|
|
22
|
+
| `S_NO_ONE` | Locked access | Always false |
|
|
23
|
+
|
|
24
|
+
## Critical Rule
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// CORRECT: Using S_ roles in decorators (runtime checks)
|
|
28
|
+
@Roles(RoleEnum.S_USER)
|
|
29
|
+
@Restricted(RoleEnum.S_VERIFIED)
|
|
30
|
+
|
|
31
|
+
// WRONG: Storing S_ roles in user.roles array
|
|
32
|
+
roles: [RoleEnum.S_USER] // NEVER do this!
|
|
33
|
+
|
|
34
|
+
// CORRECT: Empty roles or real roles only
|
|
35
|
+
roles: [] // Empty array
|
|
36
|
+
roles: [RoleEnum.ADMIN] // Real role
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `S_` prefix indicates a **system check**, not an actual role. These are evaluated dynamically at runtime based on context (current user, request, object being accessed).
|
|
40
|
+
|
|
41
|
+
## Role Decorators
|
|
42
|
+
|
|
43
|
+
- `@Roles(RoleEnum.ADMIN)` - Method-level authorization
|
|
44
|
+
- `@Restricted(RoleEnum.S_VERIFIED)` - Field-level access control
|
|
45
|
+
|
|
46
|
+
## Hierarchy Roles (Multi-Tenancy)
|
|
47
|
+
|
|
48
|
+
When `multiTenancy` is configured, hierarchy roles replace the old `S_TENANT_*` system roles:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { DefaultHR, createHierarchyRoles } from '@lenne.tech/nest-server';
|
|
52
|
+
|
|
53
|
+
// Default hierarchy
|
|
54
|
+
@Roles(DefaultHR.MEMBER) // any active member (level >= 1)
|
|
55
|
+
@Roles(DefaultHR.MANAGER) // at least manager level (level >= 2)
|
|
56
|
+
@Roles(DefaultHR.OWNER) // highest level (level >= 3)
|
|
57
|
+
|
|
58
|
+
// Custom hierarchy
|
|
59
|
+
const HR = createHierarchyRoles({ viewer: 1, editor: 2, admin: 3, owner: 4 });
|
|
60
|
+
@Roles(HR.EDITOR) // requires level >= 2
|
|
61
|
+
|
|
62
|
+
// Normal (non-hierarchy) roles
|
|
63
|
+
@Roles('auditor') // exact match only, no level compensation
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**System role OR semantics in CoreTenantGuard:** System roles (`S_EVERYONE`, `S_USER`, `S_VERIFIED`) are checked as OR alternatives **before** real roles. Method-level system roles take precedence over class-level ones (e.g., class `S_EVERYONE` + method `S_USER` → `S_USER` applies). When a system role grants access and `X-Tenant-Id` header is present, membership is still validated to set tenant context — a non-member gets 403 with `S_USER`/`S_VERIFIED` (admin bypass applies).
|
|
67
|
+
|
|
68
|
+
**Tenant context rule for real roles:** When `X-Tenant-Id` header is present, only `membership.role` is checked. `user.roles` is ignored (except ADMIN bypass). Without header, `user.roles` is checked.
|
|
69
|
+
|
|
70
|
+
Hierarchy roles use **level comparison** (higher includes lower). Normal roles use **exact match**.
|
|
71
|
+
|
|
72
|
+
Both work in `@Roles()` (method-level) and `@Restricted()` (field-level) via unified `checkRoleAccess()`.
|
|
73
|
+
|
|
74
|
+
## Role Check Implementation
|
|
75
|
+
|
|
76
|
+
The role system is evaluated in:
|
|
77
|
+
- `RolesGuard` / `BetterAuthRolesGuard` - Checks method-level `@Roles()` decorators (passes through non-system roles to `CoreTenantGuard` when multiTenancy active)
|
|
78
|
+
- `CoreTenantGuard` - Validates tenant membership and checks hierarchy/normal roles
|
|
79
|
+
- `CheckResponseInterceptor` - Filters fields based on `@Restricted()` decorators
|
|
80
|
+
- `CheckSecurityInterceptor` - Processes `securityCheck()` methods
|
|
81
|
+
|
|
82
|
+
## @Roles vs @UseGuards
|
|
83
|
+
|
|
84
|
+
**IMPORTANT: `@Roles()` already handles JWT authentication internally.**
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// CORRECT: @Roles alone is sufficient for authentication + authorization
|
|
88
|
+
@Query(() => SomeModel)
|
|
89
|
+
@Roles(RoleEnum.ADMIN)
|
|
90
|
+
async someAdminQuery(): Promise<SomeModel> { }
|
|
91
|
+
|
|
92
|
+
// WRONG: Don't add @UseGuards when @Roles is present
|
|
93
|
+
@Query(() => SomeModel)
|
|
94
|
+
@Roles(RoleEnum.ADMIN)
|
|
95
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.JWT)) // REDUNDANT - Roles already handles this
|
|
96
|
+
async someAdminQuery(): Promise<SomeModel> { }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The `@Roles()` decorator combined with `RolesGuard` automatically:
|
|
100
|
+
1. Validates the JWT token
|
|
101
|
+
2. Extracts the user from the token
|
|
102
|
+
3. Checks if the user has the required role
|
|
103
|
+
|
|
104
|
+
### When @UseGuards IS Required
|
|
105
|
+
|
|
106
|
+
`@UseGuards(AuthGuard(...))` is only needed in these specific cases:
|
|
107
|
+
|
|
108
|
+
| Case | Example | Reason |
|
|
109
|
+
|------|---------|--------|
|
|
110
|
+
| **Refresh Token** | `@UseGuards(AuthGuard(AuthGuardStrategy.JWT_REFRESH))` | Different strategy than standard JWT |
|
|
111
|
+
| **Custom Strategies** | `@UseGuards(AuthGuard(AuthGuardStrategy.CUSTOM))` | Non-standard authentication flow |
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// CORRECT: refreshToken needs JWT_REFRESH strategy (not standard JWT)
|
|
115
|
+
@Mutation(() => CoreAuthModel)
|
|
116
|
+
@Roles(RoleEnum.S_EVERYONE)
|
|
117
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.JWT_REFRESH)) // Required - different strategy!
|
|
118
|
+
async refreshToken(...): Promise<CoreAuthModel> { }
|
|
119
|
+
|
|
120
|
+
// CORRECT: Standard endpoints - @Roles is sufficient
|
|
121
|
+
@Mutation(() => CoreAuthModel)
|
|
122
|
+
@Roles(RoleEnum.S_USER) // Handles JWT auth automatically
|
|
123
|
+
async logout(...): Promise<boolean> { }
|
|
124
|
+
|
|
125
|
+
@Query(() => User)
|
|
126
|
+
@Roles(RoleEnum.ADMIN) // Handles JWT auth automatically
|
|
127
|
+
async getUser(...): Promise<User> { }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Common Mistake: Redundant Guards
|
|
131
|
+
|
|
132
|
+
When reviewing code, watch for this anti-pattern:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// WRONG: Redundant - @Roles(S_USER) already validates JWT
|
|
136
|
+
@Roles(RoleEnum.S_USER)
|
|
137
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.JWT)) // DELETE THIS
|
|
138
|
+
async someMethod() { }
|
|
139
|
+
|
|
140
|
+
// WRONG: Redundant - @Roles(ADMIN) already validates JWT
|
|
141
|
+
@Roles(RoleEnum.ADMIN)
|
|
142
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.JWT)) // DELETE THIS
|
|
143
|
+
async adminMethod() { }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Rule of thumb:** If `@Roles()` uses any role OTHER than `S_EVERYONE`, you don't need `@UseGuards(AuthGuard(JWT))`.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Testing Configuration
|
|
2
|
+
|
|
3
|
+
## Test Framework
|
|
4
|
+
|
|
5
|
+
- **Vitest** - Primary test framework (migrated from Jest)
|
|
6
|
+
- Configuration: `vitest.config.ts`
|
|
7
|
+
- Test files: `tests/` directory with `.e2e-spec.ts` suffix
|
|
8
|
+
|
|
9
|
+
## Running Tests
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all E2E tests (default)
|
|
13
|
+
pnpm test
|
|
14
|
+
|
|
15
|
+
# Run with coverage
|
|
16
|
+
pnpm run test:cov
|
|
17
|
+
|
|
18
|
+
# Run in CI mode
|
|
19
|
+
pnpm run test:ci
|
|
20
|
+
|
|
21
|
+
# Debug open handles
|
|
22
|
+
pnpm run test:e2e-doh
|
|
23
|
+
|
|
24
|
+
# Clean up leftover test artifacts (.txt, .bin files from failed file upload tests)
|
|
25
|
+
pnpm run test:cleanup
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Test Environment
|
|
29
|
+
|
|
30
|
+
- Environment: `NODE_ENV=local`
|
|
31
|
+
- Database: Local MongoDB instance (`mongodb://127.0.0.1/nest-server-local`)
|
|
32
|
+
- Test helper: `src/test/test.helper.ts`
|
|
33
|
+
- Coverage: Collected from `src/**/*.{ts,js}`
|
|
34
|
+
|
|
35
|
+
## Test Best Practices
|
|
36
|
+
|
|
37
|
+
1. **Always run tests before completing changes**: `pnpm test`
|
|
38
|
+
2. **Production-ready = all tests pass** without errors
|
|
39
|
+
3. **Use TestHelper** for GraphQL and REST API testing
|
|
40
|
+
4. **Clean up test data** in `afterAll` hooks
|
|
41
|
+
5. **Unique test data** - Use timestamps/random strings to avoid conflicts
|
|
42
|
+
|
|
43
|
+
## Test File Structure
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
47
|
+
import { TestHelper } from '../../src';
|
|
48
|
+
|
|
49
|
+
describe('Feature Name', () => {
|
|
50
|
+
let testHelper: TestHelper;
|
|
51
|
+
|
|
52
|
+
beforeAll(async () => {
|
|
53
|
+
const moduleFixture = await Test.createTestingModule({
|
|
54
|
+
imports: [ServerModule],
|
|
55
|
+
}).compile();
|
|
56
|
+
|
|
57
|
+
const app = moduleFixture.createNestApplication();
|
|
58
|
+
await app.init();
|
|
59
|
+
testHelper = new TestHelper(app);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterAll(async () => {
|
|
63
|
+
// Cleanup test data
|
|
64
|
+
await app.close();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should do something', async () => {
|
|
68
|
+
const result = await testHelper.graphQl({
|
|
69
|
+
name: 'someQuery',
|
|
70
|
+
type: TestGraphQLType.QUERY,
|
|
71
|
+
fields: ['id', 'name'],
|
|
72
|
+
});
|
|
73
|
+
expect(result.data.someQuery).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## WebSocket/Subscription Tests
|
|
79
|
+
|
|
80
|
+
When testing GraphQL subscriptions, use `httpServer.listen(0)` instead of `app.listen()`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// CORRECT: No startup log, dynamic port
|
|
84
|
+
await app.init();
|
|
85
|
+
const httpServer = app.getHttpServer();
|
|
86
|
+
await new Promise<void>((resolve) => {
|
|
87
|
+
httpServer.listen(0, '127.0.0.1', () => resolve());
|
|
88
|
+
});
|
|
89
|
+
const port = httpServer.address().port;
|
|
90
|
+
testHelper = new TestHelper(app, `ws://127.0.0.1:${port}/graphql`);
|
|
91
|
+
|
|
92
|
+
// Cleanup in afterAll
|
|
93
|
+
if (httpServer) {
|
|
94
|
+
await new Promise<void>((resolve) => httpServer.close(() => resolve()));
|
|
95
|
+
}
|
|
96
|
+
await app.close();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// WRONG: Produces "Nest application successfully started" log
|
|
101
|
+
await app.init();
|
|
102
|
+
await app.listen(3030);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Why:**
|
|
106
|
+
- `app.listen()` triggers NestJS startup log -> noisy test output
|
|
107
|
+
- Dynamic port (`0`) avoids port conflicts between parallel tests
|
|
108
|
+
- Explicit `httpServer.close()` prevents open handle warnings
|
|
109
|
+
|
|
110
|
+
## TestHelper Reference
|
|
111
|
+
|
|
112
|
+
Full documentation for TestHelper (REST, GraphQL, Cookie support):
|
|
113
|
+
`src/test/README.md` (also available in `node_modules/@lenne.tech/nest-server/src/test/README.md`)
|
|
114
|
+
|
|
115
|
+
## Common Test Issues
|
|
116
|
+
|
|
117
|
+
- **Tests timeout**: Ensure MongoDB is running
|
|
118
|
+
- **Open handles**: Use `pnpm run test:e2e-doh` to debug
|
|
119
|
+
- **Data conflicts**: Use unique identifiers per test
|
|
120
|
+
- **"NestApplication successfully started" log**: Use `httpServer.listen()` instead of `app.listen()`
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Versioning Strategy
|
|
2
|
+
|
|
3
|
+
## Version Schema: `MAJOR.MINOR.PATCH`
|
|
4
|
+
|
|
5
|
+
| Part | Meaning |
|
|
6
|
+
|---------|--------------------------------------------------------------------|
|
|
7
|
+
| `MAJOR` | Mirrors the NestJS major version (e.g., `11.x.x` = NestJS 11) |
|
|
8
|
+
| `MINOR` | Breaking changes or significant restructuring |
|
|
9
|
+
| `PATCH` | Improvements (bugfixes) or additions (new features) - non-breaking |
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
- `11.0.0` -> Initial release for NestJS 11
|
|
14
|
+
- `11.1.0` -> Breaking change or major restructuring within NestJS 11
|
|
15
|
+
- `11.1.5` -> Bugfix or new feature (backward compatible)
|
|
16
|
+
|
|
17
|
+
## Important Rules
|
|
18
|
+
|
|
19
|
+
1. **Document breaking changes** clearly in commit messages when incrementing MINOR version
|
|
20
|
+
2. **Update nest-server-starter** with migration instructions for breaking changes
|
|
21
|
+
3. **Consider downstream impact** - this package is used by multiple projects
|
|
22
|
+
|
|
23
|
+
## Release Process
|
|
24
|
+
|
|
25
|
+
1. Make changes and ensure all tests pass (`pnpm test`)
|
|
26
|
+
2. Update version in `package.json`
|
|
27
|
+
3. Build the package (`pnpm run build`)
|
|
28
|
+
4. Publish to npm
|
|
29
|
+
5. Update and test in [nest-server-starter](https://github.com/lenneTech/nest-server-starter)
|
|
30
|
+
6. Commit changes to starter with migration notes
|
|
31
|
+
|
|
32
|
+
## Package Distribution
|
|
33
|
+
|
|
34
|
+
- **NPM Package**: `@lenne.tech/nest-server`
|
|
35
|
+
- **Main entry**: `dist/index.js`
|
|
36
|
+
- **Types**: `dist/index.d.ts`
|
|
37
|
+
- **Public APIs**: Exported from `src/index.ts`
|
|
38
|
+
|
|
39
|
+
## Package Development Commands
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Build for local development (use with pnpm link)
|
|
43
|
+
pnpm run build:dev
|
|
44
|
+
|
|
45
|
+
# Create tarball for integration testing
|
|
46
|
+
pnpm run build:pack
|
|
47
|
+
|
|
48
|
+
# Clean reinstall with tests and build
|
|
49
|
+
pnpm run reinit
|
|
50
|
+
|
|
51
|
+
# Watch for changes
|
|
52
|
+
pnpm run watch
|
|
53
|
+
```
|