sonamu 0.2.47 → 0.2.48
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.js +48 -48
- package/dist/{chunk-5VT5JTY4.js → chunk-76VBQWGE.js} +316 -1
- package/dist/chunk-76VBQWGE.js.map +1 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/api/sonamu.ts +17 -0
- package/src/testing/fixture-manager.ts +431 -0
- package/src/types/types.ts +30 -0
- package/dist/chunk-5VT5JTY4.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -742,6 +742,33 @@ declare const PathAndCode: z.ZodObject<{
|
|
|
742
742
|
code: string;
|
|
743
743
|
}>;
|
|
744
744
|
type PathAndCode = z.infer<typeof PathAndCode>;
|
|
745
|
+
type FixtureSearchOptions = {
|
|
746
|
+
entityId: string;
|
|
747
|
+
field: string;
|
|
748
|
+
value: string;
|
|
749
|
+
searchType: "equals" | "like";
|
|
750
|
+
};
|
|
751
|
+
type FixtureRecord = {
|
|
752
|
+
fixtureId: string;
|
|
753
|
+
entityId: string;
|
|
754
|
+
id: number;
|
|
755
|
+
columns: {
|
|
756
|
+
[key: string]: {
|
|
757
|
+
prop: EntityProp;
|
|
758
|
+
value: any;
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
fetchedRecords: string[];
|
|
762
|
+
belongsRecords: string[];
|
|
763
|
+
target?: FixtureRecord;
|
|
764
|
+
override?: boolean;
|
|
765
|
+
};
|
|
766
|
+
type FixtureImportResult = {
|
|
767
|
+
entityId: string;
|
|
768
|
+
data: {
|
|
769
|
+
[key: string]: any;
|
|
770
|
+
};
|
|
771
|
+
};
|
|
745
772
|
|
|
746
773
|
type ServiceClient = "axios" | "axios-multipart" | "swr" | "socketio" | "window-fetch";
|
|
747
774
|
type ApiDecoratorOptions = {
|
|
@@ -1026,6 +1053,9 @@ type SonamuConfig = {
|
|
|
1026
1053
|
prefix: string;
|
|
1027
1054
|
};
|
|
1028
1055
|
};
|
|
1056
|
+
type SonamuSecrets = {
|
|
1057
|
+
[key: string]: string;
|
|
1058
|
+
};
|
|
1029
1059
|
type SonamuFastifyConfig = {
|
|
1030
1060
|
contextProvider: (defaultContext: Pick<Context, "headers" | "reply">, request: FastifyRequest, reply: FastifyReply) => Context;
|
|
1031
1061
|
guardHandler: (guard: string, request: FastifyRequest, api: {
|
|
@@ -1066,6 +1096,9 @@ declare class SonamuClass {
|
|
|
1066
1096
|
private _config;
|
|
1067
1097
|
set config(config: SonamuConfig);
|
|
1068
1098
|
get config(): SonamuConfig;
|
|
1099
|
+
private _secrets;
|
|
1100
|
+
set secrets(secrets: SonamuSecrets);
|
|
1101
|
+
get secrets(): SonamuSecrets | null;
|
|
1069
1102
|
init(doSilent?: boolean, enableSync?: boolean, apiRootPath?: string): Promise<void>;
|
|
1070
1103
|
withFastify(server: FastifyInstance<Server, IncomingMessage, ServerResponse>, config: SonamuFastifyConfig): Promise<void>;
|
|
1071
1104
|
destroy(): Promise<void>;
|
|
@@ -1410,6 +1443,7 @@ declare class FixtureManagerClass {
|
|
|
1410
1443
|
private _fdb;
|
|
1411
1444
|
set fdb(fdb: Knex);
|
|
1412
1445
|
get fdb(): Knex;
|
|
1446
|
+
private dependencyGraph;
|
|
1413
1447
|
init(): void;
|
|
1414
1448
|
cleanAndSeed(usingTables?: string[]): Promise<void>;
|
|
1415
1449
|
getChecksum(db: Knex, tableName: string): Promise<any>;
|
|
@@ -1417,6 +1451,15 @@ declare class FixtureManagerClass {
|
|
|
1417
1451
|
importFixture(entityId: string, ids: number[]): Promise<void>;
|
|
1418
1452
|
getImportQueries(entityId: string, field: string, id: number): Promise<string[]>;
|
|
1419
1453
|
destory(): Promise<void>;
|
|
1454
|
+
getFixtures(sourceDBName: keyof SonamuDBConfig, targetDBName: keyof SonamuDBConfig, searchOptions: FixtureSearchOptions): Promise<FixtureRecord[]>;
|
|
1455
|
+
createFixtureRecord(entity: Entity, row: any, visitedEntities: Set<string>, records: FixtureRecord[], singleRecord?: boolean, _db?: Knex): Promise<void>;
|
|
1456
|
+
insertFixtures(dbName: keyof SonamuDBConfig, fixtures: FixtureRecord[]): Promise<FixtureImportResult[]>;
|
|
1457
|
+
private getInsertionOrder;
|
|
1458
|
+
private prepareInsertData;
|
|
1459
|
+
private buildDependencyGraph;
|
|
1460
|
+
private insertFixture;
|
|
1461
|
+
private handleManyToManyRelations;
|
|
1462
|
+
addFixtureLoader(code: string): Promise<void>;
|
|
1420
1463
|
}
|
|
1421
1464
|
declare const FixtureManager: FixtureManagerClass;
|
|
1422
1465
|
|
|
@@ -1438,4 +1481,4 @@ declare function findAppRootPath(): Promise<string>;
|
|
|
1438
1481
|
declare function findApiRootPath(): Promise<string>;
|
|
1439
1482
|
declare function nonNullable<T>(value: T): value is NonNullable<T>;
|
|
1440
1483
|
|
|
1441
|
-
export { AlreadyProcessedException, type ApiDecoratorOptions, type ApiParam, ApiParamType, type ArrayOr, BadRequestException, type BaseListParams, BaseModel, BaseModelClass, type BelongsToOneRelationProp, type BigIntegerProp, type BooleanProp, type CommonProp, type Context, type ContextExtend, DB, type DBPreset, type DateProp, type DateTimeProp, type DecimalProp, type DistributiveOmit, type DoubleProp, DuplicateRowException, Entity, type EntityIndex, type EntityJson, EntityManager, type EntityNamesRecord, type EntityProp, type EntityPropNode, type EntitySubsetRow, type EnumProp, type EnumsLabel, type EnumsLabelKo, type ExtendedApi, FixtureManager, FixtureManagerClass, type FlattenSubsetRow, type FloatProp, type GenMigrationCode, GenerateOptions, type HasManyRelationProp, type IntegerProp, InternalServerErrorException, type JsonProp, type KnexColumnType, type KnexError, type ListResult, type ManyToManyRelationProp, type MigrationColumn, type MigrationForeign, type MigrationIndex, type MigrationJoinTable, type MigrationSet, type MigrationSetAndJoinTable, type MigrationStatus, Migrator, type MigratorOptions, NotFoundException, type OneToOneRelationProp, PathAndCode, type RelationOn, type RelationProp, type RelationType, type RenderedTemplate, RenderingNode, type SMDInput, SQLDateTimeString, type ServiceClient, ServiceUnavailableException, SoException, Sonamu, type SonamuConfig, type SonamuDBConfig, SonamuQueryMode, type StringProp, type SubsetQuery, Syncer, TargetNotFoundException, TemplateKey, TemplateOptions, type TextProp, type TimeProp, type TimestampProp, type UBRef, UnauthorizedException, UpsertBuilder, type UuidProp, type VirtualProp, api, apiParamToTsCode, apiParamTypeToTsType, asArray, findApiRootPath, findAppRootPath, getTextTypeLength, getZodObjectFromApi, getZodObjectFromApiParams, getZodTypeFromApiParamType, globAsync, i, importMultiple, isBelongsToOneRelationProp, isBigIntegerProp, isBooleanProp, isCustomJoinClause, isDaemonServer, isDateProp, isDateTimeProp, isDecimalProp, isDevelopment, isDoubleProp, isEnumProp, isFloatProp, isHasManyRelationProp, isInDocker, isIntegerProp, isJsonProp, isKnexError, isLocal, isManyToManyRelationProp, isOneToOneRelationProp, isProduction, isRefField, isRelationProp, isRemote, isSoException, isStaging, isStringProp, isTest, isTextProp, isTimeProp, isTimestampProp, isUuidProp, isVirtualProp, nonNullable, objToMap, p, propNodeToZodTypeDef, propToZodTypeDef, registeredApis, serializeZodType, setupErrorHandler, unwrapPromiseOnce, zArrayable, zodTypeToTsTypeDef, zodTypeToZodCode };
|
|
1484
|
+
export { AlreadyProcessedException, type ApiDecoratorOptions, type ApiParam, ApiParamType, type ArrayOr, BadRequestException, type BaseListParams, BaseModel, BaseModelClass, type BelongsToOneRelationProp, type BigIntegerProp, type BooleanProp, type CommonProp, type Context, type ContextExtend, DB, type DBPreset, type DateProp, type DateTimeProp, type DecimalProp, type DistributiveOmit, type DoubleProp, DuplicateRowException, Entity, type EntityIndex, type EntityJson, EntityManager, type EntityNamesRecord, type EntityProp, type EntityPropNode, type EntitySubsetRow, type EnumProp, type EnumsLabel, type EnumsLabelKo, type ExtendedApi, type FixtureImportResult, FixtureManager, FixtureManagerClass, type FixtureRecord, type FixtureSearchOptions, type FlattenSubsetRow, type FloatProp, type GenMigrationCode, GenerateOptions, type HasManyRelationProp, type IntegerProp, InternalServerErrorException, type JsonProp, type KnexColumnType, type KnexError, type ListResult, type ManyToManyRelationProp, type MigrationColumn, type MigrationForeign, type MigrationIndex, type MigrationJoinTable, type MigrationSet, type MigrationSetAndJoinTable, type MigrationStatus, Migrator, type MigratorOptions, NotFoundException, type OneToOneRelationProp, PathAndCode, type RelationOn, type RelationProp, type RelationType, type RenderedTemplate, RenderingNode, type SMDInput, SQLDateTimeString, type ServiceClient, ServiceUnavailableException, SoException, Sonamu, type SonamuConfig, type SonamuDBConfig, SonamuQueryMode, type SonamuSecrets, type StringProp, type SubsetQuery, Syncer, TargetNotFoundException, TemplateKey, TemplateOptions, type TextProp, type TimeProp, type TimestampProp, type UBRef, UnauthorizedException, UpsertBuilder, type UuidProp, type VirtualProp, api, apiParamToTsCode, apiParamTypeToTsType, asArray, findApiRootPath, findAppRootPath, getTextTypeLength, getZodObjectFromApi, getZodObjectFromApiParams, getZodTypeFromApiParamType, globAsync, i, importMultiple, isBelongsToOneRelationProp, isBigIntegerProp, isBooleanProp, isCustomJoinClause, isDaemonServer, isDateProp, isDateTimeProp, isDecimalProp, isDevelopment, isDoubleProp, isEnumProp, isFloatProp, isHasManyRelationProp, isInDocker, isIntegerProp, isJsonProp, isKnexError, isLocal, isManyToManyRelationProp, isOneToOneRelationProp, isProduction, isRefField, isRelationProp, isRemote, isSoException, isStaging, isStringProp, isTest, isTextProp, isTimeProp, isTimestampProp, isUuidProp, isVirtualProp, nonNullable, objToMap, p, propNodeToZodTypeDef, propToZodTypeDef, registeredApis, serializeZodType, setupErrorHandler, unwrapPromiseOnce, zArrayable, zodTypeToTsTypeDef, zodTypeToZodCode };
|
package/dist/index.js
CHANGED
|
@@ -80,13 +80,13 @@
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
var
|
|
83
|
+
var _chunk76VBQWGEjs = require('./chunk-76VBQWGE.js');
|
|
84
84
|
|
|
85
85
|
// src/exceptions/error-handler.ts
|
|
86
86
|
function setupErrorHandler(server) {
|
|
87
87
|
server.setErrorHandler((error, request, reply) => {
|
|
88
88
|
_nullishCoalesce(error.statusCode, () => ( (error.statusCode = 400)));
|
|
89
|
-
if (
|
|
89
|
+
if (_chunk76VBQWGEjs.isSoException.call(void 0, error) && error.payload && Array.isArray(error.payload)) {
|
|
90
90
|
const issues = error.payload;
|
|
91
91
|
const [issue] = issues;
|
|
92
92
|
const message = `${issue.message} (${issue.path.join("/")})`;
|
|
@@ -423,5 +423,5 @@ function unique(columns) {
|
|
|
423
423
|
|
|
424
424
|
|
|
425
425
|
|
|
426
|
-
exports.AlreadyProcessedException =
|
|
426
|
+
exports.AlreadyProcessedException = _chunk76VBQWGEjs.AlreadyProcessedException; exports.ApiParamType = _chunk76VBQWGEjs.ApiParamType; exports.BadRequestException = _chunk76VBQWGEjs.BadRequestException; exports.BaseModel = _chunk76VBQWGEjs.BaseModel; exports.BaseModelClass = _chunk76VBQWGEjs.BaseModelClass; exports.DB = _chunk76VBQWGEjs.DB; exports.DuplicateRowException = _chunk76VBQWGEjs.DuplicateRowException; exports.Entity = _chunk76VBQWGEjs.Entity; exports.EntityManager = _chunk76VBQWGEjs.EntityManager; exports.FixtureManager = _chunk76VBQWGEjs.FixtureManager; exports.FixtureManagerClass = _chunk76VBQWGEjs.FixtureManagerClass; exports.GenerateOptions = _chunk76VBQWGEjs.GenerateOptions; exports.InternalServerErrorException = _chunk76VBQWGEjs.InternalServerErrorException; exports.Migrator = _chunk76VBQWGEjs.Migrator; exports.NotFoundException = _chunk76VBQWGEjs.NotFoundException; exports.PathAndCode = _chunk76VBQWGEjs.PathAndCode; exports.RenderingNode = _chunk76VBQWGEjs.RenderingNode; exports.SQLDateTimeString = _chunk76VBQWGEjs.SQLDateTimeString; exports.ServiceUnavailableException = _chunk76VBQWGEjs.ServiceUnavailableException; exports.SoException = _chunk76VBQWGEjs.SoException; exports.Sonamu = _chunk76VBQWGEjs.Sonamu; exports.SonamuQueryMode = _chunk76VBQWGEjs.SonamuQueryMode; exports.Syncer = _chunk76VBQWGEjs.Syncer; exports.TargetNotFoundException = _chunk76VBQWGEjs.TargetNotFoundException; exports.TemplateKey = _chunk76VBQWGEjs.TemplateKey; exports.TemplateOptions = _chunk76VBQWGEjs.TemplateOptions; exports.UnauthorizedException = _chunk76VBQWGEjs.UnauthorizedException; exports.UpsertBuilder = _chunk76VBQWGEjs.UpsertBuilder; exports.api = _chunk76VBQWGEjs.api; exports.apiParamToTsCode = _chunk76VBQWGEjs.apiParamToTsCode; exports.apiParamTypeToTsType = _chunk76VBQWGEjs.apiParamTypeToTsType; exports.asArray = asArray; exports.findApiRootPath = _chunk76VBQWGEjs.findApiRootPath; exports.findAppRootPath = _chunk76VBQWGEjs.findAppRootPath; exports.getTextTypeLength = _chunk76VBQWGEjs.getTextTypeLength; exports.getZodObjectFromApi = _chunk76VBQWGEjs.getZodObjectFromApi; exports.getZodObjectFromApiParams = _chunk76VBQWGEjs.getZodObjectFromApiParams; exports.getZodTypeFromApiParamType = _chunk76VBQWGEjs.getZodTypeFromApiParamType; exports.globAsync = _chunk76VBQWGEjs.globAsync; exports.i = i; exports.importMultiple = _chunk76VBQWGEjs.importMultiple; exports.isBelongsToOneRelationProp = _chunk76VBQWGEjs.isBelongsToOneRelationProp; exports.isBigIntegerProp = _chunk76VBQWGEjs.isBigIntegerProp; exports.isBooleanProp = _chunk76VBQWGEjs.isBooleanProp; exports.isCustomJoinClause = _chunk76VBQWGEjs.isCustomJoinClause; exports.isDaemonServer = _chunk76VBQWGEjs.isDaemonServer; exports.isDateProp = _chunk76VBQWGEjs.isDateProp; exports.isDateTimeProp = _chunk76VBQWGEjs.isDateTimeProp; exports.isDecimalProp = _chunk76VBQWGEjs.isDecimalProp; exports.isDevelopment = _chunk76VBQWGEjs.isDevelopment; exports.isDoubleProp = _chunk76VBQWGEjs.isDoubleProp; exports.isEnumProp = _chunk76VBQWGEjs.isEnumProp; exports.isFloatProp = _chunk76VBQWGEjs.isFloatProp; exports.isHasManyRelationProp = _chunk76VBQWGEjs.isHasManyRelationProp; exports.isInDocker = _chunk76VBQWGEjs.isInDocker; exports.isIntegerProp = _chunk76VBQWGEjs.isIntegerProp; exports.isJsonProp = _chunk76VBQWGEjs.isJsonProp; exports.isKnexError = _chunk76VBQWGEjs.isKnexError; exports.isLocal = _chunk76VBQWGEjs.isLocal; exports.isManyToManyRelationProp = _chunk76VBQWGEjs.isManyToManyRelationProp; exports.isOneToOneRelationProp = _chunk76VBQWGEjs.isOneToOneRelationProp; exports.isProduction = _chunk76VBQWGEjs.isProduction; exports.isRefField = _chunk76VBQWGEjs.isRefField; exports.isRelationProp = _chunk76VBQWGEjs.isRelationProp; exports.isRemote = _chunk76VBQWGEjs.isRemote; exports.isSoException = _chunk76VBQWGEjs.isSoException; exports.isStaging = _chunk76VBQWGEjs.isStaging; exports.isStringProp = _chunk76VBQWGEjs.isStringProp; exports.isTest = _chunk76VBQWGEjs.isTest; exports.isTextProp = _chunk76VBQWGEjs.isTextProp; exports.isTimeProp = _chunk76VBQWGEjs.isTimeProp; exports.isTimestampProp = _chunk76VBQWGEjs.isTimestampProp; exports.isUuidProp = _chunk76VBQWGEjs.isUuidProp; exports.isVirtualProp = _chunk76VBQWGEjs.isVirtualProp; exports.nonNullable = _chunk76VBQWGEjs.nonNullable; exports.objToMap = objToMap; exports.p = p; exports.propNodeToZodTypeDef = _chunk76VBQWGEjs.propNodeToZodTypeDef; exports.propToZodTypeDef = _chunk76VBQWGEjs.propToZodTypeDef; exports.registeredApis = _chunk76VBQWGEjs.registeredApis; exports.serializeZodType = _chunk76VBQWGEjs.serializeZodType; exports.setupErrorHandler = setupErrorHandler; exports.unwrapPromiseOnce = _chunk76VBQWGEjs.unwrapPromiseOnce; exports.zArrayable = _chunk76VBQWGEjs.zArrayable; exports.zodTypeToTsTypeDef = _chunk76VBQWGEjs.zodTypeToTsTypeDef; exports.zodTypeToZodCode = _chunk76VBQWGEjs.zodTypeToZodCode;
|
|
427
427
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
package/src/api/sonamu.ts
CHANGED
|
@@ -29,6 +29,9 @@ export type SonamuConfig = {
|
|
|
29
29
|
prefix: string;
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
+
export type SonamuSecrets = {
|
|
33
|
+
[key: string]: string;
|
|
34
|
+
};
|
|
32
35
|
type SonamuFastifyConfig = {
|
|
33
36
|
contextProvider: (
|
|
34
37
|
defaultContext: Pick<Context, "headers" | "reply">,
|
|
@@ -117,6 +120,14 @@ class SonamuClass {
|
|
|
117
120
|
return this._config;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
private _secrets: SonamuSecrets | null = null;
|
|
124
|
+
set secrets(secrets: SonamuSecrets) {
|
|
125
|
+
this._secrets = secrets;
|
|
126
|
+
}
|
|
127
|
+
get secrets(): SonamuSecrets | null {
|
|
128
|
+
return this._secrets;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
async init(
|
|
121
132
|
doSilent: boolean = false,
|
|
122
133
|
enableSync: boolean = true,
|
|
@@ -129,12 +140,18 @@ class SonamuClass {
|
|
|
129
140
|
|
|
130
141
|
this.apiRootPath = apiRootPath ?? (await findApiRootPath());
|
|
131
142
|
const configPath = path.join(this.apiRootPath, "sonamu.config.json");
|
|
143
|
+
const secretsPath = path.join(this.apiRootPath, "sonamu.secrets.json");
|
|
132
144
|
if (fs.existsSync(configPath) === false) {
|
|
133
145
|
throw new Error(`Cannot find sonamu.config.json in ${configPath}`);
|
|
134
146
|
}
|
|
135
147
|
this.config = JSON.parse(
|
|
136
148
|
fs.readFileSync(configPath).toString()
|
|
137
149
|
) as SonamuConfig;
|
|
150
|
+
if (fs.existsSync(secretsPath)) {
|
|
151
|
+
this.secrets = JSON.parse(
|
|
152
|
+
fs.readFileSync(secretsPath).toString()
|
|
153
|
+
) as SonamuSecrets;
|
|
154
|
+
}
|
|
138
155
|
|
|
139
156
|
// DB 로드
|
|
140
157
|
this.dbConfig = await DB.readKnexfile();
|
|
@@ -5,9 +5,22 @@ import { Sonamu } from "../api";
|
|
|
5
5
|
import { BaseModel } from "../database/base-model";
|
|
6
6
|
import { EntityManager } from "../entity/entity-manager";
|
|
7
7
|
import {
|
|
8
|
+
EntityProp,
|
|
9
|
+
FixtureImportResult,
|
|
10
|
+
FixtureRecord,
|
|
11
|
+
FixtureSearchOptions,
|
|
12
|
+
ManyToManyRelationProp,
|
|
8
13
|
isBelongsToOneRelationProp,
|
|
14
|
+
isHasManyRelationProp,
|
|
15
|
+
isManyToManyRelationProp,
|
|
9
16
|
isOneToOneRelationProp,
|
|
17
|
+
isRelationProp,
|
|
18
|
+
isVirtualProp,
|
|
10
19
|
} from "../types/types";
|
|
20
|
+
import { Entity } from "../entity/entity";
|
|
21
|
+
import inflection from "inflection";
|
|
22
|
+
import { SonamuDBConfig } from "../database/db";
|
|
23
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
11
24
|
|
|
12
25
|
export class FixtureManagerClass {
|
|
13
26
|
private _tdb: Knex | null = null;
|
|
@@ -32,6 +45,15 @@ export class FixtureManagerClass {
|
|
|
32
45
|
return this._fdb;
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
private dependencyGraph: Map<
|
|
49
|
+
string,
|
|
50
|
+
{
|
|
51
|
+
fixtureId: string;
|
|
52
|
+
entityId: string;
|
|
53
|
+
dependencies: Set<string>;
|
|
54
|
+
}
|
|
55
|
+
> = new Map();
|
|
56
|
+
|
|
35
57
|
init() {
|
|
36
58
|
if (this._tdb !== null) {
|
|
37
59
|
return;
|
|
@@ -257,5 +279,414 @@ export class FixtureManagerClass {
|
|
|
257
279
|
}
|
|
258
280
|
await BaseModel.destroy();
|
|
259
281
|
}
|
|
282
|
+
|
|
283
|
+
async getFixtures(
|
|
284
|
+
sourceDBName: keyof SonamuDBConfig,
|
|
285
|
+
targetDBName: keyof SonamuDBConfig,
|
|
286
|
+
searchOptions: FixtureSearchOptions
|
|
287
|
+
) {
|
|
288
|
+
const sourceDB = knex(Sonamu.dbConfig[sourceDBName]);
|
|
289
|
+
const targetDB = knex(Sonamu.dbConfig[targetDBName]);
|
|
290
|
+
const { entityId, field, value, searchType } = searchOptions;
|
|
291
|
+
|
|
292
|
+
const entity = EntityManager.get(entityId);
|
|
293
|
+
const column =
|
|
294
|
+
entity.props.find((prop) => prop.name === field)?.type === "relation"
|
|
295
|
+
? `${field}_id`
|
|
296
|
+
: field;
|
|
297
|
+
|
|
298
|
+
let query = sourceDB(entity.table);
|
|
299
|
+
if (searchType === "equals") {
|
|
300
|
+
query = query.where(column, value);
|
|
301
|
+
} else if (searchType === "like") {
|
|
302
|
+
query = query.where(column, "like", `%${value}%`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const rows = await query;
|
|
306
|
+
if (rows.length === 0) {
|
|
307
|
+
throw new Error("No records found");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const visitedEntities = new Set<string>();
|
|
311
|
+
const records: FixtureRecord[] = [];
|
|
312
|
+
for (const row of rows) {
|
|
313
|
+
const initialRecordsLength = records.length;
|
|
314
|
+
await this.createFixtureRecord(entity, row, visitedEntities, records);
|
|
315
|
+
const currentFixtureRecord = records.find(
|
|
316
|
+
(r) => r.fixtureId === `${entityId}#${row.id}`
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (currentFixtureRecord) {
|
|
320
|
+
// 현재 fixture로부터 생성된 fetchedRecords 설정
|
|
321
|
+
currentFixtureRecord.fetchedRecords = records
|
|
322
|
+
.filter((r) => r.fixtureId !== currentFixtureRecord.fixtureId)
|
|
323
|
+
.slice(initialRecordsLength)
|
|
324
|
+
.map((r) => r.fixtureId);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for await (const record of records) {
|
|
329
|
+
const entity = EntityManager.get(record.entityId);
|
|
330
|
+
const rows: FixtureRecord[] = [];
|
|
331
|
+
const row = await targetDB(entity.table).where("id", record.id).first();
|
|
332
|
+
if (row) {
|
|
333
|
+
await this.createFixtureRecord(
|
|
334
|
+
entity,
|
|
335
|
+
row,
|
|
336
|
+
new Set(),
|
|
337
|
+
rows,
|
|
338
|
+
true,
|
|
339
|
+
targetDB
|
|
340
|
+
);
|
|
341
|
+
record.target = rows[0];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return records;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async createFixtureRecord(
|
|
349
|
+
entity: Entity,
|
|
350
|
+
row: any,
|
|
351
|
+
visitedEntities: Set<string>,
|
|
352
|
+
records: FixtureRecord[],
|
|
353
|
+
singleRecord = false,
|
|
354
|
+
_db?: Knex
|
|
355
|
+
) {
|
|
356
|
+
const fixtureId = `${entity.id}#${row.id}`;
|
|
357
|
+
if (visitedEntities.has(fixtureId)) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
visitedEntities.add(fixtureId);
|
|
361
|
+
|
|
362
|
+
const record: FixtureRecord = {
|
|
363
|
+
fixtureId,
|
|
364
|
+
entityId: entity.id,
|
|
365
|
+
id: row.id,
|
|
366
|
+
columns: {},
|
|
367
|
+
fetchedRecords: [],
|
|
368
|
+
belongsRecords: [],
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
for (const prop of entity.props) {
|
|
372
|
+
if (isVirtualProp(prop)) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
record.columns[prop.name] = {
|
|
377
|
+
prop: prop,
|
|
378
|
+
value: row[prop.name],
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const db = _db ?? BaseModel.getDB("w");
|
|
382
|
+
if (isManyToManyRelationProp(prop)) {
|
|
383
|
+
const relatedEntity = EntityManager.get(prop.with);
|
|
384
|
+
const throughTable = prop.joinTable;
|
|
385
|
+
const fromColumn = `${inflection.singularize(entity.table)}_id`;
|
|
386
|
+
const toColumn = `${inflection.singularize(relatedEntity.table)}_id`;
|
|
387
|
+
|
|
388
|
+
const relatedIds = await db(throughTable)
|
|
389
|
+
.where(fromColumn, row.id)
|
|
390
|
+
.pluck(toColumn);
|
|
391
|
+
record.columns[prop.name].value = relatedIds;
|
|
392
|
+
} else if (isHasManyRelationProp(prop)) {
|
|
393
|
+
const relatedEntity = EntityManager.get(prop.with);
|
|
394
|
+
const relatedIds = await db(relatedEntity.table)
|
|
395
|
+
.where(prop.joinColumn, row.id)
|
|
396
|
+
.pluck("id");
|
|
397
|
+
record.columns[prop.name].value = relatedIds;
|
|
398
|
+
} else if (isOneToOneRelationProp(prop) && !prop.hasJoinColumn) {
|
|
399
|
+
const relatedEntity = EntityManager.get(prop.with);
|
|
400
|
+
const relatedProp = relatedEntity.props.find(
|
|
401
|
+
(p) => p.type === "relation" && p.with === entity.id
|
|
402
|
+
);
|
|
403
|
+
if (relatedProp) {
|
|
404
|
+
const relatedRow = await db(relatedEntity.table)
|
|
405
|
+
.where("id", row.id)
|
|
406
|
+
.first();
|
|
407
|
+
record.columns[prop.name].value = relatedRow?.id;
|
|
408
|
+
}
|
|
409
|
+
} else if (isRelationProp(prop)) {
|
|
410
|
+
const relatedId = row[`${prop.name}_id`];
|
|
411
|
+
record.columns[prop.name].value = relatedId;
|
|
412
|
+
if (relatedId) {
|
|
413
|
+
record.belongsRecords.push(`${prop.with}#${relatedId}`);
|
|
414
|
+
}
|
|
415
|
+
if (!singleRecord && relatedId) {
|
|
416
|
+
const relatedEntity = EntityManager.get(prop.with);
|
|
417
|
+
const relatedRow = await db(relatedEntity.table)
|
|
418
|
+
.where("id", relatedId)
|
|
419
|
+
.first();
|
|
420
|
+
if (relatedRow) {
|
|
421
|
+
await this.createFixtureRecord(
|
|
422
|
+
relatedEntity,
|
|
423
|
+
relatedRow,
|
|
424
|
+
visitedEntities,
|
|
425
|
+
records,
|
|
426
|
+
singleRecord,
|
|
427
|
+
_db
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
records.push(record);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async insertFixtures(
|
|
438
|
+
dbName: keyof SonamuDBConfig,
|
|
439
|
+
fixtures: FixtureRecord[]
|
|
440
|
+
) {
|
|
441
|
+
this.buildDependencyGraph(fixtures);
|
|
442
|
+
const insertionOrder = this.getInsertionOrder();
|
|
443
|
+
const db = knex(Sonamu.dbConfig[dbName]);
|
|
444
|
+
|
|
445
|
+
await db.transaction(async (trx) => {
|
|
446
|
+
await trx.raw(`SET FOREIGN_KEY_CHECKS = 0`);
|
|
447
|
+
|
|
448
|
+
for (const fixtureId of insertionOrder) {
|
|
449
|
+
const fixture = fixtures.find((f) => f.fixtureId === fixtureId)!;
|
|
450
|
+
await this.insertFixture(trx, fixture);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
for (const fixtureId of insertionOrder) {
|
|
454
|
+
const fixture = fixtures.find((f) => f.fixtureId === fixtureId)!;
|
|
455
|
+
await this.handleManyToManyRelations(trx, fixture, fixtures);
|
|
456
|
+
}
|
|
457
|
+
await trx.raw(`SET FOREIGN_KEY_CHECKS = 1`);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const records: FixtureImportResult[] = [];
|
|
461
|
+
|
|
462
|
+
for await (const r of fixtures) {
|
|
463
|
+
const entity = EntityManager.get(r.entityId);
|
|
464
|
+
const record = await db(entity.table).where("id", r.id).first();
|
|
465
|
+
records.push({
|
|
466
|
+
entityId: r.entityId,
|
|
467
|
+
data: record,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return records;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private getInsertionOrder() {
|
|
475
|
+
const visited = new Set<string>();
|
|
476
|
+
const order: string[] = [];
|
|
477
|
+
const tempVisited = new Set<string>();
|
|
478
|
+
|
|
479
|
+
const visit = (fixtureId: string) => {
|
|
480
|
+
if (visited.has(fixtureId)) return;
|
|
481
|
+
if (tempVisited.has(fixtureId)) {
|
|
482
|
+
console.warn(`Circular dependency detected involving: ${fixtureId}`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
tempVisited.add(fixtureId);
|
|
487
|
+
|
|
488
|
+
const node = this.dependencyGraph.get(fixtureId)!;
|
|
489
|
+
const entity = EntityManager.get(node.entityId);
|
|
490
|
+
|
|
491
|
+
for (const depId of node.dependencies) {
|
|
492
|
+
const depNode = this.dependencyGraph.get(depId)!;
|
|
493
|
+
|
|
494
|
+
// BelongsToOne 관계이면서 nullable이 아닌 경우 먼저 방문
|
|
495
|
+
const relationProp = entity.props.find(
|
|
496
|
+
(prop) =>
|
|
497
|
+
isRelationProp(prop) &&
|
|
498
|
+
(isBelongsToOneRelationProp(prop) ||
|
|
499
|
+
(isOneToOneRelationProp(prop) && prop.hasJoinColumn)) &&
|
|
500
|
+
prop.with === depNode.entityId
|
|
501
|
+
);
|
|
502
|
+
if (relationProp && !relationProp.nullable) {
|
|
503
|
+
visit(depId);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
tempVisited.delete(fixtureId);
|
|
508
|
+
visited.add(fixtureId);
|
|
509
|
+
order.push(fixtureId);
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
for (const fixtureId of this.dependencyGraph.keys()) {
|
|
513
|
+
visit(fixtureId);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// circular dependency로 인해 방문되지 않은 fixtureId 추가
|
|
517
|
+
for (const fixtureId of this.dependencyGraph.keys()) {
|
|
518
|
+
if (!visited.has(fixtureId)) {
|
|
519
|
+
order.push(fixtureId);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return order;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private prepareInsertData(fixture: FixtureRecord) {
|
|
527
|
+
const insertData: any = {};
|
|
528
|
+
for (const [propName, column] of Object.entries(fixture.columns)) {
|
|
529
|
+
if (isVirtualProp(column.prop)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const prop = column.prop as EntityProp;
|
|
534
|
+
if (!isRelationProp(prop)) {
|
|
535
|
+
if (prop.type === "json") {
|
|
536
|
+
insertData[propName] = JSON.stringify(column.value);
|
|
537
|
+
} else {
|
|
538
|
+
insertData[propName] = column.value;
|
|
539
|
+
}
|
|
540
|
+
} else if (
|
|
541
|
+
isBelongsToOneRelationProp(prop) ||
|
|
542
|
+
(isOneToOneRelationProp(prop) && prop.hasJoinColumn)
|
|
543
|
+
) {
|
|
544
|
+
insertData[`${propName}_id`] = column.value;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return insertData;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private buildDependencyGraph(fixtures: FixtureRecord[]) {
|
|
551
|
+
this.dependencyGraph.clear();
|
|
552
|
+
|
|
553
|
+
// 1. 노드 추가
|
|
554
|
+
for (const fixture of fixtures) {
|
|
555
|
+
this.dependencyGraph.set(fixture.fixtureId, {
|
|
556
|
+
fixtureId: fixture.fixtureId,
|
|
557
|
+
entityId: fixture.entityId,
|
|
558
|
+
dependencies: new Set(),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 2. 의존성 추가
|
|
563
|
+
for (const fixture of fixtures) {
|
|
564
|
+
const node = this.dependencyGraph.get(fixture.fixtureId)!;
|
|
565
|
+
|
|
566
|
+
for (const [, column] of Object.entries(fixture.columns)) {
|
|
567
|
+
const prop = column.prop as EntityProp;
|
|
568
|
+
|
|
569
|
+
if (isRelationProp(prop)) {
|
|
570
|
+
if (
|
|
571
|
+
isBelongsToOneRelationProp(prop) ||
|
|
572
|
+
(isOneToOneRelationProp(prop) && prop.hasJoinColumn)
|
|
573
|
+
) {
|
|
574
|
+
const relatedFixtureId = `${prop.with}#${column.value}`;
|
|
575
|
+
if (this.dependencyGraph.has(relatedFixtureId)) {
|
|
576
|
+
node.dependencies.add(relatedFixtureId);
|
|
577
|
+
}
|
|
578
|
+
} else if (isManyToManyRelationProp(prop)) {
|
|
579
|
+
// ManyToMany 관계의 경우 양방향 의존성 추가
|
|
580
|
+
const relatedIds = column.value as number[];
|
|
581
|
+
for (const relatedId of relatedIds) {
|
|
582
|
+
const relatedFixtureId = `${prop.with}#${relatedId}`;
|
|
583
|
+
if (this.dependencyGraph.has(relatedFixtureId)) {
|
|
584
|
+
node.dependencies.add(relatedFixtureId);
|
|
585
|
+
this.dependencyGraph
|
|
586
|
+
.get(relatedFixtureId)!
|
|
587
|
+
.dependencies.add(fixture.fixtureId);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private async insertFixture(db: Knex, fixture: FixtureRecord) {
|
|
597
|
+
const insertData = this.prepareInsertData(fixture);
|
|
598
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
const found = await db(entity.table).where("id", fixture.id).first();
|
|
602
|
+
if (found && !fixture.override) {
|
|
603
|
+
return {
|
|
604
|
+
entityId: fixture.entityId,
|
|
605
|
+
id: found.id,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const q = db.insert(insertData).into(entity.table);
|
|
610
|
+
await q.onDuplicateUpdate.apply(q, Object.keys(insertData));
|
|
611
|
+
return {
|
|
612
|
+
entityId: fixture.entityId,
|
|
613
|
+
id: fixture.id,
|
|
614
|
+
};
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.log(err);
|
|
617
|
+
throw err;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private async handleManyToManyRelations(
|
|
622
|
+
db: Knex,
|
|
623
|
+
fixture: FixtureRecord,
|
|
624
|
+
fixtures: FixtureRecord[]
|
|
625
|
+
) {
|
|
626
|
+
for (const [, column] of Object.entries(fixture.columns)) {
|
|
627
|
+
const prop = column.prop as EntityProp;
|
|
628
|
+
if (isManyToManyRelationProp(prop)) {
|
|
629
|
+
const joinTable = (prop as ManyToManyRelationProp).joinTable;
|
|
630
|
+
const relatedIds = column.value as number[];
|
|
631
|
+
|
|
632
|
+
for (const relatedId of relatedIds) {
|
|
633
|
+
if (
|
|
634
|
+
!fixtures.find((f) => f.fixtureId === `${prop.with}#${relatedId}`)
|
|
635
|
+
) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const entity = EntityManager.get(fixture.entityId);
|
|
640
|
+
const relatedEntity = EntityManager.get(prop.with);
|
|
641
|
+
if (!entity || !relatedEntity) {
|
|
642
|
+
throw new Error(
|
|
643
|
+
`Entity not found: ${fixture.entityId}, ${prop.with}`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const [found] = await db(joinTable)
|
|
648
|
+
.where({
|
|
649
|
+
[`${inflection.singularize(entity.table)}_id`]: fixture.id,
|
|
650
|
+
[`${inflection.singularize(relatedEntity.table)}_id`]: relatedId,
|
|
651
|
+
})
|
|
652
|
+
.limit(1);
|
|
653
|
+
if (found) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const newIds = await db(joinTable).insert({
|
|
658
|
+
[`${inflection.singularize(entity.table)}_id`]: fixture.id,
|
|
659
|
+
[`${inflection.singularize(relatedEntity.table)}_id`]: relatedId,
|
|
660
|
+
});
|
|
661
|
+
console.log(
|
|
662
|
+
chalk.green(
|
|
663
|
+
`Inserted into ${joinTable}: ${entity.table}(${fixture.id}) - ${relatedEntity.table}(${relatedId}) ID: ${newIds}`
|
|
664
|
+
)
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async addFixtureLoader(code: string) {
|
|
672
|
+
const path = Sonamu.apiRootPath + "/src/testing/fixture.ts";
|
|
673
|
+
let content = readFileSync(path).toString();
|
|
674
|
+
|
|
675
|
+
const fixtureLoaderStart = content.indexOf("const fixtureLoader = {");
|
|
676
|
+
const fixtureLoaderEnd = content.indexOf("};", fixtureLoaderStart);
|
|
677
|
+
|
|
678
|
+
if (fixtureLoaderStart !== -1 && fixtureLoaderEnd !== -1) {
|
|
679
|
+
const newContent =
|
|
680
|
+
content.slice(0, fixtureLoaderEnd) +
|
|
681
|
+
" " +
|
|
682
|
+
code +
|
|
683
|
+
"\n" +
|
|
684
|
+
content.slice(fixtureLoaderEnd);
|
|
685
|
+
|
|
686
|
+
writeFileSync(path, newContent);
|
|
687
|
+
} else {
|
|
688
|
+
throw new Error("Failed to find fixtureLoader in fixture.ts");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
260
691
|
}
|
|
261
692
|
export const FixtureManager = new FixtureManagerClass();
|
package/src/types/types.ts
CHANGED
|
@@ -719,3 +719,33 @@ export const PathAndCode = z.object({
|
|
|
719
719
|
code: z.string(),
|
|
720
720
|
});
|
|
721
721
|
export type PathAndCode = z.infer<typeof PathAndCode>;
|
|
722
|
+
|
|
723
|
+
export type FixtureSearchOptions = {
|
|
724
|
+
entityId: string;
|
|
725
|
+
field: string;
|
|
726
|
+
value: string;
|
|
727
|
+
searchType: "equals" | "like";
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
export type FixtureRecord = {
|
|
731
|
+
fixtureId: string;
|
|
732
|
+
entityId: string;
|
|
733
|
+
id: number;
|
|
734
|
+
columns: {
|
|
735
|
+
[key: string]: {
|
|
736
|
+
prop: EntityProp;
|
|
737
|
+
value: any;
|
|
738
|
+
};
|
|
739
|
+
};
|
|
740
|
+
fetchedRecords: string[];
|
|
741
|
+
belongsRecords: string[]; //
|
|
742
|
+
target?: FixtureRecord; // Import 대상 DB 레코드
|
|
743
|
+
override?: boolean;
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
export type FixtureImportResult = {
|
|
747
|
+
entityId: string;
|
|
748
|
+
data: {
|
|
749
|
+
[key: string]: any;
|
|
750
|
+
};
|
|
751
|
+
};
|