@elsikora/nestjs-crud-config 1.0.1 → 1.1.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +644 -286
  2. package/dist/cjs/index.js +26 -10
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/modules/config/config.module.js +67 -1
  5. package/dist/cjs/modules/config/config.module.js.map +1 -1
  6. package/dist/cjs/modules/config/config.service.js +55 -98
  7. package/dist/cjs/modules/config/config.service.js.map +1 -1
  8. package/dist/cjs/modules/config/data/entity/entity.js +1 -0
  9. package/dist/cjs/modules/config/data/entity/entity.js.map +1 -1
  10. package/dist/cjs/modules/config/index.d.ts +2 -0
  11. package/dist/cjs/modules/config/migration/entity/entity.d.ts +12 -0
  12. package/dist/cjs/modules/config/migration/entity/entity.js +139 -0
  13. package/dist/cjs/modules/config/migration/entity/entity.js.map +1 -0
  14. package/dist/cjs/modules/config/migration/entity/index.d.ts +1 -0
  15. package/dist/cjs/modules/config/migration/enum/index.d.ts +1 -0
  16. package/dist/cjs/modules/config/migration/enum/status.d.ts +7 -0
  17. package/dist/cjs/modules/config/migration/enum/status.js +11 -0
  18. package/dist/cjs/modules/config/migration/enum/status.js.map +1 -0
  19. package/dist/cjs/modules/config/migration/index.d.ts +5 -0
  20. package/dist/cjs/modules/config/migration/interface/index.d.ts +3 -0
  21. package/dist/cjs/modules/config/migration/interface/interface.d.ts +11 -0
  22. package/dist/cjs/modules/config/migration/interface/migration-definition.interface.d.ts +29 -0
  23. package/dist/cjs/modules/config/migration/interface/migration-options.interface.d.ts +34 -0
  24. package/dist/cjs/modules/config/migration/migration-runner.service.d.ts +22 -0
  25. package/dist/cjs/modules/config/migration/migration-runner.service.js +85 -0
  26. package/dist/cjs/modules/config/migration/migration-runner.service.js.map +1 -0
  27. package/dist/cjs/modules/config/migration/migration.service.d.ts +68 -0
  28. package/dist/cjs/modules/config/migration/migration.service.js +331 -0
  29. package/dist/cjs/modules/config/migration/migration.service.js.map +1 -0
  30. package/dist/cjs/modules/config/section/entity/entity.js +1 -0
  31. package/dist/cjs/modules/config/section/entity/entity.js.map +1 -1
  32. package/dist/cjs/package.json +2 -1
  33. package/dist/cjs/shared/constant/config/index.d.ts +1 -0
  34. package/dist/cjs/shared/constant/config/migration.constant.d.ts +6 -0
  35. package/dist/cjs/shared/constant/config/migration.constant.js +15 -0
  36. package/dist/cjs/shared/constant/config/migration.constant.js.map +1 -0
  37. package/dist/cjs/shared/constant/token.constant.d.ts +2 -0
  38. package/dist/cjs/shared/constant/token.constant.js +4 -0
  39. package/dist/cjs/shared/constant/token.constant.js.map +1 -1
  40. package/dist/cjs/shared/interface/config/options.interface.d.ts +3 -2
  41. package/dist/cjs/shared/utility/index.d.ts +1 -0
  42. package/dist/cjs/shared/utility/logger.utility.d.ts +21 -0
  43. package/dist/cjs/shared/utility/logger.utility.js +67 -0
  44. package/dist/cjs/shared/utility/logger.utility.js.map +1 -0
  45. package/dist/esm/index.js +10 -3
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/modules/config/config.module.js +67 -1
  48. package/dist/esm/modules/config/config.module.js.map +1 -1
  49. package/dist/esm/modules/config/config.service.js +56 -99
  50. package/dist/esm/modules/config/config.service.js.map +1 -1
  51. package/dist/esm/modules/config/data/entity/entity.js +1 -0
  52. package/dist/esm/modules/config/data/entity/entity.js.map +1 -1
  53. package/dist/esm/modules/config/index.d.ts +2 -0
  54. package/dist/esm/modules/config/migration/entity/entity.d.ts +12 -0
  55. package/dist/esm/modules/config/migration/entity/entity.js +137 -0
  56. package/dist/esm/modules/config/migration/entity/entity.js.map +1 -0
  57. package/dist/esm/modules/config/migration/entity/index.d.ts +1 -0
  58. package/dist/esm/modules/config/migration/enum/index.d.ts +1 -0
  59. package/dist/esm/modules/config/migration/enum/status.d.ts +7 -0
  60. package/dist/esm/modules/config/migration/enum/status.js +11 -0
  61. package/dist/esm/modules/config/migration/enum/status.js.map +1 -0
  62. package/dist/esm/modules/config/migration/index.d.ts +5 -0
  63. package/dist/esm/modules/config/migration/interface/index.d.ts +3 -0
  64. package/dist/esm/modules/config/migration/interface/interface.d.ts +11 -0
  65. package/dist/esm/modules/config/migration/interface/migration-definition.interface.d.ts +29 -0
  66. package/dist/esm/modules/config/migration/interface/migration-options.interface.d.ts +34 -0
  67. package/dist/esm/modules/config/migration/migration-runner.service.d.ts +22 -0
  68. package/dist/esm/modules/config/migration/migration-runner.service.js +85 -0
  69. package/dist/esm/modules/config/migration/migration-runner.service.js.map +1 -0
  70. package/dist/esm/modules/config/migration/migration.service.d.ts +68 -0
  71. package/dist/esm/modules/config/migration/migration.service.js +331 -0
  72. package/dist/esm/modules/config/migration/migration.service.js.map +1 -0
  73. package/dist/esm/modules/config/section/entity/entity.js +1 -0
  74. package/dist/esm/modules/config/section/entity/entity.js.map +1 -1
  75. package/dist/esm/package.json +2 -1
  76. package/dist/esm/shared/constant/config/index.d.ts +1 -0
  77. package/dist/esm/shared/constant/config/migration.constant.d.ts +6 -0
  78. package/dist/esm/shared/constant/config/migration.constant.js +13 -0
  79. package/dist/esm/shared/constant/config/migration.constant.js.map +1 -0
  80. package/dist/esm/shared/constant/token.constant.d.ts +2 -0
  81. package/dist/esm/shared/constant/token.constant.js +4 -0
  82. package/dist/esm/shared/constant/token.constant.js.map +1 -1
  83. package/dist/esm/shared/interface/config/options.interface.d.ts +3 -2
  84. package/dist/esm/shared/utility/index.d.ts +1 -0
  85. package/dist/esm/shared/utility/logger.utility.d.ts +21 -0
  86. package/dist/esm/shared/utility/logger.utility.js +67 -0
  87. package/dist/esm/shared/utility/logger.utility.js.map +1 -0
  88. package/package.json +15 -14
