sonamu 0.2.48 → 0.2.49
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/dist/bin/cli-wrapper.js +12 -2
- package/dist/bin/cli-wrapper.js.map +1 -1
- package/dist/bin/cli.js +54 -62
- package/dist/bin/cli.js.map +1 -1
- package/dist/{chunk-76VBQWGE.js → chunk-HATLA54Z.js} +167 -97
- package/dist/chunk-HATLA54Z.js.map +1 -0
- package/dist/index.d.ts +15 -7
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/api/sonamu.ts +3 -2
- package/src/bin/cli-wrapper.ts +20 -1
- package/src/bin/cli.ts +5 -15
- package/src/database/_batch_update.ts +29 -14
- package/src/database/upsert-builder.ts +56 -42
- package/src/testing/fixture-manager.ts +122 -36
- package/src/types/types.ts +3 -2
- package/src/utils/utils.ts +15 -13
- package/dist/chunk-76VBQWGE.js.map +0 -1
|
@@ -158,6 +158,10 @@ export class FixtureManagerClass {
|
|
|
158
158
|
await transaction(tableName).truncate();
|
|
159
159
|
|
|
160
160
|
const rows = await frdb(tableName);
|
|
161
|
+
if (rows.length === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
console.log(chalk.blue(tableName), rows.length);
|
|
162
166
|
await transaction
|
|
163
167
|
.insert(
|
|
@@ -307,58 +311,72 @@ export class FixtureManagerClass {
|
|
|
307
311
|
throw new Error("No records found");
|
|
308
312
|
}
|
|
309
313
|
|
|
310
|
-
const
|
|
311
|
-
const records: FixtureRecord[] = [];
|
|
314
|
+
const fixtures: FixtureRecord[] = [];
|
|
312
315
|
for (const row of rows) {
|
|
313
|
-
const initialRecordsLength =
|
|
314
|
-
await this.createFixtureRecord(entity, row
|
|
315
|
-
|
|
316
|
+
const initialRecordsLength = fixtures.length;
|
|
317
|
+
const newRecords = await this.createFixtureRecord(entity, row);
|
|
318
|
+
fixtures.push(...newRecords);
|
|
319
|
+
const currentFixtureRecord = fixtures.find(
|
|
316
320
|
(r) => r.fixtureId === `${entityId}#${row.id}`
|
|
317
321
|
);
|
|
318
322
|
|
|
319
323
|
if (currentFixtureRecord) {
|
|
320
324
|
// 현재 fixture로부터 생성된 fetchedRecords 설정
|
|
321
|
-
currentFixtureRecord.fetchedRecords =
|
|
325
|
+
currentFixtureRecord.fetchedRecords = fixtures
|
|
322
326
|
.filter((r) => r.fixtureId !== currentFixtureRecord.fixtureId)
|
|
323
327
|
.slice(initialRecordsLength)
|
|
324
328
|
.map((r) => r.fixtureId);
|
|
325
329
|
}
|
|
326
330
|
}
|
|
327
331
|
|
|
328
|
-
for await (const
|
|
329
|
-
const entity = EntityManager.get(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
+
for await (const fixture of fixtures) {
|
|
333
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
334
|
+
|
|
335
|
+
// targetDB에 해당 레코드가 존재하는지 확인
|
|
336
|
+
const row = await targetDB(entity.table).where("id", fixture.id).first();
|
|
332
337
|
if (row) {
|
|
333
|
-
await this.createFixtureRecord(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
const [record] = await this.createFixtureRecord(entity, row, {
|
|
339
|
+
singleRecord: true,
|
|
340
|
+
_db: targetDB,
|
|
341
|
+
});
|
|
342
|
+
fixture.target = record;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// targetDB에 해당 레코드가 존재하지 않는 경우, unique 제약을 위반하는지 확인
|
|
347
|
+
const uniqueRow = await this.checkUniqueViolation(
|
|
348
|
+
targetDB,
|
|
349
|
+
entity,
|
|
350
|
+
fixture
|
|
351
|
+
);
|
|
352
|
+
if (uniqueRow) {
|
|
353
|
+
const [record] = await this.createFixtureRecord(entity, uniqueRow, {
|
|
354
|
+
singleRecord: true,
|
|
355
|
+
_db: targetDB,
|
|
356
|
+
});
|
|
357
|
+
fixture.unique = record;
|
|
342
358
|
}
|
|
343
359
|
}
|
|
344
360
|
|
|
345
|
-
return
|
|
361
|
+
return fixtures;
|
|
346
362
|
}
|
|
347
363
|
|
|
348
364
|
async createFixtureRecord(
|
|
349
365
|
entity: Entity,
|
|
350
366
|
row: any,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
367
|
+
options?: {
|
|
368
|
+
singleRecord?: boolean;
|
|
369
|
+
_db?: Knex;
|
|
370
|
+
},
|
|
371
|
+
visitedEntities = new Set<string>()
|
|
372
|
+
): Promise<FixtureRecord[]> {
|
|
356
373
|
const fixtureId = `${entity.id}#${row.id}`;
|
|
357
374
|
if (visitedEntities.has(fixtureId)) {
|
|
358
|
-
return;
|
|
375
|
+
return [];
|
|
359
376
|
}
|
|
360
377
|
visitedEntities.add(fixtureId);
|
|
361
378
|
|
|
379
|
+
const records: FixtureRecord[] = [];
|
|
362
380
|
const record: FixtureRecord = {
|
|
363
381
|
fixtureId,
|
|
364
382
|
entityId: entity.id,
|
|
@@ -378,7 +396,7 @@ export class FixtureManagerClass {
|
|
|
378
396
|
value: row[prop.name],
|
|
379
397
|
};
|
|
380
398
|
|
|
381
|
-
const db = _db ?? BaseModel.getDB("w");
|
|
399
|
+
const db = options?._db ?? BaseModel.getDB("w");
|
|
382
400
|
if (isManyToManyRelationProp(prop)) {
|
|
383
401
|
const relatedEntity = EntityManager.get(prop.with);
|
|
384
402
|
const throughTable = prop.joinTable;
|
|
@@ -412,32 +430,34 @@ export class FixtureManagerClass {
|
|
|
412
430
|
if (relatedId) {
|
|
413
431
|
record.belongsRecords.push(`${prop.with}#${relatedId}`);
|
|
414
432
|
}
|
|
415
|
-
if (!singleRecord && relatedId) {
|
|
433
|
+
if (!options?.singleRecord && relatedId) {
|
|
416
434
|
const relatedEntity = EntityManager.get(prop.with);
|
|
417
435
|
const relatedRow = await db(relatedEntity.table)
|
|
418
436
|
.where("id", relatedId)
|
|
419
437
|
.first();
|
|
420
438
|
if (relatedRow) {
|
|
421
|
-
await this.createFixtureRecord(
|
|
439
|
+
const newRecords = await this.createFixtureRecord(
|
|
422
440
|
relatedEntity,
|
|
423
441
|
relatedRow,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
singleRecord,
|
|
427
|
-
_db
|
|
442
|
+
options,
|
|
443
|
+
visitedEntities
|
|
428
444
|
);
|
|
445
|
+
records.push(...newRecords);
|
|
429
446
|
}
|
|
430
447
|
}
|
|
431
448
|
}
|
|
432
449
|
}
|
|
433
450
|
|
|
434
451
|
records.push(record);
|
|
452
|
+
return records;
|
|
435
453
|
}
|
|
436
454
|
|
|
437
455
|
async insertFixtures(
|
|
438
456
|
dbName: keyof SonamuDBConfig,
|
|
439
|
-
|
|
457
|
+
_fixtures: FixtureRecord[]
|
|
440
458
|
) {
|
|
459
|
+
const fixtures = _.uniqBy(_fixtures, (f) => f.fixtureId);
|
|
460
|
+
|
|
441
461
|
this.buildDependencyGraph(fixtures);
|
|
442
462
|
const insertionOrder = this.getInsertionOrder();
|
|
443
463
|
const db = knex(Sonamu.dbConfig[dbName]);
|
|
@@ -447,7 +467,27 @@ export class FixtureManagerClass {
|
|
|
447
467
|
|
|
448
468
|
for (const fixtureId of insertionOrder) {
|
|
449
469
|
const fixture = fixtures.find((f) => f.fixtureId === fixtureId)!;
|
|
450
|
-
await this.insertFixture(trx, fixture);
|
|
470
|
+
const result = await this.insertFixture(trx, fixture);
|
|
471
|
+
if (result.id !== fixture.id) {
|
|
472
|
+
// ID가 변경된 경우, 다른 fixture에서 참조하는 경우가 찾아서 수정
|
|
473
|
+
console.log(
|
|
474
|
+
chalk.yellow(
|
|
475
|
+
`Unique constraint violation: ${fixture.entityId}#${fixture.id} -> ${fixture.entityId}#${result.id}`
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
fixtures.forEach((f) => {
|
|
479
|
+
Object.values(f.columns).forEach((column) => {
|
|
480
|
+
if (
|
|
481
|
+
column.prop.type === "relation" &&
|
|
482
|
+
column.prop.with === result.entityId &&
|
|
483
|
+
column.value === fixture.id
|
|
484
|
+
) {
|
|
485
|
+
column.value = result.id;
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
fixture.id = result.id;
|
|
490
|
+
}
|
|
451
491
|
}
|
|
452
492
|
|
|
453
493
|
for (const fixtureId of insertionOrder) {
|
|
@@ -468,7 +508,7 @@ export class FixtureManagerClass {
|
|
|
468
508
|
});
|
|
469
509
|
}
|
|
470
510
|
|
|
471
|
-
return records;
|
|
511
|
+
return _.uniqBy(records, (r) => `${r.entityId}#${r.data.id}`);
|
|
472
512
|
}
|
|
473
513
|
|
|
474
514
|
private getInsertionOrder() {
|
|
@@ -598,6 +638,14 @@ export class FixtureManagerClass {
|
|
|
598
638
|
const entity = EntityManager.get(fixture.entityId);
|
|
599
639
|
|
|
600
640
|
try {
|
|
641
|
+
const uniqueFound = await this.checkUniqueViolation(db, entity, fixture);
|
|
642
|
+
if (uniqueFound) {
|
|
643
|
+
return {
|
|
644
|
+
entityId: fixture.entityId,
|
|
645
|
+
id: uniqueFound.id,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
601
649
|
const found = await db(entity.table).where("id", fixture.id).first();
|
|
602
650
|
if (found && !fixture.override) {
|
|
603
651
|
return {
|
|
@@ -688,5 +736,43 @@ export class FixtureManagerClass {
|
|
|
688
736
|
throw new Error("Failed to find fixtureLoader in fixture.ts");
|
|
689
737
|
}
|
|
690
738
|
}
|
|
739
|
+
|
|
740
|
+
// 해당 픽스쳐의 값으로 유니크 제약에 위배되는 레코드가 있는지 확인
|
|
741
|
+
private async checkUniqueViolation(
|
|
742
|
+
db: Knex,
|
|
743
|
+
entity: Entity,
|
|
744
|
+
fixture: FixtureRecord
|
|
745
|
+
) {
|
|
746
|
+
const uniqueIndexes = entity.indexes.filter((i) => i.type === "unique");
|
|
747
|
+
if (uniqueIndexes.length === 0) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
let uniqueQuery = db(entity.table);
|
|
752
|
+
for (const index of uniqueIndexes) {
|
|
753
|
+
// 컬럼 중 하나라도 null이면 유니크 제약을 위반하지 않기 때문에 해당 인덱스는 무시
|
|
754
|
+
if (
|
|
755
|
+
index.columns.some(
|
|
756
|
+
(column) => fixture.columns[column.split("_id")[0]].value === null
|
|
757
|
+
)
|
|
758
|
+
) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
uniqueQuery = uniqueQuery.orWhere((qb) => {
|
|
763
|
+
for (const column of index.columns) {
|
|
764
|
+
const field = column.split("_id")[0];
|
|
765
|
+
|
|
766
|
+
if (Array.isArray(fixture.columns[field].value)) {
|
|
767
|
+
qb.whereIn(column, fixture.columns[field].value);
|
|
768
|
+
} else {
|
|
769
|
+
qb.andWhere(column, fixture.columns[field].value);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
const [uniqueFound] = await uniqueQuery;
|
|
775
|
+
return uniqueFound;
|
|
776
|
+
}
|
|
691
777
|
}
|
|
692
778
|
export const FixtureManager = new FixtureManagerClass();
|
package/src/types/types.ts
CHANGED
|
@@ -738,8 +738,9 @@ export type FixtureRecord = {
|
|
|
738
738
|
};
|
|
739
739
|
};
|
|
740
740
|
fetchedRecords: string[];
|
|
741
|
-
belongsRecords: string[];
|
|
742
|
-
target?: FixtureRecord; // Import 대상 DB 레코드
|
|
741
|
+
belongsRecords: string[];
|
|
742
|
+
target?: FixtureRecord; // Import 대상 DB 레코드(id가 같은)
|
|
743
|
+
unique?: FixtureRecord; // Import 대상 DB 레코드(unique key가 같은)
|
|
743
744
|
override?: boolean;
|
|
744
745
|
};
|
|
745
746
|
|
package/src/utils/utils.ts
CHANGED
|
@@ -17,19 +17,21 @@ export async function importMultiple(
|
|
|
17
17
|
filePaths: string[],
|
|
18
18
|
doRefresh: boolean = false
|
|
19
19
|
): Promise<{ filePath: string; imported: any }[]> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
32
|
-
|
|
20
|
+
const results: { filePath: string; imported: any }[] = [];
|
|
21
|
+
|
|
22
|
+
for (const filePath of filePaths) {
|
|
23
|
+
const importPath = "./" + path.relative(__dirname, filePath);
|
|
24
|
+
if (doRefresh) {
|
|
25
|
+
delete require.cache[require.resolve(importPath)];
|
|
26
|
+
}
|
|
27
|
+
const imported = await import(importPath);
|
|
28
|
+
results.push({
|
|
29
|
+
filePath,
|
|
30
|
+
imported,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return results;
|
|
33
35
|
}
|
|
34
36
|
export async function findAppRootPath() {
|
|
35
37
|
const apiRootPath = await findApiRootPath();
|