sonamu 0.10.11 → 0.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/dist/ai/agents/agent.d.ts +1 -1
  2. package/dist/ai/agents/agent.d.ts.map +1 -1
  3. package/dist/ai/agents/agent.js +5 -6
  4. package/dist/ai/agents/types.d.ts +1 -1
  5. package/dist/ai/agents/types.d.ts.map +1 -1
  6. package/dist/ai/providers/rtzr/api.js +18 -18
  7. package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
  8. package/dist/ai/providers/rtzr/error.js +17 -15
  9. package/dist/ai/providers/rtzr/model.d.ts.map +1 -1
  10. package/dist/ai/providers/rtzr/model.js +3 -2
  11. package/dist/ai/providers/rtzr/options.d.ts.map +1 -1
  12. package/dist/ai/providers/rtzr/options.js +16 -16
  13. package/dist/ai/providers/rtzr/provider.d.ts.map +1 -1
  14. package/dist/ai/providers/rtzr/provider.js +9 -9
  15. package/dist/ai/providers/rtzr/utils.d.ts.map +1 -1
  16. package/dist/ai/providers/rtzr/utils.js +1 -1
  17. package/dist/api/caster.d.ts.map +1 -1
  18. package/dist/api/caster.js +17 -17
  19. package/dist/api/code-converters.d.ts +4 -2
  20. package/dist/api/code-converters.d.ts.map +1 -1
  21. package/dist/api/code-converters.js +100 -19
  22. package/dist/api/config.d.ts +16 -1
  23. package/dist/api/config.d.ts.map +1 -1
  24. package/dist/api/config.js +28 -21
  25. package/dist/api/context.d.ts +2 -2
  26. package/dist/api/context.d.ts.map +1 -1
  27. package/dist/api/context.js +1 -1
  28. package/dist/api/decorators.d.ts.map +1 -1
  29. package/dist/api/decorators.js +18 -11
  30. package/dist/api/http-validator.d.ts +36 -0
  31. package/dist/api/http-validator.d.ts.map +1 -0
  32. package/dist/api/http-validator.js +72 -0
  33. package/dist/api/index.js +6 -4
  34. package/dist/api/sonamu.d.ts +59 -20
  35. package/dist/api/sonamu.d.ts.map +1 -1
  36. package/dist/api/sonamu.js +250 -106
  37. package/dist/api/websocket-helpers.d.ts +7 -5
  38. package/dist/api/websocket-helpers.d.ts.map +1 -1
  39. package/dist/api/websocket-helpers.js +6 -6
  40. package/dist/auth/audit-log/builders.d.ts.map +1 -1
  41. package/dist/auth/audit-log/builders.js +1 -1
  42. package/dist/auth/audit-log/client-ip.d.ts +1 -1
  43. package/dist/auth/audit-log/client-ip.d.ts.map +1 -1
  44. package/dist/auth/audit-log/client-ip.js +4 -2
  45. package/dist/auth/audit-log/events.d.ts +5 -1
  46. package/dist/auth/audit-log/events.d.ts.map +1 -1
  47. package/dist/auth/audit-log/events.js +1 -1
  48. package/dist/auth/audit-log/plugin.d.ts +0 -8
  49. package/dist/auth/audit-log/plugin.d.ts.map +1 -1
  50. package/dist/auth/audit-log/plugin.js +48 -52
  51. package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
  52. package/dist/auth/audit-log-ingestor.js +16 -12
  53. package/dist/auth/auth-generator.d.ts.map +1 -1
  54. package/dist/auth/auth-generator.js +1 -1
  55. package/dist/auth/better-auth-request.d.ts.map +1 -1
  56. package/dist/auth/better-auth-request.js +6 -5
  57. package/dist/auth/knex-adapter.js +1 -1
  58. package/dist/auth/plugins/entity-definitions/index.d.ts +13 -1
  59. package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
  60. package/dist/auth/plugins/entity-definitions/index.js +1 -1
  61. package/dist/auth/plugins/wrappers/admin.d.ts +1 -1
  62. package/dist/bin/build-config.d.ts +25 -0
  63. package/dist/bin/build-config.d.ts.map +1 -1
  64. package/dist/bin/build-config.js +105 -2
  65. package/dist/bin/cli.js +133 -29
  66. package/dist/bin/compiler-policy.d.ts +3 -0
  67. package/dist/bin/compiler-policy.d.ts.map +1 -0
  68. package/dist/bin/compiler-policy.js +24 -0
  69. package/dist/bin/fixture.d.ts.map +1 -1
  70. package/dist/bin/fixture.js +5 -3
  71. package/dist/bin/test-command.d.ts +22 -1
  72. package/dist/bin/test-command.d.ts.map +1 -1
  73. package/dist/bin/test-command.js +18 -10
  74. package/dist/bin/ts-loader-registration.d.ts +7 -1
  75. package/dist/bin/ts-loader-registration.d.ts.map +1 -1
  76. package/dist/bin/ts-loader-registration.js +13 -4
  77. package/dist/cache/decorator.d.ts.map +1 -1
  78. package/dist/cache/decorator.js +11 -4
  79. package/dist/compress/compress.d.ts.map +1 -1
  80. package/dist/compress/compress.js +1 -1
  81. package/dist/cone/cone-generator.d.ts +14 -1
  82. package/dist/cone/cone-generator.d.ts.map +1 -1
  83. package/dist/cone/cone-generator.js +13 -9
  84. package/dist/database/_batch_update.js +1 -1
  85. package/dist/database/base-model.d.ts +22 -10
  86. package/dist/database/base-model.d.ts.map +1 -1
  87. package/dist/database/base-model.js +26 -16
  88. package/dist/database/base-model.types.d.ts +15 -3
  89. package/dist/database/base-model.types.d.ts.map +1 -1
  90. package/dist/database/db.d.ts +12 -2
  91. package/dist/database/db.d.ts.map +1 -1
  92. package/dist/database/db.js +10 -7
  93. package/dist/database/knex.d.ts.map +1 -1
  94. package/dist/database/knex.js +17 -14
  95. package/dist/database/puri.d.ts +11 -9
  96. package/dist/database/puri.d.ts.map +1 -1
  97. package/dist/database/puri.js +93 -79
  98. package/dist/database/puri.types.d.ts +21 -21
  99. package/dist/database/puri.types.d.ts.map +1 -1
  100. package/dist/database/puri.types.js +1 -1
  101. package/dist/database/upsert-builder.d.ts +12 -10
  102. package/dist/database/upsert-builder.d.ts.map +1 -1
  103. package/dist/database/upsert-builder.js +20 -12
  104. package/dist/dict/sd.d.ts.map +1 -1
  105. package/dist/dict/sd.js +1 -1
  106. package/dist/dict/sonamu-dictionary.d.ts.map +1 -1
  107. package/dist/dict/sonamu-dictionary.js +1 -1
  108. package/dist/dict/utils.d.ts.map +1 -1
  109. package/dist/dict/utils.js +7 -2
  110. package/dist/entity/entity-manager.d.ts +13 -4
  111. package/dist/entity/entity-manager.d.ts.map +1 -1
  112. package/dist/entity/entity-manager.js +19 -11
  113. package/dist/entity/entity-template-cone.d.ts +73 -5
  114. package/dist/entity/entity-template-cone.d.ts.map +1 -1
  115. package/dist/entity/entity-template-cone.js +4 -2
  116. package/dist/entity/entity.d.ts +6 -2
  117. package/dist/entity/entity.d.ts.map +1 -1
  118. package/dist/entity/entity.js +30 -28
  119. package/dist/env.d.ts +3 -1
  120. package/dist/env.d.ts.map +1 -1
  121. package/dist/env.js +1 -1
  122. package/dist/exceptions/error-handler.d.ts.map +1 -1
  123. package/dist/exceptions/error-handler.js +7 -2
  124. package/dist/exceptions/so-exceptions.d.ts +24 -20
  125. package/dist/exceptions/so-exceptions.d.ts.map +1 -1
  126. package/dist/exceptions/so-exceptions.js +4 -2
  127. package/dist/filter/utils.d.ts +7 -2
  128. package/dist/filter/utils.d.ts.map +1 -1
  129. package/dist/filter/utils.js +8 -6
  130. package/dist/index.d.ts +3 -0
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +13 -7
  133. package/dist/logger/configure.d.ts.map +1 -1
  134. package/dist/logger/configure.js +10 -10
  135. package/dist/migration/code-generation.d.ts.map +1 -1
  136. package/dist/migration/code-generation.js +50 -46
  137. package/dist/migration/index-where-predicate.d.ts.map +1 -1
  138. package/dist/migration/index-where-predicate.js +22 -8
  139. package/dist/migration/migration-set.d.ts.map +1 -1
  140. package/dist/migration/migration-set.js +1 -1
  141. package/dist/migration/migrator.d.ts +61 -36
  142. package/dist/migration/migrator.d.ts.map +1 -1
  143. package/dist/migration/migrator.js +276 -167
  144. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  145. package/dist/migration/postgresql-schema-reader.js +51 -36
  146. package/dist/migration/slack-confirm.d.ts.map +1 -1
  147. package/dist/migration/slack-confirm.js +1 -1
  148. package/dist/migration/types.d.ts +54 -0
  149. package/dist/migration/types.d.ts.map +1 -1
  150. package/dist/naite/naite-reporter.js +2 -2
  151. package/dist/naite/naite.d.ts +74 -15
  152. package/dist/naite/naite.d.ts.map +1 -1
  153. package/dist/naite/naite.js +5 -19
  154. package/dist/runtime-type.d.ts +6 -0
  155. package/dist/runtime-type.d.ts.map +1 -0
  156. package/dist/runtime-type.js +26 -0
  157. package/dist/ssr/hmr-opt-out.d.ts +26 -0
  158. package/dist/ssr/hmr-opt-out.d.ts.map +1 -0
  159. package/dist/ssr/hmr-opt-out.js +59 -0
  160. package/dist/ssr/renderer.d.ts.map +1 -1
  161. package/dist/ssr/renderer.js +4 -1
  162. package/dist/storage/base-file.js +14 -14
  163. package/dist/storage/buffered-file.d.ts +2 -2
  164. package/dist/storage/buffered-file.d.ts.map +1 -1
  165. package/dist/storage/buffered-file.js +12 -12
  166. package/dist/storage/s3-driver.d.ts.map +1 -1
  167. package/dist/storage/s3-driver.js +1 -1
  168. package/dist/storage/uploaded-file.d.ts +2 -2
  169. package/dist/storage/uploaded-file.d.ts.map +1 -1
  170. package/dist/storage/uploaded-file.js +8 -8
  171. package/dist/stream/sse.js +11 -11
  172. package/dist/stream/ws-core.d.ts +1 -1
  173. package/dist/stream/ws-core.d.ts.map +1 -1
  174. package/dist/stream/ws-delivery.d.ts +1 -1
  175. package/dist/stream/ws-delivery.d.ts.map +1 -1
  176. package/dist/stream/ws-delivery.js +6 -4
  177. package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
  178. package/dist/stream/ws-local-connection-store.js +4 -2
  179. package/dist/stream/ws-presence-store.js +1 -1
  180. package/dist/stream/ws-registry.d.ts +4 -4
  181. package/dist/stream/ws-registry.d.ts.map +1 -1
  182. package/dist/stream/ws-registry.js +4 -2
  183. package/dist/stream/ws-telemetry.d.ts +16 -12
  184. package/dist/stream/ws-telemetry.d.ts.map +1 -1
  185. package/dist/stream/ws-telemetry.js +22 -18
  186. package/dist/stream/ws.d.ts +18 -12
  187. package/dist/stream/ws.d.ts.map +1 -1
  188. package/dist/stream/ws.js +26 -22
  189. package/dist/syncer/api-parser.js +1 -1
  190. package/dist/syncer/checksum.d.ts.map +1 -1
  191. package/dist/syncer/checksum.js +7 -8
  192. package/dist/syncer/code-generator.d.ts.map +1 -1
  193. package/dist/syncer/code-generator.js +13 -14
  194. package/dist/syncer/entity-operations.d.ts.map +1 -1
  195. package/dist/syncer/entity-operations.js +5 -6
  196. package/dist/syncer/file-copy.d.ts +5 -0
  197. package/dist/syncer/file-copy.d.ts.map +1 -0
  198. package/dist/syncer/file-copy.js +36 -0
  199. package/dist/syncer/file-patterns.d.ts +1 -0
  200. package/dist/syncer/file-patterns.d.ts.map +1 -1
  201. package/dist/syncer/file-patterns.js +5 -2
  202. package/dist/syncer/file-tracking.d.ts.map +1 -1
  203. package/dist/syncer/file-tracking.js +5 -4
  204. package/dist/syncer/filesystem-dependencies.d.ts +15 -0
  205. package/dist/syncer/filesystem-dependencies.d.ts.map +1 -0
  206. package/dist/syncer/filesystem-dependencies.js +53 -0
  207. package/dist/syncer/module-loader.d.ts +1 -1
  208. package/dist/syncer/module-loader.d.ts.map +1 -1
  209. package/dist/syncer/module-loader.js +6 -4
  210. package/dist/syncer/syncer-actions.d.ts +4 -0
  211. package/dist/syncer/syncer-actions.d.ts.map +1 -1
  212. package/dist/syncer/syncer-actions.js +34 -17
  213. package/dist/syncer/syncer.d.ts +17 -6
  214. package/dist/syncer/syncer.d.ts.map +1 -1
  215. package/dist/syncer/syncer.js +71 -29
  216. package/dist/syncer/watcher.d.ts.map +1 -1
  217. package/dist/syncer/watcher.js +3 -3
  218. package/dist/tasks/decorator.d.ts +6 -6
  219. package/dist/tasks/decorator.d.ts.map +1 -1
  220. package/dist/tasks/decorator.js +4 -4
  221. package/dist/tasks/step-wrapper.d.ts +6 -4
  222. package/dist/tasks/step-wrapper.d.ts.map +1 -1
  223. package/dist/tasks/step-wrapper.js +5 -5
  224. package/dist/tasks/workflow-manager.d.ts.map +1 -1
  225. package/dist/tasks/workflow-manager.js +13 -7
  226. package/dist/template/entity-converter.d.ts +1 -1
  227. package/dist/template/entity-converter.d.ts.map +1 -1
  228. package/dist/template/entity-converter.js +2 -2
  229. package/dist/template/helpers.d.ts +8 -6
  230. package/dist/template/helpers.d.ts.map +1 -1
  231. package/dist/template/helpers.js +1 -1
  232. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  233. package/dist/template/implementations/generated.template.js +4 -4
  234. package/dist/template/implementations/generated_http.template.d.ts +7 -4
  235. package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
  236. package/dist/template/implementations/generated_http.template.js +21 -21
  237. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  238. package/dist/template/implementations/generated_sso.template.js +6 -3
  239. package/dist/template/implementations/http_validators.template.d.ts +18 -0
  240. package/dist/template/implementations/http_validators.template.d.ts.map +1 -0
  241. package/dist/template/implementations/http_validators.template.js +127 -0
  242. package/dist/template/implementations/model.template.d.ts.map +1 -1
  243. package/dist/template/implementations/model.template.js +1 -1
  244. package/dist/template/implementations/queries.template.js +1 -1
  245. package/dist/template/implementations/sd.template.d.ts.map +1 -1
  246. package/dist/template/implementations/sd.template.js +79 -39
  247. package/dist/template/implementations/services.template.d.ts.map +1 -1
  248. package/dist/template/implementations/services.template.js +6 -4
  249. package/dist/template/implementations/view_form.template.d.ts +6 -1
  250. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  251. package/dist/template/implementations/view_form.template.js +23 -15
  252. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  253. package/dist/template/implementations/view_list.template.js +11 -6
  254. package/dist/template/template-manager.d.ts.map +1 -1
  255. package/dist/template/template-manager.js +9 -7
  256. package/dist/template/template-types.d.ts +12 -5
  257. package/dist/template/template-types.d.ts.map +1 -1
  258. package/dist/template/template-types.js +1 -1
  259. package/dist/template/template.d.ts +2 -2
  260. package/dist/template/template.d.ts.map +1 -1
  261. package/dist/template/template.js +6 -4
  262. package/dist/template/zod-converter.d.ts +0 -19
  263. package/dist/template/zod-converter.d.ts.map +1 -1
  264. package/dist/template/zod-converter.js +100 -84
  265. package/dist/testing/_relation-graph.d.ts.map +1 -1
  266. package/dist/testing/_relation-graph.js +2 -1
  267. package/dist/testing/bootstrap.d.ts.map +1 -1
  268. package/dist/testing/bootstrap.js +9 -6
  269. package/dist/testing/data-explorer.d.ts +32 -7
  270. package/dist/testing/data-explorer.d.ts.map +1 -1
  271. package/dist/testing/data-explorer.js +77 -31
  272. package/dist/testing/dev-test-routes.d.ts +8 -1
  273. package/dist/testing/dev-test-routes.d.ts.map +1 -1
  274. package/dist/testing/dev-test-routes.js +125 -108
  275. package/dist/testing/dev-vitest-manager.d.ts +54 -8
  276. package/dist/testing/dev-vitest-manager.d.ts.map +1 -1
  277. package/dist/testing/dev-vitest-manager.js +24 -9
  278. package/dist/testing/faker-mappings.d.ts +412 -1
  279. package/dist/testing/faker-mappings.d.ts.map +1 -1
  280. package/dist/testing/faker-mappings.js +1 -1
  281. package/dist/testing/filesystem.d.ts +2 -0
  282. package/dist/testing/filesystem.d.ts.map +1 -0
  283. package/dist/testing/filesystem.js +7 -0
  284. package/dist/testing/fixture-generator.d.ts +25 -8
  285. package/dist/testing/fixture-generator.d.ts.map +1 -1
  286. package/dist/testing/fixture-generator.js +132 -73
  287. package/dist/testing/fixture-loader.d.ts +3 -1
  288. package/dist/testing/fixture-loader.d.ts.map +1 -1
  289. package/dist/testing/fixture-loader.js +1 -20
  290. package/dist/testing/fixture-manager.d.ts +8 -7
  291. package/dist/testing/fixture-manager.d.ts.map +1 -1
  292. package/dist/testing/fixture-manager.js +91 -59
  293. package/dist/testing/global-setup.d.ts.map +1 -1
  294. package/dist/testing/global-setup.js +5 -2
  295. package/dist/testing/index.d.ts +2 -0
  296. package/dist/testing/index.d.ts.map +1 -1
  297. package/dist/testing/index.js +6 -3
  298. package/dist/types/types.d.ts +101 -99
  299. package/dist/types/types.d.ts.map +1 -1
  300. package/dist/types/types.js +234 -225
  301. package/dist/ui/ai-api.d.ts.map +1 -1
  302. package/dist/ui/ai-api.js +1 -1
  303. package/dist/ui/ai-client.d.ts.map +1 -1
  304. package/dist/ui/ai-client.js +28 -18
  305. package/dist/ui/api.d.ts.map +1 -1
  306. package/dist/ui/api.js +146 -210
  307. package/dist/ui/async-route.d.ts +3 -0
  308. package/dist/ui/async-route.d.ts.map +1 -0
  309. package/dist/ui/async-route.js +12 -0
  310. package/dist/ui/cdd-service.d.ts +9 -7
  311. package/dist/ui/cdd-service.d.ts.map +1 -1
  312. package/dist/ui/cdd-service.js +7 -5
  313. package/dist/ui/migrations-api.d.ts +3 -0
  314. package/dist/ui/migrations-api.d.ts.map +1 -0
  315. package/dist/ui/migrations-api.js +215 -0
  316. package/dist/ui-web/assets/index-D3QNH-Dv.css +1 -0
  317. package/dist/ui-web/assets/index-DV1AAq6W.js +178 -0
  318. package/dist/ui-web/index.html +2 -2
  319. package/dist/utils/formatter.d.ts.map +1 -1
  320. package/dist/utils/formatter.js +7 -5
  321. package/dist/utils/fs-utils.d.ts +3 -3
  322. package/dist/utils/fs-utils.d.ts.map +1 -1
  323. package/dist/utils/fs-utils.js +6 -6
  324. package/dist/utils/model.d.ts +3 -5
  325. package/dist/utils/model.d.ts.map +1 -1
  326. package/dist/utils/model.js +1 -1
  327. package/dist/utils/object-utils.d.ts +1 -1
  328. package/dist/utils/object-utils.d.ts.map +1 -1
  329. package/dist/utils/object-utils.js +17 -16
  330. package/dist/utils/process-utils.d.ts.map +1 -1
  331. package/dist/utils/process-utils.js +4 -2
  332. package/dist/utils/runtime-value.d.ts +13 -0
  333. package/dist/utils/runtime-value.d.ts.map +1 -0
  334. package/dist/utils/runtime-value.js +40 -0
  335. package/dist/utils/sql-parser.d.ts +8 -1
  336. package/dist/utils/sql-parser.d.ts.map +1 -1
  337. package/dist/utils/sql-parser.js +6 -3
  338. package/dist/utils/type-utils.d.ts.map +1 -1
  339. package/dist/utils/type-utils.js +10 -8
  340. package/dist/utils/utils.d.ts +2 -2
  341. package/dist/utils/utils.d.ts.map +1 -1
  342. package/dist/utils/utils.js +11 -8
  343. package/dist/utils/zod-error.d.ts.map +1 -1
  344. package/dist/utils/zod-error.js +7 -4
  345. package/dist/vector/chunking.d.ts.map +1 -1
  346. package/dist/vector/chunking.js +1 -1
  347. package/dist/vector/embedding.d.ts.map +1 -1
  348. package/dist/vector/embedding.js +1 -1
  349. package/dist/vector/types.d.ts +3 -2
  350. package/dist/vector/types.d.ts.map +1 -1
  351. package/package.json +11 -7
  352. package/src/ai/agents/agent.ts +5 -5
  353. package/src/ai/agents/types.ts +1 -1
  354. package/src/ai/providers/rtzr/__tests__/model.test.ts +2 -2
  355. package/src/ai/providers/rtzr/error.ts +5 -3
  356. package/src/ai/providers/rtzr/model.ts +3 -2
  357. package/src/ai/providers/rtzr/options.ts +10 -5
  358. package/src/ai/providers/rtzr/provider.ts +10 -10
  359. package/src/ai/providers/rtzr/utils.ts +3 -1
  360. package/src/api/__tests__/config.test.ts +11 -21
  361. package/src/api/__tests__/sonamu.http-validator.test.ts +512 -0
  362. package/src/api/__tests__/sonamu.websocket.test.ts +51 -41
  363. package/src/api/__tests__/zod-compiler-policy.test.ts +95 -0
  364. package/src/api/caster.ts +6 -8
  365. package/src/api/code-converters.ts +136 -36
  366. package/src/api/config.ts +62 -4
  367. package/src/api/context.ts +3 -3
  368. package/src/api/decorators.ts +28 -17
  369. package/src/api/http-validator.ts +149 -0
  370. package/src/api/sonamu.ts +459 -152
  371. package/src/api/websocket-helpers.ts +24 -17
  372. package/src/auth/audit-log/builders.ts +3 -2
  373. package/src/auth/audit-log/client-ip.ts +4 -2
  374. package/src/auth/audit-log/events.ts +15 -1
  375. package/src/auth/audit-log/plugin.ts +129 -200
  376. package/src/auth/audit-log-ingestor.ts +19 -15
  377. package/src/auth/auth-generator.ts +10 -5
  378. package/src/auth/better-auth-request.ts +4 -3
  379. package/src/auth/knex-adapter.ts +8 -2
  380. package/src/auth/plugins/entity-definitions/index.ts +6 -5
  381. package/src/bin/__tests__/http-validator-benchmark.test.ts +94 -0
  382. package/src/bin/__tests__/test-command.test.ts +9 -15
  383. package/src/bin/__tests__/ts-loader-register.test.ts +15 -17
  384. package/src/bin/__tests__/zod-compiler-build.test.ts +307 -0
  385. package/src/bin/build-config.ts +167 -0
  386. package/src/bin/cli.ts +155 -38
  387. package/src/bin/compiler-policy.ts +35 -0
  388. package/src/bin/fixture.ts +14 -3
  389. package/src/bin/test-command.ts +74 -29
  390. package/src/bin/ts-loader-registration.ts +22 -4
  391. package/src/cache/decorator.ts +10 -3
  392. package/src/compress/compress.ts +17 -4
  393. package/src/cone/cone-generator.ts +34 -6
  394. package/src/database/__tests__/db.test.ts +1 -0
  395. package/src/database/__tests__/puri.test.ts +16 -15
  396. package/src/database/__tests__/transaction-scope.test.ts +5 -0
  397. package/src/database/_batch_update.ts +1 -0
  398. package/src/database/base-model.ts +81 -50
  399. package/src/database/base-model.types.ts +6 -6
  400. package/src/database/db.ts +29 -19
  401. package/src/database/knex.ts +30 -17
  402. package/src/database/puri-subset.test-d.ts +17 -0
  403. package/src/database/puri.ts +181 -143
  404. package/src/database/puri.types.test-d.ts +31 -1
  405. package/src/database/puri.types.ts +23 -21
  406. package/src/database/upsert-builder.ts +75 -53
  407. package/src/dict/sd.ts +15 -5
  408. package/src/dict/sonamu-dictionary.ts +6 -6
  409. package/src/dict/utils.ts +9 -1
  410. package/src/entity/entity-manager.ts +26 -9
  411. package/src/entity/entity-template-cone.ts +5 -10
  412. package/src/entity/entity.ts +50 -34
  413. package/src/env.ts +7 -3
  414. package/src/exceptions/error-handler.ts +17 -2
  415. package/src/exceptions/so-exceptions.ts +26 -20
  416. package/src/filter/utils.ts +21 -11
  417. package/src/index.ts +3 -0
  418. package/src/logger/configure.ts +26 -18
  419. package/src/migration/__tests__/migrator.test.ts +50 -57
  420. package/src/migration/code-generation.ts +132 -104
  421. package/src/migration/index-where-predicate.ts +51 -24
  422. package/src/migration/migration-set.ts +11 -5
  423. package/src/migration/migrator.ts +392 -244
  424. package/src/migration/postgresql-schema-reader.ts +52 -47
  425. package/src/migration/slack-confirm.ts +17 -15
  426. package/src/migration/types.ts +57 -0
  427. package/src/naite/naite-reporter.ts +1 -1
  428. package/src/naite/naite.ts +97 -24
  429. package/src/runtime-type.ts +34 -0
  430. package/src/ssr/hmr-opt-out.ts +78 -0
  431. package/src/ssr/renderer.ts +6 -0
  432. package/src/storage/base-file.ts +13 -13
  433. package/src/storage/buffered-file.ts +11 -11
  434. package/src/storage/s3-driver.ts +4 -1
  435. package/src/storage/uploaded-file.ts +8 -8
  436. package/src/stream/__tests__/ws-contracts.test.ts +4 -2
  437. package/src/stream/__tests__/ws-telemetry.test.ts +1 -1
  438. package/src/stream/__tests__/ws.test.ts +30 -6
  439. package/src/stream/sse.ts +12 -11
  440. package/src/stream/ws-core.ts +1 -1
  441. package/src/stream/ws-delivery.ts +16 -7
  442. package/src/stream/ws-local-connection-store.ts +2 -3
  443. package/src/stream/ws-presence-store.ts +6 -1
  444. package/src/stream/ws-registry.ts +16 -7
  445. package/src/stream/ws-telemetry.ts +66 -43
  446. package/src/stream/ws.ts +73 -39
  447. package/src/syncer/__tests__/api-parser.websocket-type-ref.test.ts +3 -2
  448. package/src/syncer/__tests__/http-validator-registry.test.ts +137 -0
  449. package/src/syncer/api-parser.ts +67 -27
  450. package/src/syncer/checksum.ts +17 -11
  451. package/src/syncer/code-generator.ts +25 -17
  452. package/src/syncer/entity-operations.ts +3 -5
  453. package/src/syncer/file-copy.ts +46 -0
  454. package/src/syncer/file-patterns.ts +11 -8
  455. package/src/syncer/file-tracking.ts +3 -4
  456. package/src/syncer/filesystem-dependencies.ts +73 -0
  457. package/src/syncer/module-loader.ts +8 -4
  458. package/src/syncer/syncer-actions.ts +56 -18
  459. package/src/syncer/syncer.ts +116 -43
  460. package/src/syncer/watcher.ts +8 -4
  461. package/src/tasks/decorator.ts +20 -12
  462. package/src/tasks/step-wrapper.ts +19 -9
  463. package/src/tasks/workflow-manager.ts +15 -9
  464. package/src/template/__tests__/http_validators.template.test.ts +160 -0
  465. package/src/template/__tests__/services.template.mutation.test.ts +24 -23
  466. package/src/template/__tests__/services.template.websocket.test.ts +21 -25
  467. package/src/template/entity-converter.ts +5 -2
  468. package/src/template/helpers.ts +8 -9
  469. package/src/template/implementations/generated.template.ts +5 -4
  470. package/src/template/implementations/generated_http.template.ts +61 -15
  471. package/src/template/implementations/generated_sso.template.ts +15 -4
  472. package/src/template/implementations/http_validators.template.ts +175 -0
  473. package/src/template/implementations/model.template.ts +3 -1
  474. package/src/template/implementations/sd.template.ts +81 -41
  475. package/src/template/implementations/services.template.ts +2 -2
  476. package/src/template/implementations/view_form.template.ts +56 -39
  477. package/src/template/implementations/view_list.template.ts +31 -23
  478. package/src/template/template-manager.ts +15 -13
  479. package/src/template/template-types.ts +13 -5
  480. package/src/template/template.ts +4 -3
  481. package/src/template/zod-converter.ts +206 -91
  482. package/src/testing/__tests__/dev-test-routes.test.ts +85 -25
  483. package/src/testing/__tests__/dev-vitest-manager.test.ts +15 -15
  484. package/src/testing/_relation-graph.ts +4 -1
  485. package/src/testing/bootstrap.ts +12 -5
  486. package/src/testing/data-explorer.ts +102 -53
  487. package/src/testing/dev-test-routes.ts +87 -50
  488. package/src/testing/dev-vitest-manager.ts +88 -32
  489. package/src/testing/faker-mappings.ts +2 -2
  490. package/src/testing/filesystem.ts +1 -0
  491. package/src/testing/fixture-generator.ts +191 -131
  492. package/src/testing/fixture-loader.ts +5 -1
  493. package/src/testing/fixture-manager.ts +129 -90
  494. package/src/testing/global-setup.ts +7 -3
  495. package/src/testing/index.ts +5 -0
  496. package/src/types/__tests__/entity-json-schema-search-text.test.ts +9 -3
  497. package/src/types/types.ts +184 -158
  498. package/src/ui/ai-api.ts +9 -7
  499. package/src/ui/ai-client.ts +61 -56
  500. package/src/ui/api.ts +726 -770
  501. package/src/ui/async-route.ts +4 -0
  502. package/src/ui/cdd-service.ts +35 -12
  503. package/src/ui/migrations-api.ts +381 -0
  504. package/src/utils/formatter.ts +7 -3
  505. package/src/utils/fs-utils.ts +5 -5
  506. package/src/utils/model.ts +4 -5
  507. package/src/utils/object-utils.ts +32 -22
  508. package/src/utils/process-utils.ts +4 -1
  509. package/src/utils/runtime-value.ts +43 -0
  510. package/src/utils/sql-parser.ts +13 -6
  511. package/src/utils/type-utils.ts +9 -7
  512. package/src/utils/utils.ts +19 -21
  513. package/src/utils/zod-error.ts +4 -2
  514. package/src/vector/chunking.ts +6 -1
  515. package/src/vector/embedding.ts +2 -1
  516. package/src/vector/types.ts +4 -2
  517. package/tsdown.api.config.ts +5 -2
  518. package/dist/ui-web/assets/index-CJf8uJYf.js +0 -176
  519. package/dist/ui-web/assets/index-GMMIVGja.css +0 -1
