@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
package/cjs/docs/docs.config.js
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get setupModuleSwaggerDocs () {
|
|
8
13
|
return setupModuleSwaggerDocs;
|
|
14
|
+
},
|
|
15
|
+
get setupSwaggerDocs () {
|
|
16
|
+
return setupSwaggerDocs;
|
|
9
17
|
}
|
|
10
18
|
});
|
|
11
19
|
const _swagger = require("@nestjs/swagger");
|
|
@@ -94,6 +102,65 @@ const _swagger = require("@nestjs/swagger");
|
|
|
94
102
|
paths: filteredPaths
|
|
95
103
|
};
|
|
96
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Filter OpenAPI document to exclude examples from endpoint responses
|
|
107
|
+
*/ function filterExamples(document, exclusions) {
|
|
108
|
+
if (!exclusions?.length || !document.paths) {
|
|
109
|
+
return document;
|
|
110
|
+
}
|
|
111
|
+
const filteredPaths = {};
|
|
112
|
+
for (const [path, pathItem] of Object.entries(document.paths)){
|
|
113
|
+
const filteredPathItem = {};
|
|
114
|
+
for (const [method, operation] of Object.entries(pathItem)){
|
|
115
|
+
// Skip non-operation properties
|
|
116
|
+
if (!operation || typeof operation !== 'object') {
|
|
117
|
+
filteredPathItem[method] = operation;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Check if this endpoint matches any exclusion pattern
|
|
121
|
+
const matchingExclusions = exclusions.filter((exclusion)=>{
|
|
122
|
+
const pathMatches = pathMatchesPattern(path, exclusion.pathPattern);
|
|
123
|
+
const methodMatches = !exclusion.method || exclusion.method === method;
|
|
124
|
+
return pathMatches && methodMatches;
|
|
125
|
+
});
|
|
126
|
+
if (matchingExclusions.length > 0) {
|
|
127
|
+
// Collect all examples to exclude
|
|
128
|
+
const examplesToExclude = new Set();
|
|
129
|
+
matchingExclusions.forEach((exclusion)=>{
|
|
130
|
+
exclusion.examples.forEach((example)=>examplesToExclude.add(example));
|
|
131
|
+
});
|
|
132
|
+
// Deep clone the operation to avoid mutating original
|
|
133
|
+
const filteredOperation = JSON.parse(JSON.stringify(operation));
|
|
134
|
+
// Filter examples from responses
|
|
135
|
+
if (filteredOperation.responses) {
|
|
136
|
+
for (const [statusCode, response] of Object.entries(filteredOperation.responses)){
|
|
137
|
+
const responseObj = response;
|
|
138
|
+
if (responseObj?.content) {
|
|
139
|
+
const content = responseObj.content;
|
|
140
|
+
for (const [mediaType, mediaTypeObj] of Object.entries(content)){
|
|
141
|
+
const media = mediaTypeObj;
|
|
142
|
+
if (media?.examples && typeof media.examples === 'object') {
|
|
143
|
+
const examples = media.examples;
|
|
144
|
+
for (const exampleName of examplesToExclude){
|
|
145
|
+
delete examples[exampleName];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
filteredPathItem[method] = filteredOperation;
|
|
153
|
+
} else {
|
|
154
|
+
filteredPathItem[method] = operation;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
filteredPaths[path] = filteredPathItem;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
...document,
|
|
161
|
+
paths: filteredPaths
|
|
162
|
+
};
|
|
163
|
+
}
|
|
97
164
|
/**
|
|
98
165
|
* Check if a path matches a pattern (supports wildcards)
|
|
99
166
|
*/ function pathMatchesPattern(path, pattern) {
|
|
@@ -187,6 +254,13 @@ function setupModuleSwaggerDocs(app, configs) {
|
|
|
187
254
|
if (config.excludeQueryParameters?.length) {
|
|
188
255
|
document = filterQueryParameters(document, config.excludeQueryParameters);
|
|
189
256
|
}
|
|
257
|
+
// Filter out excluded examples
|
|
258
|
+
if (config.excludeExamples?.length) {
|
|
259
|
+
document = filterExamples(document, config.excludeExamples);
|
|
260
|
+
}
|
|
190
261
|
_swagger.SwaggerModule.setup(config.path, app, document);
|
|
191
262
|
});
|
|
192
263
|
}
|
|
264
|
+
function setupSwaggerDocs(app, ...modules) {
|
|
265
|
+
setupModuleSwaggerDocs(app, modules);
|
|
266
|
+
}
|
package/cjs/docs/index.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./docs.config"), exports);
|
|
6
|
-
_export_star(require("./docs.setup"), exports);
|
|
7
6
|
function _export_star(from, to) {
|
|
8
7
|
Object.keys(from).forEach(function(k) {
|
|
9
8
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
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 */ "use strict";
|
|
5
7
|
Object.defineProperty(exports, "__esModule", {
|
|
6
8
|
value: true
|
|
7
9
|
});
|
|
@@ -22,10 +22,9 @@ const _datasourceconfigbuilder = require("../utils/datasource-config.builder");
|
|
|
22
22
|
function createMigrationDataSource(options) {
|
|
23
23
|
const { config, tenantId } = options;
|
|
24
24
|
const { database, tenant, tenantConfig } = (0, _datasourceconfigbuilder.getDatabaseForTenant)(config, tenantId);
|
|
25
|
-
// Log target info
|
|
26
25
|
console.log(`\n📦 Database: ${database}${tenant ? ` [${tenant.name}]` : ' [default]'}`);
|
|
27
26
|
console.log(`📁 Migrations: migrations/${tenantId || 'default'}/`);
|
|
28
|
-
if (config.bootstrapAppConfig?.databaseMode
|
|
27
|
+
if (config.bootstrapAppConfig?.databaseMode === 'multi-tenant' && !tenantId && config.tenants && config.tenants.length > 0) {
|
|
29
28
|
console.log(`\n💡 Multi-tenant mode. Available tenants:`);
|
|
30
29
|
config.tenants.forEach((t)=>console.log(` - TENANT_ID=${t.id} → ${t.database}`));
|
|
31
30
|
}
|
|
@@ -40,7 +39,6 @@ function createMigrationDataSource(options) {
|
|
|
40
39
|
...config.defaultDatabaseConfig,
|
|
41
40
|
database
|
|
42
41
|
};
|
|
43
|
-
// Resolve entities based on tenant config
|
|
44
42
|
const entities = (0, _datasourceconfigbuilder.resolveEntities)(config, tenantConfig);
|
|
45
43
|
const dsOptions = (0, _datasourceconfigbuilder.buildDataSourceOptions)(dbConfig, entities, (0, _datasourceconfigbuilder.getMigrationsGlobPattern)(config.migrationsPath, tenantId), config.migrationsTableName);
|
|
46
44
|
return new _typeorm.DataSource({
|
package/cjs/migration/index.js
CHANGED
|
@@ -1,15 +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
|
"use strict";
|
|
14
2
|
Object.defineProperty(exports, "__esModule", {
|
|
15
3
|
value: true
|
|
@@ -53,23 +53,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
53
53
|
}
|
|
54
54
|
return newObj;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
* Migration CLI Service
|
|
58
|
-
*
|
|
59
|
-
* Handles migration command execution for single and multi-tenant databases.
|
|
60
|
-
*
|
|
61
|
-
* Commands:
|
|
62
|
-
* - generate: Create new migration
|
|
63
|
-
* - run: Execute pending migrations
|
|
64
|
-
* - revert: Rollback last migration
|
|
65
|
-
* - status: Check migration status
|
|
66
|
-
* - run:all/revert:all/status:all: Batch operations for all tenants
|
|
67
|
-
*
|
|
68
|
-
* Configuration:
|
|
69
|
-
* - TENANT_ID: Target tenant (optional, defaults to default database)
|
|
70
|
-
* - --config=<path>: Migration config file path
|
|
71
|
-
* - --datasource=<path>: DataSource file path for TypeORM CLI
|
|
72
|
-
*/ function showHelp() {
|
|
56
|
+
function showHelp() {
|
|
73
57
|
console.log(`
|
|
74
58
|
Migration CLI
|
|
75
59
|
|
|
@@ -75,6 +75,36 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
75
75
|
}
|
|
76
76
|
return newObj;
|
|
77
77
|
}
|
|
78
|
+
function getLabel(tenantId) {
|
|
79
|
+
return tenantId ? `[${tenantId}]` : '[default]';
|
|
80
|
+
}
|
|
81
|
+
async function withDataSource(config, tenantId, operation) {
|
|
82
|
+
const { database } = (0, _utils.getDatabaseForTenant)(config, tenantId);
|
|
83
|
+
const label = getLabel(tenantId);
|
|
84
|
+
try {
|
|
85
|
+
const ds = await (0, _datasourcefactory.initializeDataSource)({
|
|
86
|
+
config,
|
|
87
|
+
tenantId
|
|
88
|
+
});
|
|
89
|
+
const partial = await operation(ds, label);
|
|
90
|
+
await ds.destroy();
|
|
91
|
+
return {
|
|
92
|
+
tenantId,
|
|
93
|
+
database,
|
|
94
|
+
success: true,
|
|
95
|
+
...partial
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
99
|
+
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
100
|
+
return {
|
|
101
|
+
tenantId,
|
|
102
|
+
database,
|
|
103
|
+
success: false,
|
|
104
|
+
error: errorMessage
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
78
108
|
function ensureMigrationsFolder(basePath, tenantId) {
|
|
79
109
|
const folder = (0, _utils.getMigrationsFolderPath)(basePath, tenantId);
|
|
80
110
|
if (!_fs.existsSync(folder)) {
|
|
@@ -116,15 +146,8 @@ function findDatasourcePath() {
|
|
|
116
146
|
throw new Error('Could not find datasource file. Specify with --datasource=<path>');
|
|
117
147
|
}
|
|
118
148
|
async function runMigrations(config, tenantId) {
|
|
119
|
-
|
|
120
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
121
|
-
try {
|
|
122
|
-
const ds = await (0, _datasourcefactory.initializeDataSource)({
|
|
123
|
-
config,
|
|
124
|
-
tenantId
|
|
125
|
-
});
|
|
149
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
126
150
|
const migrations = await ds.runMigrations();
|
|
127
|
-
await ds.destroy();
|
|
128
151
|
if (migrations.length === 0) {
|
|
129
152
|
console.log(`${label} No pending migrations.`);
|
|
130
153
|
} else {
|
|
@@ -132,78 +155,27 @@ async function runMigrations(config, tenantId) {
|
|
|
132
155
|
migrations.forEach((m)=>console.log(` - ${m.name}`));
|
|
133
156
|
}
|
|
134
157
|
return {
|
|
135
|
-
tenantId,
|
|
136
|
-
database,
|
|
137
|
-
success: true,
|
|
138
158
|
migrationsRun: migrations.map((m)=>m.name)
|
|
139
159
|
};
|
|
140
|
-
}
|
|
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
|
-
}
|
|
160
|
+
});
|
|
150
161
|
}
|
|
151
162
|
async function revertMigration(config, tenantId) {
|
|
152
|
-
|
|
153
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
154
|
-
try {
|
|
155
|
-
const ds = await (0, _datasourcefactory.initializeDataSource)({
|
|
156
|
-
config,
|
|
157
|
-
tenantId
|
|
158
|
-
});
|
|
163
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
159
164
|
await ds.undoLastMigration();
|
|
160
|
-
await ds.destroy();
|
|
161
165
|
console.log(`${label} ✓ Reverted!`);
|
|
162
|
-
return {
|
|
163
|
-
|
|
164
|
-
database,
|
|
165
|
-
success: true
|
|
166
|
-
};
|
|
167
|
-
} catch (error) {
|
|
168
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
169
|
-
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
170
|
-
return {
|
|
171
|
-
tenantId,
|
|
172
|
-
database,
|
|
173
|
-
success: false,
|
|
174
|
-
error: errorMessage
|
|
175
|
-
};
|
|
176
|
-
}
|
|
166
|
+
return {};
|
|
167
|
+
});
|
|
177
168
|
}
|
|
178
169
|
async function migrationStatus(config, tenantId) {
|
|
179
|
-
|
|
180
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
181
|
-
try {
|
|
182
|
-
const ds = await (0, _datasourcefactory.initializeDataSource)({
|
|
183
|
-
config,
|
|
184
|
-
tenantId
|
|
185
|
-
});
|
|
170
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
186
171
|
const hasPending = await ds.showMigrations();
|
|
187
|
-
await ds.destroy();
|
|
188
172
|
console.log(`${label} Pending: ${hasPending ? 'Yes' : 'No'}`);
|
|
189
173
|
return {
|
|
190
|
-
tenantId,
|
|
191
|
-
database,
|
|
192
|
-
success: true,
|
|
193
174
|
migrationsRun: [
|
|
194
175
|
hasPending ? 'Has pending' : 'Up to date'
|
|
195
176
|
]
|
|
196
177
|
};
|
|
197
|
-
}
|
|
198
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
199
|
-
console.error(`${label} ✗ Failed: ${errorMessage}`);
|
|
200
|
-
return {
|
|
201
|
-
tenantId,
|
|
202
|
-
database,
|
|
203
|
-
success: false,
|
|
204
|
-
error: errorMessage
|
|
205
|
-
};
|
|
206
|
-
}
|
|
178
|
+
});
|
|
207
179
|
}
|
|
208
180
|
async function runForAllTenants(config, command) {
|
|
209
181
|
const tenants = (0, _utils.getActiveTenants)(config);
|
|
@@ -22,25 +22,15 @@ function _define_property(obj, key, value) {
|
|
|
22
22
|
return obj;
|
|
23
23
|
}
|
|
24
24
|
let BaseSeeder = class BaseSeeder {
|
|
25
|
-
|
|
26
|
-
* Clear all records from the entity table
|
|
27
|
-
* Respects soft delete if entity has deletedAt column
|
|
28
|
-
* @param hard If true, perform hard delete (ignore soft delete)
|
|
29
|
-
*/ async clear(hard = false) {
|
|
25
|
+
async clear(hard = false) {
|
|
30
26
|
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
31
27
|
if (hard || !hasSoftDelete) {
|
|
32
|
-
// Hard delete - remove all records
|
|
33
28
|
await this.repository.clear();
|
|
34
29
|
} else {
|
|
35
|
-
// Soft delete - set deletedAt
|
|
36
30
|
await this.repository.createQueryBuilder().softDelete().where('id IS NOT NULL').execute();
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
|
-
|
|
40
|
-
* Get count of existing records
|
|
41
|
-
* @param includeDeleted If true, count soft-deleted records
|
|
42
|
-
* @returns Total record count
|
|
43
|
-
*/ async count(includeDeleted = false) {
|
|
33
|
+
async count(includeDeleted = false) {
|
|
44
34
|
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
45
35
|
if (hasSoftDelete && !includeDeleted) {
|
|
46
36
|
return this.repository.count({
|
|
@@ -51,22 +41,13 @@ let BaseSeeder = class BaseSeeder {
|
|
|
51
41
|
}
|
|
52
42
|
return this.repository.count();
|
|
53
43
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* @returns True if no records exist
|
|
57
|
-
*/ async isEmpty() {
|
|
58
|
-
const total = await this.count();
|
|
59
|
-
return total === 0;
|
|
44
|
+
async isEmpty() {
|
|
45
|
+
return await this.count() === 0;
|
|
60
46
|
}
|
|
61
|
-
|
|
62
|
-
* Get entity name for logging
|
|
63
|
-
*/ getEntityName() {
|
|
47
|
+
getEntityName() {
|
|
64
48
|
return this.metadata.tableName;
|
|
65
49
|
}
|
|
66
|
-
|
|
67
|
-
* Wrap operation in transaction
|
|
68
|
-
* @param operation Function to execute in transaction
|
|
69
|
-
*/ async withTransaction(operation) {
|
|
50
|
+
async withTransaction(operation) {
|
|
70
51
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
71
52
|
await queryRunner.connect();
|
|
72
53
|
await queryRunner.startTransaction();
|