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
@@ -23,7 +23,7 @@ import { isTest } from "../utils/controller";
23
23
  import { copyFileWithReplaceCoreToShared, exists } from "../utils/fs-utils";
24
24
  import type { AbsolutePath } from "../utils/path-utils";
25
25
  import { runWithGracefulShutdown } from "../utils/process-utils";
26
- import { areFilesSame, findChangedFilesUsingChecksums, renewChecksums } from "./checksum";
26
+ import { findChangedFilesUsingChecksums, renewChecksums } from "./checksum";
27
27
  import { generateTemplate, renderTemplate } from "./code-generator";
28
28
  import { createEntity, delEntity } from "./entity-operations";
29
29
  import {
@@ -55,13 +55,13 @@ export class Syncer {
55
55
 
56
56
  /**
57
57
  * 체크섬이 변경된 부분에 대해 싱크를 진행합니다.
58
- * 다만 sonamu.shared.ts는 체크섬 비교 없이 무조건 싱크(복사)합니다.
58
+ * sonamu.shared.ts는 파일이 없을 때만 1회 생성하고, 이후에는 덮어쓰지 않습니다.
59
59
  * @returns
60
60
  */
61
61
  async sync(): Promise<void> {
62
62
  const { targets } = Sonamu.config.sync;
63
63
 
64
- // sonamu.shared.ts는 무조건 싱크(복사)합니다.
64
+ // sonamu.shared.ts는 파일이 없을 때만 1회 생성합니다.
65
65
  await this.copySharedToTargets(targets);
66
66
 
67
67
  // 그 다음부터는 변경된 파일을 찾아서 동기화 작업을 실행합니다.
@@ -239,7 +239,12 @@ export class Syncer {
239
239
  console.warn(`Created directory '${path.dirname(destPath)}' because it did not exist.`);
240
240
  }
241
241
 
242
- if (await areFilesSame({ data: convertedText }, { path: destPath })) {
242
+ // 파일이 이미 존재하면 건너뜁니다.
243
+ // sonamu.shared.ts는 프로젝트에서 자유롭게 커스터마이징할 수 있어야 하므로,
244
+ // 최초 1회만 생성하고 이후에는 덮어쓰지 않습니다.
245
+ // 템플릿 내용($[[dictUtils]] 등)이 변경되었을 때 반영이 필요하면,
246
+ // 해당 파일을 삭제한 뒤 `pnpm sonamu sync`로 재생성하면 됩니다.
247
+ if (await exists(destPath)) {
243
248
  continue;
244
249
  }
245
250
 
@@ -622,7 +627,13 @@ export class Syncer {
622
627
  const sourceFile = path.join(apiI18nDir, `${locale}.ts`);
623
628
  const targetFile = path.join(targetI18nDir, `${locale}.ts`);
624
629
 
625
- await copyFileWithReplaceCoreToShared(sourceFile, targetFile);
630
+ const syncHeader = [
631
+ "/**",
632
+ " * @generated",
633
+ " * API에서 동기화된 파일입니다. 직접 수정하지 마세요.",
634
+ " */",
635
+ ].join("\n");
636
+ await copyFileWithReplaceCoreToShared(sourceFile, targetFile, syncHeader);
626
637
  !isTest() &&
627
638
  console.log(chalk.bold("Copied: ") + chalk.cyan(`${target}/src/i18n/${locale}.ts`));
628
639
  }
@@ -1,13 +1,14 @@
1
1
  import { getLogger, type Logger } from "@logtape/logtape";
2
2
  import { BackendPostgres, OpenWorkflow, type Worker } from "@sonamu-kit/tasks";
3
- import type {
4
- RunnableWorkflow,
5
- SchemaInput,
6
- SchemaOutput,
7
- StandardSchemaV1,
8
- StepApi,
9
- WorkflowRunHandle,
10
- WorkflowSpec,
3
+ import {
4
+ type RunnableWorkflow,
5
+ type SchemaInput,
6
+ type SchemaOutput,
7
+ type StandardSchemaV1,
8
+ type StepApi,
9
+ serializeRetryPolicy,
10
+ type WorkflowRunHandle,
11
+ type WorkflowSpec,
11
12
  } from "@sonamu-kit/tasks/internal";
12
13
  import assert from "assert";
13
14
  import type { Knex } from "knex";
@@ -92,6 +93,26 @@ export class WorkflowManager {
92
93
  this.#scheduledTasks = new Map();
93
94
  }
94
95
 
96
+ // Sonamu UI API에서 워크플로우 실행 목록 조회, 취소 등에 사용합니다.
97
+ get backend(): BackendPostgres {
98
+ return this.#backend;
99
+ }
100
+
101
+ get workflowDefinitions() {
102
+ return Array.from(this.#workflowsMap.values())
103
+ .flat()
104
+ .map((wf) => ({
105
+ id: wf.id,
106
+ name: wf.name,
107
+ version: wf.version,
108
+ schedules: wf.schedules.map((s) => ({
109
+ name: s.name,
110
+ expression: s.expression,
111
+ })),
112
+ retryPolicy: wf.retryPolicy ? serializeRetryPolicy(wf.retryPolicy) : undefined,
113
+ }));
114
+ }
115
+
95
116
  /**
96
117
  * 정의된 워크플로우 및 워크플로우에 대한 scheduled tasks를 동기화합니다.
97
118
  */
@@ -13,11 +13,11 @@ import { propToZodType, zodTypeToRenderingNode } from "./zod-converter";
13
13
  */
14
14
  export async function getColumnsNode(entityId: string, subsetKey: string): Promise<RenderingNode> {
15
15
  const entity = EntityManager.get(entityId);
16
- const subset = entity.subsets[subsetKey];
17
- if (subset === undefined) {
16
+ const subsetDef = entity.subsets[subsetKey];
17
+ if (subsetDef === undefined) {
18
18
  throw new ServiceUnavailableException(SD("sonamu.error.subsetNotFound")(subsetKey));
19
19
  }
20
- const propNodes = entity.fieldExprsToPropNodes(subset);
20
+ const propNodes = entity.fieldExprsToPropNodes(subsetDef);
21
21
  const rootPropNode: EntityPropNode = {
22
22
  nodeType: "object",
23
23
  children: propNodes,
@@ -77,6 +77,55 @@ export function getRelationPropFromColName(entityId: string, colName: string): R
77
77
  }
78
78
  }
79
79
 
80
+ /**
81
+ * FK 컬럼명에서 실제 relation 이름을 추출합니다.
82
+ * BelongsToOne/OneToOne relation은 subset에서 FK 컬럼명(user_id)으로 생성되므로 변환이 필요하고,
83
+ * ManyToMany relation은 subset에서 relation명(employee)으로 생성되므로 변환이 불필요합니다.
84
+ *
85
+ * @example
86
+ * getRelationNameFromColumnName("Employee", "user_id") // "user"
87
+ * getRelationNameFromColumnName("Project", "tag_ids") // "tags"
88
+ */
89
+ export function getRelationNameFromColumnName(entityId: string, colName: string): string {
90
+ // _ids (복수) 처리
91
+ if (colName.endsWith("_ids")) {
92
+ const baseName = colName.replace(/_ids$/, "");
93
+ // 먼저 base name으로 찾기
94
+ try {
95
+ const relProp = getRelationPropFromColName(entityId, baseName);
96
+ return relProp.name;
97
+ } catch {
98
+ // pluralize해서 찾기
99
+ try {
100
+ const pluralName = inflection.pluralize(baseName);
101
+ const relProp = getRelationPropFromColName(entityId, pluralName);
102
+ return relProp.name;
103
+ } catch {
104
+ return colName;
105
+ }
106
+ }
107
+ }
108
+ // _id (단수) 처리
109
+ if (colName.endsWith("_id") && colName !== "id") {
110
+ const baseName = colName.replace(/_id$/, "");
111
+ // 먼저 base name으로 찾기
112
+ try {
113
+ const relProp = getRelationPropFromColName(entityId, baseName);
114
+ return relProp.name;
115
+ } catch {
116
+ // singularize해서 찾기
117
+ try {
118
+ const singularName = inflection.singularize(baseName);
119
+ const relProp = getRelationPropFromColName(entityId, singularName);
120
+ return relProp.name;
121
+ } catch {
122
+ return colName;
123
+ }
124
+ }
125
+ }
126
+ return colName;
127
+ }
128
+
80
129
  /**
81
130
  * 소스 코드에서 객체 선언을 추출합니다.
82
131
  * 중괄호 카운팅 방식으로 중첩된 객체도 정확히 파싱합니다.
@@ -75,7 +75,7 @@ export async function render(url: string, preloadedData: PreloadedData[] = []) {
75
75
  ...this.getTargetAndPath(),
76
76
  body,
77
77
  importKeys: [],
78
- customHeaders: [],
78
+ customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */"],
79
79
  };
80
80
  }
81
81
  }
@@ -128,6 +128,10 @@ export class Template__generated extends Template {
128
128
  body,
129
129
  importKeys: sourceCode.importKeys,
130
130
  customHeaders: [
131
+ "/**",
132
+ " * @generated",
133
+ " * 직접 수정하지 마세요.",
134
+ " */",
131
135
  "/** biome-ignore-all lint: generated는 무시 */",
132
136
  "/** biome-ignore-all assist: generated는 무시 */",
133
137
  "/** biome-ignore-all format: generated는 무시 */",
@@ -62,6 +62,7 @@ export class Template__generated_http extends Template {
62
62
  ...this.getTargetAndPath(),
63
63
  body: lines.join("\n\n###\n\n"),
64
64
  importKeys: [],
65
+ customHeaders: ["# @generated", "# 직접 수정하지 마세요."],
65
66
  };
66
67
  }
67
68
 
@@ -145,7 +145,13 @@ export class Template__generated_sso extends Template {
145
145
  .join(", ");
146
146
 
147
147
  // customHeaders 구성
148
- const customHeaders = [`import { ${sonamuImports} } from "sonamu";`];
148
+ const customHeaders = [
149
+ "/**",
150
+ " * @generated",
151
+ " * 직접 수정하지 마세요.",
152
+ " */",
153
+ `import { ${sonamuImports} } from "sonamu";`,
154
+ ];
149
155
  if (this.hasAuthConfig()) {
150
156
  customHeaders.push(`import type { User } from "better-auth";`);
151
157
  }
@@ -173,8 +179,15 @@ export class Template__generated_sso extends Template {
173
179
  entities.flatMap((entity) =>
174
180
  entity.props.filter(isManyToManyRelationProp).map((prop) => {
175
181
  const fromTableKey = inflection.singularize(entity.table);
176
- const toTableKey = inflection.singularize(EntityManager.get(prop.with).table);
177
- return { table: prop.joinTable, fromTableKey, toTableKey };
182
+ const toEntity = EntityManager.get(prop.with);
183
+ const toTableKey = inflection.singularize(toEntity.table);
184
+ return {
185
+ table: prop.joinTable,
186
+ fromTableKey,
187
+ toTableKey,
188
+ fromEntityId: entity.id,
189
+ toEntityId: toEntity.id,
190
+ };
178
191
  }),
179
192
  ),
180
193
  (joinTable) => joinTable.table,
@@ -205,10 +218,14 @@ export class Template__generated_sso extends Template {
205
218
  `declare module "sonamu" {`,
206
219
  ` export interface DatabaseSchemaExtend {`,
207
220
  ...entitySchemaLines,
208
- ...joinTables.map(
209
- (joinTable) =>
210
- `${joinTable.table}: ManyToManyBaseSchema<"${joinTable.fromTableKey}", "${joinTable.toTableKey}">;`,
211
- ),
221
+ ...joinTables.map((joinTable) => {
222
+ const fromEntity = EntityManager.get(joinTable.fromEntityId);
223
+ const toEntity = EntityManager.get(joinTable.toEntityId);
224
+ const fromPkType = fromEntity.getPkType() === "integer" ? "number" : "string";
225
+ const toPkType = toEntity.getPkType() === "integer" ? "number" : "string";
226
+
227
+ return `${joinTable.table}: ManyToManyBaseSchema<"${joinTable.fromTableKey}", "${joinTable.toTableKey}", ${fromPkType}, ${toPkType}>;`;
228
+ }),
212
229
  ` }`,
213
230
  ``,
214
231
  ` export interface DatabaseForeignKeys {`,
@@ -285,19 +302,31 @@ export class Template__generated_sso extends Template {
285
302
  };
286
303
  }
287
304
 
305
+ const importKeys: string[] = [];
306
+
288
307
  // additionalFields를 TypeScript 타입으로 변환
289
308
  const fieldLines = Object.entries(additionalFields).map(([key, value]) => {
290
309
  // biome-ignore lint/suspicious/noExplicitAny: better-auth additionalFields 구조
291
- const fieldType = mapBetterAuthFieldType((value as any).type);
292
- // biome-ignore lint/suspicious/noExplicitAny: better-auth additionalFields 구조
293
- const isRequired = (value as any).required !== false;
310
+ const fieldConfig = value as any;
311
+ const isRequired = fieldConfig.required !== false;
312
+
313
+ let fieldType: string;
314
+
315
+ // sonamuType이 있으면 description을 타입명으로 사용
316
+ if (fieldConfig.sonamuType) {
317
+ fieldType = fieldConfig.sonamuType;
318
+ importKeys.push(fieldType);
319
+ } else {
320
+ fieldType = mapBetterAuthFieldType(fieldConfig.type);
321
+ }
322
+
294
323
  return ` ${key}${isRequired ? "" : "?"}: ${fieldType};`;
295
324
  });
296
325
 
297
326
  return {
298
327
  label: "Auth User Type",
299
328
  lines: ["export type SonamuUser = User & {", ...fieldLines, "};"],
300
- importKeys: [],
329
+ importKeys,
301
330
  };
302
331
  }
303
332
 
@@ -101,6 +101,10 @@ ${functions.join("\n\n")}
101
101
  importKeys: diff(unique(importKeys), typeParamNames),
102
102
  customHeaders: hasQueries
103
103
  ? [
104
+ "/**",
105
+ " * @generated",
106
+ " * 직접 수정하지 마세요.",
107
+ " */",
104
108
  "/** biome-ignore-all lint: generated는 무시 */",
105
109
  "/** biome-ignore-all assist: generated는 무시 */",
106
110
  "",
@@ -113,6 +117,10 @@ ${functions.join("\n\n")}
113
117
  "",
114
118
  ]
115
119
  : [
120
+ "/**",
121
+ " * @generated",
122
+ " * 직접 수정하지 마세요.",
123
+ " */",
116
124
  "/** biome-ignore-all lint: generated는 무시 */",
117
125
  "/** biome-ignore-all assist: generated는 무시 */",
118
126
  "",
@@ -137,9 +137,28 @@ type SDReturnType<K extends DictKey> = MergedDictionary[K] extends (...args: inf
137
137
  : LocalizedString;
138
138
 
139
139
  function getDictValue<K extends DictKey>(key: K, locale: string): SDReturnType<K> {
140
+ // 1. 지정된 locale에서 조회
140
141
  const dict = dictionaries[locale];
141
- const value = dict?.[key] ?? dictionaries[DEFAULT_LOCALE]?.[key] ?? key;
142
- return value as unknown as SDReturnType<K>;
142
+ if (dict?.[key] !== undefined) {
143
+ return dict[key] as unknown as SDReturnType<K>;
144
+ }
145
+
146
+ // 2. default locale에서 조회
147
+ if (locale !== DEFAULT_LOCALE && dictionaries[DEFAULT_LOCALE]?.[key] !== undefined) {
148
+ return dictionaries[DEFAULT_LOCALE][key] as unknown as SDReturnType<K>;
149
+ }
150
+
151
+ // 3. supported locales 순회
152
+ for (const supportedLocale of SUPPORTED_LOCALES) {
153
+ if (supportedLocale !== locale && supportedLocale !== DEFAULT_LOCALE) {
154
+ if (dictionaries[supportedLocale]?.[key] !== undefined) {
155
+ return dictionaries[supportedLocale][key] as unknown as SDReturnType<K>;
156
+ }
157
+ }
158
+ }
159
+
160
+ // 4. 모두 실패 시 key 반환
161
+ return key as unknown as SDReturnType<K>;
143
162
  }
144
163
 
145
164
  /**
@@ -229,7 +248,7 @@ SD.enumLabels = (enumName: string): Record<string, LocalizedString> => {
229
248
  ...this.getTargetAndPath(undefined, target),
230
249
  body,
231
250
  importKeys: [],
232
- customHeaders: [],
251
+ customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */"],
233
252
  };
234
253
  }
235
254
 
@@ -321,19 +321,15 @@ ${functions.join("\n\n")}
321
321
  const hookName = inflection.camelize(assertDefined(listApi.options.resourceName), true);
322
322
  const useHookName = `use${inflection.camelize(hookName)}`;
323
323
 
324
+ // ListParams 타입명 구성
325
+ const listParamsType = `${names.capital}ListParams`;
326
+
324
327
  asyncIdConfigs.push(
325
328
  `
326
329
  // AsyncIdConfig: ${names.capital}
327
- export const ${names.capital}AsyncIdConfig = {
328
- useList: ${names.capital}Service.${useHookName} as (
329
- subset: string,
330
- params?: Record<string, unknown>,
331
- options?: { enabled?: boolean }
332
- ) => {
333
- data?: { rows: Record<string, unknown>[] };
334
- isLoading: boolean;
335
- error?: Error;
336
- },
330
+ export const ${names.capital}AsyncIdConfig: AsyncIdConfig<${names.capital}SubsetKey, ${names.capital}SubsetMapping, ${listParamsType}> = {
331
+ placeholderKey: "entity.${names.capital}",
332
+ useList: ${names.capital}Service.${useHookName},
337
333
  };
338
334
  `.trim(),
339
335
  );
@@ -369,6 +365,10 @@ export const ${names.capital}AsyncIdConfig = {
369
365
  ...Object.keys(BUILT_IN_TYPES),
370
366
  ]),
371
367
  customHeaders: [
368
+ "/**",
369
+ " * @generated",
370
+ " * 직접 수정하지 마세요.",
371
+ " */",
372
372
  "/** biome-ignore-all lint: generated는 무시 */",
373
373
  "/** biome-ignore-all assist: generated는 무시 */",
374
374
  "",
@@ -376,6 +376,7 @@ export const ${names.capital}AsyncIdConfig = {
376
376
  `import type { AxiosProgressEvent } from 'axios';`,
377
377
  `import qs from 'qs';`,
378
378
  `import { ${sonamuSharedImports} } from './sonamu.shared';`,
379
+ `import type { AsyncIdConfig } from '@sonamu-kit/react-components/components';`,
379
380
  ],
380
381
  };
381
382
  }