package/src/ui/api.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { execSync } from "child_process";
1
+ import { execFileSync, execSync } from "child_process";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
 
@@ -19,13 +19,9 @@ import {
19
19
  isSoException,
20
20
  ServiceUnavailableException,
21
21
  } from "../exceptions/so-exceptions";
22
- import { Migrator } from "../migration/migrator";
23
- import { type MigrationResult } from "../migration/migrator";
24
- import { SlackConfirm } from "../migration/slack-confirm";
25
- import { type SlackConfirmPendingResult } from "../migration/slack-confirm";
26
22
  import { TemplateManager } from "../template/template-manager";
27
- import { DataExplorer } from "../testing/data-explorer";
28
- import { FixtureGenerator } from "../testing/fixture-generator";
23
+ import { DataExplorer, type DataExplorerOptions } from "../testing/data-explorer";
24
+ import { FixtureGenerator, type FixtureGenerationSpec } from "../testing/fixture-generator";
29
25
  import { FixtureManager } from "../testing/fixture-manager";
30
26
  import { type DuplicateCheckOptions } from "../testing/fixture-manager";
31
27
  import { BUILT_IN_TYPE_IDS, TemplateKey } from "../types/types";
@@ -39,8 +35,10 @@ import {
39
35
  type FlattenSubsetRow,
40
36
  type PathAndCode,
41
37
  } from "../types/types";
