sonamu 0.7.4 → 0.7.6

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.
Files changed (137) hide show
  1. package/dist/api/config.d.ts +1 -4
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +1 -1
  4. package/dist/api/sonamu.d.ts +2 -0
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +19 -47
  7. package/dist/bin/cli.js +6 -6
  8. package/dist/database/base-model.d.ts +1 -1
  9. package/dist/database/base-model.d.ts.map +1 -1
  10. package/dist/database/base-model.js +15 -4
  11. package/dist/database/code-generator.d.ts.map +1 -1
  12. package/dist/database/code-generator.js +3 -3
  13. package/dist/database/db.d.ts.map +1 -1
  14. package/dist/database/db.js +1 -1
  15. package/dist/database/puri-wrapper.d.ts +11 -11
  16. package/dist/database/puri-wrapper.d.ts.map +1 -1
  17. package/dist/database/puri-wrapper.js +7 -11
  18. package/dist/database/puri.d.ts +36 -17
  19. package/dist/database/puri.d.ts.map +1 -1
  20. package/dist/database/puri.js +54 -7
  21. package/dist/database/puri.types.d.ts +54 -17
  22. package/dist/database/puri.types.d.ts.map +1 -1
  23. package/dist/database/puri.types.js +2 -4
  24. package/dist/database/puri.types.test-d.js +129 -0
  25. package/dist/database/upsert-builder.d.ts +16 -10
  26. package/dist/database/upsert-builder.d.ts.map +1 -1
  27. package/dist/database/upsert-builder.js +10 -19
  28. package/dist/entity/entity-manager.d.ts +113 -22
  29. package/dist/entity/entity-manager.d.ts.map +1 -1
  30. package/dist/entity/entity-manager.js +1 -1
  31. package/dist/entity/entity.d.ts +34 -0
  32. package/dist/entity/entity.d.ts.map +1 -1
  33. package/dist/entity/entity.js +110 -37
  34. package/dist/index.d.ts +5 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -2
  37. package/dist/migration/code-generation.d.ts.map +1 -1
  38. package/dist/migration/code-generation.js +341 -149
  39. package/dist/migration/migration-set.d.ts.map +1 -1
  40. package/dist/migration/migration-set.js +21 -5
  41. package/dist/migration/migrator.d.ts.map +1 -1
  42. package/dist/migration/migrator.js +7 -1
  43. package/dist/migration/postgresql-schema-reader.d.ts +11 -1
  44. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  45. package/dist/migration/postgresql-schema-reader.js +111 -10
  46. package/dist/syncer/syncer.d.ts.map +1 -1
  47. package/dist/syncer/syncer.js +7 -4
  48. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  49. package/dist/template/implementations/generated.template.js +12 -2
  50. package/dist/template/implementations/generated_sso.template.d.ts +3 -3
  51. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  52. package/dist/template/implementations/generated_sso.template.js +50 -2
  53. package/dist/template/implementations/model.template.d.ts.map +1 -1
  54. package/dist/template/implementations/model.template.js +20 -15
  55. package/dist/template/implementations/model_test.template.js +4 -4
  56. package/dist/template/implementations/service.template.d.ts.map +1 -1
  57. package/dist/template/implementations/service.template.js +2 -2
  58. package/dist/template/implementations/view_enums_dropdown.template.js +2 -2
  59. package/dist/template/implementations/view_enums_select.template.js +2 -2
  60. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  61. package/dist/template/implementations/view_form.template.js +12 -9
  62. package/dist/template/implementations/view_id_async_select.template.js +4 -4
  63. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  64. package/dist/template/implementations/view_list.template.js +12 -9
  65. package/dist/template/implementations/view_search_input.template.js +2 -2
  66. package/dist/template/template.js +2 -2
  67. package/dist/template/zod-converter.d.ts.map +1 -1
  68. package/dist/template/zod-converter.js +17 -2
  69. package/dist/testing/fixture-manager.d.ts +2 -1
  70. package/dist/testing/fixture-manager.d.ts.map +1 -1
  71. package/dist/testing/fixture-manager.js +29 -29
  72. package/dist/types/types.d.ts +593 -68
  73. package/dist/types/types.d.ts.map +1 -1
  74. package/dist/types/types.js +113 -9
  75. package/dist/vector/chunking.d.ts +25 -0
  76. package/dist/vector/chunking.d.ts.map +1 -0
  77. package/dist/vector/chunking.js +97 -0
  78. package/dist/vector/config.d.ts +12 -0
  79. package/dist/vector/config.d.ts.map +1 -0
  80. package/dist/vector/config.js +83 -0
  81. package/dist/vector/embedding.d.ts +42 -0
  82. package/dist/vector/embedding.d.ts.map +1 -0
  83. package/dist/vector/embedding.js +147 -0
  84. package/dist/vector/types.d.ts +105 -0
  85. package/dist/vector/types.d.ts.map +1 -0
  86. package/dist/vector/types.js +5 -0
  87. package/dist/vector/vector-search.d.ts +47 -0
  88. package/dist/vector/vector-search.d.ts.map +1 -0
  89. package/dist/vector/vector-search.js +176 -0
  90. package/package.json +9 -8
  91. package/src/api/config.ts +0 -4
  92. package/src/api/sonamu.ts +21 -36
  93. package/src/bin/cli.ts +5 -5
  94. package/src/database/base-model.ts +20 -11
  95. package/src/database/code-generator.ts +6 -2
  96. package/src/database/db.ts +1 -0
  97. package/src/database/puri-wrapper.ts +22 -16
  98. package/src/database/puri.ts +150 -27
  99. package/src/database/puri.types.test-d.ts +457 -0
  100. package/src/database/puri.types.ts +231 -33
  101. package/src/database/upsert-builder.ts +43 -34
  102. package/src/entity/entity-manager.ts +2 -2
  103. package/src/entity/entity.ts +134 -44
  104. package/src/index.ts +6 -0
  105. package/src/migration/code-generation.ts +377 -174
  106. package/src/migration/migration-set.ts +22 -3
  107. package/src/migration/migrator.ts +6 -0
  108. package/src/migration/postgresql-schema-reader.ts +121 -21
  109. package/src/syncer/syncer.ts +6 -3
  110. package/src/template/implementations/generated.template.ts +51 -9
  111. package/src/template/implementations/generated_sso.template.ts +71 -2
  112. package/src/template/implementations/model.template.ts +25 -15
  113. package/src/template/implementations/model_test.template.ts +3 -3
  114. package/src/template/implementations/service.template.ts +5 -1
  115. package/src/template/implementations/view_enums_dropdown.template.ts +1 -1
  116. package/src/template/implementations/view_enums_select.template.ts +1 -1
  117. package/src/template/implementations/view_form.template.ts +11 -8
  118. package/src/template/implementations/view_id_async_select.template.ts +3 -3
  119. package/src/template/implementations/view_list.template.ts +11 -8
  120. package/src/template/implementations/view_search_input.template.ts +1 -1
  121. package/src/template/template.ts +1 -1
  122. package/src/template/zod-converter.ts +20 -0
  123. package/src/testing/fixture-manager.ts +31 -30
  124. package/src/types/types.ts +226 -48
  125. package/src/vector/chunking.ts +115 -0
  126. package/src/vector/config.ts +68 -0
  127. package/src/vector/embedding.ts +193 -0
  128. package/src/vector/types.ts +122 -0
  129. package/src/vector/vector-search.ts +261 -0
  130. package/dist/template/implementations/view_enums_buttonset.template.d.ts +0 -17
  131. package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +0 -1
  132. package/dist/template/implementations/view_enums_buttonset.template.js +0 -31
  133. package/dist/template/implementations/view_list_columns.template.d.ts +0 -17
  134. package/dist/template/implementations/view_list_columns.template.d.ts.map +0 -1
  135. package/dist/template/implementations/view_list_columns.template.js +0 -49
  136. package/src/template/implementations/view_enums_buttonset.template.ts +0 -34
  137. package/src/template/implementations/view_list_columns.template.ts +0 -53