package/README.md CHANGED
@@ -11,12 +11,13 @@
11
11
  </a> <img src="https://img.shields.io/badge/TypeScript-3178C6.svg?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript"> <img src="https://img.shields.io/badge/NestJS-E0234E.svg?style=for-the-badge&logo=nestjs&logoColor=white" alt="NestJS"> <img src="https://img.shields.io/badge/Node.js-339933.svg?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js"> <img src="https://img.shields.io/badge/PostgreSQL-4169E1.svg?style=for-the-badge&logo=postgresql&logoColor=white" alt="PostgreSQL"> <img src="https://img.shields.io/badge/MySQL-4479A1.svg?style=for-the-badge&logo=mysql&logoColor=white" alt="MySQL"> <img src="https://img.shields.io/badge/SQLite-003B57.svg?style=for-the-badge&logo=sqlite&logoColor=white" alt="SQLite"> <img src="https://img.shields.io/badge/MongoDB-47A248.svg?style=for-the-badge&logo=mongodb&logoColor=white" alt="MongoDB"> <img src="https://img.shields.io/badge/Swagger-85EA2D.svg?style=for-the-badge&logo=swagger&logoColor=black" alt="Swagger">
12
12
  </p>
13
13
 
14
-
15
14
  ## 📚 Table of Contents
