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
@@ -1,28 +1,35 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
- // vitest/node 모킹 - createVitest가 mock Vitest 인스턴스를 반환하도록 설정
3
+ import { type ManagedVitest } from "../dev-vitest-manager";
4
+ import { DevVitestManager } from "../dev-vitest-manager";
5
+
6
+ // 매니저가 사용하는 Vitest 기능만 충실히 구현한 테스트 대역입니다.
4
7
  const mockVitest = {
5
8
  standalone: vi.fn().mockResolvedValue(undefined),
6
9
  onFilterWatchedSpecification: vi.fn(),
10
+ invalidateFile: vi.fn(),
7
11
  setGlobalTestNamePattern: vi.fn(),
8
12
  resetGlobalTestNamePattern: vi.fn(),
9
13
  globTestSpecifications: vi.fn(),
10
14
  runTestSpecifications: vi.fn(),
11
15
  close: vi.fn(),
12
- };
13
-
14
- vi.mock("vitest/node", () => ({
15
- createVitest: vi.fn().mockResolvedValue(mockVitest),
16
- }));
16
+ } satisfies ManagedVitest;
17
17
 
18
- import { DevVitestManager } from "../dev-vitest-manager";
18
+ function createModule(duration: number) {
19
+ return {
20
+ moduleId: "dup.test.ts",
21
+ children: [],
22
+ state: () => "passed" as const,
23
+ diagnostic: () => ({ duration }),
24
+ };
25
+ }
19
26
 
