sonamu 0.4.13 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.swcrc +15 -0
  2. package/dist/api/base-frame.d.ts +8 -0
  3. package/dist/api/base-frame.d.ts.map +1 -0
  4. package/dist/api/base-frame.js +2 -0
  5. package/dist/api/base-frame.js.map +1 -0
  6. package/dist/api/caster.d.ts +5 -0
  7. package/dist/api/caster.d.ts.map +1 -0
  8. package/dist/api/caster.js +2 -0
  9. package/dist/api/caster.js.map +1 -0
  10. package/dist/api/code-converters.d.ts +23 -0
  11. package/dist/api/code-converters.d.ts.map +1 -0
  12. package/dist/api/code-converters.js +2 -0
  13. package/dist/api/code-converters.js.map +1 -0
  14. package/dist/api/context.d.ts +16 -0
  15. package/dist/api/context.d.ts.map +1 -0
  16. package/dist/api/context.js +2 -0
  17. package/dist/api/context.js.map +1 -0
  18. package/dist/api/decorators.d.ts +50 -0
  19. package/dist/api/decorators.d.ts.map +1 -0
  20. package/dist/api/decorators.js +2 -0
  21. package/dist/api/decorators.js.map +1 -0
  22. package/dist/api/index.d.ts +8 -0
  23. package/dist/api/index.d.ts.map +1 -0
  24. package/dist/api/index.js +2 -0
  25. package/dist/api/index.js.map +1 -0
  26. package/dist/api/sonamu.d.ts +83 -0
  27. package/dist/api/sonamu.d.ts.map +1 -0
  28. package/dist/api/sonamu.js +2 -0
  29. package/dist/api/sonamu.js.map +1 -0
  30. package/dist/api/sonamu.types.d.ts +30 -0
  31. package/dist/api/sonamu.types.d.ts.map +1 -0
  32. package/dist/api/sonamu.types.js +2 -0
  33. package/dist/api/sonamu.types.js.map +1 -0
  34. package/dist/bin/build-config.d.ts +5 -0
  35. package/dist/bin/build-config.d.ts.map +1 -0
  36. package/dist/bin/build-config.js +2 -0
  37. package/dist/bin/build-config.js.map +1 -0
  38. package/dist/bin/cli-wrapper.d.ts +2 -0
  39. package/dist/bin/cli-wrapper.d.ts.map +1 -0
  40. package/dist/bin/cli-wrapper.js +1 -38
  41. package/dist/bin/cli-wrapper.js.map +1 -1
  42. package/dist/bin/cli.d.ts +2 -2
  43. package/dist/bin/cli.d.ts.map +1 -0
  44. package/dist/bin/cli.js +1 -903
  45. package/dist/bin/cli.js.map +1 -1
  46. package/dist/bin/cli.mjs +2 -2
  47. package/dist/{chunk-DMJSNO2L.js → chunk-2WAC2GER.js} +44 -44
  48. package/dist/{chunk-DMJSNO2L.js.map → chunk-2WAC2GER.js.map} +1 -1
  49. package/dist/{chunk-NI37CY4T.mjs → chunk-C3IPIF6O.mjs} +2 -2
  50. package/dist/{chunk-DYFCACHD.js → chunk-EXHKSVTE.js} +7 -7
  51. package/dist/{chunk-QJFHDCBN.mjs → chunk-FCERKIIF.mjs} +2 -2
  52. package/dist/chunk-FCERKIIF.mjs.map +1 -0
  53. package/dist/{chunk-DDJ7T4MA.mjs → chunk-HGIBJYOU.mjs} +2 -2
  54. package/dist/{chunk-NIFOTHBW.mjs → chunk-JKSOJRQA.mjs} +2 -2
  55. package/dist/{chunk-CXAVBVKC.js → chunk-OTKKFP3Y.js} +100 -100
  56. package/dist/{chunk-J6S43O7G.js → chunk-UZ2IY5VE.js} +4 -4
  57. package/dist/database/_batch_update.d.ts +15 -0
  58. package/dist/database/_batch_update.d.ts.map +1 -0
  59. package/dist/database/_batch_update.js +2 -0
  60. package/dist/database/_batch_update.js.map +1 -0
  61. package/dist/database/base-model.d.ts +41 -0
  62. package/dist/database/base-model.d.ts.map +1 -0
  63. package/dist/database/base-model.js +2 -0
  64. package/dist/database/base-model.js.map +1 -0
  65. package/dist/database/code-generator.d.ts +13 -0
  66. package/dist/database/code-generator.d.ts.map +1 -0
  67. package/dist/database/code-generator.js +2 -0
  68. package/dist/database/code-generator.js.map +1 -0
  69. package/dist/database/db.d.ts +40 -0
  70. package/dist/database/db.d.ts.map +1 -0
  71. package/dist/database/db.js +2 -0
  72. package/dist/database/db.js.map +1 -0
  73. package/dist/database/drivers/knex/base-model.js +8 -8
  74. package/dist/database/drivers/knex/base-model.mjs +3 -3
  75. package/dist/database/drivers/kysely/base-model.js +9 -9
  76. package/dist/database/drivers/kysely/base-model.mjs +3 -3
  77. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
  78. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
  79. package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
  80. package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
  81. package/dist/database/puri-wrapper.d.ts +34 -0
  82. package/dist/database/puri-wrapper.d.ts.map +1 -0
  83. package/dist/database/puri-wrapper.js +2 -0
  84. package/dist/database/puri-wrapper.js.map +1 -0
  85. package/dist/database/puri.d.ts +83 -0
  86. package/dist/database/puri.d.ts.map +1 -0
  87. package/dist/database/puri.js +2 -0
  88. package/dist/database/puri.js.map +1 -0
  89. package/dist/database/puri.types.d.ts +60 -0
  90. package/dist/database/puri.types.d.ts.map +1 -0
  91. package/dist/database/puri.types.js +2 -0
  92. package/dist/database/puri.types.js.map +1 -0
  93. package/dist/database/transaction-context.d.ts +9 -0
  94. package/dist/database/transaction-context.d.ts.map +1 -0
  95. package/dist/database/transaction-context.js +2 -0
  96. package/dist/database/transaction-context.js.map +1 -0
  97. package/dist/database/types.d.ts +39 -0
  98. package/dist/database/types.d.ts.map +1 -0
  99. package/dist/database/types.js +2 -0
  100. package/dist/database/types.js.map +1 -0
  101. package/dist/database/upsert-builder.d.ts +34 -0
  102. package/dist/database/upsert-builder.d.ts.map +1 -0
  103. package/dist/database/upsert-builder.js +2 -0
  104. package/dist/database/upsert-builder.js.map +1 -0
  105. package/dist/entity/entity-manager.d.ts +32 -0
  106. package/dist/entity/entity-manager.d.ts.map +1 -0
  107. package/dist/entity/entity-manager.js +2 -0
  108. package/dist/entity/entity-manager.js.map +1 -0
  109. package/dist/entity/entity-utils.d.ts +61 -0
  110. package/dist/entity/entity-utils.d.ts.map +1 -0
  111. package/dist/entity/entity-utils.js +2 -0
  112. package/dist/entity/entity-utils.js.map +1 -0
  113. package/dist/entity/entity.d.ts +62 -0
  114. package/dist/entity/entity.d.ts.map +1 -0
  115. package/dist/entity/entity.js +2 -0
  116. package/dist/entity/entity.js.map +1 -0
  117. package/dist/entity/migrator.d.ts +135 -0
  118. package/dist/entity/migrator.d.ts.map +1 -0
  119. package/dist/entity/migrator.js +2 -0
  120. package/dist/entity/migrator.js.map +1 -0
  121. package/dist/exceptions/error-handler.d.ts +3 -0
  122. package/dist/exceptions/error-handler.d.ts.map +1 -0
  123. package/dist/exceptions/error-handler.js +2 -0
  124. package/dist/exceptions/error-handler.js.map +1 -0
  125. package/dist/exceptions/so-exceptions.d.ts +48 -0
  126. package/dist/exceptions/so-exceptions.d.ts.map +1 -0
  127. package/dist/exceptions/so-exceptions.js +2 -0
  128. package/dist/exceptions/so-exceptions.js.map +1 -0
  129. package/dist/file-storage/driver.d.ts +45 -0
  130. package/dist/file-storage/driver.d.ts.map +1 -0
  131. package/dist/file-storage/driver.js +2 -0
  132. package/dist/file-storage/driver.js.map +1 -0
  133. package/dist/file-storage/file-storage.d.ts +50 -0
  134. package/dist/file-storage/file-storage.d.ts.map +1 -0
  135. package/dist/file-storage/file-storage.js +2 -0
  136. package/dist/file-storage/file-storage.js.map +1 -0
  137. package/dist/index.d.ts +22 -813
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +1 -433
  140. package/dist/index.js.map +1 -1
  141. package/dist/index.mjs +3 -3
  142. package/dist/migration/code-generation.d.ts +15 -0
  143. package/dist/migration/code-generation.d.ts.map +1 -0
  144. package/dist/migration/code-generation.js +2 -0
  145. package/dist/migration/code-generation.js.map +1 -0
  146. package/dist/migration/migration-set.d.ts +17 -0
  147. package/dist/migration/migration-set.d.ts.map +1 -0
  148. package/dist/migration/migration-set.js +2 -0
  149. package/dist/migration/migration-set.js.map +1 -0
  150. package/dist/migration/migrator.d.ts +130 -0
  151. package/dist/migration/migrator.d.ts.map +1 -0
  152. package/dist/migration/migrator.js +2 -0
  153. package/dist/migration/migrator.js.map +1 -0
  154. package/dist/migration/types.d.ts +52 -0
  155. package/dist/migration/types.d.ts.map +1 -0
  156. package/dist/migration/types.js +2 -0
  157. package/dist/migration/types.js.map +1 -0
  158. package/dist/smd/smd-manager.d.ts +28 -0
  159. package/dist/smd/smd-manager.d.ts.map +1 -0
  160. package/dist/smd/smd-manager.js +2 -0
  161. package/dist/smd/smd-manager.js.map +1 -0
  162. package/dist/smd/smd.d.ts +40 -0
  163. package/dist/smd/smd.d.ts.map +1 -0
  164. package/dist/smd/smd.js +2 -0
  165. package/dist/smd/smd.js.map +1 -0
  166. package/dist/syncer/index.d.ts +2 -0
  167. package/dist/syncer/index.d.ts.map +1 -0
  168. package/dist/syncer/index.js +2 -0
  169. package/dist/syncer/index.js.map +1 -0
  170. package/dist/syncer/syncer.d.ts +127 -0
  171. package/dist/syncer/syncer.d.ts.map +1 -0
  172. package/dist/syncer/syncer.js +2 -0
  173. package/dist/syncer/syncer.js.map +1 -0
  174. package/dist/templates/base-template.d.ts +13 -0
  175. package/dist/templates/base-template.d.ts.map +1 -0
  176. package/dist/templates/base-template.js +2 -0
  177. package/dist/templates/base-template.js.map +1 -0
  178. package/dist/templates/entity.template.d.ts +17 -0
  179. package/dist/templates/entity.template.d.ts.map +1 -0
  180. package/dist/templates/entity.template.js +2 -0
  181. package/dist/templates/entity.template.js.map +1 -0
  182. package/dist/templates/generated.template.d.ts +27 -0
  183. package/dist/templates/generated.template.d.ts.map +1 -0
  184. package/dist/templates/generated.template.js +2 -0
  185. package/dist/templates/generated.template.js.map +1 -0
  186. package/dist/templates/generated_http.template.d.ts +24 -0
  187. package/dist/templates/generated_http.template.d.ts.map +1 -0
  188. package/dist/templates/generated_http.template.js +2 -0
  189. package/dist/templates/generated_http.template.js.map +1 -0
  190. package/dist/templates/generated_sso.template.d.ts +20 -0
  191. package/dist/templates/generated_sso.template.d.ts.map +1 -0
  192. package/dist/templates/generated_sso.template.js +2 -0
  193. package/dist/templates/generated_sso.template.js.map +1 -0
  194. package/dist/templates/index.d.ts +2 -0
  195. package/dist/templates/index.d.ts.map +1 -0
  196. package/dist/templates/index.js +2 -0
  197. package/dist/templates/index.js.map +1 -0
  198. package/dist/templates/init_types.template.d.ts +17 -0
  199. package/dist/templates/init_types.template.d.ts.map +1 -0
  200. package/dist/templates/init_types.template.js +2 -0
  201. package/dist/templates/init_types.template.js.map +1 -0
  202. package/dist/templates/model.template.d.ts +17 -0
  203. package/dist/templates/model.template.d.ts.map +1 -0
  204. package/dist/templates/model.template.js +2 -0
  205. package/dist/templates/model.template.js.map +1 -0
  206. package/dist/templates/model_test.template.d.ts +17 -0
  207. package/dist/templates/model_test.template.d.ts.map +1 -0
  208. package/dist/templates/model_test.template.js +2 -0
  209. package/dist/templates/model_test.template.js.map +1 -0
  210. package/dist/templates/service.template.d.ts +29 -0
  211. package/dist/templates/service.template.d.ts.map +1 -0
  212. package/dist/templates/service.template.js +2 -0
  213. package/dist/templates/service.template.js.map +1 -0
  214. package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
  215. package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
  216. package/dist/templates/view_enums_buttonset.template.js +2 -0
  217. package/dist/templates/view_enums_buttonset.template.js.map +1 -0
  218. package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
  219. package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
  220. package/dist/templates/view_enums_dropdown.template.js +2 -0
  221. package/dist/templates/view_enums_dropdown.template.js.map +1 -0
  222. package/dist/templates/view_enums_select.template.d.ts +17 -0
  223. package/dist/templates/view_enums_select.template.d.ts.map +1 -0
  224. package/dist/templates/view_enums_select.template.js +2 -0
  225. package/dist/templates/view_enums_select.template.js.map +1 -0
  226. package/dist/templates/view_form.template.d.ts +26 -0
  227. package/dist/templates/view_form.template.d.ts.map +1 -0
  228. package/dist/templates/view_form.template.js +2 -0
  229. package/dist/templates/view_form.template.js.map +1 -0
  230. package/dist/templates/view_id_all_select.template.d.ts +17 -0
  231. package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
  232. package/dist/templates/view_id_all_select.template.js +2 -0
  233. package/dist/templates/view_id_all_select.template.js.map +1 -0
  234. package/dist/templates/view_id_async_select.template.d.ts +17 -0
  235. package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
  236. package/dist/templates/view_id_async_select.template.js +2 -0
  237. package/dist/templates/view_id_async_select.template.js.map +1 -0
  238. package/dist/templates/view_list.template.d.ts +38 -0
  239. package/dist/templates/view_list.template.d.ts.map +1 -0
  240. package/dist/templates/view_list.template.js +2 -0
  241. package/dist/templates/view_list.template.js.map +1 -0
  242. package/dist/templates/view_list_columns.template.d.ts +17 -0
  243. package/dist/templates/view_list_columns.template.d.ts.map +1 -0
  244. package/dist/templates/view_list_columns.template.js +2 -0
  245. package/dist/templates/view_list_columns.template.js.map +1 -0
  246. package/dist/templates/view_search_input.template.d.ts +17 -0
  247. package/dist/templates/view_search_input.template.d.ts.map +1 -0
  248. package/dist/templates/view_search_input.template.js +2 -0
  249. package/dist/templates/view_search_input.template.js.map +1 -0
  250. package/dist/testing/_relation-graph.d.ts +7 -0
  251. package/dist/testing/_relation-graph.d.ts.map +1 -0
  252. package/dist/testing/_relation-graph.js +2 -0
  253. package/dist/testing/_relation-graph.js.map +1 -0
  254. package/dist/testing/fixture-manager.d.ts +35 -0
  255. package/dist/testing/fixture-manager.d.ts.map +1 -0
  256. package/dist/testing/fixture-manager.js +2 -0
  257. package/dist/testing/fixture-manager.js.map +1 -0
  258. package/dist/types/types.d.ts +609 -0
  259. package/dist/types/types.d.ts.map +1 -0
  260. package/dist/types/types.js +2 -0
  261. package/dist/types/types.js.map +1 -0
  262. package/dist/typings/knex.d.js +2 -0
  263. package/dist/typings/knex.d.js.map +1 -0
  264. package/dist/utils/async-utils.d.ts +25 -0
  265. package/dist/utils/async-utils.d.ts.map +1 -0
  266. package/dist/utils/async-utils.js +2 -0
  267. package/dist/utils/async-utils.js.map +1 -0
  268. package/dist/utils/controller.d.ts +9 -0
  269. package/dist/utils/controller.d.ts.map +1 -0
  270. package/dist/utils/controller.js +2 -0
  271. package/dist/utils/controller.js.map +1 -0
  272. package/dist/utils/fs-utils.d.ts +9 -0
  273. package/dist/utils/fs-utils.d.ts.map +1 -0
  274. package/dist/utils/fs-utils.js +2 -0
  275. package/dist/utils/fs-utils.js.map +1 -0
  276. package/dist/utils/lodash-able.d.ts +2 -0
  277. package/dist/utils/lodash-able.d.ts.map +1 -0
  278. package/dist/utils/lodash-able.js +2 -0
  279. package/dist/utils/lodash-able.js.map +1 -0
  280. package/dist/utils/model.d.ts +17 -0
  281. package/dist/utils/model.d.ts.map +1 -0
  282. package/dist/utils/model.js +2 -0
  283. package/dist/utils/model.js.map +1 -0
  284. package/dist/utils/sql-parser.d.ts +4 -0
  285. package/dist/utils/sql-parser.d.ts.map +1 -0
  286. package/dist/utils/sql-parser.js +2 -0
  287. package/dist/utils/sql-parser.js.map +1 -0
  288. package/dist/utils/utils.d.ts +9 -0
  289. package/dist/utils/utils.d.ts.map +1 -0
  290. package/dist/utils/utils.js +2 -0
  291. package/dist/utils/utils.js.map +1 -0
  292. package/dist/utils/zod-error.d.ts +8 -0
  293. package/dist/utils/zod-error.d.ts.map +1 -0
  294. package/dist/utils/zod-error.js +2 -0
  295. package/dist/utils/zod-error.js.map +1 -0
  296. package/nodemon.json +6 -0
  297. package/package.json +29 -44
  298. package/src/api/base-frame.ts +3 -4
  299. package/src/api/caster.ts +22 -23
  300. package/src/api/code-converters.ts +170 -134
  301. package/src/api/context.ts +13 -6
  302. package/src/api/decorators.ts +146 -20
  303. package/src/api/index.ts +2 -0
  304. package/src/api/sonamu.ts +374 -165
  305. package/src/bin/build-config.ts +5 -0
  306. package/src/bin/cli-wrapper.ts +29 -40
  307. package/src/bin/cli.ts +132 -190
  308. package/src/database/_batch_update.ts +10 -15
  309. package/src/database/base-model.ts +300 -216
  310. package/src/database/db.ts +191 -21
  311. package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
  312. package/src/database/puri-wrapper.ts +129 -0
  313. package/src/database/puri.ts +808 -0
  314. package/src/database/puri.types.ts +222 -0
  315. package/src/database/transaction-context.ts +18 -0
  316. package/src/database/upsert-builder.ts +32 -35
  317. package/src/entity/entity-manager.ts +7 -15
  318. package/src/entity/entity.ts +9 -31
  319. package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
  320. package/src/file-storage/driver.ts +121 -0
  321. package/src/file-storage/file-storage.ts +100 -0
  322. package/src/index.ts +14 -11
  323. package/src/migration/code-generation.ts +777 -0
  324. package/src/migration/migration-set.ts +453 -0
  325. package/src/migration/migrator.ts +823 -0
  326. package/src/migration/types.ts +53 -0
  327. package/src/shared/web.shared.ts.txt +33 -2
  328. package/src/syncer/syncer.ts +294 -127
  329. package/src/templates/generated.template.ts +13 -1
  330. package/src/templates/generated_http.template.ts +15 -12
  331. package/src/templates/generated_sso.template.ts +50 -2
  332. package/src/templates/model.template.ts +138 -2
  333. package/src/templates/service.template.ts +0 -1
  334. package/src/templates/view_form.template.ts +11 -7
  335. package/src/templates/view_list.template.ts +12 -4
  336. package/src/testing/fixture-manager.ts +229 -174
  337. package/src/types/types.ts +102 -14
  338. package/src/utils/async-utils.ts +64 -0
  339. package/src/utils/fs-utils.ts +17 -0
  340. package/src/utils/model.ts +0 -2
  341. package/src/utils/utils.ts +14 -58
  342. package/src/utils/zod-error.ts +12 -176
  343. package/tsconfig.json +2 -0
  344. package/tsup.config.js +4 -2
  345. package/.pnp.cjs +0 -14363
  346. package/.pnp.loader.mjs +0 -2047
  347. package/.vscode/extensions.json +0 -6
  348. package/.vscode/settings.json +0 -9
  349. package/.yarnrc.yml +0 -5
  350. package/dist/chunk-QJFHDCBN.mjs.map +0 -1
  351. package/src/database/base-model.abstract.ts +0 -97
  352. package/src/database/db.abstract.ts +0 -75
  353. package/src/database/drivers/knex/base-model.ts +0 -55
  354. package/src/database/drivers/knex/client.ts +0 -209
  355. package/src/database/drivers/knex/db.ts +0 -232
  356. package/src/database/drivers/knex/generator.ts +0 -659
  357. package/src/database/drivers/kysely/base-model.ts +0 -89
  358. package/src/database/drivers/kysely/client.ts +0 -309
  359. package/src/database/drivers/kysely/db.ts +0 -238
  360. package/src/database/drivers/kysely/generator.ts +0 -714
  361. package/src/database/types.ts +0 -118
  362. package/src/entity/migrator.ts +0 -1400
  363. package/src/smd/smd-manager.ts +0 -139
  364. package/src/smd/smd.ts +0 -571
  365. package/src/templates/kysely_types.template.ts +0 -205
  366. /package/dist/{chunk-NI37CY4T.mjs.map → chunk-C3IPIF6O.mjs.map} +0 -0
  367. /package/dist/{chunk-DYFCACHD.js.map → chunk-EXHKSVTE.js.map} +0 -0
  368. /package/dist/{chunk-DDJ7T4MA.mjs.map → chunk-HGIBJYOU.mjs.map} +0 -0
  369. /package/dist/{chunk-NIFOTHBW.mjs.map → chunk-JKSOJRQA.mjs.map} +0 -0
  370. /package/dist/{chunk-CXAVBVKC.js.map → chunk-OTKKFP3Y.js.map} +0 -0
  371. /package/dist/{chunk-J6S43O7G.js.map → chunk-UZ2IY5VE.js.map} +0 -0