15
+
16
16
  - [Description](#-description)
17
17
  - [Features](#-features)
18
18
  - [Installation](#-installation)
19
19
  - [Usage](#-usage)
20
+ - [Migration System](#-migration-system)
20
21
  - [API Documentation](#-api-documentation)
21
22
  - [Database Schema](#-database-schema)
22
23
  - [Advanced Configuration](#-advanced-configuration)
@@ -24,7 +25,6 @@
24
25
  - [FAQ](#-faq)
25
26
  - [License](#-license)
26
27
 
27
-
28
28
  ## 📖 Description
29
29
 
30
30
  NestJS CRUD Config is a powerful configuration management module that revolutionizes how NestJS applications handle configuration data. Unlike traditional environment variable approaches, this library stores configuration in a database with full CRUD operations, making it perfect for dynamic configuration management across multiple environments and services.
@@ -50,6 +50,7 @@ Whether you're managing API keys across different environments, storing feature
50
50
  - **🚦 Flexible controller configuration** - Customize API paths, disable endpoints, or run in headless mode
51
51
  - **🔧 Advanced CRUD customization** - Full control over routes, swagger documentation, and controller behavior
52
52
  - **📦 Modular architecture** - Enable/disable features as needed for your use case
53
+ - **🚀 Production-ready migration system** - Initialize and manage configuration state during application startup
53
54
 
54
55
  ## 🛠 Installation
55
56
 
@@ -75,6 +76,7 @@ npm install @nestjs/common @nestjs/typeorm typeorm @elsikora/nestjs-crud-automat
75
76
  ### Database Support
76
77
 
77
78
  This package works with any database supported by TypeORM:
79
+
78
80
  - PostgreSQL
79
81
  - MySQL/MariaDB
80
82
  - SQLite
@@ -89,91 +91,88 @@ This package works with any database supported by TypeORM:
89
91
 
90
92
  ```typescript
91
93
  // app.module.ts
92
- import { Module } from '@nestjs/common';
93
- import { TypeOrmModule } from '@nestjs/typeorm';
94
- import { CrudConfigModule, TOKEN_CONSTANT } from '@elsikora/nestjs-crud-config';
94
+ import { Module } from "@nestjs/common";
95
+ import { TypeOrmModule } from "@nestjs/typeorm";
96
+ import { CrudConfigModule, TOKEN_CONSTANT } from "@elsikora/nestjs-crud-config";
95
97
 
96
98
  @Module({
97
- imports: [
98
- // First register CrudConfigModule to create dynamic entities
99
- CrudConfigModule.register({
100
- environment: 'development',
101
- isVerbose: true,
102
- shouldAutoCreateSections: true,
103
-
104
- // Cache configuration
105
- cacheOptions: {
106
- isEnabled: true,
107
- maxCacheItems: 1000,
108
- maxCacheTTL: 3600000, // 1 hour in milliseconds
109
- },
110
-
111
- // Encryption configuration
112
- encryptionOptions: {
113
- isEnabled: true,
114
- encryptionKey: process.env.CONFIG_ENCRYPTION_KEY, // 32+ character key
115
- },
116
-
117
- // Controller configuration (optional)
118
- controllersOptions: {
119
- section: {
120
- isEnabled: true,
121
- properties: {
122
- path: 'api/config/sections',
123
- },
124
- },
125
- data: {
126
- isEnabled: true,
127
- properties: {
128
- path: 'api/config/data',
129
- },
130
- },
131
- },
132
-
133
- // Entity customization
134
- entityOptions: {
135
- tablePrefix: 'app_',
136
- configSection: {
137
- tableName: 'config_sections',
138
- maxNameLength: 128,
139
- maxDescriptionLength: 512,
140
- },
141
- configData: {
142
- tableName: 'config_data',
143
- maxValueLength: 8192,
144
- maxEnvironmentLength: 64,
145
- maxNameLength: 128,
146
- maxDescriptionLength: 512,
147
- },
148
- },
149
- }),
150
-
151
- // Then register TypeORM with dynamic entities using registerAsync
152
- TypeOrmModule.forRootAsync({
153
- imports: [CrudConfigModule],
154
- inject: [
155
- TOKEN_CONSTANT.CONFIG_SECTION_ENTITY,
156
- TOKEN_CONSTANT.CONFIG_DATA_ENTITY,
157
- ],
158
- useFactory: async (sectionEntity, dataEntity) => ({
159
- type: 'postgres',
160
- host: 'localhost',
161
- port: 5432,
162
- username: 'postgres',
163
- password: 'postgres',
164
- database: 'config_db',
165
- entities: [
166
- // Your other entities
167
- // UserEntity, ProductEntity, etc.
168
-
169
- // Dynamic entities from CrudConfigModule
170
- sectionEntity,
171
- dataEntity,
172
- ],
173
- synchronize: true,
174
- }),
175
- }),
176
- ],
99
+ imports: [
100
+ // First register CrudConfigModule to create dynamic entities
101
+ CrudConfigModule.register({
102
+ environment: "development",
103
+ isVerbose: true,
104
+ shouldAutoCreateSections: true,
105
+
106
+ // Cache configuration
107
+ cacheOptions: {
108
+ isEnabled: true,
109
+ maxCacheItems: 1000,
110
+ maxCacheTTL: 3600000, // 1 hour in milliseconds
111
+ },
112
+
113
+ // Encryption configuration
114
+ encryptionOptions: {
115
+ isEnabled: true,
116
+ encryptionKey: process.env.CONFIG_ENCRYPTION_KEY, // 32+ character key
117
+ },
118
+
119
+ // Controller configuration (optional)
120
+ controllersOptions: {
121
+ section: {
122
+ isEnabled: true,
123
+ properties: {
124
+ path: "api/config/sections",
125
+ },
126
+ },
127
+ data: {
128
+ isEnabled: true,
129
+ properties: {
130
+ path: "api/config/data",
131
+ },
132
+ },
133
+ },
134
+
135
+ // Entity customization
136
+ entityOptions: {
137
+ tablePrefix: "app_",
138
+ configSection: {
139
+ tableName: "config_sections",
140
+ maxNameLength: 128,
141
+ maxDescriptionLength: 512,
142
+ },
143
+ configData: {
144
+ tableName: "config_data",
145
+ maxValueLength: 8192,
146
+ maxEnvironmentLength: 64,
147
+ maxNameLength: 128,
148
+ maxDescriptionLength: 512,
149
+ },
150
+ },
151
+ }),
152
+
153
+ // Then register TypeORM with dynamic entities using registerAsync
154
+ TypeOrmModule.forRootAsync({
155
+ imports: [CrudConfigModule],
156
+ inject: [TOKEN_CONSTANT.CONFIG_SECTION_ENTITY, TOKEN_CONSTANT.CONFIG_DATA_ENTITY],
157
+ useFactory: async (sectionEntity, dataEntity) => ({
158
+ type: "postgres",
159
+ host: "localhost",
160
+ port: 5432,
161
+ username: "postgres",
162
+ password: "postgres",
163
+ database: "config_db",
164
+ entities: [
165
+ // Your other entities
166
+ // UserEntity, ProductEntity, etc.
167
+
168
+ // Dynamic entities from CrudConfigModule
169
+ sectionEntity,
170
+ dataEntity,
171
+ ],
172
+ synchronize: true,
173
+ }),
174
+ }),
175
+ ],
177
176
  })
178
177
  export class AppModule {}
179
178
  ```
@@ -182,95 +181,446 @@ export class AppModule {}
182
181
 
183
182
  ```typescript
184
183
  // config.service.ts
185
- import { Injectable } from '@nestjs/common';
186
- import { CrudConfigService } from '@elsikora/nestjs-crud-config';
184
+ import { Injectable } from "@nestjs/common";
185
+ import { CrudConfigService } from "@elsikora/nestjs-crud-config";
187
186
 
188
187
  @Injectable()
189
188
  export class MyConfigService {
190
- constructor(
191
- private readonly configService: CrudConfigService,
192
- ) {}
193
-
194
- async setupApplicationConfig() {
195
- // Set a configuration value
196
- await this.configService.set({
197
- section: 'api-settings',
198
- name: 'API_KEY',
199
- value: 'my-secret-api-key',
200
- description: 'Production API key',
201
- environment: 'production',
202
- });
189
+ constructor(private readonly configService: CrudConfigService) {}
190
+
191
+ async setupApplicationConfig() {
192
+ // Set a configuration value
193
+ await this.configService.set({
194
+ section: "api-settings",
195
+ name: "API_KEY",
196
+ value: "my-secret-api-key",
197
+ description: "Production API key",
198
+ environment: "production",
199
+ });
200
+
201
+ // Retrieve configuration by section and name
202
+ const apiConfig = await this.configService.get({
203
+ section: "api-settings",
204
+ name: "API_KEY",
205
+ environment: "production",
206
+ shouldLoadSectionInfo: true,
207
+ useCache: true,
208
+ });
209
+
210
+ console.log("API Configuration:", apiConfig);
211
+ // apiConfig.value will be automatically decrypted if it was encrypted
212
+
213
+ return apiConfig;
214
+ }
215
+
216
+ async getConfigurationList() {
217
+ // Get all configurations in a section
218
+ const configs = await this.configService.getList({
219
+ section: "api-settings",
220
+ environment: "production",
221
+ useCache: true,
222
+ });
223
+
224
+ return configs;
225
+ }
226
+
227
+ async deleteConfiguration() {
228
+ // Delete a configuration
229
+ await this.configService.delete({
230
+ section: "api-settings",
231
+ name: "API_KEY",
232
+ environment: "production",
233
+ });
234
+ }
235
+ }
236
+ ```
203
237
 
204
- // Retrieve configuration by section and name
205
- const apiConfig = await this.configService.get({
206
- section: 'api-settings',
207
- name: 'API_KEY',
208
- environment: 'production',
209
- shouldLoadSectionInfo: true,
210
- useCache: true,
211
- });
238
+ ### Async Module Registration
212
239
 
213
- console.log('API Configuration:', apiConfig);
214
- // apiConfig.value will be automatically decrypted if it was encrypted
215
-
216
- return apiConfig;
217
- }
240
+ ```typescript
241
+ // app.module.ts
242
+ import { Module } from "@nestjs/common";
243
+ import { ConfigModule, ConfigService } from "@nestjs/config";
244
+ import { CrudConfigModule } from "@elsikora/nestjs-crud-config";
218
245
 
219
- async getConfigurationList() {
220
- // Get all configurations in a section
221
- const configs = await this.configService.getList({
222
- section: 'api-settings',
223
- environment: 'production',
224
- useCache: true,
225
- });
246
+ @Module({
247
+ imports: [
248
+ ConfigModule.forRoot(),
249
+ CrudConfigModule.registerAsync({
250
+ imports: [ConfigModule],
251
+ inject: [ConfigService],
252
+ useFactory: async (configService: ConfigService) => ({
253
+ environment: configService.get("NODE_ENV", "development"),
254
+ isVerbose: configService.get("VERBOSE", false),
255
+ encryptionOptions: {
256
+ isEnabled: true,
257
+ encryptionKey: configService.get("ENCRYPTION_KEY"),
258
+ },
259
+ cacheOptions: {
260
+ isEnabled: true,
261
+ maxCacheItems: 1000,
262
+ maxCacheTTL: 3600000,
263
+ },
264
+ }),
265
+ }),
266
+ ],
267
+ })
268
+ export class AppModule {}
269
+ ```
226
270
 
227
- return configs;
228
- }
271
+ ## 🚀 Migration System
229
272
 
230
- async deleteConfiguration() {
231
- // Delete a configuration
232
- await this.configService.delete({
233
- section: 'api-settings',
234
- name: 'API_KEY',
235
- environment: 'production',
236
- });
237
- }
238
- }
273
+ The NestJS CRUD Config module includes a powerful **production-ready migration system** that allows you to initialize and manage configuration state during application startup. This system ensures your application always has the required configuration data across different environments and deployments.
274
+
275
+ ### Key Features
276
+
277
+ - **🔄 Automatic execution on startup** - Migrations run automatically when the application bootstraps
278
+ - **💾 Transaction support** - All migrations run within database transactions for data consistency
279
+ - **🌍 Environment-specific configurations** - Create different configurations for different environments
280
+ - **📊 Migration tracking** - Complete audit trail with timestamps and execution status
281
+ - **🔙 Rollback support** - Define rollback procedures for each migration
282
+ - **⚡ Stuck migration cleanup** - Automatic timeout and cleanup of crashed migrations
283
+ - **🎯 Type-safe status tracking** - Uses `EConfigMigrationStatus` enum for reliable status management
284
+ - **🛡️ Race condition protection** - Thread-safe execution preventing concurrent conflicts
285
+
286
+ ### Migration Status Enum
287
+
288
+ The migration system uses the `EConfigMigrationStatus` enum to track migration states:
289
+
290
+ ```typescript
291
+ import { EConfigMigrationStatus } from "@your-org/nestjs-crud-config";
292
+
293
+ // Available statuses:
294
+ EConfigMigrationStatus.PENDING; // Migration is pending execution
295
+ EConfigMigrationStatus.RUNNING; // Migration is currently running
296
+ EConfigMigrationStatus.COMPLETED; // Migration completed successfully
297
+ EConfigMigrationStatus.FAILED; // Migration failed with error
298
+ EConfigMigrationStatus.STUCK; // Migration is stuck (timeout)
239
299
  ```
240
300
 
241
- ### Async Module Registration
301
+ ### Basic Migration Configuration
302
+
303
+ Configure migrations in your module registration:
242
304
 
243
305
  ```typescript
244
- // app.module.ts
245
- import { Module } from '@nestjs/common';
246
- import { ConfigModule, ConfigService } from '@nestjs/config';
247
- import { CrudConfigModule } from '@elsikora/nestjs-crud-config';
306
+ import { CrudConfigModule } from "@your-org/nestjs-crud-config";
307
+ import { initialAppConfigMigration } from "./migrations/001-initial-app-config.migration";
248
308
 
249
309
  @Module({
250
- imports: [
251
- ConfigModule.forRoot(),
252
- CrudConfigModule.registerAsync({
253
- imports: [ConfigModule],
254
- inject: [ConfigService],
255
- useFactory: async (configService: ConfigService) => ({
256
- environment: configService.get('NODE_ENV', 'development'),
257
- isVerbose: configService.get('VERBOSE', false),
258
- encryptionOptions: {
259
- isEnabled: true,
260
- encryptionKey: configService.get('ENCRYPTION_KEY'),
261
- },
262
- cacheOptions: {
263
- isEnabled: true,
264
- maxCacheItems: 1000,
265
- maxCacheTTL: 3600000,
266
- },
267
- }),
268
- }),
269
- ],
310
+ imports: [
311
+ CrudConfigModule.register({
312
+ environment: process.env.NODE_ENV || "development",
313
+
314
+ // Migration configuration
315
+ migrationOptions: {
316
+ isEnabled: true,
317
+ shouldRunOnStartup: true,
318
+ useTransaction: true,
319
+ stuckMigrationTimeoutMinutes: 30,
320
+ tableName: "config_migrations", // Optional: custom table name
321
+
322
+ migrations: [
323
+ initialAppConfigMigration,
324
+ // Add more migrations here
325
+ ],
326
+ },
327
+
328
+ // Other configuration options...
329
+ }),
330
+ ],
270
331
  })
271
332
  export class AppModule {}
272
333
  ```
273
334
 
335
+ ### Creating Migration Files
336
+
337
+ Create a migration file with the required structure:
338
+
339
+ ```typescript
340
+ // migrations/001-initial-app-config.migration.ts
341
+ import type { IConfigMigrationDefinition } from "@your-org/nestjs-crud-config";
342
+ import type { CrudConfigService } from "@your-org/nestjs-crud-config";
343
+ import type { EntityManager } from "typeorm";
344
+
345
+ export const initialAppConfigMigration: IConfigMigrationDefinition = {
346
+ name: "001_initial_app_config",
347
+ description: "Initialize basic application configuration",
348
+
349
+ async up(configService: CrudConfigService, entityManager?: EntityManager): Promise<void> {
350
+ // Create application settings
351
+ await configService.set({
352
+ section: "app-settings",
353
+ name: "APP_NAME",
354
+ value: "My Application",
355
+ description: "Application name",
356
+ environment: "default",
357
+ eventManager: entityManager,
358
+ });
359
+
360
+ await configService.set({
361
+ section: "app-settings",
362
+ name: "APP_VERSION",
363
+ value: "1.0.0",
364
+ description: "Application version",
365
+ environment: "default",
366
+ eventManager: entityManager,
367
+ });
368
+
369
+ // Create API settings
370
+ await configService.set({
371
+ section: "api-settings",
372
+ name: "API_RATE_LIMIT",
373
+ value: "100",
374
+ description: "API rate limit per minute",
375
+ environment: "default",
376
+ eventManager: entityManager,
377
+ });
378
+ },
379
+
380
+ async down(configService: CrudConfigService, entityManager?: EntityManager): Promise<void> {
381
+ // Rollback: remove configurations created by this migration
382
+ const configurationsToRemove = [
383
+ { name: "APP_NAME", section: "app-settings" },
384
+ { name: "APP_VERSION", section: "app-settings" },
385
+ { name: "API_RATE_LIMIT", section: "api-settings" },
386
+ ];
387
+
388
+ for (const config of configurationsToRemove) {
389
+ try {
390
+ await configService.delete({
391
+ section: config.section,
392
+ name: config.name,
393
+ environment: "default",
394
+ eventManager: entityManager,
395
+ });
396
+ } catch (error) {
397
+ // Expected if config was never created
398
+ console.warn(`Failed to delete config ${config.section}:${config.name} during rollback`);
399
+ }
400
+ }
401
+ },
402
+ };
403
+ ```
404
+
405
+ ### Environment-Specific Migrations
406
+
407
+ Create migrations that work with different environments:
408
+
409
+ ```typescript
410
+ // migrations/002-environment-specific-config.migration.ts
411
+ export const environmentSpecificConfigMigration: IConfigMigrationDefinition = {
412
+ name: "002_environment_specific_config",
413
+ description: "Environment-specific configuration settings",
414
+
415
+ async up(configService: CrudConfigService, entityManager?: EntityManager): Promise<void> {
416
+ const environments = ["development", "staging", "production"];
417
+
418
+ for (const env of environments) {
419
+ // Environment-specific debug settings
420
+ await configService.set({
421
+ section: "app-settings",
422
+ name: "DEBUG_MODE",
423
+ value: env === "development" ? "true" : "false",
424
+ description: "Enable debug mode",
425
+ environment: env,
426
+ eventManager: entityManager,
427
+ });
428
+
429
+ // Environment-specific API limits
430
+ const rateLimits = {
431
+ development: "1000",
432
+ staging: "500",
433
+ production: "100",
434
+ };
435
+
436
+ await configService.set({
437
+ section: "api-settings",
438
+ name: "API_RATE_LIMIT",
439
+ value: rateLimits[env],
440
+ description: "API rate limit per minute",
441
+ environment: env,
442
+ eventManager: entityManager,
443
+ });
444
+ }
445
+ },
446
+
447
+ async down(configService: CrudConfigService, entityManager?: EntityManager): Promise<void> {
448
+ const environments = ["development", "staging", "production"];
449
+ const configurationsToRemove = [
450
+ { name: "DEBUG_MODE", section: "app-settings" },
451
+ { name: "API_RATE_LIMIT", section: "api-settings" },
452
+ ];
453
+
454
+ for (const config of configurationsToRemove) {
455
+ for (const environment of environments) {
456
+ try {
457
+ await configService.delete({
458
+ section: config.section,
459
+ name: config.name,
460
+ environment: environment,
461
+ eventManager: entityManager,
462
+ });
463
+ } catch (error) {
464
+ console.warn(
465
+ `Failed to delete config ${config.section}:${config.name}:${environment} during rollback`,
466
+ );
467
+ }
468
+ }
469
+ }
470
+ },
471
+ };
472
+ ```
473
+
474
+ ### Advanced Migration Configuration
475
+
476
+ Configure migrations with custom options:
477
+
478
+ ```typescript
479
+ CrudConfigModule.registerAsync({
480
+ useFactory: async (configService: ConfigService) => ({
481
+ environment: configService.get("NODE_ENV", "development"),
482
+
483
+ migrationOptions: {
484
+ isEnabled: true,
485
+ shouldRunOnStartup: true,
486
+ useTransaction: true,
487
+ stuckMigrationTimeoutMinutes: 60, // Custom timeout
488
+ tableName: "my_migrations", // Custom table name
489
+
490
+ migrations: [
491
+ initialAppConfigMigration,
492
+ environmentSpecificConfigMigration,
493
+ // Add more migrations in order
494
+ ],
495
+ },
496
+
497
+ // Other options...
498
+ }),
499
+ inject: [ConfigService],
500
+ });
501
+ ```
502
+
503
+ ### Migration Best Practices
504
+
505
+ 1. **Naming Convention**: Use descriptive names with sequence numbers
506
+ - ✅ `001_initial_app_config`
507
+ - ✅ `002_add_feature_flags`
508
+ - ❌ `migration1`, `config_setup`
509
+
510
+ 2. **Idempotency**: Migrations should be safe to run multiple times
511
+
512
+ ```typescript
513
+ // Good: Check if configuration exists before creating
514
+ const existing = await configService.get({
515
+ section: 'app-settings',
516
+ name: 'APP_NAME',
517
+ environment: 'default',
518
+ }).catch(() => null);
519
+
520
+ if (!existing) {
521
+ await configService.set({...});
522
+ }
523
+ ```
524
+
525
+ 3. **Error Handling**: Always handle errors gracefully
526
+
527
+ ```typescript
528
+ try {
529
+ await configService.set({...});
530
+ } catch (error) {
531
+ console.error('Migration failed:', error);
532
+ throw error; // Re-throw to mark migration as failed
533
+ }
534
+ ```
535
+
536
+ 4. **Rollback Support**: Always implement the `down` method
537
+
538
+ ```typescript
539
+ async down(configService: CrudConfigService, entityManager?: EntityManager): Promise<void> {
540
+ // Implement rollback logic
541
+ }
542
+ ```
543
+
544
+ 5. **Environment Awareness**: Use environment-specific configurations
545
+ ```typescript
546
+ const currentEnv = process.env.NODE_ENV || "development";
547
+ await configService.set({
548
+ environment: currentEnv,
549
+ // ... other options
550
+ });
551
+ ```
552
+
553
+ ### Migration Monitoring
554
+
555
+ Monitor migration execution with logging:
556
+
557
+ ```typescript
558
+ import { EConfigMigrationStatus } from "@your-org/nestjs-crud-config";
559
+
560
+ @Injectable()
561
+ export class MigrationMonitorService {
562
+ constructor(private readonly migrationService: ConfigMigrationService) {}
563
+
564
+ async getMigrationStatus(): Promise<void> {
565
+ const migrations = await this.migrationService.getExecutedMigrations();
566
+
567
+ const completed = migrations.filter((m) => m.status === EConfigMigrationStatus.COMPLETED);
568
+ const failed = migrations.filter((m) => m.status === EConfigMigrationStatus.FAILED);
569
+ const stuck = migrations.filter((m) => m.status === EConfigMigrationStatus.STUCK);
570
+
571
+ console.log(
572
+ `Migrations - Completed: ${completed.length}, Failed: ${failed.length}, Stuck: ${stuck.length}`,
573
+ );
574
+ }
575
+
576
+ async getFailedMigrations(): Promise<IConfigMigration[]> {
577
+ const migrations = await this.migrationService.getExecutedMigrations();
578
+ return migrations.filter((m) => m.status === EConfigMigrationStatus.FAILED);
579
+ }
580
+ }
581
+ ```
582
+
583
+ ### Testing Migrations
584
+
585
+ Test your migrations in different environments:
586
+
587
+ ```typescript
588
+ describe("Initial App Config Migration", () => {
589
+ let configService: CrudConfigService;
590
+ let migration: IConfigMigrationDefinition;
591
+
592
+ beforeEach(async () => {
593
+ // Setup test environment
594
+ migration = initialAppConfigMigration;
595
+ });
596
+
597
+ it("should create required configurations", async () => {
598
+ await migration.up(configService);
599
+
600
+ const appName = await configService.get({
601
+ section: "app-settings",
602
+ name: "APP_NAME",
603
+ environment: "default",
604
+ });
605
+
606
+ expect(appName.value).toBe("My Application");
607
+ });
608
+
609
+ it("should rollback configurations", async () => {
610
+ await migration.up(configService);
611
+ await migration.down(configService);
612
+
613
+ await expect(
614
+ configService.get({
615
+ section: "app-settings",
616
+ name: "APP_NAME",
617
+ environment: "default",
618
+ }),
619
+ ).rejects.toThrow();
620
+ });
621
+ });
622
+ ```
623
+
274
624
  ## 📚 API Documentation
275
625
 
276
626
  The module automatically generates REST API endpoints with customizable paths:
@@ -284,10 +634,11 @@ The module automatically generates REST API endpoints with customizable paths:
284
634
  - `DELETE /config/section/:id` - Delete section
285
635
 
286
636
  **Request Body for POST/PUT:**
637
+
287
638
  ```json
288
639
  {
289
- "name": "api-settings",
290
- "description": "API configuration settings"
640
+ "name": "api-settings",
641
+ "description": "API configuration settings"
291
642
  }
292
643
  ```
293
644
 
@@ -300,14 +651,15 @@ The module automatically generates REST API endpoints with customizable paths:
300
651
  - `DELETE /config/data/:id` - Delete configuration
301
652
 
302
653
  **Request Body for POST/PUT:**
654
+
303
655
  ```json
304
656
  {
305
- "name": "API_KEY",
306
- "value": "your-secret-key",
307
- "environment": "production",
308
- "description": "Production API key",
309
- "isEncrypted": true,
310
- "section": { "id": "section-uuid-here" }
657
+ "name": "API_KEY",
658
+ "value": "your-secret-key",
659
+ "environment": "production",
660
+ "description": "Production API key",
661
+ "isEncrypted": true,
662
+ "section": { "id": "section-uuid-here" }
311
663
  }
312
664
  ```
313
665
 
@@ -315,26 +667,26 @@ The module automatically generates REST API endpoints with customizable paths:
315
667
 
316
668
  ```typescript
317
669
  CrudConfigModule.register({
318
- controllersOptions: {
319
- section: {
320
- properties: {
321
- path: 'api/v1/settings/sections',
322
- name: 'CustomSectionController',
323
- swagger: {
324
- tags: ['Configuration Sections'],
325
- },
326
- },
670
+ controllersOptions: {
671
+ section: {
672
+ properties: {
673
+ path: "api/v1/settings/sections",
674
+ name: "CustomSectionController",
675
+ swagger: {
676
+ tags: ["Configuration Sections"],
327
677
  },
328
- data: {
329
- properties: {
330
- path: 'api/v1/settings/data',
331
- routes: {
332
- DELETE: { isEnabled: false }, // Disable deletion
333
- },
334
- },
678
+ },
679
+ },
680
+ data: {
681
+ properties: {
682
+ path: "api/v1/settings/data",
683
+ routes: {
684
+ DELETE: { isEnabled: false }, // Disable deletion
335
685
  },
686
+ },
336
687
  },
337
- })
688
+ },
689
+ });
338
690
  ```
339
691
 
340
692
  ## 🗄️ Database Schema
@@ -342,6 +694,7 @@ CrudConfigModule.register({
342
694
  The module creates two main tables with the following default structure:
343
695
 
344
696
  ### Configuration Sections Table (`config_section`)
697
+
345
698
  ```sql
346
699
  CREATE TABLE config_section (
347
700
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
@@ -353,6 +706,7 @@ CREATE TABLE config_section (
353
706
  ```
354
707
 
355
708
  ### Configuration Data Table (`config_data`)
709
+
356
710
  ```sql
357
711
  CREATE TABLE config_data (
358
712
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
@@ -377,23 +731,23 @@ The module provides built-in AES-256-GCM encryption for sensitive configuration
377
731
  ```typescript
378
732
  // Enable encryption globally
379
733
  CrudConfigModule.register({
380
- encryptionOptions: {
381
- isEnabled: true,
382
- encryptionKey: process.env.CONFIG_ENCRYPTION_KEY, // 32+ character key
383
- },
384
- })
734
+ encryptionOptions: {
735
+ isEnabled: true,
736
+ encryptionKey: process.env.CONFIG_ENCRYPTION_KEY, // 32+ character key
737
+ },
738
+ });
385
739
 
386
740
  // When encryption is enabled, all values are encrypted automatically
387
741
  await configService.set({
388
- section: 'database',
389
- name: 'DB_PASSWORD',
390
- value: 'my-secret-password',
742
+ section: "database",
743
+ name: "DB_PASSWORD",
744
+ value: "my-secret-password",
391
745
  });
392
746
 
393
747
  // Retrieve and decrypt automatically
394
748
  const config = await configService.get({
395
- section: 'database',
396
- name: 'DB_PASSWORD',
749
+ section: "database",
750
+ name: "DB_PASSWORD",
397
751
  });
398
752
 
399
753
  console.log(config.value); // Automatically decrypted value
@@ -408,24 +762,24 @@ The module includes comprehensive event handling with both listeners and TypeORM
408
762
  // Event listener example
409
763
  @Injectable()
410
764
  export class ConfigDataBeforeInsertListener {
411
- @OnEvent('config-data.beforeInsert')
412
- async handleBeforeInsert(payload: ConfigDataEventBeforeInsert) {
413
- // Custom validation logic
414
- const entity = payload.item;
415
- const entityManager = payload.eventManager;
416
-
417
- // Perform custom checks
418
- return { isSuccess: true };
419
- }
765
+ @OnEvent("config-data.beforeInsert")
766
+ async handleBeforeInsert(payload: ConfigDataEventBeforeInsert) {
767
+ // Custom validation logic
768
+ const entity = payload.item;
769
+ const entityManager = payload.eventManager;
770
+
771
+ // Perform custom checks
772
+ return { isSuccess: true };
773
+ }
420
774
  }
421
775
 
422
776
  // TypeORM subscriber for database-level events
423
777
  @EventSubscriber()
424
778
  export class ConfigDataBeforeInsertSubscriber {
425
- beforeInsert(event: InsertEvent<IConfigData>) {
426
- // Database-level validation
427
- return Promise.resolve(true);
428
- }
779
+ beforeInsert(event: InsertEvent<IConfigData>) {
780
+ // Database-level validation
781
+ return Promise.resolve(true);
782
+ }
429
783
  }
430
784
  ```
431
785
 
@@ -433,14 +787,15 @@ export class ConfigDataBeforeInsertSubscriber {
433
787
 
434
788
  ```typescript
435
789
  CrudConfigModule.register({
436
- controllersOptions: {
437
- section: { isEnabled: false },
438
- data: { isEnabled: false },
439
- },
440
- })
790
+ controllersOptions: {
791
+ section: { isEnabled: false },
792
+ data: { isEnabled: false },
793
+ },
794
+ });
441
795
  ```
442
796
 
443
797
  This is perfect for:
798
+
444
799
  - Background services that only need programmatic access
445
800
  - Microservices that manage config through message queues
446
801
  - Applications with custom GraphQL or gRPC interfaces
@@ -451,62 +806,61 @@ This is perfect for:
451
806
  // multi-env.service.ts
452
807
  @Injectable()
453
808
  export class MultiEnvironmentService {
454
- constructor(
455
- private readonly configService: CrudConfigService,
456
- ) {}
457
-
458
- async setupEnvironmentConfigs() {
459
- const environments = ['development', 'staging', 'production'];
460
-
461
- for (const env of environments) {
462
- await this.configService.set({
463
- section: 'database',
464
- name: 'DATABASE_URL',
465
- environment: env,
466
- value: `postgres://localhost:5432/${env}_db`,
467
- description: `Database URL for ${env} environment`,
468
- });
469
- }
809
+ constructor(private readonly configService: CrudConfigService) {}
810
+
811
+ async setupEnvironmentConfigs() {
812
+ const environments = ["development", "staging", "production"];
813
+
814
+ for (const env of environments) {
815
+ await this.configService.set({
816
+ section: "database",
817
+ name: "DATABASE_URL",
818
+ environment: env,
819
+ value: `postgres://localhost:5432/${env}_db`,
820
+ description: `Database URL for ${env} environment`,
821
+ });
470
822
  }
823
+ }
471
824
  }
472
825
  ```
473
826
 
474
827
  ## 🛣 Roadmap
475
828
 
476
- | Task / Feature | Status |
477
- |---|---|
478
- | Core dynamic entity system | ✅ Done |
479
- | TypeORM integration with all databases | ✅ Done |
480
- | Hierarchical configuration (sections/data) | ✅ Done |
481
- | Full CRUD operations with REST API | ✅ Done |
482
- | Swagger/OpenAPI documentation | ✅ Done |
483
- | Multi-environment support | ✅ Done |
484
- | Event-driven architecture with hooks | ✅ Done |
485
- | Caching system with TTL | ✅ Done |
486
- | Custom table names and prefixes | ✅ Done |
487
- | Validation and constraints | ✅ Done |
488
- | NestJS CRUD Automator integration | ✅ Done |
489
- | TypeScript interfaces and types | ✅ Done |
490
- | AES-256-GCM encryption support | ✅ Done |
491
- | Async module registration | ✅ Done |
492
- | Auto-section creation | ✅ Done |
493
- | GraphQL API endpoints | 🚧 In Progress |
494
- | Configuration versioning and history | 🚧 In Progress |
495
- | Role-based access control (RBAC) | 🚧 In Progress |
496
- | Configuration templates and inheritance | 🚧 In Progress |
829
+ | Task / Feature | Status |
830
+ | --------------------------------------------- | -------------- |
831
+ | Core dynamic entity system | ✅ Done |
832
+ | TypeORM integration with all databases | ✅ Done |
833
+ | Hierarchical configuration (sections/data) | ✅ Done |
834
+ | Full CRUD operations with REST API | ✅ Done |
835
+ | Swagger/OpenAPI documentation | ✅ Done |
836
+ | Multi-environment support | ✅ Done |
837
+ | Event-driven architecture with hooks | ✅ Done |
838
+ | Caching system with TTL | ✅ Done |
839
+ | Custom table names and prefixes | ✅ Done |
840
+ | Validation and constraints | ✅ Done |
841
+ | NestJS CRUD Automator integration | ✅ Done |
842
+ | TypeScript interfaces and types | ✅ Done |
843
+ | AES-256-GCM encryption support | ✅ Done |
844
+ | Async module registration | ✅ Done |
845
+ | Auto-section creation | ✅ Done |
846
+ | GraphQL API endpoints | 🚧 In Progress |
847
+ | Configuration versioning and history | 🚧 In Progress |
848
+ | Role-based access control (RBAC) | 🚧 In Progress |
849
+ | Configuration templates and inheritance | 🚧 In Progress |
497
850
  | Real-time configuration updates via WebSocket | 🚧 In Progress |
498
- | Configuration validation schemas | 🚧 In Progress |
499
- | Bulk import/export functionality | 🚧 In Progress |
500
- | Configuration diff and merge tools | 🚧 In Progress |
501
- | Audit logging and change tracking | 🚧 In Progress |
502
- | Configuration backup and restore | 🚧 In Progress |
503
- | Integration with external secret managers | 🚧 In Progress |
851
+ | Configuration validation schemas | 🚧 In Progress |
852
+ | Bulk import/export functionality | 🚧 In Progress |
853
+ | Configuration diff and merge tools | 🚧 In Progress |
854
+ | Audit logging and change tracking | 🚧 In Progress |
855
+ | Configuration backup and restore | 🚧 In Progress |
856
+ | Integration with external secret managers | 🚧 In Progress |
504
857
 
505
858
  ## ❓ FAQ
506
859
 
507
860
  ### What databases are supported?
508
861
 
509
862
  The module supports any database that TypeORM supports, including:
863
+
510
864
  - **PostgreSQL** - Recommended for production
511
865
  - **MySQL/MariaDB** - Popular choice for web applications
512
866
  - **SQLite** - Perfect for development and testing
@@ -518,6 +872,7 @@ The module supports any database that TypeORM supports, including:
518
872
  ### How does this compare to environment variables?
519
873
 
520
874
  While environment variables are great for simple configurations, this module provides:
875
+
521
876
  - **Database persistence** - Configurations survive container restarts
522
877
  - **Runtime updates** - Change configurations without redeployment
523
878
  - **Hierarchical organization** - Group related configurations
@@ -532,15 +887,15 @@ Yes! You can easily migrate by programmatically setting configurations during ap
532
887
 
533
888
  ```typescript
534
889
  async function migrateFromEnvVars() {
535
- const configs = [
536
- { section: 'database', name: 'DATABASE_URL', value: process.env.DATABASE_URL },
537
- { section: 'api', name: 'API_KEY', value: process.env.API_KEY },
538
- // ... more configurations
539
- ];
540
-
541
- for (const config of configs) {
542
- await configService.set(config);
543
- }
890
+ const configs = [
891
+ { section: "database", name: "DATABASE_URL", value: process.env.DATABASE_URL },
892
+ { section: "api", name: "API_KEY", value: process.env.API_KEY },
893
+ // ... more configurations
894
+ ];
895
+
896
+ for (const config of configs) {
897
+ await configService.set(config);
898
+ }
544
899
  }
545
900
  ```
546
901
 
@@ -550,11 +905,11 @@ The module provides built-in AES-256-GCM encryption support:
550
905
 
551
906
  ```typescript
552
907
  CrudConfigModule.register({
553
- encryptionOptions: {
554
- isEnabled: true,
555
- encryptionKey: process.env.CONFIG_ENCRYPTION_KEY,
556
- },
557
- })
908
+ encryptionOptions: {
909
+ isEnabled: true,
910
+ encryptionKey: process.env.CONFIG_ENCRYPTION_KEY,
911
+ },
912
+ });
558
913
  ```
559
914
 
560
915
  Sensitive values are automatically encrypted before storage and decrypted when retrieved.
@@ -562,6 +917,7 @@ Sensitive values are automatically encrypted before storage and decrypted when r
562
917
  ### Can I use this with microservices?
563
918
 
564
919
  Absolutely! The module is perfect for microservice architectures:
920
+
565
921
  - **Centralized configuration** - All services can share the same config database
566
922
  - **Service-specific sections** - Organize configurations by service
567
923
  - **Environment isolation** - Separate dev/staging/prod configurations
@@ -573,27 +929,28 @@ The module provides extensive customization options:
573
929
 
574
930
  ```typescript
