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
@@ -117,9 +117,9 @@ export class Entity {
117
117
  };
118
118
  }
119
119
 
120
- /*
121
- subset을 Puri 코드로 변환
122
- */
120
+ /**
121
+ * 주어진 이름(subsetKey)의 subset을 실제로 가져오는 Puri 코드 구현체 string을 반환합니다.
122
+ */
123
123
  getPuriSubsetQuery(subsetKey: string): string {
124
124
  const subset = this.subsets[subsetKey];
125
125
  const subsetQuery = this.resolveSubsetQuery("", subset);
@@ -132,6 +132,8 @@ export class Entity {
132
132
 
133
133
  // join
134
134
  for (const join of subsetQuery.joins) {
135
+ // join 메서드 결정: inner → join, outer → leftJoin
136
+ // FK nullable 여부는 leftJoin 타입 시그니처에서 자동으로 판단됨
135
137
  const joinMethod = join.join === "inner" ? "join" : "leftJoin";
136
138
 
137
139
  if ("custom" in join) {
@@ -144,25 +146,120 @@ export class Entity {
144
146
  }
145
147
  }
146
148
 
147
- // select
148
- const selectObj: Record<string, string> = {};
149
- for (const selectItem of subsetQuery.select) {
150
- // "users.id" 또는 "users.id as user__id" 형태
149
+ // select - 입체적 구조로 생성
150
+ const selectObj = this.buildNestedSelectObject(subsetQuery.select);
151
+
152
+ lines.push(`.select(${this.stringifyNestedSelectObject(selectObj)});`);
153
+
154
+ return lines.join("\n");
155
+ }
156
+
157
+ /**
158
+ * *.entity.json의 subset에 들어있는 필드 배열을 받아서,
159
+ * Puri의 SelectObject 타입으로 변환합니다.
160
+ *
161
+ * 예: ["users.id", "parent.id", "parent.name"]
162
+ * → { id: "users.id", parent: { id: "parent.id", name: "parent.name" } }
163
+ *
164
+ * 언더바가 아닌 중첩 객체로 변환함에 유의하세요.
165
+ * 이렇게 중첩 객체로 변환하여 select에 넘겨주면 ParseSelectObject 타입이 join된 객체의 타입을 잘 잡아줄 수 있습니다.
166
+ * 즉, enhancer에서 row를 받았을 때 hydrate된 객체 자체의 nullity와 그 안쪽 필드의 nullity가 fk nullable 여부에 따라 잘 추론됩니다.
167
+ */
168
+ private buildNestedSelectObject(
169
+ selectItems: string[],
170
+ // biome-ignore lint/suspicious/noExplicitAny: 반환 오브젝트의 값은 string일 수도 있고 또다른 오브젝트일 수도 있는데, 이를 재귀 타입으로 나타낼 수 없어 any로 처리합니다.
171
+ ): Record<string, any> {
172
+ const result: ReturnType<typeof this.buildNestedSelectObject> = {};
173
+
174
+ for (const selectItem of selectItems) {
175
+ // "users.id" 또는 "users.id as user__id" 형태 파싱
151
176
  const match = selectItem.match(/^(.+?)(?: as (.+))?$/);
152
- if (match) {
153
- const [, column, alias] = match;
177
+ if (!match) continue;
178
+
179
+ const [, column, alias] = match;
180
+ const columnValue = `"${column.trim()}"`;
181
+
182
+ if (!alias || !alias.includes("__")) {
183
+ // alias가 없거나 __를 포함하지 않으면 최상위 필드
154
184
  const key = alias ?? assertDefined(column.split(".").pop());
155
- selectObj[key] = `"${column.trim()}"`;
185
+ result[key] = columnValue;
186
+ } else {
187
+ // alias가 __를 포함하면 입체 구조로 그룹화
188
+ const parts = alias.split("__");
189
+ let current = result;
190
+
191
+ // 마지막 파트 전까지 중첩 객체 생성
192
+ for (let i = 0; i < parts.length - 1; i++) {
193
+ const part = parts[i];
194
+ if (part in current) {
195
+ if (typeof current[part] === "string") {
196
+ // 입력이 ["user", "user__id"] 같은 경우!
197
+ // 애초에 말도 안 되지만 안전하게 예외를 던집니다.
198
+ throw new Error(
199
+ `Conflict detected in select items: parent path "${parts.slice(0, i + 1).join("__")}" is already set as a field, cannot nest "${alias}" under it.`,
200
+ );
201
+ }
202
+ } else {
203
+ current[part] = {};
204
+ }
205
+ current = current[part];
206
+ }
207
+
208
+ // 마지막 파트에 값 설정
209
+ const lastPart = parts[parts.length - 1];
210
+ current[lastPart] = columnValue;
156
211
  }
157
212
  }
158
213
 
159
- lines.push(`.select({`);
160
- Object.entries(selectObj).forEach(([key, value]) => {
161
- lines.push(`${key}: ${value},`);
214
+ return result;
215
+ }
216
+
217
+ /**
218
+ * JSON.stringify와 유사한 일을 합니다.
219
+ * 다만 주어진 객체를 JSON이 아닌 TypeScript 객체 리터럴 스트링으로 만들어줍니다.
220
+ * key에 따옴표가 없어요.
221
+ * 출력 예시:
222
+ * ```typescript
223
+ * {
224
+ * id: "users.id",
225
+ * parent: {
226
+ * id: "parent.id",
227
+ * name: "parent.name",
228
+ * },
229
+ * }
230
+ * ```
231
+ * @param obj 변환할 객체
232
+ * @param indent 들여쓰기 레벨
233
+ * @param withBraces true면 중괄호 포함, false면 내용만 반환
234
+ */
235
+ private stringifyNestedSelectObject(
236
+ // biome-ignore lint/suspicious/noExplicitAny: 중첩 오브젝트의 값은 string일 수도 있고 또다른 오브젝트일 수도 있는데, 이를 재귀 타입으로 나타낼 수 없어 any로 처리합니다.
237
+ obj: Record<string, any>,
238
+ indent: number = 0,
239
+ withBraces: boolean = true,
240
+ ): string {
241
+ const spaces = " ".repeat(indent);
242
+ const innerSpaces = " ".repeat(indent + 1);
243
+
244
+ const entries = Object.entries(obj);
245
+ if (entries.length === 0) return withBraces ? "{}" : "";
246
+
247
+ const lines = entries.map(([key, value]) => {
248
+ if (typeof value === "string") {
249
+ // 컬럼 경로 (이미 따옴표 포함)
250
+ return `${innerSpaces}${key}: ${value},`;
251
+ } else {
252
+ // 중첩 객체 (항상 중괄호 포함)
253
+ return `${innerSpaces}${key}: ${this.stringifyNestedSelectObject(value, indent + 1, true)},`;
254
+ }
162
255
  });
163
- lines.push(`});`);
164
256
 
165
- return lines.join("\n");
257
+ if (withBraces) {
258
+ return `{\n${lines.join("\n")}\n${spaces}}`;
259
+ } else {
260
+ // 중괄호 없이 내용만 반환 (앞뒤 개행 제외)
261
+ return lines.join("\n");
262
+ }
166
263
  }
167
264
 
168
265
  getPuriLoaderQuery(subsetKey: string): string {
@@ -171,19 +268,6 @@ export class Entity {
171
268
 
172
269
  const lines: string[] = [`[`];
173
270
 
174
- const parseSelect = (select: string, table: string) => {
175
- const tablePrefix = `${table}.`;
176
- if (select.startsWith(tablePrefix)) {
177
- return `${select.replace(tablePrefix, "")}: "${select}"`;
178
- }
179
-
180
- if (select.includes(" as ")) {
181
- const [column, alias] = select.split(" as ");
182
- return `${alias}: "${column}"`;
183
- }
184
- return `${select}: "${select}"`;
185
- };
186
-
187
271
  // 재귀적으로 loader 생성하는 헬퍼 함수
188
272
  const generateLoaderCode = (loaders: SubsetQuery["loaders"]): string[] => {
189
273
  const loaderLines: string[] = [];
@@ -206,27 +290,28 @@ export class Entity {
206
290
  );
207
291
 
208
292
  loader.oneJoins.forEach((join: SubsetQuery["joins"][number]) => {
209
- const joinType = join.join === "inner" ? "join" : "leftJoin";
293
+ // FK nullable 여부는 leftJoin 타입 시그니처에서 자동으로 판단됨
294
+ const joinMethod = join.join === "inner" ? "join" : "leftJoin";
210
295
  if ("custom" in join) {
211
296
  // FIXME: 검증 필요
212
297
  loaderLines.push(
213
- `.${joinType}({ ${join.as}: "${join.table}" }, (j) => {`,
298
+ `.${joinMethod}({ ${join.as}: "${join.table}" }, (j) => {`,
214
299
  `j.on(Puri.rawString("${join.custom}"));`,
215
300
  "})",
216
301
  );
217
302
  } else {
218
303
  loaderLines.push(
219
- `.${joinType}({ ${join.as}: "${join.table}" }, "${join.from}", "${join.to}")`,
304
+ `.${joinMethod}({ ${join.as}: "${join.table}" }, "${join.from}", "${join.to}")`,
220
305
  );
221
306
  }
222
307
  });
223
308
 
309
+ // 입체적 select 구조 생성 (refId 포함)
310
+ const selectObj = this.buildNestedSelectObject(loader.select);
311
+ selectObj.refId = `"${toTable}.${toCol}"`;
224
312
  loaderLines.push(
225
313
  `.whereIn("${toTable}.${toCol}", fromIds)`,
226
- `.select({`,
227
- `${loader.select.map((select: string) => parseSelect(select, toTable)).join(",")},`,
228
- `refId: "${toTable}.${toCol}",`,
229
- `});`,
314
+ `.select(${this.stringifyNestedSelectObject(selectObj)});`,
230
315
  );
231
316
  } else {
232
317
  // ManyToMany
@@ -237,26 +322,28 @@ export class Entity {
237
322
  );
238
323
 
239
324
  loader.oneJoins.forEach((join: SubsetQuery["joins"][number]) => {
240
- const joinType = join.join === "inner" ? "join" : "leftJoin";
325
+ // FK nullable 여부는 leftJoin 타입 시그니처에서 자동으로 판단됨
326
+ const joinMethod = join.join === "inner" ? "join" : "leftJoin";
241
327
  if ("custom" in join) {
242
328
  // FIXME: 검증 필요
243
329
  loaderLines.push(
244
- `.${joinType}({ ${join.as}: "${join.table}" }, (j) => {`,
330
+ `.${joinMethod}({ ${join.as}: "${join.table}" }, (j) => {`,
245
331
  `j.on(Puri.rawString("${join.custom}"));`,
246
332
  "})",
247
333
  );
248
334
  } else {
249
335
  loaderLines.push(
250
- `.${joinType}({ ${join.as}: "${join.table}" }, "${join.from}", "${join.to}")`,
336
+ `.${joinMethod}({ ${join.as}: "${join.table}" }, "${join.from}", "${join.to}")`,
251
337
  );
252
338
  }
253
339
  });
340
+
341
+ // 입체적 select 구조 생성 (refId 포함)
342
+ const selectObj = this.buildNestedSelectObject(loader.select);
343
+ selectObj.refId = `"${through.table}.${through.fromCol}"`;
254
344
  loaderLines.push(
255
345
  `.whereIn("${through.table}.${through.fromCol}", fromIds)`,
256
- `.select({`,
257
- `${loader.select.map((select: string) => parseSelect(select, toTable)).join(",")},`,
258
- `refId: "${through.table}.${through.fromCol}",`,
259
- `});`,
346
+ `.select(${this.stringifyNestedSelectObject(selectObj)});`,
260
347
  );
261
348
  }
262
349
 
@@ -675,7 +762,10 @@ export class Entity {
675
762
  }
676
763
 
677
764
  registerTableSpecs(): void {
678
- const uniqueIndexes = this.indexes.filter((idx) => idx.type === "unique");
765
+ // 조인 테이블 인덱스 제외 (컬럼 이름에 '.'이 포함된 경우)
766
+ const uniqueIndexes = this.indexes
767
+ .filter((idx) => idx.type === "unique")
768
+ .filter((idx) => idx.columns.every((col) => !col.name.includes(".")));
679
769
 
680
770
  EntityManager.setTableSpec({
681
771
  name: this.table,
@@ -904,7 +994,7 @@ export class Entity {
904
994
 
905
995
  // 현재 엔티티의 인덱스에서 제외
906
996
  for (const index of EntityManager.get(this.id).indexes) {
907
- index.columns = index.columns.filter((col) => col !== oldName);
997
+ index.columns = index.columns.filter((col) => col.name !== oldName);
908
998
  }
909
999
 
910
1000
  // 프롭 삭제
package/src/index.ts CHANGED
@@ -30,6 +30,12 @@ export * from "./utils/controller";
30
30
  export * from "./utils/model";
31
31
  export * from "./utils/type-utils";
32
32
  export * from "./utils/utils";
33
+ // Vector (pgvector integration)
34
+ export * from "./vector/chunking";
35
+ export * from "./vector/config";
36
+ export * from "./vector/embedding";
37
+ export * from "./vector/types";
38
+ export * from "./vector/vector-search";
33
39
 
34
40
  // export * from "./api/code-converters";
35
41
  // export * from "./syncer/syncer";