@flusys/nestjs-core 7.0.2 → 8.0.0-beta.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.
Files changed (70) hide show
  1. package/README.md +2 -2
  2. package/config/index.d.ts +1 -1
  3. package/constants/index.d.ts +1 -1
  4. package/docs/docs.config.d.ts +1 -1
  5. package/docs/index.d.ts +1 -1
  6. package/fesm/229.js +729 -0
  7. package/fesm/config/index.js +126 -1
  8. package/fesm/constants/index.js +29 -1
  9. package/fesm/docs/index.js +288 -1
  10. package/fesm/index.js +124 -5
  11. package/fesm/interfaces/index.js +18 -4
  12. package/fesm/migration/cli.js +19 -2
  13. package/fesm/migration/index.js +45 -3
  14. package/fesm/runtime.js +85 -0
  15. package/fesm/utils/index.js +131 -1
  16. package/index.d.ts +5 -5
  17. package/interfaces/app-config.interfaces.d.ts +2 -1
  18. package/interfaces/database.interface.d.ts +1 -0
  19. package/interfaces/index.d.ts +4 -4
  20. package/interfaces/migration.interface.d.ts +2 -2
  21. package/migration/cli.d.ts +0 -1
  22. package/migration/datasource.factory.d.ts +1 -1
  23. package/migration/index.d.ts +3 -3
  24. package/migration/migration.cli.d.ts +1 -1
  25. package/migration/migration.runner.d.ts +1 -1
  26. package/package.json +25 -33
  27. package/utils/datasource-config.builder.d.ts +2 -2
  28. package/utils/index.d.ts +1 -1
  29. package/cjs/config/env-config.service.js +0 -162
  30. package/cjs/config/env-config.service.spec.js +0 -325
  31. package/cjs/config/index.js +0 -18
  32. package/cjs/constants/database.constants.js +0 -20
  33. package/cjs/constants/index.js +0 -18
  34. package/cjs/docs/docs.config.js +0 -276
  35. package/cjs/docs/docs.config.spec.js +0 -923
  36. package/cjs/docs/index.js +0 -18
  37. package/cjs/index.js +0 -23
  38. package/cjs/interfaces/app-config.interfaces.js +0 -4
  39. package/cjs/interfaces/base-entity.interface.js +0 -9
  40. package/cjs/interfaces/database.interface.js +0 -4
  41. package/cjs/interfaces/index.js +0 -21
  42. package/cjs/interfaces/migration.interface.js +0 -4
  43. package/cjs/migration/cli.js +0 -10
  44. package/cjs/migration/datasource.factory.js +0 -43
  45. package/cjs/migration/datasource.factory.spec.js +0 -113
  46. package/cjs/migration/index.js +0 -45
  47. package/cjs/migration/migration.cli.js +0 -231
  48. package/cjs/migration/migration.cli.spec.js +0 -293
  49. package/cjs/migration/migration.runner.js +0 -276
  50. package/cjs/migration/migration.runner.spec.js +0 -268
  51. package/cjs/utils/datasource-config.builder.js +0 -103
  52. package/cjs/utils/datasource-config.builder.spec.js +0 -232
  53. package/cjs/utils/index.js +0 -18
  54. package/fesm/config/env-config.service.js +0 -112
  55. package/fesm/config/env-config.service.spec.js +0 -321
  56. package/fesm/constants/database.constants.js +0 -2
  57. package/fesm/docs/docs.config.js +0 -258
  58. package/fesm/docs/docs.config.spec.js +0 -919
  59. package/fesm/interfaces/app-config.interfaces.js +0 -1
  60. package/fesm/interfaces/base-entity.interface.js +0 -6
  61. package/fesm/interfaces/database.interface.js +0 -1
  62. package/fesm/interfaces/migration.interface.js +0 -1
  63. package/fesm/migration/datasource.factory.js +0 -25
  64. package/fesm/migration/datasource.factory.spec.js +0 -109
  65. package/fesm/migration/migration.cli.js +0 -180
  66. package/fesm/migration/migration.cli.spec.js +0 -289
  67. package/fesm/migration/migration.runner.js +0 -202
  68. package/fesm/migration/migration.runner.spec.js +0 -264
  69. package/fesm/utils/datasource-config.builder.js +0 -84
  70. package/fesm/utils/datasource-config.builder.spec.js +0 -228
