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