@flusys/nestjs-core 1.1.0-beta → 1.1.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 +505 -63
- 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 +110 -142
- package/cjs/seeders/entity-reader.js +0 -17
- package/cjs/seeders/field-patterns.js +176 -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 +110 -149
- package/fesm/seeders/entity-reader.js +0 -17
- package/fesm/seeders/field-patterns.js +147 -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/app-config.interfaces.d.ts +1 -0
- package/interfaces/base-entity.interface.d.ts +3 -0
- package/interfaces/database.interface.d.ts +1 -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
|
@@ -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
|
-
|
|
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
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
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,122 @@ import { faker } from '@faker-js/faker';
|
|
|
49
38
|
return entity;
|
|
50
39
|
}
|
|
51
40
|
/**
|
|
52
|
-
* Generate value based on
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
case 'emailProvider':
|
|
110
|
+
return faker.helpers.arrayElement([
|
|
111
|
+
'smtp',
|
|
112
|
+
'sendgrid',
|
|
113
|
+
'mailgun',
|
|
114
|
+
'ses',
|
|
115
|
+
'postmark'
|
|
116
|
+
]);
|
|
117
|
+
case 'formAccessType':
|
|
118
|
+
return faker.helpers.arrayElement([
|
|
119
|
+
'public',
|
|
120
|
+
'authenticated',
|
|
121
|
+
'permission'
|
|
122
|
+
]);
|
|
123
|
+
default:
|
|
124
|
+
return undefined;
|
|
134
125
|
}
|
|
135
|
-
return undefined;
|
|
136
126
|
}
|
|
137
127
|
/**
|
|
138
128
|
* Generate value based on column type
|
|
139
129
|
*/ generateByType(column) {
|
|
140
|
-
const
|
|
141
|
-
switch(
|
|
142
|
-
// String types
|
|
143
|
-
case 'varchar':
|
|
144
|
-
case 'character varying':
|
|
145
|
-
case 'text':
|
|
130
|
+
const category = detectTypeCategory(column.type);
|
|
131
|
+
switch(category){
|
|
146
132
|
case 'string':
|
|
147
133
|
return this.generateString(column);
|
|
148
|
-
// Number types
|
|
149
|
-
case 'int':
|
|
150
134
|
case 'integer':
|
|
151
|
-
case 'smallint':
|
|
152
|
-
case 'bigint':
|
|
153
135
|
return faker.number.int({
|
|
154
136
|
min: 1,
|
|
155
137
|
max: 1000
|
|
156
138
|
});
|
|
157
139
|
case 'decimal':
|
|
158
|
-
case 'numeric':
|
|
159
|
-
case 'float':
|
|
160
|
-
case 'double':
|
|
161
|
-
case 'real':
|
|
162
140
|
return faker.number.float({
|
|
163
141
|
min: 0,
|
|
164
142
|
max: 10000,
|
|
165
143
|
fractionDigits: column.scale || 2
|
|
166
144
|
});
|
|
167
|
-
// Boolean
|
|
168
145
|
case 'boolean':
|
|
169
|
-
case 'bool':
|
|
170
146
|
return faker.datatype.boolean();
|
|
171
|
-
// Date/Time
|
|
172
147
|
case 'date':
|
|
173
|
-
return faker.date.recent({
|
|
174
|
-
days: 30
|
|
175
|
-
});
|
|
176
148
|
case 'timestamp':
|
|
177
|
-
case 'datetime':
|
|
178
149
|
return faker.date.recent({
|
|
179
150
|
days: 30
|
|
180
151
|
});
|
|
181
152
|
case 'time':
|
|
182
153
|
return faker.date.recent().toTimeString().split(' ')[0];
|
|
183
|
-
// UUID
|
|
184
154
|
case 'uuid':
|
|
185
155
|
return faker.string.uuid();
|
|
186
|
-
// JSON
|
|
187
156
|
case 'json':
|
|
188
|
-
case 'jsonb':
|
|
189
157
|
return {
|
|
190
158
|
key1: faker.lorem.word(),
|
|
191
159
|
key2: faker.number.int({
|
|
@@ -193,13 +161,11 @@ import { faker } from '@faker-js/faker';
|
|
|
193
161
|
max: 100
|
|
194
162
|
})
|
|
195
163
|
};
|
|
196
|
-
// Array
|
|
197
164
|
case 'array':
|
|
198
165
|
return [
|
|
199
166
|
faker.lorem.word(),
|
|
200
167
|
faker.lorem.word()
|
|
201
168
|
];
|
|
202
|
-
// Default
|
|
203
169
|
default:
|
|
204
170
|
return faker.lorem.word();
|
|
205
171
|
}
|
|
@@ -208,42 +174,37 @@ import { faker } from '@faker-js/faker';
|
|
|
208
174
|
* Generate string value respecting length constraints
|
|
209
175
|
*/ generateString(column) {
|
|
210
176
|
const maxLength = typeof column.length === 'number' ? column.length : 255;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
177
|
+
const category = getStringLengthCategory(column.length);
|
|
178
|
+
switch(category){
|
|
179
|
+
case 'word':
|
|
180
|
+
return faker.lorem.word().substring(0, maxLength);
|
|
181
|
+
case 'sentence':
|
|
182
|
+
return faker.lorem.sentence().substring(0, maxLength);
|
|
183
|
+
case 'paragraph':
|
|
184
|
+
return faker.lorem.paragraph().substring(0, maxLength);
|
|
217
185
|
}
|
|
218
186
|
}
|
|
219
187
|
/**
|
|
220
188
|
* Generate related entity ID (for foreign keys)
|
|
221
|
-
* @param relatedEntities Array of entities to pick from
|
|
222
189
|
*/ generateRelationId(relatedEntities) {
|
|
223
|
-
if (relatedEntities.length === 0)
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
190
|
+
if (relatedEntities.length === 0) return null;
|
|
226
191
|
return faker.helpers.arrayElement(relatedEntities).id;
|
|
227
192
|
}
|
|
228
193
|
/**
|
|
229
194
|
* 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
195
|
*/ generateRelationIds(relatedEntities, min = 1, max = 3) {
|
|
234
|
-
if (relatedEntities.length === 0)
|
|
235
|
-
return [];
|
|
236
|
-
}
|
|
196
|
+
if (relatedEntities.length === 0) return [];
|
|
237
197
|
const count = faker.number.int({
|
|
238
198
|
min,
|
|
239
199
|
max: Math.min(max, relatedEntities.length)
|
|
240
200
|
});
|
|
241
|
-
|
|
242
|
-
return selected.map((e)=>e.id);
|
|
201
|
+
return faker.helpers.arrayElements(relatedEntities, count).map((e)=>e.id);
|
|
243
202
|
}
|
|
244
203
|
constructor(locale = 'en'){
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
204
|
+
faker.setDefaultRefDate(new Date());
|
|
205
|
+
if (locale !== 'en') {
|
|
206
|
+
// @ts-expect-error - faker locale property access
|
|
207
|
+
faker.locale = locale;
|
|
208
|
+
}
|
|
248
209
|
}
|
|
249
210
|
}
|
|
@@ -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,147 @@
|
|
|
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
|
+
// Email provider type
|
|
83
|
+
if (nameLower === 'provider' || nameLower === 'emailprovider') return 'emailProvider';
|
|
84
|
+
// Form access type
|
|
85
|
+
if (nameLower === 'accesstype' || nameLower === 'access_type') return 'formAccessType';
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detect column type category
|
|
90
|
+
*/ export function detectTypeCategory(type) {
|
|
91
|
+
const typeLower = type.toLowerCase();
|
|
92
|
+
if ([
|
|
93
|
+
'varchar',
|
|
94
|
+
'character varying',
|
|
95
|
+
'text',
|
|
96
|
+
'string'
|
|
97
|
+
].includes(typeLower)) return 'string';
|
|
98
|
+
if ([
|
|
99
|
+
'int',
|
|
100
|
+
'integer',
|
|
101
|
+
'smallint',
|
|
102
|
+
'bigint'
|
|
103
|
+
].includes(typeLower)) return 'integer';
|
|
104
|
+
if ([
|
|
105
|
+
'decimal',
|
|
106
|
+
'numeric',
|
|
107
|
+
'float',
|
|
108
|
+
'double',
|
|
109
|
+
'real'
|
|
110
|
+
].includes(typeLower)) return 'decimal';
|
|
111
|
+
if ([
|
|
112
|
+
'boolean',
|
|
113
|
+
'bool'
|
|
114
|
+
].includes(typeLower)) return 'boolean';
|
|
115
|
+
if (typeLower === 'date') return 'date';
|
|
116
|
+
if ([
|
|
117
|
+
'timestamp',
|
|
118
|
+
'datetime'
|
|
119
|
+
].includes(typeLower)) return 'timestamp';
|
|
120
|
+
if (typeLower === 'time') return 'time';
|
|
121
|
+
if (typeLower === 'uuid') return 'uuid';
|
|
122
|
+
if ([
|
|
123
|
+
'json',
|
|
124
|
+
'jsonb'
|
|
125
|
+
].includes(typeLower)) return 'json';
|
|
126
|
+
if (typeLower === 'array') return 'array';
|
|
127
|
+
return 'unknown';
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get string length category for faker generation
|
|
131
|
+
*/ export function getStringLengthCategory(length) {
|
|
132
|
+
const maxLength = typeof length === 'number' ? length : 255;
|
|
133
|
+
if (maxLength <= 50) return 'word';
|
|
134
|
+
if (maxLength <= 255) return 'sentence';
|
|
135
|
+
return 'paragraph';
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if field is a system field
|
|
139
|
+
*/ export function isSystemField(fieldName) {
|
|
140
|
+
return SYSTEM_FIELDS.includes(fieldName);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get token max length respecting column constraints
|
|
144
|
+
*/ export function getTokenLength(column) {
|
|
145
|
+
const maxLength = typeof column.length === 'number' ? column.length : 64;
|
|
146
|
+
return Math.min(maxLength, 32);
|
|
147
|
+
}
|
package/fesm/seeders/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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);
|