sonamu 0.4.14 → 0.5.1

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