@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
@@ -1,37 +1,26 @@
1
1
  import { faker } from '@faker-js/faker';
2
+ import { detectFieldPattern, detectTypeCategory, getStringLengthCategory, getTokenLength } from './field-patterns';
2
3
  /**
3
4
  * Data Generator Service
4
5
  *
5
6
  * Generates realistic sample data based on column metadata.
6
7
  * Uses @faker-js/faker for data generation with intelligent field detection.
7
- *
8
- * Usage:
9
- * ```typescript
10
- * const generator = new DataGenerator();
11
- * const value = generator.generateValue(columnInfo);
12
- * const entity = generator.generateEntity(columns);
13
- * ```
14
8
  */ export class DataGenerator {
15
9
  /**
16
10
  * Generate value for a single column based on its metadata
17
11
  */ generateValue(column) {
18
- // Skip generated fields
19
- if (column.isGenerated) {
20
- return undefined;
21
- }
12
+ if (column.isGenerated) return undefined;
22
13
  // Handle nullable fields (50% chance of null)
23
- if (column.isNullable && faker.datatype.boolean()) {
24
- return null;
25
- }
14
+ if (column.isNullable && faker.datatype.boolean()) return null;
26
15
  // Check for enum
27
16
  if (column.enum && column.enum.length > 0) {
28
17
  return faker.helpers.arrayElement(column.enum);
29
18
  }
30
- // Generate based on column name pattern
31
- const nameLower = column.propertyName.toLowerCase();
32
- const value = this.generateByName(nameLower, column);
33
- if (value !== undefined) {
34
- return value;
19
+ // Generate based on field pattern
20
+ const pattern = detectFieldPattern(column);
21
+ if (pattern) {
22
+ const value = this.generateByPattern(pattern, column);
23
+ if (value !== undefined) return value;
35
24
  }
36
25
  // Generate based on column type
37
26
  return this.generateByType(column);
@@ -49,143 +38,108 @@ import { faker } from '@faker-js/faker';
49
38
  return entity;
50
39
  }
51
40
  /**
52
- * Generate value based on column name patterns
53
- */ generateByName(nameLower, column) {
54
- // Identity fields (skip, handled by database)
55
- if ([
56
- 'id',
57
- 'createdat',
58
- 'updatedat',
59
- 'deletedat'
60
- ].includes(nameLower)) {
61
- return undefined;
62
- }
63
- // Audit fields (skip, handled by interceptors)
64
- if ([
65
- 'createdbyid',
66
- 'updatedbyid',
67
- 'deletedbyid'
68
- ].includes(nameLower)) {
69
- return null;
70
- }
71
- // Name fields
72
- if (nameLower.includes('firstname')) return faker.person.firstName();
73
- if (nameLower.includes('lastname')) return faker.person.lastName();
74
- if (nameLower.includes('fullname') || nameLower === 'name') {
75
- return faker.person.fullName();
76
- }
77
- // Contact fields
78
- if (nameLower.includes('email')) return faker.internet.email().toLowerCase();
79
- if (nameLower.includes('phone')) return faker.phone.number();
80
- if (nameLower.includes('mobile')) return faker.phone.number();
81
- // Location fields
82
- if (nameLower.includes('address')) return faker.location.streetAddress();
83
- if (nameLower.includes('street')) return faker.location.street();
84
- if (nameLower.includes('city')) return faker.location.city();
85
- if (nameLower.includes('state')) return faker.location.state();
86
- if (nameLower.includes('country')) return faker.location.country();
87
- if (nameLower.includes('zipcode') || nameLower.includes('postalcode')) {
88
- return faker.location.zipCode();
89
- }
90
- // URL/Web fields
91
- if (nameLower.includes('url')) return faker.internet.url();
92
- if (nameLower.includes('website')) return faker.internet.url();
93
- if (nameLower.includes('domain')) return faker.internet.domainName();
94
- if (nameLower.includes('slug')) {
95
- return faker.helpers.slugify(faker.lorem.words(3)).toLowerCase();
96
- }
97
- // Text fields
98
- if (nameLower.includes('description')) return faker.lorem.paragraph();
99
- if (nameLower.includes('summary')) return faker.lorem.sentence();
100
- if (nameLower.includes('content')) return faker.lorem.paragraphs(3);
101
- if (nameLower.includes('title')) return faker.lorem.sentence();
102
- // User fields
103
- if (nameLower.includes('username')) return faker.internet.username();
104
- if (nameLower.includes('password')) return '$2b$12$dummy.hashed.password.value'; // Dummy bcrypt hash
105
- // Status/Active fields
106
- if (nameLower.includes('isactive')) return faker.datatype.boolean();
107
- if (nameLower.includes('isenabled')) return faker.datatype.boolean();
108
- if (nameLower.includes('ispublic')) return faker.datatype.boolean();
109
- if (nameLower.includes('isverified')) return faker.datatype.boolean();
110
- // Date fields
111
- if (nameLower.includes('birthdate') || nameLower.includes('dateofbirth')) {
112
- return faker.date.birthdate({
113
- min: 18,
114
- max: 80,
115
- mode: 'age'
116
- });
117
- }
118
- if (nameLower.includes('expiresat') || nameLower.includes('expirydate')) {
119
- return faker.date.future();
120
- }
121
- if (nameLower.includes('verifiedat')) {
122
- return faker.datatype.boolean() ? faker.date.recent() : null;
123
- }
124
- // Serial/Order fields
125
- if (nameLower.includes('serial') || nameLower.includes('order')) {
126
- return faker.number.int({
127
- min: 1,
128
- max: 100
129
- });
130
- }
131
- // Company/Organization
132
- if (nameLower.includes('company') && !nameLower.includes('id')) {
133
- return faker.company.name();
41
+ * Generate value based on detected field pattern
42
+ */ generateByPattern(pattern, column) {
43
+ switch(pattern){
44
+ case 'skip':
45
+ return undefined;
46
+ case 'null':
47
+ return null;
48
+ case 'boolean':
49
+ return faker.datatype.boolean();
50
+ case 'token':
51
+ return faker.string.alphanumeric(getTokenLength(column));
52
+ case 'firstName':
53
+ return faker.person.firstName();
54
+ case 'lastName':
55
+ return faker.person.lastName();
56
+ case 'fullName':
57
+ return faker.person.fullName();
58
+ case 'email':
59
+ return faker.internet.email().toLowerCase();
60
+ case 'phone':
61
+ return faker.phone.number();
62
+ case 'address':
63
+ return faker.location.streetAddress();
64
+ case 'street':
65
+ return faker.location.street();
66
+ case 'city':
67
+ return faker.location.city();
68
+ case 'state':
69
+ return faker.location.state();
70
+ case 'country':
71
+ return faker.location.country();
72
+ case 'zipCode':
73
+ return faker.location.zipCode();
74
+ case 'url':
75
+ return faker.internet.url();
76
+ case 'domain':
77
+ return faker.internet.domainName();
78
+ case 'slug':
79
+ return faker.helpers.slugify(faker.lorem.words(3)).toLowerCase();
80
+ case 'description':
81
+ return faker.lorem.paragraph();
82
+ case 'summary':
83
+ return faker.lorem.sentence();
84
+ case 'content':
85
+ return faker.lorem.paragraphs(3);
86
+ case 'title':
87
+ return faker.lorem.sentence();
88
+ case 'username':
89
+ return faker.internet.username();
90
+ case 'password':
91
+ return '$2b$12$dummy.hashed.password.value';
92
+ case 'birthdate':
93
+ return faker.date.birthdate({
94
+ min: 18,
95
+ max: 80,
96
+ mode: 'age'
97
+ });
98
+ case 'futureDate':
99
+ return faker.date.future();
100
+ case 'recentDateOrNull':
101
+ return faker.datatype.boolean() ? faker.date.recent() : null;
102
+ case 'serial':
103
+ return faker.number.int({
104
+ min: 1,
105
+ max: 100
106
+ });
107
+ case 'company':
108
+ return faker.company.name();
109
+ default:
110
+ return undefined;
134
111
  }
135
- return undefined;
136
112
  }
137
113
  /**
138
114
  * Generate value based on column type
139
115
  */ generateByType(column) {
140
- const type = column.type.toLowerCase();
141
- switch(type){
142
- // String types
143
- case 'varchar':
144
- case 'character varying':
145
- case 'text':
116
+ const category = detectTypeCategory(column.type);
117
+ switch(category){
146
118
  case 'string':
147
119
  return this.generateString(column);
148
- // Number types
149
- case 'int':
150
120
  case 'integer':
151
- case 'smallint':
152
- case 'bigint':
153
121
  return faker.number.int({
154
122
  min: 1,
155
123
  max: 1000
156
124
  });
157
125
  case 'decimal':
158
- case 'numeric':
159
- case 'float':
160
- case 'double':
161
- case 'real':
162
126
  return faker.number.float({
163
127
  min: 0,
164
128
  max: 10000,
165
129
  fractionDigits: column.scale || 2
166
130
  });
167
- // Boolean
168
131
  case 'boolean':
169
- case 'bool':
170
132
  return faker.datatype.boolean();
171
- // Date/Time
172
133
  case 'date':
173
- return faker.date.recent({
174
- days: 30
175
- });
176
134
  case 'timestamp':
177
- case 'datetime':
178
135
  return faker.date.recent({
179
136
  days: 30
180
137
  });
181
138
  case 'time':
182
139
  return faker.date.recent().toTimeString().split(' ')[0];
183
- // UUID
184
140
  case 'uuid':
185
141
  return faker.string.uuid();
186
- // JSON
187
142
  case 'json':
188
- case 'jsonb':
189
143
  return {
190
144
  key1: faker.lorem.word(),
191
145
  key2: faker.number.int({
@@ -193,13 +147,11 @@ import { faker } from '@faker-js/faker';
193
147
  max: 100
194
148
  })
195
149
  };
196
- // Array
197
150
  case 'array':
198
151
  return [
199
152
  faker.lorem.word(),
200
153
  faker.lorem.word()
201
154
  ];
202
- // Default
203
155
  default:
204
156
  return faker.lorem.word();
205
157
  }
@@ -208,42 +160,37 @@ import { faker } from '@faker-js/faker';
208
160
  * Generate string value respecting length constraints
209
161
  */ generateString(column) {
210
162
  const maxLength = typeof column.length === 'number' ? column.length : 255;
211
- if (maxLength <= 50) {
212
- return faker.lorem.word().substring(0, maxLength);
213
- } else if (maxLength <= 255) {
214
- return faker.lorem.sentence().substring(0, maxLength);
215
- } else {
216
- return faker.lorem.paragraph().substring(0, maxLength);
163
+ const category = getStringLengthCategory(column.length);
164
+ switch(category){
165
+ case 'word':
166
+ return faker.lorem.word().substring(0, maxLength);
167
+ case 'sentence':
168
+ return faker.lorem.sentence().substring(0, maxLength);
169
+ case 'paragraph':
170
+ return faker.lorem.paragraph().substring(0, maxLength);
217
171
  }
218
172
  }
219
173
  /**
220
174
  * Generate related entity ID (for foreign keys)
221
- * @param relatedEntities Array of entities to pick from
222
175
  */ generateRelationId(relatedEntities) {
223
- if (relatedEntities.length === 0) {
224
- return null;
225
- }
176
+ if (relatedEntities.length === 0) return null;
226
177
  return faker.helpers.arrayElement(relatedEntities).id;
227
178
  }
228
179
  /**
229
180
  * Generate multiple related entity IDs (for many-to-many)
230
- * @param relatedEntities Array of entities to pick from
231
- * @param min Minimum number of relations
232
- * @param max Maximum number of relations
233
181
  */ generateRelationIds(relatedEntities, min = 1, max = 3) {
234
- if (relatedEntities.length === 0) {
235
- return [];
236
- }
182
+ if (relatedEntities.length === 0) return [];
237
183
  const count = faker.number.int({
238
184
  min,
239
185
  max: Math.min(max, relatedEntities.length)
240
186
  });
241
- const selected = faker.helpers.arrayElements(relatedEntities, count);
242
- return selected.map((e)=>e.id);
187
+ return faker.helpers.arrayElements(relatedEntities, count).map((e)=>e.id);
243
188
  }
244
189
  constructor(locale = 'en'){
245
- // Note: faker v8+ uses faker.locale instead of setLocale
246
- // For v8+: faker.locale = locale;
247
- // Locale is set globally, not needed for basic usage
190
+ faker.setDefaultRefDate(new Date());
191
+ if (locale !== 'en') {
192
+ // @ts-expect-error - faker locale property access
193
+ faker.locale = locale;
194
+ }
248
195
  }
249
196
  }
@@ -55,23 +55,6 @@ function _define_property(obj, key, value) {
55
55
  return this.topologicalSort(graph);
56
56
  }
57
57
  /**
58
- * Check if entity has required columns populated
59
- * Used to determine if entity is ready for seeding
60
- */ hasRequiredDependencies(entityName, dataSource) {
61
- const info = this.getEntityInfo(entityName);
62
- for (const relation of info.relations){
63
- if (!relation.isNullable && relation.type === 'many-to-one') {
64
- const targetRepo = dataSource.getRepository(relation.targetEntity);
65
- if (targetRepo) {
66
- // Check if target entity has data
67
- // Note: This is a simplified check, actual implementation may need async
68
- return true;
69
- }
70
- }
71
- }
72
- return true;
73
- }
74
- /**
75
58
  * Find entity metadata by name or table name
76
59
  */ findEntityMetadata(entityName) {
77
60
  return this.getAllEntities().find((e)=>e.name === entityName || e.tableName === entityName);
@@ -0,0 +1,143 @@
1
+ /** System fields that should be skipped during generation */ export const SYSTEM_FIELDS = [
2
+ 'id',
3
+ 'createdAt',
4
+ 'updatedAt',
5
+ 'deletedAt',
6
+ 'createdById',
7
+ 'updatedById',
8
+ 'deletedById'
9
+ ];
10
+ /** Audit fields that return null */ export const AUDIT_FIELDS = [
11
+ 'createdbyid',
12
+ 'updatedbyid',
13
+ 'deletedbyid'
14
+ ];
15
+ /** Identity fields that are skipped */ export const IDENTITY_FIELDS = [
16
+ 'id',
17
+ 'createdat',
18
+ 'updatedat',
19
+ 'deletedat'
20
+ ];
21
+ /** Boolean indicator keywords */ export const BOOLEAN_KEYWORDS = [
22
+ 'verified',
23
+ 'active',
24
+ 'enabled',
25
+ 'public',
26
+ 'readonly',
27
+ 'valid'
28
+ ];
29
+ /**
30
+ * Detect field pattern from column name
31
+ */ export function detectFieldPattern(column) {
32
+ const nameLower = column.propertyName.toLowerCase();
33
+ const typeLower = column.type.toLowerCase();
34
+ // Identity fields (skip)
35
+ if (IDENTITY_FIELDS.includes(nameLower)) return 'skip';
36
+ // Audit fields (null)
37
+ if (AUDIT_FIELDS.includes(nameLower)) return 'null';
38
+ // Boolean fields with specific keywords
39
+ if ((typeLower === 'boolean' || typeLower === 'bool') && BOOLEAN_KEYWORDS.some((k)=>nameLower.includes(k))) {
40
+ return 'boolean';
41
+ }
42
+ // Token fields
43
+ if (nameLower.includes('token')) return 'token';
44
+ // Name fields
45
+ if (nameLower.includes('firstname')) return 'firstName';
46
+ if (nameLower.includes('lastname')) return 'lastName';
47
+ if (nameLower.includes('fullname') || nameLower === 'name') return 'fullName';
48
+ // Contact fields
49
+ if (nameLower.includes('email') && typeLower !== 'boolean') return 'email';
50
+ if (nameLower.includes('phone') || nameLower.includes('mobile')) return 'phone';
51
+ // Location fields
52
+ if (nameLower.includes('address')) return 'address';
53
+ if (nameLower.includes('street')) return 'street';
54
+ if (nameLower.includes('city')) return 'city';
55
+ if (nameLower.includes('state')) return 'state';
56
+ if (nameLower.includes('country')) return 'country';
57
+ if (nameLower.includes('zipcode') || nameLower.includes('postalcode')) return 'zipCode';
58
+ // URL/Web fields
59
+ if (nameLower.includes('url') || nameLower.includes('website')) return 'url';
60
+ if (nameLower.includes('domain')) return 'domain';
61
+ if (nameLower.includes('slug')) return 'slug';
62
+ // Text fields
63
+ if (nameLower.includes('description')) return 'description';
64
+ if (nameLower.includes('summary')) return 'summary';
65
+ if (nameLower.includes('content')) return 'content';
66
+ if (nameLower.includes('title')) return 'title';
67
+ // User fields
68
+ if (nameLower.includes('username')) return 'username';
69
+ if (nameLower.includes('password')) return 'password';
70
+ // Boolean status fields (fallback)
71
+ if (nameLower.includes('isactive') || nameLower.includes('isenabled') || nameLower.includes('ispublic') || nameLower.includes('isverified')) {
72
+ return 'boolean';
73
+ }
74
+ // Date fields
75
+ if (nameLower.includes('birthdate') || nameLower.includes('dateofbirth')) return 'birthdate';
76
+ if (nameLower.includes('expiresat') || nameLower.includes('expirydate')) return 'futureDate';
77
+ if (nameLower.includes('verifiedat')) return 'recentDateOrNull';
78
+ // Serial/Order fields
79
+ if (nameLower.includes('serial') || nameLower.includes('order')) return 'serial';
80
+ // Company
81
+ if (nameLower.includes('company') && !nameLower.includes('id')) return 'company';
82
+ return undefined;
83
+ }
84
+ /**
85
+ * Detect column type category
86
+ */ export function detectTypeCategory(type) {
87
+ const typeLower = type.toLowerCase();
88
+ if ([
89
+ 'varchar',
90
+ 'character varying',
91
+ 'text',
92
+ 'string'
93
+ ].includes(typeLower)) return 'string';
94
+ if ([
95
+ 'int',
96
+ 'integer',
97
+ 'smallint',
98
+ 'bigint'
99
+ ].includes(typeLower)) return 'integer';
100
+ if ([
101
+ 'decimal',
102
+ 'numeric',
103
+ 'float',
104
+ 'double',
105
+ 'real'
106
+ ].includes(typeLower)) return 'decimal';
107
+ if ([
108
+ 'boolean',
109
+ 'bool'
110
+ ].includes(typeLower)) return 'boolean';
111
+ if (typeLower === 'date') return 'date';
112
+ if ([
113
+ 'timestamp',
114
+ 'datetime'
115
+ ].includes(typeLower)) return 'timestamp';
116
+ if (typeLower === 'time') return 'time';
117
+ if (typeLower === 'uuid') return 'uuid';
118
+ if ([
119
+ 'json',
120
+ 'jsonb'
121
+ ].includes(typeLower)) return 'json';
122
+ if (typeLower === 'array') return 'array';
123
+ return 'unknown';
124
+ }
125
+ /**
126
+ * Get string length category for faker generation
127
+ */ export function getStringLengthCategory(length) {
128
+ const maxLength = typeof length === 'number' ? length : 255;
129
+ if (maxLength <= 50) return 'word';
130
+ if (maxLength <= 255) return 'sentence';
131
+ return 'paragraph';
132
+ }
133
+ /**
134
+ * Check if field is a system field
135
+ */ export function isSystemField(fieldName) {
136
+ return SYSTEM_FIELDS.includes(fieldName);
137
+ }
138
+ /**
139
+ * Get token max length respecting column constraints
140
+ */ export function getTokenLength(column) {
141
+ const maxLength = typeof column.length === 'number' ? column.length : 64;
142
+ return Math.min(maxLength, 32);
143
+ }
@@ -1,11 +1,7 @@
1
- /**
2
- * Seed System Exports
3
- *
4
- * Public API for seed data generation system.
5
- */ export { BaseSeeder } from './base-seeder';
1
+ export { BaseSeeder } from './base-seeder';
6
2
  export { EntityReader, IEntityInfo, IColumnInfo, IRelationInfo } from './entity-reader';
7
3
  export { DataGenerator } from './data-generator';
8
- export { TemplateGenerator } from './template-generator';
9
4
  export { SeedRunner, ISeedResult, ISeedOptions, ISeederLogger, defaultLogger } from './seed-runner';
10
- export { seedConfig, ISeedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder } from './seed-config';
5
+ export { seedConfig, ISeedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder, configureSeedConfig } from './seed-config';
6
+ export { SYSTEM_FIELDS, isSystemField, detectFieldPattern, detectTypeCategory } from './field-patterns';
11
7
  export { runSeedCli } from './cli';
@@ -1,81 +1,31 @@
1
- /**
2
- * Seed Configuration
3
- *
4
- * Configuration for seed data generation.
5
- * Customize record counts, seeding order, and behavior.
6
- */ /**
7
- * Default seed configuration
8
- */ export const seedConfig = {
9
- // Default record counts per entity
10
- counts: {
11
- // Auth entities
12
- Company: 10,
13
- CompanyBranch: 25,
14
- User: 50,
15
- // IAM entities
16
- Role: 8,
17
- Permission: 45,
18
- Action: 30,
19
- Menu: 20,
20
- UserIAMPermission: 50,
21
- UserCompanyPermission: 100,
22
- // Storage entities
23
- StorageConfig: 5,
24
- Folder: 20,
25
- FileManager: 100
26
- },
27
- // Entity seeding order (respects FK constraints)
28
- // Parent entities must come before child entities
29
- order: [
30
- // Auth - foundational
31
- 'Company',
32
- 'CompanyBranch',
33
- 'User',
34
- // IAM - permissions system
35
- 'Action',
36
- 'Role',
37
- 'Permission',
38
- 'Menu',
39
- 'UserIAMPermission',
40
- 'UserCompanyPermission',
41
- // Storage - file system
42
- 'StorageConfig',
43
- 'Folder',
44
- 'FileManager'
45
- ],
46
- // Skip these entities (system tables, migrations)
1
+ export const seedConfig = {
2
+ counts: {},
3
+ order: [],
47
4
  skipEntities: [
48
5
  'migrations',
49
6
  'typeorm_metadata',
50
7
  'Migration',
51
8
  'Typeorm_Metadata'
52
9
  ],
53
- // Faker locale (en, ar, es, fr, de, etc.)
54
10
  locale: 'en',
55
- // Respect soft delete when clearing data
56
11
  respectSoftDelete: true
57
12
  };
58
- /**
59
- * Get count for entity (with default fallback)
60
- */ export function getEntityCount(entityName, config = seedConfig) {
13
+ export function configureSeedConfig(config) {
14
+ Object.assign(seedConfig, config);
15
+ }
16
+ export function getEntityCount(entityName, config = seedConfig) {
61
17
  return config.counts[entityName] || 10;
62
18
  }
63
- /**
64
- * Check if entity should be skipped
65
- */ export function shouldSkipEntity(entityName, config = seedConfig) {
19
+ export function shouldSkipEntity(entityName, config = seedConfig) {
66
20
  return config.skipEntities.some((skip)=>skip.toLowerCase() === entityName.toLowerCase());
67
21
  }
68
- /**
69
- * Get seeding order for entities
70
- */ export function getSeedingOrder(availableEntities, config = seedConfig) {
22
+ export function getSeedingOrder(availableEntities, config = seedConfig) {
71
23
  const ordered = [];
72
- // Add entities in configured order
73
24
  for (const entityName of config.order){
74
25
  if (availableEntities.includes(entityName) && !shouldSkipEntity(entityName, config)) {
75
26
  ordered.push(entityName);
76
27
  }
77
28
  }
78
- // Add remaining entities not in order configuration
79
29
  for (const entityName of availableEntities){
80
30
  if (!ordered.includes(entityName) && !shouldSkipEntity(entityName, config)) {
81
31
  ordered.push(entityName);
@@ -61,6 +61,12 @@ import { seedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder } from '.
61
61
  */ hasCustomSeeder(entityName) {
62
62
  return this.customSeeders.has(entityName);
63
63
  }
64
+ getSeeder(entityName, entity, dataSource, entityInfo, batchSize) {
65
+ if (this.customSeeders.has(entityName)) {
66
+ return this.customSeeders.get(entityName);
67
+ }
68
+ return new GenericSeeder(dataSource, entity, this.dataGenerator, entityInfo, batchSize);
69
+ }
64
70
  /**
65
71
  * Run seeds for all entities
66
72
  * @param options Seeding options
@@ -125,13 +131,7 @@ import { seedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder } from '.
125
131
  if (!entity) {
126
132
  throw new Error(`Entity ${entityName} not found`);
127
133
  }
128
- // Get seeder (custom or generic)
129
- let seeder;
130
- if (this.customSeeders.has(entityName)) {
131
- seeder = this.customSeeders.get(entityName);
132
- } else {
133
- seeder = new GenericSeeder(queryRunner.manager.connection, entity, this.dataGenerator, entityInfo, options.batchSize);
134
- }
134
+ const seeder = this.getSeeder(entityName, entity, queryRunner.manager.connection, entityInfo, options.batchSize);
135
135
  // Dry run mode - preview without executing
136
136
  if (options.dryRun) {
137
137
  const count = options.count ?? getEntityCount(entityName);
@@ -203,13 +203,7 @@ import { seedConfig, getEntityCount, shouldSkipEntity, getSeedingOrder } from '.
203
203
  if (!entity) {
204
204
  throw new Error(`Entity ${entityName} not found`);
205
205
  }
206
- // Use custom seeder if available, otherwise generic
207
- let seeder;
208
- if (this.customSeeders.has(entityName)) {
209
- seeder = this.customSeeders.get(entityName);
210
- } else {
211
- seeder = new GenericSeeder(this.dataSource, entity, this.dataGenerator, this.entityReader.getEntityInfo(entityName));
212
- }
206
+ const seeder = this.getSeeder(entityName, entity, this.dataSource, this.entityReader.getEntityInfo(entityName));
213
207
  const countBefore = await seeder.count(true);
214
208
  // Skip if already empty
215
209
  if (countBefore === 0) {