575
931
  CrudConfigModule.register({
576
- entityOptions: {
577
- tablePrefix: 'myapp_',
578
- configSection: {
579
- tableName: 'configuration_sections',
580
- maxNameLength: 256,
581
- maxDescriptionLength: 1024,
582
- },
583
- configData: {
584
- tableName: 'configuration_values',
585
- maxValueLength: 16384, // 16KB values
586
- maxEnvironmentLength: 128,
587
- maxNameLength: 256,
588
- maxDescriptionLength: 1024,
589
- },
932
+ entityOptions: {
933
+ tablePrefix: "myapp_",
934
+ configSection: {
935
+ tableName: "configuration_sections",
936
+ maxNameLength: 256,
937
+ maxDescriptionLength: 1024,
590
938
  },
591
- })
939
+ configData: {
940
+ tableName: "configuration_values",
941
+ maxValueLength: 16384, // 16KB values
942
+ maxEnvironmentLength: 128,
943
+ maxNameLength: 256,
944
+ maxDescriptionLength: 1024,
945
+ },
946
+ },
947
+ });
592
948
  ```
593
949
 
594
950
  ### What happens if the database is unavailable?
595
951
 
596
952
  The module includes caching to handle temporary database outages:
953
+
597
954
  - **In-memory cache** - Recently accessed configurations are cached
598
955
  - **Configurable TTL** - Control how long configurations are cached
599
956
  - **Graceful degradation** - Falls back to cached values when database is unavailable
@@ -604,16 +961,17 @@ Yes! The module supports "headless mode" where controllers are disabled:
604
961
 
605
962
  ```typescript
606
963
  CrudConfigModule.register({
607
- controllersOptions: {
608
- section: { isEnabled: false },
609
- data: { isEnabled: false },
610
- },
611
- })
964
+ controllersOptions: {
965
+ section: { isEnabled: false },
966
+ data: { isEnabled: false },
967
+ },
968
+ });
612
969
  ```
613
970
 
614
971
  ### Is this production-ready?
615
972
 
616
973
  Yes! The module is built with production use in mind:
974
+
617
975
  - **Type-safe** - Full TypeScript support prevents runtime errors
618
976
  - **Battle-tested** - Built on proven technologies (NestJS, TypeORM)
619
977
  - **Scalable** - Works with enterprise databases