sonamu 0.7.53 → 0.8.0

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 (271) hide show
  1. package/dist/api/config.d.ts +9 -1
  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 +21 -1
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +159 -65
  7. package/dist/auth/plugins/entity-definitions/anonymous.d.ts +10 -0
  8. package/dist/auth/plugins/entity-definitions/anonymous.d.ts.map +1 -0
  9. package/dist/auth/plugins/entity-definitions/anonymous.js +23 -0
  10. package/dist/auth/plugins/entity-definitions/api-key.d.ts +9 -0
  11. package/dist/auth/plugins/entity-definitions/api-key.d.ts.map +1 -0
  12. package/dist/auth/plugins/entity-definitions/api-key.js +199 -0
  13. package/dist/auth/plugins/entity-definitions/index.d.ts +6 -0
  14. package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
  15. package/dist/auth/plugins/entity-definitions/index.js +20 -2
  16. package/dist/auth/plugins/entity-definitions/jwt.d.ts +9 -0
  17. package/dist/auth/plugins/entity-definitions/jwt.d.ts.map +1 -0
  18. package/dist/auth/plugins/entity-definitions/jwt.js +67 -0
  19. package/dist/auth/plugins/entity-definitions/organization.d.ts +9 -0
  20. package/dist/auth/plugins/entity-definitions/organization.d.ts.map +1 -0
  21. package/dist/auth/plugins/entity-definitions/organization.js +424 -0
  22. package/dist/auth/plugins/entity-definitions/passkey.d.ts +10 -0
  23. package/dist/auth/plugins/entity-definitions/passkey.d.ts.map +1 -0
  24. package/dist/auth/plugins/entity-definitions/passkey.js +129 -0
  25. package/dist/auth/plugins/entity-definitions/sso.d.ts +10 -0
  26. package/dist/auth/plugins/entity-definitions/sso.d.ts.map +1 -0
  27. package/dist/auth/plugins/entity-definitions/sso.js +110 -0
  28. package/dist/auth/plugins/entity-definitions/types.d.ts +1 -1
  29. package/dist/auth/plugins/entity-definitions/types.d.ts.map +1 -1
  30. package/dist/auth/plugins/entity-definitions/types.js +1 -1
  31. package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
  32. package/dist/auth/plugins/wrappers/admin.js +2 -4
  33. package/dist/auth/plugins/wrappers/anonymous.d.ts +18 -0
  34. package/dist/auth/plugins/wrappers/anonymous.d.ts.map +1 -0
  35. package/dist/auth/plugins/wrappers/anonymous.js +26 -0
  36. package/dist/auth/plugins/wrappers/api-key.d.ts +18 -0
  37. package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -0
  38. package/dist/auth/plugins/wrappers/api-key.js +38 -0
  39. package/dist/auth/plugins/wrappers/index.d.ts +6 -0
  40. package/dist/auth/plugins/wrappers/index.d.ts.map +1 -1
  41. package/dist/auth/plugins/wrappers/index.js +7 -1
  42. package/dist/auth/plugins/wrappers/jwt.d.ts +18 -0
  43. package/dist/auth/plugins/wrappers/jwt.d.ts.map +1 -0
  44. package/dist/auth/plugins/wrappers/jwt.js +30 -0
  45. package/dist/auth/plugins/wrappers/organization.d.ts +18 -0
  46. package/dist/auth/plugins/wrappers/organization.d.ts.map +1 -0
  47. package/dist/auth/plugins/wrappers/organization.js +67 -0
  48. package/dist/auth/plugins/wrappers/passkey.d.ts +18 -0
  49. package/dist/auth/plugins/wrappers/passkey.d.ts.map +1 -0
  50. package/dist/auth/plugins/wrappers/passkey.js +32 -0
  51. package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
  52. package/dist/auth/plugins/wrappers/phone-number.js +2 -4
  53. package/dist/auth/plugins/wrappers/sso.d.ts +853 -0
  54. package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -0
  55. package/dist/auth/plugins/wrappers/sso.js +36 -0
  56. package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
  57. package/dist/auth/plugins/wrappers/two-factor.js +2 -4
  58. package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
  59. package/dist/auth/plugins/wrappers/username.js +2 -4
  60. package/dist/bin/build-config.d.ts +2 -2
  61. package/dist/bin/build-config.js +6 -7
  62. package/dist/bin/cli.js +417 -32
  63. package/dist/bin/fixture.d.ts +27 -0
  64. package/dist/bin/fixture.d.ts.map +1 -0
  65. package/dist/bin/fixture.js +245 -0
  66. package/dist/cache/decorator.d.ts +4 -3
  67. package/dist/cache/decorator.d.ts.map +1 -1
  68. package/dist/cache/decorator.js +5 -4
  69. package/dist/cone/cone-generator.d.ts +33 -0
  70. package/dist/cone/cone-generator.d.ts.map +1 -0
  71. package/dist/cone/cone-generator.js +286 -0
  72. package/dist/database/_batch_update.d.ts.map +1 -1
  73. package/dist/database/_batch_update.js +16 -2
  74. package/dist/database/puri-subset.test-d.js +1 -1
  75. package/dist/database/puri-subset.types.d.ts +1 -1
  76. package/dist/database/puri-subset.types.d.ts.map +1 -1
  77. package/dist/database/puri-subset.types.js +1 -1
  78. package/dist/database/puri.d.ts +4 -0
  79. package/dist/database/puri.d.ts.map +1 -1
  80. package/dist/database/puri.js +20 -2
  81. package/dist/database/upsert-builder.d.ts.map +1 -1
  82. package/dist/database/upsert-builder.js +19 -3
  83. package/dist/dict/en.d.ts +15 -0
  84. package/dist/dict/en.d.ts.map +1 -1
  85. package/dist/dict/en.js +2 -1
  86. package/dist/dict/ko.d.ts +15 -0
  87. package/dist/dict/ko.d.ts.map +1 -1
  88. package/dist/dict/ko.js +2 -1
  89. package/dist/dict/rc-keys.d.ts +28 -0
  90. package/dist/dict/rc-keys.d.ts.map +1 -1
  91. package/dist/dict/rc-keys.js +31 -1
  92. package/dist/dict/sd.d.ts.map +1 -1
  93. package/dist/dict/sd.js +20 -4
  94. package/dist/entity/entity-manager.d.ts +298 -2
  95. package/dist/entity/entity-manager.d.ts.map +1 -1
  96. package/dist/entity/entity-manager.js +4 -1
  97. package/dist/entity/entity-template-cone.d.ts +14 -0
  98. package/dist/entity/entity-template-cone.d.ts.map +1 -0
  99. package/dist/entity/entity-template-cone.js +222 -0
  100. package/dist/entity/entity.d.ts +47 -2
  101. package/dist/entity/entity.d.ts.map +1 -1
  102. package/dist/entity/entity.js +161 -14
  103. package/dist/ssr/renderer.js +3 -3
  104. package/dist/syncer/api-parser.js +12 -1
  105. package/dist/syncer/checksum.d.ts +0 -14
  106. package/dist/syncer/checksum.d.ts.map +1 -1
  107. package/dist/syncer/checksum.js +1 -23
  108. package/dist/syncer/syncer-actions.d.ts.map +1 -1
  109. package/dist/syncer/syncer-actions.js +8 -2
  110. package/dist/syncer/syncer.d.ts +1 -1
  111. package/dist/syncer/syncer.d.ts.map +1 -1
  112. package/dist/syncer/syncer.js +17 -10
  113. package/dist/tasks/workflow-manager.d.ts +13 -1
  114. package/dist/tasks/workflow-manager.d.ts.map +1 -1
  115. package/dist/tasks/workflow-manager.js +18 -1
  116. package/dist/template/entity-converter.js +4 -4
  117. package/dist/template/helpers.d.ts +10 -0
  118. package/dist/template/helpers.d.ts.map +1 -1
  119. package/dist/template/helpers.js +48 -1
  120. package/dist/template/implementations/entry-server.template.d.ts +1 -1
  121. package/dist/template/implementations/entry-server.template.js +7 -2
  122. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  123. package/dist/template/implementations/generated.template.js +5 -1
  124. package/dist/template/implementations/generated_http.template.d.ts +1 -0
  125. package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
  126. package/dist/template/implementations/generated_http.template.js +6 -2
  127. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  128. package/dist/template/implementations/generated_sso.template.js +29 -8
  129. package/dist/template/implementations/queries.template.d.ts.map +1 -1
  130. package/dist/template/implementations/queries.template.js +9 -1
  131. package/dist/template/implementations/sd.template.d.ts +1 -1
  132. package/dist/template/implementations/sd.template.d.ts.map +1 -1
  133. package/dist/template/implementations/sd.template.js +28 -4
  134. package/dist/template/implementations/services.template.d.ts.map +1 -1
  135. package/dist/template/implementations/services.template.js +12 -12
  136. package/dist/template/implementations/view_form.template.d.ts +11 -7
  137. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  138. package/dist/template/implementations/view_form.template.js +97 -87
  139. package/dist/template/implementations/view_list.template.d.ts +3 -3
  140. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  141. package/dist/template/implementations/view_list.template.js +115 -109
  142. package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
  143. package/dist/template/implementations/view_search_input.template.js +18 -14
  144. package/dist/template/zod-converter.d.ts.map +1 -1
  145. package/dist/template/zod-converter.js +95 -7
  146. package/dist/testing/_relation-graph.js +1 -1
  147. package/dist/testing/data-explorer.d.ts +61 -0
  148. package/dist/testing/data-explorer.d.ts.map +1 -0
  149. package/dist/testing/data-explorer.js +274 -0
  150. package/dist/testing/faker-mappings.d.ts +20 -0
  151. package/dist/testing/faker-mappings.d.ts.map +1 -0
  152. package/dist/testing/faker-mappings.js +421 -0
  153. package/dist/testing/fixture-generator.d.ts +161 -0
  154. package/dist/testing/fixture-generator.d.ts.map +1 -0
  155. package/dist/testing/fixture-generator.js +954 -0
  156. package/dist/testing/fixture-manager.d.ts +6 -1
  157. package/dist/testing/fixture-manager.d.ts.map +1 -1
  158. package/dist/testing/fixture-manager.js +72 -4
  159. package/dist/testing/index.d.ts +3 -0
  160. package/dist/testing/index.d.ts.map +1 -1
  161. package/dist/testing/index.js +4 -1
  162. package/dist/types/types.d.ts +1520 -26
  163. package/dist/types/types.d.ts.map +1 -1
  164. package/dist/types/types.js +136 -22
  165. package/dist/ui/ai-client.d.ts.map +1 -1
  166. package/dist/ui/ai-client.js +9 -4
  167. package/dist/ui/api.d.ts.map +1 -1
  168. package/dist/ui/api.js +303 -24
  169. package/dist/ui-web/assets/index-CsUr-_pV.js +254 -0
  170. package/dist/ui-web/assets/index-T42zzs1K.css +1 -0
  171. package/dist/ui-web/index.html +2 -2
  172. package/dist/utils/fs-utils.d.ts +2 -1
  173. package/dist/utils/fs-utils.d.ts.map +1 -1
  174. package/dist/utils/fs-utils.js +14 -3
  175. package/package.json +19 -11
  176. package/src/api/config.ts +12 -1
  177. package/src/api/sonamu.ts +179 -65
  178. package/src/auth/plugins/entity-definitions/anonymous.ts +17 -0
  179. package/src/auth/plugins/entity-definitions/api-key.ts +93 -0
  180. package/src/auth/plugins/entity-definitions/index.ts +18 -0
  181. package/src/auth/plugins/entity-definitions/jwt.ts +35 -0
  182. package/src/auth/plugins/entity-definitions/organization.ts +215 -0
  183. package/src/auth/plugins/entity-definitions/passkey.ts +64 -0
  184. package/src/auth/plugins/entity-definitions/sso.ts +62 -0
  185. package/src/auth/plugins/entity-definitions/types.ts +11 -1
  186. package/src/auth/plugins/wrappers/admin.ts +1 -3
  187. package/src/auth/plugins/wrappers/anonymous.ts +30 -0
  188. package/src/auth/plugins/wrappers/api-key.ts +42 -0
  189. package/src/auth/plugins/wrappers/index.ts +6 -0
  190. package/src/auth/plugins/wrappers/jwt.ts +34 -0
  191. package/src/auth/plugins/wrappers/organization.ts +73 -0
  192. package/src/auth/plugins/wrappers/passkey.ts +36 -0
  193. package/src/auth/plugins/wrappers/phone-number.ts +1 -3
  194. package/src/auth/plugins/wrappers/sso.ts +40 -0
  195. package/src/auth/plugins/wrappers/two-factor.ts +1 -3
  196. package/src/auth/plugins/wrappers/username.ts +1 -3
  197. package/src/bin/build-config.ts +6 -6
  198. package/src/bin/cli.ts +452 -31
  199. package/src/bin/fixture.ts +302 -0
  200. package/src/cache/decorator.ts +4 -3
  201. package/src/cone/cone-generator.ts +363 -0
  202. package/src/database/_batch_update.ts +11 -0
  203. package/src/database/puri-subset.test-d.ts +13 -13
  204. package/src/database/puri-subset.types.ts +1 -1
  205. package/src/database/puri.ts +43 -1
  206. package/src/database/upsert-builder.ts +16 -2
  207. package/src/dict/en.ts +1 -0
  208. package/src/dict/ko.ts +1 -0
  209. package/src/dict/rc-keys.ts +32 -0
  210. package/src/dict/sd.ts +23 -3
  211. package/src/entity/entity-manager.ts +4 -0
  212. package/src/entity/entity-template-cone.ts +298 -0
  213. package/src/entity/entity.ts +189 -13
  214. package/src/shared/app.shared.ts.txt +5 -0
  215. package/src/shared/web.shared.ts.txt +9 -5
  216. package/src/skills/project/README.md +21 -0
  217. package/src/skills/project/architecture.md +373 -0
  218. package/src/skills/project/business-logic.md +270 -0
  219. package/src/skills/project/requirements.md +160 -0
  220. package/src/skills/sonamu/SKILL.md +168 -3
  221. package/src/skills/sonamu/api.md +102 -0
  222. package/src/skills/sonamu/database.md +220 -1
  223. package/src/skills/sonamu/entity-relations.md +89 -1
  224. package/src/skills/sonamu/fixture-cli.md +501 -0
  225. package/src/skills/sonamu/frontend.md +214 -0
  226. package/src/skills/sonamu/i18n.md +95 -0
  227. package/src/skills/sonamu/model.md +153 -0
  228. package/src/skills/sonamu/project-init.md +178 -8
  229. package/src/skills/sonamu/scaffolding.md +112 -0
  230. package/src/skills/sonamu/subset.md +9 -3
  231. package/src/skills/sonamu/testing.md +287 -2
  232. package/src/skills/sonamu/workflow.md +70 -5
  233. package/src/ssr/renderer.ts +2 -2
  234. package/src/syncer/api-parser.ts +12 -0
  235. package/src/syncer/checksum.ts +0 -38
  236. package/src/syncer/syncer-actions.ts +7 -1
  237. package/src/syncer/syncer.ts +16 -5
  238. package/src/tasks/workflow-manager.ts +29 -8
  239. package/src/template/entity-converter.ts +3 -3
  240. package/src/template/helpers.ts +49 -0
  241. package/src/template/implementations/entry-server.template.ts +1 -1
  242. package/src/template/implementations/generated.template.ts +4 -0
  243. package/src/template/implementations/generated_http.template.ts +1 -0
  244. package/src/template/implementations/generated_sso.template.ts +40 -11
  245. package/src/template/implementations/queries.template.ts +8 -0
  246. package/src/template/implementations/sd.template.ts +22 -3
  247. package/src/template/implementations/services.template.ts +11 -10
  248. package/src/template/implementations/view_form.template.ts +111 -101
  249. package/src/template/implementations/view_list.template.ts +120 -119
  250. package/src/template/implementations/view_search_input.template.ts +17 -13
  251. package/src/template/zod-converter.ts +103 -6
  252. package/src/testing/_relation-graph.ts +1 -1
  253. package/src/testing/data-explorer.ts +427 -0
  254. package/src/testing/faker-mappings.ts +434 -0
  255. package/src/testing/fixture-generator.ts +1166 -0
  256. package/src/testing/fixture-manager.ts +91 -6
  257. package/src/testing/index.ts +3 -0
  258. package/src/types/types.ts +222 -26
  259. package/src/ui/ai-client.ts +9 -1
  260. package/src/ui/api.ts +429 -23
  261. package/src/utils/fs-utils.ts +14 -1
  262. package/dist/template/implementations/view_enums_select.template.d.ts +0 -17
  263. package/dist/template/implementations/view_enums_select.template.d.ts.map +0 -1
  264. package/dist/template/implementations/view_enums_select.template.js +0 -62
  265. package/dist/template/implementations/view_id_async_select.template.d.ts +0 -17
  266. package/dist/template/implementations/view_id_async_select.template.d.ts.map +0 -1
  267. package/dist/template/implementations/view_id_async_select.template.js +0 -125
  268. package/dist/ui-web/assets/index-Bd_2AkLb.css +0 -1
  269. package/dist/ui-web/assets/index-BpSbhQWo.js +0 -225
  270. package/src/template/implementations/view_enums_select.template.ts +0 -65
  271. package/src/template/implementations/view_id_async_select.template.ts +0 -139