38
+ import { isObjectValue } from "../utils/runtime-value";
42
39
  import { nonNullable } from "../utils/utils";
43
40
  import { setAiApi } from "./ai-api";
41
+ import { forwardAsyncErrors } from "./async-route";
44
42
  import { type CddAddRuleRequest } from "./cdd-service";
45
43
  import {
46
44
  addRule,
@@ -52,35 +50,45 @@ import {
52
50
  readContent,
53
51
  readRule,
54
52
  } from "./cdd-service";
53
+ import { registerMigrationsApi } from "./migrations-api";
55
54
 
56
- export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
57
- fastify.register(
58
- async (server) => {
59
- // migrator
60
- const migrator = new Migrator();
61
-
62
- // waitForHMRCompleted
63
- async function waitForHMRCompleted<T>(fn: () => Promise<T>): Promise<T> {
64
- const waitPromise = new Promise<void>((resolve) => {
65
- const handler = () => {
66
- clearTimeout(timeout);
67
- resolve();
68
- };
55
+ interface EntityTemplateInput {
56
+ entityId: string;
57
+ enumId?: string;
58
+ }
69
59
 
70
- const timeout = setTimeout(() => {
71
- Sonamu.syncer.eventEmitter.off("onHMRCompleted", handler);
72
- resolve();
73
- }, 1500);
60
+ async function waitForHMRCompleted<T>(fn: () => Promise<T>): Promise<T> {
61
+ const waitPromise = new Promise<void>((resolve) => {
62
+ const handler = () => {
63
+ clearTimeout(timeout);
64
+ resolve();
65
+ };
74
66
 
75
- Sonamu.syncer.eventEmitter.once("onHMRCompleted", handler);
76
- });
67
+ const timeout = setTimeout(() => {
68
+ Sonamu.syncer.eventEmitter.off("onHMRCompleted", handler);
69
+ resolve();
70
+ }, 1500);
77
71
 
78
- const result = await fn();
79
- await waitPromise;
80
- return result;
81
- }
72
+ Sonamu.syncer.eventEmitter.once("onHMRCompleted", handler);
73
+ });
82
74
 
75
+ const result = await fn();
76
+ await waitPromise;
77
+ return result;
78
+ }
79
+
80
+ function flattenSubsetRows(subsetRows: EntitySubsetRow[]): FlattenSubsetRow[] {
81
+ return subsetRows.flatMap((subsetRow) => {
82
+ const { children, ...sRow } = subsetRow;
83
+ return [sRow, ...flattenSubsetRows(children)];
84
+ });
85
+ }
86
+
87
+ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
88
+ fastify.register(
89
+ async (server) => {
83
90
  await setAiApi(server);
91
+ await registerMigrationsApi(server);
84
92
 
85
93
  server.get("/api/sonamu/config", async () => {
86
94
  return Sonamu.config;
@@ -92,165 +100,188 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
92
100
  preset?: "types" | "entity.json" | "generated";
93
101
  absPath?: string;
94
102
  };
95
- }>("/api/tools/openVscode", async (request) => {
96
- const { entityId, preset, absPath } = request.query;
97
-
98
- const targetPath = (() => {
99
- if (entityId && preset) {
100
- const entity = EntityManager.get(entityId);
101
- const { names } = entity;
102
-
103
- const { apiRootPath } = Sonamu;
104
- const filename = (() => {
105
- switch (preset) {
106
- case "types":
107
- return `${names.fs}.types.ts`;
108
- case "entity.json":
109
- return `${names.fs}.entity.json`;
110
- case "generated":
111
- return `${names.fs}.generated.ts`;
103
+ }>("/api/tools/openVscode", (request) =>
104
+ forwardAsyncErrors(async () => {
105
+ const { entityId, preset, absPath } = request.query;
106
+
107
+ const targetPath = (() => {
108
+ if (entityId && preset) {
109
+ const entity = EntityManager.get(entityId);
110
+ const { names } = entity;
111
+
112
+ const { apiRootPath } = Sonamu;
113
+ const filename = (() => {
114
+ switch (preset) {
115
+ case "types":
116
+ return `${names.fs}.types.ts`;
117
+ case "entity.json":
118
+ return `${names.fs}.entity.json`;
119
+ case "generated":
120
+ return `${names.fs}.generated.ts`;
121
+ }
122
+ return undefined;
123
+ })();
124
+ return `${apiRootPath}/src/application/${entity.names.parentFs}/${filename}`;
125
+ } else {
126
+ if (!absPath) {
127
+ throw new BadRequestException(SD("sonamu.error.presetOrAbsPathRequired"));
112
128
  }
113
- })();
114
- return `${apiRootPath}/src/application/${entity.names.parentFs}/${filename}`;
115
- } else {
116
- if (!absPath) {
117
- throw new BadRequestException(SD("sonamu.error.presetOrAbsPathRequired"));
129
+ return absPath;
118
130
  }
119
- return absPath;
131
+ })();
132
+ execSync(`code ${targetPath}`);
133
+ }),
134
+ );
135
+
136
+ server.get<{
137
+ Querystring: {
138
+ editor: "vscode" | "cursor" | "zed";
139
+ absPath: string;
140
+ };
141
+ }>("/api/tools/openEditor", (request) =>
142
+ forwardAsyncErrors(async () => {
143
+ const commands = {
144
+ vscode: "code",
145
+ cursor: "cursor",
146
+ zed: "zed",
147
+ } as const;
148
+ const command = commands[request.query.editor];
149
+ if (command === undefined) {
150
+ throw new BadRequestException(SD("error.badRequest"));
120
151
  }
121
- })();
122
- execSync(`code ${targetPath}`);
123
- });
152
+
153
+ // 셸을 거치지 않아 파일 경로가 명령으로 해석되지 않도록 합니다.
154
+ execFileSync(command, [request.query.absPath]);
155
+ }),
156
+ );
124
157
 
125
158
  server.get<{
126
159
  Querystring: {
127
160
  origin: string;
128
161
  entityId?: string;
129
162
  };
130
- }>("/api/tools/getSuggestion", async (request) => {
131
- const { origin, entityId } = request.query;
132
-
133
- // 치환 용어집
134
- const glossary = new Map<string, string>([
135
- ["status", "상태"],
136
- ["type", "타입"],
137
- ["image", "이미지"],
138
- ["images", "이미지리스트"],
139
- ["url", "URL"],
140
- ["id", "ID"],
141
- ["name", `{EntityID}명`],
142
- ["title", "{EntityID}명"],
143
- ["parent", "상위{EntityID}"],
144
- ["desc", "설명"],
145
- ["at", "일시"],
146
- ["created", "등록"],
147
- ["updated", "수정"],
148
- ["deleted", "삭제"],
149
- ["by", "유저"],
150
- ["date", "일자"],
151
- ["time", "시간"],
152
- ["ko", "(한글)"],
153
- ["en", "(영문)"],
154
- ["krw", "(원)"],
155
- ["usd", "(USD)"],
156
- ["color", "컬러"],
157
- ["code", "코드"],
158
- ["x", "X좌표"],
159
- ["y", "Y좌표"],
160
- ["current", "현재"],
161
- ["stock", "재고"],
162
- ["total", "총"],
163
- ["admin", "관리자"],
164
- ["group", "그룹"],
165
- ["item", "아이템"],
166
- ["cnt", "수량"],
167
- ["price", "가격"],
168
- ["preset", "프리셋"],
169
- ["acct", "계좌"],
170
- ["tel", "전화번호"],
171
- ["no", "번호"],
172
- ["body", "내용"],
173
- ["content", "내용"],
174
- ["orderno", "정렬순서"],
175
- ["priority", "우선순위"],
176
- ["text", "텍스트"],
177
- ["key", "키"],
178
- ["sum", "합산"],
179
- ["expected", "예상"],
180
- ["actual", "실제"],
181
- ]);
182
- // 전체 엔티티 순회하며, 엔티티 타이틀과 프롭 설명을 치환 용어집에 추가
183
- for (const entityId of EntityManager.getAllIds()) {
184
- const entity = EntityManager.get(entityId);
185
- if ((entity.title ?? "") !== "") {
186
- glossary.set(inflection.underscore(entity.id), entity.title);
187
- glossary.set(
188
- inflection.underscore(inflection.pluralize(entity.id)),
189
- `${entity.title}리스트`,
190
- );
191
- }
192
-
193
- entity.props.forEach((prop) => {
194
- if (glossary.has(prop.name)) {
195
- return;
196
- }
197
- if (prop.desc) {
198
- glossary.set(prop.name, prop.desc.replace(entity.title ?? "", "{EntityID}"));
163
+ }>("/api/tools/getSuggestion", (request) =>
164
+ forwardAsyncErrors(async () => {
165
+ const { origin, entityId } = request.query;
166
+
167
+ // 치환 용어집
168
+ const glossary = new Map<string, string>([
169
+ ["status", "상태"],
170
+ ["type", "타입"],
171
+ ["image", "이미지"],
172
+ ["images", "이미지리스트"],
173
+ ["url", "URL"],
174
+ ["id", "ID"],
175
+ ["name", `{EntityID}명`],
176
+ ["title", "{EntityID}명"],
177
+ ["parent", "상위{EntityID}"],
178
+ ["desc", "설명"],
179
+ ["at", "일시"],
180
+ ["created", "등록"],
181
+ ["updated", "수정"],
182
+ ["deleted", "삭제"],
183
+ ["by", "유저"],
184
+ ["date", "일자"],
185
+ ["time", "시간"],
186
+ ["ko", "(한글)"],
187
+ ["en", "(영문)"],
188
+ ["krw", "(원)"],
189
+ ["usd", "(USD)"],
190
+ ["color", "컬러"],
191
+ ["code", "코드"],
192
+ ["x", "X좌표"],
193
+ ["y", "Y좌표"],
194
+ ["current", "현재"],
195
+ ["stock", "재고"],
196
+ ["total", "총"],
197
+ ["admin", "관리자"],
198
+ ["group", "그룹"],
199
+ ["item", "아이템"],
200
+ ["cnt", "수량"],
201
+ ["price", "가격"],
202
+ ["preset", "프리셋"],
203
+ ["acct", "계좌"],
204
+ ["tel", "전화번호"],
205
+ ["no", "번호"],
206
+ ["body", "내용"],
207
+ ["content", "내용"],
208
+ ["orderno", "정렬순서"],
209
+ ["priority", "우선순위"],
210
+ ["text", "텍스트"],
211
+ ["key", "키"],
212
+ ["sum", "합산"],
213
+ ["expected", "예상"],
214
+ ["actual", "실제"],
215
+ ]);
216
+ // 전체 엔티티 순회하며, 엔티티 타이틀과 프롭 설명을 치환 용어집에 추가
217
+ for (const glossaryEntityId of EntityManager.getAllIds()) {
218
+ const glossaryEntity = EntityManager.get(glossaryEntityId);
219
+ if ((glossaryEntity.title ?? "") !== "") {
220
+ glossary.set(inflection.underscore(glossaryEntity.id), glossaryEntity.title);
221
+ glossary.set(
222
+ inflection.underscore(inflection.pluralize(glossaryEntity.id)),
223
+ `${glossaryEntity.title}리스트`,
224
+ );
199
225
  }
200
- });
201
- }
202
226
 
203
- const suggested = (() => {
204
- // 단어 분리, 가능한 조합 생성
205
- const words = origin.split("_");
206
- const combinations = [...range(words.length, 0, -1)].flatMap((len) => {
207
- return [
208
- ...range(0, words.length - len + 1, (idx) => {
209
- return {
210
- len,
211
- w: words.slice(idx, idx + len).join("_"),
212
- };
213
- }),
214
- ];
215
- });
216
-
217
- // 조합을 순회하며, 치환 용어집에 있는 단어가 포함된 경우, 치환 용어로 치환
218
- const REPLACED_PREFIX = "#REPLACED//"; // 치환된 단어를 join 이후에도 식별하기 위해 prefix 추가
219
- let remainArr: string[] = [...words];
220
- for (const comb of combinations) {
221
- const remainStr = remainArr.join("_");
222
- if (remainStr.includes(comb.w) && glossary.has(comb.w)) {
223
- remainArr = remainStr
224
- .replace(comb.w, REPLACED_PREFIX + glossary.get(comb.w))
225
- .split("_");
226
- }
227
+ glossaryEntity.props.forEach((prop) => {
228
+ if (glossary.has(prop.name)) {
229
+ return;
230
+ }
231
+ if (prop.desc) {
232
+ glossary.set(
233
+ prop.name,
234
+ prop.desc.replace(glossaryEntity.title ?? "", "{EntityID}"),
235
+ );
236
+ }
237
+ });
227
238
  }
228
239
 
229
- return remainArr
230
- .map((r) => {
231
- if (r.startsWith(REPLACED_PREFIX)) {
232
- return r.replace(REPLACED_PREFIX, "");
233
- } else {
234
- return r.toUpperCase();
240
+ const suggested = (() => {
241
+ // 단어 분리, 가능한 조합 생성
242
+ const words = origin.split("_");
243
+ const combinations = [...range(words.length, 0, -1)].flatMap((len) => {
244
+ return [
245
+ ...range(0, words.length - len + 1, (idx) => {
246
+ return {
247
+ len,
248
+ w: words.slice(idx, idx + len).join("_"),
249
+ };
250
+ }),
251
+ ];
252
+ });
253
+
254
+ // 조합을 순회하며, 치환 용어집에 있는 단어가 포함된 경우, 치환 용어로 치환
255
+ const REPLACED_PREFIX = "#REPLACED//"; // 치환된 단어를 join 이후에도 식별하기 위해 prefix 추가
256
+ let remainArr: string[] = [...words];
257
+ for (const comb of combinations) {
258
+ const remainStr = remainArr.join("_");
259
+ if (remainStr.includes(comb.w) && glossary.has(comb.w)) {
260
+ remainArr = remainStr
261
+ .replace(comb.w, REPLACED_PREFIX + glossary.get(comb.w))
262
+ .split("_");
235
263
  }
236
- })
237
- .join("")
238
- .replace(/{EntityID}/g, entityId ? EntityManager.get(entityId).title : "");
239
- })();
264
+ }
240
265
 
241
- return { suggested };
242
- });
266
+ return remainArr
267
+ .map((r) => {
268
+ if (r.startsWith(REPLACED_PREFIX)) {
269
+ return r.replace(REPLACED_PREFIX, "");
270
+ } else {
271
+ return r.toUpperCase();
272
+ }
273
+ })
274
+ .join("")
275
+ .replace(/{EntityID}/g, entityId ? EntityManager.get(entityId).title : "");
276
+ })();
277
+
278
+ return { suggested };
279
+ }),
280
+ );
243
281
 
244
282
  server.get("/api/entity/findMany", async () => {
245
283
  const entityIds = EntityManager.getAllIds();
246
284
 
247
- function flattenSubsetRows(subsetRows: EntitySubsetRow[]): FlattenSubsetRow[] {
248
- return subsetRows.flatMap((subsetRow) => {
249
- const { children, ...sRow } = subsetRow;
250
- return [sRow, ...flattenSubsetRows(children)];
251
- });
252
- }
253
-
254
285
  const entities = await Promise.all(
255
286
  entityIds.map((entityId) => {
256
287
  const entity = EntityManager.get(entityId);
@@ -292,45 +323,55 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
292
323
  filter?: "enums" | "types";
293
324
  reload?: "1";
294
325
  };
295
- }>("/api/entity/typeIds", async (request): Promise<{ typeIds: string[] }> => {
296
- const { filter, reload } = request.query;
326
+ }>(
327
+ "/api/entity/typeIds",
328
+ (request): Promise<{ typeIds: string[] }> =>
329
+ forwardAsyncErrors(async () => {
330
+ const { filter, reload } = request.query;
297
331
 
298
- if (reload === "1") {
299
- await Sonamu.syncer.autoloadTypes();
300
- }
332
+ if (reload === "1") {
333
+ await Sonamu.syncer.autoloadTypes();
334
+ }
301
335
 
302
- const typeIds = (() => {
303
- // 프로젝트에서 정의한 타입들
304
- const projectTypeIds = Object.entries(Sonamu.syncer.types)
305
- .filter(([_typeId, zodType]) => (zodType._zod.def.type as string) !== "enum")
306
- .map(([typeId, _zodType]) => typeId);
336
+ const typeIds = (() => {
337
+ // 프로젝트에서 정의한 타입들
338
+ const projectTypeIds = Object.entries(Sonamu.syncer.types)
339
+ .filter(([_typeId, zodType]) => {
340
+ const { _zod: zodMetadata } = zodType;
341
+ return (
342
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ (zodMetadata
343
+ .def.type as string) !== "enum"
344
+ );
345
+ })
346
+ .map(([typeId, _zodType]) => typeId);
307
347
 
308
- // 내장 타입들 (sonamu 코어에서 제공)
309
- const builtInTypeIds = [...BUILT_IN_TYPE_IDS];
348
+ // 내장 타입들 (sonamu 코어에서 제공)
349
+ const builtInTypeIds = [...BUILT_IN_TYPE_IDS];
310
350
 
311
- // 모든 타입 병합
312
- const allTypeIds = [...builtInTypeIds, ...projectTypeIds];
351
+ // 모든 타입 병합
352
+ const allTypeIds = [...builtInTypeIds, ...projectTypeIds];
313
353
 
314
- if (filter === "types") {
315
- return allTypeIds;
316
- }
354
+ if (filter === "types") {
355
+ return allTypeIds;
356
+ }
317
357
 
318
- const enumIds = EntityManager.getAllIds().flatMap((entityId) => {
319
- const entity = EntityManager.get(entityId);
320
- return Object.keys(entity.enumLabels);
321
- });
358
+ const enumIds = EntityManager.getAllIds().flatMap((entityId) => {
359
+ const entity = EntityManager.get(entityId);
360
+ return Object.keys(entity.enumLabels);
361
+ });
322
362
 
323
- if (filter === "enums") {
324
- return enumIds;
325
- } else {
326
- return [...allTypeIds, ...enumIds];
327
- }
328
- })();
363
+ if (filter === "enums") {
364
+ return enumIds;
365
+ } else {
366
+ return [...allTypeIds, ...enumIds];
367
+ }
368
+ })();
329
369
 
330
- return {
331
- typeIds,
332
- };
333
- });
370
+ return {
371
+ typeIds,
372
+ };
373
+ }),
374
+ );
334
375
 
335
376
  server.post<{
336
377
  Body: {
@@ -341,25 +382,29 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
341
382
  parentId?: string;
342
383
  };
343
384
  };
344
- }>("/api/entity/create", async (request) => {
345
- return await waitForHMRCompleted(async () => {
346
- const { form } = request.body;
347
- await Sonamu.syncer.createEntity({ ...form, entityId: form.id });
385
+ }>("/api/entity/create", (request) =>
386
+ forwardAsyncErrors(async () => {
387
+ return await waitForHMRCompleted(async () => {
388
+ const { form } = request.body;
389
+ await Sonamu.syncer.createEntity({ ...form, entityId: form.id });
348
390
 
349
- return 1;
350
- });
351
- });
391
+ return 1;
392
+ });
393
+ }),
394
+ );
352
395
 
353
396
  server.post<{
354
397
  Body: {
355
398
  entityId: string;
356
399
  };
357
- }>("/api/entity/del", async (request) => {
358
- return await waitForHMRCompleted(async () => {
359
- const { entityId } = request.body;
360
- return await Sonamu.syncer.delEntity(entityId);
361
- });
362
- });
400
+ }>("/api/entity/del", (request) =>
401
+ forwardAsyncErrors(async () => {
402
+ return await waitForHMRCompleted(async () => {
403
+ const { entityId } = request.body;
404
+ return await Sonamu.syncer.delEntity(entityId);
405
+ });
406
+ }),
407
+ );
363
408
 
364
409
  server.post<{
365
410
  Body: {
@@ -370,18 +415,20 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
370
415
  parentId?: string;
371
416
  };
372
417
  };
373
- }>("/api/entity/modifyEntityBase", async (request) => {
374
- return await waitForHMRCompleted(async () => {
375
- const { entityId, newValues } = request.body;
376
- const entity = EntityManager.get(entityId);
377
- entity.title = newValues.title;
378
- entity.table = newValues.table;
379
- entity.parentId = newValues.parentId;
380
- await entity.save();
418
+ }>("/api/entity/modifyEntityBase", (request) =>
419
+ forwardAsyncErrors(async () => {
420
+ return await waitForHMRCompleted(async () => {
421
+ const { entityId, newValues } = request.body;
422
+ const entity = EntityManager.get(entityId);
423
+ entity.title = newValues.title;
424
+ entity.table = newValues.table;
425
+ entity.parentId = newValues.parentId;
426
+ await entity.save();
381
427
 
382
- return 1;
383
- });
384
- });
428
+ return 1;
429
+ });
430
+ }),
431
+ );
385
432
 
386
433
  server.post<{
387
434
  Body: {
@@ -390,40 +437,44 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
390
437
  fields: string[];
391
438
  fieldsInternal?: string[];
392
439
  };
393
- }>("/api/entity/modifySubset", async (request) => {
394
- return await waitForHMRCompleted(async () => {
395
- const { entityId, subsetKey, fields, fieldsInternal } = request.body;
396
- const entity = EntityManager.get(entityId);
397
- entity.subsets[subsetKey] = fields;
398
- if (fieldsInternal !== undefined) {
399
- if (fieldsInternal.length > 0) {
400
- entity.subsetsInternal[subsetKey] = fieldsInternal;
401
- } else {
402
- delete entity.subsetsInternal[subsetKey];
440
+ }>("/api/entity/modifySubset", (request) =>
441
+ forwardAsyncErrors(async () => {
442
+ return await waitForHMRCompleted(async () => {
443
+ const { entityId, subsetKey, fields, fieldsInternal } = request.body;
444
+ const entity = EntityManager.get(entityId);
445
+ entity.subsets[subsetKey] = fields;
446
+ if (fieldsInternal !== undefined) {
447
+ if (fieldsInternal.length > 0) {
448
+ entity.subsetsInternal[subsetKey] = fieldsInternal;
449
+ } else {
450
+ delete entity.subsetsInternal[subsetKey];
451
+ }
403
452
  }
404
- }
405
- await entity.save();
453
+ await entity.save();
406
454
 
407
- return { updated: fields, updatedInternal: fieldsInternal };
408
- });
409
- });
455
+ return { updated: fields, updatedInternal: fieldsInternal };
456
+ });
457
+ }),
458
+ );
410
459
 
411
460
  server.post<{
412
461
  Body: {
413
462
  entityId: string;
414
463
  subsetKey: string;
415
464
  };
416
- }>("/api/entity/delSubset", async (request) => {
417
- return await waitForHMRCompleted(async () => {
418
- const { entityId, subsetKey } = request.body;
419
- const entity = EntityManager.get(entityId);
420
- delete entity.subsets[subsetKey];
421
- delete entity.subsetsInternal[subsetKey];
422
- await entity.save();
465
+ }>("/api/entity/delSubset", (request) =>
466
+ forwardAsyncErrors(async () => {
467
+ return await waitForHMRCompleted(async () => {
468
+ const { entityId, subsetKey } = request.body;
469
+ const entity = EntityManager.get(entityId);
470
+ delete entity.subsets[subsetKey];
471
+ delete entity.subsetsInternal[subsetKey];
472
+ await entity.save();
423
473
 
424
- return 1;
425
- });
426
- });
474
+ return 1;
475
+ });
476
+ }),
477
+ );
427
478
 
428
479
  server.post<{
429
480
  Body: {
@@ -431,14 +482,16 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
431
482
  newProp: EntityProp;
432
483
  at?: number;
433
484
  };
434
- }>("/api/entity/createProp", async (request) => {
435
- return await waitForHMRCompleted(async () => {
436
- const { entityId, at, newProp } = request.body;
437
- const entity = EntityManager.get(entityId);
438
- await entity.createProp(newProp, at);
439
- return true;
440
- });
441
- });
485
+ }>("/api/entity/createProp", (request) =>
486
+ forwardAsyncErrors(async () => {
487
+ return await waitForHMRCompleted(async () => {
488
+ const { entityId, at, newProp } = request.body;
489
+ const entity = EntityManager.get(entityId);
490
+ await entity.createProp(newProp, at);
491
+ return true;
492
+ });
493
+ }),
494
+ );
442
495
 
443
496
  server.post<{
444
497
  Body: {
@@ -446,31 +499,35 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
446
499
  newProp: EntityProp;
447
500
  at: number;
448
501
  };
449
- }>("/api/entity/modifyProp", async (request) => {
450
- return await waitForHMRCompleted(async () => {
451
- const { entityId, at, newProp } = request.body;
502
+ }>("/api/entity/modifyProp", (request) =>
503
+ forwardAsyncErrors(async () => {
504
+ return await waitForHMRCompleted(async () => {
505
+ const { entityId, at, newProp } = request.body;
452
506
 
453
- const entity = EntityManager.get(entityId);
454
- entity.modifyProp(newProp, at);
507
+ const entity = EntityManager.get(entityId);
508
+ entity.modifyProp(newProp, at);
455
509
 
456
- return true;
457
- });
458
- });
510
+ return true;
511
+ });
512
+ }),
513
+ );
459
514
 
460
515
  server.post<{
461
516
  Body: {
462
517
  entityId: string;
463
518
  at: number;
464
519
  };
465
- }>("/api/entity/delProp", async (request) => {
466
- return await waitForHMRCompleted(async () => {
467
- const { entityId, at } = request.body;
520
+ }>("/api/entity/delProp", (request) =>
521
+ forwardAsyncErrors(async () => {
522
+ return await waitForHMRCompleted(async () => {
523
+ const { entityId, at } = request.body;
468
524
 
469
- const entity = EntityManager.get(entityId);
470
- entity.delProp(at);
471
- return true;
472
- });
473
- });
525
+ const entity = EntityManager.get(entityId);
526
+ entity.delProp(at);
527
+ return true;
528
+ });
529
+ }),
530
+ );
474
531
 
475
532
  server.post<{
476
533
  Body: {
@@ -478,76 +535,84 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
478
535
  at: number;
479
536
  to: number;
480
537
  };
481
- }>("/api/entity/moveProp", async (request) => {
482
- return await waitForHMRCompleted(async () => {
483
- const { entityId, at, to } = request.body;
538
+ }>("/api/entity/moveProp", (request) =>
539
+ forwardAsyncErrors(async () => {
540
+ return await waitForHMRCompleted(async () => {
541
+ const { entityId, at, to } = request.body;
484
542
 
485
- const entity = EntityManager.get(entityId);
486
- entity.moveProp(at, to);
543
+ const entity = EntityManager.get(entityId);
544
+ entity.moveProp(at, to);
487
545
 
488
- return true;
489
- });
490
- });
546
+ return true;
547
+ });
548
+ }),
549
+ );
491
550
 
492
551
  server.post<{
493
552
  Body: {
494
553
  entityId: string;
495
554
  indexes: EntityIndex[];
496
555
  };
497
- }>("/api/entity/modifyIndexes", async (request) => {
498
- return await waitForHMRCompleted(async () => {
499
- const { entityId, indexes } = request.body;
500
- const entity = EntityManager.get(entityId);
501
- entity.indexes = indexes;
502
- await entity.save();
556
+ }>("/api/entity/modifyIndexes", (request) =>
557
+ forwardAsyncErrors(async () => {
558
+ return await waitForHMRCompleted(async () => {
559
+ const { entityId, indexes } = request.body;
560
+ const entity = EntityManager.get(entityId);
561
+ entity.indexes = indexes;
562
+ await entity.save();
503
563
 
504
- return { updated: indexes };
505
- });
506
- });
564
+ return { updated: indexes };
565
+ });
566
+ }),
567
+ );
507
568
 
