sonamu 0.5.6 → 0.6.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 (365) hide show
  1. package/dist/api/base-frame.js +12 -2
  2. package/dist/api/caster.js +66 -2
  3. package/dist/api/code-converters.js +489 -2
  4. package/dist/api/config.d.ts +76 -0
  5. package/dist/api/config.d.ts.map +1 -0
  6. package/dist/api/config.js +32 -0
  7. package/dist/api/context.d.ts +1 -0
  8. package/dist/api/context.d.ts.map +1 -1
  9. package/dist/api/context.js +3 -2
  10. package/dist/api/decorators.d.ts +1 -0
  11. package/dist/api/decorators.d.ts.map +1 -1
  12. package/dist/api/decorators.js +142 -2
  13. package/dist/api/index.js +9 -2
  14. package/dist/api/sonamu.d.ts +8 -22
  15. package/dist/api/sonamu.d.ts.map +1 -1
  16. package/dist/api/sonamu.js +482 -2
  17. package/dist/bin/build-config.d.ts +2 -1
  18. package/dist/bin/build-config.d.ts.map +1 -1
  19. package/dist/bin/build-config.js +12 -2
  20. package/dist/bin/cli-wrapper.js +71 -2
  21. package/dist/bin/cli.js +418 -2
  22. package/dist/bin/hot-hook-register.d.ts +11 -0
  23. package/dist/bin/hot-hook-register.d.ts.map +1 -0
  24. package/dist/bin/hot-hook-register.js +21 -0
  25. package/dist/database/_batch_update.js +78 -2
  26. package/dist/database/base-model.js +247 -2
  27. package/dist/database/code-generator.js +53 -2
  28. package/dist/database/db.d.ts +5 -16
  29. package/dist/database/db.d.ts.map +1 -1
  30. package/dist/database/db.js +132 -2
  31. package/dist/database/knex-plugins/knex-on-duplicate-update.js +39 -2
  32. package/dist/database/puri-wrapper.d.ts +22 -10
  33. package/dist/database/puri-wrapper.d.ts.map +1 -1
  34. package/dist/database/puri-wrapper.js +109 -2
  35. package/dist/database/puri.d.ts +105 -73
  36. package/dist/database/puri.d.ts.map +1 -1
  37. package/dist/database/puri.js +539 -2
  38. package/dist/database/puri.types.d.ts +33 -42
  39. package/dist/database/puri.types.d.ts.map +1 -1
  40. package/dist/database/puri.types.js +3 -2
  41. package/dist/database/transaction-context.d.ts +3 -3
  42. package/dist/database/transaction-context.d.ts.map +1 -1
  43. package/dist/database/transaction-context.js +14 -2
  44. package/dist/database/upsert-builder.js +215 -2
  45. package/dist/entity/entity-manager.d.ts +3 -1
  46. package/dist/entity/entity-manager.d.ts.map +1 -1
  47. package/dist/entity/entity-manager.js +114 -2
  48. package/dist/entity/entity-utils.js +210 -2
  49. package/dist/entity/entity.d.ts.map +1 -1
  50. package/dist/entity/entity.js +651 -2
  51. package/dist/exceptions/error-handler.js +29 -2
  52. package/dist/exceptions/so-exceptions.js +85 -2
  53. package/dist/file-storage/driver.js +79 -2
  54. package/dist/file-storage/file-storage.js +75 -2
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +28 -2
  58. package/dist/migration/code-generation.js +558 -2
  59. package/dist/migration/migration-set.js +364 -2
  60. package/dist/migration/migrator.d.ts +0 -9
  61. package/dist/migration/migrator.d.ts.map +1 -1
  62. package/dist/migration/migrator.js +510 -2
  63. package/dist/migration/types.js +3 -2
  64. package/dist/naite/naite.d.ts +12 -0
  65. package/dist/naite/naite.d.ts.map +1 -0
  66. package/dist/naite/naite.js +72 -0
  67. package/dist/stream/index.js +3 -2
  68. package/dist/stream/sse.js +38 -2
  69. package/dist/syncer/api-parser.d.ts +20 -0
  70. package/dist/syncer/api-parser.d.ts.map +1 -0
  71. package/dist/syncer/api-parser.js +229 -0
  72. package/dist/syncer/checksum.d.ts +21 -0
  73. package/dist/syncer/checksum.d.ts.map +1 -0
  74. package/dist/syncer/checksum.js +98 -0
  75. package/dist/syncer/code-generator.d.ts +20 -0
  76. package/dist/syncer/code-generator.d.ts.map +1 -0
  77. package/dist/syncer/code-generator.js +141 -0
  78. package/dist/syncer/entity-operations.d.ts +17 -0
  79. package/dist/syncer/entity-operations.d.ts.map +1 -0
  80. package/dist/syncer/entity-operations.js +58 -0
  81. package/dist/syncer/file-patterns.d.ts +29 -0
  82. package/dist/syncer/file-patterns.d.ts.map +1 -0
  83. package/dist/syncer/file-patterns.js +38 -0
  84. package/dist/syncer/index.d.ts +6 -0
  85. package/dist/syncer/index.d.ts.map +1 -1
  86. package/dist/syncer/index.js +9 -2
  87. package/dist/syncer/module-loader.d.ts +35 -0
  88. package/dist/syncer/module-loader.d.ts.map +1 -0
  89. package/dist/syncer/module-loader.js +82 -0
  90. package/dist/syncer/syncer.d.ts +93 -108
  91. package/dist/syncer/syncer.d.ts.map +1 -1
  92. package/dist/syncer/syncer.js +375 -2
  93. package/dist/template/entity-converter.d.ts +14 -0
  94. package/dist/template/entity-converter.d.ts.map +1 -0
  95. package/dist/template/entity-converter.js +101 -0
  96. package/dist/template/helpers.d.ts +23 -0
  97. package/dist/template/helpers.d.ts.map +1 -0
  98. package/dist/template/helpers.js +64 -0
  99. package/dist/{templates → template/implementations}/entity.template.d.ts +3 -3
  100. package/dist/template/implementations/entity.template.d.ts.map +1 -0
  101. package/dist/template/implementations/entity.template.js +87 -0
  102. package/dist/{templates → template/implementations}/generated.template.d.ts +3 -3
  103. package/dist/template/implementations/generated.template.d.ts.map +1 -0
  104. package/dist/template/implementations/generated.template.js +232 -0
  105. package/dist/{templates → template/implementations}/generated_http.template.d.ts +3 -3
  106. package/dist/template/implementations/generated_http.template.d.ts.map +1 -0
  107. package/dist/template/implementations/generated_http.template.js +131 -0
  108. package/dist/{templates → template/implementations}/generated_sso.template.d.ts +3 -3
  109. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -0
  110. package/dist/template/implementations/generated_sso.template.js +105 -0
  111. package/dist/{templates → template/implementations}/init_types.template.d.ts +3 -3
  112. package/dist/template/implementations/init_types.template.d.ts.map +1 -0
  113. package/dist/template/implementations/init_types.template.js +38 -0
  114. package/dist/template/implementations/model.template.d.ts +17 -0
  115. package/dist/template/implementations/model.template.d.ts.map +1 -0
  116. package/dist/template/implementations/model.template.js +171 -0
  117. package/dist/{templates → template/implementations}/model_test.template.d.ts +3 -3
  118. package/dist/template/implementations/model_test.template.d.ts.map +1 -0
  119. package/dist/template/implementations/model_test.template.js +35 -0
  120. package/dist/{templates → template/implementations}/service.template.d.ts +6 -6
  121. package/dist/template/implementations/service.template.d.ts.map +1 -0
  122. package/dist/template/implementations/service.template.js +193 -0
  123. package/dist/{templates → template/implementations}/view_enums_buttonset.template.d.ts +3 -3
  124. package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +1 -0
  125. package/dist/template/implementations/view_enums_buttonset.template.js +31 -0
  126. package/dist/{templates → template/implementations}/view_enums_dropdown.template.d.ts +3 -4
  127. package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +1 -0
  128. package/dist/template/implementations/view_enums_dropdown.template.js +50 -0
  129. package/dist/{templates → template/implementations}/view_enums_select.template.d.ts +3 -3
  130. package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -0
  131. package/dist/template/implementations/view_enums_select.template.js +55 -0
  132. package/dist/{templates → template/implementations}/view_form.template.d.ts +5 -5
  133. package/dist/template/implementations/view_form.template.d.ts.map +1 -0
  134. package/dist/template/implementations/view_form.template.js +337 -0
  135. package/dist/{templates → template/implementations}/view_id_all_select.template.d.ts +3 -3
  136. package/dist/template/implementations/view_id_all_select.template.d.ts.map +1 -0
  137. package/dist/template/implementations/view_id_all_select.template.js +31 -0
  138. package/dist/{templates → template/implementations}/view_id_async_select.template.d.ts +3 -3
  139. package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -0
  140. package/dist/template/implementations/view_id_async_select.template.js +105 -0
  141. package/dist/{templates → template/implementations}/view_list.template.d.ts +5 -13
  142. package/dist/template/implementations/view_list.template.d.ts.map +1 -0
  143. package/dist/template/implementations/view_list.template.js +465 -0
  144. package/dist/{templates → template/implementations}/view_list_columns.template.d.ts +3 -3
  145. package/dist/template/implementations/view_list_columns.template.d.ts.map +1 -0
  146. package/dist/template/implementations/view_list_columns.template.js +49 -0
  147. package/dist/{templates → template/implementations}/view_search_input.template.d.ts +3 -3
  148. package/dist/template/implementations/view_search_input.template.d.ts.map +1 -0
  149. package/dist/template/implementations/view_search_input.template.js +64 -0
  150. package/dist/template/index.d.ts +5 -0
  151. package/dist/template/index.d.ts.map +1 -0
  152. package/dist/template/index.js +6 -0
  153. package/dist/template/template.d.ts +39 -0
  154. package/dist/template/template.d.ts.map +1 -0
  155. package/dist/template/template.js +47 -0
  156. package/dist/template/zod-converter.d.ts +18 -0
  157. package/dist/template/zod-converter.d.ts.map +1 -0
  158. package/dist/template/zod-converter.js +166 -0
  159. package/dist/testing/_relation-graph.js +80 -2
  160. package/dist/testing/fixture-manager.d.ts.map +1 -1
  161. package/dist/testing/fixture-manager.js +521 -2
  162. package/dist/types/types.d.ts +39 -40
  163. package/dist/types/types.d.ts.map +1 -1
  164. package/dist/types/types.js +289 -2
  165. package/dist/typings/knex.d.js +3 -2
  166. package/dist/utils/async-utils.d.ts +7 -0
  167. package/dist/utils/async-utils.d.ts.map +1 -1
  168. package/dist/utils/async-utils.js +57 -2
  169. package/dist/utils/console-util.d.ts +2 -0
  170. package/dist/utils/console-util.d.ts.map +1 -0
  171. package/dist/utils/console-util.js +6 -0
  172. package/dist/utils/controller.js +26 -2
  173. package/dist/utils/esm-utils.d.ts +45 -0
  174. package/dist/utils/esm-utils.d.ts.map +1 -0
  175. package/dist/utils/esm-utils.js +56 -0
  176. package/dist/utils/fs-utils.js +17 -2
  177. package/dist/utils/lodash-able.js +6 -2
  178. package/dist/utils/model.js +22 -2
  179. package/dist/utils/path-utils.d.ts +89 -0
  180. package/dist/utils/path-utils.d.ts.map +1 -0
  181. package/dist/utils/path-utils.js +60 -0
  182. package/dist/utils/process-utils.d.ts +13 -0
  183. package/dist/utils/process-utils.d.ts.map +1 -0
  184. package/dist/utils/process-utils.js +36 -0
  185. package/dist/utils/sql-parser.js +35 -2
  186. package/dist/utils/utils.d.ts +4 -7
  187. package/dist/utils/utils.d.ts.map +1 -1
  188. package/dist/utils/utils.js +33 -2
  189. package/dist/utils/zod-error.d.ts.map +1 -1
  190. package/dist/utils/zod-error.js +19 -2
  191. package/package.json +21 -9
  192. package/src/api/code-converters.ts +2 -2
  193. package/src/api/config.ts +142 -0
  194. package/src/api/context.ts +1 -0
  195. package/src/api/decorators.ts +15 -5
  196. package/src/api/sonamu.ts +102 -87
  197. package/src/bin/build-config.ts +2 -1
  198. package/src/bin/cli-wrapper.ts +10 -3
  199. package/src/bin/cli.ts +108 -56
  200. package/src/bin/hot-hook-register.ts +22 -0
  201. package/src/database/base-model.ts +1 -1
  202. package/src/database/code-generator.ts +1 -1
  203. package/src/database/db.ts +53 -60
  204. package/src/database/puri-wrapper.ts +104 -26
  205. package/src/database/puri.ts +477 -580
  206. package/src/database/puri.types.ts +111 -201
  207. package/src/database/transaction-context.ts +4 -4
  208. package/src/database/upsert-builder.ts +1 -1
  209. package/src/entity/entity-manager.ts +19 -15
  210. package/src/entity/entity.ts +4 -3
  211. package/src/index.ts +2 -0
  212. package/src/migration/code-generation.ts +1 -1
  213. package/src/migration/migration-set.ts +1 -1
  214. package/src/migration/migrator.ts +23 -152
  215. package/src/naite/naite.ts +70 -0
  216. package/src/syncer/api-parser.ts +299 -0
  217. package/src/syncer/checksum.ts +152 -0
  218. package/src/syncer/code-generator.ts +202 -0
  219. package/src/syncer/entity-operations.ts +68 -0
  220. package/src/syncer/file-patterns.ts +56 -0
  221. package/src/syncer/index.ts +6 -0
  222. package/src/syncer/module-loader.ts +125 -0
  223. package/src/syncer/syncer.ts +363 -1420
  224. package/src/template/entity-converter.ts +123 -0
  225. package/src/template/helpers.ts +84 -0
  226. package/src/{templates → template/implementations}/entity.template.ts +4 -4
  227. package/src/{templates → template/implementations}/generated.template.ts +9 -9
  228. package/src/{templates → template/implementations}/generated_http.template.ts +9 -6
  229. package/src/{templates → template/implementations}/generated_sso.template.ts +7 -7
  230. package/src/{templates → template/implementations}/init_types.template.ts +4 -4
  231. package/src/{templates → template/implementations}/model.template.ts +9 -9
  232. package/src/{templates → template/implementations}/model_test.template.ts +5 -5
  233. package/src/{templates → template/implementations}/service.template.ts +29 -12
  234. package/src/{templates → template/implementations}/view_enums_buttonset.template.ts +3 -3
  235. package/src/{templates → template/implementations}/view_enums_dropdown.template.ts +5 -21
  236. package/src/{templates → template/implementations}/view_enums_select.template.ts +4 -4
  237. package/src/{templates → template/implementations}/view_form.template.ts +11 -13
  238. package/src/{templates → template/implementations}/view_id_all_select.template.ts +3 -3
  239. package/src/{templates → template/implementations}/view_id_async_select.template.ts +3 -3
  240. package/src/{templates → template/implementations}/view_list.template.ts +13 -64
  241. package/src/{templates → template/implementations}/view_list_columns.template.ts +3 -3
  242. package/src/{templates → template/implementations}/view_search_input.template.ts +3 -3
  243. package/src/template/index.ts +4 -0
  244. package/src/template/template.ts +86 -0
  245. package/src/template/zod-converter.ts +219 -0
  246. package/src/testing/fixture-manager.ts +8 -1
  247. package/src/types/types.ts +39 -62
  248. package/src/utils/async-utils.ts +17 -0
  249. package/src/utils/console-util.ts +4 -0
  250. package/src/utils/esm-utils.ts +69 -0
  251. package/src/utils/path-utils.ts +102 -0
  252. package/src/utils/process-utils.ts +46 -0
  253. package/src/utils/sql-parser.ts +1 -1
  254. package/src/utils/utils.ts +14 -40
  255. package/src/utils/zod-error.ts +0 -1
  256. package/dist/api/base-frame.js.map +0 -1
  257. package/dist/api/caster.js.map +0 -1
  258. package/dist/api/code-converters.js.map +0 -1
  259. package/dist/api/context.js.map +0 -1
  260. package/dist/api/decorators.js.map +0 -1
  261. package/dist/api/index.js.map +0 -1
  262. package/dist/api/sonamu.js.map +0 -1
  263. package/dist/bin/build-config.js.map +0 -1
  264. package/dist/bin/cli-wrapper.js.map +0 -1
  265. package/dist/bin/cli.js.map +0 -1
  266. package/dist/database/_batch_update.js.map +0 -1
  267. package/dist/database/base-model.js.map +0 -1
  268. package/dist/database/code-generator.js.map +0 -1
  269. package/dist/database/db.js.map +0 -1
  270. package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +0 -1
  271. package/dist/database/puri-wrapper.js.map +0 -1
  272. package/dist/database/puri.js.map +0 -1
  273. package/dist/database/puri.types.js.map +0 -1
  274. package/dist/database/transaction-context.js.map +0 -1
  275. package/dist/database/upsert-builder.js.map +0 -1
  276. package/dist/entity/entity-manager.js.map +0 -1
  277. package/dist/entity/entity-utils.js.map +0 -1
  278. package/dist/entity/entity.js.map +0 -1
  279. package/dist/exceptions/error-handler.js.map +0 -1
  280. package/dist/exceptions/so-exceptions.js.map +0 -1
  281. package/dist/file-storage/driver.js.map +0 -1
  282. package/dist/file-storage/file-storage.js.map +0 -1
  283. package/dist/index.js.map +0 -1
  284. package/dist/migration/code-generation.js.map +0 -1
  285. package/dist/migration/migration-set.js.map +0 -1
  286. package/dist/migration/migrator.js.map +0 -1
  287. package/dist/migration/types.js.map +0 -1
  288. package/dist/stream/index.js.map +0 -1
  289. package/dist/stream/sse.js.map +0 -1
  290. package/dist/syncer/index.js.map +0 -1
  291. package/dist/syncer/syncer.js.map +0 -1
  292. package/dist/templates/base-template.d.ts +0 -13
  293. package/dist/templates/base-template.d.ts.map +0 -1
  294. package/dist/templates/base-template.js +0 -2
  295. package/dist/templates/base-template.js.map +0 -1
  296. package/dist/templates/entity.template.d.ts.map +0 -1
  297. package/dist/templates/entity.template.js +0 -2
  298. package/dist/templates/entity.template.js.map +0 -1
  299. package/dist/templates/generated.template.d.ts.map +0 -1
  300. package/dist/templates/generated.template.js +0 -2
  301. package/dist/templates/generated.template.js.map +0 -1
  302. package/dist/templates/generated_http.template.d.ts.map +0 -1
  303. package/dist/templates/generated_http.template.js +0 -2
  304. package/dist/templates/generated_http.template.js.map +0 -1
  305. package/dist/templates/generated_sso.template.d.ts.map +0 -1
  306. package/dist/templates/generated_sso.template.js +0 -2
  307. package/dist/templates/generated_sso.template.js.map +0 -1
  308. package/dist/templates/index.d.ts +0 -2
  309. package/dist/templates/index.d.ts.map +0 -1
  310. package/dist/templates/index.js +0 -2
  311. package/dist/templates/index.js.map +0 -1
  312. package/dist/templates/init_types.template.d.ts.map +0 -1
  313. package/dist/templates/init_types.template.js +0 -2
  314. package/dist/templates/init_types.template.js.map +0 -1
  315. package/dist/templates/model.template.d.ts +0 -17
  316. package/dist/templates/model.template.d.ts.map +0 -1
  317. package/dist/templates/model.template.js +0 -2
  318. package/dist/templates/model.template.js.map +0 -1
  319. package/dist/templates/model_test.template.d.ts.map +0 -1
  320. package/dist/templates/model_test.template.js +0 -2
  321. package/dist/templates/model_test.template.js.map +0 -1
  322. package/dist/templates/service.template.d.ts.map +0 -1
  323. package/dist/templates/service.template.js +0 -2
  324. package/dist/templates/service.template.js.map +0 -1
  325. package/dist/templates/view_enums_buttonset.template.d.ts.map +0 -1
  326. package/dist/templates/view_enums_buttonset.template.js +0 -2
  327. package/dist/templates/view_enums_buttonset.template.js.map +0 -1
  328. package/dist/templates/view_enums_dropdown.template.d.ts.map +0 -1
  329. package/dist/templates/view_enums_dropdown.template.js +0 -2
  330. package/dist/templates/view_enums_dropdown.template.js.map +0 -1
  331. package/dist/templates/view_enums_select.template.d.ts.map +0 -1
  332. package/dist/templates/view_enums_select.template.js +0 -2
  333. package/dist/templates/view_enums_select.template.js.map +0 -1
  334. package/dist/templates/view_form.template.d.ts.map +0 -1
  335. package/dist/templates/view_form.template.js +0 -2
  336. package/dist/templates/view_form.template.js.map +0 -1
  337. package/dist/templates/view_id_all_select.template.d.ts.map +0 -1
  338. package/dist/templates/view_id_all_select.template.js +0 -2
  339. package/dist/templates/view_id_all_select.template.js.map +0 -1
  340. package/dist/templates/view_id_async_select.template.d.ts.map +0 -1
  341. package/dist/templates/view_id_async_select.template.js +0 -2
  342. package/dist/templates/view_id_async_select.template.js.map +0 -1
  343. package/dist/templates/view_list.template.d.ts.map +0 -1
  344. package/dist/templates/view_list.template.js +0 -2
  345. package/dist/templates/view_list.template.js.map +0 -1
  346. package/dist/templates/view_list_columns.template.d.ts.map +0 -1
  347. package/dist/templates/view_list_columns.template.js +0 -2
  348. package/dist/templates/view_list_columns.template.js.map +0 -1
  349. package/dist/templates/view_search_input.template.d.ts.map +0 -1
  350. package/dist/templates/view_search_input.template.js +0 -2
  351. package/dist/templates/view_search_input.template.js.map +0 -1
  352. package/dist/testing/_relation-graph.js.map +0 -1
  353. package/dist/testing/fixture-manager.js.map +0 -1
  354. package/dist/types/types.js.map +0 -1
  355. package/dist/typings/knex.d.js.map +0 -1
  356. package/dist/utils/async-utils.js.map +0 -1
  357. package/dist/utils/controller.js.map +0 -1
  358. package/dist/utils/fs-utils.js.map +0 -1
  359. package/dist/utils/lodash-able.js.map +0 -1
  360. package/dist/utils/model.js.map +0 -1
  361. package/dist/utils/sql-parser.js.map +0 -1
  362. package/dist/utils/utils.js.map +0 -1
  363. package/dist/utils/zod-error.js.map +0 -1
  364. package/src/templates/base-template.ts +0 -19
  365. package/src/templates/index.ts +0 -1
