sonamu 0.7.11 → 0.7.13

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 (153) hide show
  1. package/dist/api/config.d.ts +10 -6
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +2 -1
  4. package/dist/api/sonamu.d.ts +4 -0
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +49 -5
  7. package/dist/bin/cli.js +118 -170
  8. package/dist/database/base-model.d.ts +10 -50
  9. package/dist/database/base-model.d.ts.map +1 -1
  10. package/dist/database/base-model.js +19 -84
  11. package/dist/database/base-model.types.d.ts +4 -4
  12. package/dist/database/base-model.types.d.ts.map +1 -1
  13. package/dist/database/base-model.types.js +1 -1
  14. package/dist/database/db.d.ts +1 -0
  15. package/dist/database/db.d.ts.map +1 -1
  16. package/dist/database/db.js +24 -13
  17. package/dist/database/puri-subset.test-d.js +1 -1
  18. package/dist/database/puri-subset.types.d.ts +1 -0
  19. package/dist/database/puri-subset.types.d.ts.map +1 -1
  20. package/dist/database/puri-subset.types.js +2 -2
  21. package/dist/database/puri.d.ts +82 -3
  22. package/dist/database/puri.d.ts.map +1 -1
  23. package/dist/database/puri.js +180 -14
  24. package/dist/database/puri.types.d.ts +33 -6
  25. package/dist/database/puri.types.d.ts.map +1 -1
  26. package/dist/database/puri.types.js +1 -1
  27. package/dist/database/puri.types.test-d.js +1 -1
  28. package/dist/entity/entity-manager.d.ts +5 -4
  29. package/dist/entity/entity-manager.d.ts.map +1 -1
  30. package/dist/entity/entity-manager.js +8 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/migration/code-generation.d.ts.map +1 -1
  35. package/dist/migration/code-generation.js +33 -2
  36. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  37. package/dist/migration/postgresql-schema-reader.js +53 -22
  38. package/dist/naite/messaging-types.d.ts.map +1 -1
  39. package/dist/naite/messaging-types.js +1 -1
  40. package/dist/naite/naite.js +2 -2
  41. package/dist/stream/sse.d.ts +2 -6
  42. package/dist/stream/sse.d.ts.map +1 -1
  43. package/dist/stream/sse.js +9 -3
  44. package/dist/syncer/api-parser.d.ts.map +1 -1
  45. package/dist/syncer/api-parser.js +7 -2
  46. package/dist/syncer/file-patterns.d.ts +1 -1
  47. package/dist/syncer/file-patterns.d.ts.map +1 -1
  48. package/dist/syncer/file-patterns.js +6 -5
  49. package/dist/syncer/module-loader.d.ts +5 -0
  50. package/dist/syncer/module-loader.d.ts.map +1 -1
  51. package/dist/syncer/module-loader.js +17 -1
  52. package/dist/syncer/syncer.d.ts +5 -1
  53. package/dist/syncer/syncer.d.ts.map +1 -1
  54. package/dist/syncer/syncer.js +28 -19
  55. package/dist/tasks/decorator.d.ts +26 -0
  56. package/dist/tasks/decorator.d.ts.map +1 -0
  57. package/dist/tasks/decorator.js +28 -0
  58. package/dist/tasks/step-wrapper.d.ts +18 -0
  59. package/dist/tasks/step-wrapper.d.ts.map +1 -0
  60. package/dist/tasks/step-wrapper.js +38 -0
  61. package/dist/tasks/workflow-manager.d.ts +40 -0
  62. package/dist/tasks/workflow-manager.d.ts.map +1 -0
  63. package/dist/tasks/workflow-manager.js +193 -0
  64. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  65. package/dist/template/implementations/generated.template.js +7 -3
  66. package/dist/types/types.d.ts +26 -10
  67. package/dist/types/types.d.ts.map +1 -1
  68. package/dist/types/types.js +15 -2
  69. package/dist/ui/ai-api.d.ts +1 -0
  70. package/dist/ui/ai-api.d.ts.map +1 -0
  71. package/dist/ui/ai-api.js +50 -0
  72. package/dist/ui/ai-client.d.ts +1 -0
  73. package/dist/ui/ai-client.d.ts.map +1 -0
  74. package/dist/ui/ai-client.js +438 -0
  75. package/dist/ui/api.d.ts +3 -0
  76. package/dist/ui/api.d.ts.map +1 -0
  77. package/dist/ui/api.js +680 -0
  78. package/dist/ui-web/assets/brand-icons-Cu_C0hZ4.svg +1008 -0
  79. package/dist/ui-web/assets/brand-icons-F3SPCeH1.woff +0 -0
  80. package/dist/ui-web/assets/brand-icons-XL9sxUpA.woff2 +0 -0
  81. package/dist/ui-web/assets/brand-icons-sqJ2Pg7a.eot +0 -0
  82. package/dist/ui-web/assets/brand-icons-ubhWoxly.ttf +0 -0
  83. package/dist/ui-web/assets/flags-DOLqOU7Y.png +0 -0
  84. package/dist/ui-web/assets/icons-BOCtAERH.woff +0 -0
  85. package/dist/ui-web/assets/icons-CHzK1VD9.eot +0 -0
  86. package/dist/ui-web/assets/icons-D29ZQHHw.ttf +0 -0
  87. package/dist/ui-web/assets/icons-Du6TOHnR.woff2 +0 -0
  88. package/dist/ui-web/assets/icons-RwhydX30.svg +1518 -0
  89. package/dist/ui-web/assets/index-CpaB9P6g.css +1 -0
  90. package/dist/ui-web/assets/index-J9MCfjCd.js +95 -0
  91. package/dist/ui-web/assets/outline-icons-BfdLr8tr.svg +366 -0
  92. package/dist/ui-web/assets/outline-icons-DD8jm0uy.ttf +0 -0
  93. package/dist/ui-web/assets/outline-icons-DInHoiqI.woff2 +0 -0
  94. package/dist/ui-web/assets/outline-icons-LX8adJ4n.eot +0 -0
  95. package/dist/ui-web/assets/outline-icons-aQ88nltS.woff +0 -0
  96. package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +1 -0
  97. package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +1 -0
  98. package/dist/ui-web/index.html +13 -0
  99. package/dist/ui-web/vite.svg +1 -0
  100. package/dist/utils/formatter.d.ts.map +1 -1
  101. package/dist/utils/formatter.js +10 -2
  102. package/dist/utils/model.d.ts +9 -2
  103. package/dist/utils/model.d.ts.map +1 -1
  104. package/dist/utils/model.js +16 -1
  105. package/dist/utils/type-utils.d.ts.map +1 -1
  106. package/dist/utils/type-utils.js +3 -1
  107. package/dist/vector/embedding.d.ts +2 -5
  108. package/dist/vector/embedding.d.ts.map +1 -1
  109. package/dist/vector/embedding.js +9 -13
  110. package/dist/vector/types.d.ts.map +1 -1
  111. package/dist/vector/types.js +1 -1
  112. package/package.json +9 -5
  113. package/src/api/config.ts +15 -11
  114. package/src/api/sonamu.ts +60 -6
  115. package/src/bin/cli.ts +57 -119
  116. package/src/database/base-model.ts +21 -128
  117. package/src/database/base-model.types.ts +3 -4
  118. package/src/database/db.ts +28 -18
  119. package/src/database/puri-subset.test-d.ts +1 -0
  120. package/src/database/puri-subset.types.ts +2 -0
  121. package/src/database/puri.ts +238 -27
  122. package/src/database/puri.types.test-d.ts +1 -1
  123. package/src/database/puri.types.ts +49 -6
  124. package/src/entity/entity-manager.ts +9 -0
  125. package/src/index.ts +1 -1
  126. package/src/migration/code-generation.ts +40 -1
  127. package/src/migration/postgresql-schema-reader.ts +53 -22
  128. package/src/naite/messaging-types.ts +43 -44
  129. package/src/naite/naite.ts +1 -1
  130. package/src/shared/app.shared.ts.txt +13 -0
  131. package/src/shared/web.shared.ts.txt +13 -0
  132. package/src/stream/sse.ts +15 -3
  133. package/src/syncer/api-parser.ts +6 -1
  134. package/src/syncer/file-patterns.ts +11 -9
  135. package/src/syncer/module-loader.ts +35 -0
  136. package/src/syncer/syncer.ts +34 -21
  137. package/src/tasks/decorator.ts +71 -0
  138. package/src/tasks/step-wrapper.ts +84 -0
  139. package/src/tasks/workflow-manager.ts +330 -0
  140. package/src/template/implementations/generated.template.ts +19 -6
  141. package/src/types/types.ts +20 -4
  142. package/src/ui/ai-api.ts +60 -0
  143. package/src/ui/ai-client.ts +499 -0
  144. package/src/ui/api.ts +786 -0
  145. package/src/utils/formatter.ts +8 -1
  146. package/src/utils/model.ts +26 -2
  147. package/src/utils/type-utils.ts +2 -0
  148. package/src/vector/embedding.ts +10 -14
  149. package/src/vector/types.ts +1 -2
  150. package/dist/vector/vector-search.d.ts +0 -47
  151. package/dist/vector/vector-search.d.ts.map +0 -1
  152. package/dist/vector/vector-search.js +0 -176
  153. package/src/vector/vector-search.ts +0 -261