508
569
  server.post<{
509
570
  Body: {
510
571
  entityId: string;
511
572
  enumLabels: Entity["enumLabels"];
512
573
  };
513
- }>("/api/entity/modifyEnumLabels", async (request) => {
514
- return await waitForHMRCompleted(async () => {
515
- const { entityId, enumLabels } = request.body;
516
- const entity = EntityManager.get(entityId);
517
- entity.enumLabels = enumLabels;
518
- await entity.save();
574
+ }>("/api/entity/modifyEnumLabels", (request) =>
575
+ forwardAsyncErrors(async () => {
576
+ return await waitForHMRCompleted(async () => {
577
+ const { entityId, enumLabels } = request.body;
578
+ const entity = EntityManager.get(entityId);
579
+ entity.enumLabels = enumLabels;
580
+ await entity.save();
519
581
 
520
- return { updated: enumLabels };
521
- });
522
- });
582
+ return { updated: enumLabels };
583
+ });
584
+ }),
585
+ );
523
586
 
524
587
  server.post<{
525
588
  Body: {
526
589
  entityId: string;
527
590
  newEnumId: string;
528
591
  };
529
- }>("/api/entity/createEnumId", async (request) => {
530
- return await waitForHMRCompleted(async () => {
531
- const { entityId, newEnumId } = request.body;
532
- const entity = EntityManager.get(entityId);
592
+ }>("/api/entity/createEnumId", (request) =>
593
+ forwardAsyncErrors(async () => {
594
+ return await waitForHMRCompleted(async () => {
595
+ const { entityId, newEnumId } = request.body;
596
+ const entity = EntityManager.get(entityId);
533
597
 
534
- if (entity.enumLabels[newEnumId]) {
535
- throw new Error(`이미 존재하는 enumId입니다: ${newEnumId}`);
536
- }
598
+ if (entity.enumLabels[newEnumId]) {
599
+ throw new Error(`이미 존재하는 enumId입니다: ${newEnumId}`);
600
+ }
537
601
 
538
- entity.enumLabels[newEnumId] = newEnumId.endsWith("Status")
539
- ? {
540
- active: "노출",
541
- hidden: "숨김",
542
- }
543
- : {
544
- "": "",
545
- };
546
- await entity.save();
602
+ entity.enumLabels[newEnumId] = newEnumId.endsWith("Status")
603
+ ? {
604
+ active: "노출",
605
+ hidden: "숨김",
606
+ }
607
+ : {
608
+ "": "",
609
+ };
610
+ await entity.save();
547
611
 
548
- return 1;
549
- });
550
- });
612
+ return 1;
613
+ });
614
+ }),
615
+ );
551
616
 
