@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.
Files changed (56) hide show
  1. package/README.md +507 -61
  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
@@ -45,7 +45,8 @@
45
45
  const defaultTenantConfig = {
46
46
  id: 'default',
47
47
  database: config.defaultDatabaseConfig.database || 'default',
48
- enableCompanyFeature: config.bootstrapAppConfig?.enableCompanyFeature ?? false
48
+ enableCompanyFeature: config.bootstrapAppConfig?.enableCompanyFeature ?? false,
49
+ permissionMode: config.bootstrapAppConfig?.permissionMode ?? 'FULL'
49
50
  };
50
51
  return {
51
52
  database: defaultTenantConfig.database,
@@ -60,18 +61,6 @@
60
61
  }
61
62
  return config.entities;
62
63
  }
63
- /**
64
- * Build full database config for a tenant
65
- */ export function buildTenantDatabaseConfig(baseConfig, tenant) {
66
- return {
67
- type: baseConfig.type,
68
- host: tenant.host ?? baseConfig.host,
69
- port: tenant.port ?? baseConfig.port,
70
- username: tenant.username ?? baseConfig.username,
71
- password: tenant.password ?? baseConfig.password,
72
- database: tenant.database
73
- };
74
- }
75
64
  /**
76
65
  * Get active tenants from config
77
66
  */ export function getActiveTenants(config) {
@@ -17,3 +17,6 @@ export interface IOrderable {
17
17
  export interface IMetadata {
18
18
  metadata?: Record<string, any> | null;
19
19
  }
20
+ export interface ICompanyOwned {
21
+ companyId?: string | null;
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-core",
3
- "version": "1.0.0-beta",
3
+ "version": "1.0.0",
4
4
  "description": "Core types, interfaces, and constants for Flusys NestJS packages",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -61,7 +61,7 @@
61
61
  }
62
62
  },
63
63
  "peerDependencies": {
64
- "@faker-js/faker": "^9.0.0",
64
+ "@faker-js/faker": "^10.0.0",
65
65
  "@nestjs/common": "^10.0.0 || ^11.0.0",
66
66
  "@nestjs/config": "^3.0.0 || ^4.0.0",
67
67
  "@nestjs/core": "^10.0.0 || ^11.0.0",
@@ -3,7 +3,7 @@ export declare class DataGenerator {
3
3
  constructor(locale?: string);
4
4
  generateValue(column: IColumnInfo): any;
5
5
  generateEntity(columns: IColumnInfo[]): Record<string, any>;
6
- private generateByName;
6
+ private generateByPattern;
7
7
  private generateByType;
8
8
  private generateString;
9
9
  generateRelationId<T extends {
@@ -34,7 +34,6 @@ export declare class EntityReader {
34
34
  getAllEntities(): EntityMetadata[];
35
35
  getEntityInfo(entityName: string): IEntityInfo;
36
36
  getSeedingOrder(skipEntities?: string[]): string[];
37
- hasRequiredDependencies(entityName: string, dataSource: DataSource): boolean;
38
37
  private findEntityMetadata;
39
38
  private extractColumns;
40
39
  private extractRelations;
@@ -0,0 +1,12 @@
1
+ import { IColumnInfo } from './entity-reader';
2
+ export declare const SYSTEM_FIELDS: string[];
3
+ export declare const AUDIT_FIELDS: string[];
4
+ export declare const IDENTITY_FIELDS: string[];
5
+ export declare const BOOLEAN_KEYWORDS: string[];
6
+ export type FieldPatternType = 'skip' | 'null' | 'boolean' | 'token' | 'firstName' | 'lastName' | 'fullName' | 'email' | 'phone' | 'address' | 'street' | 'city' | 'state' | 'country' | 'zipCode' | 'url' | 'domain' | 'slug' | 'description' | 'summary' | 'content' | 'title' | 'username' | 'password' | 'birthdate' | 'futureDate' | 'recentDateOrNull' | 'serial' | 'company';
7
+ export type ColumnTypeCategory = 'string' | 'integer' | 'decimal' | 'boolean' | 'date' | 'timestamp' | 'time' | 'uuid' | 'json' | 'array' | 'unknown';
8
+ export declare function detectFieldPattern(column: IColumnInfo): FieldPatternType | undefined;
9
+ export declare function detectTypeCategory(type: string): ColumnTypeCategory;
10
+ export declare function getStringLengthCategory(length: number | string | undefined): 'word' | 'sentence' | 'paragraph';
11
+ export declare function isSystemField(fieldName: string): boolean;
12
+ export declare function getTokenLength(column: IColumnInfo): number;
@@ -1,7 +1,7 @@
1
1
  export { BaseSeeder } from './base-seeder';
2
2
  export { EntityReader, IEntityInfo, IColumnInfo, IRelationInfo } from './entity-reader';
3
3
  export { DataGenerator } from './data-generator';
4
- export { TemplateGenerator } from './template-generator';
5
- export { SeedRunner, ISeedResult, ISeedOptions, ISeederLogger, defaultLogger, } from './seed-runner';
6
- export { seedConfig, ISeedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder } from './seed-config';
4
+ export { SeedRunner, ISeedResult, ISeedOptions, ISeederLogger, defaultLogger } from './seed-runner';
5
+ export { seedConfig, ISeedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder, configureSeedConfig } from './seed-config';
6
+ export { SYSTEM_FIELDS, isSystemField, detectFieldPattern, detectTypeCategory } from './field-patterns';
7
7
  export { runSeedCli } from './cli';
@@ -6,6 +6,7 @@ export interface ISeedConfig {
6
6
  respectSoftDelete: boolean;
7
7
  }
8
8
  export declare const seedConfig: ISeedConfig;
9
+ export declare function configureSeedConfig(config: Partial<ISeedConfig>): void;
9
10
  export declare function getEntityCount(entityName: string, config?: ISeedConfig): number;
10
11
  export declare function shouldSkipEntity(entityName: string, config?: ISeedConfig): boolean;
11
12
  export declare function getSeedingOrder(availableEntities: string[], config?: ISeedConfig): string[];
@@ -34,6 +34,7 @@ export declare class SeedRunner {
34
34
  registerCustomSeeder(entityName: string, seeder: BaseSeeder<any>): void;
35
35
  unregisterCustomSeeder(entityName: string): void;
36
36
  hasCustomSeeder(entityName: string): boolean;
37
+ private getSeeder;
37
38
  runAll(options?: ISeedOptions): Promise<ISeedResult[]>;
38
39
  runSingle(entityName: string, options?: ISeedOptions): Promise<ISeedResult>;
39
40
  clearAll(hard?: boolean, continueOnError?: boolean): Promise<ISeedResult[]>;
@@ -9,5 +9,4 @@ export declare function getDatabaseForTenant(config: IMigrationConfig, tenantId?
9
9
  tenantConfig: ITenantDatabaseConfig;
10
10
  };
11
11
  export declare function resolveEntities(config: IMigrationConfig, tenantConfig?: ITenantDatabaseConfig): any[];
12
- export declare function buildTenantDatabaseConfig(baseConfig: IDatabaseConfig, tenant: ITenantDatabaseConfig): IDatabaseConfig;
13
12
  export declare function getActiveTenants(config: IMigrationConfig): ITenantDatabaseConfig[];
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "setupSwaggerDocs", {
6
- enumerable: true,
7
- get: function() {
8
- return setupSwaggerDocs;
9
- }
10
- });
11
- const _docsconfig = require("./docs.config");
12
- function setupSwaggerDocs(app, ...modules) {
13
- (0, _docsconfig.setupModuleSwaggerDocs)(app, modules);
14
- }
@@ -1,297 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "TemplateGenerator", {
6
- enumerable: true,
7
- get: function() {
8
- return TemplateGenerator;
9
- }
10
- });
11
- const _fs = /*#__PURE__*/ _interop_require_wildcard(require("fs"));
12
- const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
13
- function _getRequireWildcardCache(nodeInterop) {
14
- if (typeof WeakMap !== "function") return null;
15
- var cacheBabelInterop = new WeakMap();
16
- var cacheNodeInterop = new WeakMap();
17
- return (_getRequireWildcardCache = function(nodeInterop) {
18
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
19
- })(nodeInterop);
20
- }
21
- function _interop_require_wildcard(obj, nodeInterop) {
22
- if (!nodeInterop && obj && obj.__esModule) {
23
- return obj;
24
- }
25
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
26
- return {
27
- default: obj
28
- };
29
- }
30
- var cache = _getRequireWildcardCache(nodeInterop);
31
- if (cache && cache.has(obj)) {
32
- return cache.get(obj);
33
- }
34
- var newObj = {
35
- __proto__: null
36
- };
37
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
38
- for(var key in obj){
39
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
40
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
41
- if (desc && (desc.get || desc.set)) {
42
- Object.defineProperty(newObj, key, desc);
43
- } else {
44
- newObj[key] = obj[key];
45
- }
46
- }
47
- }
48
- newObj.default = obj;
49
- if (cache) {
50
- cache.set(obj, newObj);
51
- }
52
- return newObj;
53
- }
54
- let TemplateGenerator = class TemplateGenerator {
55
- /**
56
- * Generate seeder file for an entity
57
- * @param entityInfo Entity metadata
58
- * @param outputDir Output directory for seeder files
59
- */ generateSeederFile(entityInfo, outputDir) {
60
- const fileName = this.getSeederFileName(entityInfo.name);
61
- const filePath = _path.join(outputDir, fileName);
62
- const content = this.generateSeederContent(entityInfo);
63
- // Ensure directory exists
64
- if (!_fs.existsSync(outputDir)) {
65
- _fs.mkdirSync(outputDir, {
66
- recursive: true
67
- });
68
- }
69
- // Write file
70
- _fs.writeFileSync(filePath, content, 'utf-8');
71
- return filePath;
72
- }
73
- /**
74
- * Generate index file that exports all seeders
75
- * @param entityInfos Array of entity metadata
76
- * @param outputDir Output directory
77
- */ generateIndexFile(entityInfos, outputDir) {
78
- const indexPath = _path.join(outputDir, 'index.ts');
79
- const exports1 = entityInfos.map((info)=>{
80
- const className = this.getSeederClassName(info.name);
81
- const fileName = this.getSeederFileName(info.name).replace('.ts', '');
82
- return `export { ${className} } from './${fileName}';`;
83
- }).join('\n');
84
- _fs.writeFileSync(indexPath, exports1, 'utf-8');
85
- return indexPath;
86
- }
87
- /**
88
- * Generate seeder file content
89
- */ generateSeederContent(entityInfo) {
90
- const className = this.getSeederClassName(entityInfo.name);
91
- const imports = this.generateImports(entityInfo);
92
- const classContent = this.generateClass(entityInfo, className);
93
- return `${imports}\n\n${classContent}\n`;
94
- }
95
- /**
96
- * Generate import statements
97
- */ generateImports(entityInfo) {
98
- const imports = [
99
- `import { DataSource } from 'typeorm';`,
100
- `import { faker } from '@faker-js/faker';`,
101
- `import { BaseSeeder } from '@flusys/nestjs-core/seeders/base-seeder';`
102
- ];
103
- // Add entity import (adjust path as needed)
104
- const entityImportPath = this.getEntityImportPath(entityInfo.name);
105
- imports.push(`import { ${entityInfo.name} } from '${entityImportPath}';`);
106
- // Add relation imports
107
- const relationEntities = new Set(entityInfo.relations.map((r)=>r.targetEntity));
108
- for (const relEntity of relationEntities){
109
- const relPath = this.getEntityImportPath(relEntity);
110
- imports.push(`import { ${relEntity} } from '${relPath}';`);
111
- }
112
- return imports.join('\n');
113
- }
114
- /**
115
- * Generate class content
116
- */ generateClass(entityInfo, className) {
117
- const generateMethod = this.generateGenerateMethod(entityInfo);
118
- const relationMethod = this.generateRelationMethod(entityInfo);
119
- return `
120
- /**
121
- * ${className}
122
- *
123
- * Seeder for ${entityInfo.name} entity.
124
- * Generates sample data for development/testing.
125
- */
126
- export class ${className} extends BaseSeeder<${entityInfo.name}> {
127
- constructor(dataSource: DataSource) {
128
- super(dataSource, ${entityInfo.name});
129
- }
130
-
131
- ${generateMethod}${relationMethod ? '\n\n' + relationMethod : ''}
132
- }`.trim();
133
- }
134
- /**
135
- * Generate the generate() method
136
- */ generateGenerateMethod(entityInfo) {
137
- const fields = this.generateFieldAssignments(entityInfo.columns);
138
- return ` /**
139
- * Generate sample ${entityInfo.name} data
140
- * @param count Number of records to generate
141
- * @returns Array of created entities
142
- */
143
- async generate(count: number = 10): Promise<${entityInfo.name}[]> {
144
- const entities: ${entityInfo.name}[] = [];
145
-
146
- for (let i = 0; i < count; i++) {
147
- const entity = this.repository.create({
148
- ${fields}
149
- });
150
-
151
- entities.push(entity);
152
- }
153
-
154
- return this.repository.save(entities);
155
- }`;
156
- }
157
- /**
158
- * Generate field assignments for entity creation
159
- */ generateFieldAssignments(columns) {
160
- const assignments = [];
161
- for (const column of columns){
162
- // Skip system fields
163
- if (this.isSystemField(column.propertyName)) {
164
- continue;
165
- }
166
- // Skip foreign keys (handled in relations method)
167
- if (column.propertyName.endsWith('Id')) {
168
- continue;
169
- }
170
- const fakerCall = this.getFakerCall(column);
171
- const comment = column.isNullable ? ' // Optional' : ' // Required';
172
- assignments.push(` ${column.propertyName}: ${fakerCall},${comment}`);
173
- }
174
- return assignments.join('\n');
175
- }
176
- /**
177
- * Generate relation handling method if entity has relations
178
- */ generateRelationMethod(entityInfo) {
179
- const manyToOneRelations = entityInfo.relations.filter((r)=>r.type === 'many-to-one');
180
- if (manyToOneRelations.length === 0) {
181
- return null;
182
- }
183
- const relationParams = manyToOneRelations.map((r)=>`${r.propertyName}s: ${r.targetEntity}[]`).join(', ');
184
- const relationAssignments = manyToOneRelations.map((r)=>{
185
- return ` const random${r.targetEntity} = faker.helpers.arrayElement(${r.propertyName}s);
186
- entity.${r.propertyName}Id = random${r.targetEntity}.id;`;
187
- }).join('\n');
188
- return ` /**
189
- * Generate with related entities
190
- * @param ${relationParams}
191
- * @param count Number of records to generate
192
- */
193
- async generateWithRelations(${relationParams}, count: number = 10): Promise<${entityInfo.name}[]> {
194
- const entities = await this.generate(count);
195
-
196
- for (const entity of entities) {
197
- ${relationAssignments}
198
- await this.repository.save(entity);
199
- }
200
-
201
- return entities;
202
- }`;
203
- }
204
- /**
205
- * Get faker call for column based on its properties
206
- */ getFakerCall(column) {
207
- const nameLower = column.propertyName.toLowerCase();
208
- // Name fields
209
- if (nameLower.includes('firstname')) return 'faker.person.firstName()';
210
- if (nameLower.includes('lastname')) return 'faker.person.lastName()';
211
- if (nameLower === 'name' || nameLower.includes('fullname')) {
212
- return 'faker.person.fullName()';
213
- }
214
- // Contact
215
- if (nameLower.includes('email')) return 'faker.internet.email().toLowerCase()';
216
- if (nameLower.includes('phone')) return 'faker.phone.number()';
217
- // Text
218
- if (nameLower.includes('description')) return 'faker.lorem.paragraph()';
219
- if (nameLower.includes('content')) return 'faker.lorem.paragraphs(2)';
220
- if (nameLower.includes('title')) return 'faker.lorem.sentence()';
221
- // URL/Web
222
- if (nameLower.includes('slug')) {
223
- return "faker.helpers.slugify(faker.lorem.words(3)).toLowerCase()";
224
- }
225
- if (nameLower.includes('url')) return 'faker.internet.url()';
226
- // Status
227
- if (nameLower.includes('isactive') || nameLower.includes('isenabled')) {
228
- return 'true';
229
- }
230
- // Type-based
231
- const type = column.type.toLowerCase();
232
- if (type.includes('bool')) return 'faker.datatype.boolean()';
233
- if (type.includes('int') || type.includes('serial')) {
234
- return 'faker.number.int({ min: 1, max: 1000 })';
235
- }
236
- if (type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
237
- return 'faker.number.float({ min: 0, max: 1000, precision: 0.01 })';
238
- }
239
- if (type.includes('date') || type.includes('timestamp')) {
240
- return 'faker.date.recent({ days: 30 })';
241
- }
242
- if (type.includes('uuid')) return 'faker.string.uuid()';
243
- if (type.includes('json')) return '{ key: faker.lorem.word() }';
244
- // Default string
245
- const maxLength = typeof column.length === 'number' ? column.length : 255;
246
- if (maxLength <= 50) {
247
- return 'faker.lorem.word()';
248
- } else if (maxLength <= 255) {
249
- return 'faker.lorem.sentence()';
250
- } else {
251
- return 'faker.lorem.paragraph()';
252
- }
253
- }
254
- /**
255
- * Check if field is a system field (should be skipped)
256
- */ isSystemField(fieldName) {
257
- const systemFields = [
258
- 'id',
259
- 'createdAt',
260
- 'updatedAt',
261
- 'deletedAt',
262
- 'createdById',
263
- 'updatedById',
264
- 'deletedById'
265
- ];
266
- return systemFields.includes(fieldName);
267
- }
268
- /**
269
- * Get seeder class name from entity name
270
- */ getSeederClassName(entityName) {
271
- return `${entityName}Seeder`;
272
- }
273
- /**
274
- * Get seeder file name from entity name
275
- */ getSeederFileName(entityName) {
276
- const kebab = entityName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
277
- return `${kebab}.seeder.ts`;
278
- }
279
- /**
280
- * Get entity import path (adjust based on project structure)
281
- */ getEntityImportPath(entityName) {
282
- // Try to determine module from entity name
283
- // This is a simplified approach - adjust based on actual project structure
284
- const nameLower = entityName.toLowerCase();
285
- if (nameLower.includes('user') || nameLower.includes('company') || nameLower.includes('branch')) {
286
- return `@flusys/nestjs-auth/entities`;
287
- }
288
- if (nameLower.includes('role') || nameLower.includes('permission') || nameLower.includes('action') || nameLower.includes('menu')) {
289
- return `@flusys/nestjs-iam/entities`;
290
- }
291
- if (nameLower.includes('file') || nameLower.includes('folder') || nameLower.includes('storage')) {
292
- return `@flusys/nestjs-storage/entities`;
293
- }
294
- // Default fallback
295
- return `@flusys/nestjs-shared/entities`;
296
- }
297
- };
@@ -1,3 +0,0 @@
1
- import { INestApplication } from '@nestjs/common';
2
- import { IModuleSwaggerOptions } from './docs.config';
3
- export declare function setupSwaggerDocs(app: INestApplication, ...modules: IModuleSwaggerOptions[]): void;
@@ -1,4 +0,0 @@
1
- import { setupModuleSwaggerDocs } from './docs.config';
2
- export function setupSwaggerDocs(app, ...modules) {
3
- setupModuleSwaggerDocs(app, modules);
4
- }