@@ -0,0 +1,499 @@
1
+ // import { anthropic } from "@ai-sdk/anthropic";
2
+ // import { type ModelMessage, stepCountIs, streamText, tool } from "ai";
3
+ // import assert from "assert";
4
+ // import fs from "fs";
5
+ // import path from "path";
6
+ // import {
7
+ // EntityManager,
8
+ // type EntityProp,
9
+ // type FixtureRecord,
10
+ // nonNullable,
11
+ // Sonamu,
12
+ // TemplateOptions,
13
+ // } from "sonamu";
14
+ // import { z } from "zod";
15
+
16
+ // type ValidationError = {
17
+ // field: string;
18
+ // message: string;
19
+ // };
20
+
21
+ // class AIClient {
22
+ // private model = anthropic("claude-sonnet-4-5");
23
+
24
+ // async init() {
25
+ // console.log("AI client initialized with AI SDK");
26
+ // }
27
+
28
+ // handleFixture(messages: ModelMessage[], fixtureRecords: FixtureRecord[]) {
29
+ // // 현재 fixtureRecords에서 사용된 엔티티들의 구조 정보 수집
30
+ // const usedEntityIds = [...new Set(fixtureRecords.map((r) => r.entityId))];
31
+ // const entityStructures = usedEntityIds.map((entityId) => {
32
+ // const entity = EntityManager.get(entityId);
33
+
34
+ // return {
35
+ // entityId: entity.id,
36
+ // table: entity.table,
37
+ // props: entity.props,
38
+ // relations: entity.relations,
39
+ // enumLabels: entity.enumLabels,
40
+ // };
41
+ // });
42
+
43
+ // const systemMessage = `
44
+ // 당신은 픽스쳐 레코드를 수정하고 생성할 수 있는 도우미입니다.
45
+
46
+ // 현재 픽스쳐 레코드:
47
+ // ${JSON.stringify(fixtureRecords, null, 2)}
48
+
49
+ // 엔티티 구조 정보:
50
+ // ${JSON.stringify(entityStructures, null, 2)}
51
+
52
+ // ## 픽스쳐 수정
53
+ // 사용자가 픽스쳐 값 수정을 요청하면 updateFixtures 도구를 사용하여 변경사항을 적용하세요.
54
+ // - fixtureId: 수정할 픽스쳐 ID (형식: "EntityId#id")
55
+ // - updates: 컬럼명을 키로, 새 값을 값으로 하는 객체
56
+
57
+ // 예시: "User#1" 픽스쳐의 "name" 컬럼을 "홍길동"으로 변경하려면:
58
+ // updateFixtures({ updates: [{ fixtureId: "User#1", updates: { name: "홍길동" } }] })
59
+
60
+ // 변경될 컬럼의 type이 relation인 경우, 관련 엔티티에도 반영되어야 할 컬럼이 있는지 확인하세요.
61
+
62
+ // ## 픽스쳐 생성
63
+ // 사용자가 새로운 픽스쳐 생성을 요청하면 createFixtures 도구를 사용하세요.
64
+ // - entityId: 생성할 엔티티 ID
65
+ // - id: 새 레코드의 ID (기존 픽스쳐와 중복되지 않는 음수 사용 권장, 예: -1, -2)
66
+ // - columns: 컬럼명을 키로, 값을 값으로 하는 객체 (엔티티 구조 참고)
67
+
68
+ // 예시: 새로운 User 픽스쳐를 생성하려면:
69
+ // createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
70
+ // `;
71
+
72
+ // return streamText({
73
+ // model: this.model,
74
+ // system: systemMessage,
75
+ // messages,
76
+ // tools: {
77
+ // updateFixtures: tool({
78
+ // description:
79
+ // "픽스쳐 레코드의 값을 수정합니다. 사용자가 특정 컬럼이나 값을 변경해달라고 요청할 때 사용하세요.",
80
+ // inputSchema: z.object({
81
+ // updates: z.array(
82
+ // z.object({
83
+ // fixtureId: z.string().describe("수정할 픽스쳐 ID (형식: EntityId#id)"),
84
+ // updates: z
85
+ // .record(z.string(), z.unknown())
86
+ // .describe("컬럼명을 키로, 새 값을 값으로 하는 객체"),
87
+ // }),
88
+ // ),
89
+ // }),
90
+ // execute: async ({
91
+ // updates,
92
+ // }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
93
+ // // fixtureRecords를 복사하고 업데이트 적용
94
+ // const updatedRecords: FixtureRecord[] = fixtureRecords.map((record) => {
95
+ // const update = updates.find((u) => u.fixtureId === record.fixtureId);
96
+ // if (update) {
97
+ // // columns의 value를 업데이트
98
+ // for (const [columnName, newValue] of Object.entries(update.updates)) {
99
+ // record.columns[columnName].value =
100
+ // newValue as FixtureRecord["columns"][string]["value"];
101
+ // }
102
+ // return record;
103
+ // }
104
+
105
+ // return record;
106
+ // });
107
+
108
+ // return { success: true, updatedRecords };
109
+ // },
110
+ // }),
111
+ // createFixtures: tool({
112
+ // description:
113
+ // "새로운 픽스쳐 레코드를 생성합니다. 사용자가 새로운 데이터를 추가해달라고 요청할 때 사용하세요.",
114
+ // inputSchema: z.object({
115
+ // fixtures: z.array(
116
+ // z.object({
117
+ // entityId: z.string().describe("생성할 엔티티 ID"),
118
+ // id: z.number().describe("새 레코드의 ID (음수 권장, 예: -1, -2)"),
119
+ // columns: z
120
+ // .record(z.string(), z.unknown())
121
+ // .describe("컬럼명을 키로, 값을 값으로 하는 객체"),
122
+ // }),
123
+ // ),
124
+ // }),
125
+ // execute: async ({
126
+ // fixtures,
127
+ // }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
128
+ // const newRecords: FixtureRecord[] = fixtures.map((fixture) => {
129
+ // const entity = EntityManager.get(fixture.entityId);
130
+
131
+ // // 엔티티 props를 기반으로 columns 구성
132
+ // const columns: FixtureRecord["columns"] = {};
133
+ // for (const prop of entity.props) {
134
+ // if (prop.type === "virtual") continue;
135
+
136
+ // let value = fixture.columns[prop.name] ?? null;
137
+
138
+ // if (prop.name === "created_at") {
139
+ // // 현재 시간으로 설정
140
+ // value = new Date().toISOString();
141
+ // } else if (
142
+ // prop.type === "relation" &&
143
+ // (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")
144
+ // ) {
145
+ // // 배열로 변환
146
+ // value = Array.isArray(value) ? value : [value].filter(nonNullable);
147
+ // }
148
+
149
+ // columns[prop.name] = {
150
+ // prop,
151
+ // value: value as FixtureRecord["columns"][string]["value"],
152
+ // };
153
+ // }
154
+
155
+ // return {
156
+ // fixtureId: `${fixture.entityId}#${fixture.id}`,
157
+ // entityId: fixture.entityId,
158
+ // id: fixture.id,
159
+ // columns,
160
+ // fetchedRecords: [],
161
+ // belongsRecords: [],
162
+ // override: false,
163
+ // };
164
+ // });
165
+
166
+ // // 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
167
+ // for (const newRecord of newRecords) {
168
+ // for (const [_colName, col] of Object.entries(newRecord.columns)) {
169
+ // if (col.prop.type !== "relation" || col.value === null) continue;
170
+
171
+ // const relatedEntityId = col.prop.with;
172
+ // const relatedIds = Array.isArray(col.value) ? col.value : [col.value];
173
+
174
+ // for (const relatedId of relatedIds) {
175
+ // const relatedFixtureId = `${relatedEntityId}#${relatedId}`;
176
+ // const relatedRecord = newRecords.find((r) => r.fixtureId === relatedFixtureId);
177
+
178
+ // if (relatedRecord) {
179
+ // // 역방향 relation 찾기
180
+ // const reverseCol = Object.entries(relatedRecord.columns).find(
181
+ // ([, c]) => c.prop.type === "relation" && c.prop.with === newRecord.entityId,
182
+ // );
183
+
184
+ // if (reverseCol) {
185
+ // const [reverseColName, reverseColValue] = reverseCol;
186
+ // const currentValue = reverseColValue.value;
187
+
188
+ // // 역방향이 배열인 경우 (HasMany, ManyToMany)
189
+ // if (
190
+ // reverseColValue.prop.type === "relation" &&
191
+ // (reverseColValue.prop.relationType === "HasMany" ||
192
+ // reverseColValue.prop.relationType === "ManyToMany")
193
+ // ) {
194
+ // assert(Array.isArray(currentValue), "currentValue must be an array");
195
+ // if (!currentValue.includes(newRecord.id)) {
196
+ // relatedRecord.columns[reverseColName] = {
197
+ // ...reverseColValue,
198
+ // value: [...currentValue, newRecord.id],
199
+ // };
200
+ // }
201
+ // } else {
202
+ // // 역방향이 단일 값인 경우 (BelongsToOne, OneToOne)
203
+ // relatedRecord.columns[reverseColName] = {
204
+ // ...reverseColValue,
205
+ // value: newRecord.id,
206
+ // };
207
+ // }
208
+ // }
209
+ // }
210
+ // }
211
+ // }
212
+ // }
213
+
214
+ // return { success: true, updatedRecords: newRecords };
215
+ // },
216
+ // }),
217
+ // },
218
+ // });
219
+ // }
220
+
221
+ // handleEntity(messages: ModelMessage[]) {
222
+ // // entity.instructions.md 파일 읽기
223
+ // const instructionsPath = path.join(import.meta.dirname, "..", "entity.instructions.md");
224
+ // const instructions = fs.readFileSync(instructionsPath, "utf-8");
225
+
226
+ // // 현재 등록된 엔티티 정보 수집
227
+ // const entityIds = EntityManager.getAllIds();
228
+ // const existingEntities = entityIds.map((entityId) => {
229
+ // const entity = EntityManager.get(entityId);
230
+ // return {
231
+ // id: entity.id,
232
+ // title: entity.title,
233
+ // table: entity.table,
234
+ // props: entity.props.map((p) => ({
235
+ // name: p.name,
236
+ // type: p.type,
237
+ // desc: p.desc,
238
+ // })),
239
+ // };
240
+ // });
241
+
242
+ // const systemMessage = `
243
+ // 당신은 Sonamu 프레임워크에서 Entity와 Enum을 생성하는 도우미입니다.
244
+
245
+ // ${instructions}
246
+
247
+ // ## 현재 등록된 Entity 목록
248
+ // 다른 엔티티와 관계(relation)를 맺거나 subset에서 참조할 때 반드시 아래 정보를 확인하세요.
249
+
250
+ // ${JSON.stringify(existingEntities, null, 2)}
251
+
252
+ // ## Tool 사용 가이드
253
+
254
+ // ### Entity 생성 (createEntity)
255
+ // 사용자가 새로운 Entity 생성을 요청하면 createEntity 도구를 사용하세요.
256
+ // - entityId: PascalCase로 된 Entity ID (예: "User", "ProductCategory")
257
+ // - title: 한글 제목 (예: "사용자", "상품 카테고리")
258
+ // - table: snake_case로 된 테이블명 (예: "users", "product_categories")
259
+ // - parentId: 부모 Entity ID (선택사항)
260
+ // - props: Entity의 프로퍼티 배열 (위 문서의 Property Types 참고)
261
+ // - indexes: 인덱스 배열
262
+ // - subsets: 서브셋 정의 (기본값: { A: ["id"] })
263
+ // - enums: Enum 정의
264
+
265
+ // ### Entity 수정 (updateEntity)
266
+ // 기존 Entity를 수정할 때 updateEntity 도구를 사용하세요. Enum 추가, props 추가/수정, indexes 수정 등 모든 수정 작업에 사용합니다.
267
+ // - entityId: 수정할 Entity ID
268
+ // - updates: 수정할 필드들 (부분 업데이트)
269
+ // - title: 엔티티 한글 제목
270
+ // - table: 테이블명
271
+ // - props: 추가할 프로퍼티 배열 (기존 props에 추가, 같은 이름이면 교체)
272
+ // - indexes: 추가할 인덱스 배열 (기존 indexes에 추가)
273
+ // - subsets: 서브셋 정의 (기존 subsets에 병합)
274
+ // - enumLabels: Enum 정의 (기존 enumLabels에 병합)
275
+ // - mode: "merge"(기본값) 또는 "replace"
276
+ // - merge: 기존 값에 병합
277
+ // - replace: 해당 필드 전체 교체
278
+
279
+ // 예시: Employee에 새 Enum 추가
280
+ // updateEntity({ entityId: "Employee", updates: { enumLabels: { "EmployeeRole": { "admin": "관리자", "user": "일반" } } } })
281
+
282
+ // 예시: Project에 새 프로퍼티 추가
283
+ // updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type: "integer", desc: "우선순위" }] } })
284
+
285
+ // ## 필수 사항
286
+ // - Entity의 props에는 최소한 id(integer, unsigned), created_at(timestamp)가 포함되어야 합니다.
287
+ // - relation 필드는 onUpdate, onDelete가 필수입니다. (예외: OneToOne에서 hasJoinColumn이 false인 경우)
288
+ // - Enum ID는 보통 EntityId + 속성명 형태입니다 (예: UserStatus, ProductType)
289
+ // - subset에서 다른 엔티티의 프로퍼티를 참조할 때는 반드시 해당 엔티티의 실제 프로퍼티명을 사용하세요.
290
+
291
+ // ## 검증 오류 처리
292
+ // 도구 호출 결과로 검증 오류(validationErrors)가 반환되면:
293
+ // 1. 오류 메시지를 분석하여 문제점을 파악하세요.
294
+ // 2. 오류를 수정한 데이터로 createEntity를 다시 호출하세요.
295
+ // 3. 사용자에게 오류를 그대로 전달하지 말고, 수정 후 재시도하세요.
296
+
297
+ // ### 일반적인 검증 오류와 수정 방법
298
+ // | 오류 메시지 | 수정 방법 |
299
+ // |------------|----------|
300
+ // | "id 프로퍼티가 필수" | props에 { name: "id", type: "integer", unsigned: true } 추가 |
301
+ // | "created_at 프로퍼티가 필수" | props에 { name: "created_at", type: "timestamp", dbDefault: "CURRENT_TIMESTAMP" } 추가 |
302
+ // | "XxxOrderBy enum이 필수" | enums에 { "XxxOrderBy": { "id-desc": "ID최신순" } } 추가 |
303
+ // | "XxxSearchField enum이 필수" | enums에 { "XxxSearchField": { "id": "ID" } } 추가 |
304
+ // | "string 타입은 length가 필수" | 해당 prop에 length 추가 (예: 255) |
305
+ // | "text 타입은 textType이 필수" | 해당 prop에 textType 추가 ("text", "mediumtext", "longtext") |
306
+ // | "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
307
+ // `;
308
+
309
+ // return streamText({
310
+ // model: this.model,
311
+ // system: systemMessage,
312
+ // messages,
313
+ // stopWhen: stepCountIs(2),
314
+ // tools: {
315
+ // createEntity: tool({
316
+ // description:
317
+ // "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
318
+ // inputSchema: TemplateOptions.shape.entity,
319
+ // execute: async (
320
+ // entity,
321
+ // ): Promise<{
322
+ // success: boolean;
323
+ // entityId: string;
324
+ // error?: string;
325
+ // validationErrors?: ValidationError[];
326
+ // }> => {
327
+ // try {
328
+ // // 입력 검증
329
+ // const validationErrors = validateEntityJson(entity);
330
+
331
+ // if (validationErrors.length > 0) {
332
+ // return {
333
+ // success: false,
334
+ // entityId: entity.entityId,
335
+ // error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
336
+ // validationErrors,
337
+ // };
338
+ // }
339
+
340
+ // await Sonamu.syncer.createEntity({
341
+ // subsets: { A: ["id"] },
342
+ // enums: {},
343
+ // ...entity,
344
+ // });
345
+
346
+ // // EntityManager 리로드
347
+ // await EntityManager.reload();
348
+
349
+ // return { success: true, entityId: entity.entityId };
350
+ // } catch (e) {
351
+ // const error = e instanceof Error ? e.message : "Unknown error";
352
+ // return { success: false, entityId: entity.entityId, error };
353
+ // }
354
+ // },
355
+ // }),
356
+ // updateEntity: tool({
357
+ // description:
358
+ // "기존 Entity를 수정합니다. Enum 추가, props 추가/수정, indexes 수정, subsets 수정 등 모든 엔티티 수정 작업에 사용하세요.",
359
+ // inputSchema: z.object({
360
+ // entityId: z.string().describe("수정할 Entity ID"),
361
+ // updates: TemplateOptions.shape.entity.partial().describe("수정할 필드들"),
362
+ // mode: z
363
+ // .enum(["merge", "replace"])
364
+ // .optional()
365
+ // .describe("수정 모드: merge(기본값, 기존 값에 병합) 또는 replace(전체 교체)"),
366
+ // }),
367
+ // execute: async ({
368
+ // entityId,
369
+ // updates,
370
+ // mode = "merge",
371
+ // }): Promise<{
372
+ // success: boolean;
373
+ // entityId: string;
374
+ // error?: string;
375
+ // validationErrors?: ValidationError[];
376
+ // }> => {
377
+ // try {
378
+ // const entity = EntityManager.get(entityId);
379
+
380
+ // for (const [key, value] of Object.entries(updates)) {
381
+ // if (
382
+ // ["entityId", "parentId", "title", "table"].includes(key) &&
383
+ // value !== undefined
384
+ // ) {
385
+ // entity[key] = value;
386
+ // }
387
+ // }
388
+
389
+ // // props: merge 시 이름 기준 병합, replace 시 교체
390
+ // if (updates.props !== undefined) {
391
+ // if (mode === "replace") {
392
+ // entity.props = updates.props as EntityProp[];
393
+ // } else {
394
+ // for (const newProp of updates.props) {
395
+ // const existingIndex = entity.props.findIndex((p) => p.name === newProp.name);
396
+ // if (existingIndex >= 0) {
397
+ // entity.props[existingIndex] = newProp as EntityProp;
398
+ // } else {
399
+ // entity.props.push(newProp as EntityProp);
400
+ // }
401
+ // }
402
+ // }
403
+ // }
404
+
405
+ // // indexes: merge 시 추가, replace 시 교체
406
+ // if (updates.indexes !== undefined) {
407
+ // entity.indexes =
408
+ // mode === "replace" ? updates.indexes : [...entity.indexes, ...updates.indexes];
409
+ // }
410
+
411
+ // // subsets, enumLabels: assign으로 병합 또는 교체
412
+ // if (updates.subsets !== undefined) {
413
+ // entity.subsets =
414
+ // mode === "replace" ? updates.subsets : { ...entity.subsets, ...updates.subsets };
415
+ // }
416
+
417
+ // if (updates.enums !== undefined) {
418
+ // entity.enumLabels =
419
+ // mode === "replace" ? updates.enums : { ...entity.enumLabels, ...updates.enums };
420
+ // }
421
+
422
+ // // 저장 전 검증
423
+ // const validationErrors = validateEntityJson({
424
+ // ...entity,
425
+ // entityId: entity.id,
426
+ // enums: entity.enumLabels,
427
+ // });
428
+
429
+ // if (validationErrors.length > 0) {
430
+ // return {
431
+ // success: false,
432
+ // entityId,
433
+ // error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
434
+ // validationErrors,
435
+ // };
436
+ // }
437
+
438
+ // await entity.save();
439
+
440
+ // return { success: true, entityId };
441
+ // } catch (e) {
442
+ // const error = e instanceof Error ? e.message : "Unknown error";
443
+ // return { success: false, entityId, error };
444
+ // }
445
+ // },
446
+ // }),
447
+ // },
448
+ // });
449
+ // }
450
+ // }
451
+
452
+ // /**
453
+ // * Entity JSON이 entity.instructions.md의 규칙을 따르는지 검증합니다.
454
+ // */
455
+ // function validateEntityJson(input: TemplateOptions["entity"]): ValidationError[] {
456
+ // const errors: ValidationError[] = [];
457
+ // const { entityId, props, enums } = input;
458
+
459
+ // // 1. id, created_at prop 필수
460
+ // const hasIdProp = props?.some((p) => p.name === "id");
461
+ // if (!hasIdProp) {
462
+ // errors.push({ field: "props", message: "id 프로퍼티가 필수입니다." });
463
+ // }
464
+ // const hasCreatedAtProp = props?.some((p) => p.name === "created_at");
465
+ // if (!hasCreatedAtProp) {
466
+ // errors.push({ field: "props", message: "created_at 프로퍼티가 필수입니다." });
467
+ // }
468
+
469
+ // // 2. 필수 enum 검증: EntityNameOrderBy, EntityNameSearchField
470
+ // const orderByEnumId = `${entityId}OrderBy`;
471
+ // const searchFieldEnumId = `${entityId}SearchField`;
472
+
473
+ // if (!enums?.[orderByEnumId]) {
474
+ // errors.push({
475
+ // field: "enums",
476
+ // message: `${orderByEnumId} enum이 필수입니다. (예: { "id-desc": "ID최신순" })`,
477
+ // });
478
+ // }
479
+ // if (!enums?.[searchFieldEnumId]) {
480
+ // errors.push({
481
+ // field: "enums",
482
+ // message: `${searchFieldEnumId} enum이 필수입니다. (예: { "id": "ID" })`,
483
+ // });
484
+ // }
485
+
486
+ // // 3. enum prop의 id가 enums에 정의되어 있는지 확인 (cross-field 검증)
487
+ // for (const prop of props ?? []) {
488
+ // if (prop.type === "enum" && !enums?.[prop.id]) {
489
+ // errors.push({
490
+ // field: `props.${prop.name}`,
491
+ // message: `enum id "${prop.id}"가 enums에 정의되어 있지 않습니다.`,
492
+ // });
493
+ // }
494
+ // }
495
+
496
+ // return errors;
497
+ // }
498
+
499
+ // export const aiClient = new AIClient();