@@ -0,0 +1,457 @@
1
+ import { describe, expectTypeOf, it } from "vitest";
2
+ import type {
3
+ AvailableColumns,
4
+ ExtractColumnType,
5
+ LeftJoinedMarker,
6
+ ParseSelectObject,
7
+ } from "./puri.types";
8
+
9
+ // ============================================================================
10
+ // 테스트용 Mock 스키마
11
+ // ============================================================================
12
+
13
+ type MockSchema = {
14
+ users: {
15
+ id: number;
16
+ name: string;
17
+ email: string;
18
+ department_id: number | null;
19
+ };
20
+ departments: {
21
+ id: number;
22
+ name: string;
23
+ company_id: number;
24
+ };
25
+ companies: {
26
+ id: number;
27
+ name: string;
28
+ };
29
+ employees: {
30
+ id: number;
31
+ employee_number: string;
32
+ salary: string | null; // nullable 필드
33
+ user_id: number;
34
+ department_id: number | null;
35
+ };
36
+ };
37
+
38
+ // ============================================================================
39
+ // ExtractColumnType 테스트
40
+ // ============================================================================
41
+
42
+ describe("ExtractColumnType", () => {
43
+ describe("단일 테이블", () => {
44
+ it("기본 컬럼 타입을 추출한다", () => {
45
+ type Tables = { users: MockSchema["users"] };
46
+ type Result = ExtractColumnType<Tables, "users.id">;
47
+
48
+ const result = {} as Result;
49
+ expectTypeOf(result).toEqualTypeOf<number>();
50
+ });
51
+
52
+ it("nullable 컬럼 타입을 추출한다", () => {
53
+ type Tables = { users: MockSchema["users"] };
54
+ type Result = ExtractColumnType<Tables, "users.department_id">;
55
+
56
+ const result = {} as Result;
57
+ expectTypeOf(result).toEqualTypeOf<number | null>();
58
+ });
59
+
60
+ it("단일 테이블에서는 테이블명 없이 컬럼명만으로 추출 가능하다", () => {
61
+ type Tables = { users: MockSchema["users"] };
62
+ type Result = ExtractColumnType<Tables, "id">;
63
+
64
+ const result = {} as Result;
65
+ expectTypeOf(result).toEqualTypeOf<number>();
66
+ });
67
+ });
68
+
69
+ describe("innerJoin된 테이블", () => {
70
+ it("innerJoin 테이블의 컬럼은 non-null이다", () => {
71
+ type Tables = {
72
+ users: MockSchema["users"];
73
+ department: MockSchema["departments"]; // innerJoin
74
+ };
75
+ type Result = ExtractColumnType<Tables, "department.id">;
76
+
77
+ const result = {} as Result;
78
+ expectTypeOf(result).toEqualTypeOf<number>();
79
+ });
80
+ });
81
+
82
+ describe("leftJoin된 테이블", () => {
83
+ it("leftJoin 테이블의 컬럼은 nullable이다", () => {
84
+ type Tables = {
85
+ users: MockSchema["users"];
86
+ department: MockSchema["departments"] & LeftJoinedMarker; // leftJoin
87
+ };
88
+ type Result = ExtractColumnType<Tables, "department.id">;
89
+
90
+ const result = {} as Result;
91
+ expectTypeOf(result).toEqualTypeOf<number | null>();
92
+ });
93
+
94
+ it("leftJoin 테이블의 원래 nullable 컬럼도 nullable이다", () => {
95
+ type Tables = {
96
+ users: MockSchema["users"];
97
+ employee: MockSchema["employees"] & LeftJoinedMarker; // leftJoin
98
+ };
99
+ type Result = ExtractColumnType<Tables, "employee.salary">;
100
+
101
+ const result = {} as Result;
102
+ expectTypeOf(result).toEqualTypeOf<string | null>();
103
+ });
104
+ });
105
+
106
+ describe("non-null FK로 leftJoin된 테이블", () => {
107
+ it("non-null FK로 leftJoin된 테이블의 컬럼은 non-null이다 (마커 없음)", () => {
108
+ type Tables = {
109
+ users: MockSchema["users"];
110
+ department: MockSchema["departments"] & LeftJoinedMarker; // nullable FK
111
+ company: MockSchema["companies"] & LeftJoinedMarker; // non-null FK → 마커 없음
112
+ };
113
+ type Result = ExtractColumnType<Tables, "company.id">;
114
+
115
+ const result = {} as Result;
116
+ expectTypeOf(result).toEqualTypeOf<number>();
117
+ });
118
+ });
119
+ });
120
+
121
+ // ============================================================================
122
+ // ParseSelectObject 테스트
123
+ // ============================================================================
124
+
125
+ describe("ParseSelectObject", () => {
126
+ describe("단일 테이블 (flat select)", () => {
127
+ it("기본 필드를 파싱한다", () => {
128
+ type Tables = { users: MockSchema["users"] };
129
+ type Select = {
130
+ id: "users.id";
131
+ name: "users.name";
132
+ };
133
+ type Result = ParseSelectObject<Tables, Select>;
134
+
135
+ const result = {} as Result;
136
+ expectTypeOf(result).toEqualTypeOf<{
137
+ id: number;
138
+ name: string;
139
+ }>();
140
+ });
141
+
142
+ it("nullable 필드를 파싱한다", () => {
143
+ type Tables = { users: MockSchema["users"] };
144
+ type Select = {
145
+ id: "users.id";
146
+ department_id: "users.department_id";
147
+ };
148
+ type Result = ParseSelectObject<Tables, Select>;
149
+
150
+ const result = {} as Result;
151
+ expectTypeOf(result).toEqualTypeOf<{
152
+ id: number;
153
+ department_id: number | null;
154
+ }>();
155
+ });
156
+ });
157
+
158
+ describe("innerJoin + flat select", () => {
159
+ it("innerJoin 테이블의 필드는 non-null이다", () => {
160
+ type Tables = {
161
+ users: MockSchema["users"];
162
+ department: MockSchema["departments"]; // innerJoin
163
+ };
164
+ type Select = {
165
+ id: "users.id";
166
+ dept_id: "department.id";
167
+ dept_name: "department.name";
168
+ };
169
+ type Result = ParseSelectObject<Tables, Select>;
170
+
171
+ const result = {} as Result;
172
+ expectTypeOf(result).toEqualTypeOf<{
173
+ id: number;
174
+ dept_id: number;
175
+ dept_name: string;
176
+ }>();
177
+ });
178
+ });
179
+
180
+ describe("leftJoin + flat select", () => {
181
+ it("leftJoin 테이블의 필드는 nullable이다", () => {
182
+ type Tables = {
183
+ users: MockSchema["users"];
184
+ department: MockSchema["departments"] & LeftJoinedMarker; // leftJoin
185
+ };
186
+ type Select = {
187
+ id: "users.id";
188
+ dept_id: "department.id";
189
+ dept_name: "department.name";
190
+ };
191
+ type Result = ParseSelectObject<Tables, Select>;
192
+
193
+ const result = {} as Result;
194
+ expectTypeOf(result).toEqualTypeOf<{
195
+ id: number;
196
+ dept_id: number | null;
197
+ dept_name: string | null;
198
+ }>();
199
+ });
200
+ });
201
+
202
+ // ============================================================================
203
+ // 핵심: 입체적 select 구조
204
+ // ============================================================================
205
+
206
+ describe("innerJoin + nested select (입체적 구조)", () => {
207
+ it("innerJoin 테이블의 중첩 객체는 non-null이다", () => {
208
+ type Tables = {
209
+ users: MockSchema["users"];
210
+ department: MockSchema["departments"]; // innerJoin
211
+ };
212
+ type Select = {
213
+ id: "users.id";
214
+ department: {
215
+ id: "department.id";
216
+ name: "department.name";
217
+ };
218
+ };
219
+ type Result = ParseSelectObject<Tables, Select>;
220
+
221
+ const result = {} as Result;
222
+ expectTypeOf(result).toEqualTypeOf<{
223
+ id: number;
224
+ department: {
225
+ id: number;
226
+ name: string;
227
+ };
228
+ }>();
229
+ });
230
+ });
231
+
232
+ describe("leftJoin + nested select (입체적 구조)", () => {
233
+ it("leftJoin 테이블의 중첩 객체는 nullable이다 (필드는 non-null)", () => {
234
+ type Tables = {
235
+ users: MockSchema["users"];
236
+ department: MockSchema["departments"] & LeftJoinedMarker; // leftJoin
237
+ };
238
+ type Select = {
239
+ id: "users.id";
240
+ department: {
241
+ id: "department.id";
242
+ name: "department.name";
243
+ };
244
+ };
245
+ type Result = ParseSelectObject<Tables, Select>;
246
+
247
+ const result = {} as Result;
248
+ expectTypeOf(result).toEqualTypeOf<{
249
+ id: number;
250
+ department: {
251
+ id: number;
252
+ name: string;
253
+ } | null; // 객체 단위로 nullable
254
+ }>();
255
+ });
256
+
257
+ it("leftJoin 테이블 내의 원래 nullable 필드도 non-null이다", () => {
258
+ type Tables = {
259
+ users: MockSchema["users"];
260
+ employee: MockSchema["employees"] & LeftJoinedMarker; // leftJoin
261
+ };
262
+ type Select = {
263
+ id: "users.id";
264
+ employee: {
265
+ id: "employee.id";
266
+ salary: "employee.salary"; // 스키마상 nullable이지만...
267
+ };
268
+ };
269
+ type Result = ParseSelectObject<Tables, Select>;
270
+
271
+ const result = {} as Result;
272
+ // employee 객체가 null이 아닐 때만 접근하므로, salary의 원래 nullability 유지
273
+ expectTypeOf(result).toEqualTypeOf<{
274
+ id: number;
275
+ employee: {
276
+ id: number;
277
+ salary: string | null; // 스키마의 원래 nullability 유지
278
+ } | null;
279
+ }>();
280
+ });
281
+ });
282
+
283
+ describe("non-null FK leftJoin + nested select (입체적 구조)", () => {
284
+ it("non-null FK로 leftJoin된 테이블의 중첩 객체는 non-null이다", () => {
285
+ type Tables = {
286
+ users: MockSchema["users"];
287
+ department: MockSchema["departments"] & LeftJoinedMarker; // nullable FK → 마커 있음
288
+ department__company: MockSchema["companies"]; // non-null FK → 마커 없음
289
+ };
290
+ type Select = {
291
+ id: "users.id";
292
+ department: {
293
+ id: "department.id";
294
+ name: "department.name";
295
+ company: {
296
+ name: "department__company.name";
297
+ };
298
+ };
299
+ };
300
+ type Result = ParseSelectObject<Tables, Select>;
301
+
302
+ const result = {} as Result;
303
+ expectTypeOf(result).toEqualTypeOf<{
304
+ id: number;
305
+ department: {
306
+ id: number;
307
+ name: string;
308
+ company: {
309
+ name: string;
310
+ }; // non-null! (non-null FK로 조인되어 마커 없음)
311
+ } | null;
312
+ }>();
313
+ });
314
+
315
+ it("깊은 중첩에서도 non-null FK leftJoin은 non-null이다", () => {
316
+ type Tables = {
317
+ employees: MockSchema["employees"];
318
+ user: MockSchema["users"]; // innerJoin → 마커 없음
319
+ user__employee: MockSchema["employees"] & LeftJoinedMarker; // nullable FK
320
+ user__employee__department: MockSchema["departments"]; // non-null FK → 마커 없음
321
+ };
322
+ type Select = {
323
+ id: "employees.id";
324
+ user: {
325
+ id: "user.id";
326
+ employee: {
327
+ id: "user__employee.id";
328
+ department: {
329
+ id: "user__employee__department.id";
330
+ };
331
+ };
332
+ };
333
+ };
334
+ type Result = ParseSelectObject<Tables, Select>;
335
+
336
+ const result = {} as Result;
337
+ expectTypeOf(result).toEqualTypeOf<{
338
+ id: number;
339
+ user: {
340
+ // user는 innerJoin이므로 무조건 존재합니다.
341
+ id: number;
342
+ employee: {
343
+ // user의 employee는 nullable FK leftJoin이므로 null일 수 있습니다.
344
+ id: number;
345
+ department: {
346
+ // employee가 존재한다면 department는 non-null FK leftJoin이므로 무조건 존재합니다.
347
+ id: number;
348
+ }; // non-null FK → non-null
349
+ } | null; // nullable FK leftJoin → nullable
350
+ }; // innerJoin → non-null
351
+ }>();
352
+ });
353
+ });
354
+
355
+ describe("복합 케이스", () => {
356
+ it("innerJoin + nullable FK leftJoin + non-null FK leftJoin 조합", () => {
357
+ type Tables = {
358
+ employees: MockSchema["employees"];
359
+ user: MockSchema["users"]; // innerJoin (non-null FK)
360
+ department: MockSchema["departments"] & LeftJoinedMarker; // nullable FK leftJoin
361
+ department__company: MockSchema["companies"]; // non-null FK leftJoin → 마커 없음
362
+ };
363
+ type Select = {
364
+ id: "employees.id";
365
+ employee_number: "employees.employee_number";
366
+ salary: "employees.salary";
367
+ user: {
368
+ id: "user.id";
369
+ username: "user.name";
370
+ };
371
+ department: {
372
+ id: "department.id";
373
+ name: "department.name";
374
+ company: {
375
+ name: "department__company.name";
376
+ };
377
+ };
378
+ };
379
+ type Result = ParseSelectObject<Tables, Select>;
380
+
381
+ const result = {} as Result;
382
+ expectTypeOf(result).toEqualTypeOf<{
383
+ id: number;
384
+ employee_number: string;
385
+ salary: string | null; // 스키마상 nullable
386
+ user: {
387
+ id: number;
388
+ username: string;
389
+ }; // innerJoin → non-null
390
+ department: {
391
+ id: number;
392
+ name: string;
393
+ company: {
394
+ name: string;
395
+ }; // non-null FK → non-null
396
+ } | null; // nullable FK leftJoin → nullable
397
+ }>();
398
+ });
399
+
400
+ it("여러 leftJoin 관계", () => {
401
+ type Tables = {
402
+ employees: MockSchema["employees"];
403
+ department: MockSchema["departments"] & LeftJoinedMarker;
404
+ manager: MockSchema["users"] & LeftJoinedMarker;
405
+ };
406
+ type Select = {
407
+ id: "employees.id";
408
+ department: {
409
+ name: "department.name";
410
+ };
411
+ manager: {
412
+ name: "manager.name";
413
+ };
414
+ };
415
+ type Result = ParseSelectObject<Tables, Select>;
416
+
417
+ const result = {} as Result;
418
+ expectTypeOf(result).toEqualTypeOf<{
419
+ id: number;
420
+ department: { name: string } | null;
421
+ manager: { name: string } | null;
422
+ }>();
423
+ });
424
+ });
425
+ });
426
+
427
+ // ============================================================================
428
+ // AvailableColumns 테스트
429
+ // ============================================================================
430
+
431
+ describe("AvailableColumns", () => {
432
+ it("단일 테이블에서 사용 가능한 컬럼을 추출한다", () => {
433
+ type Tables = { users: MockSchema["users"] };
434
+ type Result = AvailableColumns<Tables>;
435
+
436
+ // "users.id" | "users.name" | "users.email" | "users.department_id" | "id" | "name" | "email" | "department_id"
437
+ const valid1: Result = "users.id";
438
+ const valid2: Result = "id"; // 단일 테이블이면 테이블명 생략 가능
439
+
440
+ expectTypeOf(valid1).toExtend<Result>();
441
+ expectTypeOf(valid2).toExtend<Result>();
442
+ });
443
+
444
+ it("여러 테이블에서 사용 가능한 컬럼을 추출한다", () => {
445
+ type Tables = {
446
+ users: MockSchema["users"];
447
+ department: MockSchema["departments"];
448
+ };
449
+ type Result = AvailableColumns<Tables>;
450
+
451
+ const valid1: Result = "users.id";
452
+ const valid2: Result = "department.name";
453
+
454
+ expectTypeOf(valid1).toExtend<Result>();
455
+ expectTypeOf(valid2).toExtend<Result>();
456
+ });
457
+ });