@flusys/nestjs-core 1.1.0-beta → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +505 -63
- package/cjs/config/env-config.service.js +1 -1
- package/cjs/docs/docs.config.js +77 -3
- package/cjs/docs/index.js +0 -1
- package/cjs/interfaces/base-entity.interface.js +5 -3
- package/cjs/interfaces/database.interface.js +1 -3
- package/cjs/migration/datasource.factory.js +1 -3
- package/cjs/migration/index.js +0 -12
- package/cjs/migration/migration.cli.js +1 -17
- package/cjs/migration/migration.runner.js +37 -65
- package/cjs/seeders/base-seeder.js +6 -25
- package/cjs/seeders/cli.js +65 -172
- package/cjs/seeders/data-generator.js +110 -142
- package/cjs/seeders/entity-reader.js +0 -17
- package/cjs/seeders/field-patterns.js +176 -0
- package/cjs/seeders/index.js +16 -8
- package/cjs/seeders/seed-config.js +9 -48
- package/cjs/seeders/seed-runner.js +8 -14
- package/cjs/utils/datasource-config.builder.js +2 -14
- package/docs/docs.config.d.ts +7 -0
- package/docs/index.d.ts +0 -1
- package/fesm/config/env-config.service.js +1 -1
- package/fesm/docs/docs.config.js +68 -0
- package/fesm/docs/index.js +0 -1
- package/fesm/interfaces/app-config.interfaces.js +1 -3
- package/fesm/interfaces/base-entity.interface.js +5 -5
- package/fesm/interfaces/database.interface.js +1 -5
- package/fesm/migration/cli.js +1 -20
- package/fesm/migration/datasource.factory.js +3 -20
- package/fesm/migration/index.js +0 -14
- package/fesm/migration/migration.cli.js +1 -17
- package/fesm/migration/migration.runner.js +43 -132
- package/fesm/seeders/base-seeder.js +7 -51
- package/fesm/seeders/cli.js +65 -182
- package/fesm/seeders/data-generator.js +110 -149
- package/fesm/seeders/entity-reader.js +0 -17
- package/fesm/seeders/field-patterns.js +147 -0
- package/fesm/seeders/index.js +3 -7
- package/fesm/seeders/seed-config.js +9 -59
- package/fesm/seeders/seed-runner.js +8 -14
- package/fesm/utils/datasource-config.builder.js +2 -13
- package/interfaces/app-config.interfaces.d.ts +1 -0
- package/interfaces/base-entity.interface.d.ts +3 -0
- package/interfaces/database.interface.d.ts +1 -0
- package/package.json +2 -2
- package/seeders/data-generator.d.ts +1 -1
- package/seeders/entity-reader.d.ts +0 -1
- package/seeders/field-patterns.d.ts +12 -0
- package/seeders/index.d.ts +3 -3
- package/seeders/seed-config.d.ts +1 -0
- package/seeders/seed-runner.d.ts +1 -0
- package/utils/datasource-config.builder.d.ts +0 -1
- package/cjs/docs/docs.setup.js +0 -14
- package/cjs/seeders/template-generator.js +0 -297
- package/docs/docs.setup.d.ts +0 -3
- package/fesm/docs/docs.setup.js +0 -4
- package/fesm/seeders/template-generator.js +0 -257
- package/seeders/template-generator.d.ts +0 -16
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Seed Configuration
|
|
3
|
-
*
|
|
4
|
-
* Configuration for seed data generation.
|
|
5
|
-
* Customize record counts, seeding order, and behavior.
|
|
6
|
-
*/ "use strict";
|
|
1
|
+
"use strict";
|
|
7
2
|
Object.defineProperty(exports, "__esModule", {
|
|
8
3
|
value: true
|
|
9
4
|
});
|
|
@@ -14,6 +9,9 @@ function _export(target, all) {
|
|
|
14
9
|
});
|
|
15
10
|
}
|
|
16
11
|
_export(exports, {
|
|
12
|
+
get configureSeedConfig () {
|
|
13
|
+
return configureSeedConfig;
|
|
14
|
+
},
|
|
17
15
|
get getEntityCount () {
|
|
18
16
|
return getEntityCount;
|
|
19
17
|
},
|
|
@@ -28,55 +26,20 @@ _export(exports, {
|
|
|
28
26
|
}
|
|
29
27
|
});
|
|
30
28
|
const seedConfig = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Auth entities
|
|
34
|
-
Company: 10,
|
|
35
|
-
CompanyBranch: 25,
|
|
36
|
-
User: 50,
|
|
37
|
-
// IAM entities
|
|
38
|
-
Role: 8,
|
|
39
|
-
Permission: 45,
|
|
40
|
-
Action: 30,
|
|
41
|
-
Menu: 20,
|
|
42
|
-
UserIAMPermission: 50,
|
|
43
|
-
UserCompanyPermission: 100,
|
|
44
|
-
// Storage entities
|
|
45
|
-
StorageConfig: 5,
|
|
46
|
-
Folder: 20,
|
|
47
|
-
FileManager: 100
|
|
48
|
-
},
|
|
49
|
-
// Entity seeding order (respects FK constraints)
|
|
50
|
-
// Parent entities must come before child entities
|
|
51
|
-
order: [
|
|
52
|
-
// Auth - foundational
|
|
53
|
-
'Company',
|
|
54
|
-
'CompanyBranch',
|
|
55
|
-
'User',
|
|
56
|
-
// IAM - permissions system
|
|
57
|
-
'Action',
|
|
58
|
-
'Role',
|
|
59
|
-
'Permission',
|
|
60
|
-
'Menu',
|
|
61
|
-
'UserIAMPermission',
|
|
62
|
-
'UserCompanyPermission',
|
|
63
|
-
// Storage - file system
|
|
64
|
-
'StorageConfig',
|
|
65
|
-
'Folder',
|
|
66
|
-
'FileManager'
|
|
67
|
-
],
|
|
68
|
-
// Skip these entities (system tables, migrations)
|
|
29
|
+
counts: {},
|
|
30
|
+
order: [],
|
|
69
31
|
skipEntities: [
|
|
70
32
|
'migrations',
|
|
71
33
|
'typeorm_metadata',
|
|
72
34
|
'Migration',
|
|
73
35
|
'Typeorm_Metadata'
|
|
74
36
|
],
|
|
75
|
-
// Faker locale (en, ar, es, fr, de, etc.)
|
|
76
37
|
locale: 'en',
|
|
77
|
-
// Respect soft delete when clearing data
|
|
78
38
|
respectSoftDelete: true
|
|
79
39
|
};
|
|
40
|
+
function configureSeedConfig(config) {
|
|
41
|
+
Object.assign(seedConfig, config);
|
|
42
|
+
}
|
|
80
43
|
function getEntityCount(entityName, config = seedConfig) {
|
|
81
44
|
return config.counts[entityName] || 10;
|
|
82
45
|
}
|
|
@@ -85,13 +48,11 @@ function shouldSkipEntity(entityName, config = seedConfig) {
|
|
|
85
48
|
}
|
|
86
49
|
function getSeedingOrder(availableEntities, config = seedConfig) {
|
|
87
50
|
const ordered = [];
|
|
88
|
-
// Add entities in configured order
|
|
89
51
|
for (const entityName of config.order){
|
|
90
52
|
if (availableEntities.includes(entityName) && !shouldSkipEntity(entityName, config)) {
|
|
91
53
|
ordered.push(entityName);
|
|
92
54
|
}
|
|
93
55
|
}
|
|
94
|
-
// Add remaining entities not in order configuration
|
|
95
56
|
for (const entityName of availableEntities){
|
|
96
57
|
if (!ordered.includes(entityName) && !shouldSkipEntity(entityName, config)) {
|
|
97
58
|
ordered.push(entityName);
|
|
@@ -58,6 +58,12 @@ let SeedRunner = class SeedRunner {
|
|
|
58
58
|
*/ hasCustomSeeder(entityName) {
|
|
59
59
|
return this.customSeeders.has(entityName);
|
|
60
60
|
}
|
|
61
|
+
getSeeder(entityName, entity, dataSource, entityInfo, batchSize) {
|
|
62
|
+
if (this.customSeeders.has(entityName)) {
|
|
63
|
+
return this.customSeeders.get(entityName);
|
|
64
|
+
}
|
|
65
|
+
return new GenericSeeder(dataSource, entity, this.dataGenerator, entityInfo, batchSize);
|
|
66
|
+
}
|
|
61
67
|
/**
|
|
62
68
|
* Run seeds for all entities
|
|
63
69
|
* @param options Seeding options
|
|
@@ -122,13 +128,7 @@ let SeedRunner = class SeedRunner {
|
|
|
122
128
|
if (!entity) {
|
|
123
129
|
throw new Error(`Entity ${entityName} not found`);
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
let seeder;
|
|
127
|
-
if (this.customSeeders.has(entityName)) {
|
|
128
|
-
seeder = this.customSeeders.get(entityName);
|
|
129
|
-
} else {
|
|
130
|
-
seeder = new GenericSeeder(queryRunner.manager.connection, entity, this.dataGenerator, entityInfo, options.batchSize);
|
|
131
|
-
}
|
|
131
|
+
const seeder = this.getSeeder(entityName, entity, queryRunner.manager.connection, entityInfo, options.batchSize);
|
|
132
132
|
// Dry run mode - preview without executing
|
|
133
133
|
if (options.dryRun) {
|
|
134
134
|
const count = options.count ?? (0, _seedconfig.getEntityCount)(entityName);
|
|
@@ -200,13 +200,7 @@ let SeedRunner = class SeedRunner {
|
|
|
200
200
|
if (!entity) {
|
|
201
201
|
throw new Error(`Entity ${entityName} not found`);
|
|
202
202
|
}
|
|
203
|
-
|
|
204
|
-
let seeder;
|
|
205
|
-
if (this.customSeeders.has(entityName)) {
|
|
206
|
-
seeder = this.customSeeders.get(entityName);
|
|
207
|
-
} else {
|
|
208
|
-
seeder = new GenericSeeder(this.dataSource, entity, this.dataGenerator, this.entityReader.getEntityInfo(entityName));
|
|
209
|
-
}
|
|
203
|
+
const seeder = this.getSeeder(entityName, entity, this.dataSource, this.entityReader.getEntityInfo(entityName));
|
|
210
204
|
const countBefore = await seeder.count(true);
|
|
211
205
|
// Skip if already empty
|
|
212
206
|
if (countBefore === 0) {
|
|
@@ -12,9 +12,6 @@ _export(exports, {
|
|
|
12
12
|
get buildDataSourceOptions () {
|
|
13
13
|
return buildDataSourceOptions;
|
|
14
14
|
},
|
|
15
|
-
get buildTenantDatabaseConfig () {
|
|
16
|
-
return buildTenantDatabaseConfig;
|
|
17
|
-
},
|
|
18
15
|
get getActiveTenants () {
|
|
19
16
|
return getActiveTenants;
|
|
20
17
|
},
|
|
@@ -70,7 +67,8 @@ function getDatabaseForTenant(config, tenantId) {
|
|
|
70
67
|
const defaultTenantConfig = {
|
|
71
68
|
id: 'default',
|
|
72
69
|
database: config.defaultDatabaseConfig.database || 'default',
|
|
73
|
-
enableCompanyFeature: config.bootstrapAppConfig?.enableCompanyFeature ?? false
|
|
70
|
+
enableCompanyFeature: config.bootstrapAppConfig?.enableCompanyFeature ?? false,
|
|
71
|
+
permissionMode: config.bootstrapAppConfig?.permissionMode ?? 'FULL'
|
|
74
72
|
};
|
|
75
73
|
return {
|
|
76
74
|
database: defaultTenantConfig.database,
|
|
@@ -83,16 +81,6 @@ function resolveEntities(config, tenantConfig) {
|
|
|
83
81
|
}
|
|
84
82
|
return config.entities;
|
|
85
83
|
}
|
|
86
|
-
function buildTenantDatabaseConfig(baseConfig, tenant) {
|
|
87
|
-
return {
|
|
88
|
-
type: baseConfig.type,
|
|
89
|
-
host: tenant.host ?? baseConfig.host,
|
|
90
|
-
port: tenant.port ?? baseConfig.port,
|
|
91
|
-
username: tenant.username ?? baseConfig.username,
|
|
92
|
-
password: tenant.password ?? baseConfig.password,
|
|
93
|
-
database: tenant.database
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
84
|
function getActiveTenants(config) {
|
|
97
85
|
return config.tenants || [];
|
|
98
86
|
}
|
package/docs/docs.config.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface IQueryParameterExclusion {
|
|
|
14
14
|
method?: string;
|
|
15
15
|
parameters: string[];
|
|
16
16
|
}
|
|
17
|
+
export interface IExampleExclusion {
|
|
18
|
+
pathPattern: string;
|
|
19
|
+
method?: string;
|
|
20
|
+
examples: string[];
|
|
21
|
+
}
|
|
17
22
|
export interface IModuleSwaggerOptions {
|
|
18
23
|
modules?: Type<unknown>[];
|
|
19
24
|
title: string;
|
|
@@ -25,5 +30,7 @@ export interface IModuleSwaggerOptions {
|
|
|
25
30
|
excludeTags?: string[];
|
|
26
31
|
excludeSchemaProperties?: ISchemaPropertyExclusion[];
|
|
27
32
|
excludeQueryParameters?: IQueryParameterExclusion[];
|
|
33
|
+
excludeExamples?: IExampleExclusion[];
|
|
28
34
|
}
|
|
29
35
|
export declare function setupModuleSwaggerDocs(app: INestApplication, configs: IModuleSwaggerOptions[]): void;
|
|
36
|
+
export declare function setupSwaggerDocs(app: INestApplication, ...modules: IModuleSwaggerOptions[]): void;
|
package/docs/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ let EnvConfigService = class EnvConfigService {
|
|
|
34
34
|
return this.getValue(key, throwOnMissing).toLowerCase() === 'true';
|
|
35
35
|
}
|
|
36
36
|
getPort() {
|
|
37
|
-
return this.getNumber('PORT', false);
|
|
37
|
+
return this.getNumber('PORT', false) || 3000;
|
|
38
38
|
}
|
|
39
39
|
isProduction() {
|
|
40
40
|
return this.getValue('MODE', false).toUpperCase() !== 'DEV';
|
package/fesm/docs/docs.config.js
CHANGED
|
@@ -84,6 +84,65 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
|
84
84
|
paths: filteredPaths
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Filter OpenAPI document to exclude examples from endpoint responses
|
|
89
|
+
*/ function filterExamples(document, exclusions) {
|
|
90
|
+
if (!exclusions?.length || !document.paths) {
|
|
91
|
+
return document;
|
|
92
|
+
}
|
|
93
|
+
const filteredPaths = {};
|
|
94
|
+
for (const [path, pathItem] of Object.entries(document.paths)){
|
|
95
|
+
const filteredPathItem = {};
|
|
96
|
+
for (const [method, operation] of Object.entries(pathItem)){
|
|
97
|
+
// Skip non-operation properties
|
|
98
|
+
if (!operation || typeof operation !== 'object') {
|
|
99
|
+
filteredPathItem[method] = operation;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Check if this endpoint matches any exclusion pattern
|
|
103
|
+
const matchingExclusions = exclusions.filter((exclusion)=>{
|
|
104
|
+
const pathMatches = pathMatchesPattern(path, exclusion.pathPattern);
|
|
105
|
+
const methodMatches = !exclusion.method || exclusion.method === method;
|
|
106
|
+
return pathMatches && methodMatches;
|
|
107
|
+
});
|
|
108
|
+
if (matchingExclusions.length > 0) {
|
|
109
|
+
// Collect all examples to exclude
|
|
110
|
+
const examplesToExclude = new Set();
|
|
111
|
+
matchingExclusions.forEach((exclusion)=>{
|
|
112
|
+
exclusion.examples.forEach((example)=>examplesToExclude.add(example));
|
|
113
|
+
});
|
|
114
|
+
// Deep clone the operation to avoid mutating original
|
|
115
|
+
const filteredOperation = JSON.parse(JSON.stringify(operation));
|
|
116
|
+
// Filter examples from responses
|
|
117
|
+
if (filteredOperation.responses) {
|
|
118
|
+
for (const [statusCode, response] of Object.entries(filteredOperation.responses)){
|
|
119
|
+
const responseObj = response;
|
|
120
|
+
if (responseObj?.content) {
|
|
121
|
+
const content = responseObj.content;
|
|
122
|
+
for (const [mediaType, mediaTypeObj] of Object.entries(content)){
|
|
123
|
+
const media = mediaTypeObj;
|
|
124
|
+
if (media?.examples && typeof media.examples === 'object') {
|
|
125
|
+
const examples = media.examples;
|
|
126
|
+
for (const exampleName of examplesToExclude){
|
|
127
|
+
delete examples[exampleName];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
filteredPathItem[method] = filteredOperation;
|
|
135
|
+
} else {
|
|
136
|
+
filteredPathItem[method] = operation;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
filteredPaths[path] = filteredPathItem;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...document,
|
|
143
|
+
paths: filteredPaths
|
|
144
|
+
};
|
|
145
|
+
}
|
|
87
146
|
/**
|
|
88
147
|
* Check if a path matches a pattern (supports wildcards)
|
|
89
148
|
*/ function pathMatchesPattern(path, pattern) {
|
|
@@ -179,6 +238,15 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
|
179
238
|
if (config.excludeQueryParameters?.length) {
|
|
180
239
|
document = filterQueryParameters(document, config.excludeQueryParameters);
|
|
181
240
|
}
|
|
241
|
+
// Filter out excluded examples
|
|
242
|
+
if (config.excludeExamples?.length) {
|
|
243
|
+
document = filterExamples(document, config.excludeExamples);
|
|
244
|
+
}
|
|
182
245
|
SwaggerModule.setup(config.path, app, document);
|
|
183
246
|
});
|
|
184
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Setup Swagger docs with variadic module configs
|
|
250
|
+
*/ export function setupSwaggerDocs(app, ...modules) {
|
|
251
|
+
setupModuleSwaggerDocs(app, modules);
|
|
252
|
+
}
|
package/fesm/docs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*/ export { };
|
|
2
|
+
* Entity Contract Interfaces
|
|
3
|
+
*
|
|
4
|
+
* These interfaces define the standard fields that entities should have.
|
|
5
|
+
* Use them as contracts when implementing new entities.
|
|
6
|
+
*/ /** Base entity with UUID, timestamps, and soft delete */ /** Entity with company relation (for multi-company support) */ export { };
|
package/fesm/migration/cli.js
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
* Migration CLI Entry Point
|
|
4
|
-
*
|
|
5
|
-
* Main executable for running TypeORM migrations via npm scripts.
|
|
6
|
-
*
|
|
7
|
-
* Usage (via package.json scripts):
|
|
8
|
-
* - npm run migration generate --name=CreateUsers
|
|
9
|
-
* - npm run migration run
|
|
10
|
-
* - npm run migration revert
|
|
11
|
-
* - npm run migration status
|
|
12
|
-
* - npm run migration run:all (multi-tenant)
|
|
13
|
-
* - npm run migration revert:all (multi-tenant)
|
|
14
|
-
* - npm run migration status:all (multi-tenant)
|
|
15
|
-
*
|
|
16
|
-
* Environment Variables:
|
|
17
|
-
* - TENANT_ID: Target specific tenant (optional)
|
|
18
|
-
*
|
|
19
|
-
* This file is the executable entry point referenced in package.json.
|
|
20
|
-
* The actual CLI logic is in migration.cli.ts (runMigrationCli function).
|
|
21
|
-
*/ import { runMigrationCli } from './migration.cli';
|
|
2
|
+
import { runMigrationCli } from './migration.cli';
|
|
22
3
|
runMigrationCli().catch((err)=>{
|
|
23
4
|
console.error('CLI Error:', err.message);
|
|
24
5
|
process.exit(1);
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { DataSource } from 'typeorm';
|
|
2
2
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
|
3
3
|
import { buildDataSourceOptions, getDatabaseForTenant, getMigrationsGlobPattern, resolveEntities } from '../utils/datasource-config.builder';
|
|
4
|
-
|
|
5
|
-
* Create a DataSource for migrations
|
|
6
|
-
*
|
|
7
|
-
* @param options - Configuration options
|
|
8
|
-
* @param options.config - Migration configuration
|
|
9
|
-
* @param options.tenantId - Optional tenant ID for multi-tenant mode
|
|
10
|
-
* @returns DataSource instance (not initialized)
|
|
11
|
-
*/ export function createMigrationDataSource(options) {
|
|
4
|
+
export function createMigrationDataSource(options) {
|
|
12
5
|
const { config, tenantId } = options;
|
|
13
6
|
const { database, tenant, tenantConfig } = getDatabaseForTenant(config, tenantId);
|
|
14
|
-
// Log target info
|
|
15
7
|
console.log(`\n📦 Database: ${database}${tenant ? ` [${tenant.name}]` : ' [default]'}`);
|
|
16
8
|
console.log(`📁 Migrations: migrations/${tenantId || 'default'}/`);
|
|
17
|
-
if (config.bootstrapAppConfig?.databaseMode
|
|
9
|
+
if (config.bootstrapAppConfig?.databaseMode === 'multi-tenant' && !tenantId && config.tenants && config.tenants.length > 0) {
|
|
18
10
|
console.log(`\n💡 Multi-tenant mode. Available tenants:`);
|
|
19
11
|
config.tenants.forEach((t)=>console.log(` - TENANT_ID=${t.id} → ${t.database}`));
|
|
20
12
|
}
|
|
@@ -29,7 +21,6 @@ import { buildDataSourceOptions, getDatabaseForTenant, getMigrationsGlobPattern,
|
|
|
29
21
|
...config.defaultDatabaseConfig,
|
|
30
22
|
database
|
|
31
23
|
};
|
|
32
|
-
// Resolve entities based on tenant config
|
|
33
24
|
const entities = resolveEntities(config, tenantConfig);
|
|
34
25
|
const dsOptions = buildDataSourceOptions(dbConfig, entities, getMigrationsGlobPattern(config.migrationsPath, tenantId), config.migrationsTableName);
|
|
35
26
|
return new DataSource({
|
|
@@ -37,15 +28,7 @@ import { buildDataSourceOptions, getDatabaseForTenant, getMigrationsGlobPattern,
|
|
|
37
28
|
namingStrategy: new SnakeNamingStrategy()
|
|
38
29
|
});
|
|
39
30
|
}
|
|
40
|
-
|
|
41
|
-
* Initialize and return a DataSource
|
|
42
|
-
*
|
|
43
|
-
* Creates a DataSource and initializes the connection.
|
|
44
|
-
* The connection should be destroyed after use with `ds.destroy()`.
|
|
45
|
-
*
|
|
46
|
-
* @param options - Configuration options
|
|
47
|
-
* @returns Initialized DataSource instance
|
|
48
|
-
*/ export async function initializeDataSource(options) {
|
|
31
|
+
export async function initializeDataSource(options) {
|
|
49
32
|
const ds = createMigrationDataSource(options);
|
|
50
33
|
await ds.initialize();
|
|
51
34
|
return ds;
|
package/fesm/migration/index.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migration Module Exports
|
|
3
|
-
*
|
|
4
|
-
* Public API for TypeORM migration management in single and multi-tenant databases.
|
|
5
|
-
*
|
|
6
|
-
* Main exports:
|
|
7
|
-
* - createMigrationDataSource: Create DataSource for migrations
|
|
8
|
-
* - initializeDataSource: Create and initialize DataSource
|
|
9
|
-
* - runMigrationCli: Programmatic CLI execution
|
|
10
|
-
*
|
|
11
|
-
* The CLI entry point is at ./cli.ts (used by npm scripts)
|
|
12
|
-
*/ // DataSource factory for migration config files
|
|
13
1
|
export { createMigrationDataSource, initializeDataSource } from './datasource.factory';
|
|
14
|
-
// CLI service for programmatic usage
|
|
15
2
|
export { runMigrationCli } from './migration.cli';
|
|
16
|
-
// Migration runner functions (optional, for advanced usage)
|
|
17
3
|
export { generateMigration, runMigrations, revertMigration, migrationStatus, runForAllTenants, ensureMigrationsFolder } from './migration.runner';
|
|
@@ -2,23 +2,7 @@ import * as path from 'path';
|
|
|
2
2
|
import { envConfig } from '../config';
|
|
3
3
|
import { getDatabaseForTenant, getMigrationsFolderPath } from '../utils';
|
|
4
4
|
import { generateMigration, migrationStatus, revertMigration, runForAllTenants, runMigrations } from './migration.runner';
|
|
5
|
-
|
|
6
|
-
* Migration CLI Service
|
|
7
|
-
*
|
|
8
|
-
* Handles migration command execution for single and multi-tenant databases.
|
|
9
|
-
*
|
|
10
|
-
* Commands:
|
|
11
|
-
* - generate: Create new migration
|
|
12
|
-
* - run: Execute pending migrations
|
|
13
|
-
* - revert: Rollback last migration
|
|
14
|
-
* - status: Check migration status
|
|
15
|
-
* - run:all/revert:all/status:all: Batch operations for all tenants
|
|
16
|
-
*
|
|
17
|
-
* Configuration:
|
|
18
|
-
* - TENANT_ID: Target tenant (optional, defaults to default database)
|
|
19
|
-
* - --config=<path>: Migration config file path
|
|
20
|
-
* - --datasource=<path>: DataSource file path for TypeORM CLI
|
|
21
|
-
*/ function showHelp() {
|
|
5
|
+
function showHelp() {
|
|
22
6
|
console.log(`
|
|
23
7
|
Migration CLI
|
|
24
8
|
|
|
@@ -4,26 +4,37 @@ import * as path from 'path';
|
|
|
4
4
|
import { envConfig } from '../config';
|
|
5
5
|
import { getActiveTenants, getDatabaseForTenant, getMigrationsFolderPath } from '../utils';
|
|
6
6
|
import { initializeDataSource } from './datasource.factory';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
function getLabel(tenantId) {
|
|
8
|
+
return tenantId ? `[${tenantId}]` : '[default]';
|
|
9
|
+
}
|
|
10
|
+
async function withDataSource(config, tenantId, operation) {
|
|
11
|
+
const { database } = getDatabaseForTenant(config, tenantId);
|
|
12
|
+
const label = getLabel(tenantId);
|
|
13
|
+
try {
|
|
14
|
+
const ds = await initializeDataSource({
|
|
15
|
+
config,
|
|
16
|
+
tenantId
|
|
17
|
+
});
|
|
18
|
+
const partial = await operation(ds, label);
|
|
19
|
+
await ds.destroy();
|
|
20
|
+
return {
|
|
21
|
+
tenantId,
|
|
22
|
+
database,
|
|
23
|
+
success: true,
|
|
24
|
+
...partial
|
|
25
|
+
};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
28
|
+
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
29
|
+
return {
|
|
30
|
+
tenantId,
|
|
31
|
+
database,
|
|
32
|
+
success: false,
|
|
33
|
+
error: errorMessage
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function ensureMigrationsFolder(basePath, tenantId) {
|
|
27
38
|
const folder = getMigrationsFolderPath(basePath, tenantId);
|
|
28
39
|
if (!fs.existsSync(folder)) {
|
|
29
40
|
fs.mkdirSync(folder, {
|
|
@@ -33,16 +44,7 @@ import { initializeDataSource } from './datasource.factory';
|
|
|
33
44
|
}
|
|
34
45
|
return folder;
|
|
35
46
|
}
|
|
36
|
-
|
|
37
|
-
* Generate a new migration file
|
|
38
|
-
*
|
|
39
|
-
* Uses TypeORM CLI to generate migration based on entity changes.
|
|
40
|
-
*
|
|
41
|
-
* @param config - Migration configuration
|
|
42
|
-
* @param name - Migration name (e.g., "CreateUsers")
|
|
43
|
-
* @param tenantId - Optional tenant ID for multi-tenant mode
|
|
44
|
-
* @param datasourcePath - Optional path to datasource file
|
|
45
|
-
*/ export async function generateMigration(config, name, tenantId, datasourcePath) {
|
|
47
|
+
export async function generateMigration(config, name, tenantId, datasourcePath) {
|
|
46
48
|
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
47
49
|
const folder = ensureMigrationsFolder(config.migrationsPath, tenantId);
|
|
48
50
|
console.log(`${label} Generating migration: ${name}`);
|
|
@@ -72,24 +74,9 @@ function findDatasourcePath() {
|
|
|
72
74
|
}
|
|
73
75
|
throw new Error('Could not find datasource file. Specify with --datasource=<path>');
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
* Executes all pending migrations for the specified tenant or default database.
|
|
79
|
-
*
|
|
80
|
-
* @param config - Migration configuration
|
|
81
|
-
* @param tenantId - Optional tenant ID for multi-tenant mode
|
|
82
|
-
* @returns Migration result with success status
|
|
83
|
-
*/ export async function runMigrations(config, tenantId) {
|
|
84
|
-
const { database } = getDatabaseForTenant(config, tenantId);
|
|
85
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
86
|
-
try {
|
|
87
|
-
const ds = await initializeDataSource({
|
|
88
|
-
config,
|
|
89
|
-
tenantId
|
|
90
|
-
});
|
|
77
|
+
export async function runMigrations(config, tenantId) {
|
|
78
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
91
79
|
const migrations = await ds.runMigrations();
|
|
92
|
-
await ds.destroy();
|
|
93
80
|
if (migrations.length === 0) {
|
|
94
81
|
console.log(`${label} No pending migrations.`);
|
|
95
82
|
} else {
|
|
@@ -97,105 +84,29 @@ function findDatasourcePath() {
|
|
|
97
84
|
migrations.forEach((m)=>console.log(` - ${m.name}`));
|
|
98
85
|
}
|
|
99
86
|
return {
|
|
100
|
-
tenantId,
|
|
101
|
-
database,
|
|
102
|
-
success: true,
|
|
103
87
|
migrationsRun: migrations.map((m)=>m.name)
|
|
104
88
|
};
|
|
105
|
-
}
|
|
106
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
-
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
108
|
-
return {
|
|
109
|
-
tenantId,
|
|
110
|
-
database,
|
|
111
|
-
success: false,
|
|
112
|
-
error: errorMessage
|
|
113
|
-
};
|
|
114
|
-
}
|
|
89
|
+
});
|
|
115
90
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*
|
|
119
|
-
* Rolls back the most recently executed migration.
|
|
120
|
-
*
|
|
121
|
-
* @param config - Migration configuration
|
|
122
|
-
* @param tenantId - Optional tenant ID for multi-tenant mode
|
|
123
|
-
* @returns Migration result with success status
|
|
124
|
-
*/ export async function revertMigration(config, tenantId) {
|
|
125
|
-
const { database } = getDatabaseForTenant(config, tenantId);
|
|
126
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
127
|
-
try {
|
|
128
|
-
const ds = await initializeDataSource({
|
|
129
|
-
config,
|
|
130
|
-
tenantId
|
|
131
|
-
});
|
|
91
|
+
export async function revertMigration(config, tenantId) {
|
|
92
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
132
93
|
await ds.undoLastMigration();
|
|
133
|
-
await ds.destroy();
|
|
134
94
|
console.log(`${label} ✓ Reverted!`);
|
|
135
|
-
return {
|
|
136
|
-
|
|
137
|
-
database,
|
|
138
|
-
success: true
|
|
139
|
-
};
|
|
140
|
-
} catch (error) {
|
|
141
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
142
|
-
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
143
|
-
return {
|
|
144
|
-
tenantId,
|
|
145
|
-
database,
|
|
146
|
-
success: false,
|
|
147
|
-
error: errorMessage
|
|
148
|
-
};
|
|
149
|
-
}
|
|
95
|
+
return {};
|
|
96
|
+
});
|
|
150
97
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
*
|
|
154
|
-
* Shows whether there are pending migrations to run.
|
|
155
|
-
*
|
|
156
|
-
* @param config - Migration configuration
|
|
157
|
-
* @param tenantId - Optional tenant ID for multi-tenant mode
|
|
158
|
-
* @returns Migration result with status information
|
|
159
|
-
*/ export async function migrationStatus(config, tenantId) {
|
|
160
|
-
const { database } = getDatabaseForTenant(config, tenantId);
|
|
161
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
162
|
-
try {
|
|
163
|
-
const ds = await initializeDataSource({
|
|
164
|
-
config,
|
|
165
|
-
tenantId
|
|
166
|
-
});
|
|
98
|
+
export async function migrationStatus(config, tenantId) {
|
|
99
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
167
100
|
const hasPending = await ds.showMigrations();
|
|
168
|
-
await ds.destroy();
|
|
169
101
|
console.log(`${label} Pending: ${hasPending ? 'Yes' : 'No'}`);
|
|
170
102
|
return {
|
|
171
|
-
tenantId,
|
|
172
|
-
database,
|
|
173
|
-
success: true,
|
|
174
103
|
migrationsRun: [
|
|
175
104
|
hasPending ? 'Has pending' : 'Up to date'
|
|
176
105
|
]
|
|
177
106
|
};
|
|
178
|
-
}
|
|
179
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
180
|
-
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
181
|
-
return {
|
|
182
|
-
tenantId,
|
|
183
|
-
database,
|
|
184
|
-
success: false,
|
|
185
|
-
error: errorMessage
|
|
186
|
-
};
|
|
187
|
-
}
|
|
107
|
+
});
|
|
188
108
|
}
|
|
189
|
-
|
|
190
|
-
* Run migration command for all tenants
|
|
191
|
-
*
|
|
192
|
-
* Executes the specified command for all active tenants in batch mode.
|
|
193
|
-
* Only works in multi-tenant database mode.
|
|
194
|
-
*
|
|
195
|
-
* @param config - Migration configuration
|
|
196
|
-
* @param command - Command to run: 'run', 'revert', or 'status'
|
|
197
|
-
* @returns Array of migration results for each tenant
|
|
198
|
-
*/ export async function runForAllTenants(config, command) {
|
|
109
|
+
export async function runForAllTenants(config, command) {
|
|
199
110
|
const tenants = getActiveTenants(config);
|
|
200
111
|
console.log(`\n🔄 Running '${command}' for ${tenants.length} tenant(s)...\n`);
|
|
201
112
|
const results = [];
|