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
@@ -477,6 +477,154 @@ function UserListPage() {
477
477
  - `selectedKeys`: 현재 선택된 키 배열
478
478
  - `isAllSelected`: 전체 선택 여부
479
479
 
480
+ ## IdAsyncSelect
481
+
482
+ Entity의 레코드를 비동기로 검색하여 선택하는 컴포넌트입니다. Entity의 Primary Key 타입에 따라 제네릭 타입을 명시해야 합니다.
483
+
484
+ ### 기본 사용법
485
+
486
+ IdAsyncSelect는 일반적으로 Entity별 래퍼 컴포넌트로 사용합니다:
487
+
488
+ ```typescript
489
+ import { IdAsyncSelect } from "@sonamu-kit/react-components/components";
490
+ import { UserAsyncIdConfig } from "@/services/services.generated";
491
+ import type { UserSubsetKey, UserSubsetMapping } from "@/services/sonamu.generated";
492
+ import type { UserListParams } from "@/services/user/user.types";
493
+
494
+ export type UserIdAsyncSelectProps<T extends UserSubsetKey> = {
495
+ subset: T;
496
+ baseListParams?: UserListParams;
497
+ displayField?: keyof UserSubsetMapping[T] & string;
498
+ valueField?: keyof UserSubsetMapping[T] & string;
499
+ placeholder?: string;
500
+ clearable?: boolean;
501
+ disabled?: boolean;
502
+ className?: string;
503
+ multiple?: boolean;
504
+ value?: number | number[] | null; // Number PK
505
+ onValueChange?: (value: number | number[] | undefined) => void;
506
+ };
507
+
508
+ export function UserIdAsyncSelect<T extends UserSubsetKey>({
509
+ subset,
510
+ value,
511
+ onValueChange,
512
+ baseListParams,
513
+ displayField = "name",
514
+ valueField = "id",
515
+ placeholder = "사용자",
516
+ clearable,
517
+ disabled,
518
+ className,
519
+ multiple = false,
520
+ }: UserIdAsyncSelectProps<T>) {
521
+ return (
522
+ <IdAsyncSelect<number> // Number PK
523
+ config={UserAsyncIdConfig}
524
+ subset={subset}
525
+ baseListParams={baseListParams}
526
+ displayField={displayField}
527
+ valueField={valueField}
528
+ placeholder={placeholder}
529
+ clearable={clearable}
530
+ disabled={disabled}
531
+ className={className}
532
+ multiple={multiple}
533
+ value={value}
534
+ onValueChange={onValueChange}
535
+ />
536
+ );
537
+ }
538
+ ```
539
+
540
+ **주요 Props:**
541
+ - `config`: 자동 생성된 AsyncIdConfig (EntityAsyncIdConfig 형태)
542
+ - `subset`: 조회할 Subset 키
543
+ - `baseListParams`: 목록 조회 필터 파라미터
544
+ - `displayField`: 화면에 표시할 필드명 (기본값: Entity에 따라 다름)
545
+ - `valueField`: value로 사용할 필드명 (기본값: "id")
546
+ - `multiple`: 다중 선택 여부
547
+ - `value`: 현재 선택된 값 (PK 타입에 따라 number 또는 string)
548
+ - `onValueChange`: 값 변경 핸들러
549
+
550
+ ### IMPORTANT: String Primary Key Support
551
+
552
+ **Number PK Entity (대부분의 Entity):**
553
+ ```typescript
554
+ export type PostIdAsyncSelectProps<T extends PostSubsetKey> = {
555
+ // ...
556
+ value?: number | number[] | null;
557
+ onValueChange?: (value: number | number[] | undefined) => void;
558
+ };
559
+
560
+ export function PostIdAsyncSelect<T extends PostSubsetKey>({...}: PostIdAsyncSelectProps<T>) {
561
+ return (
562
+ <IdAsyncSelect<number> // ← Number PK
563
+ config={PostAsyncIdConfig}
564
+ // ...
565
+ />
566
+ );
567
+ }
568
+ ```
569
+
570
+ **String PK Entity (User, Account, Session, Verification 등):**
571
+ ```typescript
572
+ export type AccountIdAsyncSelectProps<T extends AccountSubsetKey> = {
573
+ // ...
574
+ value?: string | string[] | null; // ← String으로 변경
575
+ onValueChange?: (value: string | string[] | undefined) => void;
576
+ };
577
+
578
+ export function AccountIdAsyncSelect<T extends AccountSubsetKey>({...}: AccountIdAsyncSelectProps<T>) {
579
+ return (
580
+ <IdAsyncSelect<string> // ← String PK
581
+ config={AccountAsyncIdConfig}
582
+ // ...
583
+ />
584
+ );
585
+ }
586
+ ```
587
+
588
+ **String PK를 사용하는 주요 Entity:**
589
+ - `User`: 사용자 계정 (일반적으로 UUID 또는 영문자 ID)
590
+ - `Account`: 인증 계정 정보
591
+ - `Session`: 세션 관리
592
+ - `Verification`: 인증 토큰
593
+
594
+ **scaffolding 후 수정 필요:**
595
+ - Sonamu scaffolding은 기본적으로 Number PK를 가정하고 코드를 생성합니다
596
+ - String PK Entity의 경우 생성된 IdAsyncSelect 래퍼 컴포넌트를 수동으로 수정해야 합니다
597
+ - 제네릭 타입, value 타입, onValueChange 타입을 모두 `string`으로 변경해야 합니다
598
+
599
+ ### 폼에서 사용
600
+
601
+ ```tsx
602
+ function PostForm() {
603
+ const { form, setForm, register } = useTypeForm(PostSaveParams, {
604
+ title: "",
605
+ author_id: 0, // or "" for string PK
606
+ });
607
+
608
+ return (
609
+ <form>
610
+ <Input {...register("title")} />
611
+
612
+ {/* Number PK */}
613
+ <UserIdAsyncSelect
614
+ subset="A"
615
+ {...register("author_id")}
616
+ />
617
+
618
+ {/* String PK */}
619
+ <AccountIdAsyncSelect
620
+ subset="A"
621
+ {...register("account_id")}
622
+ />
623
+ </form>
624
+ );
625
+ }
626
+ ```
627
+
480
628
  ## FileInput
481
629
 
482
630
  파일 업로드 컴포넌트 (이미지/일반 파일, eager/lazy 모드)
@@ -721,6 +869,72 @@ registerSSR({
721
869
  });
722
870
  ```
723
871
 
872
+ ## 프로젝트 초기 설정
873
+
874
+ ### 프로젝트명 변경 (신규 프로젝트 생성 시)
875
+
876
+ 사용자가 프로젝트 생성을 요청했을 때, 프론트엔드의 "Sonamu" 텍스트를 프로젝트명으로 변경해야 합니다.
877
+
878
+ **변경해야 할 파일 4개:**
879
+
880
+ 1. **`packages/web/index.html`** - 브라우저 탭 제목
881
+ ```html
882
+ <!-- 변경 전 -->
883
+ <title>Sonamu Project</title>
884
+
885
+ <!-- 변경 후 -->
886
+ <title>{프로젝트명}</title>
887
+ ```
888
+
889
+ 2. **`packages/web/src/routes/__root.tsx`** - TanStack Router head 설정 (가장 중요!)
890
+ ```typescript
891
+ // 변경 전
892
+ head: () => ({
893
+ meta: [
894
+ { title: "Sonamu Project" },
895
+ ],
896
+ }),
897
+
898
+ // 변경 후
899
+ head: () => ({
900
+ meta: [
901
+ { title: "{프로젝트명}" },
902
+ ],
903
+ }),
904
+ ```
905
+
906
+ **중요:** `__root.tsx`를 변경하지 않으면 HMR 시 title이 "Sonamu"로 되돌아갑니다!
907
+
908
+ 3. **`packages/web/src/routes/index.tsx`** - 메인 페이지 제목
909
+ ```tsx
910
+ // 변경 전
911
+ <h1 className="text-2xl font-bold mb-4">Welcome to Sonamu</h1>
912
+
913
+ // 변경 후
914
+ <h1 className="text-2xl font-bold mb-4">Welcome to {프로젝트명}</h1>
915
+ ```
916
+
917
+ 4. **`packages/web/src/components/Sidebar.tsx`** - 사이드바 앱 이름
918
+ ```typescript
919
+ // 변경 전
920
+ const title = isAdmin ? "Admin" : "Sonamu App";
921
+
922
+ // 변경 후
923
+ const title = isAdmin ? "Admin" : "{프로젝트명}";
924
+ ```
925
+
926
+ **작업 순서:**
927
+ 1. 사용자가 "KOPRI 프로젝트 생성해줘" 요청
928
+ 2. API 프로젝트 생성 (`pnpm create sonamu kopri`)
929
+ 3. **프론트엔드 4개 파일에서 "Sonamu" → "KOPRI" 변경**
930
+ 4. 개발 서버 실행 후 브라우저에서 확인
931
+
932
+ **확인 방법:**
933
+ - 브라우저 탭에 프로젝트명이 표시되는지 확인
934
+ - 파일 저장 시 HMR로 탭 제목이 변경되지 않는지 확인 (변경되면 `__root.tsx` 누락)
935
+
936
+ ---
937
+
724
938
  ## Rules
725
939
 
726
940
  - NEVER manually modify `services.generated.ts`
@@ -174,3 +174,98 @@ localizedColumn(tag, "name"); // → "태그" (ko) / "Tag" (en)
174
174
  | `enum.*` | Enum 라벨 | `enum.UserRole.admin` |
175
175
  | `menu.*` | 메뉴/네비게이션 | `menu.home`, `menu.settings` |
176
176
  | `rc.*` | react-components 내장 | `rc.pagination.next` |
177
+
178
+ ## 트러블슈팅
179
+
180
+ ### LocalizedString 타입 오류
181
+
182
+ `Type 'string' is not assignable to type '{ __brand: "LocalizedString"; }'` 오류가 발생하는 경우:
183
+
184
+ **원인 1: ko.ts에 i18n 키 누락**
185
+ ```typescript
186
+ // 잘못된 예: ko.ts에 키가 없으면 타입 오류
187
+ SD("validation.password"); // 키가 ko.ts에 없음
188
+ ```
189
+
190
+ 해결: ko.ts에 키 추가 후 `pnpm sync`
191
+
192
+ **원인 2: 모델에서 SD() 미사용**
193
+ ```typescript
194
+ // 잘못된 예: 문자열 직접 사용
195
+ throw new BadRequestException("이메일이 잘못되었습니다");
196
+
197
+ // 올바른 예: SD() 함수 사용
198
+ throw new BadRequestException(SD("error.invalidEmail"));
199
+ ```
200
+
201
+ 모든 사용자 노출 문자열은 반드시 SD() 함수로 감싸야 합니다.
202
+
203
+ ---
204
+
205
+ ## 타입 안정성
206
+
207
+ ### String PK 지원
208
+
209
+ Entity가 String PK를 사용하는 경우 i18n 함수 타입도 이를 반영해야 합니다.
210
+
211
+ **BAD: Number만 지원**
212
+ ```typescript
213
+ // ko.ts
214
+ notFound: (name: string, id: number) => `존재하지 않는 ${name} ID ${id}`,
215
+
216
+ // String PK 엔티티에서 타입 오류 발생
217
+ // Account, Session, User, Verification 등
218
+ throw new NotFoundException(SD("notFound")(this.modelName, id));
219
+ // Error: Argument of type 'string' is not assignable to parameter of type 'number'
220
+ ```
221
+
222
+ **GOOD: Union 타입으로 둘 다 지원**
223
+ ```typescript
224
+ // ko.ts
225
+ notFound: (name: string, id: number | string) => `존재하지 않는 ${name} ID ${id}`,
226
+
227
+ // Number PK, String PK 모두 사용 가능
228
+ throw new NotFoundException(SD("notFound")(this.modelName, id));
229
+ ```
230
+
231
+ **적용 대상 i18n 키:**
232
+ - `notFound`: Entity 조회 실패
233
+ - `error.entityNotFound`: 구체적인 엔티티 조회 실패 (deprecated, notFound 사용 권장)
234
+
235
+ ### 네임스페이스별 타입 패턴
236
+
237
+ | i18n 키 | 파라미터 타입 | 용도 |
238
+ |---------|--------------|------|
239
+ | `notFound` | `(name: string, id: number \| string)` | Entity 조회 실패 |
240
+ | `search.invalidField` | `(field: string)` | 잘못된 검색 필드 |
241
+ | `validation.required` | `(field: string)` | 필수 필드 검증 |
242
+ | `validation.maxLength` | `(field: string, max: number)` | 최대 길이 검증 |
243
+ | `validation.minLength` | `(field: string, min: number)` | 최소 길이 검증 |
244
+ | `entity.create` | `(name: string)` | Entity 생성 화면 |
245
+ | `entity.edit` | `(name: string, id: number \| string)` | Entity 수정 화면 |
246
+
247
+ ### PK 타입 확인 방법
248
+
249
+ Entity 파일에서 id 필드 타입을 확인:
250
+
251
+ ```json
252
+ // String PK
253
+ {
254
+ "props": {
255
+ "id": { "type": "varchar(255)", "autoIncrement": false }
256
+ }
257
+ }
258
+
259
+ // Number PK (기본값)
260
+ {
261
+ "props": {
262
+ "id": { "type": "int", "autoIncrement": true }
263
+ }
264
+ }
265
+ ```
266
+
267
+ **KOPRI 프로젝트의 String PK 엔티티:**
268
+ - Account
269
+ - Session
270
+ - User
271
+ - Verification
@@ -456,3 +456,156 @@ if (params.orderBy) {
456
456
  - 스캐폴딩 후 model의 orderBy 케이스 확인
457
457
  - entity.json의 orderBy enum과 일치하는지 확인
458
458
  - search 케이스, enhancers 등 다른 커스텀 로직도 확인
459
+
460
+ ---
461
+
462
+ ## 코드 품질과 일관성
463
+
464
+ ### DRY 원칙: this.modelName 사용
465
+
466
+ 에러 메시지에서 모델명을 하드코딩하지 않고 `this.modelName`을 사용합니다.
467
+
468
+ **BAD: 모델명 하드코딩**
469
+ ```typescript
470
+ // department.model.ts
471
+ if (!rows[0]) {
472
+ throw new NotFoundException(SD("notFound")("Department", id));
473
+ }
474
+
475
+ // user.model.ts
476
+ if (!rows[0]) {
477
+ throw new NotFoundException(SD("notFound")("User", id));
478
+ }
479
+ ```
480
+
481
+ **GOOD: this.modelName 활용**
482
+ ```typescript
483
+ // 모든 Model 공통
484
+ if (!rows[0]) {
485
+ throw new NotFoundException(SD("notFound")(this.modelName, id));
486
+ }
487
+ ```
488
+
489
+ **장점:**
490
+ - 복붙 실수 방지: 다른 모델 코드 복사 시 모델명 수정 불필요
491
+ - 일관성: 모든 모델이 동일한 패턴 사용
492
+ - 유지보수: constructor의 modelName만 변경하면 모든 에러 메시지 자동 반영
493
+
494
+ ### 일관된 i18n 키 사용
495
+
496
+ 프로젝트 전체에서 동일한 목적의 i18n 키를 일관되게 사용합니다.
497
+
498
+ **BAD: 중복된 i18n 키**
499
+ ```typescript
500
+ // 여러 모델에서 서로 다른 키 사용
501
+ throw new NotFoundException(SD("error.entityNotFound")(this.modelName, id));
502
+ throw new NotFoundException(SD("error.notFound")(this.modelName, id));
503
+ throw new NotFoundException(SD("notFound")(this.modelName, id));
504
+
505
+ // 검색 필드 오류
506
+ throw new BadRequestException(SD("error.unknownSearchField")(params.search));
507
+ throw new BadRequestException(SD("error.invalidSearchField")(params.search));
508
+ ```
509
+
510
+ **GOOD: 표준 i18n 키 사용**
511
+ ```typescript
512
+ // Entity 조회 실패 - 짧고 명확
513
+ throw new NotFoundException(SD("notFound")(this.modelName, id));
514
+
515
+ // 검색 필드 오류 - search 네임스페이스
516
+ throw new BadRequestException(SD("search.invalidField")(params.search));
517
+ ```
518
+
519
+ **권장 i18n 키 패턴:**
520
+ | 상황 | i18n 키 | 사용처 |
521
+ |------|---------|--------|
522
+ | Entity 조회 실패 | `notFound` | findById |
523
+ | 잘못된 검색 필드 | `search.invalidField` | findMany search |
524
+ | 필수 필드 누락 | `validation.required` | save 검증 |
525
+ | 권한 없음 | `error.forbidden` | guards 실패 |
526
+ | 로그인 필요 | `error.loginRequired` | Context.user null |
527
+
528
+ ### 벌크 리팩토링 전략
529
+
530
+ 여러 모델 파일을 일관되게 수정할 때 sed를 활용한 자동화:
531
+
532
+ **1단계: 패턴 확인**
533
+ ```bash
534
+ # 수정 대상 파일 찾기
535
+ grep -r 'SD("error.entityNotFound")' packages/api/src/application/*/
536
+ ```
537
+
538
+ **2단계: 변경 검증 (dry-run)**
539
+ ```bash
540
+ # 변경될 내용 미리 확인
541
+ sed -n 's/SD("error.entityNotFound")(\(.*\), id)/SD("notFound")(this.modelName, id)/p' file.ts
542
+ ```
543
+
544
+ **3단계: 일괄 적용**
545
+ ```bash
546
+ # 모든 model 파일 수정
547
+ find packages/api/src/application -name "*.model.ts" -exec sed -i '' \
548
+ 's/SD("error.entityNotFound")(\(.*\), id)/SD("notFound")(this.modelName, id)/g' {} \;
549
+ ```
550
+
551
+ **4단계: 빌드로 검증**
552
+ ```bash
553
+ # TypeScript 타입 체크
554
+ pnpm typecheck
555
+
556
+ # 전체 빌드
557
+ pnpm build
558
+ ```
559
+
560
+ **주의사항:**
561
+ - 반드시 git commit 후 실행 (롤백 가능하도록)
562
+ - dry-run으로 변경 내용 먼저 확인
563
+ - 빌드로 타입 오류 체크
564
+ - 테스트 실행으로 동작 검증
565
+
566
+ ### 타입 체크 패턴
567
+
568
+ **satisfies vs as const:**
569
+
570
+ ```typescript
571
+ // BAD: 타입 단언으로 타입 체크 우회
572
+ const params = {
573
+ num: 24,
574
+ page: 1,
575
+ search: "id" as const,
576
+ orderBy: "wrong-value" as const, // 오류 감지 안 됨
577
+ ...rawParams,
578
+ } as RoleListParams;
579
+
580
+ // GOOD: satisfies로 컴파일 타임 검증
581
+ const params = {
582
+ num: 24,
583
+ page: 1,
584
+ search: "id" as const,
585
+ orderBy: "wrong-value" as const, // 컴파일 오류 발생!
586
+ ...rawParams,
587
+ } satisfies RoleListParams;
588
+ ```
589
+
590
+ **적용 권장 위치:**
591
+ - findMany의 params 기본값
592
+ - 복잡한 객체 리터럴 (타입 체크가 중요한 경우)
593
+
594
+ ### 코드 리뷰 체크리스트
595
+
596
+ 새로운 Model 작성 시:
597
+ - [ ] `this.modelName` 사용 (하드코딩 금지)
598
+ - [ ] 표준 i18n 키 사용 (`notFound`, `search.invalidField`)
599
+ - [ ] satisfies 키워드 활용 (타입 안전성)
600
+ - [ ] debug 옵션 불필요하게 명시하지 않음
601
+ - [ ] orderBy 모든 케이스 exhaustive 처리
602
+ - [ ] ManyToMany relation이 있으면 _ids 배열 SaveParams에 추가
603
+
604
+ 20개 Model 일괄 수정 시:
605
+ - [ ] miomock 같은 레퍼런스 코드와 패턴 비교
606
+ - [ ] 불일치하는 패턴 우선순위 정리
607
+ - [ ] sed 등으로 자동화 스크립트 작성
608
+ - [ ] 변경 전 git commit
609
+ - [ ] dry-run으로 변경 내용 검증
610
+ - [ ] pnpm typecheck로 타입 오류 확인
611
+ - [ ] pnpm test로 동작 검증
@@ -10,17 +10,176 @@ description: Sonamu 프로젝트 생성 및 초기화. Entity 설계 요청 시
10
10
  **한 번에 하나씩, 순서대로 진행:**
11
11
 
12
12
  ```
13
- 1. 프로젝트 생성 확인 ← 첫 번째
13
+ 0. 작업 경로 확인 ← 최우선
14
+ 1. 프로젝트 생성 확인
14
15
  2. (프로젝트 없으면) 생성 여부 확인
15
16
  3. (생성 원하면) 프로젝트명 확인
16
17
  4. (생성 원하면) 기본값 사용 vs 옵션 설정
17
18
  5. 프로젝트 생성 실행
18
- 6. 설정 확인/커스터마이징 (config.md 참조)
19
- 7. entity-basic.md 이동
19
+ 6. (요구사항 제공 시) 요구사항 문서화 ← 중요!
20
+ 7. 설정 확인/커스터마이징 (config.md 참조)
21
+ 8. → entity-basic.md로 이동
20
22
  ```
21
23
 
22
24
  ---
23
25
 
26
+ ## 요구사항 문서화
27
+
28
+ **사용자가 프로젝트 생성과 함께 요구사항을 제공한 경우, 반드시 문서화합니다.**
29
+
30
+ ### 프로세스
31
+
32
+ 1. **프로젝트 생성 완료 확인**
33
+ - `pnpm create sonamu` 완료
34
+ - `pnpm install` 완료
35
+ - `.claude/skills/project/` 디렉토리 자동 생성 확인
36
+
37
+ 2. **요구사항 작성**
38
+ ```bash
39
+ # .claude/skills/project/requirements.md 파일에 작성
40
+ ```
41
+
42
+ 3. **작성 내용**
43
+ - 프로젝트 개요
44
+ - 핵심 기능 목록
45
+ - 사용자 역할 정의
46
+ - 주요 엔티티 목록
47
+ - 비즈니스 규칙
48
+ - 기술 스택
49
+ - 추가 요구사항
50
+
51
+ 4. **지속적 참조**
52
+ - 이후 모든 개발 과정에서 이 문서를 참조
53
+ - Entity 설계, API 개발, 비즈니스 로직 구현 시 일관성 유지
54
+
55
+ ### 예시 시나리오
56
+
57
+ ```
58
+ 사용자: "연구과제 관리 시스템을 만들려고 해.
59
+ 공고, 과제, 평가 관리가 필요하고,
60
+ 관리자/평가위원/신청자 역할이 있어."
61
+
62
+ Claude:
63
+ 1. 프로젝트 경로 확인
64
+ 2. "pnpm create sonamu research_system --yes" 실행
65
+ 3. 프로젝트 생성 완료
66
+ 4. .claude/skills/project/requirements.md에 다음 내용 작성:
67
+
68
+ ---
69
+ # 연구과제 관리 시스템
70
+
71
+ ## 프로젝트 개요
72
+ 연구과제 공모, 지원, 평가를 관리하는 시스템
73
+
74
+ ## 핵심 기능
75
+ - 공고 관리 (생성, 수정, 발행, 마감)
76
+ - 과제 지원 및 관리
77
+ - 평가 프로세스 (평가위원 배정, 평가표 작성, 점수 산정)
78
+ - 사용자 권한 관리
79
+
80
+ ## 사용자 역할
81
+ - 관리자: 전체 시스템 관리, 공고 생성, 평가위원 배정
82
+ - 평가위원: 평가표 작성, 점수 부여
83
+ - 신청자: 과제 지원, 진행 상황 확인
84
+
85
+ ## 주요 엔티티
86
+ - Announcement (공고)
87
+ - Project (과제)
88
+ - Evaluation (평가)
89
+ - User (사용자)
90
+ - ...
91
+ ---
92
+
93
+ 5. 이후 Entity 설계 시 이 문서 참조하여 일관되게 진행
94
+ ```
95
+
96
+ ### 중요 사항
97
+
98
+ **반드시 문서화해야 하는 경우:**
99
+ - 사용자가 프로젝트 생성과 함께 요구사항/기능 명세를 제공한 경우
100
+ - 비즈니스 규칙이나 특별한 제약사항이 있는 경우
101
+
102
+ **문서화 시점:**
103
+ - 프로젝트 생성 직후
104
+ - Entity 설계 시작 전
105
+
106
+ **문서 활용:**
107
+ - Entity 설계 시 참조
108
+ - API 비즈니스 로직 구현 시 참조
109
+ - 테스트 케이스 작성 시 참조
110
+ - 프론트엔드 UI 설계 시 참조
111
+
112
+ ---
113
+
114
+ ## 0. 작업 경로 확인
115
+
116
+ **프로젝트 생성 전 반드시 확인:**
117
+
118
+ 개발자들은 종종 프로젝트 유형에 따라 작업 디렉토리를 분리합니다:
119
+ - 회사/프레임워크 프로젝트: `~/Development/`
120
+ - 개인 프로젝트: `~/dev/programming/`
121
+ - 클라이언트 작업: `~/Projects/clients/`
122
+
123
+ ### 확인 프로세스
124
+
125
+ 1. **현재 작업 디렉토리 파악**
126
+ ```bash
127
+ pwd # 현재 디렉토리 확인
128
+ ```
129
+
130
+ 2. **사용자에게 확인 질문**
131
+ ```
132
+ "현재 {현재_디렉토리}에서 요청하셨는데, 여기에 프로젝트를 생성할까요?
133
+ 다른 경로에서 작업하신다면 프로젝트를 생성할 디렉토리를 말씀해 주세요.
134
+
135
+ 1. 현재 디렉토리에 생성
136
+ 2. 다른 디렉토리 지정"
137
+ ```
138
+
139
+ 3. **선택에 따른 처리**
140
+
141
+ **1번 선택 시:**
142
+ - 현재 디렉토리에서 바로 진행
143
+ - 다음 단계(프로젝트 생성 확인)로 이동
144
+
145
+ **2번 선택 시:**
146
+ - "프로젝트를 생성할 디렉토리 경로를 알려주세요."
147
+ - 경로 받은 후: `cd {지정_경로}`
148
+ - 디렉토리 존재 여부 확인
149
+ - 디렉토리 없으면: `mkdir -p {지정_경로} && cd {지정_경로}`
150
+ - 다음 단계로 이동
151
+
152
+ ### 예시 대화
153
+
154
+ ```
155
+ 사용자: "설문조사 시스템을 만들려고 해. Entity 설계해줘."
156
+
157
+ Claude: "현재 /Users/noa/Documents에서 요청하셨는데, 여기에 프로젝트를 생성할까요?
158
+ 다른 경로에서 작업하신다면 프로젝트를 생성할 디렉토리를 말씀해 주세요.
159
+
160
+ 1. 현재 디렉토리에 생성
161
+ 2. 다른 디렉토리 지정"
162
+
163
+ 사용자: "2"
164
+
165
+ Claude: "프로젝트를 생성할 디렉토리 경로를 알려주세요."
166
+
167
+ 사용자: "/Users/noa/Development"
168
+
169
+ Claude: "/Users/noa/Development로 이동합니다.
170
+
171
+ Sonamu 프로젝트가 이미 생성되어 있나요?"
172
+ ```
173
+
174
+ ### 주의사항
175
+
176
+ - **상대 경로도 허용**: `~/Development`, `../projects` 등
177
+ - **틸드(~) 확장**: 홈 디렉토리 자동 확장
178
+ - **경로 검증**: 존재하지 않는 경로는 생성 여부 확인
179
+ - **Working directory 변경**: 경로 이동 후 모든 명령어는 새 경로 기준
180
+
181
+ ---
182
+
24
183
  ## 전체 프로세스 상세
25
184
 
26
185
  ### A. Sonamu 개발자용 (로컬 링크)
@@ -144,6 +303,14 @@ pnpm -r build
144
303
  ```
145
304
  사용자: "설문조사 시스템을 만들려고 해. Entity 설계해줘."
146
305
 
306
+ Claude: "현재 /Users/noa/Documents에서 요청하셨는데, 여기에 프로젝트를 생성할까요?
307
+ 다른 경로에서 작업하신다면 프로젝트를 생성할 디렉토리를 말씀해 주세요.
308
+
309
+ 1. 현재 디렉토리에 생성
310
+ 2. 다른 디렉토리 지정"
311
+
312
+ 사용자: "1"
313
+
147
314
  Claude: "Sonamu 프로젝트가 이미 생성되어 있나요?"
148
315
 
149
316
  사용자: "아니"
@@ -175,13 +342,16 @@ pnpm create sonamu survey_system --yes
175
342
 
176
343
  Claude: "설문조사 시스템 Entity를 설계하기 전에 확인할게요.
177
344
 
178
- 1. Sonamu 프로젝트가 이미 생성되어 있나요?
179
- - 생성되어 있다면 프로젝트 경로를 알려주세요.
180
- - 아직 없다면 pnpm create sonamu로 먼저 생성할까요?
181
- 2. 프로젝트명은 무엇으로 할까요?
182
- 3. 어느 경로에 생성할까요?"
345
+ 1. 어느 경로에 프로젝트를 생성할까요?
346
+ 2. Sonamu 프로젝트가 이미 생성되어 있나요?
347
+ 3. 프로젝트명은 무엇으로 할까요?
348
+ 4. 기본값으로 생성할까요, 옵션을 직접 설정할까요?"
183
349
  ```
184
350
 
351
+ **잘못된 이유:**
352
+ - 여러 질문을 한 번에 던짐 (한 번에 하나씩 질문해야 함)
353
+ - 질문 순서가 명확하지 않음
354
+
185
355
  ---
186
356
 
187
357
  ## 프로젝트 생성 후 설정