@@ -0,0 +1,152 @@
1
+ import path from "path";
2
+ import { globAsync } from "../utils/async-utils";
3
+ import { createReadStream, PathLike } from "fs";
4
+ import { readFile, writeFile } from "fs/promises";
5
+ import { exists } from "../utils/fs-utils";
6
+
7
+ import crypto from "crypto";
8
+ import equal from "fast-deep-equal";
9
+ import * as _ from "lodash-es";
10
+ import { Sonamu } from "../api/sonamu";
11
+ import {
12
+ AbsolutePath,
13
+ ApiRelativePath,
14
+ } from "../utils/path-utils";
15
+ import { getChecksumPatternGroupInAbsolutePath } from "./file-patterns";
16
+
17
+ type PathAndChecksum = {
18
+ path: AbsolutePath;
19
+ checksum: string;
20
+ };
21
+
22
+ /**
23
+ * 체크섬 파일에 저장된 내용과 현재 실제 파일의 체크섬을 비교하여 변경된 파일을 찾습니다.
24
+ * @returns 변경된 파일 경로 배열. 프로젝트 루트부터 슬래시로 시작합니다. 예시: "/src/application/user/user.model.ts"
25
+ */
26
+ export async function findChangedFilesUsingChecksums(): Promise<
27
+ AbsolutePath[]
28
+ > {
29
+ const calculatedChecksums = await getCurrentChecksums();
30
+ const savedChecksums = await getPreviousChecksums();
31
+
32
+ const isSame = equal(calculatedChecksums, savedChecksums);
33
+ if (isSame) {
34
+ return [];
35
+ }
36
+
37
+ const diff = _.differenceWith(calculatedChecksums, savedChecksums, _.isEqual);
38
+
39
+ return diff.map((r) => r.path);
40
+ }
41
+
42
+ /**
43
+ * 체크섬을 갱신합니다.
44
+ * 현재 파일들의 체크섬을 계산해서 구한 다음, 체크섬 파일에 저장된 내용과 다르면 체크섬 파일을 갱신합니다.
45
+ */
46
+ export async function renewChecksums(): Promise<void> {
47
+ const calculatedChecksums = await getCurrentChecksums();
48
+ const savedChecksums = await getPreviousChecksums();
49
+
50
+ const isSame = equal(calculatedChecksums, savedChecksums);
51
+ if (isSame) {
52
+ return;
53
+ }
54
+
55
+ await saveChecksums(calculatedChecksums);
56
+ }
57
+
58
+ /**
59
+ * 두 파일의 내용이 같은지 체크섬으로 비교합니다.
60
+ * 만약 파일이 둘 중 하나라도 없다면 비교 불가로 false 반환합니다.
61
+ * @param one 파일 경로
62
+ * @param two 파일 경로
63
+ * @returns boolean
64
+ */
65
+ export async function areFilesSame(
66
+ one: PathLike,
67
+ two: PathLike
68
+ ): Promise<boolean> {
69
+ if (!(await exists(one)) || !(await exists(two))) {
70
+ return false;
71
+ }
72
+
73
+ const oneChecksum = await getChecksumOfFile(one);
74
+ const twoChecksum = await getChecksumOfFile(two);
75
+
76
+ return oneChecksum === twoChecksum;
77
+ }
78
+
79
+ async function getCurrentChecksums(): Promise<PathAndChecksum[]> {
80
+ const filePaths = (
81
+ await Promise.all(
82
+ Object.entries(getChecksumPatternGroupInAbsolutePath()).map(
83
+ async ([_fileType, pattern]) => {
84
+ return globAsync(pattern) as Promise<AbsolutePath[]>;
85
+ }
86
+ )
87
+ )
88
+ )
89
+ .flat()
90
+ .sort();
91
+
92
+ const fileChecksums = await Promise.all(
93
+ filePaths.map(async (filePath) => {
94
+ return {
95
+ path: filePath,
96
+ checksum: await getChecksumOfFile(filePath),
97
+ };
98
+ })
99
+ );
100
+
101
+ return fileChecksums;
102
+ }
103
+
104
+ async function getPreviousChecksums(): Promise<PathAndChecksum[]> {
105
+ const checksumFilePath = getChecksumFilePath();
106
+ if (!(await exists(checksumFilePath))) {
107
+ return [];
108
+ }
109
+
110
+ const previousChecksums = JSON.parse(
111
+ await readFile(checksumFilePath, "utf-8")
112
+ ).map((r: { path: ApiRelativePath; checksum: string }) => ({
113
+ path: path.join(Sonamu.apiRootPath, r.path), // 체크섬 파일에서 읽을 때: API 상대 경로 → 절대 경로
114
+ checksum: r.checksum,
115
+ })) as PathAndChecksum[];
116
+ return previousChecksums;
117
+ }
118
+
119
+ function getChecksumFilePath(): AbsolutePath {
120
+ return path.join(Sonamu.apiRootPath, "sonamu.lock") as AbsolutePath;
121
+ }
122
+
123
+ async function saveChecksums(checksums: PathAndChecksum[]): Promise<void> {
124
+ const checksumFilePath = getChecksumFilePath();
125
+ await writeFile(
126
+ checksumFilePath,
127
+ JSON.stringify(
128
+ checksums.map((r) => ({
129
+ path: path.relative(Sonamu.apiRootPath, r.path), // 체크섬 파일에 저장할 때: 절대 경로 → API 상대 경로
130
+ checksum: r.checksum,
131
+ })),
132
+ null,
133
+ 2
134
+ ),
135
+ "utf-8"
136
+ );
137
+ console.log("checksum saved", checksumFilePath);
138
+ }
139
+
140
+ async function getChecksumOfFile(filePath: PathLike): Promise<string> {
141
+ return new Promise<string>((resolve, reject) => {
142
+ const hash = crypto.createHash("sha1");
143
+ const input = createReadStream(filePath);
144
+ input.on("error", reject);
145
+ input.on("data", function (chunk: any) {
146
+ hash.update(chunk);
147
+ });
148
+ input.on("close", function () {
149
+ resolve(hash.digest("hex"));
150
+ });
151
+ });
152
+ }
@@ -0,0 +1,202 @@
1
+ import path from "path";
2
+ import { Sonamu } from "../api/sonamu";
3
+ import { AlreadyProcessedException } from "../exceptions/so-exceptions";
4
+ import {
5
+ GenerateOptions,
6
+ PathAndCode,
7
+ TemplateKey,
8
+ TemplateOptions,
9
+ } from "../types/types";
10
+ import { everyAsync, filterAsync } from "../utils/async-utils";
11
+ import { exists } from "../utils/fs-utils";
12
+ import chalk from "chalk";
13
+ import { mkdir, writeFile } from "fs/promises";
14
+ import * as _ from "lodash-es";
15
+ import { Template } from "../template/template";
16
+ import { RenderedTemplate } from "../template/template";
17
+ import { EntityManager } from "../entity/entity-manager";
18
+ import { wrapIf } from "../utils/lodash-able";
19
+ import prettier from "prettier";
20
+ import { AbsolutePath } from "../utils/path-utils";
21
+
22
+ /**
23
+ * 템플릿을 렌더링하고 파일로 생성합니다.
24
+ * overwrite 옵션이 false인 경우, 이미 존재하는 파일은 건너뜁니다.
25
+ * @param key - 템플릿 키 (예: "entity", "model", "service" 등)
26
+ * @param templateOptions - 템플릿 렌더링에 필요한 옵션
27
+ * @param _generateOptions - 생성 옵션 (overwrite 여부)
28
+ * @returns 생성된 파일 경로 배열
29
+ */
30
+ export async function generateTemplate(
31
+ key: TemplateKey,
32
+ templateOptions: any,
33
+ _generateOptions?: GenerateOptions
34
+ ): Promise<AbsolutePath[]> {
35
+ const generateOptions = {
36
+ overwrite: false,
37
+ ..._generateOptions,
38
+ };
39
+
40
+ // 키 children
41
+ const keys: TemplateKey[] = [key];
42
+
43
+ // 템플릿 렌더
44
+ const pathAndCodes = (
45
+ await Promise.all(
46
+ keys.map(async (key) => {
47
+ return await renderTemplate(key, templateOptions);
48
+ })
49
+ )
50
+ ).flat();
51
+
52
+ const filteredPathAndCodes: PathAndCode[] = await (async () => {
53
+ if (generateOptions.overwrite === true) {
54
+ return pathAndCodes;
55
+ } else {
56
+ return await filterAsync(pathAndCodes, async (pathAndCode) => {
57
+ const { targets } = Sonamu.config.sync;
58
+ const filePath = `${Sonamu.appRootPath}/${pathAndCode.path}`;
59
+ const dstFilePaths = targets.map((target) =>
60
+ filePath.replace("/:target/", `/${target}/`)
61
+ );
62
+ return await everyAsync(
63
+ dstFilePaths,
64
+ async (dstPath) => !(await exists(dstPath))
65
+ );
66
+ });
67
+ }
68
+ })();
69
+
70
+ if (filteredPathAndCodes.length === 0) {
71
+ throw new AlreadyProcessedException("이미 경로에 모든 파일이 존재합니다.");
72
+ }
73
+
74
+ return (await Promise.all(
75
+ filteredPathAndCodes.map((pathAndCode) => writeCodeToPathEachTarget(pathAndCode))
76
+ )).flat();
77
+ }
78
+
79
+ /**
80
+ * 템플릿을 렌더링하여 PathAndCode 객체를 반환합니다.
81
+ * 파일로 쓰지 않고 메모리상에서만 렌더링합니다.
82
+ * @param key - 템플릿 키
83
+ * @param options - 템플릿 렌더링 옵션
84
+ * @returns 경로와 코드 쌍의 배열
85
+ */
86
+ export async function renderTemplate<T extends keyof TemplateOptions>(
87
+ key: T,
88
+ options: TemplateOptions[T]
89
+ ): Promise<PathAndCode[]> {
90
+ const template = Template.find(key);
91
+
92
+ const rendered = await template.render(options);
93
+ const resolved = await resolveRenderedTemplate(key, rendered);
94
+
95
+ let preTemplateResolved: PathAndCode[] = [];
96
+ if (rendered.preTemplates) {
97
+ preTemplateResolved = (
98
+ await Promise.all(
99
+ rendered.preTemplates.map(({ key, options }) => {
100
+ return renderTemplate(key, options);
101
+ })
102
+ )
103
+ ).flat();
104
+ }
105
+
106
+ return [resolved, ...preTemplateResolved];
107
+ }
108
+
109
+ async function resolveRenderedTemplate(
110
+ key: TemplateKey,
111
+ result: RenderedTemplate
112
+ ): Promise<PathAndCode> {
113
+ const { target, path: filePath, body, importKeys, customHeaders } = result;
114
+
115
+ // import 할 대상의 대상 path 추출
116
+ const importDefs = importKeys
117
+ .reduce(
118
+ (r, importKey) => {
119
+ const modulePath = EntityManager.getModulePath(importKey);
120
+ let importPath = modulePath;
121
+ if (modulePath.includes("/") || modulePath.includes(".")) {
122
+ importPath = wrapIf(
123
+ path.relative(path.dirname(filePath), modulePath),
124
+ (p) => [p.startsWith(".") === false, "./" + p]
125
+ );
126
+ }
127
+
128
+ // 같은 파일에서 import 하는 경우 keys 로 나열 처리
129
+ const existsOne = r.find((importDef) => importDef.from === importPath);
130
+ if (existsOne) {
131
+ existsOne.keys = _.uniq(existsOne.keys.concat(importKey));
132
+ } else {
133
+ r.push({
134
+ keys: [importKey],
135
+ from: importPath,
136
+ });
137
+ }
138
+ return r;
139
+ },
140
+ [] as {
141
+ keys: string[];
142
+ from: string;
143
+ }[]
144
+ )
145
+ // 셀프 참조 방지
146
+ .filter(
147
+ (importDef) =>
148
+ filePath.endsWith(importDef.from.replace("./", "") + ".ts") === false
149
+ );
150
+
151
+ // 커스텀 헤더 포함하여 헤더 생성
152
+ const header = [
153
+ ...(customHeaders ?? []),
154
+ ...importDefs.map(
155
+ (importDef) =>
156
+ `import { ${importDef.keys.join(", ")} } from '${importDef.from}'`
157
+ ),
158
+ ].join("\n");
159
+
160
+ const formatted = await (async () => {
161
+ if (key === "generated_http") {
162
+ return [header, body].join("\n\n");
163
+ } else {
164
+ return prettier.format([header, body].join("\n\n"), {
165
+ parser: key === "entity" ? "json" : "typescript",
166
+ });
167
+ }
168
+ })();
169
+
170
+ return {
171
+ path: target + "/" + filePath,
172
+ code: formatted,
173
+ };
174
+ }
175
+
176
+ async function writeCodeToPathEachTarget(
177
+ pathAndCode: PathAndCode
178
+ ): Promise<AbsolutePath[]> {
179
+ const { targets } = Sonamu.config.sync;
180
+ const { appRootPath } = Sonamu;
181
+ const filePath = `${Sonamu.appRootPath}/${pathAndCode.path}` as AbsolutePath;
182
+
183
+ const dstFilePaths = _.uniq(
184
+ targets.map((target) =>
185
+ filePath.replace("/:target/", `/${target}/`)
186
+ ) as AbsolutePath[]
187
+ );
188
+ return await Promise.all(
189
+ dstFilePaths.map(async (dstFilePath) => {
190
+ const dir = path.dirname(dstFilePath);
191
+ if (!(await exists(dir))) {
192
+ await mkdir(dir, { recursive: true });
193
+ }
194
+ await writeFile(dstFilePath, pathAndCode.code);
195
+ console.log(
196
+ chalk.bold("Generated: ") +
197
+ chalk.blue(`${dstFilePath.replace(appRootPath + "/", "")}`)
198
+ );
199
+ return dstFilePath;
200
+ })
201
+ );
202
+ }
@@ -0,0 +1,68 @@
1
+ import { rm } from "fs/promises";
2
+ import { exists } from "../utils/fs-utils";
3
+ import chalk from "chalk";
4
+ import { EntityManager } from "../entity/entity-manager";
5
+ import { TemplateOptions } from "../types/types";
6
+ import { BadRequestException } from "../exceptions/so-exceptions";
7
+ import { Sonamu } from "../api/sonamu";
8
+ import { generateTemplate } from "./code-generator";
9
+
10
+ /**
11
+ * 새로운 엔티티를 생성합니다.
12
+ * entityId는 반드시 CamelCase 형식이어야 합니다.
13
+ */
14
+ export async function createEntity(
15
+ form: Omit<TemplateOptions["entity"], "title"> & { title?: string }
16
+ ) {
17
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(form.entityId)) {
18
+ throw new BadRequestException("entityId는 CamelCase 형식이어야 합니다.");
19
+ }
20
+
21
+ await generateTemplate("entity", form);
22
+
23
+ // reload entities
24
+ await EntityManager.reload();
25
+ }
26
+
27
+ /**
28
+ * 엔티티를 삭제합니다.
29
+ * parentId가 있는 서브 엔티티의 경우 entity.json만 삭제하고,
30
+ * 루트 엔티티의 경우 디렉토리 전체와 타겟 디렉토리를 삭제합니다.
31
+ */
32
+ export async function delEntity(
33
+ entityId: string
34
+ ): Promise<{ delPaths: string[] }> {
35
+ const entity = EntityManager.get(entityId);
36
+
37
+ const delPaths = (() => {
38
+ if (entity.parentId) {
39
+ return [
40
+ `${Sonamu.apiRootPath}/src/application/${entity.names.parentFs}/${entity.names.fs}.entity.json`,
41
+ ];
42
+ } else {
43
+ return [
44
+ `${Sonamu.apiRootPath}/src/application/${entity.names.fs}`,
45
+ `${Sonamu.apiRootPath}/dist/application/${entity.names.fs}`,
46
+ ...Sonamu.config.sync.targets
47
+ .map((target) => [
48
+ `${Sonamu.appRootPath}/${target}/src/services/${entity.names.fs}`,
49
+ ])
50
+ .flat(),
51
+ ];
52
+ }
53
+ })(); // iife
54
+
55
+ for await (const delPath of delPaths) {
56
+ if (await exists(delPath)) {
57
+ console.log(chalk.red(`DELETE ${delPath}`));
58
+ await rm(delPath, { recursive: true, force: true });
59
+ } else {
60
+ console.log(chalk.yellow(`NOT_EXISTS ${delPath}`));
61
+ }
62
+ }
63
+
64
+ // reload entities
65
+ await EntityManager.reload();
66
+
67
+ return { delPaths };
68
+ }
@@ -0,0 +1,56 @@
1
+ import path from "path";
2
+ import { Sonamu } from "../api/sonamu";
3
+ import { AbsolutePath, ApiRelativePath } from "../utils/path-utils";
4
+
5
+ export type FileType =
6
+ | "model"
7
+ | "types"
8
+ | "functions"
9
+ | "generated"
10
+ | "entity"
11
+ | "frame"
12
+ | "config";
13
+
14
+ export type GlobPattern<T extends ApiRelativePath | AbsolutePath> = {
15
+ [key in FileType]: T;
16
+ };
17
+
18
+ /**
19
+ * Syncer가 관심 가지고 지켜보는 파일들입니다.
20
+ * 이 파일들에 변경이 생기면 추가적인 작업(이하 "싱크" 또는 "싱크 액션")을 수행합니다.
21
+ * 이 작업이라 함은 파일 복사 또는 템플릿 렌더링을 통한 code generation을 의미합니다.
22
+ *
23
+ * **경로 형식**: API 상대 경로 (src/로 시작)
24
+ * **사용**: getChecksumPatternGroupInAbsolutePath()로 절대 경로 변환 후 glob 사용
25
+ */
26
+ export const checksumPatternGroup: GlobPattern<ApiRelativePath> = {
27
+ entity: "src/application/**/*.entity.json",
28
+ types: "src/application/**/*.types.ts",
29
+ generated: "src/application/sonamu.generated.ts",
30
+ model: "src/application/**/*.model.ts",
31
+ frame: "src/application/**/*.frame.ts",
32
+ functions: "src/application/**/*.functions.ts",
33
+ config: "sonamu.config.ts",
34
+ };
35
+
36
+ /**
37
+ * API 상대 경로 패턴을 절대 경로 패턴으로 변환합니다.
38
+ *
39
+ * **목적**: Glob 패턴을 파일시스템에서 사용할 수 있는 절대 경로로 변환
40
+ *
41
+ * **사용처**: checksum.ts에서 실제 파일을 찾을 때
42
+ *
43
+ * @returns 절대 경로 기반 Glob 패턴 맵
44
+ *
45
+ * @example
46
+ * // 입력: { entity: "src/application/**\/*.entity.json" }
47
+ * // 출력: { entity: "/Users/.../api/src/application/**\/*.entity.json" }
48
+ */
49
+ export function getChecksumPatternGroupInAbsolutePath(): GlobPattern<AbsolutePath> {
50
+ return Object.fromEntries(
51
+ Object.entries(checksumPatternGroup).map(([key, value]) => [
52
+ key,
53
+ path.join(Sonamu.apiRootPath, value), // API 상대 경로 → 절대 경로
54
+ ])
55
+ ) as GlobPattern<AbsolutePath>;
56
+ }
@@ -1 +1,7 @@
1
1
  export * from "./syncer";