20
27
  describe("DevVitestManager", () => {
21
28
  let manager: DevVitestManager;
22
29
 
23
30
  beforeEach(() => {
24
31
  vi.clearAllMocks();
25
- manager = new DevVitestManager();
32
+ manager = new DevVitestManager(async () => mockVitest);
26
33
 
27
34
  // 기본 mock: 빈 테스트 결과
28
35
  const mockTestModule = {
@@ -114,13 +121,6 @@ describe("DevVitestManager", () => {
114
121
  await manager.start();
115
122
  mockVitest.globTestSpecifications.mockResolvedValue([{ moduleId: "dup.test.ts" }]);
116
123
 
117
- const createModule = (duration: number) => ({
118
- moduleId: "dup.test.ts",
119
- children: [],
120
- state: () => "passed" as const,
121
- diagnostic: () => ({ duration }),
122
- });
123
-
124
124
  mockVitest.runTestSpecifications.mockResolvedValue({
125
125
  testModules: [createModule(10), createModule(25)],
126
126
  });
@@ -44,7 +44,10 @@ export class RelationGraph {
44
44
  }
45
45
  } else if (isManyToManyRelationProp(prop)) {
46
46
  // ManyToMany 관계의 경우 양방향 의존성 추가
47
- const relatedIds = column.value as unknown as (number | string)[];
47
+ if (!Array.isArray(column.value)) {
48
+ throw new Error(`${fixture.fixtureId}.${prop.name} 관계 값은 배열이어야 합니다.`);
49
+ }
50
+ const relatedIds = column.value;
48
51
  for (const relatedId of relatedIds) {
49
52
  const relatedFixtureId = `${prop.with}#${relatedId}`;
50
53
  if (this.graph.has(relatedFixtureId)) {
@@ -57,14 +57,21 @@ export function bootstrap(vi: VitestUtils, options?: BootstrapOptions) {
57
57
  }
58
58
 
59
59
  function getMockContext(): Context {
60
+ // SAFETY: 테스트 컨텍스트에서는 Fastify 요청과 응답을 사용하지 않으며 접근 시 즉시 실패해야 합니다.
61
+ const request: Context["request"] = null as never;
62
+ // SAFETY: 테스트 컨텍스트에서는 Fastify 요청과 응답을 사용하지 않으며 접근 시 즉시 실패해야 합니다.
63
+ const reply: Context["reply"] = null as never;
64
+ // SAFETY: 모의 구현은 실제 createSSE와 같은 호출 계약을 유지하되 항상 명시적으로 실패합니다.
65
+ const createSSE = (() => {
66
+ throw new Error("createSSE is not available in mock context");
67
+ }) as Context["createSSE"];
68
+
60
69
  return {
61
70
  transport: "http",
62
- request: null as unknown as Context["request"],
63
- reply: null as unknown as Context["reply"],
71
+ request,
72
+ reply,
64
73
  headers: {},
65
- createSSE: (() => {
66
- throw new Error("createSSE is not available in mock context");
67
- }) as Context["createSSE"],
74
+ createSSE,
68
75
  session: null,
69
76
  user: null,
70
77
  naiteStore: Naite.createStore(),
@@ -1,22 +1,67 @@
1
1
  import { type Knex } from "knex";
2
+ import { z } from "zod";
2
3
 
3
4
  import { type CacheManager } from "../cache/types";
4
5
  import { type Entity } from "../entity/entity";
5
6
  import { type EntityManager } from "../entity/entity-manager";
6
7
  import { isBelongsToOneRelationProp, isOneToOneRelationProp, isRelationProp } from "../types/types";
7
- import { nonNullable } from "../utils/utils";
8
+ import { isFunctionValue } from "../utils/runtime-value";
8
9
 
9
10
  export type DataExplorerStrategy = "sample" | "ids" | "query" | "file" | "recent" | "random";
10
11
 
12
+ export type DataExplorerValue =
13
+ | string
14
+ | number
15
+ | boolean
16
+ | bigint
17
+ | Date
18
+ | Uint8Array
19
+ | null
20
+ | undefined
21
+ | DataExplorerValue[]
22
+ | DataExplorerRecord;
23
+
24
+ export type DataExplorerRecord = {
25
+ [columnName: string]: DataExplorerValue;
26
+ };
27
+
28
+ export const dataExplorerValueSchema: z.ZodType<DataExplorerValue> = z.lazy(() =>
29
+ z.union([
30
+ z.string(),
31
+ z.number(),
32
+ z.boolean(),
33
+ z.bigint(),
34
+ z.date(),
35
+ z.instanceof(Uint8Array),
36
+ z.null(),
37
+ z.undefined(),
38
+ z.array(dataExplorerValueSchema),
39
+ z.record(z.string(), dataExplorerValueSchema),
40
+ ]),
41
+ );
42
+ export const dataExplorerRecordSchema = z.record(z.string(), dataExplorerValueSchema);
43
+ export const dataExplorerRecordsSchema = z.array(dataExplorerRecordSchema);
44
+ const dataExplorerDirectionSchema = z.enum(["asc", "desc"]);
45
+ export const dataSourceConfigSchema = z.object({
46
+ strategy: z.enum(["sample", "ids", "query", "file", "recent", "random"]).optional(),
47
+ limit: z.number().optional(),
48
+ where: z.record(z.string(), dataExplorerValueSchema).optional(),
49
+ orderBy: z.string().optional(),
50
+ ids: z.array(z.union([z.number(), z.string()])).optional(),
51
+ filePath: z.string().optional(),
52
+ useCache: z.boolean().optional(),
53
+ cacheTtl: z.number().optional(),
54
+ });
55
+
11
56
  /** WHERE 조건 타입 (객체 또는 Knex QueryBuilder 함수) */
12
- export type WhereCondition = Record<string, unknown> | ((queryBuilder: Knex.QueryBuilder) => void);
57
+ export type WhereCondition = object | ((queryBuilder: Knex.QueryBuilder) => void);
13
58
 
14
59
  export type DataExplorerOptions = {
15
60
  strategy: DataExplorerStrategy;
16
61
  limit?: number;
17
62
  where?: WhereCondition;
18
63
  orderBy?: string;
19
- ids?: number[];
64
+ ids?: (number | string)[];
20
65
  filePath?: string;
21
66
  /** 캐싱 사용 여부 (기본값: false) */
22
67
  useCache?: boolean;
@@ -35,10 +80,10 @@ export type ExploreWithRelationsResult = {
35
80
  /** 메인 entity 데이터 */
36
81
  main: {
37
82
  entityId: string;
38
- records: Record<string, unknown>[];
83
+ records: DataExplorerRecord[];
39
84
  };
40
85
  /** 관련 entity 데이터 (entityId -> records) */
41
- related: Map<string, Record<string, unknown>[]>;
86
+ related: Map<string, DataExplorerRecord[]>;
42
87
  };
43
88
 
44
89
  // 기존 DB 데이터를 탐색하여 fixture 생성 시 참조할 수 있는 시스템
@@ -53,10 +98,7 @@ export class DataExplorer {
53
98
  this.cache = cacheManager;
54
99
  }
55
100
 
56
- async explore(
57
- entityName: string,
58
- options: DataExplorerOptions,
59
- ): Promise<Record<string, unknown>[]> {
101
+ async explore(entityName: string, options: DataExplorerOptions): Promise<DataExplorerRecord[]> {
60
102
  const entity = this.entityManager.get(entityName);
61
103
  if (!entity) {
62
104
  throw new Error(`Entity not found: ${entityName}`);
@@ -78,7 +120,7 @@ export class DataExplorer {
78
120
  private async exploreInternal(
79
121
  entity: Entity,
80
122
  options: DataExplorerOptions,
81
- ): Promise<Record<string, unknown>[]> {
123
+ ): Promise<DataExplorerRecord[]> {
82
124
  const query = this.db(entity.table);
83
125
 
84
126
  switch (options.strategy) {
@@ -90,7 +132,7 @@ export class DataExplorer {
90
132
  if (createdAtCol) {
91
133
  query.orderBy(createdAtCol, "desc");
92
134
  }
93
- return await query.limit(options.limit || 10);
135
+ return dataExplorerRecordsSchema.parse(await query.limit(options.limit || 10));
94
136
  }
95
137
 
96
138
  case "random":
@@ -100,22 +142,30 @@ export class DataExplorer {
100
142
  if (options.ids && options.ids.length > 0) {
101
143
  query.whereIn("id", options.ids);
102
144
  }
103
- return await query;
145
+ return dataExplorerRecordsSchema.parse(await query);
104
146
 
105
147
  case "query":
106
148
  if (options.where) {
107
- query.where(options.where);
149
+ const whereCallback = z
150
+ .custom<(queryBuilder: Knex.QueryBuilder) => void>(isFunctionValue)
151
+ .safeParse(options.where);
152
+ if (whereCallback.success) {
153
+ query.where(whereCallback.data);
154
+ } else {
155
+ query.where(dataExplorerRecordSchema.parse(options.where));
156
+ }
108
157
  }
109
158
  if (options.orderBy) {
110
159
  const [col, dir = "asc"] = options.orderBy.split(":");
160
+ const direction = dataExplorerDirectionSchema.parse(dir);
111
161
  // id 컬럼은 숫자로 캐스팅하여 정렬합니다 (문자열 정렬 방지)
112
162
  if (col === "id") {
113
- query.orderByRaw(`CAST(?? AS INTEGER) ${dir}`, [col]);
163
+ query.orderByRaw(`CAST(?? AS INTEGER) ${direction}`, [col]);
114
164
  } else {
115
- query.orderBy(col, dir as "asc" | "desc");
165
+ query.orderBy(col, direction);
116
166
  }
117
167
  }
118
- return await query.limit(options.limit || 10);
168
+ return dataExplorerRecordsSchema.parse(await query.limit(options.limit || 10));
119
169
 
120
170
  case "file":
121
171
  if (!options.filePath) {
@@ -129,15 +179,12 @@ export class DataExplorer {
129
179
  }
130
180
 
131
181
  // 균등 샘플링 (PostgreSQL ROW_NUMBER 사용)
132
- private async sampleData(
133
- query: Knex.QueryBuilder,
134
- limit: number,
135
- ): Promise<Record<string, unknown>[]> {
182
+ private async sampleData(query: Knex.QueryBuilder, limit: number): Promise<DataExplorerRecord[]> {
136
183
  const [{ count }] = await query.clone().count("* as count");
137
184
  const total = Number(count);
138
185
 
139
186
  if (total <= limit) {
140
- return await query.limit(limit);
187
+ return dataExplorerRecordsSchema.parse(await query.limit(limit));
141
188
  }
142
189
 
143
190
  // 균등 간격 계산
@@ -163,14 +210,14 @@ export class DataExplorer {
163
210
  [tableName, interval, limit],
164
211
  );
165
212
 
166
- return result.rows;
213
+ return dataExplorerRecordsSchema.parse(result.rows);
167
214
  }
168
215
 
169
216
  private async randomSample(
170
217
  query: Knex.QueryBuilder,
171
218
  limit: number,
172
- ): Promise<Record<string, unknown>[]> {
173
- return await query.orderByRaw("RANDOM()").limit(limit);
219
+ ): Promise<DataExplorerRecord[]> {
220
+ return dataExplorerRecordsSchema.parse(await query.orderByRaw("RANDOM()").limit(limit));
174
221
  }
175
222
 
176
223
  private findTimestampColumn(entity: Entity, columnName: string): string | null {
@@ -178,29 +225,29 @@ export class DataExplorer {
178
225
  return prop?.name || null;
179
226
  }
180
227
 
181
- private async loadFromFile(filePath: string): Promise<Record<string, unknown>[]> {
228
+ private async loadFromFile(filePath: string): Promise<DataExplorerRecord[]> {
182
229
  const fs = await import("fs/promises");
183
230
  const content = await fs.readFile(filePath, "utf-8");
184
231
 
185
232
  if (filePath.endsWith(".json")) {
186
- const parsed = JSON.parse(content);
187
- if (!Array.isArray(parsed)) {
188
- throw new Error("JSON file must contain an array");
233
+ try {
234
+ const parsed = dataExplorerRecordsSchema.safeParse(JSON.parse(content));
235
+ if (!parsed.success) {
236
+ throw new Error("JSON file must contain an array of records");
237
+ }
238
+ return parsed.data;
239
+ } catch (error) {
240
+ throw new Error("JSON file must contain a valid array of records", { cause: error });
189
241
  }
190
- return parsed as Record<string, unknown>[];
191
242
  } else if (filePath.endsWith(".csv")) {
192
243
  const lines = content.split("\n").filter((line) => line.trim());
193
244
  if (lines.length === 0) return [];
194
245
 
195
246
  const headers = lines[0].split(",").map((h) => h.trim());
196
- return lines.slice(1).map((line) => {
247
+ return lines.slice(1).map((line): DataExplorerRecord => {
197
248
  const values = line.split(",");
198
- return headers.reduce(
199
- (obj: Record<string, unknown>, header: string, i: number) => {
200
- obj[header] = values[i]?.trim();
201
- return obj;
202
- },
203
- {} as Record<string, unknown>,
249
+ return Object.fromEntries(
250
+ headers.map((header, index) => [header, values[index]?.trim()] as const),
204
251
  );
205
252
  });
206
253
  }
@@ -212,7 +259,7 @@ export class DataExplorer {
212
259
  entityName: string,
213
260
  relationProp: string,
214
261
  options?: Partial<DataExplorerOptions>,
215
- ): Promise<Record<string, unknown>[]> {
262
+ ): Promise<DataExplorerRecord[]> {
216
263
  const entity = this.entityManager.get(entityName);
217
264
  const prop = entity.props.find((p) => p.name === relationProp);
218
265
 
@@ -222,17 +269,15 @@ export class DataExplorer {
222
269
 
223
270
  const dataSource = prop.cone?.dataSource;
224
271
  const strategy = dataSource?.strategy || options?.strategy || "sample";
225
- const config =
226
- dataSource?.config && typeof dataSource.config === "object"
227
- ? (dataSource.config as Record<string, unknown>)
228
- : {};
229
-
230
- return this.explore(prop.with, {
272
+ const config = dataSourceConfigSchema.parse(dataSource?.config ?? {});
273
+ const exploreOptions: DataExplorerOptions = {
231
274
  strategy,
232
- limit: options?.limit || (typeof config.limit === "number" ? config.limit : 10),
233
- ...(typeof config === "object" ? config : {}),
275
+ limit: options?.limit ?? config.limit ?? 10,
276
+ ...config,
234
277
  ...options,
235
- });
278
+ };
279
+
280
+ return this.explore(prop.with, exploreOptions);
236
281
  }
237
282
 
238
283
  /**
@@ -242,7 +287,7 @@ export class DataExplorer {
242
287
  entityName: string,
243
288
  relationProps: string[],
244
289
  options?: Partial<DataExplorerOptions>,
245
- ): Promise<Record<string, Record<string, unknown>[]>> {
290
+ ): Promise<Record<string, DataExplorerRecord[]>> {
246
291
  const results = await Promise.all(
247
292
  relationProps.map(async (prop) => {
248
293
  const data = await this.exploreRelation(entityName, prop, options);
@@ -314,8 +359,8 @@ export class DataExplorer {
314
359
  */
315
360
  private async collectRelatedData(
316
361
  entityName: string,
317
- records: Record<string, unknown>[],
318
- relatedMap: Map<string, Record<string, unknown>[]>,
362
+ records: DataExplorerRecord[],
363
+ relatedMap: Map<string, DataExplorerRecord[]>,
319
364
  visited: Set<string>,
320
365
  remainingDepth: number,
321
366
  ): Promise<void> {
@@ -324,7 +369,10 @@ export class DataExplorer {
324
369
  }
325
370
 
326
371
  const entity = this.entityManager.get(entityName);
327
- const recordIds = records.map((r) => r.id).filter(nonNullable);
372
+ const recordIds = records.flatMap((record) => {
373
+ const parsedId = z.union([z.number(), z.string()]).safeParse(record.id);
374
+ return parsedId.success ? [parsedId.data] : [];
375
+ });
328
376
 
329
377
  // 1. Forward references: 이 entity가 참조하는 다른 entity
330
378
  const forwardRelationProps = entity.props.filter(
@@ -345,9 +393,10 @@ export class DataExplorer {
345
393
 
346
394
  // 참조하는 ID들 수집
347
395
  const foreignKeyName = `${prop.name}_id`;
348
- const referencedIds = records
349
- .map((record) => record[foreignKeyName])
350
- .filter(Boolean) as number[];
396
+ const referencedIds = records.flatMap((record) => {
397
+ const parsedId = z.union([z.number(), z.string()]).safeParse(record[foreignKeyName]);
398
+ return parsedId.success ? [parsedId.data] : [];
399
+ });
351
400
 
352
401
  if (referencedIds.length === 0) {
353
402
  continue;
@@ -8,12 +8,34 @@ import { Sonamu } from "../api/sonamu";
8
8
  import { type SerializedTrace } from "../naite/naite";
9
9
  import { createSSEFactory } from "../stream/sse";
10
10
  import { type SSEConnection } from "../stream/sse";
11
- import { type ManagerStatus, type RunResult, type TestCaseResult } from "./dev-vitest-manager";
11
+ import {
12
+ type ManagerStatus,
13
+ type RunResult,
14
+ type TestCaseResult,
15
+ type TestEventPayload,
16
+ } from "./dev-vitest-manager";
12
17
  import { DevVitestManager } from "./dev-vitest-manager";
13
18
 
14
19
  const SCHEMA_VERSION = 1;
15
20
  const HEARTBEAT_INTERVAL_MS = 30_000;
16
21
 
22
+ export type DevTestRouteManager = Pick<
23
+ DevVitestManager,
24
+ | "start"
25
+ | "run"
26
+ | "getStatus"
27
+ | "emitEvent"
28
+ | "addEventListener"
29
+ | "removeEventListener"
30
+ | "shutdown"
31
+ >;
32
+
33
+ export type DevTestRouteDependencies = {
34
+ manager?: DevTestRouteManager;
35
+ apiRootPath?: string;
36
+ sseAvailable?: boolean;
37
+ };
38
+
17
39
  const SerializedTraceSchema = z.object({
18
40
  key: z.string(),
19
41
  value: z.unknown(),
@@ -48,6 +70,22 @@ const TestCaseResultSchema: z.ZodType<TestCaseResult> = z.lazy(() =>
48
70
  }),
49
71
  );
50
72
 
73
+ const RunResultSchema: z.ZodType<RunResult> = z.object({
74
+ ok: z.boolean(),
75
+ summary: z.object({
76
+ total: z.number(),
77
+ passed: z.number(),
78
+ failed: z.number(),
79
+ skipped: z.number(),
80
+ durationMs: z.number(),
81
+ }),
82
+ results: z.array(TestCaseResultSchema),
83
+ });
84
+ const RunRequestSchema = z.object({
85
+ files: z.array(z.string()).optional(),
86
+ pattern: z.string().optional(),
87
+ });
88
+
51
89
  const TestEventSchema = z.object({
52
90
  snapshot: z.object({
53
91
  schemaVersion: z.number(),
@@ -56,6 +94,7 @@ const TestEventSchema = z.object({
56
94
  ready: z.boolean(),
57
95
  running: z.boolean(),
58
96
  lastRunAt: z.string().nullable(),
97
+ sseAvailable: z.boolean(),
59
98
  }),
60
99
  }),
61
100
  runQueued: z.object({
@@ -77,7 +116,7 @@ const TestEventSchema = z.object({
77
116
  runId: z.string(),
78
117
  startedAt: z.string(),
79
118
  finishedAt: z.string(),
80
- result: z.record(z.string(), z.unknown()),
119
+ result: RunResultSchema,
81
120
  }),
82
121
  runErrored: z.object({
83
122
  schemaVersion: z.number(),
@@ -107,6 +146,15 @@ const TestEventSchema = z.object({
107
146
  });
108
147
 
109
148
  type TestEvents = z.infer<typeof TestEventSchema>;
149
+ const TestEventNameSchema = z.enum([
150
+ "snapshot",
151
+ "runQueued",
152
+ "runStarted",
153
+ "runCompleted",
154
+ "runErrored",
155
+ "runNodeProgress",
156
+ "heartbeat",
157
+ ]);
110
158
 
111
159
  function toPathPrefix(basePath: string): string {
112
160
  return basePath.endsWith("/") ? basePath : `${basePath}/`;
@@ -127,24 +175,6 @@ function relativizeNode(node: TestCaseResult, basePath: string): TestCaseResult
127
175
  };
128
176
  }
129
177
 
130
- function isRunNodeProgressData(data: unknown): data is TestEvents["runNodeProgress"] {
131
- if (typeof data !== "object" || data === null) return false;
132
- const d = data as Record<string, unknown>;
133
- return (
134
- d.schemaVersion === 1 &&
135
- typeof d.runId === "string" &&
136
- typeof d.startedAt === "string" &&
137
- typeof d.at === "string" &&
138
- (d.kind === "file" || d.kind === "suite" || d.kind === "test") &&
139
- (d.phase === "ready" || d.phase === "result") &&
140
- typeof d.fileId === "string" &&
141
- typeof d.nodeId === "string" &&
142
- (d.parentId === null || typeof d.parentId === "string") &&
143
- typeof d.node === "object" &&
144
- d.node !== null
145
- );
146
- }
147
-
148
178
  function relativizeResult(result: RunResult, basePath: string): RunResult {
149
179
  const prefix = toPathPrefix(basePath);
150
180
  return {
@@ -156,14 +186,21 @@ function relativizeResult(result: RunResult, basePath: string): RunResult {
156
186
  export async function registerDevTestRoutes(
157
187
  server: FastifyInstance,
158
188
  config: SonamuDevRunnerConfig,
189
+ dependencies?: DevTestRouteDependencies,
159
190
  ): Promise<void> {
160
191
  const prefix = config.routePrefix ?? "/__test__";
161
192
 
162
- const manager = new DevVitestManager();
193
+ const defaultManager = dependencies?.manager === undefined ? new DevVitestManager() : null;
194
+ const manager = dependencies?.manager ?? defaultManager;
195
+ if (manager === null) {
196
+ throw new Error("DevVitestManager dependency is unavailable");
197
+ }
163
198
  await manager.start(config.vitestConfigPath);
164
- Sonamu.devVitestManager = manager;
199
+ if (defaultManager !== null) {
200
+ Sonamu.devVitestManager = defaultManager;
201
+ }
165
202
 
166
- const sseAvailable = !!Sonamu.config.server.plugins?.sse;
203
+ const sseAvailable = dependencies?.sseAvailable ?? !!Sonamu.config.server.plugins?.sse;
167
204
 
168
205
  if (sseAvailable) {
169
206
  server.get(`${prefix}/events`, (request, reply): void => {
@@ -177,7 +214,8 @@ export async function registerDevTestRoutes(
177
214
  sse.publish("snapshot", {
178
215
  schemaVersion: SCHEMA_VERSION,
179
216
  serverTime: new Date().toISOString(),
180
- status,
217
+ // UI는 HTTP status와 같은 형태를 기대하므로 SSE 스냅샷에도 sseAvailable을 포함합니다.
218
+ status: { ...status, sseAvailable },
181
219
  });
182
220
 
183
221
  const heartbeatTimer = setInterval(() => {
@@ -187,19 +225,29 @@ export async function registerDevTestRoutes(
187
225
  });
188
226
  }, HEARTBEAT_INTERVAL_MS);
189
227
 
190
- const basePath = Sonamu.apiRootPath;
191
- const listener = (event: string, data: unknown) => {
192
- const key = event as keyof TestEvents;
228
+ const basePath = dependencies?.apiRootPath ?? Sonamu.apiRootPath;
229
+ const listener = (event: string, data: TestEventPayload) => {
230
+ const parsedKey = TestEventNameSchema.safeParse(event);
231
+ if (!parsedKey.success) return;
232
+ const key = parsedKey.data;
193
233
  if (key === "runNodeProgress") {
194
- if (!isRunNodeProgressData(data)) return;
234
+ const progressEnvelope = TestEventSchema.partial().safeParse({ runNodeProgress: data });
235
+ if (!progressEnvelope.success || progressEnvelope.data.runNodeProgress === undefined) {
236
+ return;
237
+ }
238
+ const progress = progressEnvelope.data.runNodeProgress;
195
239
  const relativized = {
196
- ...data,
197
- node: relativizeNode(data.node, basePath),
240
+ ...progress,
241
+ node: relativizeNode(progress.node, basePath),
198
242
  };
199
243
  sse.publish(key, relativized);
200
244
  return;
201
245
  }
202
- sse.publish(key, data as TestEvents[typeof key]);
246
+ const parsedEnvelope = TestEventSchema.partial().safeParse({ [key]: data });
247
+ if (!parsedEnvelope.success || parsedEnvelope.data[key] === undefined) return;
248
+ const parsedData = parsedEnvelope.data[key];
249
+ // SAFETY: key와 payload는 같은 TestEventSchema 항목으로 함께 검증되었습니다.
250
+ sse.publish(key, parsedData as TestEvents[typeof key]);
203
251
  };
204
252
  manager.addEventListener(listener);
205
253
 
@@ -216,17 +264,8 @@ export async function registerDevTestRoutes(
216
264
  }
217
265
 
218
266
  server.post(`${prefix}/run`, async (request, reply) => {
219
- if (!Sonamu.devVitestManager) {
220
- reply.status(503);
221
- return { ok: false, error: "DevVitestManager is not initialized" };
222
- }
223
-
224
267
  const runId = randomUUID();
225
- const body = request.body as { files?: string[]; pattern?: string } | null;
226
- const runRequest = {
227
- files: body?.files,
228
- pattern: body?.pattern,
229
- };
268
+ const runRequest = RunRequestSchema.parse(request.body ?? {});
230
269
 
231
270
  manager.emitEvent("runQueued", {
232
271
  schemaVersion: SCHEMA_VERSION,
@@ -243,8 +282,8 @@ export async function registerDevTestRoutes(
243
282
  startedAt,
244
283
  });
245
284
 
246
- const rawResult: RunResult = await Sonamu.devVitestManager.run(runRequest, runId);
247
- const result = relativizeResult(rawResult, Sonamu.apiRootPath);
285
+ const rawResult: RunResult = await manager.run(runRequest, runId);
286
+ const result = relativizeResult(rawResult, dependencies?.apiRootPath ?? Sonamu.apiRootPath);
248
287
 
249
288
  const finishedAt = new Date().toISOString();
250
289
  manager.emitEvent("runCompleted", {
@@ -274,16 +313,14 @@ export async function registerDevTestRoutes(
274
313
  });
275
314
 
276
315
  server.get(`${prefix}/status`, async () => {
277
- const status: ManagerStatus = Sonamu.devVitestManager?.getStatus() ?? {
278
- ready: false,
279
- running: false,
280
- lastRunAt: null,
281
- };
316
+ const status: ManagerStatus = manager.getStatus();
282
317
  return { ...status, sseAvailable };
283
318
  });
284
319
 
285
320
  server.addHook("onClose", async () => {
286
- await Sonamu.devVitestManager?.shutdown();
287
- Sonamu.devVitestManager = null;
321
+ await manager.shutdown();
322
+ if (defaultManager !== null) {
323
+ Sonamu.devVitestManager = null;
324
+ }
288
325
  });
289
326
  }