package/src/api/sonamu.ts CHANGED
@@ -1,23 +1,35 @@
1
+ import path from "path";
2
+ import { readFile } from "fs/promises";
3
+ import { exists } from "../utils/fs-utils";
4
+ import { AsyncLocalStorage } from "async_hooks";
1
5
  import chalk from "chalk";
2
- import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
3
- import { IncomingMessage, Server, ServerResponse } from "http";
6
+ import fastify from "fastify";
7
+
4
8
  import { ZodError } from "zod";
5
- import path from "path";
6
- import fs from "fs-extra";
7
9
  import { getZodObjectFromApi } from "./code-converters";
8
- import { Context } from "./context";
9
- import { BadRequestException } from "../exceptions/so-exceptions";
10
- import { EntityManager } from "../entity/entity-manager";
10
+ import {
11
+ BadRequestException,
12
+ NotFoundException,
13
+ } from "../exceptions/so-exceptions";
14
+ import { humanizeZodError } from "../utils/zod-error";
11
15
  import { fastifyCaster } from "./caster";
12
- import { ApiParam, ApiParamType } from "../types/types";
13
- import { Syncer } from "../syncer/syncer";
16
+ import {
17
+ ApiParamType,
18
+ SonamuFastifyConfig,
19
+ SonamuServerOptions,
20
+ } from "../types/types";
14
21
  import { isLocal, isTest } from "../utils/controller";
