mongodb-dynamic-api 4.13.0 → 4.14.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/README.md CHANGED
@@ -1,69 +1,39 @@
1
- <div style="text-align: center; width: 100%;">
2
- <div style="display: inline-block">
3
-
4
- [![NPM version](https://img.shields.io/npm/v/mongodb-dynamic-api.svg)](https://www.npmjs.com/package/mongodb-dynamic-api)
5
- ![NPM](https://img.shields.io/npm/l/mongodb-dynamic-api?registry_uri=https%3A%2F%2Fregistry.npmjs.com)
6
- ![npm](https://img.shields.io/npm/dw/mongodb-dynamic-api)
7
- </div>
8
- </div>
9
-
10
- <div style="text-align: center; width: 100%;">
11
- <div style="display: inline-block">
12
-
13
- ![GitHub branch checks state](https://img.shields.io/github/checks-status/MikeDev75015/mongodb-dynamic-api/main)
14
- [![CircleCI](https://circleci.com/gh/MikeDev75015/mongodb-dynamic-api.svg?style=shield)](https://app.circleci.com/pipelines/github/MikeDev75015/mongodb-dynamic-api)
15
- ![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/MikeDev75015_mongodb-dynamic-api?server=https%3A%2F%2Fsonarcloud.io)
16
- </div>
17
- </div>
18
-
19
- <div style="text-align: center; width: 100%;">
20
- <div style="display: inline-block">
21
-
22
- ![Sonar Tests](https://img.shields.io/sonar/tests/MikeDev75015_mongodb-dynamic-api?server=https%3A%2F%2Fsonarcloud.io)
23
- ![Sonar Coverage](https://img.shields.io/sonar/coverage/MikeDev75015_mongodb-dynamic-api?server=https%3A%2F%2Fsonarcloud.io)
24
- </div>
25
- </div>
26
-
27
- <div style="text-align: center; width: 100%;">
28
- <div style="display: inline-block">
29
-
30
- [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeDev75015_mongodb-dynamic-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=MikeDev75015_mongodb-dynamic-api)
31
- [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeDev75015_mongodb-dynamic-api&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=MikeDev75015_mongodb-dynamic-api)
32
- [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeDev75015_mongodb-dynamic-api&metric=security_rating)](https://sonarcloud.io/dashboard?id=MikeDev75015_mongodb-dynamic-api)
33
- [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=MikeDev75015_mongodb-dynamic-api&metric=ncloc)](https://sonarcloud.io/dashboard?id=MikeDev75015_mongodb-dynamic-api)
34
- [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=MikeDev75015_mongodb-dynamic-api&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=MikeDev75015_mongodb-dynamic-api)
35
- </div>
36
- </div>
37
-
38
- <div style="text-align: center; width: 100%;">
39
- <div style="display: inline-block">
40
-
41
- ![GitHub top language](https://img.shields.io/github/languages/top/MikeDev75015/mongodb-dynamic-api)
42
- ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MikeDev75015/mongodb-dynamic-api)
43
- ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/MikeDev75015/mongodb-dynamic-api)
44
- ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/MikeDev75015/mongodb-dynamic-api/main)
45
- </div>
46
- </div>
1
+ <p align="center">
2
+ <h1 align="center">mongodb-dynamic-api</h1>
3
+ <p align="center">
4
+ A production-ready <strong>NestJS 11</strong> module that instantly generates fully typed REST APIs + WebSockets<br/>
5
+ for any MongoDB collection — zero boilerplate, enterprise features included.
6
+ </p>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/mongodb-dynamic-api"><img src="https://img.shields.io/npm/v/mongodb-dynamic-api.svg" alt="NPM version"/></a>
11
+ <img src="https://img.shields.io/npm/l/mongodb-dynamic-api" alt="License"/>
12
+ <img src="https://img.shields.io/npm/dw/mongodb-dynamic-api" alt="Weekly downloads"/>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <img src="https://img.shields.io/github/checks-status/MikeDev75015/mongodb-dynamic-api/main" alt="CI"/>
17
+ <a href="https://app.circleci.com/pipelines/github/MikeDev75015/mongodb-dynamic-api"><img src="https://circleci.com/gh/MikeDev75015/mongodb-dynamic-api.svg?style=shield" alt="CircleCI"/></a>
18
+ <img src="https://img.shields.io/sonar/quality_gate/MikeDev75015_mongodb-dynamic-api?server=https%3A%2F%2Fsonarcloud.io" alt="Sonar Quality Gate"/>
19
+ <img src="https://img.shields.io/sonar/coverage/MikeDev75015_mongodb-dynamic-api?server=https%3A%2F%2Fsonarcloud.io" alt="Coverage"/>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <code>npm install --save mongodb-dynamic-api</code>
24
+ </p>
47
25
 
48
26
  ---
49
27
 
50
- ## mongodb-dynamic-api <img src="https://pbs.twimg.com/media/EDoWJbUXYAArclg.png" width="24" height="24" />
51
-
52
- A powerful, production-ready NestJS module that automatically generates fully functional REST APIs with WebSocket support for MongoDB collections.
53
-
54
- ```bash
55
- npm install --save mongodb-dynamic-api
56
- ```
57
-
58
- ---
59
-
60
- > ## ⚠️ What's new in v4.0.0 — Breaking Changes
28
+ > [!WARNING]
29
+ > **v4 — Breaking changes.** Dual-token auth (`accessToken` + `refreshToken`), new default expiry (`expiresIn: '15m'`), 2 new endpoints (`/auth/refresh-token`, `/auth/logout`).
61
30
  >
62
- > **v4 refactors the entire JWT authentication flow.** Please read the notes below before upgrading from v3.
31
+ > <details>
32
+ > <summary>📋 Full migration guide (v3 → v4)</summary>
63
33
  >
64
- > ### 🔄 Dual-token authentication (access + refresh)
65
- > Login and register now return **`{ accessToken, refreshToken }`** instead of a single token.
66
- > The `/auth/refresh-token` endpoint now requires the **refresh token** (not the access token).
34
+ > ### 🔄 Dual-token authentication
35
+ > Login and register now return **`{ accessToken, refreshToken }`** instead of a single token.
36
+ > The `/auth/refresh-token` endpoint now requires the **refresh token**.
67
37
  >
68
38
  > ### ⏱️ New default expiration times
69
39
  > | Token | v3 | v4 |
@@ -76,169 +46,167 @@ npm install --save mongodb-dynamic-api
76
46
  > ### 🆕 Two new endpoints
77
47
  > | Endpoint | Description |
78
48
  > |---|---|
79
- > | `POST /auth/refresh-token` | Get a new token pair using the refresh token (protected by `JwtRefreshGuard`) |
49
+ > | `POST /auth/refresh-token` | Get a new token pair using the refresh token |
80
50
  > | `POST /auth/logout` | Invalidate the refresh token server-side (204 No Content) |
81
51
  >
82
52
  > ### 🔒 New options in `useAuth`
83
- > - **`jwt.refreshSecret`** — dedicated signing secret for refresh tokens (falls back to `secret` if omitted)
84
- > - **`refreshToken.refreshTokenField`** — entity field storing the bcrypt hash (enables server-side revocation)
85
- > - **`refreshToken.useCookie`** — send/read refresh token via httpOnly cookie (`cookie-parser` auto-registered)
53
+ > - **`jwt.refreshSecret`** — dedicated signing secret for refresh tokens
54
+ > - **`refreshToken.refreshTokenField`** — entity field storing the bcrypt hash (server-side revocation)
55
+ > - **`refreshToken.useCookie`** — send/read refresh token via httpOnly cookie
56
+ >
57
+ > 📖 Full details: [README/authentication.md → Migration Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authentication.md#migration-guide-v3--v4)
86
58
  >
87
- > 📖 **Full migration guide:** [README/authentication.md → Migration Guide (v3 → v4)](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authentication.md#migration-guide-v3--v4)
59
+ > </details>
88
60
 
89
61
  ---
90
62
 
91
- <div style="text-align: center; width: 100%;">
63
+ ## Features
92
64
 
93
- # Dynamic API Module<br>with WebSockets
65
+ <table>
66
+ <tr>
67
+ <td>
94
68
 
95
- </div>
69
+ 🚀 **Zero Boilerplate**<br/>
70
+ Full CRUD REST API generated from a single schema definition.
96
71
 
97
- ## 📋 Table of Contents
72
+ </td>
73
+ <td>
98
74
 
99
- - [Overview](#overview)
100
- - [Key Features](#key-features)
101
- - [Installation](#installation)
102
- - [Quick Start](#quick-start)
103
- - [Advanced Features](#advanced-features)
104
- - [API Reference](#api-reference)
105
- - [Best Practices](#best-practices)
75
+ 🔐 **JWT Authentication**<br/>
76
+ Dual-token (access + refresh), 8 built-in endpoints, cookie mode, server-side revocation.
106
77
 
107
- ---
78
+ </td>
79
+ </tr>
80
+ <tr>
81
+ <td>
108
82
 
109
- ## Overview
83
+ 🔓 **Passwordless / OTP** ⭐<br/>
84
+ Magic-link / OTP login flow with configurable token delivery.
110
85
 
111
- **mongodb-dynamic-api** is a flexible and highly configurable NestJS 11 module that dramatically accelerates API development by automatically generating complete CRUD operations for your MongoDB collections. Instead of writing repetitive boilerplate code, define your data models and let the module handle the rest.
86
+ </td>
87
+ <td>
112
88
 
113
- The module provides:
114
- - **Automatic CRUD generation** - Full REST API endpoints generated from your schemas
115
- - **Real-time capabilities** - Built-in WebSocket support for live updates
116
- - **Enterprise-ready features** - Authentication, authorization, caching, and validation out of the box
117
- - **Developer-friendly** - Swagger documentation auto-generated for all endpoints
118
- - **Highly customizable** - Fine-grained control at global, controller, and route levels
89
+ 🛡️ **Authorization**<br/>
90
+ Ability predicates per-route access control in `filter` or `throw` mode.
119
91
 
120
- ---
92
+ </td>
93
+ </tr>
94
+ <tr>
95
+ <td>
121
96
 
122
- ## Key Features
123
-
124
- | Feature | Description |
125
- |---------|-------------|
126
- | 🚀 **Zero Boilerplate** | Generate complete CRUD APIs from schema definitions |
127
- | 🔐 **JWT Authentication** | Built-in dual-token authentication (access + refresh) with 8 endpoints (login, register, get/update account, reset/change password, refresh token, logout) |
128
- | 🛡️ **Authorization** | Role-based access control with ability predicates |
129
- | ⚡ **Smart Caching** | Global caching with automatic invalidation |
130
- | ✅ **Validation** | Global and per-route validation with class-validator |
131
- | 📡 **WebSockets** | Socket.IO support for calling routes via WebSocket events |
132
- | 📚 **Swagger UI** | Auto-generated OpenAPI documentation |
133
- | 🔄 **Versioning** | URI-based API versioning |
134
- | 🗑️ **Soft Delete** | Built-in soft delete with `SoftDeletableEntity` |
135
- | 🔍 **Advanced Queries** | MongoDB queries + Aggregation pipelines for complex analytics |
97
+ **Smart Caching**<br/>
98
+ Global HTTP cache with auto-invalidation, `disableCache` per route or controller.
136
99
 
137
- ---
100
+ </td>
101
+ <td>
138
102
 
139
- ## Installation
103
+ **Validation**<br/>
104
+ `class-validator` integration, configurable `ValidationPipe` globally or per route.
140
105
 
141
- ### Prerequisites
106
+ </td>
107
+ </tr>
108
+ <tr>
109
+ <td>
142
110
 
143
- - Node.js >= 16.0.0
144
- - NestJS >= 11.0.0
145
- - MongoDB >= 4.0
111
+ 📡 **WebSockets**<br/>
112
+ Socket.IO support, room-targeted broadcast, `onConnection` hook, debug mode.
146
113
 
147
- ### Create a New Project
114
+ </td>
115
+ <td>
148
116
 
149
- Start a new NestJS project with TypeScript in strict mode:
117
+ 🟢 **Presence** ⭐<br/>
118
+ Real-time online/offline tracking — InMemory or Redis adapter.
150
119
 
151
- ```bash
152
- nest new --strict your-project-name
153
- cd your-project-name
154
- ```
120
+ </td>
121
+ </tr>
122
+ <tr>
123
+ <td>
155
124
 
156
- ### Install the Package
125
+ 🔁 **Callbacks**<br/>
126
+ `beforeSave`, `afterSave`, `beforeDelete` hooks with typed context + authenticated user.
157
127
 
158
- Install mongodb-dynamic-api (all dependencies are included):
128
+ </td>
129
+ <td>
159
130
 
160
- ```bash
161
- npm install --save mongodb-dynamic-api
162
- ```
131
+ 🌊 **Cascade Delete** ⭐<br/>
132
+ Cross-collection cascades with `beforeDeleteCallback` and soft-delete support.
163
133
 
164
- > **✨ Note:** All required dependencies are included in the package:
165
- > - `@nestjs/mongoose` & `mongoose` - MongoDB integration
166
- > - `@nestjs/jwt` & `@nestjs/passport` - Authentication
167
- > - `@nestjs/swagger` - API documentation
168
- > - `class-validator` & `class-transformer` - Validation & transformation
169
- > - `@nestjs/cache-manager` & `cache-manager` - Caching
170
- > - `@nestjs/websockets` & `socket.io` - Real-time support
171
- >
172
- > **No additional packages required** for any of the optional features (authentication, caching, websockets, validation, etc.).
134
+ </td>
135
+ </tr>
136
+ <tr>
137
+ <td>
173
138
 
174
- ---
139
+ 🎛️ **Custom Routes** ⭐<br/>
140
+ Add any HTTP method with a custom service and WebSocket gateway in `forFeature`.
141
+
142
+ </td>
143
+ <td>
144
+
145
+ 🏷️ **Field Decorators** ⭐<br/>
146
+ `@DerivedField` for computed values, `@ProtectedField` for runtime-stripped fields.
147
+
148
+ </td>
149
+ </tr>
150
+ <tr>
151
+ <td>
175
152
 
176
- ## Quick Start
153
+ 🔍 **Aggregate + Pagination**<br/>
154
+ MongoDB aggregation pipelines via `toPipeline`, auto-paginated with `$facet`.
177
155
 
178
- This guide will help you set up your first dynamic API in less than 5 minutes.
156
+ </td>
157
+ <td>
179
158
 
180
- ### Step 1: Configure the Root Module
159
+ 📚 **Swagger UI**<br/>
160
+ Auto-generated OpenAPI documentation for every route, zero configuration.
181
161
 
182
- Import `DynamicApiModule.forRoot()` in your `app.module.ts` and provide your MongoDB connection string:
162
+ </td>
163
+ </tr>
164
+ </table>
165
+
166
+ > All dependencies included — `@nestjs/mongoose`, `@nestjs/jwt`, `@nestjs/swagger`, `class-validator`, `socket.io` and more. **No extra installs.**
167
+
168
+ ---
169
+
170
+ ## ⚡ Quick Start
171
+
172
+ ### 1 — Configure the root module
183
173
 
184
174
  ```typescript
185
175
  // src/app.module.ts
186
176
  import { Module } from '@nestjs/common';
187
177
  import { DynamicApiModule } from 'mongodb-dynamic-api';
188
- import { AppController } from './app.controller';
189
- import { AppService } from './app.service';
190
178
 
191
179
  @Module({
192
180
  imports: [
193
- DynamicApiModule.forRoot(
194
- 'mongodb://localhost:27017/my-database', // MongoDB connection URI
195
- ),
181
+ DynamicApiModule.forRoot(process.env.MONGODB_URI),
196
182
  ],
197
- controllers: [AppController],
198
- providers: [AppService],
199
183
  })
200
184
  export class AppModule {}
201
185
  ```
202
186
 
203
- > **💡 Tip:** Use environment variables for connection strings in production:
204
- > ```typescript
205
- > DynamicApiModule.forRoot(process.env.MONGODB_URI)
206
- > ```
207
-
208
- ### Step 2: Define Your Entity
209
-
210
- Create your first entity using Mongoose decorators. All entities must extend either `BaseEntity` or `SoftDeletableEntity`:
187
+ ### 2 Define your entity
211
188
 
212
189
  ```typescript
213
190
  // src/users/user.entity.ts
214
191
  import { Prop, Schema } from '@nestjs/mongoose';
215
192
  import { BaseEntity } from 'mongodb-dynamic-api';
193
+ import { IsEmail, IsNotEmpty } from 'class-validator';
216
194
 
217
195
  @Schema({ collection: 'users' })
218
196
  export class User extends BaseEntity {
197
+ @IsNotEmpty()
219
198
  @Prop({ type: String, required: true })
220
199
  name: string;
221
200
 
201
+ @IsEmail()
222
202
  @Prop({ type: String, required: true, unique: true })
223
203
  email: string;
224
-
225
- @Prop({ type: String })
226
- phone?: string;
227
-
228
- @Prop({ type: Boolean, default: true })
229
- isActive: boolean;
230
204
  }
231
205
  ```
232
206
 
233
- > **📝 Note:**
234
- > - `BaseEntity` provides `id`, `createdAt`, and `updatedAt` fields automatically
235
- > - Timestamps are **automatically enabled** - no need to add `timestamps: true` in `@Schema()`
236
- > - `_id` and `__v` are automatically excluded from JSON responses
237
- > - See [Entities documentation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/entities.md) for more details
207
+ > `BaseEntity` automatically provides `id`, `createdAt`, `updatedAt` and excludes `_id` / `__v` from responses.
238
208
 
239
- ### Step 3: Create a Feature Module
240
-
241
- Use `DynamicApiModule.forFeature()` to generate the API endpoints:
209
+ ### 3 Generate the API
242
210
 
243
211
  ```typescript
244
212
  // src/users/users.module.ts
@@ -246,682 +214,73 @@ import { Module } from '@nestjs/common';
246
214
  import { DynamicApiModule } from 'mongodb-dynamic-api';
247
215
  import { User } from './user.entity';
248
216
 
249
- @Module({
250
- imports: [
251
- DynamicApiModule.forFeature({
252
- entity: User,
253
- controllerOptions: {
254
- path: 'users',
255
- },
256
- }),
257
- ],
258
- })
259
- export class UsersModule {}
260
- ```
261
-
262
- ### Step 4: Register the Feature Module
263
-
264
- Add `UsersModule` to the imports array in `app.module.ts`:
265
-
266
- ```typescript
267
- // src/app.module.ts
268
- import { Module } from '@nestjs/common';
269
- import { DynamicApiModule } from 'mongodb-dynamic-api';
270
- import { UsersModule } from './users/users.module';
271
-
272
- @Module({
273
- imports: [
274
- DynamicApiModule.forRoot(
275
- 'mongodb://localhost:27017/my-database',
276
- ),
277
- UsersModule, // Add your feature module here
278
- ],
279
- controllers: [AppController],
280
- providers: [AppService],
281
- })
282
- export class AppModule {}
283
- ```
284
-
285
- ### Step 5: Start Your Application
286
-
287
- ```bash
288
- npm run start:dev
289
- ```
290
-
291
- **🎉 Congratulations!** Your API is now running with complete CRUD operations at `http://localhost:3000/users`
292
-
293
- ---
294
-
295
- ## API Reference
296
-
297
- Your generated API includes the following endpoints:
298
-
299
- ### Available Endpoints
300
-
301
- | Endpoint | Method | Description | Request Body | Params | Query |
302
- |:---------|:------:|:------------|:-------------|:-------|:------|
303
- | `/users` | `GET` | Retrieve all users | - | - | MongoDB query object |
304
- | `/users/:id` | `GET` | Retrieve a single user by ID | - | `id` | - |
305
- | `/users/many` | `POST` | Create multiple users at once | `{ list: User[] }` | - | - |
306
- | `/users` | `POST` | Create a single user | `User` | - | - |
307
- | `/users/:id` | `PUT` | Replace a user completely | `User` | `id` | - |
308
- | `/users` | `PATCH` | Update multiple users | `Partial<User>` | - | `ids[]` |
309
- | `/users/:id` | `PATCH` | Update a single user partially | `Partial<User>` | `id` | - |
310
- | `/users` | `DELETE` | Delete multiple users | - | - | `ids[]` |
311
- | `/users/:id` | `DELETE` | Delete a single user | - | `id` | - |
312
- | `/users/duplicate` | `POST` | Duplicate multiple users with updates | `Partial<User>` | - | `ids[]` |
313
- | `/users/duplicate/:id` | `POST` | Duplicate a single user with updates | `Partial<User>` | `id` | - |
314
- | `/users/aggregate` | `GET` | Execute MongoDB aggregation pipeline | - | - | Query DTO params |
315
-
316
- > **💡 Note:** The `Aggregate` route requires a custom Query DTO with a `toPipeline` static method. See [Advanced Queries documentation](#advanced-queries-with-aggregate) below.
317
-
318
- ### Request Examples
319
-
320
- #### Get Many Users
321
-
322
- ```bash
323
- # Get all users
324
- GET /users
325
-
326
- # Query parameters are passed directly to MongoDB find()
327
- # You can use any valid MongoDB query
328
- GET /users?isActive=true&name=John
329
- ```
330
-
331
- #### Create One User
332
-
333
- ```bash
334
- POST /users
335
- Content-Type: application/json
336
-
337
- {
338
- "name": "John Doe",
339
- "email": "john.doe@example.com",
340
- "phone": "+1234567890",
341
- "isActive": true
342
- }
343
- ```
344
-
345
- **Response (201 Created):**
346
- ```json
347
- {
348
- "id": "507f1f77bcf86cd799439011",
349
- "name": "John Doe",
350
- "email": "john.doe@example.com",
351
- "phone": "+1234567890",
352
- "isActive": true,
353
- "createdAt": "2026-02-21T10:30:00.000Z",
354
- "updatedAt": "2026-02-21T10:30:00.000Z"
355
- }
356
- ```
357
-
358
- #### Create Many Users
359
-
360
- ```bash
361
- POST /users/many
362
- Content-Type: application/json
363
-
364
- {
365
- "list": [
366
- {
367
- "name": "Alice Smith",
368
- "email": "alice@example.com"
369
- },
370
- {
371
- "name": "Bob Johnson",
372
- "email": "bob@example.com"
373
- }
374
- ]
375
- }
376
- ```
377
-
378
- **Response (201 Created):**
379
- ```json
380
- [
381
- {
382
- "id": "507f1f77bcf86cd799439011",
383
- "name": "Alice Smith",
384
- "email": "alice@example.com",
385
- "isActive": true,
386
- "createdAt": "2026-02-21T10:30:00.000Z",
387
- "updatedAt": "2026-02-21T10:30:00.000Z"
388
- },
389
- {
390
- "id": "507f1f77bcf86cd799439012",
391
- "name": "Bob Johnson",
392
- "email": "bob@example.com",
393
- "isActive": true,
394
- "createdAt": "2026-02-21T10:30:00.000Z",
395
- "updatedAt": "2026-02-21T10:30:00.000Z"
396
- }
397
- ]
398
- ```
399
-
400
- #### Update One User
401
-
402
- ```bash
403
- PATCH /users/507f1f77bcf86cd799439011
404
- Content-Type: application/json
405
-
406
- {
407
- "phone": "+0987654321",
408
- "isActive": false
409
- }
410
- ```
411
-
412
- **Response (200 OK):**
413
- ```json
414
- {
415
- "id": "507f1f77bcf86cd799439011",
416
- "name": "John Doe",
417
- "email": "john.doe@example.com",
418
- "phone": "+0987654321",
419
- "isActive": false,
420
- "createdAt": "2026-02-21T10:30:00.000Z",
421
- "updatedAt": "2026-02-21T10:35:00.000Z"
422
- }
423
- ```
424
-
425
- #### Duplicate One User
426
-
427
- ```bash
428
- POST /users/duplicate/507f1f77bcf86cd799439011
429
- Content-Type: application/json
430
-
431
- {
432
- "email": "john.doe.copy@example.com"
433
- }
434
- ```
435
-
436
- **Response (201 Created):**
437
- ```json
438
- {
439
- "id": "507f1f77bcf86cd799439013",
440
- "name": "John Doe",
441
- "email": "john.doe.copy@example.com",
442
- "phone": "+0987654321",
443
- "isActive": false,
444
- "createdAt": "2026-02-21T10:40:00.000Z",
445
- "updatedAt": "2026-02-21T10:40:00.000Z"
446
- }
447
- ```
448
-
449
- ---
450
-
451
- ## Advanced Features
452
-
453
- The module supports powerful customization options. Here are some quick examples:
454
-
455
- ### Advanced Queries with Aggregate
456
-
457
- The `Aggregate` route type enables **MongoDB aggregation pipelines** for complex queries, analytics, and data transformations. This is perfect for:
458
- - Grouping and counting data
459
- - Calculating statistics (sum, average, min, max)
460
- - Joining related collections with `$lookup`
461
- - Complex filtering and transformations
462
- - Pagination with counting
463
-
464
- **Key Requirements:**
465
- 1. You **must** provide a custom Query DTO
466
- 2. The Query DTO **must** have a static `toPipeline` method
467
- 3. The method returns an array of MongoDB pipeline stages
468
- 4. Optionally provide a custom Presenter with `fromAggregate` method
469
-
470
- **Example - User Statistics:**
471
-
472
- ```typescript
473
- // src/users/dtos/user-stats.query.ts
474
- import { IsOptional, IsString } from 'class-validator';
475
- import { PipelineStage } from 'mongodb-pipeline-builder';
476
-
477
- export class UserStatsQuery {
478
- @IsOptional()
479
- @IsString()
480
- status?: string;
481
-
482
- static toPipeline(query: UserStatsQuery): PipelineStage[] {
483
- const pipeline: PipelineStage[] = [];
484
-
485
- // Filter by status if provided
486
- if (query.status) {
487
- pipeline.push({ $match: { isActive: query.status === 'active' } });
488
- }
489
-
490
- // Group by creation date (year-month)
491
- pipeline.push({
492
- $group: {
493
- _id: {
494
- year: { $year: '$createdAt' },
495
- month: { $month: '$createdAt' },
496
- },
497
- count: { $sum: 1 },
498
- users: { $push: { id: '$_id', name: '$name', email: '$email' } },
499
- },
500
- });
501
-
502
- // Sort by date
503
- pipeline.push({
504
- $sort: { '_id.year': -1, '_id.month': -1 },
505
- });
506
-
507
- return pipeline;
508
- }
509
- }
510
-
511
- // src/users/dtos/user-stats.presenter.ts
512
- export class UserStatsPresenter {
513
- period: string;
514
- count: number;
515
- users: { id: string; name: string; email: string }[];
516
-
517
- static fromAggregate(results: any[]): UserStatsPresenter[] {
518
- return results.map(result => ({
519
- period: `${result._id.year}-${String(result._id.month).padStart(2, '0')}`,
520
- count: result.count,
521
- users: result.users,
522
- }));
523
- }
524
- }
525
-
526
- // src/users/users.module.ts
527
217
  @Module({
528
218
  imports: [
529
219
  DynamicApiModule.forFeature({
530
220
  entity: User,
531
221
  controllerOptions: { path: 'users' },
532
- routes: [
533
- {
534
- type: 'Aggregate',
535
- subPath: 'stats',
536
- dTOs: {
537
- query: UserStatsQuery,
538
- presenter: UserStatsPresenter,
539
- },
540
- },
541
- ],
542
222
  }),
543
223
  ],
544
224
  })
545
225
  export class UsersModule {}
546
226
  ```
547
227
 
548
- **Usage:**
549
- ```bash
550
- # Get all user statistics
551
- GET /users/aggregate/stats
552
-
553
- # Filter by active users
554
- GET /users/aggregate/stats?status=active
555
- ```
556
-
557
- **With Pagination Support:**
558
-
559
- The Aggregate route automatically detects pagination when your pipeline starts with a `$facet` stage:
560
-
561
- ```typescript
562
- import { Type } from 'class-transformer';
563
- import { IsOptional, IsNumber } from 'class-validator';
564
- import { PipelineBuilder, PipelineStage } from 'mongodb-pipeline-builder';
565
-
566
- export class PaginatedUsersQuery {
567
- @IsOptional()
568
- @IsNumber()
569
- @Type(() => Number)
570
- page?: number = 1;
571
-
572
- @IsOptional()
573
- @IsNumber()
574
- @Type(() => Number)
575
- limit?: number = 10;
576
-
577
- static toPipeline(query: PaginatedUsersQuery): PipelineStage[] {
578
- const builder = new PipelineBuilder('paginated-users');
579
-
580
- // Using method chaining - all methods return the builder
581
- return builder
582
- .Match({ isActive: true }) // Add your filters
583
- .Paging(query.limit, query.page) // Paging(elementsPerPage, page)
584
- .build(); // Build and return the pipeline array
585
- }
586
- }
587
- ```
588
-
589
- **Response with pagination:**
590
- ```json
591
- {
592
- "list": [...],
593
- "count": 150,
594
- "totalPage": 15
595
- }
596
- ```
597
-
598
- > 📚 **Learn More:** The module uses [`mongodb-pipeline-builder`](https://www.npmjs.com/package/mongodb-pipeline-builder) (by the same author) to greatly simplify building aggregation pipelines with:
599
- > - **Fluent API** - Chain methods for readable pipeline construction
600
- > - **Type Safety** - Full TypeScript support with IntelliSense
601
- > - **Automatic Pagination** - Built-in support for `$facet` with counting via `Paging()` method
602
- > - **Stage Methods** - Pre-built methods: `Match()`, `Group()`, `Lookup()`, `Sort()`, `Project()`, `AddFields()`, etc.
603
- > - **Operators** - Import operators like `$Sum()`, `$Average()`, `$First()`, `$Max()`, `$Push()`, etc.
604
- > - **Helpers** - Utility functions: `LookupEqualityHelper()`, `ProjectOnlyHelper()`, `Field()`, etc.
605
- > - **No manual stage objects** - Write `builder.Match({ field: value })` instead of `{ $match: { field: value } }`
606
- >
607
- > **Example with operators and helpers:**
608
- > ```typescript
609
- > import { PipelineBuilder } from 'mongodb-pipeline-builder';
610
- > import { $Sum, $Average, $First, $Push } from 'mongodb-pipeline-builder/operators';
611
- > import { Field, ProjectOnlyHelper, LookupEqualityHelper } from 'mongodb-pipeline-builder/helpers';
612
- >
613
- > const pipeline = new PipelineBuilder('my-pipeline')
614
- > .Match({ status: 'active' })
615
- > .Project(ProjectOnlyHelper('name', 'email', 'orders'))
616
- > .Lookup(LookupEqualityHelper('orders', 'userOrders', '_id', 'userId'))
617
- > .Group({
618
- > _id: '$department',
619
- > totalUsers: $Sum(1),
620
- > avgOrderValue: $Average('$orders.total'),
621
- > firstUser: $First('$name'),
622
- > allEmails: $Push('$email'),
623
- > })
624
- > .AddFields(
625
- > Field('calculatedField', { $multiply: ['$totalUsers', 10] })
626
- > )
627
- > .Sort({ totalUsers: -1 })
628
- > .Paging(10, 1) // 10 per page, page 1
629
- > .build();
630
- > ```
631
- >
632
- > This makes complex pipelines much easier to write and maintain!
633
-
634
- **Advanced Example - Join Collections with $lookup:**
635
-
636
- ```typescript
637
- // src/orders/dtos/order-analytics.query.ts
638
- import { IsOptional, IsDateString } from 'class-validator';
639
- import { PipelineBuilder, PipelineStage } from 'mongodb-pipeline-builder';
640
- import { $First, $Sum, $Average } from 'mongodb-pipeline-builder/operators';
641
- import { LookupEqualityHelper } from 'mongodb-pipeline-builder/helpers';
642
-
643
- export class OrderAnalyticsQuery {
644
- @IsOptional()
645
- @IsDateString()
646
- startDate?: string;
647
-
648
- @IsOptional()
649
- @IsDateString()
650
- endDate?: string;
651
-
652
- static toPipeline(query: OrderAnalyticsQuery): PipelineStage[] {
653
- // Using PipelineBuilder (simplified approach - recommended)
654
- const builder = new PipelineBuilder('order-analytics');
655
-
656
- // Filter by date range
657
- if (query.startDate || query.endDate) {
658
- const dateFilter: any = {};
659
- if (query.startDate) dateFilter.$gte = new Date(query.startDate);
660
- if (query.endDate) dateFilter.$lte = new Date(query.endDate);
661
- builder.Match({ createdAt: dateFilter });
662
- }
663
-
664
- // Using method chaining with helpers for readable pipeline construction
665
- return builder
666
- .Lookup(LookupEqualityHelper('users', 'user', 'userId', '_id')) // Join with users
667
- .Unwind('$user') // Unwind user array
668
- .Lookup(LookupEqualityHelper('products', 'products', 'items.productId', '_id')) // Join with products
669
- .Group({ // Group by user and calculate totals using operators
670
- _id: '$userId',
671
- userName: $First('$user.name'),
672
- userEmail: $First('$user.email'),
673
- orderCount: $Sum(1),
674
- totalAmount: $Sum('$total'),
675
- avgOrderValue: $Average('$total'),
676
- })
677
- .Sort({ totalAmount: -1 }) // Sort by total amount descending
678
- .build(); // Build and return the pipeline
679
-
680
- /* Manual approach (without PipelineBuilder and helpers):
681
- const pipeline: PipelineStage[] = [];
682
-
683
- if (query.startDate || query.endDate) {
684
- const dateFilter: any = {};
685
- if (query.startDate) dateFilter.$gte = new Date(query.startDate);
686
- if (query.endDate) dateFilter.$lte = new Date(query.endDate);
687
- pipeline.push({ $match: { createdAt: dateFilter } });
688
- }
689
-
690
- pipeline.push({
691
- $lookup: {
692
- from: 'users',
693
- localField: 'userId',
694
- foreignField: '_id',
695
- as: 'user',
696
- },
697
- });
698
-
699
- pipeline.push({ $unwind: '$user' });
700
-
701
- pipeline.push({
702
- $lookup: {
703
- from: 'products',
704
- localField: 'items.productId',
705
- foreignField: '_id',
706
- as: 'products',
707
- },
708
- });
709
-
710
- pipeline.push({
711
- $group: {
712
- _id: '$userId',
713
- userName: { $first: '$user.name' },
714
- userEmail: { $first: '$user.email' },
715
- orderCount: { $sum: 1 },
716
- totalAmount: { $sum: '$total' },
717
- avgOrderValue: { $avg: '$total' },
718
- },
719
- });
720
-
721
- pipeline.push({ $sort: { totalAmount: -1 } });
722
-
723
- return pipeline;
724
-
725
- Compare with PipelineBuilder approach:
726
- - LookupEqualityHelper() vs verbose $lookup object
727
- - $First(), $Sum(), $Average() vs raw MongoDB operators
728
- - Method chaining vs array.push()
729
- - Much more readable and maintainable!
730
- */
731
- }
732
- }
733
-
734
- // src/orders/dtos/order-analytics.presenter.ts
735
- export class OrderAnalyticsPresenter {
736
- userId: string;
737
- userName: string;
738
- userEmail: string;
739
- orderCount: number;
740
- totalAmount: number;
741
- avgOrderValue: number;
742
-
743
- static fromAggregate(results: any[]): OrderAnalyticsPresenter[] {
744
- return results.map(result => ({
745
- userId: result._id,
746
- userName: result.userName,
747
- userEmail: result.userEmail,
748
- orderCount: result.orderCount,
749
- totalAmount: Math.round(result.totalAmount * 100) / 100,
750
- avgOrderValue: Math.round(result.avgOrderValue * 100) / 100,
751
- }));
752
- }
753
- }
754
-
755
- // Usage
756
- GET /orders/aggregate/analytics?startDate=2026-01-01&endDate=2026-12-31
757
- ```
758
-
759
- **Response:**
760
- ```json
761
- [
762
- {
763
- "userId": "507f1f77bcf86cd799439011",
764
- "userName": "John Doe",
765
- "userEmail": "john@example.com",
766
- "orderCount": 15,
767
- "totalAmount": 2450.50,
768
- "avgOrderValue": 163.37
769
- },
770
- {
771
- "userId": "507f1f77bcf86cd799439012",
772
- "userName": "Jane Smith",
773
- "userEmail": "jane@example.com",
774
- "orderCount": 12,
775
- "totalAmount": 1850.75,
776
- "avgOrderValue": 154.23
777
- }
778
- ]
779
- ```
780
-
781
- ### Route Configuration
782
-
783
- Control which routes are generated and customize individual routes:
784
-
785
- ```typescript
786
- DynamicApiModule.forFeature({
787
- entity: User,
788
- controllerOptions: {
789
- path: 'users',
790
- apiTag: 'Users Management',
791
- },
792
- routes: [
793
- { type: 'GetMany' },
794
- { type: 'GetOne' },
795
- { type: 'CreateOne', validationPipeOptions: { whitelist: true } },
796
- { type: 'UpdateOne' },
797
- { type: 'DeleteOne' },
798
- // Exclude specific routes
799
- { type: 'DeleteMany', excluded: true },
800
- ],
801
- })
802
- ```
803
-
804
- ### Enable Soft Delete
805
-
806
- Use `SoftDeletableEntity` for soft delete functionality:
807
-
808
- ```typescript
809
- import { SoftDeletableEntity } from 'mongodb-dynamic-api';
810
-
811
- @Schema({ collection: 'users' })
812
- export class User extends SoftDeletableEntity {
813
- @Prop({ type: String, required: true })
814
- name: string;
815
- // ... other fields
816
- }
817
- ```
818
-
819
- Deleted entities are marked with `isDeleted: true` instead of being removed.
820
-
821
- > 📚 **Learn More:** See detailed guides in the [Optional Features](#optional-features-all-dependencies-included) section below.
228
+ Register `UsersModule` in `AppModule`, run `npm run start:dev` — your API is live at `http://localhost:3000/users`. 🎉
822
229
 
823
230
  ---
824
231
 
825
- ## Best Practices
826
-
827
- ### 1. Use Environment Variables
828
-
829
- Always use environment variables for sensitive configuration:
830
-
831
- ```typescript
832
- DynamicApiModule.forRoot(process.env.MONGODB_URI)
833
- ```
834
-
835
- ### 2. Add Validation
836
-
837
- Use class-validator decorators on your entities:
838
-
839
- ```typescript
840
- import { IsEmail, IsNotEmpty, Length } from 'class-validator';
841
-
842
- @Schema({ collection: 'users' })
843
- export class User extends BaseEntity {
844
- @IsNotEmpty()
845
- @Length(2, 100)
846
- @Prop({ type: String, required: true })
847
- name: string;
848
-
849
- @IsEmail()
850
- @Prop({ type: String, required: true, unique: true })
851
- email: string;
852
- }
853
- ```
854
-
855
- ### 3. Optimize with Indexes
856
-
857
- Define indexes for frequently queried fields:
858
-
859
- ```typescript
860
- import { DynamicAPISchemaOptions } from 'mongodb-dynamic-api';
861
-
862
- @DynamicAPISchemaOptions({
863
- indexes: [
864
- { fields: { email: 1 }, options: { unique: true } },
865
- { fields: { createdAt: -1 } },
866
- ],
867
- })
868
- @Schema({ collection: 'users' })
869
- export class User extends BaseEntity {
870
- // ...
871
- }
872
- ```
873
-
874
- > 📚 **Learn More:** See [Schema Options](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/schema-options.md) and [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/validation.md) guides.
232
+ ## 📡 Generated Endpoints
233
+
234
+ | Route Type | Method | Path | Description |
235
+ |:-----------|:------:|:-----|:------------|
236
+ | `GetMany` | `GET` | `/users` | List all — supports MongoDB query params |
237
+ | `GetOne` | `GET` | `/users/:id` | Get a single document by ID |
238
+ | `CreateMany` | `POST` | `/users/many` | Bulk create — body: `{ list: User[] }` |
239
+ | `CreateOne` | `POST` | `/users` | Create a single document |
240
+ | `ReplaceOne` | `PUT` | `/users/:id` | Full replacement |
241
+ | `UpdateMany` | `PATCH` | `/users` | Partial update — query: `?ids[]=` |
242
+ | `UpdateOne` | `PATCH` | `/users/:id` | Partial update by ID |
243
+ | `DeleteMany` | `DELETE` | `/users` | Delete multiple — query: `?ids[]=` |
244
+ | `DeleteOne` | `DELETE` | `/users/:id` | Delete by ID |
245
+ | `DuplicateMany` | `POST` | `/users/duplicate` | Clone multiple with field overrides |
246
+ | `DuplicateOne` | `POST` | `/users/duplicate/:id` | Clone one with field overrides |
247
+ | `Aggregate` | `GET` | `/users/aggregate` | Custom aggregation pipeline (requires Query DTO) |
248
+
249
+ > Use the `routes` array in `forFeature` to cherry-pick, exclude, or fine-tune any route individually.
875
250
 
876
251
  ---
877
252
 
878
- ## Learn More
879
-
880
- Explore advanced features and configurations:
881
-
882
- ### Core Concepts
883
-
884
- - **[Entities](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/entities.md)** - Learn about BaseEntity and SoftDeletableEntity (timestamps auto-enabled)
885
- - **[Schema Options](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/schema-options.md)** - Configure indexes, hooks with options (document/query), and custom initialization
886
-
887
- ### Optional Features (All Dependencies Included)
888
-
889
- | Feature | Description | Documentation |
890
- |---------|-------------|---------------|
891
- | 📚 **Swagger UI** | Auto-generated OpenAPI documentation | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/swagger-ui.md) |
892
- | 🔄 **Versioning** | URI-based API versioning | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/versioning.md) |
893
- | **Validation** | Request validation with class-validator | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/validation.md) |
894
- | **Caching** | Global caching with auto-invalidation | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/caching.md) |
895
- | 🔐 **Authentication** | JWT dual-token auth (8 endpoints) — access + refresh tokens, cookie mode, server-side revocation | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authentication.md) |
896
- | 🛡️ **Authorization** | Role-based access control | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authorization.md) |
897
- | 📡 **WebSockets** | Socket.IO integration for routes | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/websockets.md) |
898
- | 🟢 **Presence** | Real-time online/offline tracking for WebSocket users | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/presence.md) |
899
- | 🗂️ **Route Configuration** | All route options: DTOs, interceptors, etc. | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/route-config.md) |
900
- | 🔁 **Callbacks** | beforeSave / afterSave hooks, typed contexts, user access, audit trails | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/callbacks.md) |
901
- | 🎛️ **Controller Configuration** | All `controllerOptions` and `forFeature` options | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/controller-config.md) |
902
-
903
- ### Important Notes
904
-
905
- - **Timestamps**: Automatically enabled when entity extends `BaseEntity` (provides `createdAt` and `updatedAt`)
906
- - **Soft Delete**: Use `SoftDeletableEntity` to add `isDeleted` and `deletedAt` fields
907
- - **Version Format**: Must be numeric strings (`'1'`, `'2'`), not semantic versioning
908
- - **WebSocket Events**:
909
- - Auth events have fixed names: `auth-login`, `auth-register`, `auth-get-account`, `auth-update-account`, `auth-reset-password`, `auth-change-password`, `auth-refresh-token`, `auth-logout`
910
- - CRUD events are generated from entity name or `apiTag`: `kebabCase(routeType + '/' + displayedName)`
911
- - **Ability Predicates**: Signature varies by context:
912
- - Auth routes: `(user, body?) => boolean`
913
- - CRUD routes: `(entity, user) => boolean`
253
+ ## 📚 Documentation
254
+
255
+ | Feature | Description | Guide |
256
+ |:--------|:------------|:-----:|
257
+ | 🗂️ **Entities** | `BaseEntity`, `SoftDeletableEntity`, timestamps, JSON transform | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/entities.md) |
258
+ | 🗃️ **Schema Options** | Indexes, lifecycle hooks, custom schema initialization | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/schema-options.md) |
259
+ | 🔐 **Authentication** | Dual-token JWT, 8 endpoints, cookie mode, revocation, OTP ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authentication.md) |
260
+ | 🛡️ **Authorization** | Ability predicates, `filter` vs `throw` mode | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/authorization.md) |
261
+ | ⚡ **Caching** | Global cache, auto-purge endpoint, `disableCache` option ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/caching.md) |
262
+ | **Validation** | `class-validator`, global + per-route `ValidationPipe` | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/validation.md) |
263
+ | 📡 **WebSockets** | Socket.IO, room-targeted broadcast, `onConnection`, debug ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/websockets.md) |
264
+ | 🟢 **Presence** | Online/offline tracking, InMemory & Redis adapters ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/presence.md) |
265
+ | 🔁 **Callbacks** | `beforeSave`, `afterSave`, `beforeDelete`, typed contexts ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/callbacks.md) |
266
+ | 🔄 **Versioning** | URI-based API versioning | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/versioning.md) |
267
+ | 📚 **Swagger UI** | Auto-generated OpenAPI docs, visibility decorators | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/swagger-ui.md) |
268
+ | 🗂️ **Route Config** | DTOs, cascade delete, predicates, subPath, interceptors ⭐ | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/route-config.md) |
269
+ | 🎛️ **Controller Config** | `forFeature` options, `customRoutes`, `extraProviders` | [View](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/controller-config.md) |
270
+
271
+ > [!NOTE]
272
+ > **Key reminders:**
273
+ > - `BaseEntity` auto-enables timestamps no `timestamps: true` needed in `@Schema()`
274
+ > - Soft delete: extend `SoftDeletableEntity` to get `isDeleted` + `deletedAt` fields
275
+ > - Version strings must be numeric (`'1'`, `'2'`), not semver
276
+ > - WebSocket auth event names are fixed: `auth-login`, `auth-register`, `auth-refresh-token`, `auth-logout`…
277
+ > - CRUD WS events auto-generated: `kebabCase(routeType + '/' + displayedName)`
278
+ > - Ability predicate signature: `(user, body?) => boolean` for auth, `(entity, user) => boolean` for CRUD
914
279
 
915
280
  ---
916
281
 
917
282
  ## License
918
283
 
919
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
920
-
921
- ---
922
-
284
+ MIT see [LICENSE](LICENSE)
923
285
 
924
286
  **Made with ❤️ by [Mickaël NODANCHE](https://cv-mikeonline.web.app)**
925
-
926
-
927
-