552
617
  server.post<{
553
618
  Body: {
@@ -557,58 +622,62 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
557
622
  after: string;
558
623
  };
559
624
  };
560
- }>("/api/entity/modifyEnumId", async (request) => {
561
- return await waitForHMRCompleted(async () => {
562
- const { entityId, enumId } = request.body;
563
- const entityIds = EntityManager.getAllIds();
564
- const isExists = entityIds.some((entityId) => {
565
- const entity = EntityManager.get(entityId);
566
- return Object.keys(entity.enumLabels).includes(enumId.after);
567
- });
568
- if (isExists) {
569
- throw new Error(`이미 존재하는 EnumId입니다: ${enumId.after}`);
570
- }
625
+ }>("/api/entity/modifyEnumId", (request) =>
626
+ forwardAsyncErrors(async () => {
627
+ return await waitForHMRCompleted(async () => {
628
+ const { entityId, enumId } = request.body;
629
+ const entityIds = EntityManager.getAllIds();
630
+ const isExists = entityIds.some((candidateEntityId) => {
631
+ const candidateEntity = EntityManager.get(candidateEntityId);
632
+ return Object.keys(candidateEntity.enumLabels).includes(enumId.after);
633
+ });
634
+ if (isExists) {
635
+ throw new Error(`이미 존재하는 EnumId입니다: ${enumId.after}`);
636
+ }
571
637
 
572
- const entity = EntityManager.get(entityId);
573
- entity.enumLabels[enumId.after] = entity.enumLabels[enumId.before];
574
- delete entity.enumLabels[enumId.before];
638
+ const entity = EntityManager.get(entityId);
639
+ entity.enumLabels[enumId.after] = entity.enumLabels[enumId.before];
640
+ delete entity.enumLabels[enumId.before];
575
641
 
576
- await entity.save();
642
+ await entity.save();
577
643
 
578
- for (const entityId of entityIds) {
579
- const entity = EntityManager.get(entityId);
580
- for (const prop of entity.props) {
581
- if (prop.type === "enum" && prop.id === enumId.before) {
582
- prop.id = enumId.after;
644
+ for (const relatedEntityId of entityIds) {
645
+ const relatedEntity = EntityManager.get(relatedEntityId);
646
+ for (const prop of relatedEntity.props) {
647
+ if (prop.type === "enum" && prop.id === enumId.before) {
648
+ prop.id = enumId.after;
649
+ }
583
650
  }
651
+ await relatedEntity.save();
584
652
  }
585
- await entity.save();
586
- }
587
- });
588
- });
653
+ });
654
+ }),
655
+ );
589
656
 
590
657
  server.post<{
591
658
  Body: {
592
659
  entityId: string;
593
660
  enumId: string;
594
661
  };
595
- }>("/api/entity/deleteEnumId", async (request) => {
596
- return await waitForHMRCompleted(async () => {
597
- const { entityId, enumId } = request.body;
598
-
599
- const entityIds = EntityManager.getAllIds();
600
- const isReferenced = entityIds
601
- .flatMap((entityId) => EntityManager.get(entityId).props)
602
- .some((prop) => prop.type === "enum" && prop.id === enumId);
603
- if (isReferenced) {
604
- throw new Error(`${enumId}를 참조하는 프로퍼티가 존재합니다.`);
605
- }
662
+ }>("/api/entity/deleteEnumId", (request) =>
663
+ forwardAsyncErrors(async () => {
664
+ return await waitForHMRCompleted(async () => {
665
+ const { entityId, enumId } = request.body;
666
+
667
+ const entityIds = EntityManager.getAllIds();
668
+ const isReferenced = entityIds
669
+ .flatMap((referencingEntityId) => EntityManager.get(referencingEntityId).props)
670
+ .some((prop) => prop.type === "enum" && prop.id === enumId);
671
+ if (isReferenced) {
672
+ throw new Error(`${enumId}를 참조하는 프로퍼티가 존재합니다.`);
673
+ }
606
674
 
607
- const entity = EntityManager.get(entityId);
608
- delete entity.enumLabels[enumId];
609
- await entity.save();
610
- });
611
- });
675
+ const entity = EntityManager.get(entityId);
676
+ delete entity.enumLabels[enumId];
677
+ await entity.save();
678
+ });
679
+ }),
680
+ );
612
681
 