@@ -1,202 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import { DataSource } from 'typeorm';
5
- import { envConfig } from '../config';
6
- import { getActiveTenants, getDatabaseForTenant, getMigrationsFolderPath, resolveDatabaseConnection } from '../utils';
7
- import { initializeDataSource } from './datasource.factory';
8
- function getLabel(tenantId) {
9
- return tenantId ? `[${tenantId}]` : '[default]';
10
- }
11
- async function withDataSource(config, tenantId, operation) {
12
- const { database } = getDatabaseForTenant(config, tenantId);
13
- const label = getLabel(tenantId);
14
- try {
15
- const ds = await initializeDataSource({
16
- config,
17
- tenantId
18
- });
19
- const partial = await operation(ds, label);
20
- await ds.destroy();
21
- return {
22
- tenantId,
23
- database,
24
- success: true,
25
- ...partial
26
- };
27
- } catch (error) {
28
- const errorMessage = error instanceof Error ? error.message : String(error);
29
- console.error(`${label} ✗ Failed: ${errorMessage}`);
30
- return {
31
- tenantId,
32
- database,
33
- success: false,
34
- error: errorMessage
35
- };
36
- }
37
- }
38
- export function ensureMigrationsFolder(basePath, tenantId) {
39
- const folder = getMigrationsFolderPath(basePath, tenantId);
40
- if (!fs.existsSync(folder)) {
41
- fs.mkdirSync(folder, {
42
- recursive: true
43
- });
44
- console.log(`📁 Created: ${folder}`);
45
- }
46
- return folder;
47
- }
48
- export async function generateMigration(config, name, tenantId, datasourcePath) {
49
- const label = tenantId ? `[${tenantId}]` : '[default]';
50
- const folder = ensureMigrationsFolder(config.migrationsPath, tenantId);
51
- console.log(`${label} Generating migration: ${name}`);
52
- console.log(`${label} Target folder: ${folder}`);
53
- const dsPath = datasourcePath || findDatasourcePath();
54
- const migrationFile = path.join(folder, name);
55
- const command = `ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli migration:generate "${migrationFile}" -d "${dsPath}"`;
56
- execSync(command, {
57
- stdio: 'inherit',
58
- env: {
59
- ...envConfig.getEnv(),
60
- TENANT_ID: tenantId || ''
61
- },
62
- cwd: process.cwd()
63
- });
64
- console.log(`${label} ✓ Generated!`);
65
- }
66
- function findDatasourcePath() {
67
- const possiblePaths = [
68
- 'src/persistence/migration.config.ts',
69
- 'projects/flusysnest/src/persistence/migration.config.ts'
70
- ];
71
- for (const p of possiblePaths){
72
- if (fs.existsSync(path.join(process.cwd(), p))) {
73
- return p;
74
- }
75
- }
76
- throw new Error('Could not find datasource file. Specify with --datasource=<path>');
77
- }
78
- export async function runMigrations(config, tenantId) {
79
- return withDataSource(config, tenantId, async (ds, label)=>{
80
- const migrations = await ds.runMigrations();
81
- if (migrations.length === 0) {
82
- console.log(`${label} No pending migrations.`);
83
- } else {
84
- console.log(`${label} ✓ Executed ${migrations.length} migration(s):`);
85
- migrations.forEach((m)=>console.log(` - ${m.name}`));
86
- }
87
- return {
88
- migrationsRun: migrations.map((m)=>m.name)
89
- };
90
- });
91
- }
92
- export async function revertMigration(config, tenantId) {
93
- return withDataSource(config, tenantId, async (ds, label)=>{
94
- await ds.undoLastMigration();
95
- console.log(`${label} ✓ Reverted!`);
96
- return {};
97
- });
98
- }
99
- function clearMigrationFiles(basePath, tenantId, label) {
100
- const folder = getMigrationsFolderPath(basePath, tenantId);
101
- if (!fs.existsSync(folder)) return;
102
- const files = fs.readdirSync(folder).filter((f)=>f.endsWith('.ts') || f.endsWith('.js'));
103
- files.forEach((file)=>fs.rmSync(path.join(folder, file)));
104
- console.log(`${label} ✓ Removed ${files.length} migration file(s) from ${folder}`);
105
- }
106
- async function dropAndRecreateDatabase(dbConfig, label) {
107
- const { type, database } = dbConfig;
108
- if (type === 'sqlite') {
109
- if (database && fs.existsSync(database)) fs.rmSync(database);
110
- console.log(`${label} ✓ Deleted sqlite file`);
111
- return;
112
- }
113
- if (type !== 'mysql' && type !== 'mariadb' && type !== 'postgres') {
114
- throw new Error(`Database reset is not supported for type '${type}'`);
115
- }
116
- const adminDs = new DataSource({
117
- type,
118
- host: dbConfig.host,
119
- port: dbConfig.port,
120
- username: dbConfig.username,
121
- password: dbConfig.password,
122
- database: type === 'postgres' ? 'postgres' : undefined
123
- });
124
- await adminDs.initialize();
125
- try {
126
- if (type === 'postgres') {
127
- await adminDs.query(`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()`, [
128
- database
129
- ]);
130
- await adminDs.query(`DROP DATABASE IF EXISTS "${database}"`);
131
- await adminDs.query(`CREATE DATABASE "${database}"`);
132
- } else {
133
- await adminDs.query(`DROP DATABASE IF EXISTS \`${database}\``);
134
- await adminDs.query(`CREATE DATABASE \`${database}\` CHARACTER SET utf8mb4`);
135
- }
136
- console.log(`${label} ✓ Database '${database}' dropped and recreated`);
137
- } finally{
138
- await adminDs.destroy();
139
- }
140
- }
141
- export async function resetDatabase(config, tenantId) {
142
- const { database } = getDatabaseForTenant(config, tenantId);
143
- const label = getLabel(tenantId);
144
- try {
145
- clearMigrationFiles(config.migrationsPath, tenantId, label);
146
- const dbConfig = resolveDatabaseConnection(config, tenantId);
147
- await dropAndRecreateDatabase(dbConfig, label);
148
- return {
149
- tenantId,
150
- database,
151
- success: true
152
- };
153
- } catch (error) {
154
- const errorMessage = error instanceof Error ? error.message : String(error);
155
- console.error(`${label} ✗ Failed: ${errorMessage}`);
156
- return {
157
- tenantId,
158
- database,
159
- success: false,
160
- error: errorMessage
161
- };
162
- }
163
- }
164
- export async function migrationStatus(config, tenantId) {
165
- return withDataSource(config, tenantId, async (ds, label)=>{
166
- const hasPending = await ds.showMigrations();
167
- console.log(`${label} Pending: ${hasPending ? 'Yes' : 'No'}`);
168
- return {
169
- migrationsRun: [
170
- hasPending ? 'Has pending' : 'Up to date'
171
- ]
172
- };
173
- });
174
- }
175
- export async function runForAllTenants(config, command) {
176
- const tenants = getActiveTenants(config);
177
- console.log(`\n🔄 Running '${command}' for ${tenants.length} tenant(s)...\n`);
178
- const results = [];
179
- for (const tenant of tenants){
180
- let result;
181
- switch(command){
182
- case 'run':
183
- result = await runMigrations(config, tenant.id);
184
- break;
185
- case 'revert':
186
- result = await revertMigration(config, tenant.id);
187
- break;
188
- case 'status':
189
- result = await migrationStatus(config, tenant.id);
190
- break;
191
- }
192
- results.push(result);
193
- console.log('');
194
- }
195
- console.log('\n=== Summary ===');
196
- results.forEach((r)=>{
197
- const icon = r.success ? '✓' : '✗';
198
- const msg = r.error ? ` - ${r.error}` : '';
199
- console.log(`${icon} ${r.tenantId || 'default'}${msg}`);
200
- });
201
- return results;
202
- }
@@ -1,264 +0,0 @@
1
- const mockExistsSync = jest.fn();
2
- const mockMkdirSync = jest.fn();
3
- const mockReaddirSync = jest.fn();
4
- const mockRmSync = jest.fn();
5
- const mockExecSync = jest.fn();
6
- jest.mock('fs', ()=>({
7
- existsSync: (...args)=>mockExistsSync(...args),
8
- mkdirSync: (...args)=>mockMkdirSync(...args),
9
- readdirSync: (...args)=>mockReaddirSync(...args),
10
- rmSync: (...args)=>mockRmSync(...args)
11
- }));
12
- jest.mock('child_process', ()=>({
13
- execSync: (...args)=>mockExecSync(...args)
14
- }));
15
- jest.mock('../config', ()=>({
16
- envConfig: {
17
- getEnv: jest.fn().mockReturnValue({
18
- NODE_ENV: 'test'
19
- })
20
- }
21
- }));
22
- const mockRunMigrations = jest.fn();
23
- const mockUndoLastMigration = jest.fn();
24
- const mockShowMigrations = jest.fn();
25
- const mockDestroy = jest.fn();
26
- const mockDsInitialize = jest.fn();
27
- const mockAdminInitialize = jest.fn();
28
- const mockAdminQuery = jest.fn();
29
- const mockAdminDestroy = jest.fn();
30
- jest.mock('./datasource.factory', ()=>({
31
- initializeDataSource: jest.fn()
32
- }));
33
- jest.mock('typeorm', ()=>({
34
- DataSource: jest.fn().mockImplementation(()=>({
35
- initialize: mockAdminInitialize,
36
- query: mockAdminQuery,
37
- destroy: mockAdminDestroy
38
- }))
39
- }));
40
- import { ensureMigrationsFolder, generateMigration, runMigrations, revertMigration, resetDatabase, migrationStatus, runForAllTenants } from './migration.runner';
41
- import { initializeDataSource } from './datasource.factory';
42
- describe('migration.runner', ()=>{
43
- const baseConfig = {
44
- defaultDatabaseConfig: {
45
- type: 'postgres',
46
- database: 'flusys_default'
47
- },
48
- migrationsPath: 'migrations',
49
- entities: []
50
- };
51
- let logSpy;
52
- let errorSpy;
53
- beforeEach(()=>{
54
- jest.clearAllMocks();
55
- logSpy = jest.spyOn(console, 'log').mockImplementation(()=>undefined);
56
- errorSpy = jest.spyOn(console, 'error').mockImplementation(()=>undefined);
57
- initializeDataSource.mockResolvedValue({
58
- runMigrations: mockRunMigrations,
59
- undoLastMigration: mockUndoLastMigration,
60
- showMigrations: mockShowMigrations,
61
- destroy: mockDestroy,
62
- initialize: mockDsInitialize
63
- });
64
- });
65
- afterEach(()=>{
66
- logSpy.mockRestore();
67
- errorSpy.mockRestore();
68
- });
69
- describe('ensureMigrationsFolder', ()=>{
70
- it('should create the folder when it does not exist', ()=>{
71
- mockExistsSync.mockReturnValue(false);
72
- const folder = ensureMigrationsFolder('migrations', 'tenant-a');
73
- expect(folder).toBe('migrations/tenant-a');
74
- expect(mockMkdirSync).toHaveBeenCalledWith('migrations/tenant-a', {
75
- recursive: true
76
- });
77
- });
78
- it('should not create the folder when it already exists', ()=>{
79
- mockExistsSync.mockReturnValue(true);
80
- ensureMigrationsFolder('migrations');
81
- expect(mockMkdirSync).not.toHaveBeenCalled();
82
- });
83
- });
84
- describe('generateMigration', ()=>{
85
- it('should shell out to the typeorm CLI with the resolved datasource path', async ()=>{
86
- mockExistsSync.mockImplementation((p)=>p.includes('flusysnest'));
87
- await generateMigration(baseConfig, 'AddProductTable', 'tenant-a');
88
- expect(mockExecSync).toHaveBeenCalledTimes(1);
89
- const [command, options] = mockExecSync.mock.calls[0];
90
- expect(command).toContain('migration:generate');
91
- expect(command).toContain('migrations/tenant-a/AddProductTable');
92
- expect(command).toContain('projects/flusysnest/src/persistence/migration.config.ts');
93
- expect(options.env.TENANT_ID).toBe('tenant-a');
94
- });
95
- it('should use an explicit datasource path when provided', async ()=>{
96
- mockExistsSync.mockReturnValue(false);
97
- await generateMigration(baseConfig, 'AddProductTable', undefined, 'custom/datasource.ts');
98
- const [command] = mockExecSync.mock.calls[0];
99
- expect(command).toContain('-d "custom/datasource.ts"');
100
- });
101
- it('should throw when no datasource file can be found and none is specified', async ()=>{
102
- mockExistsSync.mockReturnValue(false);
103
- await expect(generateMigration(baseConfig, 'AddProductTable')).rejects.toThrow('Could not find datasource file. Specify with --datasource=<path>');
104
- });
105
- });
106
- describe('runMigrations', ()=>{
107
- it('should report success with the list of executed migrations', async ()=>{
108
- mockRunMigrations.mockResolvedValue([
109
- {
110
- name: 'M1'
111
- },
112
- {
113
- name: 'M2'
114
- }
115
- ]);
116
- const result = await runMigrations(baseConfig);
117
- expect(result).toEqual({
118
- tenantId: undefined,
119
- database: 'flusys_default',
120
- success: true,
121
- migrationsRun: [
122
- 'M1',
123
- 'M2'
124
- ]
125
- });
126
- expect(mockDestroy).toHaveBeenCalled();
127
- });
128
- it('should report no pending migrations when none run', async ()=>{
129
- mockRunMigrations.mockResolvedValue([]);
130
- const result = await runMigrations(baseConfig);
131
- expect(result.migrationsRun).toEqual([]);
132
- expect(result.success).toBe(true);
133
- });
134
- it('should capture errors and mark the result as failed', async ()=>{
135
- initializeDataSource.mockRejectedValue(new Error('connection refused'));
136
- const result = await runMigrations(baseConfig, 'tenant-a');
137
- expect(result).toEqual({
138
- tenantId: 'tenant-a',
139
- database: 'flusys_default',
140
- success: false,
141
- error: 'connection refused'
142
- });
143
- expect(errorSpy).toHaveBeenCalled();
144
- });
145
- });
146
- describe('revertMigration', ()=>{
147
- it('should undo the last migration and close the connection', async ()=>{
148
- const result = await revertMigration(baseConfig);
149
- expect(mockUndoLastMigration).toHaveBeenCalled();
150
- expect(mockDestroy).toHaveBeenCalled();
151
- expect(result.success).toBe(true);
152
- });
153
- it('should capture revert errors', async ()=>{
154
- mockUndoLastMigration.mockRejectedValue(new Error('no migrations to revert'));
155
- const result = await revertMigration(baseConfig);
156
- expect(result.success).toBe(false);
157
- expect(result.error).toBe('no migrations to revert');
158
- });
159
- });
160
- describe('migrationStatus', ()=>{
161
- it('should report pending status', async ()=>{
162
- mockShowMigrations.mockResolvedValue(true);
163
- const result = await migrationStatus(baseConfig);
164
- expect(result.migrationsRun).toEqual([
165
- 'Has pending'
166
- ]);
167
- });
168
- it('should report up-to-date status', async ()=>{
169
- mockShowMigrations.mockResolvedValue(false);
170
- const result = await migrationStatus(baseConfig);
171
- expect(result.migrationsRun).toEqual([
172
- 'Up to date'
173
- ]);
174
- });
175
- });
176
- describe('resetDatabase', ()=>{
177
- it('should remove migration files then drop/recreate a postgres database', async ()=>{
178
- mockExistsSync.mockReturnValue(true);
179
- mockReaddirSync.mockReturnValue([
180
- '1-init.ts',
181
- '2-add.js',
182
- 'readme.md'
183
- ]);
184
- mockAdminQuery.mockResolvedValue(undefined);
185
- const config = {
186
- defaultDatabaseConfig: {
187
- type: 'postgres',
188
- host: 'localhost',
189
- port: 5432,
190
- username: 'root',
191
- password: 'pw',
192
- database: 'flusys_default'
193
- },
194
- migrationsPath: 'migrations',
195
- entities: []
196
- };
197
- const result = await resetDatabase(config);
198
- expect(mockRmSync).toHaveBeenCalledTimes(2);
199
- expect(mockAdminQuery).toHaveBeenCalledWith(expect.stringContaining('DROP DATABASE IF EXISTS "flusys_default"'));
200
- expect(mockAdminDestroy).toHaveBeenCalled();
201
- expect(result.success).toBe(true);
202
- });
203
- it('should delete the sqlite file directly for sqlite databases', async ()=>{
204
- mockExistsSync.mockReturnValue(true);
205
- mockReaddirSync.mockReturnValue([]);
206
- const config = {
207
- defaultDatabaseConfig: {
208
- type: 'sqlite',
209
- database: 'flusys.sqlite'
210
- },
211
- migrationsPath: 'migrations',
212
- entities: []
213
- };
214
- const result = await resetDatabase(config);
215
- expect(mockRmSync).toHaveBeenCalledWith('flusys.sqlite');
216
- expect(mockAdminInitialize).not.toHaveBeenCalled();
217
- expect(result.success).toBe(true);
218
- });
219
- it('should fail for unsupported database types', async ()=>{
220
- mockExistsSync.mockReturnValue(false);
221
- const config = {
222
- defaultDatabaseConfig: {
223
- type: 'mssql',
224
- database: 'flusys'
225
- },
226
- migrationsPath: 'migrations',
227
- entities: []
228
- };
229
- const result = await resetDatabase(config);
230
- expect(result.success).toBe(false);
231
- expect(result.error).toContain("Database reset is not supported for type 'mssql'");
232
- });
233
- });
234
- describe('runForAllTenants', ()=>{
235
- it('should run the given command across all configured tenants and summarize results', async ()=>{
236
- mockRunMigrations.mockResolvedValue([
237
- {
238
- name: 'M1'
239
- }
240
- ]);
241
- const config = {
242
- ...baseConfig,
243
- tenants: [
244
- {
245
- id: 'tenant-a',
246
- database: 'db_a'
247
- },
248
- {
249
- id: 'tenant-b',
250
- database: 'db_b'
251
- }
252
- ]
253
- };
254
- const results = await runForAllTenants(config, 'run');
255
- expect(results).toHaveLength(2);
256
- expect(results.every((r)=>r.success)).toBe(true);
257
- expect(initializeDataSource).toHaveBeenCalledTimes(2);
258
- });
259
- it('should return an empty result set when no tenants are configured', async ()=>{
260
- const results = await runForAllTenants(baseConfig, 'status');
261
- expect(results).toEqual([]);
262
- });
263
- });
264
- });
@@ -1,84 +0,0 @@
1
- /**
2
- * Build DataSource options from database config
3
- */ export function buildDataSourceOptions(config, entities, migrationsPath, migrationsTableName = 'migrations') {
4
- return {
5
- type: config.type,
6
- host: config.host,
7
- port: config.port,
8
- username: config.username,
9
- password: config.password,
10
- database: config.database || '',
11
- entities,
12
- migrations: [
13
- migrationsPath
14
- ],
15
- migrationsTableName,
16
- synchronize: false
17
- };
18
- }
19
- /**
20
- * Get migration folder path for a tenant
21
- */ export function getMigrationsFolderPath(basePath, tenantId) {
22
- const folder = tenantId || 'default';
23
- return `${basePath}/${folder}`;
24
- }
25
- /**
26
- * Get migrations glob pattern for a tenant
27
- */ export function getMigrationsGlobPattern(basePath, tenantId) {
28
- return `${getMigrationsFolderPath(basePath, tenantId)}/*{.ts,.js}`;
29
- }
30
- /**
31
- * Get database and config for a tenant (or default)
32
- */ export function getDatabaseForTenant(config, tenantId) {
33
- if (tenantId && config.tenants) {
34
- const tenant = config.tenants.find((t)=>t.id === tenantId);
35
- if (!tenant) {
36
- throw new Error(`Tenant '${tenantId}' not found. Available: ${config.tenants.map((t)=>t.id).join(', ')}`);
37
- }
38
- return {
39
- database: tenant.database,
40
- tenant,
41
- tenantConfig: tenant
42
- };
43
- }
44
- // Default config - use bootstrapAppConfig for feature flags
45
- const defaultTenantConfig = {
46
- id: 'default',
47
- database: config.defaultDatabaseConfig.database || 'default',
48
- enableCompanyFeature: config.bootstrapAppConfig?.enableCompanyFeature ?? false,
49
- permissionMode: config.bootstrapAppConfig?.permissionMode ?? 'FULL'
50
- };
51
- return {
52
- database: defaultTenantConfig.database,
53
- tenantConfig: defaultTenantConfig
54
- };
55
- }
56
- /**
57
- * Resolve full connection details for a tenant.
58
- */ export function resolveDatabaseConnection(config, tenantId) {
59
- const { database, tenant } = getDatabaseForTenant(config, tenantId);
60
- return tenant ? {
61
- ...config.defaultDatabaseConfig,
62
- host: tenant.host ?? config.defaultDatabaseConfig.host,
63
- port: tenant.port ?? config.defaultDatabaseConfig.port,
64
- username: tenant.username ?? config.defaultDatabaseConfig.username,
65
- password: tenant.password ?? config.defaultDatabaseConfig.password,
66
- database: tenant.database
67
- } : {
68
- ...config.defaultDatabaseConfig,
69
- database
70
- };
71
- }
72
- /**
73
- * Resolve entities based on tenant config
74
- */ export function resolveEntities(config, tenantConfig) {
75
- if (typeof config.entities === 'function') {
76
- return config.entities(tenantConfig);
77
- }
78
- return config.entities;
79
- }
80
- /**
81
- * Get active tenants from config
82
- */ export function getActiveTenants(config) {
83
- return config.tenants || [];
84
- }