@flusys/nestjs-core 4.0.2 → 4.1.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 +462 -562
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,742 +1,642 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flusys/nestjs-core
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> **Version:** 4.0.2
|
|
5
|
-
> **Type:** Pure TypeScript foundation (zero NestJS dependencies)
|
|
3
|
+
> Foundation infrastructure for NestJS applications — type-safe environment configuration, TypeORM migration CLI, intelligent data seeding, and modular Swagger documentation.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/nestjs-core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nestjs.com/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
|
|
11
|
+
---
|
|
8
12
|
|
|
9
13
|
## Table of Contents
|
|
10
14
|
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
+
- [Overview](#overview)
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Architecture](#architecture)
|
|
18
|
+
- [Compatibility](#compatibility)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Environment Setup](#environment-setup)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [EnvConfigService](#envconfigservice)
|
|
23
|
+
- [Migration CLI](#migration-cli)
|
|
24
|
+
- [Migration Config File](#migration-config-file)
|
|
25
|
+
- [CLI Commands](#cli-commands)
|
|
26
|
+
- [Programmatic API](#programmatic-api)
|
|
15
27
|
- [Seed System](#seed-system)
|
|
16
|
-
- [
|
|
28
|
+
- [BaseSeeder](#baseseeder)
|
|
29
|
+
- [SeedRunner](#seedrunner)
|
|
30
|
+
- [DataGenerator](#datagenerator)
|
|
31
|
+
- [Seed CLI](#seed-cli)
|
|
17
32
|
- [Swagger Documentation](#swagger-documentation)
|
|
18
|
-
- [
|
|
33
|
+
- [setupSwaggerDocs](#setupswaggerdocs)
|
|
34
|
+
- [setupModuleSwaggerDocs](#setupmoduleswaggerdocs)
|
|
35
|
+
- [Database Utilities](#database-utilities)
|
|
36
|
+
- [Core Interfaces](#core-interfaces)
|
|
37
|
+
- [Injection Tokens & Constants](#injection-tokens--constants)
|
|
38
|
+
- [Multi-Tenant Support](#multi-tenant-support)
|
|
39
|
+
- [Troubleshooting](#troubleshooting)
|
|
40
|
+
- [License](#license)
|
|
19
41
|
|
|
20
42
|
---
|
|
21
43
|
|
|
22
|
-
##
|
|
44
|
+
## Overview
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
│ └── env-config.service.ts # Environment configuration singleton
|
|
28
|
-
├── constants/
|
|
29
|
-
│ └── database.constants.ts # MODULE_OPTIONS, DEFAULT_TENANT_HEADER
|
|
30
|
-
├── interfaces/
|
|
31
|
-
│ ├── app-config.interfaces.ts # IBootstrapAppConfig, IDynamicModuleConfig
|
|
32
|
-
│ ├── base-entity.interface.ts # IBaseEntity, ISoftDeletable, ITimestampable
|
|
33
|
-
│ ├── database.interface.ts # IDatabaseConfig, ITenantDatabaseConfig, IDataSourceBuildOptions
|
|
34
|
-
│ └── migration.interface.ts # IMigrationConfig, IMigrationResult
|
|
35
|
-
├── utils/
|
|
36
|
-
│ └── datasource-config.builder.ts # DataSource utilities
|
|
37
|
-
├── migration/
|
|
38
|
-
│ ├── cli.ts # CLI entry point
|
|
39
|
-
│ ├── migration.cli.ts # Migration CLI commands
|
|
40
|
-
│ ├── datasource.factory.ts # DataSource factory
|
|
41
|
-
│ └── migration.runner.ts # Migration execution
|
|
42
|
-
├── seeders/
|
|
43
|
-
│ ├── cli.ts # Seed CLI entry point
|
|
44
|
-
│ ├── base-seeder.ts # Abstract base seeder class
|
|
45
|
-
│ ├── seed-runner.ts # Seed orchestration
|
|
46
|
-
│ ├── seed-config.ts # Global seed configuration
|
|
47
|
-
│ ├── entity-reader.ts # TypeORM metadata extraction
|
|
48
|
-
│ ├── data-generator.ts # Faker.js data generation
|
|
49
|
-
│ └── field-patterns.ts # Intelligent field pattern detection
|
|
50
|
-
├── docs/
|
|
51
|
-
│ └── docs.config.ts # Swagger setup utilities
|
|
52
|
-
└── index.ts
|
|
53
|
-
```
|
|
46
|
+
`@flusys/nestjs-core` is the lowest-level dependency in the FLUSYS NestJS monorepo. Every other package depends on it.
|
|
47
|
+
|
|
48
|
+
**Key design decision:** This package is **pure TypeScript with zero NestJS runtime dependencies** (except the isolated `docs/` module). The migration and seed CLIs run as standalone Node.js processes — no NestJS app bootstrap required. This makes it usable in CI pipelines, build scripts, and non-NestJS tools.
|
|
54
49
|
|
|
55
50
|
---
|
|
56
51
|
|
|
57
|
-
##
|
|
52
|
+
## Features
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
- **Type-safe environment config** — Strongly typed `process.env` access with restricted key protection and validation
|
|
55
|
+
- **Migration CLI** — Full CLI and programmatic API for TypeORM migrations, including multi-tenant (run-for-all-tenants)
|
|
56
|
+
- **Data seeder** — Metadata-driven test data generation with Faker.js, topological sort for dependency resolution, and custom seeder support
|
|
57
|
+
- **Swagger setup** — Modular API documentation with advanced filtering by tags, schemas, parameters, and examples
|
|
58
|
+
- **DataSource utilities** — TypeORM DataSource configuration builders for single and multi-tenant setups
|
|
59
|
+
- **Core interfaces** — Contract definitions (entities, config, database, migrations) used by all feature modules
|
|
60
|
+
- **SnakeNamingStrategy** — All columns use `snake_case` by default via `typeorm-naming-strategies`
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
@flusys/nestjs-core ← THIS PACKAGE (pure TypeScript)
|
|
68
|
+
↓
|
|
69
|
+
@flusys/nestjs-shared ← NestJS shared infrastructure
|
|
70
|
+
↓
|
|
71
|
+
auth | iam | storage | email | form-builder | event-manager | notification | localization
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| Method | Description |
|
|
66
|
-
|--------|-------------|
|
|
67
|
-
| `tryGetValue(key, throwOnMissing?)` | Get env value (throws for restricted keys) |
|
|
68
|
-
| `getNumber(key, throwOnMissing?)` | Get env value as number |
|
|
69
|
-
| `getBoolean(key, throwOnMissing?)` | Get env value as boolean |
|
|
70
|
-
| `getPort()` | Get PORT (default: 3000) |
|
|
71
|
-
| `isProduction()` | Check if MODE !== 'DEV' |
|
|
72
|
-
| `getFrontendUrl()` | Get FRONTEND_URL |
|
|
73
|
-
| `getOrigins()` | Get ALLOW_ORIGINS as comma-separated array |
|
|
74
|
-
| `getTypeOrmConfig()` | Get database config |
|
|
75
|
-
| `getJwtConfig()` | Get JWT config (secret, expiration, refreshSecret, refreshExpiration) |
|
|
76
|
-
| `getRedisUrl()` | Get Redis connection URL (default: redis://localhost:6379) |
|
|
77
|
-
| `getMailConfig()` | Get mail configuration (MAIL_FROM, MAIL_APP_PASSWORD) |
|
|
78
|
-
| `getTenantId()` | Get TENANT_ID |
|
|
79
|
-
| `useTenantMode()` | Check USE_TENANT_MODE |
|
|
80
|
-
| `getLogConfig()` | Get logging config `{ dir, level, maxSize, maxFiles }` |
|
|
81
|
-
| `getEnv()` | Get all env vars as `Record<string, string>` |
|
|
74
|
+
`nestjs-core` is the only FLUSYS package with no upstream FLUSYS dependencies. All other packages depend on it.
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
---
|
|
84
77
|
|
|
85
|
-
|
|
78
|
+
## Compatibility
|
|
86
79
|
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
|
|
|
80
|
+
| Package | Version |
|
|
81
|
+
|---------|---------|
|
|
82
|
+
| `typeorm` | `^0.3.0` |
|
|
83
|
+
| `typeorm-naming-strategies` | `^4.1.0` |
|
|
84
|
+
| `@faker-js/faker` | `^8.0.0` |
|
|
85
|
+
| `dotenv` | `^16.0.0` |
|
|
86
|
+
| `@nestjs/common` | `^11.0.0` *(docs/ only)* |
|
|
87
|
+
| `@nestjs/swagger` | `^8.0.0` *(docs/ only)* |
|
|
88
|
+
| Node.js | `>= 18.x` |
|
|
89
|
+
| TypeScript | `>= 5.0` |
|
|
94
90
|
|
|
95
91
|
---
|
|
96
92
|
|
|
97
|
-
##
|
|
93
|
+
## Installation
|
|
98
94
|
|
|
99
|
-
|
|
95
|
+
```bash
|
|
96
|
+
npm install @flusys/nestjs-core
|
|
97
|
+
```
|
|
100
98
|
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
```bash
|
|
100
|
+
# Peer dependencies
|
|
101
|
+
npm install typeorm typeorm-naming-strategies @faker-js/faker dotenv
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
host?: string;
|
|
108
|
-
port?: number;
|
|
109
|
-
username?: string;
|
|
110
|
-
password?: string;
|
|
111
|
-
database?: string;
|
|
112
|
-
}
|
|
103
|
+
# For migration and seed CLIs
|
|
104
|
+
npm install -D ts-node tsconfig-paths
|
|
113
105
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
name?: string;
|
|
118
|
-
host?: string;
|
|
119
|
-
port?: number;
|
|
120
|
-
username?: string;
|
|
121
|
-
password?: string;
|
|
122
|
-
enableCompanyFeature?: boolean;
|
|
123
|
-
enableEmailVerification?: boolean;
|
|
124
|
-
permissionMode?: 'FULL' | 'RBAC' | 'DIRECT';
|
|
125
|
-
}
|
|
106
|
+
# For Swagger (docs/ module only)
|
|
107
|
+
npm install @nestjs/common @nestjs/swagger
|
|
108
|
+
```
|
|
126
109
|
|
|
127
|
-
|
|
128
|
-
databaseMode: 'single' | 'multi-tenant';
|
|
129
|
-
enableCompanyFeature: boolean;
|
|
130
|
-
permissionMode?: 'FULL' | 'RBAC' | 'DIRECT';
|
|
131
|
-
enableEmailVerification?: boolean;
|
|
132
|
-
}
|
|
110
|
+
---
|
|
133
111
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
112
|
+
## Environment Setup
|
|
113
|
+
|
|
114
|
+
Create a `.env` file in your project root:
|
|
115
|
+
|
|
116
|
+
```env
|
|
117
|
+
PORT=2002
|
|
118
|
+
MODE=dev
|
|
119
|
+
FRONTEND_URL=http://localhost:2001
|
|
120
|
+
ALLOW_ORIGINS=http://localhost:2001,http://localhost:3000
|
|
121
|
+
|
|
122
|
+
# Database
|
|
123
|
+
DB_HOST=localhost
|
|
124
|
+
DB_PORT=5432
|
|
125
|
+
DB_USER=postgres
|
|
126
|
+
DB_PASSWORD=password
|
|
127
|
+
DB_NAME=myapp
|
|
128
|
+
|
|
129
|
+
# JWT
|
|
130
|
+
JWT_SECRET=your-jwt-secret-min-32-chars
|
|
131
|
+
JWT_EXPIRATION=15m
|
|
132
|
+
REFRESH_TOKEN_SECRET=your-refresh-secret
|
|
133
|
+
REFRESH_TOKEN_EXPIRATION=7d
|
|
134
|
+
|
|
135
|
+
# Cache
|
|
136
|
+
REDIS_URL=redis://localhost:6379
|
|
137
|
+
|
|
138
|
+
# Email
|
|
139
|
+
MAIL_FROM=noreply@example.com
|
|
140
|
+
MAIL_APP_PASSWORD=app-password
|
|
141
|
+
|
|
142
|
+
# Multi-tenant (optional)
|
|
143
|
+
TENANT_ID=
|
|
144
|
+
USE_TENANT_MODE=false
|
|
145
|
+
|
|
146
|
+
# Logging
|
|
147
|
+
LOG_DIR=logs
|
|
148
|
+
LOG_LEVEL=debug
|
|
149
|
+
LOG_MAX_SIZE=20m
|
|
150
|
+
LOG_MAX_FILES=14d
|
|
151
|
+
DISABLE_HTTP_LOGGING=false
|
|
147
152
|
```
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
---
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
interface IBaseEntity {
|
|
153
|
-
id: string;
|
|
154
|
-
createdAt: Date;
|
|
155
|
-
updatedAt: Date;
|
|
156
|
-
deletedAt?: Date | null;
|
|
157
|
-
}
|
|
156
|
+
## Quick Start
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
deletedAt?: Date | null;
|
|
161
|
-
}
|
|
158
|
+
### Environment Config
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
import { envConfig } from '@flusys/nestjs-core';
|
|
162
|
+
|
|
163
|
+
const port = envConfig.getPort(); // number (default: 3000)
|
|
164
|
+
const isProd = envConfig.isProduction(); // boolean
|
|
165
|
+
const db = envConfig.getTypeOrmConfig(); // { host, port, username, password }
|
|
166
|
+
const jwt = envConfig.getJwtConfig(); // { secret, expiration, refreshSecret, refreshExpiration }
|
|
167
|
+
const origins = envConfig.getOrigins(); // string[] from ALLOW_ORIGINS
|
|
168
|
+
const redis = envConfig.getRedisUrl(); // string
|
|
169
|
+
const mail = envConfig.getMailConfig(); // { MAIL_FROM, MAIL_APP_PASSWORD }
|
|
170
|
+
const logConfig = envConfig.getLogConfig(); // { dir, level, maxSize, maxFiles, disableHttpLogging }
|
|
171
|
+
const tenantId = envConfig.getTenantId(); // string | null
|
|
172
|
+
const multiTenant = envConfig.useTenantMode(); // boolean
|
|
173
|
+
|
|
174
|
+
// Custom variable (throws if missing when throwOnMissing: true)
|
|
175
|
+
const myVar = envConfig.tryGetValue('MY_CUSTOM_VAR', true);
|
|
176
|
+
```
|
|
167
177
|
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
### Add Migration Scripts to package.json
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"scripts": {
|
|
183
|
+
"migration:generate": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/migration/cli generate",
|
|
184
|
+
"migration:run": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/migration/cli run",
|
|
185
|
+
"migration:revert": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/migration/cli revert",
|
|
186
|
+
"migration:status": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/migration/cli status",
|
|
187
|
+
"migration:run:all": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/migration/cli run:all",
|
|
188
|
+
"seed:run": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/seeders/cli run",
|
|
189
|
+
"seed:clear": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/seeders/cli clear",
|
|
190
|
+
"seed:status": "ts-node -r tsconfig-paths/register ./node_modules/@flusys/nestjs-core/seeders/cli status"
|
|
191
|
+
}
|
|
170
192
|
}
|
|
193
|
+
```
|
|
171
194
|
|
|
172
|
-
|
|
173
|
-
metadata?: Record<string, any> | null;
|
|
174
|
-
}
|
|
195
|
+
### Setup Swagger
|
|
175
196
|
|
|
176
|
-
|
|
177
|
-
|
|
197
|
+
```typescript
|
|
198
|
+
import { NestFactory } from '@nestjs/core';
|
|
199
|
+
import { setupSwaggerDocs } from '@flusys/nestjs-core/docs';
|
|
200
|
+
|
|
201
|
+
async function bootstrap() {
|
|
202
|
+
const app = await NestFactory.create(AppModule);
|
|
203
|
+
setupSwaggerDocs(app, { title: 'My API', version: '1.0' });
|
|
204
|
+
await app.listen(3000);
|
|
178
205
|
}
|
|
179
206
|
```
|
|
180
207
|
|
|
181
208
|
---
|
|
182
209
|
|
|
183
|
-
##
|
|
210
|
+
## EnvConfigService
|
|
184
211
|
|
|
185
|
-
|
|
212
|
+
A singleton pre-instantiated as `envConfig`. Loads `.env` on import via `dotenv`.
|
|
186
213
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
### Restricted Keys
|
|
215
|
+
|
|
216
|
+
The following keys cannot be accessed via `tryGetValue()` — use their dedicated typed methods instead:
|
|
217
|
+
|
|
218
|
+
`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`, `JWT_SECRET`, `JWT_EXPIRATION`, `REFRESH_TOKEN_SECRET`, `REFRESH_TOKEN_EXPIRATION`, `MAIL_FROM`, `MAIL_APP_PASSWORD`, `REDIS_URL`
|
|
219
|
+
|
|
220
|
+
### Method Reference
|
|
221
|
+
|
|
222
|
+
| Method | Returns | Description |
|
|
223
|
+
|--------|---------|-------------|
|
|
224
|
+
| `tryGetValue(key, throwOnMissing?)` | `string` | Get any non-restricted env var |
|
|
225
|
+
| `getNumber(key, throwOnMissing?)` | `number` | Get env var as number |
|
|
226
|
+
| `getBoolean(key, throwOnMissing?)` | `boolean` | Get env var as boolean |
|
|
227
|
+
| `getPort()` | `number` | `PORT` (default: 3000) |
|
|
228
|
+
| `isProduction()` | `boolean` | `MODE !== 'DEV'` (case-insensitive) |
|
|
229
|
+
| `getFrontendUrl()` | `string` | `FRONTEND_URL` |
|
|
230
|
+
| `getOrigins()` | `string[]` | `ALLOW_ORIGINS` split by comma |
|
|
231
|
+
| `getTypeOrmConfig()` | `IDatabaseConfig` | DB connection config |
|
|
232
|
+
| `getJwtConfig()` | `IJwtConfig` | JWT secrets and expirations |
|
|
233
|
+
| `getRedisUrl()` | `string` | `REDIS_URL` (default: `redis://localhost:6379`) |
|
|
234
|
+
| `getMailConfig()` | `IMailConfig` | `MAIL_FROM`, `MAIL_APP_PASSWORD` |
|
|
235
|
+
| `getTenantId()` | `string \| null` | `TENANT_ID` or null |
|
|
236
|
+
| `useTenantMode()` | `boolean` | `USE_TENANT_MODE` |
|
|
237
|
+
| `getLogConfig()` | `ILogConfig` | Logging configuration |
|
|
238
|
+
| `getEnv()` | `Record<string, string>` | Full `process.env` snapshot |
|
|
239
|
+
|
|
240
|
+
---
|
|
197
241
|
|
|
198
|
-
|
|
242
|
+
## Migration CLI
|
|
243
|
+
|
|
244
|
+
### Migration Config File
|
|
245
|
+
|
|
246
|
+
Create `migration.config.ts` in your project root:
|
|
199
247
|
|
|
200
248
|
```typescript
|
|
201
249
|
import { IMigrationConfig } from '@flusys/nestjs-core';
|
|
250
|
+
import { AuthModule } from '@flusys/nestjs-auth';
|
|
251
|
+
import { IAMModule } from '@flusys/nestjs-iam';
|
|
202
252
|
|
|
203
|
-
const
|
|
253
|
+
const config: IMigrationConfig = {
|
|
204
254
|
defaultDatabaseConfig: {
|
|
205
255
|
type: 'postgres',
|
|
206
|
-
host: 'localhost',
|
|
207
|
-
port: 5432,
|
|
208
|
-
username: '
|
|
209
|
-
password: '
|
|
210
|
-
database: '
|
|
211
|
-
},
|
|
212
|
-
bootstrapAppConfig: {
|
|
213
|
-
databaseMode: 'multi-tenant',
|
|
214
|
-
enableCompanyFeature: true,
|
|
256
|
+
host: process.env.DB_HOST ?? 'localhost',
|
|
257
|
+
port: Number(process.env.DB_PORT ?? 5432),
|
|
258
|
+
username: process.env.DB_USER ?? 'postgres',
|
|
259
|
+
password: process.env.DB_PASSWORD ?? '',
|
|
260
|
+
database: process.env.DB_NAME ?? 'myapp',
|
|
215
261
|
},
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
{
|
|
262
|
+
entities: [
|
|
263
|
+
...AuthModule.getEntities({ enableCompanyFeature: true, enableEmailVerification: true }),
|
|
264
|
+
...IAMModule.getEntities({ enableCompanyFeature: true, permissionMode: 'FULL' }),
|
|
265
|
+
// ... other module entities
|
|
219
266
|
],
|
|
220
|
-
migrationsPath: '
|
|
221
|
-
|
|
222
|
-
// Dynamic entity resolution per tenant
|
|
223
|
-
return tenantConfig?.enableCompanyFeature
|
|
224
|
-
? [UserWithCompany, RoleWithCompany]
|
|
225
|
-
: [User, Role];
|
|
226
|
-
},
|
|
227
|
-
migrationsTableName: 'typeorm_migrations',
|
|
267
|
+
migrationsPath: 'src/migrations',
|
|
268
|
+
migrationsTableName: 'migrations',
|
|
228
269
|
};
|
|
270
|
+
|
|
271
|
+
export default config;
|
|
229
272
|
```
|
|
230
273
|
|
|
231
|
-
###
|
|
274
|
+
### CLI Commands
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# Generate a new migration (compares entities to current DB schema)
|
|
278
|
+
npm run migration:generate -- -n CreateUsersTable
|
|
279
|
+
|
|
280
|
+
# Run all pending migrations
|
|
281
|
+
npm run migration:run
|
|
282
|
+
|
|
283
|
+
# Revert the last applied migration
|
|
284
|
+
npm run migration:revert
|
|
285
|
+
|
|
286
|
+
# Show migration status (which are applied, which are pending)
|
|
287
|
+
npm run migration:status
|
|
288
|
+
|
|
289
|
+
# Multi-tenant: run migrations for all tenants
|
|
290
|
+
npm run migration:run:all
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Programmatic API
|
|
232
294
|
|
|
233
295
|
```typescript
|
|
234
296
|
import {
|
|
235
|
-
|
|
297
|
+
createMigrationDataSource,
|
|
236
298
|
runMigrations,
|
|
237
299
|
revertMigration,
|
|
238
300
|
migrationStatus,
|
|
301
|
+
generateMigration,
|
|
239
302
|
runForAllTenants,
|
|
240
|
-
|
|
241
|
-
} from '@flusys/nestjs-core/migration';
|
|
303
|
+
} from '@flusys/nestjs-core';
|
|
242
304
|
|
|
243
|
-
//
|
|
244
|
-
await
|
|
305
|
+
// Create a DataSource for migration operations
|
|
306
|
+
const dataSource = await createMigrationDataSource(config, tenantId);
|
|
245
307
|
|
|
246
308
|
// Run pending migrations
|
|
247
|
-
|
|
309
|
+
await runMigrations(dataSource);
|
|
248
310
|
|
|
249
311
|
// Revert last migration
|
|
250
|
-
|
|
312
|
+
await revertMigration(dataSource);
|
|
251
313
|
|
|
252
314
|
// Check status
|
|
253
|
-
await migrationStatus(
|
|
315
|
+
const status = await migrationStatus(dataSource);
|
|
316
|
+
|
|
317
|
+
// Generate a new migration file
|
|
318
|
+
await generateMigration(config, 'MigrationName');
|
|
254
319
|
|
|
255
|
-
// Multi-tenant
|
|
256
|
-
|
|
320
|
+
// Multi-tenant: run for all configured tenants
|
|
321
|
+
await runForAllTenants(config, runMigrations);
|
|
257
322
|
```
|
|
258
323
|
|
|
259
324
|
---
|
|
260
325
|
|
|
261
326
|
## Seed System
|
|
262
327
|
|
|
263
|
-
###
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
npm run seed:run # Seed all entities
|
|
267
|
-
npm run seed:run -- --entity=User --count=100 # Specific entity
|
|
268
|
-
npm run seed:run -- --count=50 --clear # Clear before seeding
|
|
269
|
-
npm run seed:clear # Clear all data
|
|
270
|
-
npm run seed:clear -- --hard # Hard delete
|
|
271
|
-
npm run seed:status # Show status
|
|
272
|
-
npm run seed:run:all # Multi-tenant
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Seed Configuration
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
import { configureSeedConfig, seedConfig, ISeedConfig } from '@flusys/nestjs-core/seeders';
|
|
279
|
-
|
|
280
|
-
interface ISeedConfig {
|
|
281
|
-
counts: Record<string, number>; // Per-entity counts
|
|
282
|
-
order: string[]; // Preferred seeding order
|
|
283
|
-
skipEntities: string[]; // Entities to skip
|
|
284
|
-
locale: string; // Faker locale
|
|
285
|
-
respectSoftDelete: boolean; // Default clear behavior
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Configure before running seeders
|
|
289
|
-
configureSeedConfig({
|
|
290
|
-
counts: { User: 50, Product: 100, Order: 200 },
|
|
291
|
-
order: ['User', 'Company', 'Product', 'Order'],
|
|
292
|
-
skipEntities: ['migrations', 'typeorm_metadata', 'Migration'],
|
|
293
|
-
locale: 'en',
|
|
294
|
-
respectSoftDelete: true,
|
|
295
|
-
});
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### SeedRunner
|
|
299
|
-
|
|
300
|
-
The main orchestrator for seed operations:
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
import { SeedRunner, ISeedOptions, ISeedResult } from '@flusys/nestjs-core/seeders';
|
|
304
|
-
|
|
305
|
-
const runner = new SeedRunner(dataSource, logger?);
|
|
306
|
-
|
|
307
|
-
// Custom seeder management
|
|
308
|
-
runner.registerCustomSeeder('User', new UserSeeder(dataSource)); // Register
|
|
309
|
-
runner.unregisterCustomSeeder('User'); // Unregister
|
|
310
|
-
runner.hasCustomSeeder('User'); // Check: boolean
|
|
311
|
-
|
|
312
|
-
// Run all entities in dependency order
|
|
313
|
-
const results: ISeedResult[] = await runner.runAll({
|
|
314
|
-
count: 50, // Records per entity
|
|
315
|
-
clear: true, // Clear before seeding
|
|
316
|
-
entity: 'User', // Seed specific entity only
|
|
317
|
-
respectSoftDelete: true, // Use soft delete when clearing
|
|
318
|
-
skipIfExists: false, // Skip if data exists
|
|
319
|
-
dryRun: false, // Preview without executing
|
|
320
|
-
continueOnError: true, // Continue on failures
|
|
321
|
-
batchSize: 1000, // Batch size for bulk inserts
|
|
322
|
-
onProgress: (current, total, entity) => { },
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// Run single entity
|
|
326
|
-
const result: ISeedResult = await runner.runSingle('User', options);
|
|
327
|
-
|
|
328
|
-
// Clear all data (reverse dependency order)
|
|
329
|
-
const results = await runner.clearAll(hard?, continueOnError?);
|
|
328
|
+
### BaseSeeder
|
|
330
329
|
|
|
331
|
-
|
|
332
|
-
const status = await runner.getStatus();
|
|
333
|
-
// Returns: [{ entity, tableName, count, isEmpty, hasSoftDelete }]
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Custom Seeders
|
|
337
|
-
|
|
338
|
-
Extend `BaseSeeder<T>` for entity-specific seeding logic:
|
|
330
|
+
Extend `BaseSeeder` to create custom seeders:
|
|
339
331
|
|
|
340
332
|
```typescript
|
|
341
|
-
import { BaseSeeder,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
private generator: DataGenerator;
|
|
355
|
-
|
|
356
|
-
constructor(ds: DataSource) {
|
|
357
|
-
super(ds, User);
|
|
358
|
-
this.generator = new DataGenerator('en');
|
|
333
|
+
import { BaseSeeder, SeedRunner } from '@flusys/nestjs-core';
|
|
334
|
+
import { DataSource } from 'typeorm';
|
|
335
|
+
|
|
336
|
+
export class UserSeeder extends BaseSeeder {
|
|
337
|
+
name = 'UserSeeder';
|
|
338
|
+
order = 1; // Lower runs first (topological sort by dependencies)
|
|
339
|
+
dependencies = []; // Seeder names this depends on
|
|
340
|
+
|
|
341
|
+
async run(dataSource: DataSource): Promise<void> {
|
|
342
|
+
const repo = dataSource.getRepository(User);
|
|
343
|
+
await repo.save([
|
|
344
|
+
{ email: 'admin@example.com', name: 'Admin User' },
|
|
345
|
+
]);
|
|
359
346
|
}
|
|
360
347
|
|
|
361
|
-
async
|
|
362
|
-
|
|
363
|
-
for (let i = 0; i < count; i++) {
|
|
364
|
-
users.push(this.repository.create({
|
|
365
|
-
email: faker.internet.email(),
|
|
366
|
-
name: faker.person.fullName(),
|
|
367
|
-
password: await bcrypt.hash('password123', 12),
|
|
368
|
-
}));
|
|
369
|
-
}
|
|
370
|
-
return this.repository.save(users);
|
|
348
|
+
async clear(dataSource: DataSource): Promise<void> {
|
|
349
|
+
await dataSource.getRepository(User).delete({});
|
|
371
350
|
}
|
|
372
351
|
}
|
|
373
|
-
|
|
374
|
-
// Register before running
|
|
375
|
-
const runner = new SeedRunner(dataSource);
|
|
376
|
-
runner.registerCustomSeeder('User', new UserSeeder(dataSource));
|
|
377
|
-
await runner.runAll({ count: 50, clear: true });
|
|
378
352
|
```
|
|
379
353
|
|
|
380
|
-
###
|
|
354
|
+
### SeedRunner
|
|
381
355
|
|
|
382
356
|
```typescript
|
|
383
|
-
|
|
384
|
-
entity: string;
|
|
385
|
-
success: boolean;
|
|
386
|
-
count: number;
|
|
387
|
-
error?: string;
|
|
388
|
-
dryRun?: boolean;
|
|
389
|
-
skipped?: boolean;
|
|
390
|
-
}
|
|
357
|
+
import { SeedRunner, seedConfig } from '@flusys/nestjs-core';
|
|
391
358
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
tableName: string;
|
|
395
|
-
columns: IColumnInfo[];
|
|
396
|
-
relations: IRelationInfo[];
|
|
397
|
-
hasSoftDelete: boolean;
|
|
398
|
-
hasCompany: boolean;
|
|
399
|
-
}
|
|
359
|
+
// Register seeders
|
|
360
|
+
seedConfig.register([UserSeeder, RoleSeeder, PermissionSeeder]);
|
|
400
361
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
length?: number | string;
|
|
406
|
-
isNullable: boolean;
|
|
407
|
-
isUnique: boolean;
|
|
408
|
-
isPrimary: boolean;
|
|
409
|
-
isGenerated: boolean;
|
|
410
|
-
default?: any;
|
|
411
|
-
enum?: any[];
|
|
412
|
-
precision?: number;
|
|
413
|
-
scale?: number;
|
|
414
|
-
}
|
|
362
|
+
// Run all seeders (respects order and dependencies)
|
|
363
|
+
const runner = new SeedRunner(dataSource);
|
|
364
|
+
const result = await runner.run();
|
|
365
|
+
console.log(`Seeded ${result.count} records`);
|
|
415
366
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
joinColumnName?: string;
|
|
422
|
-
}
|
|
367
|
+
// Clear all seeded data (in reverse order)
|
|
368
|
+
await runner.clear();
|
|
369
|
+
|
|
370
|
+
// Check seeder status
|
|
371
|
+
const status = await runner.status();
|
|
423
372
|
```
|
|
424
373
|
|
|
425
|
-
###
|
|
374
|
+
### DataGenerator
|
|
426
375
|
|
|
427
|
-
|
|
376
|
+
Generates realistic test data from TypeORM entity metadata using Faker.js:
|
|
428
377
|
|
|
429
378
|
```typescript
|
|
430
|
-
import {
|
|
431
|
-
|
|
432
|
-
const reader = new EntityReader(dataSource);
|
|
433
|
-
|
|
434
|
-
// Get all entities
|
|
435
|
-
const entities = reader.getAllEntities();
|
|
379
|
+
import { DataGenerator } from '@flusys/nestjs-core';
|
|
436
380
|
|
|
437
|
-
|
|
438
|
-
const info: IEntityInfo = reader.getEntityInfo('User');
|
|
381
|
+
const generator = new DataGenerator(dataSource);
|
|
439
382
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
|
|
383
|
+
// Auto-detect field types and generate appropriate data
|
|
384
|
+
const fakeUser = generator.generate(User, {
|
|
385
|
+
overrides: { email: 'specific@email.com' },
|
|
386
|
+
count: 10, // generate 10 records
|
|
387
|
+
});
|
|
443
388
|
```
|
|
444
389
|
|
|
445
|
-
|
|
390
|
+
**Automatic field pattern detection:**
|
|
446
391
|
|
|
447
|
-
|
|
392
|
+
| Pattern | Generated value |
|
|
393
|
+
|---------|----------------|
|
|
394
|
+
| `email` | `faker.internet.email()` |
|
|
395
|
+
| `name`, `title` | `faker.person.fullName()` |
|
|
396
|
+
| `phone` | `faker.phone.number()` |
|
|
397
|
+
| `address` | `faker.location.streetAddress()` |
|
|
398
|
+
| `url`, `website` | `faker.internet.url()` |
|
|
399
|
+
| `description`, `text` | `faker.lorem.paragraph()` |
|
|
400
|
+
| `slug` | `faker.helpers.slugify()` |
|
|
401
|
+
| `uuid`, `id` | `faker.string.uuid()` |
|
|
402
|
+
| `boolean`, `isActive` | `faker.datatype.boolean()` |
|
|
403
|
+
| `date`, `createdAt` | `faker.date.recent()` |
|
|
404
|
+
| `number`, `price` | `faker.number.int()` |
|
|
448
405
|
|
|
449
|
-
|
|
450
|
-
import { DataGenerator, IColumnInfo } from '@flusys/nestjs-core/seeders';
|
|
406
|
+
### Seed CLI
|
|
451
407
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const value = generator.generateValue(columnInfo);
|
|
408
|
+
```bash
|
|
409
|
+
# Run all seeders
|
|
410
|
+
npm run seed:run
|
|
456
411
|
|
|
457
|
-
|
|
458
|
-
|
|
412
|
+
# Clear all seeded data
|
|
413
|
+
npm run seed:clear
|
|
459
414
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const relatedIds = generator.generateRelationIds(relatedEntities, min?, max?);
|
|
415
|
+
# Show seeder status
|
|
416
|
+
npm run seed:status
|
|
463
417
|
```
|
|
464
418
|
|
|
465
419
|
---
|
|
466
420
|
|
|
467
|
-
##
|
|
421
|
+
## Swagger Documentation
|
|
468
422
|
|
|
469
|
-
|
|
423
|
+
### setupSwaggerDocs
|
|
470
424
|
|
|
471
|
-
|
|
425
|
+
Sets up a single global Swagger UI for the whole application:
|
|
472
426
|
|
|
473
427
|
```typescript
|
|
474
|
-
import {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
| 'address' | 'street' | 'city' | 'state' | 'country' | 'zipCode'
|
|
485
|
-
| 'url' | 'domain' | 'slug'
|
|
486
|
-
| 'description' | 'summary' | 'content' | 'title'
|
|
487
|
-
| 'username' | 'password'
|
|
488
|
-
| 'birthdate' | 'futureDate' | 'recentDateOrNull'
|
|
489
|
-
| 'serial' | 'company'
|
|
490
|
-
| 'emailProvider' // smtp, sendgrid, mailgun, ses, postmark
|
|
491
|
-
| 'formAccessType'; // public, authenticated, permission
|
|
492
|
-
|
|
493
|
-
// Detect pattern for a column
|
|
494
|
-
const pattern = detectFieldPattern(column);
|
|
428
|
+
import { setupSwaggerDocs } from '@flusys/nestjs-core/docs';
|
|
429
|
+
|
|
430
|
+
setupSwaggerDocs(app, {
|
|
431
|
+
title: 'My API',
|
|
432
|
+
description: 'API documentation',
|
|
433
|
+
version: '1.0.0',
|
|
434
|
+
path: 'api/docs', // default: 'api/docs'
|
|
435
|
+
bearerAuthEnabled: true, // default: true
|
|
436
|
+
persistAuthorization: true, // default: true
|
|
437
|
+
});
|
|
495
438
|
```
|
|
496
439
|
|
|
497
|
-
###
|
|
440
|
+
### setupModuleSwaggerDocs
|
|
498
441
|
|
|
499
|
-
|
|
442
|
+
Sets up module-scoped Swagger documentation at separate URLs — one per feature module:
|
|
500
443
|
|
|
501
444
|
```typescript
|
|
502
|
-
import {
|
|
503
|
-
|
|
504
|
-
// Exported constant
|
|
505
|
-
SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt', 'deletedAt', 'createdById', 'updatedById', 'deletedById'];
|
|
445
|
+
import { setupModuleSwaggerDocs } from '@flusys/nestjs-core/docs';
|
|
506
446
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
447
|
+
setupModuleSwaggerDocs(app, [
|
|
448
|
+
{
|
|
449
|
+
title: 'Authentication API',
|
|
450
|
+
description: 'Auth endpoints',
|
|
451
|
+
version: '1.0.0',
|
|
452
|
+
path: 'api/docs/auth',
|
|
453
|
+
includeTags: ['Authentication', 'Users', 'Companies'],
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
title: 'IAM API',
|
|
457
|
+
description: 'Permissions and roles',
|
|
458
|
+
version: '1.0.0',
|
|
459
|
+
path: 'api/docs/iam',
|
|
460
|
+
includeTags: ['Roles', 'Actions', 'Permissions'],
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
title: 'Storage API',
|
|
464
|
+
description: 'File management',
|
|
465
|
+
version: '1.0.0',
|
|
466
|
+
path: 'api/docs/storage',
|
|
467
|
+
includeTags: ['Files', 'Folders'],
|
|
468
|
+
},
|
|
469
|
+
]);
|
|
514
470
|
```
|
|
515
471
|
|
|
516
|
-
###
|
|
472
|
+
### IModuleSwaggerOptions
|
|
517
473
|
|
|
518
474
|
```typescript
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
475
|
+
interface IModuleSwaggerOptions {
|
|
476
|
+
title: string;
|
|
477
|
+
description?: string;
|
|
478
|
+
version?: string;
|
|
479
|
+
path: string;
|
|
480
|
+
includeTags?: string[]; // Whitelist specific controller tags
|
|
481
|
+
excludeTags?: string[]; // Blacklist specific controller tags
|
|
482
|
+
includeSchemas?: string[]; // Whitelist specific DTO schemas
|
|
483
|
+
excludeSchemas?: string[]; // Blacklist specific DTO schemas
|
|
484
|
+
bearerAuthEnabled?: boolean;
|
|
485
|
+
persistAuthorization?: boolean;
|
|
486
|
+
}
|
|
530
487
|
```
|
|
531
488
|
|
|
532
489
|
---
|
|
533
490
|
|
|
534
|
-
##
|
|
535
|
-
|
|
536
|
-
### Setup Functions
|
|
491
|
+
## Database Utilities
|
|
537
492
|
|
|
538
493
|
```typescript
|
|
539
|
-
import {
|
|
494
|
+
import { buildDataSourceOptions, getMigrationsFolderPath, getActiveTenants } from '@flusys/nestjs-core';
|
|
495
|
+
|
|
496
|
+
// Build a TypeORM DataSourceOptions object
|
|
497
|
+
const options = buildDataSourceOptions({
|
|
498
|
+
config: databaseConfig,
|
|
499
|
+
entities: [...authEntities, ...iamEntities],
|
|
500
|
+
migrationsPath: 'src/migrations',
|
|
501
|
+
migrationsTableName: 'migrations',
|
|
502
|
+
});
|
|
540
503
|
|
|
541
|
-
//
|
|
542
|
-
|
|
504
|
+
// Get migrations folder path (supports tenant-specific subdirectories)
|
|
505
|
+
const migrationsPath = getMigrationsFolderPath('src/migrations', 'tenant-1');
|
|
543
506
|
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
{ title: 'Auth API', path: 'auth/docs', bearerAuth: true },
|
|
547
|
-
{ title: 'Admin API', path: 'admin/docs', excludeTags: ['public'] },
|
|
548
|
-
]);
|
|
507
|
+
// Get all active tenant configs
|
|
508
|
+
const tenants = getActiveTenants(migrationConfig);
|
|
549
509
|
```
|
|
550
510
|
|
|
551
|
-
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Core Interfaces
|
|
514
|
+
|
|
515
|
+
These interfaces are used as contracts across all FLUSYS feature modules:
|
|
552
516
|
|
|
553
517
|
```typescript
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
title: string;
|
|
557
|
-
description: string;
|
|
558
|
-
version?: string; // Default: '1.0'
|
|
559
|
-
path: string; // Swagger UI path
|
|
560
|
-
bearerAuth?: boolean; // Add JWT auth
|
|
561
|
-
globalHeaders?: ISwaggerGlobalHeader[];
|
|
562
|
-
excludeTags?: string[]; // Hide endpoints by tag
|
|
563
|
-
excludeSchemaProperties?: ISchemaPropertyExclusion[];
|
|
564
|
-
excludeQueryParameters?: IQueryParameterExclusion[];
|
|
565
|
-
excludeExamples?: IExampleExclusion[];
|
|
566
|
-
}
|
|
518
|
+
// Entity contracts
|
|
519
|
+
interface IIdentity { id: string; createdAt: Date; updatedAt: Date; deletedAt?: Date | null; }
|
|
567
520
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
521
|
+
// Bootstrap configuration (fixed at startup)
|
|
522
|
+
interface IBootstrapAppConfig {
|
|
523
|
+
databaseMode: 'single' | 'multi-tenant';
|
|
524
|
+
enableCompanyFeature: boolean;
|
|
525
|
+
enableEmailVerification?: boolean;
|
|
526
|
+
permissionMode?: 'FULL' | 'RBAC' | 'DIRECT';
|
|
571
527
|
}
|
|
572
528
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
529
|
+
// DataSource configuration
|
|
530
|
+
interface IDatabaseConfig {
|
|
531
|
+
type: 'postgres' | 'mysql' | 'mariadb' | 'sqlite' | 'mssql';
|
|
532
|
+
host: string;
|
|
533
|
+
port: number;
|
|
534
|
+
username: string;
|
|
535
|
+
password: string;
|
|
536
|
+
database: string;
|
|
577
537
|
}
|
|
578
538
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
539
|
+
// Multi-tenant config
|
|
540
|
+
interface ITenantDatabaseConfig extends IDatabaseConfig { id: string; }
|
|
541
|
+
|
|
542
|
+
// Module options for dynamic modules
|
|
543
|
+
interface IDynamicModuleConfig {
|
|
544
|
+
global?: boolean;
|
|
545
|
+
includeController?: boolean;
|
|
584
546
|
}
|
|
585
547
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
548
|
+
// DataSource service options (base for all module configs)
|
|
549
|
+
interface IDataSourceServiceOptions {
|
|
550
|
+
defaultDatabaseConfig?: IDatabaseConfig;
|
|
551
|
+
tenantDefaultDatabaseConfig?: IDatabaseConfig;
|
|
552
|
+
tenants?: ITenantDatabaseConfig[];
|
|
590
553
|
}
|
|
591
554
|
```
|
|
592
555
|
|
|
593
|
-
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Injection Tokens & Constants
|
|
594
559
|
|
|
595
560
|
```typescript
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
{ schemaName: 'LoginResponseDto', properties: ['company', 'branch'] },
|
|
602
|
-
{ schemaName: 'UserDto', properties: ['companyId'] },
|
|
603
|
-
],
|
|
604
|
-
excludeTags: enableCompanyFeature ? undefined : ['Companies', 'Branches'],
|
|
605
|
-
}]);
|
|
561
|
+
import {
|
|
562
|
+
MIGRATION_CONFIG_TOKEN,
|
|
563
|
+
SEED_CONFIG_TOKEN,
|
|
564
|
+
DATASOURCE_TOKEN,
|
|
565
|
+
} from '@flusys/nestjs-core';
|
|
606
566
|
```
|
|
607
567
|
|
|
608
568
|
---
|
|
609
569
|
|
|
610
|
-
##
|
|
570
|
+
## Multi-Tenant Support
|
|
611
571
|
|
|
612
|
-
|
|
572
|
+
`nestjs-core` provides the foundation for multi-tenant architectures:
|
|
613
573
|
|
|
614
574
|
```typescript
|
|
615
|
-
import {
|
|
616
|
-
// Database
|
|
617
|
-
IDatabaseConfig,
|
|
618
|
-
ITenantDatabaseConfig,
|
|
619
|
-
IDataSourceBuildOptions,
|
|
620
|
-
// App Config
|
|
621
|
-
IBootstrapAppConfig,
|
|
622
|
-
IDynamicModuleConfig,
|
|
623
|
-
IDataSourceServiceOptions,
|
|
624
|
-
IModuleOptionsFactory,
|
|
625
|
-
IAsyncModuleOptions,
|
|
626
|
-
// Migration
|
|
627
|
-
IMigrationConfig,
|
|
628
|
-
IMigrationResult,
|
|
629
|
-
EntityResolver,
|
|
630
|
-
// Entity Contracts
|
|
631
|
-
IBaseEntity,
|
|
632
|
-
ISoftDeletable,
|
|
633
|
-
ITimestampable,
|
|
634
|
-
IOrderable,
|
|
635
|
-
ICompanyOwned,
|
|
636
|
-
IMetadata,
|
|
637
|
-
} from '@flusys/nestjs-core';
|
|
638
|
-
```
|
|
575
|
+
import { buildDataSourceOptions, getDatabaseForTenant, getActiveTenants } from '@flusys/nestjs-core';
|
|
639
576
|
|
|
640
|
-
|
|
577
|
+
// Get database config for a specific tenant
|
|
578
|
+
const tenantDb = getDatabaseForTenant(migrationConfig, 'tenant-id');
|
|
641
579
|
|
|
642
|
-
|
|
643
|
-
|
|
580
|
+
// Build a DataSource for that tenant
|
|
581
|
+
const dsOptions = buildDataSourceOptions({
|
|
582
|
+
config: tenantDb,
|
|
583
|
+
entities: [...allEntities],
|
|
584
|
+
migrationsPath: getMigrationsFolderPath('src/migrations', 'tenant-id'),
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Run migrations for all configured tenants
|
|
588
|
+
await runForAllTenants(migrationConfig, async (dataSource) => {
|
|
589
|
+
await runMigrations(dataSource);
|
|
590
|
+
});
|
|
644
591
|
```
|
|
645
592
|
|
|
646
|
-
|
|
593
|
+
---
|
|
647
594
|
|
|
648
|
-
|
|
649
|
-
import { envConfig } from '@flusys/nestjs-core/config';
|
|
650
|
-
```
|
|
595
|
+
## Troubleshooting
|
|
651
596
|
|
|
652
|
-
|
|
597
|
+
**`Cannot find migration config file`**
|
|
653
598
|
|
|
654
|
-
|
|
655
|
-
import {
|
|
656
|
-
buildDataSourceOptions,
|
|
657
|
-
getDatabaseForTenant,
|
|
658
|
-
resolveEntities,
|
|
659
|
-
getActiveTenants,
|
|
660
|
-
getMigrationsFolderPath,
|
|
661
|
-
getMigrationsGlobPattern,
|
|
662
|
-
} from '@flusys/nestjs-core/utils';
|
|
663
|
-
```
|
|
599
|
+
Ensure `migration.config.ts` exists at the project root and exports a default `IMigrationConfig` object. The CLI looks for it relative to the current working directory.
|
|
664
600
|
|
|
665
|
-
|
|
601
|
+
---
|
|
666
602
|
|
|
667
|
-
|
|
668
|
-
import {
|
|
669
|
-
createMigrationDataSource,
|
|
670
|
-
initializeDataSource,
|
|
671
|
-
runMigrationCli,
|
|
672
|
-
generateMigration,
|
|
673
|
-
runMigrations,
|
|
674
|
-
revertMigration,
|
|
675
|
-
migrationStatus,
|
|
676
|
-
runForAllTenants,
|
|
677
|
-
ensureMigrationsFolder,
|
|
678
|
-
} from '@flusys/nestjs-core/migration';
|
|
679
|
-
```
|
|
603
|
+
**`RESTRICTED_KEY` error from `tryGetValue`**
|
|
680
604
|
|
|
681
|
-
|
|
605
|
+
You are trying to access a sensitive key (DB credentials, JWT secrets) via the generic accessor. Use the typed method instead:
|
|
682
606
|
|
|
683
607
|
```typescript
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
DataGenerator,
|
|
690
|
-
// Interfaces
|
|
691
|
-
ISeedResult,
|
|
692
|
-
ISeedOptions,
|
|
693
|
-
ISeederLogger,
|
|
694
|
-
IEntityInfo,
|
|
695
|
-
IColumnInfo,
|
|
696
|
-
IRelationInfo,
|
|
697
|
-
ISeedConfig,
|
|
698
|
-
// Functions
|
|
699
|
-
defaultLogger,
|
|
700
|
-
seedConfig,
|
|
701
|
-
configureSeedConfig,
|
|
702
|
-
runSeedCli,
|
|
703
|
-
getEntityCount,
|
|
704
|
-
shouldSkipEntity,
|
|
705
|
-
getSeedingOrder,
|
|
706
|
-
// Field pattern functions
|
|
707
|
-
detectFieldPattern,
|
|
708
|
-
detectTypeCategory,
|
|
709
|
-
isSystemField,
|
|
710
|
-
// Constants
|
|
711
|
-
SYSTEM_FIELDS,
|
|
712
|
-
} from '@flusys/nestjs-core/seeders';
|
|
608
|
+
// Wrong
|
|
609
|
+
envConfig.tryGetValue('JWT_SECRET') // throws RestrictedKeyError
|
|
610
|
+
|
|
611
|
+
// Correct
|
|
612
|
+
envConfig.getJwtConfig().secret
|
|
713
613
|
```
|
|
714
614
|
|
|
715
|
-
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
**`SnakeNamingStrategy` column name mismatch**
|
|
716
618
|
|
|
717
|
-
|
|
619
|
+
All columns use `snake_case` by default. A TypeScript property `createdAt` maps to column `created_at`. If your existing schema uses camelCase, override the naming strategy in `buildDataSourceOptions`.
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
**Seeder runs out of order**
|
|
624
|
+
|
|
625
|
+
Set explicit `order` numbers or use `dependencies` to declare which seeders must run before yours:
|
|
718
626
|
|
|
719
627
|
```typescript
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
ISchemaPropertyExclusion,
|
|
725
|
-
IQueryParameterExclusion,
|
|
726
|
-
ISwaggerGlobalHeader,
|
|
727
|
-
IExampleExclusion,
|
|
728
|
-
} from '@flusys/nestjs-core/docs';
|
|
628
|
+
export class ProductSeeder extends BaseSeeder {
|
|
629
|
+
name = 'ProductSeeder';
|
|
630
|
+
dependencies = ['CategorySeeder']; // CategorySeeder must run first
|
|
631
|
+
}
|
|
729
632
|
```
|
|
730
633
|
|
|
731
634
|
---
|
|
732
635
|
|
|
733
|
-
##
|
|
636
|
+
## License
|
|
734
637
|
|
|
735
|
-
|
|
736
|
-
- `typeorm` - Migration/seeder utilities (DataSource, EntityMetadata)
|
|
737
|
-
- `@faker-js/faker` - Seed data generation
|
|
738
|
-
- `@nestjs/common`, `@nestjs/swagger` - Swagger documentation setup only
|
|
638
|
+
MIT © FLUSYS
|
|
739
639
|
|
|
740
640
|
---
|
|
741
641
|
|
|
742
|
-
**
|
|
642
|
+
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|