@@ -3,8 +3,11 @@
3
3
  https://github.com/knex/knex/issues/5716
4
4
  */
5
5
 
6
+ import { getLogger } from "@logtape/logtape";
6
7
  import type { Knex } from "knex";
7
8
 
9
+ const logger = getLogger(["sonamu", "internal", "batch-update"]);
10
+
8
11
  type ColumnValue = string | number | boolean | null;
9
12
  export type RowWithId<Id extends string> = {
10
13
  [key in Id]: ColumnValue;
@@ -39,11 +42,19 @@ export async function batchUpdate<Id extends string>(
39
42
 
40
43
  if (trx) {
41
44
  for (const chunk of chunks) {
45
+ logger.debug("Processing Batch Chunk in Existing Transaction: {current} / {total}", {
46
+ current: chunk.length,
47
+ total: chunks.length,
48
+ });
42
49
  await executeUpdate(chunk, trx);
43
50
  }
44
51
  } else {
45
52
  await knex.transaction(async (newTrx) => {
46
53
  for (const chunk of chunks) {
54
+ logger.debug("Processing Batch Chunk in New Transaction: {current} / {total}", {
55
+ current: chunk.length,
56
+ total: chunks.length,
57
+ });
47
58
  await executeUpdate(chunk, newTrx);
48
59
  }
49
60
  });
@@ -109,7 +109,7 @@ describe("LoadersResult", () => {
109
109
  it("단일 로더의 결과 타입을 생성한다", () => {
110
110
  type MockLoaderQb = (
111
111
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
112
- fromIds: number[],
112
+ fromIds: number[] | string[],
113
113
  ) => MockPuri<{ id: number; title: string; refId: number }>;
114
114
 
115
115
  type Loaders = [
@@ -129,12 +129,12 @@ describe("LoadersResult", () => {
129
129
  it("중첩 로더를 처리한다", () => {
130
130
  type CommentLoaderQb = (
131
131
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
132
- fromIds: number[],
132
+ fromIds: number[] | string[],
133
133
  ) => MockPuri<{ id: number; content: string; refId: number }>;
134
134
 
135
135
  type PostLoaderQb = (
136
136
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
137
- fromIds: number[],
137
+ fromIds: number[] | string[],
138
138
  ) => MockPuri<{ id: number; title: string; refId: number }>;
139
139
 
140
140
  type Loaders = [
@@ -166,7 +166,7 @@ describe("LoadersResult", () => {
166
166
  refId: "id";
167
167
  qb: (
168
168
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
169
- fromIds: number[],
169
+ fromIds: number[] | string[],
170
170
  ) => MockPuri<{ id: number; name: string; refId: number }>;
171
171
  };
172
172
  type DepartmentLoader = {
@@ -174,7 +174,7 @@ describe("LoadersResult", () => {
174
174
  refId: "id";
175
175
  qb: (
176
176
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
177
- fromIds: number[],
177
+ fromIds: number[] | string[],
178
178
  ) => MockPuri<{ id: number; name: string; company_name: string; refId: number }>;
179
179
  };
180
180
  type EmployeeLoader = {
@@ -182,7 +182,7 @@ describe("LoadersResult", () => {
182
182
  refId: "id";
183
183
  qb: (
184
184
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
185
- fromIds: number[],
185
+ fromIds: number[] | string[],
186
186
  ) => MockPuri<{ id: number; employee_number: string; refId: number }>;
187
187
  loaders: [
188
188
  {
@@ -190,7 +190,7 @@ describe("LoadersResult", () => {
190
190
  refId: "id";
191
191
  qb: (
192
192
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
193
- fromIds: number[],
193
+ fromIds: number[] | string[],
194
194
  ) => MockPuri<{ id: number; name: string; email: string; refId: number }>;
195
195
  },
196
196
  ];
@@ -267,7 +267,7 @@ describe("InferAllSubsets", () => {
267
267
  refId: "company__id";
268
268
  qb: (
269
269
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
270
- fromIds: number[],
270
+ fromIds: number[] | string[],
271
271
  ) => MockPuri<{ id: number; name: string; refId: number }>;
272
272
  };
273
273
  type DepartmentProjectsLoader = {
@@ -275,7 +275,7 @@ describe("InferAllSubsets", () => {
275
275
  refId: "department__id";
276
276
  qb: (
277
277
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
278
- fromIds: number[],
278
+ fromIds: number[] | string[],
279
279
  ) => MockPuri<{ id: number; name: string; status: string; refId: number }>;
280
280
  };
281
281
  type LoaderQueries = {
@@ -316,7 +316,7 @@ describe("InferAllSubsets", () => {
316
316
  refId: "company__id";
317
317
  qb: (
318
318
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
319
- fromIds: number[],
319
+ fromIds: number[] | string[],
320
320
  ) => MockPuri<{ id: number; name: string; refId: number }>;
321
321
  loaders: [
322
322
  {
@@ -324,7 +324,7 @@ describe("InferAllSubsets", () => {
324
324
  refId: "id";
325
325
  qb: (
326
326
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
327
- fromIds: number[],
327
+ fromIds: number[] | string[],
328
328
  ) => MockPuri<{ id: number; name: string; status: string; refId: number }>;
329
329
  },
330
330
  ];
@@ -428,7 +428,7 @@ describe("InferAllSubsets", () => {
428
428
  refId: "id";
429
429
  qb: (
430
430
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
431
- fromIds: number[],
431
+ fromIds: number[] | string[],
432
432
  ) => MockPuri<{ id: number; name: string; status: string; refId: number }>;
433
433
  loaders: [
434
434
  {
@@ -436,7 +436,7 @@ describe("InferAllSubsets", () => {
436
436
  refId: "id";
437
437
  qb: (
438
438
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
439
- fromIds: number[],
439
+ fromIds: number[] | string[],
440
440
  ) => MockPuri<{ id: number; name: string; refId: number }>;
441
441
  },
442
442
  ];
@@ -41,7 +41,7 @@ export type ExtractPuriResult<T> = T extends Puri<any, any, infer R> ? R : never
41
41
  */
42
42
  export type PuriLoaderQbFn = (
43
43
  qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
44
- fromIds: number[],
44
+ fromIds: number[] | string[],
45
45
  ) => Puri<any, any, any>;
46
46
 
47
47
  /**
@@ -748,9 +748,13 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
748
748
  // 기존 ORDER BY clear
749
749
  this.knexQuery.clear("order");
750
750
  if (distinctOn) {
751
- // DISTINCT ON은 SELECT 절의 맨 앞에 와야 하므로, 기존 select를 clear하고 다시 추가
751
+ // DISTINCT ON은 SELECT 절의 맨 앞에 와야 하므로, 기존 select(subset 필드들)보존 후 clear하고 다시 추가
752
+ const existingSubsetCols = (this.knexQuery as any)._statements
753
+ .filter((s: any) => s.grouping === "columns")
754
+ .flatMap((s: any) => s.value);
752
755
  this.knexQuery.clear("select");
753
756
  this.knexQuery.select(this.knex.raw(`DISTINCT ON (??) ??`, [distinctOn, distinctOn]));
757
+ existingSubsetCols.map((col: any) => this.knexQuery.select(col));
754
758
  this.knexQuery.select(similarityExpr);
755
759
  this.knexQuery.orderByRaw(`??, ?? ${operator} ?::vector`, [
756
760
  distinctOn,
@@ -1120,6 +1124,25 @@ export class WhereGroup<TTables extends Record<string, any>> {
1120
1124
  return this;
1121
1125
  }
1122
1126
 
1127
+ // whereIn / whereNotIn 메서드들
1128
+ whereIn<TColumn extends AvailableColumns<TTables>>(
1129
+ column: TColumn,
1130
+ values: ExtractColumnType<TTables, TColumn & string>[],
1131
+ ): this;
1132
+ whereIn(...args: any[]): WhereGroup<TTables> {
1133
+ this.builder.whereIn(args[0], args[1]);
1134
+ return this;
1135
+ }
1136
+
1137
+ whereNotIn<TColumn extends AvailableColumns<TTables>>(
1138
+ column: TColumn,
1139
+ values: ExtractColumnType<TTables, TColumn & string>[],
1140
+ ): this;
1141
+ whereNotIn(...args: any[]): WhereGroup<TTables> {
1142
+ this.builder.whereNotIn(args[0], args[1]);
1143
+ return this;
1144
+ }
1145
+
1123
1146
  // orWhere 메서드들
1124
1147
  orWhere(conditions: WhereCondition<TTables>): this;
1125
1148
  orWhere<TColumn extends AvailableColumns<TTables>>(
@@ -1136,6 +1159,25 @@ export class WhereGroup<TTables extends Record<string, any>> {
1136
1159
  return this;
1137
1160
  }
1138
1161
 
1162
+ // orWhereIn / orWhereNotIn 메서드들
1163
+ orWhereIn<TColumn extends AvailableColumns<TTables>>(
1164
+ column: TColumn,
1165
+ values: ExtractColumnType<TTables, TColumn & string>[],
1166
+ ): this;
1167
+ orWhereIn(...args: any[]): WhereGroup<TTables> {
1168
+ this.builder.orWhereIn(args[0], args[1]);
1169
+ return this;
1170
+ }
1171
+
1172
+ orWhereNotIn<TColumn extends AvailableColumns<TTables>>(
1173
+ column: TColumn,
1174
+ values: ExtractColumnType<TTables, TColumn & string>[],
1175
+ ): this;
1176
+ orWhereNotIn(...args: any[]): WhereGroup<TTables> {
1177
+ this.builder.orWhereNotIn(args[0], args[1]);
1178
+ return this;
1179
+ }
1180
+
1139
1181
  // 중첩 그룹
1140
1182
  whereGroup(callback: (g: WhereGroup<TTables>) => void): this;
1141
1183
  whereGroup(callback: (g: WhereGroup<TTables>) => void): WhereGroup<TTables> {
@@ -1,3 +1,4 @@
1
+ import { getLogger } from "@logtape/logtape";
1
2
  import { randomUUID } from "crypto";
2
3
  import type { Knex } from "knex";
3
4
  import { isArray, unique } from "radashi";
@@ -8,6 +9,8 @@ import { assertDefined, chunk, nonNullable } from "../utils/utils";
8
9
  import { batchUpdate, type RowWithId } from "./_batch_update";
9
10
  import type { ColumnKeys, ForeignKeyColumns, IdType, TableName } from "./puri.types";
10
11
 
12
+ const logger = getLogger(["sonamu", "internal", "upsert-builder"]);
13
+
11
14
  /**
12
15
  * FK 타입 추론을 위해 DatabaseForeignKeys export
13
16
  * (module augmentation 자동 로드 보장)
@@ -256,7 +259,13 @@ export class UpsertBuilder {
256
259
  const allIds: (number | string)[] = [];
257
260
 
258
261
  // 레벨별로 순차 처리
259
- for (const levelRows of levels) {
262
+ for (let levelIdx = 0; levelIdx < levels.length; levelIdx++) {
263
+ const levelRows = levels[levelIdx];
264
+ logger.debug("Processing Query Level: {current} / {total}", {
265
+ current: levelIdx + 1,
266
+ total: levels.length,
267
+ });
268
+
260
269
  // 이전 레벨에서 얻은 ID로 자기 참조 해결
261
270
  const resolvedRows = levelRows.map((row) => {
262
271
  const resolved = { ...row };
@@ -284,8 +293,13 @@ export class UpsertBuilder {
284
293
  const levelChunks = chunkSize ? chunk(resolvedRows, chunkSize) : [resolvedRows];
285
294
  const selectFields = unique(["id", ...extractFields]);
286
295
 
287
- for (const dataChunk of levelChunks) {
296
+ for (let index = 0; index < levelChunks.length; index++) {
297
+ const dataChunk = levelChunks[index];
288
298
  if (dataChunk.length === 0) continue;
299
+ logger.debug("Processing Chunk: {current} / {total}", {
300
+ current: index + 1,
301
+ total: levelChunks.length,
302
+ });
289
303
 
290
304
  // uuid를 별도로 보관하고, DB에 저장할 데이터에서 제거
291
305
  const originalUuids = dataChunk.map((r) => r.uuid as string);
package/src/dict/en.ts CHANGED
@@ -18,6 +18,7 @@ export default {
18
18
  "error.alreadyProcessed": "Already processed",
19
19
  "error.duplicateRow": "Duplicate data",
20
20
  "error.targetNotFound": "Target not found",
21
+ "error.api.notFound": "API not found",
21
22
 
22
23
  // Common UI
23
24
  "common.save": "Save",
package/src/dict/ko.ts CHANGED
@@ -18,6 +18,7 @@ export default {
18
18
  "error.alreadyProcessed": "이미 처리되었습니다",
19
19
  "error.duplicateRow": "중복된 데이터입니다",
20
20
  "error.targetNotFound": "대상을 찾을 수 없습니다",
21
+ "error.api.notFound": "존재하지 않는 API입니다",
21
22
 
22
23
  // 공통 UI
23
24
  "common.save": "저장",
@@ -77,6 +77,22 @@ export const rcKeysKo = {
77
77
  "rc.calendar.month.10": "11월",
78
78
  "rc.calendar.month.11": "12월",
79
79
 
80
+ // Sonamu Filter Component
81
+ "rc.sonamuFilter.title": "소나무 필터",
82
+ "rc.sonamuFilter.apply": "적용",
83
+ "rc.sonamuFilter.reset": "초기화",
84
+ "rc.sonamuFilter.addRule": "규칙 추가",
85
+ "rc.sonamuFilter.noRulesYet": '아직 규칙이 없습니다. "+ 규칙 추가"를 클릭하여 시작하세요.',
86
+ "rc.sonamuFilter.selectField": "필드 선택",
87
+ "rc.sonamuFilter.selectOperator": "연산자 선택",
88
+ "rc.sonamuFilter.selectOperatorFirst": "먼저 연산자를 선택하세요",
89
+ "rc.sonamuFilter.enterValue": "값 입력",
90
+ "rc.sonamuFilter.enterNumber": "숫자 입력",
91
+ "rc.sonamuFilter.notSupported": "지원하지 않음",
92
+ "rc.sonamuFilter.startDate": "시작일",
93
+ "rc.sonamuFilter.endDate": "종료일",
94
+ "rc.sonamuFilter.appliedFilters": "🌲 적용된 소나무 필터",
95
+
80
96
  // Common
81
97
  "rc.common.cancel": "취소",
82
98
  "rc.common.save": "저장",
@@ -157,6 +173,22 @@ export const rcKeysEn = {
157
173
  "rc.calendar.month.10": "November",
158
174
  "rc.calendar.month.11": "December",
159
175
 
176
+ // Sonamu Filter Component
177
+ "rc.sonamuFilter.title": "Sonamu Filter",
178
+ "rc.sonamuFilter.apply": "Apply",
179
+ "rc.sonamuFilter.reset": "Reset",
180
+ "rc.sonamuFilter.addRule": "Add Rule",
181
+ "rc.sonamuFilter.noRulesYet": 'No rules yet. Click "+ Add Rule" to start.',
182
+ "rc.sonamuFilter.selectField": "Select field",
183
+ "rc.sonamuFilter.selectOperator": "Operator",
184
+ "rc.sonamuFilter.selectOperatorFirst": "Select operator first",
185
+ "rc.sonamuFilter.enterValue": "Enter value",
186
+ "rc.sonamuFilter.enterNumber": "Enter number",
187
+ "rc.sonamuFilter.notSupported": "Not supported",
188
+ "rc.sonamuFilter.startDate": "Start date",
189
+ "rc.sonamuFilter.endDate": "End date",
190
+ "rc.sonamuFilter.appliedFilters": "🌲 Applied SonamuFilters",
191
+
160
192
  // Common
161
193
  "rc.common.cancel": "Cancel",
162
194
  "rc.common.save": "Save",
package/src/dict/sd.ts CHANGED
@@ -25,11 +25,31 @@ type SDReturnType<K extends DictKey> = SonamuDict[K] extends (...args: infer P)
25
25
  ? (...args: P) => LocalizedString
26
26
  : LocalizedString;
27
27
 
28
- const FALLBACK_LOCALE = "en";
29
28
  function getDictValue<K extends DictKey>(key: K, locale: string): SDReturnType<K> {
29
+ const { defaultLocale, supportedLocales } = Sonamu.config.i18n;
30
+
31
+ // 1. 지정된 locale에서 조회
30
32
  const dict = dictionaries[locale];
31
- const value = dict?.[key] ?? dictionaries[FALLBACK_LOCALE]?.[key] ?? key;
32
- return value as unknown as SDReturnType<K>;
33
+ if (dict?.[key] !== undefined) {
34
+ return dict[key] as unknown as SDReturnType<K>;
35
+ }
36
+
37
+ // 2. default locale에서 조회
38
+ if (locale !== defaultLocale && dictionaries[defaultLocale]?.[key] !== undefined) {
39
+ return dictionaries[defaultLocale][key] as unknown as SDReturnType<K>;
40
+ }
41
+
42
+ // 3. supported locales 순회
43
+ for (const supportedLocale of supportedLocales) {
44
+ if (supportedLocale !== locale && supportedLocale !== defaultLocale) {
45
+ if (dictionaries[supportedLocale]?.[key] !== undefined) {
46
+ return dictionaries[supportedLocale][key] as unknown as SDReturnType<K>;
47
+ }
48
+ }
49
+ }
50
+
51
+ // 4. 모두 실패 시 key 반환
52
+ return key as unknown as SDReturnType<K>;
33
53
  }
34
54
 
35
55
  /**
@@ -104,6 +104,10 @@ class EntityManagerClass {
104
104
  return Array.from(EntityManager.entities.keys()).sort();
105
105
  }
106
106
 
107
+ getAllEntities(): Entity[] {
108
+ return Array.from(this.entities.values());
109
+ }
110
+
107
111
  getAllParentIds(): string[] {
108
112
  return this.getAllIds().filter((entityId) => {
109
113
  const entity = this.get(entityId);
@@ -0,0 +1,298 @@
1
+ import { type FakerMappingConfig, fakerMappings } from "../testing/faker-mappings";
2
+ import type { Cone, EntityJson, EntityProp, OneToOneRelationProp, SubsetDef } from "../types/types";
3
+ import {
4
+ isBelongsToOneRelationProp,
5
+ isEnumDefWithCone,
6
+ isOneToOneRelationProp,
7
+ isRelationProp,
8
+ isSubsetDefWithCone,
9
+ } from "../types/types";
10
+
11
+ /**
12
+ * Entity의 템플릿 cone을 생성합니다.
13
+ *
14
+ * LLM을 사용하지 않고 faker-mappings.ts를 활용하여 기본 cone을 생성합니다.
15
+ * stub entity 생성 시 자동으로 호출되어 최소한의 cone 메타데이터를 제공합니다.
16
+ */
17
+ export function generateTemplateCones(
18
+ entity: EntityJson,
19
+ locale: "ko" | "en" | "ja" = "ko",
20
+ ): {
21
+ entityCone?: Cone;
22
+ propCones: Record<string, Cone>;
23
+ subsetCones: Record<string, Cone>;
24
+ enumCones: Record<string, Cone>;
25
+ } {
26
+ const mapping = fakerMappings[locale];
27
+
28
+ if (!mapping) {
29
+ throw new Error(`Unsupported locale: ${locale}`);
30
+ }
31
+
32
+ // 1. Entity cone
33
+ const entityCone: Cone = {
34
+ desc: getEntityDesc(entity, locale),
35
+ fixtureHint: getEntityFixtureHint(entity, locale),
36
+ };
37
+
38
+ // 2. Prop cones
39
+ const propCones: Record<string, Cone> = {};
40
+ for (const prop of entity.props) {
41
+ const fakerConfig = findFakerMapping(prop, mapping);
42
+
43
+ propCones[prop.name] = {
44
+ desc: fakerConfig?.comment || getDefaultDesc(prop, locale),
45
+ fixtureGenerator: fakerConfig?.faker,
46
+ fixtureHint: getFixtureHint(prop, fakerConfig, locale),
47
+ dataSource: shouldHaveDataSource(prop)
48
+ ? { strategy: "recent", config: { limit: 5 } }
49
+ : undefined,
50
+ };
51
+ }
52
+
53
+ // 3. Subset cones
54
+ const subsetCones: Record<string, Cone> = {};
55
+ for (const [key, subset] of Object.entries(entity.subsets || {})) {
56
+ subsetCones[key] = {
57
+ desc: getSubsetDesc(key, subset, locale),
58
+ note: getSubsetNote(key, subset, locale),
59
+ };
60
+ }
61
+
62
+ // 4. Enum cones
63
+ const enumCones: Record<string, Cone> = {};
64
+ for (const [enumId, enumDef] of Object.entries(entity.enums || {})) {
65
+ const values = isEnumDefWithCone(enumDef) ? enumDef.values : enumDef;
66
+
67
+ enumCones[enumId] = {
68
+ desc: getEnumDesc(enumId, locale),
69
+ note: getEnumNote(enumId, values, locale),
70
+ values: Object.keys(values).reduce(
71
+ (acc, key) => {
72
+ acc[key] = {
73
+ desc: getEnumValueDesc(values[key] || key, locale),
74
+ };
75
+ return acc;
76
+ },
77
+ {} as Record<string, { desc: string }>,
78
+ ),
79
+ };
80
+ }
81
+
82
+ return { entityCone, propCones, subsetCones, enumCones };
83
+ }
84
+
85
+ /**
86
+ * Prop에 맞는 faker mapping을 찾습니다.
87
+ *
88
+ * 우선순위:
89
+ * 1. field_patterns에서 prop name 매칭 (정확한 이름 매칭)
90
+ * 2. field_patterns에서 부분 매칭 (name 필드가 user_name이면 name 매칭)
91
+ * 3. type_defaults에서 prop type 매칭
92
+ */
93
+ function findFakerMapping(
94
+ prop: EntityProp,
95
+ mapping: {
96
+ field_patterns: Record<string, FakerMappingConfig>;
97
+ type_defaults: Record<string, FakerMappingConfig>;
98
+ },
99
+ ): FakerMappingConfig | undefined {
100
+ if (isRelationProp(prop)) {
101
+ return undefined;
102
+ }
103
+
104
+ const propName = prop.name.toLowerCase();
105
+
106
+ // 1. 정확한 필드명 매칭
107
+ if (mapping.field_patterns[propName]) {
108
+ return mapping.field_patterns[propName];
109
+ }
110
+
111
+ // 2. 부분 매칭 (user_name → name, email_address → email)
112
+ for (const [pattern, config] of Object.entries(mapping.field_patterns)) {
113
+ if (propName.includes(pattern) || propName.endsWith(`_${pattern}`)) {
114
+ return config;
115
+ }
116
+ }
117
+
118
+ // 3. 타입 기본값 매칭
119
+ const typeKey = prop.type;
120
+ if (mapping.type_defaults[typeKey]) {
121
+ return mapping.type_defaults[typeKey];
122
+ }
123
+
124
+ return undefined;
125
+ }
126
+
127
+ /**
128
+ * Entity 설명을 생성합니다.
129
+ */
130
+ function getEntityDesc(entity: EntityJson, locale: "ko" | "en" | "ja"): string {
131
+ const title = entity.title || entity.id;
132
+
133
+ const templates = {
134
+ ko: `${title} 엔티티`,
135
+ en: `${title} entity`,
136
+ ja: `${title}エンティティ`,
137
+ };
138
+
139
+ return templates[locale];
140
+ }
141
+
142
+ /**
143
+ * Entity fixture hint를 생성합니다.
144
+ */
145
+ function getEntityFixtureHint(entity: EntityJson, locale: "ko" | "en" | "ja"): string {
146
+ const title = entity.title || entity.id;
147
+
148
+ const templates = {
149
+ ko: `${title} 테스트 데이터를 생성합니다. 각 필드는 실제 데이터와 유사한 형식으로 생성됩니다.`,
150
+ en: `Generates ${title} test data. Each field is generated in a format similar to real data.`,
151
+ ja: `${title}のテストデータを生成します。各フィールドは実際のデータに似た形式で生成されます。`,
152
+ };
153
+
154
+ return templates[locale];
155
+ }
156
+
157
+ /**
158
+ * Prop 기본 설명을 생성합니다 (faker mapping이 없는 경우).
159
+ */
160
+ function getDefaultDesc(prop: EntityProp, locale: "ko" | "en" | "ja"): string {
161
+ if (isRelationProp(prop)) {
162
+ const templates = {
163
+ ko: `${prop.with} 참조`,
164
+ en: `Reference to ${prop.with}`,
165
+ ja: `${prop.with}への参照`,
166
+ };
167
+ return templates[locale];
168
+ }
169
+
170
+ const templates = {
171
+ ko: prop.name,
172
+ en: prop.name,
173
+ ja: prop.name,
174
+ };
175
+
176
+ return templates[locale];
177
+ }
178
+
179
+ /**
180
+ * Prop fixture hint를 생성합니다.
181
+ */
182
+ function getFixtureHint(
183
+ prop: EntityProp,
184
+ fakerConfig: FakerMappingConfig | undefined,
185
+ locale: "ko" | "en" | "ja",
186
+ ): string | undefined {
187
+ if (isRelationProp(prop)) {
188
+ const templates = {
189
+ ko: `기존 ${prop.with} 데이터를 참조합니다`,
190
+ en: `References existing ${prop.with} data`,
191
+ ja: `既存の${prop.with}データを参照します`,
192
+ };
193
+ return templates[locale];
194
+ }
195
+
196
+ if (fakerConfig?.comment) {
197
+ const templates = {
198
+ ko: `${fakerConfig.comment} 형식으로 생성됩니다`,
199
+ en: `Generated as ${fakerConfig.comment}`,
200
+ ja: `${fakerConfig.comment}形式で生成されます`,
201
+ };
202
+ return templates[locale];
203
+ }
204
+
205
+ return undefined;
206
+ }
207
+
208
+ /**
209
+ * Prop이 dataSource를 가져야 하는지 판단합니다.
210
+ *
211
+ * BelongsToOne 또는 OneToOne (hasJoinColumn: true)인 경우 dataSource가 필요합니다.
212
+ */
213
+ function shouldHaveDataSource(prop: EntityProp): boolean {
214
+ if (!isRelationProp(prop)) {
215
+ return false;
216
+ }
217
+
218
+ // BelongsToOne은 항상 dataSource 필요
219
+ if (isBelongsToOneRelationProp(prop)) {
220
+ return true;
221
+ }
222
+
223
+ // OneToOne은 hasJoinColumn: true인 경우에만 dataSource 필요
224
+ if (isOneToOneRelationProp(prop)) {
225
+ return (prop as OneToOneRelationProp).hasJoinColumn === true;
226
+ }
227
+
228
+ return false;
229
+ }
230
+
231
+ /**
232
+ * Subset 설명을 생성합니다.
233
+ */
234
+ function getSubsetDesc(key: string, _subset: SubsetDef, locale: "ko" | "en" | "ja"): string {
235
+ const templates = {
236
+ ko: `${key} 서브셋`,
237
+ en: `${key} subset`,
238
+ ja: `${key}サブセット`,
239
+ };
240
+
241
+ return templates[locale];
242
+ }
243
+
244
+ /**
245
+ * Subset note를 생성합니다.
246
+ */
247
+ function getSubsetNote(_key: string, subset: SubsetDef, locale: "ko" | "en" | "ja"): string {
248
+ const fields = isSubsetDefWithCone(subset) ? subset.fields : subset;
249
+ const fieldNames = fields.map((f) => (typeof f === "string" ? f : f.field)).join(", ");
250
+
251
+ const templates = {
252
+ ko: `포함된 필드: ${fieldNames}`,
253
+ en: `Included fields: ${fieldNames}`,
254
+ ja: `含まれるフィールド: ${fieldNames}`,
255
+ };
256
+
257
+ return templates[locale];
258
+ }
259
+
260
+ /**
261
+ * Enum 설명을 생성합니다.
262
+ */
263
+ function getEnumDesc(_enumId: string, locale: "ko" | "en" | "ja"): string {
264
+ const templates = {
265
+ ko: `열거형`,
266
+ en: `enum`,
267
+ ja: `列挙型`,
268
+ };
269
+
270
+ return templates[locale];
271
+ }
272
+
273
+ /**
274
+ * Enum note를 생성합니다.
275
+ */
276
+ function getEnumNote(
277
+ _enumId: string,
278
+ values: Record<string, string>,
279
+ locale: "ko" | "en" | "ja",
280
+ ): string {
281
+ const valueList = Object.keys(values).join(", ");
282
+
283
+ const templates = {
284
+ ko: `가능한 값: ${valueList}`,
285
+ en: `Possible values: ${valueList}`,
286
+ ja: `可能な値: ${valueList}`,
287
+ };
288
+
289
+ return templates[locale];
290
+ }
291
+
292
+ /**
293
+ * Enum value 설명을 생성합니다.
294
+ */
295
+ function getEnumValueDesc(label: string, _locale: "ko" | "en" | "ja"): string {
296
+ // label을 그대로 사용
297
+ return label;
298
+ }