613
682
  server.post<{
614
683
  Body: {
@@ -619,28 +688,32 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
619
688
  subsetKey?: string;
620
689
  cone: Cone;
621
690
  };
622
- }>("/api/entity/updateCone", async (request) => {
623
- return await waitForHMRCompleted(async () => {
624
- const { entityId, target, propName, enumId, subsetKey, cone } = request.body;
625
- const entity = EntityManager.get(entityId);
691
+ }>("/api/entity/updateCone", (request) =>
692
+ forwardAsyncErrors(async () => {
693
+ return await waitForHMRCompleted(async () => {
694
+ const { entityId, target, propName, enumId, subsetKey, cone } = request.body;
695
+ const entity = EntityManager.get(entityId);
626
696
 
627
- if (target === "entity") {
628
- entity.cone = cone;
629
- } else if (target === "prop" && propName) {
630
- const prop = entity.props.find((p) => p.name === propName);
631
- if (prop) {
632
- (prop as { cone?: Cone }).cone = cone;
697
+ if (target === "entity") {
698
+ entity.cone = cone;
699
+ } else if (target === "prop" && propName) {
700
+ const prop = entity.props.find((p) => p.name === propName);
701
+ if (prop) {
702
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ (
703
+ prop as { cone?: Cone }
704
+ ).cone = cone;
705
+ }
706
+ } else if (target === "enum" && enumId) {
707
+ entity.enumCones[enumId] = cone;
708
+ } else if (target === "subset" && subsetKey) {
709
+ entity.subsetCones[subsetKey] = cone;
633
710
  }
634
- } else if (target === "enum" && enumId) {
635
- entity.enumCones[enumId] = cone;
636
- } else if (target === "subset" && subsetKey) {
637
- entity.subsetCones[subsetKey] = cone;
638
- }
639
711
 
640
- await entity.save();
641
- return true;
642
- });
643
- });
712
+ await entity.save();
713
+ return true;
714
+ });
715
+ }),
716
+ );
644
717
 
645
718
  server.post<{
646
719
  Body: {
@@ -658,6 +731,7 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
658
731
  const entity = EntityManager.get(entityId);
659
732
 
660
733
  // locale 기본값: Sonamu.config.i18n.defaultLocale 사용
734
+ // SAFETY: i18n 구성 스키마가 지원 locale을 ko/en/ja로 제한한다.
661
735
  const effectiveLocale =
662
736
  locale ?? (Sonamu.config.i18n.defaultLocale as "ko" | "en" | "ja");
663
737
 
@@ -713,221 +787,55 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
713
787
  Querystring: {
714
788
  entityId: string;
715
789
  };
716
- }>("/api/entity/getTableColumns", async (request) => {
717
- const { entityId } = request.query;
718
- const entity = EntityManager.get(entityId);
719
- const columns = entity.getTableColumns();
720
- return { columns };
721
- });
722
-
723
- server.get("/api/migrations/status", async () => {
724
- const status = await migrator.getStatus();
725
-
726
- return { status };
727
- });
728
-
729
- server.post<{
730
- Body: {
731
- action: "apply" | "rollback" | "shadow";
732
- targets: (keyof SonamuDBConfig)[];
733
- force?: boolean;
734
- forceReason?: string;
735
- requestor?: string;
736
- };
737
- }>(
738
- "/api/migrations/runAction",
739
- async (request): Promise<MigrationResult | SlackConfirmPendingResult> => {
740
- const { action, targets, force, forceReason, requestor } = request.body;
741
-
742
- if (action === "shadow") {
743
- return migrator.runShadowTest();
744
- }
745
-
746
- // Slack 승인 체크 (apply 시에만)
747
- if (action === "apply") {
748
- const slackConfirm = new SlackConfirm();
749
- const requiresApproval = targets.some((t) => slackConfirm.isTargetRequiresApproval(t));
750
-
751
- // 로컬 DB인 경우 승인 스킵
752
- const localHosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
753
- const isLocalTarget = targets.every((target) => {
754
- const targetConfig = Sonamu.dbConfig[target];
755
- const host = (targetConfig?.connection as { host?: string })?.host ?? "localhost";
756
- return localHosts.includes(host.toLowerCase());
757
- });
758
-
759
- if (requiresApproval && slackConfirm.isConfigured() && !isLocalTarget) {
760
- const { conns } = await migrator.getStatus();
761
-
762
- // 모든 타겟 DB에서 pending인 마이그레이션의 합집합을 구합니다.
763
- const pendingMigrations = [
764
- ...new Set(
765
- conns
766
- .filter((conn) => targets.includes(conn.connKey))
767
- .flatMap((conn) => conn.pending),
768
- ),
769
- ];
770
-
771
- if (pendingMigrations.length > 0) {
772
- // 기존 승인 요청 확인
773
- const existing = await slackConfirm.getExistingRequest(pendingMigrations);
774
-
775
- if (existing) {
776
- // 기존 요청이 있으면 승인 상태 확인
777
- const { approved, rejected } = await slackConfirm.checkApproval(
778
- existing.channel,
779
- existing.ts,
780
- );
781
-
782
- if (approved) {
783
- // 승인됨 → 실행
784
- const result = await migrator.runAction(action, targets);
785
- if (result.length > 0) {
786
- await slackConfirm.logExecution(
787
- existing.channel,
788
- existing.ts,
789
- result,
790
- requestor,
791
- );
792
- }
793
- return result;
794
- } else if (rejected) {
795
- throw new BadRequestException(SD("sonamu.error.migrationRejected"));
796
- } else if (force) {
797
- // Force 진행
798
- await slackConfirm.forceApproval(
799
- existing.channel,
800
- existing.ts,
801
- forceReason ?? "사유 없음",
802
- requestor,
803
- );
804
- const result = await migrator.runAction(action, targets);
805
- if (result.length > 0) {
806
- await slackConfirm.logExecution(
807
- existing.channel,
808
- existing.ts,
809
- result,
810
- requestor,
811
- );
812
- }
813
- return result;
814
- } else {
815
- // 대기중
816
- return {
817
- type: "pending",
818
- channel: existing.channel,
819
- ts: existing.ts,
820
- };
821
- }
822
- } else {
823
- // 새 승인 요청 발송
824
- const { channel, ts } = await slackConfirm.postApprovalRequest(
825
- pendingMigrations,
826
- targets,
827
- requestor,
828
- );
829
- await slackConfirm.saveRequest(pendingMigrations, channel, ts);
830
-
831
- return {
832
- type: "pending",
833
- channel,
834
- ts,
835
- };
836
- }
837
- }
838
- }
839
- }
840
-
841
- return migrator.runAction(action, targets);
842
- },
790
+ }>("/api/entity/getTableColumns", (request) =>
791
+ forwardAsyncErrors(async () => {
792
+ const { entityId } = request.query;
793
+ const entity = EntityManager.get(entityId);
794
+ const columns = entity.getTableColumns();
795
+ return { columns };
796
+ }),
843
797
  );
844
798
 
845
- server.post<{
846
- Body: {
847
- channel: string;
848
- ts: string;
849
- };
850
- }>("/api/migrations/checkApproval", async (request) => {
851
- const { channel, ts } = request.body;
852
- const slackConfirm = new SlackConfirm();
853
-
854
- if (!slackConfirm.isConfigured()) {
855
- return { approved: true, rejected: false };
856
- }
857
-
858
- return slackConfirm.checkApproval(channel, ts);
859
- });
860
-
861
- server.post<{
862
- Body: {
863
- channel: string;
864
- ts: string;
865
- reason: string;
866
- requestor?: string;
867
- };
868
- }>("/api/migrations/forceApproval", async (request) => {
869
- const { channel, ts, reason, requestor } = request.body;
870
- const slackConfirm = new SlackConfirm();
871
-
872
- if (!slackConfirm.isConfigured()) {
873
- throw new BadRequestException(SD("sonamu.error.slackConfirmNotConfigured"));
874
- }
875
-
876
- await slackConfirm.forceApproval(channel, ts, reason, requestor);
877
- return { success: true };
878
- });
879
-
880
- server.post<{
881
- Body: {
882
- codeNames: string[];
883
- };
884
- }>("/api/migrations/delCodes", async (request) => {
885
- const { codeNames } = request.body;
886
- return await migrator.delCodes(codeNames);
887
- });
888
-
889
- server.post("/api/migrations/generatePreparedCodes", async (_requestt) => {
890
- return await migrator.generatePreparedCodes();
891
- });
892
-
893
799
  server.post<{
894
800
  Body: {
895
801
  entityIds: string[];
896
802
  templateKeys: string[];
897
803
  };
898
- }>("/api/scaffolding/getStatus", async (request) => {
899
- const { entityIds, templateKeys: _templateKeys } = request.body;
900
- if ((entityIds ?? []).length === 0) {
901
- throw new BadRequestException(SD("sonamu.error.entityIdsRequired"));
902
- } else if ((_templateKeys ?? []).length === 0) {
903
- throw new BadRequestException(SD("sonamu.error.templateKeysRequired"));
904
- }
804
+ }>("/api/scaffolding/getStatus", (request) =>
805
+ forwardAsyncErrors(async () => {
806
+ const { entityIds, templateKeys: _templateKeys } = request.body;
807
+ if ((entityIds ?? []).length === 0) {
808
+ throw new BadRequestException(SD("sonamu.error.entityIdsRequired"));
809
+ } else if ((_templateKeys ?? []).length === 0) {
810
+ throw new BadRequestException(SD("sonamu.error.templateKeysRequired"));
811
+ }
905
812
 
906
- // sorting
907
- entityIds.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
908
- const templateKeys = TemplateKey.options.filter((tk) => _templateKeys.includes(tk));
813
+ // sorting
814
+ entityIds.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
815
+ const templateKeys = TemplateKey.options.filter((tk) => _templateKeys.includes(tk));
909
816
 
910
- const combinations = entityIds.flatMap((entityId) => {
911
- return templateKeys.map((templateKey) => [entityId, templateKey]);
912
- });
817
+ const combinations = entityIds.flatMap((entityId) => {
818
+ return templateKeys.map((templateKey) => [entityId, templateKey]);
819
+ });
913
820
 
914
- const statuses = await Promise.all(
915
- combinations.map(async ([entityId, templateKey]) => {
916
- const { subPath, fullPath, isExists } = await Sonamu.syncer.checkExistsGenCode(
917
- entityId,
918
- templateKey as TemplateKey,
919
- );
920
- return {
921
- entityId,
922
- templateKey,
923
- subPath,
924
- fullPath,
925
- isExists,
926
- };
927
- }),
928
- );
929
- return { statuses };
930
- });
821
+ const statuses = await Promise.all(
822
+ combinations.map(async ([entityId, templateKey]) => {
823
+ const { subPath, fullPath, isExists } = await Sonamu.syncer.checkExistsGenCode(
824
+ entityId,
825
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ templateKey as TemplateKey,
826
+ );
827
+ return {
828
+ entityId,
829
+ templateKey,
830
+ subPath,
831
+ fullPath,
832
+ isExists,
833
+ };
834
+ }),
835
+ );
836
+ return { statuses };
837
+ }),
838
+ );
931
839
 
