@flusys/nestjs-core 1.1.0-beta → 2.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.
Files changed (56) hide show
  1. package/README.md +505 -63
  2. package/cjs/config/env-config.service.js +1 -1
  3. package/cjs/docs/docs.config.js +77 -3
  4. package/cjs/docs/index.js +0 -1
  5. package/cjs/interfaces/base-entity.interface.js +5 -3
  6. package/cjs/interfaces/database.interface.js +1 -3
  7. package/cjs/migration/datasource.factory.js +1 -3
  8. package/cjs/migration/index.js +0 -12
  9. package/cjs/migration/migration.cli.js +1 -17
  10. package/cjs/migration/migration.runner.js +37 -65
  11. package/cjs/seeders/base-seeder.js +6 -25
  12. package/cjs/seeders/cli.js +65 -172
  13. package/cjs/seeders/data-generator.js +96 -142
  14. package/cjs/seeders/entity-reader.js +0 -17
  15. package/cjs/seeders/field-patterns.js +172 -0
  16. package/cjs/seeders/index.js +16 -8
  17. package/cjs/seeders/seed-config.js +9 -48
  18. package/cjs/seeders/seed-runner.js +8 -14
  19. package/cjs/utils/datasource-config.builder.js +2 -14
  20. package/docs/docs.config.d.ts +7 -0
  21. package/docs/index.d.ts +0 -1
  22. package/fesm/config/env-config.service.js +1 -1
  23. package/fesm/docs/docs.config.js +68 -0
  24. package/fesm/docs/index.js +0 -1
  25. package/fesm/interfaces/app-config.interfaces.js +1 -3
  26. package/fesm/interfaces/base-entity.interface.js +5 -5
  27. package/fesm/interfaces/database.interface.js +1 -5
  28. package/fesm/migration/cli.js +1 -20
  29. package/fesm/migration/datasource.factory.js +3 -20
  30. package/fesm/migration/index.js +0 -14
  31. package/fesm/migration/migration.cli.js +1 -17
  32. package/fesm/migration/migration.runner.js +43 -132
  33. package/fesm/seeders/base-seeder.js +7 -51
  34. package/fesm/seeders/cli.js +65 -182
  35. package/fesm/seeders/data-generator.js +96 -149
  36. package/fesm/seeders/entity-reader.js +0 -17
  37. package/fesm/seeders/field-patterns.js +143 -0
  38. package/fesm/seeders/index.js +3 -7
  39. package/fesm/seeders/seed-config.js +9 -59
  40. package/fesm/seeders/seed-runner.js +8 -14
  41. package/fesm/utils/datasource-config.builder.js +2 -13
  42. package/interfaces/base-entity.interface.d.ts +3 -0
  43. package/package.json +2 -2
  44. package/seeders/data-generator.d.ts +1 -1
  45. package/seeders/entity-reader.d.ts +0 -1
  46. package/seeders/field-patterns.d.ts +12 -0
  47. package/seeders/index.d.ts +3 -3
  48. package/seeders/seed-config.d.ts +1 -0
  49. package/seeders/seed-runner.d.ts +1 -0
  50. package/utils/datasource-config.builder.d.ts +0 -1
  51. package/cjs/docs/docs.setup.js +0 -14
  52. package/cjs/seeders/template-generator.js +0 -297
  53. package/docs/docs.setup.d.ts +0 -3
  54. package/fesm/docs/docs.setup.js +0 -4
  55. package/fesm/seeders/template-generator.js +0 -257
  56. 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
- // Get seeder (custom or generic)
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
- // Use custom seeder if available, otherwise generic
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
  }
@@ -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
@@ -1,2 +1 @@
1
1
  export * from './docs.config';
2
- export * from './docs.setup';
@@ -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';
@@ -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
+ }
@@ -1,2 +1 @@
1
1
  export * from './docs.config';
2
- export * from './docs.setup';
@@ -1,3 +1 @@
1
- /**
2
- * Async module options - common pattern for NestJS async module registration
3
- */ export { };
1
+ /** Async module options pattern */ export { };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Base Entity Interface
3
- * All entities should implement this for consistency
4
- */ /**
5
- * Entity with metadata
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 { };
@@ -1,5 +1 @@
1
- /**
2
- * Supported database types
3
- */ /**
4
- * DataSource build options for TypeORM
5
- */ export { };
1
+ export { };
@@ -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 == 'multi-tenant' && !tenantId && config.tenants && config.tenants.length > 0) {
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;
@@ -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
- * Migration Runner Functions
9
- *
10
- * Core functions for executing migration operations.
11
- * Supports both single and multi-tenant database configurations.
12
- *
13
- * Functions:
14
- * - ensureMigrationsFolder: Create migrations directory
15
- * - generateMigration: Create new migration file
16
- * - runMigrations: Execute pending migrations
17
- * - revertMigration: Rollback last migration
18
- * - migrationStatus: Check migration status
19
- * - runForAllTenants: Batch operations for all tenants
20
- */ /**
21
- * Ensure migrations folder exists
22
- *
23
- * @param basePath - Base migrations path
24
- * @param tenantId - Optional tenant ID
25
- * @returns Full path to migrations folder
26
- */ export function ensureMigrationsFolder(basePath, tenantId) {
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
- * Run pending migrations
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
- } catch (error) {
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
- * Revert last migration
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
- tenantId,
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
- * Check migration status
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
- } catch (error) {
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
- * Base Seeder Class
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
- * Check if entity has any records
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();