2
+ export * from "./code-generator";
3
+ export * from "./module-loader";
4
+ export * from "./entity-operations";
5
+ export * from "./file-patterns";
6
+ export * from "./checksum";
7
+ export * from "./api-parser";
@@ -0,0 +1,125 @@
1
+ import path from "path";
2
+ import { globAsync } from "../utils/async-utils";
3
+ import { importMembers } from "../utils/esm-utils";
4
+ import { z } from "zod";
5
+ import { Sonamu } from "../api/sonamu";
6
+ import { readApisFromFile } from "./api-parser";
7
+ import { BaseFrameClass } from "../api/base-frame";
8
+ import { BaseModelClass } from "../database/base-model";
9
+ import { AbsolutePath, runtimePath } from "../utils/path-utils";
10
+ import { ApiParam, ApiParamType } from "../types/types";
11
+ import { ApiDecoratorOptions } from "../api/decorators";
12
+
13
+ export type LoadedApis = {
14
+ typeParameters: ApiParamType.TypeParam[];
15
+ parameters: ApiParam[];
16
+ returnType: ApiParamType;
17
+ modelName: string;
18
+ methodName: string;
19
+ path: string;
20
+ options: ApiDecoratorOptions;
21
+ }[];
22
+
23
+ export type LoadedTypes = { [typeName: string]: z.ZodObject<any> };
24
+
25
+ export type LoadedModels = {
26
+ [modelName: string]: BaseModelClass | BaseFrameClass;
27
+ };
28
+
29
+ /**
30
+ * *.model.ts와 *.frame.ts 파일들에서 API 메소드를 파싱하여 로드합니다.
31
+ * registeredApis에 API가 등록되어 있어야 하기 때문에, *.model.ts 파일들을 먼저 import해야 합니다.
32
+ * 따라서 loadModels()를 먼저 호출해야 합니다.
33
+ */
34
+ export async function loadApis(): Promise<LoadedApis> {
35
+ // 얘는 특이하게도 환경에 따라 .ts나 .js를 import하는 경우가 아니고,
36
+ // 타입이 살아있는 .ts 소스 코드만을 읽어야 합니다.
37
+ // 이것은 dev서버(hot reload)가 아닌 production 환경에서도 동일합니다.
38
+ // 모델들의 .ts 파일이 있어야 이를 읽어서 라우트를 등록할 수 있어요!
39
+ const modelPathsPattern = path.join(
40
+ Sonamu.apiRootPath,
41
+ "src/application/**/*.{model,frame}.ts" // !! runtimePath 안 씀 주의 !!
42
+ );
43
+ const modelPaths = (await globAsync(modelPathsPattern)) as AbsolutePath[];
44
+
45
+ const apis: LoadedApis = [];
46
+ let count = 0;
47
+ for (const filePath of modelPaths) {
48
+ const parsedApis = await readApisFromFile(filePath);
49
+ apis.push(...parsedApis);
50
+ count++;
51
+ }
52
+ // console.log(
53
+ // chalk.gray(`[Loading] Loaded APIs from "*.model.ts" files: ${count} files.`)
54
+ // );
55
+
56
+ return apis;
57
+ }
58
+
59
+ /**
60
+ * *.model.ts와 *.frame.ts 파일들에서 Model/Frame 클래스 인스턴스를 로드합니다.
61
+ */
62
+ export async function loadModels(): Promise<LoadedModels> {
63
+ const modelPathsPattern = path.join(
64
+ Sonamu.apiRootPath,
65
+ runtimePath("src/application/**/*.{model,frame}.ts")
66
+ );
67
+ const modelPaths = await globAsync(modelPathsPattern);
68
+
69
+ const models: LoadedModels = {};
70
+ let count = 0;
71
+ for (const filePath of modelPaths) {
72
+ const importedMembers = await importMembers<
73
+ BaseModelClass | BaseFrameClass
74
+ >(filePath);
75
+
76
+ for (const { name, value } of importedMembers) {
77
+ if (name.endsWith("Model") || name.endsWith("Frame")) {
78
+ models[name] = value;
79
+ }
80
+ }
81
+ count++;
82
+ }
83
+ // console.log(
84
+ // chalk.gray(
85
+ // `[Loading] Loaded model/frame instances from ${runtimePath("*.{model,frame}.ts")} files: ${count} files.`
86
+ // )
87
+ // );
88
+
89
+ return models;
90
+ }
91
+
92
+ /**
93
+ * *.types.ts와 *.generated.ts 파일들에서 Zod 스키마를 로드합니다.
94
+ */
95
+ export async function loadTypes(): Promise<LoadedTypes> {
96
+ const typePathsPatterns = [
97
+ path.join(Sonamu.apiRootPath, runtimePath("src/application/**/*.types.ts")),
98
+ path.join(
99
+ Sonamu.apiRootPath,
100
+ runtimePath("src/application/**/*.generated.ts")
101
+ ),
102
+ ];
103
+ const typePaths = (
104
+ await Promise.all(typePathsPatterns.map(globAsync))
105
+ ).flat();
106
+
107
+ const types: LoadedTypes = {};
108
+ let count = 0;
109
+ for (const filePath of typePaths) {
110
+ const importedMembers = await importMembers<z.ZodObject<any>>(filePath);
111
+ for (const { name, value } of importedMembers) {
112
+ if (value instanceof z.ZodObject) {
113
+ types[name] = value;
114
+ }
115
+ }
116
+ count++;
117
+ }
118
+ // console.log(
119
+ // chalk.gray(
120
+ // `[Loading] Loaded zod types from ${runtimePath("*.types.ts")} and ${runtimePath("*.generated.ts")} files: ${count} files.`
121
+ // )
122
+ // );
123
+
124
+ return types;
125
+ }