932
840
  server.post<{
933
841
  Body: {
@@ -938,54 +846,53 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
938
846
  overwrite?: boolean;
939
847
  }[];
940
848
  };
941
- }>("/api/scaffolding/generate", async (request) => {
942
- const { options } = request.body;
943
- if (options.length === 0) {
944
- throw new BadRequestException(SD("sonamu.error.optionsRequired"));
945
- }
946
-
947
- // 1. 모든 템플릿에서 필요한 dict 키를 수집
948
- const keys = options.flatMap(({ templateKey }) => {
949
- const template = TemplateManager.get(templateKey);
950
- return template.getRequiredDictKeys() ?? [];
951
- });
849
+ }>("/api/scaffolding/generate", (request) =>
850
+ forwardAsyncErrors(async () => {
851
+ const { options } = request.body;
852
+ if (options.length === 0) {
853
+ throw new BadRequestException(SD("sonamu.error.optionsRequired"));
854
+ }
952
855
 
953
- // 2. target별로 ensureDictKeys 호출 (순차 처리)
954
- await sonamuDictionary.ensureDictKeys([...new Set(keys)]);
856
+ // 1. 모든 템플릿에서 필요한 dict 키를 수집
857
+ const keys = options.flatMap(({ templateKey }) => {
858
+ const template = TemplateManager.get(templateKey);
859
+ return template.getRequiredDictKeys() ?? [];
860
+ });
955
861
 
956
- // 3. 템플릿 생성 (병렬 처리)
957
- const result = await Promise.all(
958
- options.map(async ({ entityId, templateKey, enumId, overwrite }) => {
959
- try {
960
- return await Sonamu.syncer.generateTemplate(
961
- templateKey as TemplateKey,
962
- {
963
- entityId,
964
- enumId,
965
- } as {
966
- entityId: string;
967
- enumId?: string;
968
- },
969
- {
970
- overwrite,
971
- },
972
- );
973
- } catch (e) {
974
- if (isSoException(e) && e.statusCode === 541) {
975
- return null;
976
- } else {
977
- console.error(e);
978
- throw e;
862
+ // 2. target별로 ensureDictKeys 호출 (순차 처리)
863
+ await sonamuDictionary.ensureDictKeys([...new Set(keys)]);
864
+
865
+ // 3. 템플릿 생성 (병렬 처리)
866
+ const result = await Promise.all(
867
+ options.map(async ({ entityId, templateKey, enumId, overwrite }) => {
868
+ try {
869
+ return await Sonamu.syncer.generateTemplate(
870
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ templateKey as TemplateKey,
871
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 Entity 템플릿 옵션을 보장한다. */ {
872
+ entityId,
873
+ enumId,
874
+ } as EntityTemplateInput,
875
+ {
876
+ overwrite,
877
+ },
878
+ );
879
+ } catch (e) {
880
+ if (isSoException(e) && e.statusCode === 541) {
881
+ return null;
882
+ } else {
883
+ console.error(e);
884
+ throw e;
885
+ }
979
886
  }
980
- }
981
- }),
982
- );
887
+ }),
888
+ );
983
889
 
984
- if (result.filter(nonNullable).length === 0) {
985
- throw new ServiceUnavailableException(SD("sonamu.error.allFilesGenerated"));
986
- }
987
- return result;
988
- });
890
+ if (result.filter(nonNullable).length === 0) {
891
+ throw new ServiceUnavailableException(SD("sonamu.error.allFilesGenerated"));
892
+ }
893
+ return result;
894
+ }),
895
+ );
989
896
 
990
897
  server.post<{
991
898
  Body: {
@@ -995,48 +902,63 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
995
902
  enumId?: string;
996
903
  };
997
904
  };
998
- }>("/api/scaffolding/preview", async (request): Promise<{ pathAndCodes: PathAndCode[] }> => {
999
- const { option } = request.body;
905
+ }>(
906
+ "/api/scaffolding/preview",
907
+ (request): Promise<{ pathAndCodes: PathAndCode[] }> =>
908
+ forwardAsyncErrors(async () => {
909
+ const { option } = request.body;
1000
910
 
1001
- try {
1002
- const { templateKey, ...templateOptions } = option;
1003
- const pathAndCodes = await Sonamu.syncer.renderTemplate(
1004
- templateKey as TemplateKey,
1005
- templateOptions,
1006
- );
911
+ try {
912
+ const { templateKey, ...templateOptions } = option;
913
+ const pathAndCodes = await Sonamu.syncer.renderTemplate(
914
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ templateKey as TemplateKey,
915
+ templateOptions,
916
+ );
1007
917
 
1008
- return { pathAndCodes };
1009
- } catch (e) {
1010
- console.error(e);
1011
- throw e;
1012
- }
1013
- });
918
+ return { pathAndCodes };
919
+ } catch (e) {
920
+ console.error(e);
921
+ throw e;
922
+ }
923
+ }),
924
+ );
1014
925
 
1015
- server.post("/api/fixture", async (request) => {
1016
- const { sourceDB, targetDB, search, duplicateCheck } = request.body as {
1017
- sourceDB: keyof SonamuDBConfig;
1018
- targetDB: keyof SonamuDBConfig;
1019
- search: FixtureSearchOptions;
1020
- duplicateCheck?: DuplicateCheckOptions;
1021
- };
926
+ server.post("/api/fixture", (request) =>
927
+ forwardAsyncErrors(async () => {
928
+ const { sourceDB, targetDB, search, duplicateCheck } =
929
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ request.body as {
930
+ sourceDB: keyof SonamuDBConfig;
931
+ targetDB: keyof SonamuDBConfig;
932
+ search: FixtureSearchOptions;
933
+ duplicateCheck?: DuplicateCheckOptions;
934
+ };
1022
935
 
1023
- return FixtureManager.getFixtures(sourceDB, targetDB, search, duplicateCheck);
1024
- });
936
+ return FixtureManager.getFixtures(sourceDB, targetDB, search, duplicateCheck);
937
+ }),
938
+ );
1025
939
 
1026
- server.post("/api/fixture/import", async (request) => {
1027
- const { db, fixtures } = request.body as {
1028
- db: keyof SonamuDBConfig;
1029
- fixtures: FixtureRecord[];
1030
- };
940
+ server.post("/api/fixture/import", (request) =>
941
+ forwardAsyncErrors(async () => {
942
+ const { db, fixtures } =
943
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ request.body as {
944
+ db: keyof SonamuDBConfig;
945
+ fixtures: FixtureRecord[];
946
+ };
1031
947
 
1032
- return FixtureManager.insertFixtures(db, fixtures);
1033
- });
948
+ return FixtureManager.insertFixtures(db, fixtures);
949
+ }),
950
+ );
1034
951
 
1035
- server.post("/api/fixture/addFixtureLoader", async (request) => {
1036
- const { code } = request.body as { code: string };
952
+ server.post("/api/fixture/addFixtureLoader", (request) =>
953
+ forwardAsyncErrors(async () => {
954
+ const { code } =
955
+ /* SAFETY: UI 도구의 Zod 입력 스키마가 이 값의 타입을 보장한다. */ request.body as {
956
+ code: string;
957
+ };
1037
958
 
1038
- return FixtureManager.addFixtureLoader(code);
1039
- });
959
+ return FixtureManager.addFixtureLoader(code);
960
+ }),
961
+ );
1040
962
 
1041
963
  server.get("/api/i18n/dictionary", async () => {
1042
964
  return sonamuDictionary.getDictionary();
@@ -1053,14 +975,16 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1053
975
  .send(buffer);
1054
976
  });
1055
977
 
1056
- server.post("/api/i18n/import", async (request) => {
1057
- const data = await request.file();
1058
- if (!data) {
1059
- throw new BadRequestException(SD("sonamu.error.fileNotUploaded"));
1060
- }
1061
- const buffer = await data.toBuffer();
1062
- return sonamuDictionary.importFromExcel(buffer);
1063
- });
978
+ server.post("/api/i18n/import", (request) =>
979
+ forwardAsyncErrors(async () => {
980
+ const data = await request.file();
981
+ if (!data) {
982
+ throw new BadRequestException(SD("sonamu.error.fileNotUploaded"));
983
+ }
984
+ const buffer = await data.toBuffer();
985
+ return sonamuDictionary.importFromExcel(buffer);
986
+ }),
987
+ );
1064
988
 
1065
989
  server.post<{
1066
990
  Body: {
@@ -1069,33 +993,41 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1069
993
  source: "entity" | "project" | "sonamu";
1070
994
  values: Record<string, string>;
1071
995
  };
1072
- }>("/api/i18n/update", async (request) => {
1073
- await sonamuDictionary.updateEntry(request.body);
1074
- return { success: true };
1075
- });
996
+ }>("/api/i18n/update", (request) =>
997
+ forwardAsyncErrors(async () => {
998
+ await sonamuDictionary.updateEntry(request.body);
999
+ return { success: true };
1000
+ }),
1001
+ );
1076
1002
 
