@flusys/nestjs-core 1.0.0-beta → 1.0.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 +507 -61
- 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 +96 -142
- package/cjs/seeders/entity-reader.js +0 -17
- package/cjs/seeders/field-patterns.js +172 -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 +96 -149
- package/fesm/seeders/entity-reader.js +0 -17
- package/fesm/seeders/field-patterns.js +143 -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/base-entity.interface.d.ts +3 -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
|
@@ -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 = [];
|
|
@@ -11,51 +11,16 @@ function _define_property(obj, key, value) {
|
|
|
11
11
|
}
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
17
|
-
* Abstract base class for all entity seeders.
|
|
18
|
-
* Provides common methods for data generation, clearing, and counting.
|
|
19
|
-
*
|
|
20
|
-
* Usage:
|
|
21
|
-
* ```typescript
|
|
22
|
-
* export class UserSeeder extends BaseSeeder<User> {
|
|
23
|
-
* constructor(dataSource: DataSource) {
|
|
24
|
-
* super(dataSource, User);
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* async generate(count: number): Promise<User[]> {
|
|
28
|
-
* const users: User[] = [];
|
|
29
|
-
* for (let i = 0; i < count; i++) {
|
|
30
|
-
* users.push(this.repository.create({
|
|
31
|
-
* name: faker.person.fullName(),
|
|
32
|
-
* email: faker.internet.email(),
|
|
33
|
-
* }));
|
|
34
|
-
* }
|
|
35
|
-
* return this.repository.save(users);
|
|
36
|
-
* }
|
|
37
|
-
* }
|
|
38
|
-
* ```
|
|
39
|
-
*/ export class BaseSeeder {
|
|
40
|
-
/**
|
|
41
|
-
* Clear all records from the entity table
|
|
42
|
-
* Respects soft delete if entity has deletedAt column
|
|
43
|
-
* @param hard If true, perform hard delete (ignore soft delete)
|
|
44
|
-
*/ async clear(hard = false) {
|
|
14
|
+
export class BaseSeeder {
|
|
15
|
+
async clear(hard = false) {
|
|
45
16
|
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
46
17
|
if (hard || !hasSoftDelete) {
|
|
47
|
-
// Hard delete - remove all records
|
|
48
18
|
await this.repository.clear();
|
|
49
19
|
} else {
|
|
50
|
-
// Soft delete - set deletedAt
|
|
51
20
|
await this.repository.createQueryBuilder().softDelete().where('id IS NOT NULL').execute();
|
|
52
21
|
}
|
|
53
22
|
}
|
|
54
|
-
|
|
55
|
-
* Get count of existing records
|
|
56
|
-
* @param includeDeleted If true, count soft-deleted records
|
|
57
|
-
* @returns Total record count
|
|
58
|
-
*/ async count(includeDeleted = false) {
|
|
23
|
+
async count(includeDeleted = false) {
|
|
59
24
|
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
60
25
|
if (hasSoftDelete && !includeDeleted) {
|
|
61
26
|
return this.repository.count({
|
|
@@ -66,22 +31,13 @@ function _define_property(obj, key, value) {
|
|
|
66
31
|
}
|
|
67
32
|
return this.repository.count();
|
|
68
33
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* @returns True if no records exist
|
|
72
|
-
*/ async isEmpty() {
|
|
73
|
-
const total = await this.count();
|
|
74
|
-
return total === 0;
|
|
34
|
+
async isEmpty() {
|
|
35
|
+
return await this.count() === 0;
|
|
75
36
|
}
|
|
76
|
-
|
|
77
|
-
* Get entity name for logging
|
|
78
|
-
*/ getEntityName() {
|
|
37
|
+
getEntityName() {
|
|
79
38
|
return this.metadata.tableName;
|
|
80
39
|
}
|
|
81
|
-
|
|
82
|
-
* Wrap operation in transaction
|
|
83
|
-
* @param operation Function to execute in transaction
|
|
84
|
-
*/ async withTransaction(operation) {
|
|
40
|
+
async withTransaction(operation) {
|
|
85
41
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
86
42
|
await queryRunner.connect();
|
|
87
43
|
await queryRunner.startTransaction();
|