@lenne.tech/nest-server 11.21.2 → 11.22.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.
- 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/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 +172 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +25 -25
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.js +8 -4
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/error-code/error-code.module.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.d.ts +1 -0
- package/dist/core/modules/tenant/core-tenant.guard.js +59 -4
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.helpers.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/server.module.js +6 -6
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.d.ts +6 -2
- package/dist/test/test.helper.js +28 -6
- package/dist/test/test.helper.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.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 +8 -3
- 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/better-auth/core-better-auth-user.mapper.ts +29 -25
- package/src/core/modules/better-auth/core-better-auth.service.ts +13 -9
- 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/modules/tenant/INTEGRATION-CHECKLIST.md +13 -2
- package/src/core/modules/tenant/README.md +26 -1
- package/src/core/modules/tenant/core-tenant.guard.ts +142 -11
- package/src/core/modules/tenant/core-tenant.helpers.ts +6 -2
- package/src/core.module.ts +52 -10
- package/src/server/server.module.ts +7 -9
- package/src/test/README.md +47 -0
- package/src/test/test.helper.ts +55 -6
|
@@ -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
|
+
```
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
- [Commands](#common-development-commands) | [Architecture](#code-architecture) | [Guidelines](#development-guidelines) | [Troubleshooting](#debugging--troubleshooting)
|
|
8
|
+
- **Detailed Rules**: See `.claude/rules/` for in-depth documentation
|
|
9
|
+
|
|
10
|
+
## Self-Improvement Instructions
|
|
11
|
+
|
|
12
|
+
**Claude Code must actively maintain and improve this file and `.claude/rules/`.**
|
|
13
|
+
|
|
14
|
+
After significant development sessions, update with new learnings (patterns, pitfalls, best practices). Keep this file concise (~150 lines) and move detailed topics to `.claude/rules/`.
|
|
15
|
+
|
|
16
|
+
## Living Documentation
|
|
17
|
+
|
|
18
|
+
The following documents must be kept up to date when making changes that affect them:
|
|
19
|
+
|
|
20
|
+
| Document | Update when... |
|
|
21
|
+
|----------|---------------|
|
|
22
|
+
| `docs/REQUEST-LIFECYCLE.md` | Adding/changing decorators, interceptors, guards, pipes, middleware, plugins, services, modules, or the request/response flow |
|
|
23
|
+
| `migration-guides/` | Releasing MINOR or MAJOR versions (see `.claude/rules/migration-guides.md`) |
|
|
24
|
+
| `.claude/rules/configurable-features.md` | Adding new configurable features |
|
|
25
|
+
|
|
26
|
+
**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.
|
|
27
|
+
|
|
28
|
+
## Project Overview
|
|
29
|
+
|
|
30
|
+
**@lenne.tech/nest-server** - An extension layer on top of NestJS for building server applications with GraphQL and MongoDB.
|
|
31
|
+
|
|
32
|
+
- **NPM**: https://www.npmjs.com/package/@lenne.tech/nest-server
|
|
33
|
+
- **GitHub**: https://github.com/lenneTech/nest-server
|
|
34
|
+
- **Starter Project**: https://github.com/lenneTech/nest-server-starter (reference for migrations)
|
|
35
|
+
- **CLI**: https://github.com/lenneTech/cli (`lt server module <Name>`)
|
|
36
|
+
- **Package Manager**: pnpm (development only, published to npm registry)
|
|
37
|
+
|
|
38
|
+
## Common Development Commands
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Building & Running
|
|
42
|
+
pnpm run build # Build (outputs to dist/)
|
|
43
|
+
pnpm start # Start in local mode
|
|
44
|
+
pnpm run start:dev # Development mode with watch
|
|
45
|
+
|
|
46
|
+
# Testing (ALWAYS run before completing changes)
|
|
47
|
+
pnpm test # Run E2E tests (Vitest)
|
|
48
|
+
pnpm run test:cov # With coverage
|
|
49
|
+
pnpm run test:e2e-doh # Debug open handles
|
|
50
|
+
pnpm run test:cleanup # Remove leftover test artifacts (.txt, .bin)
|
|
51
|
+
|
|
52
|
+
# Linting & Formatting
|
|
53
|
+
pnpm run lint # ESLint check
|
|
54
|
+
pnpm run lint:fix # Auto-fix
|
|
55
|
+
pnpm run format # Prettier format
|
|
56
|
+
|
|
57
|
+
# Package Development
|
|
58
|
+
pnpm run build:dev # Build for local development (use with pnpm link)
|
|
59
|
+
pnpm run reinit # Clean reinstall + tests + build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Code Architecture
|
|
63
|
+
|
|
64
|
+
**Two-Layer Structure:**
|
|
65
|
+
- `src/core/` - Reusable framework components (exported)
|
|
66
|
+
- `src/server/` - Internal test implementation (not exported)
|
|
67
|
+
- `src/index.ts` - Public API exports
|
|
68
|
+
|
|
69
|
+
**Key Components:**
|
|
70
|
+
- `CoreModule` - Dynamic module with GraphQL (optional, disable via `graphQl: false`), MongoDB, security
|
|
71
|
+
- `src/core/common/` - Decorators, helpers, interceptors, services
|
|
72
|
+
- `src/core/modules/` - Auth, BetterAuth, ErrorCode, File, HealthCheck, Migrate, SystemSetup, Tus, User
|
|
73
|
+
|
|
74
|
+
See `.claude/rules/architecture.md` for detailed documentation.
|
|
75
|
+
See [`docs/REQUEST-LIFECYCLE.md`](docs/REQUEST-LIFECYCLE.md) for the complete request lifecycle, security architecture, and interceptor/decorator reference.
|
|
76
|
+
|
|
77
|
+
## Development Guidelines
|
|
78
|
+
|
|
79
|
+
### Core Principles
|
|
80
|
+
|
|
81
|
+
1. **Module Inheritance Pattern** - Extend through inheritance, not hooks/events
|
|
82
|
+
- See `.claude/rules/module-inheritance.md`
|
|
83
|
+
|
|
84
|
+
2. **Dynamic Integration** - Components opt-in, configurable, no package modification required
|
|
85
|
+
|
|
86
|
+
3. **Backward Compatibility** - Changes affect all consuming projects
|
|
87
|
+
|
|
88
|
+
4. **Test Coverage** - All changes must pass `pnpm test`
|
|
89
|
+
|
|
90
|
+
5. **Export Management** - New public components → `src/index.ts`
|
|
91
|
+
|
|
92
|
+
### Role System (Critical)
|
|
93
|
+
|
|
94
|
+
System roles (`S_` prefix) are runtime checks only - **NEVER store in user.roles**:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
@Roles(RoleEnum.S_USER) // Correct: runtime check
|
|
98
|
+
roles: [RoleEnum.S_USER] // WRONG: never store S_ roles!
|
|
99
|
+
roles: [RoleEnum.ADMIN] // Correct: real role
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See `.claude/rules/role-system.md` for complete documentation.
|
|
103
|
+
|
|
104
|
+
### Versioning
|
|
105
|
+
|
|
106
|
+
`MAJOR.MINOR.PATCH` where MAJOR = NestJS version, MINOR = breaking changes, PATCH = non-breaking.
|
|
107
|
+
|
|
108
|
+
See `.claude/rules/versioning.md` for release process.
|
|
109
|
+
|
|
110
|
+
## Environment Configuration
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// config.env.ts supports:
|
|
114
|
+
- Direct environment variables
|
|
115
|
+
- NEST_SERVER_CONFIG JSON variable
|
|
116
|
+
- NSC__* prefixed variables
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Key areas:** JWT, MongoDB, GraphQL, email, security, betterAuth
|
|
120
|
+
|
|
121
|
+
## Debugging & Troubleshooting
|
|
122
|
+
|
|
123
|
+
| Issue | Solution |
|
|
124
|
+
|-------|----------|
|
|
125
|
+
| Tests timeout | Ensure MongoDB running on localhost:27017 |
|
|
126
|
+
| GraphQL introspection fails | Check `config.env.ts` introspection setting |
|
|
127
|
+
| Module not found after adding | Verify export in `src/index.ts`, run `pnpm run build` |
|
|
128
|
+
| Open handles in tests | Run `pnpm run test:e2e-doh` |
|
|
129
|
+
|
|
130
|
+
## Best Practices
|
|
131
|
+
|
|
132
|
+
1. **All code, comments, documentation in English**
|
|
133
|
+
2. **Run tests before completing changes** - `pnpm test`
|
|
134
|
+
3. **Follow existing patterns** for consistency
|
|
135
|
+
4. **Never store S_ roles** in user.roles array
|
|
136
|
+
5. **Use Module Inheritance Pattern** for core modules
|
|
137
|
+
6. **Document breaking changes** in commits
|
|
138
|
+
7. **Integration Checklists for Core Modules** - Every core module requiring project integration needs `INTEGRATION-CHECKLIST.md` (see `.claude/rules/core-modules.md`)
|
|
139
|
+
8. **Don't add redundant @UseGuards** - `@Roles()` already handles JWT auth (see `.claude/rules/role-system.md`)
|
|
140
|
+
9. **Fixed package versions only** - Never use `^`, `~`, or ranges in package.json (see `.claude/rules/package-management.md`)
|
|
141
|
+
|
|
142
|
+
## Migration Guides
|
|
143
|
+
|
|
144
|
+
When releasing MINOR or MAJOR versions, create migration guides in `migration-guides/`:
|
|
145
|
+
- Use `migration-guides/TEMPLATE.md` as starting point
|
|
146
|
+
- Always analyze `src/server/` and [nest-server-starter](https://github.com/lenneTech/nest-server-starter)
|
|
147
|
+
- Ask developer for additional projects to analyze
|
|
148
|
+
- See `.claude/rules/migration-guides.md` for complete process
|
|
149
|
+
|
|
150
|
+
## Modular Rules
|
|
151
|
+
|
|
152
|
+
Detailed documentation in `.claude/rules/`:
|
|
153
|
+
|
|
154
|
+
| File | Content |
|
|
155
|
+
|------|---------|
|
|
156
|
+
| `module-inheritance.md` | Core architectural pattern for extending modules |
|
|
157
|
+
| `role-system.md` | Role system, S_ prefix rules, @Roles vs @UseGuards |
|
|
158
|
+
| `architecture.md` | Detailed code architecture |
|
|
159
|
+
| `testing.md` | Test configuration and best practices |
|
|
160
|
+
| `versioning.md` | Version strategy and release process |
|
|
161
|
+
| `core-modules.md` | Path-scoped rules for `src/core/modules/` incl. Integration Checklist requirements |
|
|
162
|
+
| `better-auth.md` | **Security-critical**: Better-Auth module rules (standard compliance, security, testing) |
|
|
163
|
+
| `module-deprecation.md` | Legacy Auth → BetterAuth migration roadmap |
|
|
164
|
+
| `migration-guides.md` | Process for creating version migration guides |
|
|
165
|
+
| `configurable-features.md` | Configuration patterns: "Presence implies enabled" and "Boolean shorthand" (`true` / `{}`) |
|
|
166
|
+
| `package-management.md` | Fixed package versions only - no `^`, `~`, or ranges |
|
|
167
|
+
|
|
168
|
+
## In-Depth Documentation
|
|
169
|
+
|
|
170
|
+
| File | Content |
|
|
171
|
+
|------|---------|
|
|
172
|
+
| [`docs/REQUEST-LIFECYCLE.md`](docs/REQUEST-LIFECYCLE.md) | Complete request lifecycle, security architecture, interceptor chain, decorator reference, CrudService pipeline, Safety Net, diagrams |
|
|
@@ -317,4 +317,14 @@ interface IBetterAuthWithPasskey extends IBetterAuthBase {
|
|
|
317
317
|
passkey: IBetterAuthPasskeyEnabled;
|
|
318
318
|
trustedOrigins: string[];
|
|
319
319
|
}
|
|
320
|
+
export interface ICoreModuleOverrides {
|
|
321
|
+
betterAuth?: {
|
|
322
|
+
controller?: Type<any>;
|
|
323
|
+
resolver?: Type<any>;
|
|
324
|
+
};
|
|
325
|
+
errorCode?: {
|
|
326
|
+
controller?: Type<any>;
|
|
327
|
+
service?: Type<any>;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
320
330
|
export {};
|
|
@@ -533,32 +533,32 @@ let CoreBetterAuthUserMapper = class CoreBetterAuthUserMapper {
|
|
|
533
533
|
const fullyMigratedUsers = usersWithBoth[0]?.count || 0;
|
|
534
534
|
const pendingMigrationUsers = totalUsers - fullyMigratedUsers;
|
|
535
535
|
const migrationPercentage = totalUsers > 0 ? Math.round((fullyMigratedUsers / totalUsers) * 100 * 100) / 100 : 0;
|
|
536
|
-
const
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
as: 'accounts',
|
|
541
|
-
foreignField: 'userId',
|
|
542
|
-
from: 'account',
|
|
543
|
-
localField: '_id',
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
$match: {
|
|
548
|
-
$or: [
|
|
549
|
-
{ iamId: { $exists: false } },
|
|
550
|
-
{ iamId: null },
|
|
551
|
-
{
|
|
552
|
-
$and: [{ iamId: { $exists: true, $ne: null } }, { 'accounts.providerId': { $ne: 'credential' } }],
|
|
553
|
-
},
|
|
554
|
-
],
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
{ $limit: 100 },
|
|
558
|
-
{ $project: { email: 1 } },
|
|
559
|
-
])
|
|
536
|
+
const usersWithoutIamId = await usersCollection
|
|
537
|
+
.find({ $or: [{ iamId: { $exists: false } }, { iamId: null }] })
|
|
538
|
+
.limit(100)
|
|
539
|
+
.project({ email: 1 })
|
|
560
540
|
.toArray();
|
|
561
|
-
const
|
|
541
|
+
const remaining = 100 - usersWithoutIamId.length;
|
|
542
|
+
let usersWithIamButNoAccount = [];
|
|
543
|
+
if (remaining > 0) {
|
|
544
|
+
usersWithIamButNoAccount = await usersCollection
|
|
545
|
+
.aggregate([
|
|
546
|
+
{ $match: { iamId: { $exists: true, $ne: null } } },
|
|
547
|
+
{
|
|
548
|
+
$lookup: {
|
|
549
|
+
as: 'accounts',
|
|
550
|
+
foreignField: 'userId',
|
|
551
|
+
from: 'account',
|
|
552
|
+
localField: '_id',
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
{ $match: { 'accounts.providerId': { $ne: 'credential' } } },
|
|
556
|
+
{ $limit: remaining },
|
|
557
|
+
{ $project: { email: 1 } },
|
|
558
|
+
])
|
|
559
|
+
.toArray();
|
|
560
|
+
}
|
|
561
|
+
const pendingUserEmails = [...usersWithoutIamId, ...usersWithIamButNoAccount].map((u) => u.email).filter(Boolean);
|
|
562
562
|
const canDisableLegacyAuth = totalUsers > 0 && fullyMigratedUsers === totalUsers;
|
|
563
563
|
return {
|
|
564
564
|
canDisableLegacyAuth,
|