15
22
  import { findApiRootPath } from "../utils/utils";
16
- import { ApiDecoratorOptions } from "./decorators";
17
- import { humanizeZodError } from "../utils/zod-error";
18
- import { DatabaseDriver, SonamuDBConfig } from "../database/types";
19
- import { DB } from "../database/db";
20
- import { AsyncLocalStorage } from "async_hooks";
23
+ import { DB, SonamuDBConfig } from "../database/db";
24
+ import { attachOnDuplicateUpdate } from "../database/knex-plugins/knex-on-duplicate-update";
25
+ import type { ExtendedApi } from "./decorators";
26
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
27
+ import type { IncomingMessage, Server, ServerResponse } from "http";
28
+ import type { Context, UploadContext } from "./context";
29
+ import type { Syncer } from "../syncer/syncer";
30
+ import type { FSWatcher } from "chokidar";
31
+ import { formatInTimeZone } from "date-fns-tz";
32
+ import type { Driver } from "../file-storage/driver";
21
33
 
22
34
  export type SonamuConfig = {
23
35
  projectName?: string;
@@ -30,54 +42,24 @@ export type SonamuConfig = {
30
42
  route: {
31
43
  prefix: string;
32
44
  };
45
+ timezone?: string;
46
+ ui?: {
47
+ port: number;
48
+ };
33
49
  };
34
50
  export type SonamuSecrets = {
35
51
  [key: string]: string;
36
52
  };
37
- type SonamuFastifyConfig = {
38
- contextProvider: (
39
- defaultContext: Pick<Context, "headers" | "reply">,
40
- request: FastifyRequest,
41
- reply: FastifyReply
42
- ) => Context;
43
- guardHandler: (
44
- guard: string,
45
- request: FastifyRequest,
46
- api: {
47
- typeParameters: ApiParamType.TypeParam[];
48
- parameters: ApiParam[];
49
- returnType: ApiParamType;
50
- modelName: string;
51
- methodName: string;
52
- path: string;
53
- options: ApiDecoratorOptions;
54
- }
55
- ) => void;
56
- cache?: {
57
- get: (key: string) => Promise<unknown | null>;
58
- put: (key: string, value: unknown, ttl?: number) => Promise<void>;
59
- resolveKey: (
60
- path: string,
61
- reqBody: {
62
- [key: string]: unknown;
63
- }
64
- ) =>
65
- | {
66
- cache: false;
67
- }
68
- | {
69
- cache: true;
70
- key: string;
71
- ttl?: number;
72
- };
73
- };
74
- };
75
53
  class SonamuClass {
76
54
  public isInitialized: boolean = false;
77
55
  public asyncLocalStorage: AsyncLocalStorage<{
78
56
  context: Context;
79
57
  }> = new AsyncLocalStorage();
80
58
 
59
+ public uploadStorage: AsyncLocalStorage<{
60
+ uploadContext: UploadContext;
61
+ }> = new AsyncLocalStorage();
62
+
81
63
  public getContext(): Context {
82
64
  const store = this.asyncLocalStorage.getStore();
83
65
  if (store?.context) {
@@ -86,6 +68,16 @@ class SonamuClass {
86
68
  throw new Error("Sonamu cannot find context");
87
69
  }
88
70
 
71
+ public getUploadContext(): UploadContext {
72
+ const store = this.uploadStorage.getStore();
73
+ if (store?.uploadContext) {
74
+ return store.uploadContext;
75
+ }
76
+ throw new Error(
77
+ "Sonamu cannot find upload context. Did you use @upload decorator?"
78
+ );
79
+ }
80
+
89
81
  private _apiRootPath: string | null = null;
90
82
  set apiRootPath(apiRootPath: string) {
91
83
  this._apiRootPath = apiRootPath;
@@ -104,24 +96,13 @@ class SonamuClass {
104
96
  set dbConfig(dbConfig: SonamuDBConfig) {
105
97
  this._dbConfig = dbConfig;
106
98
  }
107
- get dbConfig() {
99
+ get dbConfig(): SonamuDBConfig {
108
100
  if (this._dbConfig === null) {
109
101
  throw new Error("Sonamu has not been initialized");
110
102
  }
111
103
  return this._dbConfig!;
112
104
  }
113
105
 
114
- private _dbClient: DatabaseDriver | null = null;
115
- set dbClient(_dbClient: DatabaseDriver) {
116
- this._dbClient = _dbClient;
117
- }
118
- get dbClient() {
119
- if (this._dbClient === null) {
120
- throw new Error("Sonamu has not been initialized");
121
- }
122
- return this._dbClient!;
123
- }
124
-
125
106
  private _syncer: Syncer | null = null;
126
107
  set syncer(syncer: Syncer) {
127
108
  this._syncer = syncer;
@@ -152,6 +133,21 @@ class SonamuClass {
152
133
  return this._secrets;
153
134
  }
154
135
 
136
+ private _storage: Driver | null = null;
137
+ set storage(storage: Driver) {
138
+ this._storage = storage;
139
+ }
140
+ get storage(): Driver | null {
141
+ return this._storage;
142
+ }
143
+
144
+ // HMR 처리
145
+ public watcher: FSWatcher | null = null;
146
+ private pendingFiles: string[] = [];
147
+ private hmrStartTime: number = 0;
148
+
149
+ public server: FastifyInstance | null = null;
150
+
155
151
  async initForTesting() {
156
152
  await this.init(true, false, undefined, true);
157
153
  }
@@ -171,26 +167,25 @@ class SonamuClass {
171
167
  );
172
168
 
173
169
  // API 루트 패스
174
- this.apiRootPath = apiRootPath ?? (await findApiRootPath());
170
+ this.apiRootPath = apiRootPath ?? findApiRootPath();
175
171
  const configPath = path.join(this.apiRootPath, "sonamu.config.json");
176
172
  const secretsPath = path.join(this.apiRootPath, "sonamu.secrets.json");
177
- if (fs.existsSync(configPath) === false) {
173
+ if (!(await exists(configPath))) {
178
174
  throw new Error(`Cannot find sonamu.config.json in ${configPath}`);
179
175
  }
180
176
  this.config = JSON.parse(
181
- fs.readFileSync(configPath).toString()
177
+ (await readFile(configPath)).toString()
182
178
  ) as SonamuConfig;
183
- if (fs.existsSync(secretsPath)) {
179
+ if (await exists(secretsPath)) {
184
180
  this.secrets = JSON.parse(
185
- fs.readFileSync(secretsPath).toString()
181
+ (await readFile(secretsPath)).toString()
186
182
  ) as SonamuSecrets;
187
183
  }
188
184
 
189
185
  // DB 로드
190
- const baseConfig = await DB.getBaseConfig(this.apiRootPath);
191
- this.dbClient = baseConfig.client;
192
- DB.init(baseConfig as any);
193
- this.dbConfig = DB.fullConfig;
186
+ this.dbConfig = await DB.readKnexfile();
187
+ !doSilent && console.log(chalk.green("DB Config Loaded!"));
188
+ attachOnDuplicateUpdate();
194
189
 
195
190
  // 테스팅인 경우 엔티티 로드 & 싱크 없이 중단
196
191
  if (forTesting) {
@@ -199,9 +194,11 @@ class SonamuClass {
199
194
  }
200
195
 
201
196
  // Entity 로드
197
+ const { EntityManager } = await import("../entity/entity-manager");
202
198
  await EntityManager.autoload(doSilent);
203
199
 
204
200
  // Syncer
201
+ const { Syncer } = await import("../syncer/syncer");
205
202
  this.syncer = new Syncer();
206
203
 
207
204
  // Autoload: Models / Types / APIs
@@ -212,17 +209,48 @@ class SonamuClass {
212
209
  if (isLocal() && !isTest() && enableSync) {
213
210
  await this.syncer.sync();
214
211
 
215
- fetch("http://127.0.0.1:57001/api/reload", {
216
- method: "GET",
217
- }).catch((e) =>
218
- console.log(chalk.dim(`Failed to reload Sonamu UI: ${e.message}`))
219
- );
212
+ // FIXME: hmr 설정된 경우만 워처 시작
213
+ this.startWatcher();
214
+
215
+ this.syncer.syncUI();
220
216
  }
221
217
 
222
218
  this.isInitialized = true;
223
219
  !doSilent && console.timeEnd(chalk.cyan("Sonamu.init"));
224
220
  }
225
221
 
222
+ async createServer(
223
+ options: SonamuServerOptions,
224
+ initOptions?: {
225
+ enableSync?: boolean;
226
+ doSilent?: boolean;
227
+ }
228
+ ) {
229
+ const server = fastify(options.fastify);
230
+ this.server = server;
231
+
232
+ // Storage 설정 저장
233
+ if (options.storage) {
234
+ this.storage = options.storage;
235
+ }
236
+
237
+ // 플러그인 등록
238
+ if (options.plugins) {
239
+ this.registerPlugins(server, options.plugins);
240
+ }
241
+
242
+ // API 라우팅 설정
243
+ await this.withFastify(server, options.apiConfig, {
244
+ enableSync: initOptions?.enableSync,
245
+ doSilent: initOptions?.doSilent,
246
+ });
247
+
248
+ // 서버 시작
249
+ await this.boot(server, options);
250
+
251
+ return server;
252
+ }
253
+
226
254
  async withFastify(
227
255
  server: FastifyInstance<Server, IncomingMessage, ServerResponse>,
228
256
  config: SonamuFastifyConfig,
@@ -235,6 +263,27 @@ class SonamuClass {
235
263
  await this.init(options?.doSilent, options?.enableSync);
236
264
  }
237
265
 
266
+ this.server = server;
267
+
268
+ // timezone 설정
269
+ const timezone = this.config.timezone;
270
+ if (timezone) {
271
+ const DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
272
+ // ISO 8601 날짜 형식 정규식 (예: 2024-01-15T09:30:00.000Z)
273
+ const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
274
+
275
+ server.setReplySerializer((payload) => {
276
+ return JSON.stringify(payload, (_key, value) => {
277
+ if (typeof value === "string" && ISO_DATE_REGEX.test(value)) {
278
+ return formatInTimeZone(new Date(value), timezone, DATE_FORMAT);
279
+ }
280
+ return value;
281
+ });
282
+ });
283
+ !options?.doSilent &&
284
+ console.log(chalk.green(`Timezone set to ${timezone}`));
285
+ }
286
+
238
287
  // 전체 라우팅 리스트
239
288
  server.get(
240
289
  `${this.config.route.prefix}/routes`,
@@ -251,107 +300,267 @@ class SonamuClass {
251
300
  }
252
301
  );
253
302
 
254
- // API 라우팅 등록
255
- this.syncer.apis.map((api) => {
256
- // model
257
- if (this.syncer.models[api.modelName] === undefined) {
258
- throw new Error(`정의되지 않은 모델에 접근 ${api.modelName}`);
259
- }
260
- const model = this.syncer.models[api.modelName];
303
+ // API 라우팅 (로컬HMR 상태와 구분)
304
+ if (isLocal()) {
305
+ server.all("*", (request, reply) => {
306
+ const found = this.syncer.apis.find(
307
+ (api) =>
308
+ this.config.route.prefix + api.path === request.url.split("?")[0] &&
309
+ (api.options.httpMethod ?? "GET") === request.method.toUpperCase()
310
+ );
311
+ if (found) {
312
+ return this.getApiHandler(found, config)(request, reply);
313
+ }
314
+ throw new NotFoundException("존재하지 않는 API 접근입니다.");
315
+ });
316
+ } else {
317
+ this.syncer.apis.map((api) => {
318
+ // model
319
+ if (this.syncer.models[api.modelName] === undefined) {
320
+ throw new Error(`정의되지 않은 모델에 접근 ${api.modelName}`);
321
+ }
322
+
323
+ // route
324
+ server.route({
325
+ method: api.options.httpMethod!,
326
+ url: this.config.route.prefix + api.path,
327
+ handler: this.getApiHandler(api, config),
328
+ }); // END server.route
329
+ });
330
+ }
331
+ }
332
+
333
+ getApiHandler(api: ExtendedApi, config: SonamuFastifyConfig) {
334
+ return async (
335
+ request: FastifyRequest,
336
+ reply: FastifyReply
337
+ ): Promise<unknown> => {
338
+ (api.options.guards ?? []).every((guard) =>
339
+ config.guardHandler(guard, request, api)
340
+ );
261
341
 
262
342
  // 파라미터 정보로 zod 스키마 빌드
263
343
  const ReqType = getZodObjectFromApi(api, this.syncer.types);
264
344
 
265
- // route
266
- server.route({
267
- method: api.options.httpMethod!,
268
- url: this.config.route.prefix + api.path,
269
- handler: async (request, reply): Promise<unknown> => {
270
- (api.options.guards ?? []).every((guard) =>
271
- config.guardHandler(guard, request, api)
272
- );
273
-
274
- // request 파싱
275
- const which = api.options.httpMethod === "GET" ? "query" : "body";
276
- let reqBody: {
277
- [key: string]: unknown;
278
- };
279
- try {
280
- reqBody = fastifyCaster(ReqType).parse(request[which] ?? {});
281
- } catch (e) {
282
- if (e instanceof ZodError) {
283
- const messages = humanizeZodError(e)
284
- .map((issue) => issue.message)
285
- .join(" ");
286
- throw new BadRequestException(messages);
287
- } else {
288
- throw e;
289
- }
290
- }
345
+ // request 파싱
346
+ const which = api.options.httpMethod === "GET" ? "query" : "body";
347
+ let reqBody: {
348
+ [key: string]: unknown;
349
+ };
350
+ try {
351
+ reqBody = fastifyCaster(ReqType).parse(request[which] ?? {});
352
+ } catch (e) {
353
+ if (e instanceof ZodError) {
354
+ const messages = humanizeZodError(e)
355
+ .map((issue) => issue.message)
356
+ .join(" ");
357
+ throw new BadRequestException(messages, {
358
+ zodError: e,
359
+ });
360
+ } else {
361
+ throw e;
362
+ }
363
+ }
364
+
365
+ // Content-Type
366
+ reply.type(api.options.contentType ?? "application/json");
291
367
 
292
- // Content-Type
293
- reply.type(api.options.contentType ?? "application/json");
294
-
295
- // 캐시
296
- const { cacheKey, cacheTtl, cachedData } = await (async () => {
297
- if (config.cache) {
298
- try {
299
- const cacheKeyRes = config.cache.resolveKey(api.path, reqBody);
300
- if (cacheKeyRes.cache === false) {
301
- return { cacheKey: null, cachedData: null };
302
- }
303
-
304
- const cacheKey = cacheKeyRes.key;
305
- const cacheTtl = cacheKeyRes.ttl;
306
- const cachedData = await config.cache.get(cacheKey);
307
- return { cacheKey, cacheTtl, cachedData };
308
- } catch (e) {
309
- console.error(e);
310
- }
368
+ // 캐시
369
+ const { cacheKey, cacheTtl, cachedData } = await (async () => {
370
+ if (config.cache) {
371
+ try {
372
+ const cacheKeyRes = config.cache.resolveKey(api.path, reqBody);
373
+ if (cacheKeyRes.cache === false) {
311
374
  return { cacheKey: null, cachedData: null };
312
375
  }
313
- return { cacheKey: null, cachedData: null };
314
- })();
315
- if (cachedData !== null) {
316
- return cachedData;
376
+
377
+ const cacheKey = cacheKeyRes.key;
378
+ const cacheTtl = cacheKeyRes.ttl;
379
+ const cachedData = await config.cache.get(cacheKey);
380
+ return { cacheKey, cacheTtl, cachedData };
381
+ } catch (e) {
382
+ console.error(e);
317
383
  }
384
+ return { cacheKey: null, cachedData: null };
385
+ }
386
+ return { cacheKey: null, cachedData: null };
387
+ })();
388
+ if (cachedData !== null) {
389
+ return cachedData;
390
+ }
318
391
 
319
- // 결과 (AsyncLocalStorage 적용)
320
- const context = config.contextProvider(
321
- {
322
- headers: request.headers,
323
- reply,
324
- },
325
- request,
326
- reply
327
- );
328
- return this.asyncLocalStorage.run({ context }, async () => {
329
- const result = await (model as any)[api.methodName].apply(
330
- model,
331
- api.parameters.map((param) => {
332
- // Context 인젝션
333
- if (ApiParamType.isContext(param.type)) {
334
- return context;
335
- } else {
336
- return reqBody[param.name];
337
- }
338
- })
339
- );
340
- reply.type(api.options.contentType ?? "application/json");
341
-
342
- // 캐시 키 있는 경우 갱신 후 저장
343
- if (config.cache && cacheKey) {
344
- await config.cache.put(cacheKey, result, cacheTtl);
345
- }
346
- return result;
347
- });
392
+ // 결과 (AsyncLocalStorage 적용)
393
+ const context = config.contextProvider(
394
+ {
395
+ request,
396
+ reply,
397
+ headers: request.headers,
348
398
  },
349
- }); // END server.route
399
+ request,
400
+ reply
401
+ );
402
+ const model = this.syncer.models[api.modelName];
403
+ return this.asyncLocalStorage.run({ context }, async () => {
404
+ const result = await (model as any)[api.methodName].apply(
405
+ model,
406
+ api.parameters.map((param) => {
407
+ // Context 인젝션
408
+ if (ApiParamType.isContext(param.type)) {
409
+ return context;
410
+ } else {
411
+ return reqBody[param.name];
412
+ }
413
+ })
414
+ );
415
+ reply.type(api.options.contentType ?? "application/json");
416
+
417
+ // 캐시 키 있는 경우 갱신 후 저장
418
+ if (config.cache && cacheKey) {
419
+ await config.cache.put(cacheKey, result, cacheTtl);
420
+ }
421
+ return result;
422
+ });
423
+ };
424
+ }
425
+
426
+ startWatcher(): void {
427
+ const watchPath = path.join(this.apiRootPath, "src");
428
+ const chokidar = require("chokidar") as typeof import("chokidar");
429
+ this.watcher = chokidar.watch(watchPath, {
430
+ ignored: (path, stats) =>
431
+ (!!stats?.isFile() &&
432
+ !path.endsWith(".ts") &&
433
+ !path.endsWith(".json")) ||
434
+ path.endsWith("src/index.ts"),
435
+ persistent: true,
436
+ ignoreInitial: true,
350
437
  });
438
+ this.watcher.on("all", async (event: string, filePath: string) => {
439
+ if (event !== "change" && event !== "add") {
440
+ return;
441
+ }
442
+
443
+ await this.handleFileChange(event, filePath);
444
+ });
445
+ }
446
+
447
+ private registerPlugins(
448
+ server: FastifyInstance,
449
+ plugins: SonamuServerOptions["plugins"]
450
+ ) {
451
+ if (!plugins) {
452
+ return;
453
+ }
454
+
455
+ const pluginsModules = {
456
+ formbody: "@fastify/formbody",
457
+ qs: "fastify-qs",
458
+ cors: "@fastify/cors",
459
+ multipart: "@fastify/multipart",
460
+ } as const;
461
+
462
+ const registerPlugin = <K extends keyof NonNullable<typeof plugins>>(
463
+ key: K,
464
+ pluginName: string
465
+ ) => {
466
+ const option = plugins[key];
467
+ if (!option) return;
468
+
469
+ if (option === true) {
470
+ server.register(import(pluginName));
471
+ } else {
472
+ server.register(import(pluginName), option);
473
+ }
474
+ };
475
+
476
+ Object.entries(pluginsModules).forEach(([key, pluginName]) => {
477
+ registerPlugin(key as keyof typeof plugins, pluginName);
478
+ });
479
+
480
+ if (plugins.custom) {
481
+ plugins.custom(server);
482
+ }
483
+ }
484
+
485
+ private async boot(server: FastifyInstance, options: SonamuServerOptions) {
486
+ const port = options.listen?.port ?? 3000;
487
+ const host = options.listen?.host ?? "localhost";
488
+
489
+ server.addHook("onClose", async () => {
490
+ await options.lifecycle?.onShutdown?.(server);
491
+ await this.destroy();
492
+ });
493
+
494
+ const shutdown = async () => {
495
+ try {
496
+ await server.close();
497
+ process.exit(0);
498
+ } catch (err) {
499
+ console.error("Error during shutdown:", err);
500
+ process.exit(1);
501
+ }
502
+ };
503
+
504
+ process.on("SIGINT", shutdown);
505
+ process.on("SIGTERM", shutdown);
506
+
507
+ if (options.lifecycle?.onError) {
508
+ server.setErrorHandler(options.lifecycle?.onError);
509
+ }
510
+
511
+ server
512
+ .listen({ port, host })
513
+ .then(async () => {
514
+ await options.lifecycle?.onStart?.(server);
515
+ })
516
+ .catch(async (err) => {
517
+ console.error(chalk.red("Failed to start server:", err));
518
+ await shutdown();
519
+ });
520
+ }
521
+
522
+ private async handleFileChange(
523
+ event: string,
524
+ filePath: string
525
+ ): Promise<void> {
526
+ // 첫 번째 파일이면 HMR 시작 시간 기록
527
+ if (this.pendingFiles.length === 0) {
528
+ this.hmrStartTime = Date.now();
529
+ }
530
+
531
+ this.pendingFiles.push(filePath);
532
+
533
+ const relativePath = filePath.replace(this.apiRootPath, "api");
534
+ console.log(chalk.bold(`Detected(${event}): ${chalk.blue(relativePath)}`));
535
+
536
+ await this.syncer.syncFromWatcher([filePath]);
537
+
538
+ // 처리 완료된 파일을 대기 목록에서 제거
539
+ this.pendingFiles = this.pendingFiles.slice(1);
540
+
541
+ // 모든 파일 처리가 완료되면 최종 메시지 출력
542
+ if (this.pendingFiles.length === 0) {
543
+ await this.finishHMR();
544
+ }
545
+ }
546
+
547
+ private async finishHMR(): Promise<void> {
548
+ await this.syncer.saveChecksums(await this.syncer.getCurrentChecksums());
549
+
550
+ const endTime = Date.now();
551
+ const totalTime = endTime - this.hmrStartTime;
552
+ const msg = `HMR Done! ${chalk.bold.white(`${totalTime}ms`)}`;
553
+ const margin = Math.max(0, (process.stdout.columns - msg.length) / 2);
554
+
555
+ console.log(
556
+ chalk.black.bgGreen(" ".repeat(margin) + msg + " ".repeat(margin))
557
+ );
351
558
  }
352
559
 
353
560
  async destroy(): Promise<void> {
354
- await DB.destroy();
561
+ const { BaseModel } = require("../database/base-model");
562
+ await BaseModel.destroy();
563
+ await this.watcher?.close();
355
564
  }
356
565
  }
357
566
  export const Sonamu = new SonamuClass();
@@ -0,0 +1,5 @@
1
+ /**
2
+ * SWC 빌드 명령어
3
+ */
4
+ export const SWC_BUILD_COMMAND =
5
+ "swc src -d dist --strip-leading-paths --source-maps -C module.type=commonjs -C jsc.parser.syntax=typescript -C jsc.parser.decorators=true -C jsc.target=es5";