sonamu 0.7.9 → 0.7.11
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/bin/cli.js +6 -2
- package/dist/database/puri.d.ts +17 -1
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +47 -1
- package/dist/database/puri.types.d.ts +28 -0
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +2 -3
- package/dist/database/upsert-builder.d.ts +3 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +19 -4
- package/dist/naite/naite-reporter.d.ts +7 -4
- package/dist/naite/naite-reporter.d.ts.map +1 -1
- package/dist/naite/naite-reporter.js +45 -21
- package/dist/template/implementations/model.template.js +2 -2
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +4 -2
- package/dist/types/types.d.ts +1 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +4 -1
- package/dist/utils/console-util.js +2 -2
- package/package.json +7 -7
- package/src/database/puri.ts +80 -0
- package/src/database/puri.types.ts +33 -2
- package/src/database/upsert-builder.ts +27 -9
- package/src/naite/naite-reporter.ts +51 -20
- package/src/template/implementations/model.template.ts +1 -1
- package/src/template/zod-converter.ts +3 -0
- package/src/types/types.ts +3 -0
- package/src/utils/console-util.ts +1 -1
|
@@ -6,7 +6,7 @@ import { Naite } from "../naite/naite";
|
|
|
6
6
|
import type { DatabaseForeignKeys, DatabaseSchemaExtend, EntityIndex } from "../types/types";
|
|
7
7
|
import { assertDefined, chunk, nonNullable } from "../utils/utils";
|
|
8
8
|
import { batchUpdate, type RowWithId } from "./_batch_update";
|
|
9
|
-
import type { ForeignKeyColumns, TableName } from "./puri.types";
|
|
9
|
+
import type { ColumnKeys, ForeignKeyColumns, TableName } from "./puri.types";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* FK 타입 추론을 위해 DatabaseForeignKeys export
|
|
@@ -14,6 +14,9 @@ import type { ForeignKeyColumns, TableName } from "./puri.types";
|
|
|
14
14
|
*/
|
|
15
15
|
export type { DatabaseForeignKeys };
|
|
16
16
|
|
|
17
|
+
type InheritableColumns<TTable extends TableName<DatabaseSchemaExtend>> =
|
|
18
|
+
TTable extends keyof DatabaseSchemaExtend ? ColumnKeys<DatabaseSchemaExtend[TTable]> : never;
|
|
19
|
+
|
|
17
20
|
// 테이블 데이터 타입
|
|
18
21
|
type TableData = {
|
|
19
22
|
references: Set<string>;
|
|
@@ -33,6 +36,7 @@ export type UBRef = {
|
|
|
33
36
|
export type UpsertOptions<TTable extends TableName<DatabaseSchemaExtend>> = {
|
|
34
37
|
chunkSize?: number;
|
|
35
38
|
cleanOrphans?: ForeignKeyColumns<TTable> | ForeignKeyColumns<TTable>[];
|
|
39
|
+
inherit?: InheritableColumns<TTable>[];
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
// insertOnly 옵션
|
|
@@ -289,16 +293,30 @@ export class UpsertBuilder {
|
|
|
289
293
|
resultRows = await wdb.insert(dataForDb).into(tableName).returning(selectFields);
|
|
290
294
|
} else {
|
|
291
295
|
// UPSERT 모드 - onConflict 사용 (unique index 없으면 PK fallback)
|
|
292
|
-
const conflictColumns =
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
296
|
+
const conflictColumns = table.uniqueIndexes[0]?.columns.map((c) => c.name) ?? ["id"];
|
|
297
|
+
|
|
298
|
+
const allColumns = Object.keys(dataForDb[0]);
|
|
299
|
+
let updateColumns = allColumns.filter((c) => !conflictColumns.includes(c));
|
|
300
|
+
|
|
301
|
+
// inherit 옵션 처리 - inherit 컬럼은 update 대상에서 제외
|
|
302
|
+
if (options?.inherit?.length) {
|
|
303
|
+
const inheritColumns = options.inherit as string[];
|
|
304
|
+
|
|
305
|
+
const excludedFromUpdate = updateColumns.filter((c) => inheritColumns.includes(c));
|
|
306
|
+
updateColumns = updateColumns.filter((c) => !inheritColumns.includes(c));
|
|
307
|
+
|
|
308
|
+
// 실제로 제외된 컬럼 로깅
|
|
309
|
+
if (excludedFromUpdate.length) {
|
|
310
|
+
Naite.t("puri:ub-inherit", {
|
|
311
|
+
tableName,
|
|
312
|
+
inheritColumns,
|
|
313
|
+
excludedFromUpdate,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
299
317
|
|
|
300
318
|
// updateColumns가 비어있어도 merge()를 사용하여 모든 행이 RETURNING되도록 보장
|
|
301
|
-
const mergeColumns = updateColumns.length
|
|
319
|
+
const mergeColumns = updateColumns.length ? updateColumns : conflictColumns;
|
|
302
320
|
|
|
303
321
|
resultRows = await wdb
|
|
304
322
|
.insert(dataForDb)
|
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
* NaiteReporter
|
|
3
3
|
*
|
|
4
4
|
* 테스트 결과와 Trace 정보를 Unix Socket으로 VS Code extension에 전달합니다.
|
|
5
|
-
* extension이 ~/.sonamu/naite.sock에 소켓 서버를 열어둡니다.
|
|
5
|
+
* extension이 ~/.sonamu/naite-{hash}.sock에 소켓 서버를 열어둡니다.
|
|
6
|
+
*
|
|
7
|
+
* 프로젝트별로 고유한 소켓을 사용하기 위해 sonamu.config.ts 경로의 해시를 사용합니다.
|
|
6
8
|
*
|
|
7
9
|
* fs mock 충돌을 피하기 위해 net 모듈만 사용합니다.
|
|
8
10
|
*/
|
|
9
11
|
/** biome-ignore-all lint/suspicious/noExplicitAny: Naite는 expect와 호응하도록 any를 허용함 */
|
|
10
12
|
|
|
13
|
+
import { createHash } from "crypto";
|
|
11
14
|
import { connect, type Socket } from "net";
|
|
12
15
|
import { homedir } from "os";
|
|
13
16
|
import { join } from "path";
|
|
17
|
+
import { findApiRootPath } from "../utils/utils";
|
|
14
18
|
import type { NaiteMessagingTypes } from "./messaging-types";
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* sonamu.config.ts 경로를 받아서 프로젝트별 고유 해시를 생성합니다.
|
|
22
|
+
* 익스텐션과 동일한 방식으로 계산해야 합니다.
|
|
23
|
+
*/
|
|
24
|
+
function getProjectHash(configPath: string): string {
|
|
25
|
+
return createHash("md5").update(configPath).digest("hex").slice(0, 8);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 현재 프로젝트의 소켓 경로를 계산합니다.
|
|
30
|
+
* Sonamu.apiRootPath가 설정된 후에 호출해야 합니다.
|
|
31
|
+
*/
|
|
32
|
+
function getSocketPath(): string {
|
|
33
|
+
// sonamu.config.ts의 절대 경로 계산
|
|
34
|
+
// apiRootPath는 /project/api 형태이고, config는 /project/api/src/sonamu.config.ts에 있음
|
|
35
|
+
const configPath = join(findApiRootPath(), "src", "sonamu.config.ts");
|
|
36
|
+
const hash = getProjectHash(configPath);
|
|
37
|
+
|
|
38
|
+
return process.platform === "win32"
|
|
39
|
+
? `\\\\.\\pipe\\naite-${hash}`
|
|
40
|
+
: join(homedir(), ".sonamu", `naite-${hash}.sock`);
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
class NaiteReporterClass {
|
|
44
|
+
private socketPath: string | null = null;
|
|
21
45
|
private socket: Socket | null = null;
|
|
22
46
|
private connected = false;
|
|
23
47
|
private buffer: string[] = [];
|
|
@@ -25,11 +49,16 @@ class NaiteReporterClass {
|
|
|
25
49
|
/**
|
|
26
50
|
* 소켓 연결 시도
|
|
27
51
|
*/
|
|
28
|
-
private ensureConnection(): void {
|
|
29
|
-
if (this.connected || this.socket)
|
|
52
|
+
private async ensureConnection(): Promise<void> {
|
|
53
|
+
if (this.connected || this.socket) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
30
56
|
|
|
31
|
-
|
|
32
|
-
this.
|
|
57
|
+
return new Promise((res, rej) => {
|
|
58
|
+
if (!this.socketPath) {
|
|
59
|
+
this.socketPath = getSocketPath();
|
|
60
|
+
}
|
|
61
|
+
this.socket = connect(this.socketPath);
|
|
33
62
|
|
|
34
63
|
this.socket.on("connect", () => {
|
|
35
64
|
this.connected = true;
|
|
@@ -38,30 +67,30 @@ class NaiteReporterClass {
|
|
|
38
67
|
this.socket?.write(msg);
|
|
39
68
|
}
|
|
40
69
|
this.buffer = [];
|
|
70
|
+
res();
|
|
41
71
|
});
|
|
42
72
|
|
|
43
|
-
this.socket.on("error", () => {
|
|
73
|
+
this.socket.on("error", (e) => {
|
|
44
74
|
// 연결 실패 무시 (extension이 꺼져있을 수 있음)
|
|
45
75
|
this.connected = false;
|
|
46
76
|
this.socket = null;
|
|
77
|
+
rej(e);
|
|
47
78
|
});
|
|
48
79
|
|
|
49
80
|
this.socket.on("close", () => {
|
|
50
81
|
this.connected = false;
|
|
51
82
|
this.socket = null;
|
|
52
83
|
});
|
|
53
|
-
}
|
|
54
|
-
// 연결 실패 무시
|
|
55
|
-
}
|
|
84
|
+
});
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
/**
|
|
59
88
|
* 메시지 전송 (줄바꿈으로 구분)
|
|
60
89
|
*/
|
|
61
|
-
private send(data: NaiteMessagingTypes.NaiteMessage): void {
|
|
90
|
+
private async send(data: NaiteMessagingTypes.NaiteMessage): Promise<void> {
|
|
62
91
|
const msg = `${JSON.stringify(data)}\n`;
|
|
63
92
|
|
|
64
|
-
this.ensureConnection();
|
|
93
|
+
await this.ensureConnection().catch((_) => {});
|
|
65
94
|
|
|
66
95
|
if (this.connected && this.socket) {
|
|
67
96
|
this.socket.write(msg);
|
|
@@ -75,12 +104,12 @@ class NaiteReporterClass {
|
|
|
75
104
|
* beforeAll에서 호출합니다.
|
|
76
105
|
* 테스트 run 시작을 알립니다 (데이터 클리어 신호).
|
|
77
106
|
*/
|
|
78
|
-
startTestRun(): void {
|
|
107
|
+
async startTestRun(): Promise<void> {
|
|
79
108
|
if (process.env.CI) {
|
|
80
109
|
return;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
this.send({
|
|
112
|
+
await this.send({
|
|
84
113
|
type: "run/start",
|
|
85
114
|
startedAt: new Date().toISOString(),
|
|
86
115
|
});
|
|
@@ -90,12 +119,14 @@ class NaiteReporterClass {
|
|
|
90
119
|
* afterEach에서 호출합니다.
|
|
91
120
|
* 테스트 케이스 결과를 traces와 함께 전송합니다.
|
|
92
121
|
*/
|
|
93
|
-
reportTestResult(
|
|
122
|
+
async reportTestResult(
|
|
123
|
+
result: Omit<NaiteMessagingTypes.TestResult, "receivedAt">,
|
|
124
|
+
): Promise<void> {
|
|
94
125
|
if (process.env.CI) {
|
|
95
126
|
return;
|
|
96
127
|
}
|
|
97
128
|
|
|
98
|
-
this.send({
|
|
129
|
+
await this.send({
|
|
99
130
|
type: "test/result",
|
|
100
131
|
receivedAt: new Date().toISOString(),
|
|
101
132
|
...result,
|
|
@@ -106,12 +137,12 @@ class NaiteReporterClass {
|
|
|
106
137
|
* afterAll에서 호출합니다.
|
|
107
138
|
* 테스트 run 종료를 알립니다.
|
|
108
139
|
*/
|
|
109
|
-
endTestRun(): void {
|
|
140
|
+
async endTestRun(): Promise<void> {
|
|
110
141
|
if (process.env.CI) {
|
|
111
142
|
return;
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
this.send({
|
|
145
|
+
await this.send({
|
|
115
146
|
type: "run/end",
|
|
116
147
|
endedAt: new Date().toISOString(),
|
|
117
148
|
});
|
|
@@ -105,7 +105,7 @@ class ${entityId}ModelClass extends BaseModelClass<
|
|
|
105
105
|
search: "${def.search}" as const,
|
|
106
106
|
orderBy: "${def.orderBy}" as const,
|
|
107
107
|
...rawParams,
|
|
108
|
-
};
|
|
108
|
+
} satisfies ${entityId}ListParams;
|
|
109
109
|
|
|
110
110
|
// build queries
|
|
111
111
|
const { qb, onSubset: _ } = this.getSubsetQueries(subset);
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
isRelationProp,
|
|
48
48
|
isStringArrayProp,
|
|
49
49
|
isStringSingleProp,
|
|
50
|
+
isTsVectorProp,
|
|
50
51
|
isUuidArrayProp,
|
|
51
52
|
isUuidSingleProp,
|
|
52
53
|
isVectorArrayProp,
|
|
@@ -215,6 +216,8 @@ export function propToZodTypeDef(prop: EntityProp, injectImportKeys: string[]):
|
|
|
215
216
|
stmt = `${prop.name}: z.array(z.number())`;
|
|
216
217
|
} else if (isVectorArrayProp(prop)) {
|
|
217
218
|
stmt = `${prop.name}: z.array(z.array(z.number()))`;
|
|
219
|
+
} else if (isTsVectorProp(prop)) {
|
|
220
|
+
stmt = `${prop.name}: z.string()`;
|
|
218
221
|
} else if (isVirtualProp(prop)) {
|
|
219
222
|
stmt = `${prop.name}: ${prop.id}`;
|
|
220
223
|
injectImportKeys.push(prop.id);
|
package/src/types/types.ts
CHANGED
|
@@ -422,6 +422,9 @@ export function isVectorArrayProp(p: unknown): p is VectorArrayProp {
|
|
|
422
422
|
export function isVectorProp(p: unknown): p is VectorProp | VectorArrayProp {
|
|
423
423
|
return isVectorSingleProp(p) || isVectorArrayProp(p);
|
|
424
424
|
}
|
|
425
|
+
export function isTsVectorProp(p: unknown): p is TsVectorProp {
|
|
426
|
+
return (p as TsVectorProp)?.type === "tsvector";
|
|
427
|
+
}
|
|
425
428
|
export function isRelationProp(p: unknown): p is RelationProp {
|
|
426
429
|
return (p as RelationProp)?.type === "relation";
|
|
427
430
|
}
|