procbay-schema 1.0.58 → 1.0.60
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "procbay-schema",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.60",
|
|
4
4
|
"description": "A set of utilities for managing Prisma database schemas, seeding, and maintenance operations for the Procure-to-Pay system",
|
|
5
5
|
"main": "src/prisma/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@ export async function seedIndustries(prismaClient) {
|
|
|
23
23
|
const prisma = prismaClient || new PrismaClient();
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
// Load
|
|
26
|
+
// Load industries from JSON
|
|
27
27
|
const industryData = loadContentData("industries");
|
|
28
28
|
|
|
29
29
|
if (!industryData || industryData.length === 0) {
|
|
@@ -40,7 +40,7 @@ export async function seedIndustries(prismaClient) {
|
|
|
40
40
|
enableMemoryMonitoring: true,
|
|
41
41
|
batchUpsertMode: true,
|
|
42
42
|
// Industry-specific unique fields for constraint mapping
|
|
43
|
-
uniqueFields: ["name"
|
|
43
|
+
uniqueFields: ["name"],
|
|
44
44
|
// Medium batch size as specified (500-1000)
|
|
45
45
|
batchSize: 750,
|
|
46
46
|
// Memory monitoring configuration
|
|
@@ -60,29 +60,24 @@ export async function seedIndustries(prismaClient) {
|
|
|
60
60
|
|
|
61
61
|
for (const industry of chunk) {
|
|
62
62
|
try {
|
|
63
|
-
// Ensure parent_id defaults to 0 if not provided
|
|
64
|
-
const industryRecord = {
|
|
65
|
-
...industry,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
63
|
await client.industry.upsert({
|
|
69
64
|
where: {
|
|
70
|
-
name:
|
|
65
|
+
name: industry.name,
|
|
71
66
|
},
|
|
72
67
|
update: {
|
|
73
|
-
slug:
|
|
74
|
-
description:
|
|
75
|
-
is_active:
|
|
76
|
-
created_by:
|
|
77
|
-
updated_by:
|
|
68
|
+
slug: industry.slug,
|
|
69
|
+
description: industry.description,
|
|
70
|
+
is_active: industry.is_active,
|
|
71
|
+
created_by: industry.created_by,
|
|
72
|
+
updated_by: industry.updated_by,
|
|
78
73
|
},
|
|
79
74
|
create: {
|
|
80
|
-
name:
|
|
81
|
-
slug:
|
|
82
|
-
description:
|
|
83
|
-
is_active:
|
|
84
|
-
created_by:
|
|
85
|
-
updated_by:
|
|
75
|
+
name: industry.name,
|
|
76
|
+
slug: industry.slug,
|
|
77
|
+
description: industry.description,
|
|
78
|
+
is_active: industry.is_active,
|
|
79
|
+
created_by: industry.created_by,
|
|
80
|
+
updated_by: industry.updated_by,
|
|
86
81
|
},
|
|
87
82
|
});
|
|
88
83
|
created++;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import colors from "colors";
|
|
2
2
|
import { connectionManager, withContentPool } from "./connection-manager.js";
|
|
3
3
|
import { MemoryManager } from "./performance-helpers.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
logSeederStart,
|
|
6
|
+
logSeederComplete,
|
|
7
|
+
logSeederError,
|
|
8
|
+
} from "./progress-tracker.js";
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Enhanced content seeder for medium-volume content data (500-1000 records)
|
|
@@ -19,13 +23,14 @@ export class ContentSeeder {
|
|
|
19
23
|
memoryCheckInterval: options.memoryCheckInterval || 100,
|
|
20
24
|
memoryThreshold: options.memoryThreshold || 0.5,
|
|
21
25
|
useConnectionPooling: options.useConnectionPooling !== false,
|
|
22
|
-
enableUniqueConstraintMapping:
|
|
26
|
+
enableUniqueConstraintMapping:
|
|
27
|
+
options.enableUniqueConstraintMapping !== false,
|
|
23
28
|
batchUpsertMode: options.batchUpsertMode !== false,
|
|
24
|
-
uniqueFields: options.uniqueFields || [
|
|
25
|
-
dbName: options.dbName ||
|
|
29
|
+
uniqueFields: options.uniqueFields || ["name"],
|
|
30
|
+
dbName: options.dbName || "default",
|
|
26
31
|
dbUrl: options.dbUrl || process.env.DATABASE_URL,
|
|
27
32
|
logProgress: options.logProgress !== false,
|
|
28
|
-
...options
|
|
33
|
+
...options,
|
|
29
34
|
};
|
|
30
35
|
|
|
31
36
|
this.uniqueConstraintMaps = new Map();
|
|
@@ -33,7 +38,7 @@ export class ContentSeeder {
|
|
|
33
38
|
maxHeapUsed: 0,
|
|
34
39
|
startHeapUsed: 0,
|
|
35
40
|
currentHeapUsed: 0,
|
|
36
|
-
gcTriggered: 0
|
|
41
|
+
gcTriggered: 0,
|
|
37
42
|
};
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -61,11 +66,21 @@ export class ContentSeeder {
|
|
|
61
66
|
this.options.dbName,
|
|
62
67
|
this.options.dbUrl,
|
|
63
68
|
async (pooledClient) => {
|
|
64
|
-
return await this.performSeeding(
|
|
69
|
+
return await this.performSeeding(
|
|
70
|
+
entityName,
|
|
71
|
+
data,
|
|
72
|
+
processor,
|
|
73
|
+
pooledClient
|
|
74
|
+
);
|
|
65
75
|
}
|
|
66
76
|
);
|
|
67
77
|
} else {
|
|
68
|
-
return await this.performSeeding(
|
|
78
|
+
return await this.performSeeding(
|
|
79
|
+
entityName,
|
|
80
|
+
data,
|
|
81
|
+
processor,
|
|
82
|
+
prismaClient
|
|
83
|
+
);
|
|
69
84
|
}
|
|
70
85
|
} catch (error) {
|
|
71
86
|
logSeederError(entityName, error.message);
|
|
@@ -83,9 +98,11 @@ export class ContentSeeder {
|
|
|
83
98
|
const errors = [];
|
|
84
99
|
|
|
85
100
|
if (this.options.logProgress) {
|
|
86
|
-
console.log(
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
console.log(
|
|
102
|
+
colors.cyan(
|
|
103
|
+
`Processing ${data.length.toLocaleString()} ${entityName} in ${chunks.length} batches of ${this.options.batchSize}...`
|
|
104
|
+
)
|
|
105
|
+
);
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
// Build unique constraint map if enabled
|
|
@@ -95,14 +112,17 @@ export class ContentSeeder {
|
|
|
95
112
|
|
|
96
113
|
// Process chunks in parallel batches
|
|
97
114
|
for (let i = 0; i < chunks.length; i += this.options.maxConcurrentBatches) {
|
|
98
|
-
const currentBatch = chunks.slice(
|
|
99
|
-
|
|
115
|
+
const currentBatch = chunks.slice(
|
|
116
|
+
i,
|
|
117
|
+
i + this.options.maxConcurrentBatches
|
|
118
|
+
);
|
|
119
|
+
|
|
100
120
|
const batchPromises = currentBatch.map(async (chunk, index) => {
|
|
101
121
|
const chunkIndex = i + index;
|
|
102
122
|
return await this.processChunkWithRetry(
|
|
103
|
-
chunk,
|
|
104
|
-
processor,
|
|
105
|
-
chunkIndex,
|
|
123
|
+
chunk,
|
|
124
|
+
processor,
|
|
125
|
+
chunkIndex,
|
|
106
126
|
entityName,
|
|
107
127
|
prismaClient
|
|
108
128
|
);
|
|
@@ -112,7 +132,7 @@ export class ContentSeeder {
|
|
|
112
132
|
|
|
113
133
|
// Aggregate results
|
|
114
134
|
batchResults.forEach((result, index) => {
|
|
115
|
-
if (result.status ===
|
|
135
|
+
if (result.status === "fulfilled") {
|
|
116
136
|
totalProcessed += result.value.processed;
|
|
117
137
|
totalCreated += result.value.created;
|
|
118
138
|
} else {
|
|
@@ -132,12 +152,18 @@ export class ContentSeeder {
|
|
|
132
152
|
|
|
133
153
|
// Progress reporting
|
|
134
154
|
if (this.options.logProgress) {
|
|
135
|
-
this.reportProgress(
|
|
155
|
+
this.reportProgress(
|
|
156
|
+
i + this.options.maxConcurrentBatches,
|
|
157
|
+
chunks.length,
|
|
158
|
+
totalProcessed,
|
|
159
|
+
totalCreated,
|
|
160
|
+
data.length
|
|
161
|
+
);
|
|
136
162
|
}
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
if (this.options.logProgress) {
|
|
140
|
-
process.stdout.write(
|
|
166
|
+
process.stdout.write("\n");
|
|
141
167
|
}
|
|
142
168
|
|
|
143
169
|
const duration = (Date.now() - Date.now()) / 1000;
|
|
@@ -147,15 +173,19 @@ export class ContentSeeder {
|
|
|
147
173
|
errors,
|
|
148
174
|
duration,
|
|
149
175
|
rate: totalProcessed / duration,
|
|
150
|
-
memoryStats: this.memoryStats
|
|
176
|
+
memoryStats: this.memoryStats,
|
|
151
177
|
};
|
|
152
178
|
|
|
153
179
|
if (errors.length === 0) {
|
|
154
|
-
logSeederComplete(
|
|
180
|
+
logSeederComplete(
|
|
181
|
+
`${entityName} (${totalCreated.toLocaleString()} created)`
|
|
182
|
+
);
|
|
155
183
|
} else {
|
|
156
|
-
console.log(
|
|
157
|
-
|
|
158
|
-
|
|
184
|
+
console.log(
|
|
185
|
+
colors.yellow(
|
|
186
|
+
`${entityName} completed with ${errors.length} errors. ${totalCreated.toLocaleString()} created.`
|
|
187
|
+
)
|
|
188
|
+
);
|
|
159
189
|
}
|
|
160
190
|
|
|
161
191
|
// Final memory cleanup
|
|
@@ -167,16 +197,26 @@ export class ContentSeeder {
|
|
|
167
197
|
/**
|
|
168
198
|
* Process chunk with retry logic and exponential backoff
|
|
169
199
|
*/
|
|
170
|
-
async processChunkWithRetry(
|
|
200
|
+
async processChunkWithRetry(
|
|
201
|
+
chunk,
|
|
202
|
+
processor,
|
|
203
|
+
chunkIndex,
|
|
204
|
+
entityName,
|
|
205
|
+
prismaClient
|
|
206
|
+
) {
|
|
171
207
|
let lastError;
|
|
172
|
-
|
|
208
|
+
|
|
173
209
|
for (let attempt = 1; attempt <= this.options.retryAttempts; attempt++) {
|
|
174
210
|
try {
|
|
175
211
|
let result;
|
|
176
212
|
|
|
177
213
|
if (this.options.batchUpsertMode) {
|
|
178
214
|
// Use batch upsert if enabled
|
|
179
|
-
result = await this.performBatchUpsert(
|
|
215
|
+
result = await this.performBatchUpsert(
|
|
216
|
+
chunk,
|
|
217
|
+
entityName,
|
|
218
|
+
prismaClient
|
|
219
|
+
);
|
|
180
220
|
} else {
|
|
181
221
|
// Use custom processor
|
|
182
222
|
result = await processor(chunk, chunkIndex, prismaClient);
|
|
@@ -184,25 +224,27 @@ export class ContentSeeder {
|
|
|
184
224
|
|
|
185
225
|
return {
|
|
186
226
|
processed: chunk.length,
|
|
187
|
-
created: result.created || result.count || chunk.length
|
|
227
|
+
created: result.created || result.count || chunk.length,
|
|
188
228
|
};
|
|
189
229
|
} catch (error) {
|
|
190
230
|
lastError = error;
|
|
191
231
|
if (attempt < this.options.retryAttempts) {
|
|
192
|
-
const delay = this.options.exponentialBackoff
|
|
232
|
+
const delay = this.options.exponentialBackoff
|
|
193
233
|
? this.options.retryDelay * Math.pow(2, attempt - 1)
|
|
194
234
|
: this.options.retryDelay;
|
|
195
|
-
|
|
235
|
+
|
|
196
236
|
if (this.options.logProgress) {
|
|
197
|
-
console.log(
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
console.log(
|
|
238
|
+
colors.yellow(
|
|
239
|
+
`Chunk ${chunkIndex} attempt ${attempt} failed, retrying in ${delay}ms...`
|
|
240
|
+
)
|
|
241
|
+
);
|
|
200
242
|
}
|
|
201
243
|
await this.sleep(delay);
|
|
202
244
|
}
|
|
203
245
|
}
|
|
204
246
|
}
|
|
205
|
-
|
|
247
|
+
|
|
206
248
|
throw new Error(
|
|
207
249
|
`Chunk ${chunkIndex} failed after ${this.options.retryAttempts} attempts: ${lastError.message}`
|
|
208
250
|
);
|
|
@@ -212,12 +254,12 @@ export class ContentSeeder {
|
|
|
212
254
|
* Perform batch upsert with unique constraint handling
|
|
213
255
|
*/
|
|
214
256
|
async performBatchUpsert(chunk, entityName, prismaClient) {
|
|
215
|
-
const model =
|
|
216
|
-
|
|
257
|
+
const model = this.getPrismaModelName(entityName);
|
|
258
|
+
|
|
217
259
|
if (this.options.enableUniqueConstraintMapping) {
|
|
218
260
|
// Filter out records that would violate unique constraints
|
|
219
261
|
const validRecords = this.filterUniqueConstraints(chunk, entityName);
|
|
220
|
-
|
|
262
|
+
|
|
221
263
|
if (validRecords.length === 0) {
|
|
222
264
|
return { created: 0 };
|
|
223
265
|
}
|
|
@@ -229,7 +271,7 @@ export class ContentSeeder {
|
|
|
229
271
|
// Use createMany for batch insert with skip duplicates
|
|
230
272
|
const result = await prismaClient[model].createMany({
|
|
231
273
|
data: chunk,
|
|
232
|
-
skipDuplicates: true
|
|
274
|
+
skipDuplicates: true,
|
|
233
275
|
});
|
|
234
276
|
|
|
235
277
|
// Update unique constraint map
|
|
@@ -240,7 +282,11 @@ export class ContentSeeder {
|
|
|
240
282
|
return result;
|
|
241
283
|
} catch (error) {
|
|
242
284
|
// Fallback to individual upserts if batch fails
|
|
243
|
-
console.warn(
|
|
285
|
+
console.warn(
|
|
286
|
+
colors.yellow(
|
|
287
|
+
`Batch upsert failed for ${entityName}, falling back to individual upserts`
|
|
288
|
+
)
|
|
289
|
+
);
|
|
244
290
|
return await this.fallbackToIndividualUpserts(chunk, model, prismaClient);
|
|
245
291
|
}
|
|
246
292
|
}
|
|
@@ -250,18 +296,20 @@ export class ContentSeeder {
|
|
|
250
296
|
*/
|
|
251
297
|
async fallbackToIndividualUpserts(chunk, model, prismaClient) {
|
|
252
298
|
let created = 0;
|
|
253
|
-
|
|
299
|
+
|
|
254
300
|
for (const record of chunk) {
|
|
255
301
|
try {
|
|
256
302
|
await prismaClient[model].upsert({
|
|
257
303
|
where: this.buildWhereClause(record),
|
|
258
304
|
update: record,
|
|
259
|
-
create: record
|
|
305
|
+
create: record,
|
|
260
306
|
});
|
|
261
307
|
created++;
|
|
262
308
|
} catch (error) {
|
|
263
309
|
// Log individual errors but continue processing
|
|
264
|
-
console.warn(
|
|
310
|
+
console.warn(
|
|
311
|
+
colors.yellow(`Individual upsert failed: ${error.message}`)
|
|
312
|
+
);
|
|
265
313
|
}
|
|
266
314
|
}
|
|
267
315
|
|
|
@@ -272,17 +320,17 @@ export class ContentSeeder {
|
|
|
272
320
|
* Build unique constraint map for efficient duplicate detection
|
|
273
321
|
*/
|
|
274
322
|
async buildUniqueConstraintMap(entityName, prismaClient) {
|
|
275
|
-
const model =
|
|
276
|
-
|
|
323
|
+
const model = this.getPrismaModelName(entityName);
|
|
324
|
+
|
|
277
325
|
try {
|
|
278
326
|
const existingRecords = await prismaClient[model].findMany({
|
|
279
|
-
select: this.buildSelectClause()
|
|
327
|
+
select: this.buildSelectClause(),
|
|
280
328
|
});
|
|
281
329
|
|
|
282
330
|
const constraintMap = new Map();
|
|
283
|
-
|
|
284
|
-
existingRecords.forEach(record => {
|
|
285
|
-
this.options.uniqueFields.forEach(field => {
|
|
331
|
+
|
|
332
|
+
existingRecords.forEach((record) => {
|
|
333
|
+
this.options.uniqueFields.forEach((field) => {
|
|
286
334
|
if (record[field]) {
|
|
287
335
|
constraintMap.set(`${field}:${record[field]}`, true);
|
|
288
336
|
}
|
|
@@ -290,14 +338,20 @@ export class ContentSeeder {
|
|
|
290
338
|
});
|
|
291
339
|
|
|
292
340
|
this.uniqueConstraintMaps.set(entityName, constraintMap);
|
|
293
|
-
|
|
341
|
+
|
|
294
342
|
if (this.options.logProgress) {
|
|
295
|
-
console.log(
|
|
296
|
-
|
|
297
|
-
|
|
343
|
+
console.log(
|
|
344
|
+
colors.debug(
|
|
345
|
+
`Built unique constraint map for ${entityName}: ${constraintMap.size} existing records`
|
|
346
|
+
)
|
|
347
|
+
);
|
|
298
348
|
}
|
|
299
349
|
} catch (error) {
|
|
300
|
-
console.warn(
|
|
350
|
+
console.warn(
|
|
351
|
+
colors.yellow(
|
|
352
|
+
`Failed to build unique constraint map for ${entityName}: ${error.message}`
|
|
353
|
+
)
|
|
354
|
+
);
|
|
301
355
|
}
|
|
302
356
|
}
|
|
303
357
|
|
|
@@ -308,7 +362,7 @@ export class ContentSeeder {
|
|
|
308
362
|
const constraintMap = this.uniqueConstraintMaps.get(entityName);
|
|
309
363
|
if (!constraintMap) return chunk;
|
|
310
364
|
|
|
311
|
-
return chunk.filter(record => {
|
|
365
|
+
return chunk.filter((record) => {
|
|
312
366
|
for (const field of this.options.uniqueFields) {
|
|
313
367
|
if (record[field] && constraintMap.has(`${field}:${record[field]}`)) {
|
|
314
368
|
return false; // Skip this record as it would violate unique constraint
|
|
@@ -325,8 +379,8 @@ export class ContentSeeder {
|
|
|
325
379
|
const constraintMap = this.uniqueConstraintMaps.get(entityName);
|
|
326
380
|
if (!constraintMap) return;
|
|
327
381
|
|
|
328
|
-
chunk.forEach(record => {
|
|
329
|
-
this.options.uniqueFields.forEach(field => {
|
|
382
|
+
chunk.forEach((record) => {
|
|
383
|
+
this.options.uniqueFields.forEach((field) => {
|
|
330
384
|
if (record[field]) {
|
|
331
385
|
constraintMap.set(`${field}:${record[field]}`, true);
|
|
332
386
|
}
|
|
@@ -334,12 +388,73 @@ export class ContentSeeder {
|
|
|
334
388
|
});
|
|
335
389
|
}
|
|
336
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Convert entity name (plural) to Prisma model name (singular)
|
|
393
|
+
* Handles common English pluralization patterns
|
|
394
|
+
*/
|
|
395
|
+
getPrismaModelName(entityName) {
|
|
396
|
+
const lower = entityName.toLowerCase();
|
|
397
|
+
|
|
398
|
+
// Handle special cases
|
|
399
|
+
const specialCases = {
|
|
400
|
+
industries: "industry",
|
|
401
|
+
categories: "category",
|
|
402
|
+
countries: "country",
|
|
403
|
+
cities: "city",
|
|
404
|
+
states: "state",
|
|
405
|
+
currencies: "currency",
|
|
406
|
+
companies: "company",
|
|
407
|
+
activities: "activity",
|
|
408
|
+
properties: "property",
|
|
409
|
+
facilities: "facility",
|
|
410
|
+
authorities: "authority",
|
|
411
|
+
deliveries: "delivery",
|
|
412
|
+
templates: "template",
|
|
413
|
+
forms: "form",
|
|
414
|
+
attributes: "attribute",
|
|
415
|
+
roles: "role",
|
|
416
|
+
permissions: "permission",
|
|
417
|
+
users: "user",
|
|
418
|
+
departments: "department",
|
|
419
|
+
vendors: "vendor",
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// Return special case if exists
|
|
423
|
+
if (specialCases[lower]) {
|
|
424
|
+
return specialCases[lower];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Handle -ies ending (e.g., "stories" -> "story")
|
|
428
|
+
if (lower.endsWith("ies")) {
|
|
429
|
+
return lower.slice(0, -3) + "y";
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Handle -es ending after s, x, z, ch, sh (e.g., "boxes" -> "box")
|
|
433
|
+
if (
|
|
434
|
+
lower.endsWith("ses") ||
|
|
435
|
+
lower.endsWith("xes") ||
|
|
436
|
+
lower.endsWith("zes") ||
|
|
437
|
+
lower.endsWith("ches") ||
|
|
438
|
+
lower.endsWith("shes")
|
|
439
|
+
) {
|
|
440
|
+
return lower.slice(0, -2);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Handle regular -s ending
|
|
444
|
+
if (lower.endsWith("s")) {
|
|
445
|
+
return lower.slice(0, -1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Return as-is if no pluralization detected
|
|
449
|
+
return lower;
|
|
450
|
+
}
|
|
451
|
+
|
|
337
452
|
/**
|
|
338
453
|
* Build WHERE clause for upsert operations
|
|
339
454
|
*/
|
|
340
455
|
buildWhereClause(record) {
|
|
341
456
|
const whereClause = {};
|
|
342
|
-
|
|
457
|
+
|
|
343
458
|
// Use the first unique field as the primary identifier
|
|
344
459
|
const primaryField = this.options.uniqueFields[0];
|
|
345
460
|
if (record[primaryField]) {
|
|
@@ -354,8 +469,8 @@ export class ContentSeeder {
|
|
|
354
469
|
*/
|
|
355
470
|
buildSelectClause() {
|
|
356
471
|
const selectClause = { id: true };
|
|
357
|
-
|
|
358
|
-
this.options.uniqueFields.forEach(field => {
|
|
472
|
+
|
|
473
|
+
this.options.uniqueFields.forEach((field) => {
|
|
359
474
|
selectClause[field] = true;
|
|
360
475
|
});
|
|
361
476
|
|
|
@@ -370,7 +485,7 @@ export class ContentSeeder {
|
|
|
370
485
|
this.memoryStats.startHeapUsed = memUsage.heapUsed;
|
|
371
486
|
this.memoryStats.currentHeapUsed = memUsage.heapUsed;
|
|
372
487
|
this.memoryStats.maxHeapUsed = memUsage.heapUsed;
|
|
373
|
-
|
|
488
|
+
|
|
374
489
|
if (this.options.logProgress) {
|
|
375
490
|
MemoryManager.logMemoryUsage(`${this.options.dbName} seeding start`);
|
|
376
491
|
}
|
|
@@ -382,20 +497,26 @@ export class ContentSeeder {
|
|
|
382
497
|
checkMemoryUsage(processedCount, entityName) {
|
|
383
498
|
const memUsage = process.memoryUsage();
|
|
384
499
|
this.memoryStats.currentHeapUsed = memUsage.heapUsed;
|
|
385
|
-
this.memoryStats.maxHeapUsed = Math.max(
|
|
500
|
+
this.memoryStats.maxHeapUsed = Math.max(
|
|
501
|
+
this.memoryStats.maxHeapUsed,
|
|
502
|
+
memUsage.heapUsed
|
|
503
|
+
);
|
|
386
504
|
|
|
387
505
|
// Check every N records based on interval
|
|
388
506
|
if (processedCount % this.options.memoryCheckInterval === 0) {
|
|
389
|
-
const memoryThresholdBytes =
|
|
390
|
-
|
|
507
|
+
const memoryThresholdBytes =
|
|
508
|
+
this.options.memoryThreshold * this.memoryStats.startHeapUsed * 10; // Allow 10x growth
|
|
509
|
+
|
|
391
510
|
if (memUsage.heapUsed > memoryThresholdBytes) {
|
|
392
511
|
if (this.options.logProgress) {
|
|
393
|
-
console.log(
|
|
512
|
+
console.log(
|
|
513
|
+
colors.yellow(`Memory threshold exceeded, triggering GC...`)
|
|
514
|
+
);
|
|
394
515
|
}
|
|
395
|
-
|
|
516
|
+
|
|
396
517
|
MemoryManager.forceGarbageCollectionWithDelay();
|
|
397
518
|
this.memoryStats.gcTriggered++;
|
|
398
|
-
|
|
519
|
+
|
|
399
520
|
if (this.options.logProgress) {
|
|
400
521
|
MemoryManager.logMemoryUsage(`After GC (${entityName})`);
|
|
401
522
|
}
|
|
@@ -408,12 +529,20 @@ export class ContentSeeder {
|
|
|
408
529
|
*/
|
|
409
530
|
finalMemoryCleanup() {
|
|
410
531
|
MemoryManager.forceGarbageCollectionWithDelay();
|
|
411
|
-
|
|
532
|
+
|
|
412
533
|
if (this.options.logProgress) {
|
|
413
|
-
MemoryManager.logMemoryUsage(
|
|
414
|
-
|
|
415
|
-
const heapGrowth = (
|
|
416
|
-
|
|
534
|
+
MemoryManager.logMemoryUsage("Seeding complete");
|
|
535
|
+
|
|
536
|
+
const heapGrowth = (
|
|
537
|
+
(this.memoryStats.maxHeapUsed - this.memoryStats.startHeapUsed) /
|
|
538
|
+
1024 /
|
|
539
|
+
1024
|
|
540
|
+
).toFixed(2);
|
|
541
|
+
console.log(
|
|
542
|
+
colors.gray(
|
|
543
|
+
`Memory growth: ${heapGrowth}MB, GC triggered: ${this.memoryStats.gcTriggered} times`
|
|
544
|
+
)
|
|
545
|
+
);
|
|
417
546
|
}
|
|
418
547
|
}
|
|
419
548
|
|
|
@@ -422,11 +551,11 @@ export class ContentSeeder {
|
|
|
422
551
|
*/
|
|
423
552
|
reportProgress(currentIndex, totalChunks, processed, created, total) {
|
|
424
553
|
const progress = Math.min((currentIndex / totalChunks) * 100, 100);
|
|
425
|
-
|
|
554
|
+
|
|
426
555
|
process.stdout.write(
|
|
427
|
-
`\r${colors.green(
|
|
428
|
-
|
|
429
|
-
|
|
556
|
+
`\r${colors.green("▸")} Progress: ${progress.toFixed(1)}% | ` +
|
|
557
|
+
`Processed: ${processed.toLocaleString()}/${total.toLocaleString()} | ` +
|
|
558
|
+
`Created: ${created.toLocaleString()}`
|
|
430
559
|
);
|
|
431
560
|
}
|
|
432
561
|
|
|
@@ -445,7 +574,7 @@ export class ContentSeeder {
|
|
|
445
574
|
* Sleep utility
|
|
446
575
|
*/
|
|
447
576
|
sleep(ms) {
|
|
448
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
577
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
449
578
|
}
|
|
450
579
|
}
|
|
451
580
|
|
|
@@ -453,20 +582,25 @@ export class ContentSeeder {
|
|
|
453
582
|
* Factory function to create ContentSeeder with performance config
|
|
454
583
|
*/
|
|
455
584
|
export async function createContentSeeder(entityName, options = {}) {
|
|
456
|
-
const { createPerformanceConfig } = await import(
|
|
585
|
+
const { createPerformanceConfig } = await import("./performance-config.js");
|
|
457
586
|
const performanceConfig = createPerformanceConfig();
|
|
458
|
-
const config = performanceConfig.getConfig(
|
|
459
|
-
|
|
587
|
+
const config = performanceConfig.getConfig("content", entityName);
|
|
588
|
+
|
|
460
589
|
return new ContentSeeder({
|
|
461
590
|
...config,
|
|
462
|
-
...options
|
|
591
|
+
...options,
|
|
463
592
|
});
|
|
464
593
|
}
|
|
465
594
|
|
|
466
595
|
/**
|
|
467
596
|
* Convenience function for content seeding with optimized defaults
|
|
468
597
|
*/
|
|
469
|
-
export async function seedContentData(
|
|
598
|
+
export async function seedContentData(
|
|
599
|
+
entityName,
|
|
600
|
+
data,
|
|
601
|
+
processor = null,
|
|
602
|
+
options = {}
|
|
603
|
+
) {
|
|
470
604
|
const seeder = await createContentSeeder(entityName, options);
|
|
471
605
|
return await seeder.seedContent(entityName, data, processor);
|
|
472
606
|
}
|