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
@@ -67,7 +67,8 @@ export class FixtureManagerClass {
67
67
  // UpsertBuilder 기반 import를 위한 상태
68
68
  private builder: UpsertBuilder = new UpsertBuilder();
69
69
  private fixtureRefMap: Map<string, UBRef> = new Map();
70
- private skippedFixtures: Map<string, { entityId: string; existingId: number }> = new Map();
70
+ private skippedFixtures: Map<string, { entityId: string; existingId: number | string }> =
71
+ new Map();
71
72
 
72
73
  init() {
73
74
  if (this._tdb !== null) {
@@ -132,6 +133,45 @@ export class FixtureManagerClass {
132
133
  env: { ...process.env, ...fixturePgEnv } as NodeJS.ProcessEnv,
133
134
  shell: "/bin/bash",
134
135
  });
136
+
137
+ // 3. 시퀀스 리셋 (데이터 복사 후 시퀀스를 MAX(id)로 정렬)
138
+ await this.resetSequences(Sonamu.dbConfig.test);
139
+ }
140
+
141
+ /**
142
+ * 모든 테이블의 시퀀스를 현재 MAX(id)로 리셋합니다.
143
+ * fixture sync 후 시퀀스가 실제 데이터와 맞지 않는 문제를 해결합니다.
144
+ */
145
+ private async resetSequences(dbConfig: SonamuDBConfig["test"]) {
146
+ const testDb = createKnexInstance(dbConfig);
147
+ const entities = EntityManager.getAllEntities();
148
+
149
+ try {
150
+ for (const entity of entities) {
151
+ const tableName = entity.table || entity.id.toLowerCase();
152
+
153
+ // id 필드의 타입을 확인합니다
154
+ const idProp = entity.props.find((p) => p.name === "id");
155
+ const idType = idProp?.type;
156
+
157
+ // integer나 bigInteger가 아닌 경우 sequence reset을 스킵합니다 (text, uuid 등)
158
+ if (!idType || (idType !== "integer" && idType !== "bigInteger")) {
159
+ console.log(`Skipping sequence reset for ${tableName} (id type: ${idType || "unknown"})`);
160
+ continue;
161
+ }
162
+
163
+ // PostgreSQL 시퀀스를 현재 테이블의 MAX(id)로 리셋합니다.
164
+ // 세 번째 인자를 생략하면 기본값 true가 적용되어, 다음 INSERT 시 MAX(id)+1부터 시작합니다.
165
+ await testDb.raw(`
166
+ SELECT setval(
167
+ pg_get_serial_sequence('public.${tableName}', 'id'),
168
+ COALESCE((SELECT MAX(id) FROM ${tableName}), 1)
169
+ )
170
+ `);
171
+ }
172
+ } finally {
173
+ await testDb.destroy();
174
+ }
135
175
  }
136
176
 
137
177
  private visitedRecords = new Set<string>();
@@ -330,7 +370,7 @@ export class FixtureManagerClass {
330
370
  async createFixtureRecord(
331
371
  entity: Entity,
332
372
  row: {
333
- id: number;
373
+ id: number | string;
334
374
  [key: string]: string | number | boolean | null;
335
375
  },
336
376
  options?: {
@@ -344,7 +384,7 @@ export class FixtureManagerClass {
344
384
  const create = async (
345
385
  entity: Entity,
346
386
  row: {
347
- id: number;
387
+ id: number | string;
348
388
  [key: string]: string | number | boolean | null;
349
389
  },
350
390
  ) => {
@@ -389,12 +429,16 @@ export class FixtureManagerClass {
389
429
  .pluck("id");
390
430
  record.columns[prop.name].value = relatedIds;
391
431
  } else if (isOneToOneRelationProp(prop) && !prop.hasJoinColumn) {
432
+ // 역방향 OneToOne: FK를 가진 관련 엔티티를 찾습니다
433
+ // 예시: User OneToOne Employee (Employee가 user_id FK를 가짐)
392
434
  const relatedEntity = EntityManager.get(prop.with);
393
435
  const relatedProp = relatedEntity.props.find(
394
436
  (p) => isRelationProp(p) && p.with === entity.id,
395
437
  );
396
- if (relatedProp) {
397
- const relatedRow = await db(relatedEntity.table).where("id", row.id).first();
438
+ if (relatedProp && isRelationProp(relatedProp)) {
439
+ // 관련 엔티티에서 FK 컬럼으로 쿼리합니다 (id 아님)
440
+ const fkColumn = `${relatedProp.name}_id`;
441
+ const relatedRow = await db(relatedEntity.table).where(fkColumn, row.id).first();
398
442
  record.columns[prop.name].value = relatedRow?.id;
399
443
  }
400
444
  } else if (isRelationProp(prop)) {
@@ -553,7 +597,48 @@ export class FixtureManagerClass {
553
597
  // 5. ManyToMany 관계 처리
554
598
  await this.processManyToManyRelations(trx, fixtures, insertedIdsByTable);
555
599
 
556
- // 6. 결과 수집
600
+ // 6. PostgreSQL 시퀀스 리셋
601
+ // Fixture 삽입 후 각 테이블의 ID 시퀀스를 최대 ID 값으로 업데이트합니다.
602
+ // 이렇게 하지 않으면 다음 INSERT 시 ID가 2000번대로 생성될 수 있습니다.
603
+ console.log(chalk.blue("Resetting sequences..."));
604
+ for (const tableName of tableOrder) {
605
+ try {
606
+ // Entity를 찾아서 id 타입 확인
607
+ const entity = EntityManager.getAllEntities().find(
608
+ (e) => e.table === tableName || e.id.toLowerCase() === tableName,
609
+ );
610
+
611
+ if (entity) {
612
+ const idProp = entity.props.find((p) => p.name === "id");
613
+ const idType = idProp?.type;
614
+
615
+ // integer나 bigInteger가 아닌 경우 sequence reset을 스킵합니다
616
+ if (!idType || (idType !== "integer" && idType !== "bigInteger")) {
617
+ console.log(
618
+ chalk.gray(
619
+ `Skipped sequence reset for ${tableName} (id type: ${idType || "unknown"})`,
620
+ ),
621
+ );
622
+ continue;
623
+ }
624
+ }
625
+
626
+ // 테이블의 최대 ID 조회
627
+ const maxIdResult = await trx(tableName).max("id as max_id").first();
628
+ const maxId = maxIdResult?.max_id;
629
+
630
+ if (maxId !== null && maxId !== undefined) {
631
+ // 시퀀스를 최대 ID로 설정
632
+ await trx.raw(`SELECT setval('${tableName}_id_seq', ?)`, [maxId]);
633
+ console.log(chalk.green(`Reset sequence for ${tableName}: ${maxId}`));
634
+ }
635
+ } catch (_err) {
636
+ // 시퀀스가 없는 테이블(join table 등)은 무시
637
+ console.log(chalk.gray(`Skipped sequence reset for ${tableName}`));
638
+ }
639
+ }
640
+
641
+ // 7. 결과 수집
557
642
  for (const fixture of fixtures) {
558
643
  const entity = EntityManager.get(fixture.entityId);
559
644
 
@@ -1,5 +1,8 @@
1
1
  export * from "./bootstrap";
2
+ export * from "./data-explorer";
3
+ export * from "./fixture-generator";
2
4
  export * from "./fixture-loader";
5
+ export * from "./fixture-manager";
3
6
  export * from "./global-setup";
4
7
  export * from "./naite-vitest-reporter";
5
8
  export * from "./parallel-db-manager";
@@ -16,6 +16,34 @@ export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K
16
16
  /*
17
17
  Model-Definition
18
18
  */
19
+
20
+ /**
21
+ * cone: 범용 메타데이터 시스템
22
+ *
23
+ * Entity, Prop, Enum, Subset에 추가할 수 있는 범용 메타데이터입니다.
24
+ * Fixture 생성, UI 라벨, 문서화 등 다양한 용도로 활용할 수 있습니다.
25
+ */
26
+ export type Cone = {
27
+ // 일반 정보
28
+ desc?: string; // 짧은 설명 (UI 라벨용)
29
+ note?: string; // 자유로운 메모 (무제한 길이)
30
+ tags?: string[]; // 분류/검색용 태그
31
+
32
+ // Fixture 생성 관련
33
+ fixtureHint?: string; // 생성 힌트 (무제한 길이, 짧은 패턴 또는 긴 설명 가능)
34
+ fixtureGenerator?: string; // Faker.js 코드 또는 커스텀 함수
35
+ fixtureDefault?: unknown; // 기본값
36
+
37
+ // 참조 데이터 관련
38
+ dataSource?: {
39
+ strategy: "sample" | "ids" | "query" | "file" | "recent" | "random";
40
+ config?: unknown; // 전략별 설정
41
+ };
42
+
43
+ // 확장성
44
+ [key: string]: unknown; // 사용자 정의 메타데이터
45
+ };
46
+
19
47
  export type GeneratedColumnType = "STORED" | "VIRTUAL";
20
48
  export type GeneratedColumn = {
21
49
  type: GeneratedColumnType;
@@ -28,7 +56,18 @@ export type CommonProp = {
28
56
  desc?: string;
29
57
  dbDefault?: string;
30
58
  generated?: GeneratedColumn;
59
+ cone?: Cone; // cone 메타데이터
31
60
  };
61
+
62
+ /**
63
+ * 하위 호환성을 위한 헬퍼 함수
64
+ *
65
+ * desc 필드와 cone.desc 둘 다 지원합니다.
66
+ * cone.desc가 있으면 우선적으로 사용하고, 없으면 desc를 사용합니다.
67
+ */
68
+ export function getDescription(item: { desc?: string; cone?: Cone }): string | undefined {
69
+ return item.cone?.desc || item.desc;
70
+ }
32
71
  export type IntegerProp = CommonProp & {
33
72
  type: "integer";
34
73
  }; // PG: integer / TS: number / JSON: number
@@ -41,13 +80,56 @@ export type BigIntegerProp = CommonProp & {
41
80
  export type BigIntegerArrayProp = CommonProp & {
42
81
  type: "bigInteger[]";
43
82
  }; // PG: bigint[] / TS: bigint[] / JSON: bigint[]
83
+
84
+ /**
85
+ * Zod 4 String Format 타입
86
+ * entity.json에서 string 타입의 prop에 zodFormat 옵션을 지정하여
87
+ * BaseSchema 생성 시 Zod의 string format validation을 적용합니다.
88
+ */
89
+ export const ZodStringFormat = z.enum([
90
+ // 기본 포맷
91
+ "email",
92
+ "uuid",
93
+ "url",
94
+ "httpUrl",
95
+ "hostname",
96
+ "emoji",
97
+ "base64",
98
+ "base64url",
99
+ "hex",
100
+ "jwt",
101
+ "nanoid",
102
+ "cuid",
103
+ "cuid2",
104
+ "ulid",
105
+ "ipv4",
106
+ "ipv6",
107
+ "mac",
108
+ "cidrv4",
109
+ "cidrv6",
110
+ // hash 포맷 (알고리즘별)
111
+ "hashMd5",
112
+ "hashSha1",
113
+ "hashSha256",
114
+ "hashSha384",
115
+ "hashSha512",
116
+ // ISO 포맷
117
+ "isoDate",
118
+ "isoTime",
119
+ "isoDatetime",
120
+ "isoDuration",
121
+ ]);
122
+ export type ZodStringFormat = z.infer<typeof ZodStringFormat>;
123
+
44
124
  export type StringProp = CommonProp & {
45
125
  type: "string";
46
126
  length?: number; // PG: varchar(n), text / TS: string / JSON: string
127
+ zodFormat?: ZodStringFormat;
47
128
  }; // PG: text / TS: string / JSON: string
48
129
  export type StringArrayProp = CommonProp & {
49
130
  type: "string[]";
50
131
  length?: number; // PG: varchar(n)[], text[] / TS: string[] / JSON: string[]
132
+ zodFormat?: ZodStringFormat;
51
133
  }; // PG: varchar(n)[], text[] / TS: string[] / JSON: string[]
52
134
  export type EnumProp = CommonProp & {
53
135
  type: "enum";
@@ -131,6 +213,7 @@ type _RelationProp = {
131
213
  nullable?: boolean; // DEFAULT: false
132
214
  toFilter?: true; // DEFAULT: false
133
215
  desc?: string;
216
+ cone?: Cone; // cone 메타데이터
134
217
  };
135
218
  export type OneToOneRelationProp = _RelationProp & {
136
219
  relationType: "OneToOne";
@@ -312,20 +395,77 @@ export function isInternalSubsetField(f: SubsetField): boolean {
312
395
  return typeof f !== "string" && f.internal === true;
313
396
  }
314
397
 
398
+ /**
399
+ * SubsetDef: Subset 정의
400
+ *
401
+ * 하위 호환성을 위해 SubsetField[] 배열 형태도 지원합니다.
402
+ */
403
+ export type SubsetDef =
404
+ | SubsetField[] // 기존 배열 형태
405
+ | {
406
+ // 새로운 객체 형태
407
+ fields: SubsetField[];
408
+ cone?: Cone;
409
+ };
410
+
411
+ /**
412
+ * EnumDef: Enum 정의
413
+ *
414
+ * 하위 호환성을 위해 Record<string, string> 형태도 지원합니다.
415
+ */
416
+ export type EnumDef =
417
+ | Record<string, string> // 기존 Record 형태
418
+ | {
419
+ // 새로운 객체 형태
420
+ values: Record<string, string>;
421
+ cone?: Cone;
422
+ };
423
+
424
+ /**
425
+ * SubsetDef가 새로운 객체 형태인지 확인
426
+ */
427
+ export function isSubsetDefWithCone(def: SubsetDef): def is { fields: SubsetField[]; cone?: Cone } {
428
+ return !Array.isArray(def) && "fields" in def;
429
+ }
430
+
431
+ /**
432
+ * EnumDef가 새로운 객체 형태인지 확인
433
+ */
434
+ export function isEnumDefWithCone(
435
+ def: EnumDef,
436
+ ): def is { values: Record<string, string>; cone?: Cone } {
437
+ return (
438
+ "values" in def && !("cone" in def && def.cone === undefined && Object.keys(def).length > 1)
439
+ );
440
+ }
441
+
442
+ /**
443
+ * SubsetDef에서 fields 추출
444
+ */
445
+ export function getSubsetFields(def: SubsetDef): SubsetField[] {
446
+ return Array.isArray(def) ? def : def.fields;
447
+ }
448
+
449
+ /**
450
+ * EnumDef에서 values 추출
451
+ */
452
+ export function getEnumDefValues(def: EnumDef): Record<string, string> {
453
+ return isEnumDefWithCone(def) ? def.values : def;
454
+ }
455
+
315
456
  export type EntityJson = {
316
457
  id: string;
317
458
  parentId?: string;
318
459
  table: string;
319
460
  title?: string;
461
+ cone?: Cone; // cone 메타데이터
320
462
  props: EntityProp[];
321
463
  indexes: EntityIndex[];
322
464
  subsets: {
323
- [subset: string]: SubsetField[];
465
+ [subset: string]: SubsetDef;
324
466
  };
325
467
  enums: {
326
- [enumId: string]: {
327
- [key: string]: string;
328
- };
468
+ [enumId: string]: EnumDef;
329
469
  };
330
470
  };
331
471
  export type EntitySubsetRow = {
@@ -671,6 +811,11 @@ export type ApiParam = {
671
811
  defaultDef?: string;
672
812
  };
673
813
  export namespace ApiParamType {
814
+ export type Function = {
815
+ t: "function";
816
+ parameters: ApiParam[];
817
+ returnType: ApiParamType;
818
+ };
674
819
  export type Object = {
675
820
  t: "object";
676
821
  props: ApiParam[];
@@ -834,7 +979,8 @@ export type ApiParamType =
834
979
  | ApiParamType.Ref
835
980
  | ApiParamType.IndexedAccess
836
981
  | ApiParamType.TypeParam
837
- | ApiParamType.TupleType;
982
+ | ApiParamType.TupleType
983
+ | ApiParamType.Function;
838
984
 
839
985
  /* Template */
840
986
  /**
@@ -867,6 +1013,8 @@ export type RenderingNode = {
867
1013
  | "string-image"
868
1014
  | "string-datetime"
869
1015
  | "string-date"
1016
+ | "string-id"
1017
+ | "string-fk_id"
870
1018
  | "datetime"
871
1019
  | "number-plain"
872
1020
  | "number-id"
@@ -896,6 +1044,28 @@ const GeneratedColumnSchema = z.object({
896
1044
  expression: z.string(),
897
1045
  });
898
1046
 
1047
+ /**
1048
+ * Cone 스키마 검증
1049
+ *
1050
+ * cone 메타데이터의 유효성을 검증합니다.
1051
+ */
1052
+ const ConeSchema = z
1053
+ .object({
1054
+ desc: z.string().optional(),
1055
+ note: z.string().optional(),
1056
+ tags: z.array(z.string()).optional(),
1057
+ fixtureHint: z.string().optional(),
1058
+ fixtureGenerator: z.string().optional(),
1059
+ fixtureDefault: z.unknown().optional(),
1060
+ dataSource: z
1061
+ .object({
1062
+ strategy: z.enum(["sample", "ids", "query", "file", "recent", "random"]),
1063
+ config: z.unknown().optional(),
1064
+ })
1065
+ .optional(),
1066
+ })
1067
+ .catchall(z.unknown()); // 사용자 정의 메타데이터 허용
1068
+
899
1069
  const BasePropFields = {
900
1070
  name: z.string(),
901
1071
  desc: z.string().optional(),
@@ -903,6 +1073,7 @@ const BasePropFields = {
903
1073
  toFilter: z.boolean().default(false).optional(),
904
1074
  dbDefault: z.union([z.string(), z.number(), z.boolean()]).optional(),
905
1075
  generated: GeneratedColumnSchema.optional(),
1076
+ cone: ConeSchema.optional(),
906
1077
  };
907
1078
 
908
1079
  // 부가 필드가 필요없는 prop
@@ -936,6 +1107,7 @@ const StringPropSchema = z
936
1107
  ...BasePropFields,
937
1108
  type: z.literal("string"),
938
1109
  length: z.number().optional(),
1110
+ zodFormat: ZodStringFormat.optional(),
939
1111
  })
940
1112
  .strict();
941
1113
  const StringArrayPropSchema = z
@@ -943,6 +1115,7 @@ const StringArrayPropSchema = z
943
1115
  ...BasePropFields,
944
1116
  type: z.literal("string[]"),
945
1117
  length: z.number().optional(),
1118
+ zodFormat: ZodStringFormat.optional(),
946
1119
  })
947
1120
  .strict();
948
1121
 
@@ -1238,21 +1411,49 @@ const EntityIndexSchema = z
1238
1411
  })
1239
1412
  .strict();
1240
1413
 
1414
+ /**
1415
+ * SubsetDef 스키마
1416
+ *
1417
+ * 하위 호환성을 위해 배열 형태와 객체 형태 둘 다 지원합니다.
1418
+ */
1419
+ const SubsetDefSchema = z.union([
1420
+ // 기존 배열 형태
1421
+ z.array(z.union([z.string(), z.object({ field: z.string(), internal: z.boolean().optional() })])),
1422
+ // 새로운 객체 형태
1423
+ z.object({
1424
+ fields: z.array(
1425
+ z.union([z.string(), z.object({ field: z.string(), internal: z.boolean().optional() })]),
1426
+ ),
1427
+ cone: ConeSchema.optional(),
1428
+ }),
1429
+ ]);
1430
+
1431
+ /**
1432
+ * EnumDef 스키마
1433
+ *
1434
+ * 하위 호환성을 위해 Record 형태와 객체 형태 둘 다 지원합니다.
1435
+ */
1436
+ const EnumDefSchema = z.union([
1437
+ // 기존 Record 형태
1438
+ z.record(z.string(), z.string()),
1439
+ // 새로운 객체 형태
1440
+ z.object({
1441
+ values: z.record(z.string(), z.string()),
1442
+ cone: ConeSchema.optional(),
1443
+ }),
1444
+ ]);
1445
+
1241
1446
  export const EntityJsonSchema = z
1242
1447
  .object({
1243
1448
  id: z.string().describe("PascalCase로 된 Entity ID"),
1244
1449
  title: z.string().describe("Entity 이름"),
1245
1450
  table: z.string().describe("snake_case로 된 테이블명"),
1246
1451
  parentId: z.string().optional().describe("부모 Entity ID"),
1452
+ cone: ConeSchema.optional(),
1247
1453
  props: z.array(EntityPropSchema),
1248
1454
  indexes: z.array(EntityIndexSchema),
1249
- subsets: z.record(
1250
- z.string(),
1251
- z.array(
1252
- z.union([z.string(), z.object({ field: z.string(), internal: z.boolean().optional() })]),
1253
- ),
1254
- ),
1255
- enums: z.record(z.string(), z.record(z.string(), z.string())),
1455
+ subsets: z.record(z.string(), SubsetDefSchema),
1456
+ enums: z.record(z.string(), EnumDefSchema),
1256
1457
  })
1257
1458
  .strict();
1258
1459
 
@@ -1312,14 +1513,6 @@ export const TemplateOptions = z.object({
1312
1513
  view_id_all_select: z.object({
1313
1514
  entityId: z.string(),
1314
1515
  }),
1315
- view_id_async_select: z.object({
1316
- entityId: z.string(),
1317
- textField: z.string(),
1318
- }),
1319
- view_enums_select: z.object({
1320
- entityId: z.string(),
1321
- enumId: z.string(),
1322
- }),
1323
1516
  view_enums_buttonset: z.object({
1324
1517
  entityId: z.string(),
1325
1518
  enumId: z.string(),
@@ -1347,8 +1540,6 @@ export const TemplateKey = z.enum([
1347
1540
  "view_search_input",
1348
1541
  "view_form",
1349
1542
  "view_id_all_select",
1350
- "view_id_async_select",
1351
- "view_enums_select",
1352
1543
  "view_enums_buttonset",
1353
1544
  "queries",
1354
1545
  "entry_server",
@@ -1378,7 +1569,7 @@ type ColumnValue = string | number | boolean | Date | null;
1378
1569
  export type FixtureRecord = {
1379
1570
  fixtureId: string;
1380
1571
  entityId: string;
1381
- id: number;
1572
+ id: number | string;
1382
1573
  columns: {
1383
1574
  [key: string]: {
1384
1575
  prop: EntityProp;
@@ -1409,12 +1600,17 @@ export type RelationNode = {
1409
1600
  export interface DatabaseSchemaExtend {}
1410
1601
  // biome-ignore lint/suspicious/noEmptyInterface: sonamu.generated.sso 에서 확장을 위해 준비된 빈 인터페이스
1411
1602
  export interface DatabaseForeignKeys {}
1412
- export type ManyToManyBaseSchema<FromIdKey extends string, ToIdKey extends string> = {
1603
+ export type ManyToManyBaseSchema<
1604
+ FromIdKey extends string,
1605
+ ToIdKey extends string,
1606
+ FromPkType = number,
1607
+ ToPkType = number,
1608
+ > = {
1413
1609
  id: number;
1414
1610
  } & {
1415
- [K in `${FromIdKey}_id`]: number;
1611
+ [K in `${FromIdKey}_id`]: FromPkType;
1416
1612
  } & {
1417
- [K in `${ToIdKey}_id`]: number;
1613
+ [K in `${ToIdKey}_id`]: ToPkType;
1418
1614
  };
1419
1615
 
1420
1616
  // 객체, 함수, 비동기 함수를 모두 포괄하는 타입
@@ -9,6 +9,7 @@ import { EntityManager } from "../entity/entity-manager";
9
9
  import {
10
10
  type EntityProp,
11
11
  type FixtureRecord,
12
+ getEnumDefValues,
12
13
  isInternalSubsetField,
13
14
  normalizeSubsetField,
14
15
  TemplateOptions,
@@ -433,6 +434,7 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
433
434
  if (updates.parentId !== undefined) entity.parentId = updates.parentId;
434
435
  if (updates.title !== undefined) entity.title = updates.title;
435
436
  if (updates.table !== undefined) entity.table = updates.table;
437
+ if (updates.cone !== undefined) entity.cone = updates.cone;
436
438
 
437
439
  // props: merge 시 이름 기준 병합, replace 시 교체
438
440
  if (updates.props !== undefined) {
@@ -483,8 +485,14 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
483
485
  }
484
486
 
485
487
  if (updates.enums !== undefined) {
488
+ const convertedEnums = Object.fromEntries(
489
+ Object.entries(updates.enums).map(([key, enumDef]) => [
490
+ key,
491
+ getEnumDefValues(enumDef),
492
+ ]),
493
+ );
486
494
  entity.enumLabels =
487
- mode === "replace" ? updates.enums : { ...entity.enumLabels, ...updates.enums };
495
+ mode === "replace" ? convertedEnums : { ...entity.enumLabels, ...convertedEnums };
488
496
  }
489
497
 
490
498
  // 저장 전 검증