1077
1003
  server.post<{
1078
1004
  Body: {
1079
1005
  key: string;
1080
1006
  values: Record<string, string>;
1081
1007
  };
1082
- }>("/api/i18n/create", async (request) => {
1083
- await sonamuDictionary.createEntry(request.body);
1084
- return { success: true };
1085
- });
1008
+ }>("/api/i18n/create", (request) =>
1009
+ forwardAsyncErrors(async () => {
1010
+ await sonamuDictionary.createEntry(request.body);
1011
+ return { success: true };
1012
+ }),
1013
+ );
1086
1014
 
1087
1015
  server.post<{
1088
1016
  Body: {
1089
1017
  key: string;
1090
1018
  };
1091
- }>("/api/i18n/delete", async (request) => {
1092
- await sonamuDictionary.deleteEntry(request.body.key);
1093
- return { success: true };
1094
- });
1019
+ }>("/api/i18n/delete", (request) =>
1020
+ forwardAsyncErrors(async () => {
1021
+ await sonamuDictionary.deleteEntry(request.body.key);
1022
+ return { success: true };
1023
+ }),
1024
+ );
1095
1025
 
1096
- server.post<{ Body: { keys: string[] } }>("/api/i18n/checkUsage", async (request) => {
1097
- return sonamuDictionary.checkUsage(request.body.keys);
1098
- });
1026
+ server.post<{ Body: { keys: string[] } }>("/api/i18n/checkUsage", (request) =>
1027
+ forwardAsyncErrors(async () => {
1028
+ return sonamuDictionary.checkUsage(request.body.keys);
1029
+ }),
1030
+ );
1099
1031
 
1100
1032
  // Tasks API
1101
1033
  server.get("/api/tasks/status", async () => {
@@ -1123,61 +1055,71 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1123
1055
  createdAfter?: string;
1124
1056
  createdBefore?: string;
1125
1057
  };
1126
- }>("/api/tasks/workflowRuns", async (request) => {
1127
- const backend = Sonamu.workflows.backend;
1128
- const { limit, after, before, order, status, workflowName, createdAfter, createdBefore } =
1129
- request.query;
1130
- return backend.listWorkflowRuns({
1131
- limit: limit ? Number.parseInt(limit, 10) : undefined,
1132
- after,
1133
- before,
1134
- order,
1135
- status: status ? status.split(",") : undefined,
1136
- workflowName: workflowName || undefined,
1137
- createdAfter: createdAfter ? new Date(createdAfter) : undefined,
1138
- createdBefore: createdBefore ? new Date(createdBefore) : undefined,
1139
- });
1140
- });
1058
+ }>("/api/tasks/workflowRuns", (request) =>
1059
+ forwardAsyncErrors(async () => {
1060
+ const backend = Sonamu.workflows.backend;
1061
+ const { limit, after, before, order, status, workflowName, createdAfter, createdBefore } =
1062
+ request.query;
1063
+ return backend.listWorkflowRuns({
1064
+ limit: limit ? Number.parseInt(limit, 10) : undefined,
1065
+ after,
1066
+ before,
1067
+ order,
1068
+ status: status ? status.split(",") : undefined,
1069
+ workflowName: workflowName || undefined,
1070
+ createdAfter: createdAfter ? new Date(createdAfter) : undefined,
1071
+ createdBefore: createdBefore ? new Date(createdBefore) : undefined,
1072
+ });
1073
+ }),
1074
+ );
1141
1075
 
1142
1076
  server.get<{
1143
1077
  Params: { id: string };
1144
- }>("/api/tasks/workflowRuns/:id", async (request) => {
1145
- const backend = Sonamu.workflows.backend;
1146
- const workflowRun = await backend.getWorkflowRun({
1147
- workflowRunId: request.params.id,
1148
- });
1149
- if (!workflowRun) {
1150
- throw new Error(`Workflow run not found: ${request.params.id}`);
1151
- }
1152
- return workflowRun;
1153
- });
1078
+ }>("/api/tasks/workflowRuns/:id", (request) =>
1079
+ forwardAsyncErrors(async () => {
1080
+ const backend = Sonamu.workflows.backend;
1081
+ const workflowRun = await backend.getWorkflowRun({
1082
+ workflowRunId: request.params.id,
1083
+ });
1084
+ if (!workflowRun) {
1085
+ throw new Error(`Workflow run not found: ${request.params.id}`);
1086
+ }
1087
+ return workflowRun;
1088
+ }),
1089
+ );
1154
1090
 
1155
1091
  server.post<{
1156
1092
  Params: { id: string };
1157
- }>("/api/tasks/workflowRuns/:id/cancel", async (request) => {
1158
- const backend = Sonamu.workflows.backend;
1159
- return backend.cancelWorkflowRun({
1160
- workflowRunId: request.params.id,
1161
- });
1162
- });
1093
+ }>("/api/tasks/workflowRuns/:id/cancel", (request) =>
1094
+ forwardAsyncErrors(async () => {
1095
+ const backend = Sonamu.workflows.backend;
1096
+ return backend.cancelWorkflowRun({
1097
+ workflowRunId: request.params.id,
1098
+ });
1099
+ }),
1100
+ );
1163
1101
 
1164
1102
  server.post<{
1165
1103
  Params: { id: string };
1166
- }>("/api/tasks/workflowRuns/:id/pause", async (request) => {
1167
- const backend = Sonamu.workflows.backend;
1168
- return backend.pauseWorkflowRun({
1169
- workflowRunId: request.params.id,
1170
- });
1171
- });
1104
+ }>("/api/tasks/workflowRuns/:id/pause", (request) =>
1105
+ forwardAsyncErrors(async () => {
1106
+ const backend = Sonamu.workflows.backend;
1107
+ return backend.pauseWorkflowRun({
1108
+ workflowRunId: request.params.id,
1109
+ });
1110
+ }),
1111
+ );
1172
1112
 
1173
1113
  server.post<{
1174
1114
  Params: { id: string };
1175
- }>("/api/tasks/workflowRuns/:id/resume", async (request) => {
1176
- const backend = Sonamu.workflows.backend;
1177
- return backend.resumeWorkflowRun({
1178
- workflowRunId: request.params.id,
1179
- });
1180
- });
1115
+ }>("/api/tasks/workflowRuns/:id/resume", (request) =>
1116
+ forwardAsyncErrors(async () => {
1117
+ const backend = Sonamu.workflows.backend;
1118
+ return backend.resumeWorkflowRun({
1119
+ workflowRunId: request.params.id,
1120
+ });
1121
+ }),
1122
+ );
1181
1123
 
1182
1124
  server.get<{
1183
1125
  Params: { id: string };
@@ -1186,32 +1128,36 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1186
1128
  after?: string;
1187
1129
  before?: string;
1188
1130
  };
1189
- }>("/api/tasks/workflowRuns/:id/steps", async (request) => {
1190
- const backend = Sonamu.workflows.backend;
1191
- const { limit, after, before } = request.query;
1192
- return backend.listStepAttempts({
1193
- workflowRunId: request.params.id,
1194
- limit: limit ? Number.parseInt(limit, 10) : undefined,
1195
- after,
1196
- before,
1197
- });
1198
- });
1131
+ }>("/api/tasks/workflowRuns/:id/steps", (request) =>
1132
+ forwardAsyncErrors(async () => {
1133
+ const backend = Sonamu.workflows.backend;
1134
+ const { limit, after, before } = request.query;
1135
+ return backend.listStepAttempts({
1136
+ workflowRunId: request.params.id,
1137
+ limit: limit ? Number.parseInt(limit, 10) : undefined,
1138
+ after,
1139
+ before,
1140
+ });
1141
+ }),
1142
+ );
1199
1143
 
1200
1144
  /**
1201
1145
  * Health Check API
1202
1146
  * MCP 도구가 Sonamu 서버를 자동 감지하기 위한 엔드포인트
1203
1147
  */
1204
- server.get("/api/sonamu/health", async (request) => {
1205
- const address = request.server.server.address();
1206
- const port = address && typeof address === "object" ? address.port : 0;
1207
-
1208
- return {
1209
- ok: true,
1210
- project: process.cwd().split("/").pop() || "unknown",
1211
- port,
1212
- timestamp: new Date().toISOString(),
1213
- };
1214
- });
1148
+ server.get("/api/sonamu/health", (request) =>
1149
+ forwardAsyncErrors(async () => {
1150
+ const address = request.server.server.address();
1151
+ const port = address && isObjectValue(address) ? address.port : 0;
1152
+
1153
+ return {
1154
+ ok: true,
1155
+ project: process.cwd().split("/").pop() || "unknown",
1156
+ port,
1157
+ timestamp: new Date().toISOString(),
1158
+ };
1159
+ }),
1160
+ );
1215
1161
 
1216
1162
  /**
1217
1163
  * Fixture 생성 API
@@ -1220,7 +1166,7 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1220
1166
  Body: {
1221
1167
  entity: string;
1222
1168
  count?: number;
1223
- overrides?: Record<string, unknown>;
1169
+ overrides?: FixtureGenerationSpec["overrides"];
1224
1170
  targetDb?: "fixture" | "test";
1225
1171
  };
1226
1172
  }>("/api/sonamu/fixture/generate", async (request, reply) => {
@@ -1271,7 +1217,7 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1271
1217
  entity: string;
1272
1218
  strategy: "sample" | "recent" | "random" | "query";
1273
1219
  limit?: number;
1274
- where?: Record<string, unknown>;
1220
+ where?: DataExplorerOptions["where"];
1275
1221
  };
1276
1222
  }>("/api/sonamu/fixture/explore", async (request, reply) => {
1277
1223
  const { entity, strategy, limit = 10, where } = request.body;
@@ -1420,35 +1366,45 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
1420
1366
  return getCddTree();
1421
1367
  });
1422
1368
 
1423
- server.post<{ Body: { filePath: string } }>("/api/cdd/readContent", async (request) => {
1424
- const { filePath } = request.body;
1425
- return readContent(filePath);
1426
- });
1369
+ server.post<{ Body: { filePath: string } }>("/api/cdd/readContent", (request) =>
1370
+ forwardAsyncErrors(async () => {
1371
+ const { filePath } = request.body;
1372
+ return readContent(filePath);
1373
+ }),
1374
+ );
1427
1375
 
1428
- server.post<{ Body: { filePath: string } }>("/api/cdd/editContent", async (request) => {
1429
- const { filePath } = request.body;
1430
- return editContent(filePath);
1431
- });
1376
+ server.post<{ Body: { filePath: string } }>("/api/cdd/editContent", (request) =>
1377
+ forwardAsyncErrors(async () => {
1378
+ const { filePath } = request.body;
1379
+ return editContent(filePath);
1380
+ }),
1381
+ );
1432
1382
 
1433
- server.post<{ Body: { filePath: string } }>("/api/cdd/openSource", async (request) => {
1434
- const { filePath } = request.body;
1435
- openSourceFile(filePath);
1436
- return { success: true };
1437
- });
1383
+ server.post<{ Body: { filePath: string } }>("/api/cdd/openSource", (request) =>
1384
+ forwardAsyncErrors(async () => {
1385
+ const { filePath } = request.body;
1386
+ openSourceFile(filePath);
1387
+ return { success: true };
1388
+ }),
1389
+ );
1438
1390
 
1439
1391
  // CDD Rules API
1440
1392
  server.get("/api/cdd/rules", async () => {
1441
1393
  return listRules();
1442
1394
  });
1443
1395
 
1444
- server.post<{ Body: { ruleKey: string } }>("/api/cdd/readRule", async (request) => {
1445
- const { ruleKey } = request.body;
1446
- return readRule(ruleKey);
1447
- });
1396
+ server.post<{ Body: { ruleKey: string } }>("/api/cdd/readRule", (request) =>
1397
+ forwardAsyncErrors(async () => {
1398
+ const { ruleKey } = request.body;
1399
+ return readRule(ruleKey);
1400
+ }),
1401
+ );
1448
1402
 
1449
- server.post<{ Body: CddAddRuleRequest }>("/api/cdd/addRule", async (request) => {
1450
- return addRule(request.body);
1451
- });
1403
+ server.post<{ Body: CddAddRuleRequest }>("/api/cdd/addRule", (request) =>
1404
+ forwardAsyncErrors(async () => {
1405
+ return addRule(request.body);
1406
+ }),
1407
+ );
1452
1408
 
1453
1409
  // CDD AC API
1454
1410
  server.get("/api/cdd/ac", async () => {