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
@@ -0,0 +1,222 @@
1
+ export type ComparisonOperator = "=" | ">" | ">=" | "<" | "<=" | "<>" | "!=";
2
+ export type Expand<T> = T extends any[]
3
+ ? { [K in keyof T[0]]: T[0][K] }[] // 배열이면 첫 번째 요소를 Expand하고 배열로 감쌈
4
+ : T extends object ? { [K in keyof T]: T[K] } : T;
5
+
6
+ // EmptyRecord가 남아있으면 AvailableColumns 추론이 제대로 되지 않음 (EmptyRecord를 {}로 변경하면 정상 동작함)
7
+ export type MergeJoined<TExisting, TNew> =
8
+ TExisting extends EmptyRecord
9
+ ? TNew // 첫 join: EmptyRecord 제거하고 대체
10
+ : TExisting & TNew; // 이후 join: 누적
11
+
12
+ type DeepEqual<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false;
13
+ type Extends<T, U> = DeepEqual<T, Record<string, never>> extends true ? false : (T extends U ? true : false);
14
+ type NullableToOptional<T> = {
15
+ [K in keyof T as T[K] extends null | undefined ? K : never]?: Exclude<T[K], null | undefined>
16
+ } & Partial<{
17
+ [K in keyof T as T[K] extends null | undefined ? never : K]: T[K]
18
+ }>;
19
+
20
+ // Join 등이 Empty 상태일 떄 {}가 아니라 EmptyRecord를 써서
21
+ export type EmptyRecord = Record<string, never>;
22
+
23
+ // Group By, Order By, Having 등에서 선택 가능한 컬럼
24
+ export type ResultAvailableColumns<
25
+ TSchema,
26
+ T extends keyof TSchema | string,
27
+ TOriginal = any,
28
+ TResult = any,
29
+ TJoined = EmptyRecord,
30
+ > = AvailableColumns<TSchema, T, TOriginal, TJoined> | `${keyof TResult & string}`;
31
+
32
+ // 사용 가능한 컬럼 경로 타입 (메인 테이블 + 조인된 테이블들)
33
+ export type AvailableColumns<
34
+ TSchema,
35
+ T extends keyof TSchema | string,
36
+ TOriginal = any,
37
+ TJoined = EmptyRecord,
38
+ > = T extends keyof TSchema
39
+ ? // 기존 테이블 케이스
40
+ | (Extends<TJoined, Record<string, any>> extends false
41
+ // 이게 TSchema[T]에 존재하면
42
+ ? keyof TSchema[T]
43
+ : {
44
+ [K in keyof TJoined]: TJoined[K] extends Record<string, any>
45
+ ? `${string & K}.${keyof TJoined[K] & string}`
46
+ : never;
47
+ }[keyof TJoined])
48
+ | `${T & string}.${keyof TSchema[T] & string}`
49
+ : // 서브쿼리 케이스 (T는 alias string)
50
+ | keyof TOriginal
51
+ | `${T & string}.${keyof TOriginal & string}`
52
+ | (Extends<TJoined, Record<string, any>> extends true
53
+ ? {
54
+ [K in keyof TJoined]: TJoined[K] extends Record<string, any>
55
+ ? `${string & K}.${keyof TJoined[K] & string}`
56
+ : never;
57
+ }[keyof TJoined]
58
+ : never);
59
+
60
+ // 컬럼 경로에서 타입 추출
61
+ export type ExtractColumnType<
62
+ TSchema,
63
+ T extends keyof TSchema | string,
64
+ Path extends string,
65
+ TOriginal = any,
66
+ TJoined = EmptyRecord,
67
+ > = T extends keyof TSchema
68
+ ? // 기존 테이블 케이스
69
+ Path extends keyof TSchema[T]
70
+ ? TSchema[T][Path] // 메인 테이블 컬럼
71
+ : Path extends `${T & string}.${infer Column}`
72
+ ? Column extends keyof TSchema[T]
73
+ ? TSchema[T][Column]
74
+ : never
75
+ : Path extends `${infer Table}.${infer Column}`
76
+ ? Table extends keyof TJoined
77
+ ? TJoined[Table] extends Record<string, any>
78
+ ? Column extends keyof TJoined[Table]
79
+ ? TJoined[Table][Column]
80
+ : never
81
+ : never
82
+ : never
83
+ : never
84
+ : // 서브쿼리 케이스 (T는 alias)
85
+ Path extends `${T & string}.${infer Column}`
86
+ ? Column extends keyof TOriginal
87
+ ? TOriginal[Column] // 서브쿼리 alias.컬럼
88
+ : never
89
+ : Path extends `${infer Table}.${infer Column}`
90
+ ? Table extends keyof TJoined
91
+ ? TJoined[Table] extends Record<string, any>
92
+ ? Column extends keyof TJoined[Table]
93
+ ? TJoined[Table][Column]
94
+ : never
95
+ : never
96
+ : never
97
+ : Path extends keyof TOriginal
98
+ ? TOriginal[Path] // 서브쿼리 컬럼 직접 접근 (가장 마지막에)
99
+ : never;
100
+
101
+ // SQL 함수 타입 정의
102
+ export type SqlFunction<T extends "string" | "number" | "boolean" | "date"> = {
103
+ _type: "sql_function";
104
+ _return: T;
105
+ _sql: string;
106
+ };
107
+
108
+ // SQL 함수 결과에서 타입 추출
109
+ type ExtractSqlType<T> =
110
+ T extends SqlFunction<infer R>
111
+ ? R extends "string"
112
+ ? string
113
+ : R extends "number"
114
+ ? number
115
+ : R extends "boolean"
116
+ ? boolean
117
+ : R extends "date"
118
+ ? Date
119
+ : never
120
+ : never;
121
+
122
+ // Select 값 타입 확장
123
+ export type SelectValue<
124
+ TSchema,
125
+ T extends keyof TSchema | string,
126
+ TOriginal = any,
127
+ TJoined = EmptyRecord,
128
+ > =
129
+ | AvailableColumns<TSchema, T, TOriginal, TJoined> // 기존 컬럼
130
+ | SqlFunction<"string" | "number" | "boolean" | "date">; // SQL 함수
131
+
132
+ // Select 객체 타입 정의
133
+ export type SelectObject<
134
+ TSchema,
135
+ T extends keyof TSchema | string,
136
+ TOriginal = any,
137
+ TJoined = EmptyRecord,
138
+ > = Record<string, SelectValue<TSchema, T, TOriginal, TJoined>>;
139
+
140
+ // Select 결과 타입 추론
141
+ export type ParseSelectObject<
142
+ TSchema,
143
+ T extends keyof TSchema | string,
144
+ S extends SelectObject<TSchema, T, TOriginal, TJoined>,
145
+ TOriginal = any,
146
+ TJoined = EmptyRecord,
147
+ > = {
148
+ [K in keyof S]: S[K] extends SqlFunction<any>
149
+ ? ExtractSqlType<S[K]> // SQL 함수면 타입 추출
150
+ : ExtractColumnType<TSchema, T, S[K] & string, TOriginal, TJoined>;
151
+ };
152
+
153
+ // Where 조건 타입 (조인된 테이블 컬럼도 포함)
154
+ export type WhereCondition<
155
+ TSchema,
156
+ T extends keyof TSchema | string,
157
+ TOriginal = any,
158
+ TJoined = EmptyRecord,
159
+ > =
160
+ // 메인 테이블/서브쿼리 조건들
161
+ (T extends keyof TSchema
162
+ ? {
163
+ [K in keyof TSchema[T]]?: TSchema[T][K] | TSchema[T][K][];
164
+ }
165
+ : {
166
+ [K in keyof TOriginal]?: TOriginal[K] | TOriginal[K][];
167
+ }) &
168
+ // 조인된 테이블들의 조건들
169
+ (TJoined extends Record<string, any>
170
+ ? {
171
+ [K in keyof TJoined as TJoined[K] extends Record<string, any>
172
+ ? keyof TJoined[K] & string
173
+ : never]?: TJoined[K] extends Record<string, any>
174
+ ?
175
+ | TJoined[K][K extends keyof TJoined[K] ? K : never]
176
+ | TJoined[K][K extends keyof TJoined[K] ? K : never][]
177
+ : never;
178
+ }
179
+ : Record<string, never>);
180
+
181
+ // Fulltext index 컬럼 추출 타입 (메인 테이블 + 조인된 테이블)
182
+ export type FulltextColumns<
183
+ TSchema,
184
+ T extends keyof TSchema | string,
185
+ TOriginal = any,
186
+ TJoined = EmptyRecord,
187
+ > = T extends keyof TSchema
188
+ ? // 기존 테이블 케이스
189
+ | (TSchema[T] extends { __fulltext__: readonly (infer Col)[] }
190
+ ? Col & string
191
+ : never)
192
+ | (TSchema[T] extends { __fulltext__: readonly (infer Col)[] }
193
+ ? `${T & string}.${Col & string}`
194
+ : never)
195
+ | (TJoined extends Record<string, any>
196
+ ? {
197
+ [K in keyof TJoined]: TJoined[K] extends {
198
+ __fulltext__: readonly (infer Col)[];
199
+ }
200
+ ? (Col & string) | `${string & K}.${Col & string}`
201
+ : never;
202
+ }[keyof TJoined]
203
+ : never)
204
+ : // 서브쿼리 케이스 (T는 alias)
205
+ | (TOriginal extends { __fulltext__: readonly (infer Col)[] }
206
+ ? Col & string
207
+ : never)
208
+ | (TOriginal extends { __fulltext__: readonly (infer Col)[] }
209
+ ? `${T & string}.${Col & string}`
210
+ : never)
211
+ | (TJoined extends Record<string, any>
212
+ ? {
213
+ [K in keyof TJoined]: TJoined[K] extends {
214
+ __fulltext__: readonly (infer Col)[];
215
+ }
216
+ ? (Col & string) | `${string & K}.${Col & string}`
217
+ : never;
218
+ }[keyof TJoined]
219
+ : never);
220
+
221
+ // Insert 타입: id, created_at 제외
222
+ export type InsertData<T> = NullableToOptional<Omit<T, "id" | "created_at" | "__fulltext__">>;
@@ -0,0 +1,18 @@
1
+ import type { PuriWrapper } from "./puri-wrapper";
2
+ import type { DBPreset } from "./db";
3
+
4
+ export class TransactionContext {
5
+ private transactions: Map<DBPreset, PuriWrapper> = new Map();
6
+
7
+ getTransaction(preset: DBPreset): PuriWrapper | undefined {
8
+ return this.transactions.get(preset);
9
+ }
10
+
11
+ setTransaction(preset: DBPreset, trx: PuriWrapper): void {
12
+ this.transactions.set(preset, trx);
13
+ }
14
+
15
+ deleteTransaction(preset: DBPreset): void {
16
+ this.transactions.delete(preset);
17
+ }
18
+ }
@@ -1,12 +1,9 @@
1
- import { v4 as uuidv4 } from "uuid";
1
+ import { randomUUID } from "crypto";
2
2
  import _ from "lodash";
3
3
  import { Knex } from "knex";
4
- import { Kysely } from "kysely";
5
4
  import { EntityManager } from "../entity/entity-manager";
6
5
  import { nonNullable } from "../utils/utils";
7
6
  import { RowWithId, batchUpdate } from "./_batch_update";
8
- import { Database, DatabaseDriver, DriverSpec } from "./types";
9
- import { DB } from "./db";
10
7
 
11
8
  type TableData = {
12
9
  references: Set<string>;
@@ -28,7 +25,7 @@ export function isRefField(field: any): field is UBRef {
28
25
  );
29
26
  }
30
27
 
31
- export class UpsertBuilder<D extends DatabaseDriver> {
28
+ export class UpsertBuilder {
32
29
  tables: Map<string, TableData>;
33
30
  constructor() {
34
31
  this.tables = new Map();
@@ -61,7 +58,7 @@ export class UpsertBuilder<D extends DatabaseDriver> {
61
58
  }
62
59
 
63
60
  register<T extends string>(
64
- tableName: DriverSpec[D]["table"],
61
+ tableName: string,
65
62
  row: {
66
63
  [key in T]?:
67
64
  | UBRef
@@ -84,7 +81,7 @@ export class UpsertBuilder<D extends DatabaseDriver> {
84
81
  if (isRefField(val)) {
85
82
  return val.uuid;
86
83
  } else {
87
- return row[unqCol as keyof typeof row] ?? uuidv4(); // nullable인 경우 uuid로 랜덤값 삽입
84
+ return row[unqCol as keyof typeof row] ?? randomUUID(); // nullable인 경우 uuid로 랜덤값 삽입
88
85
  }
89
86
  });
90
87
 
@@ -108,7 +105,7 @@ export class UpsertBuilder<D extends DatabaseDriver> {
108
105
  }
109
106
 
110
107
  // 찾을 수 없는 경우 생성
111
- return uuidv4();
108
+ return randomUUID();
112
109
  })();
113
110
 
114
111
  // 모든 유니크키에 대해 유니크맵에 uuid 저장
@@ -127,7 +124,7 @@ export class UpsertBuilder<D extends DatabaseDriver> {
127
124
  rowValue.use ??= "id";
128
125
  table.references.add(rowValue.of + "." + rowValue.use);
129
126
  r[rowKey] = rowValue;
130
- } else if (typeof rowValue === "object") {
127
+ } else if (typeof rowValue === "object" && !(rowValue instanceof Date)) {
131
128
  // object인 경우 JSON으로 변환
132
129
  r[rowKey] = rowValue === null ? null : JSON.stringify(rowValue);
133
130
  } else {
@@ -148,23 +145,23 @@ export class UpsertBuilder<D extends DatabaseDriver> {
148
145
  }
149
146
 
150
147
  async upsert(
151
- wdb: DriverSpec[D]["core"],
152
- tableName: DriverSpec[D]["table"],
148
+ wdb: Knex,
149
+ tableName: string,
153
150
  chunkSize?: number
154
151
  ): Promise<number[]> {
155
152
  return this.upsertOrInsert(wdb, tableName, "upsert", chunkSize);
156
153
  }
157
154
  async insertOnly(
158
- wdb: DriverSpec[D]["core"],
159
- tableName: DriverSpec[D]["table"],
155
+ wdb: Knex,
156
+ tableName: string,
160
157
  chunkSize?: number
161
158
  ): Promise<number[]> {
162
159
  return this.upsertOrInsert(wdb, tableName, "insert", chunkSize);
163
160
  }
164
161
 
165
162
  async upsertOrInsert(
166
- _wdb: DriverSpec[D]["core"],
167
- tableName: DriverSpec[D]["table"],
163
+ wdb: Knex,
164
+ tableName: string,
168
165
  mode: "upsert" | "insert",
169
166
  chunkSize?: number
170
167
  ): Promise<number[]> {
@@ -189,8 +186,6 @@ export class UpsertBuilder<D extends DatabaseDriver> {
189
186
  throw new Error(`${tableName} 해결되지 않은 참조가 있습니다.`);
190
187
  }
191
188
 
192
- const wdb = DB.toClient(_wdb);
193
-
194
189
  // 전체 테이블 순회하여 현재 테이블 참조하는 모든 테이블 추출
195
190
  const { references, refTables } = Array.from(this.tables).reduce(
196
191
  (r, [, table]) => {
@@ -226,20 +221,18 @@ export class UpsertBuilder<D extends DatabaseDriver> {
226
221
  const uuidMap = new Map<string, any>();
227
222
 
228
223
  for (const chunk of chunks) {
224
+ const q = wdb.insert(chunk).into(tableName);
229
225
  if (mode === "insert") {
230
- await wdb.insert(tableName, chunk);
226
+ await q;
231
227
  } else if (mode === "upsert") {
232
- await wdb.upsert(tableName, chunk);
233
- // await q.onDuplicateUpdate.apply(q, Object.keys(normalRows[0]));
228
+ await q.onDuplicateUpdate.apply(q, Object.keys(normalRows[0]));
234
229
  }
235
230
 
236
231
  // upsert된 row들을 다시 조회하여 uuidMap에 저장
237
232
  const uuids = chunk.map((row) => row.uuid);
238
- const upsertedRows = await wdb
239
- .from(tableName)
233
+ const upsertedRows = await wdb(tableName)
240
234
  .select(_.uniq(["uuid", "id", ...extractFields]))
241
- .where(["uuid", "in", uuids])
242
- .execute();
235
+ .whereIn("uuid", uuids);
243
236
  upsertedRows.forEach((row: any) => {
244
237
  uuidMap.set(row.uuid, row);
245
238
  });
@@ -271,19 +264,24 @@ export class UpsertBuilder<D extends DatabaseDriver> {
271
264
  if (selfRefRows.length > 0) {
272
265
  // 처리된 데이터를 제외하고 다시 upsert
273
266
  table.rows = selfRefRows;
274
- const selfRefIds = await this.upsert(_wdb, tableName, chunkSize);
267
+ const selfRefIds = await this.upsert(wdb, tableName, chunkSize);
275
268
  allIds.push(...selfRefIds);
269
+ } else {
270
+ // 자기 참조가 없으면 해당 테이블의 데이터 초기화
271
+ table.rows = [];
272
+ table.references.clear();
273
+ table.uniquesMap.clear();
276
274
  }
277
275
 
278
276
  return allIds;
279
277
  }
280
278
 
281
279
  async updateBatch(
282
- wdb: Knex | Kysely<Database>,
283
- tableName: DriverSpec[D]["table"],
280
+ wdb: Knex,
281
+ tableName: string,
284
282
  options?: {
285
283
  chunkSize?: number;
286
- where?: DriverSpec[D]["column"] | DriverSpec[D]["column"][];
284
+ where?: string | string[];
287
285
  }
288
286
  ): Promise<void> {
289
287
  options = _.defaults(options, {
@@ -307,12 +305,11 @@ export class UpsertBuilder<D extends DatabaseDriver> {
307
305
  return row as RowWithId<string>;
308
306
  });
309
307
 
310
- await batchUpdate(
311
- DB.toClient(wdb),
312
- tableName,
313
- whereColumns as string[],
314
- rows,
315
- options.chunkSize
316
- );
308
+ await batchUpdate(wdb, tableName, whereColumns, rows, options.chunkSize);
309
+
310
+ // updateBatch 완료 후 처리된 데이터 제거
311
+ table.rows = [];
312
+ table.references.clear();
313
+ table.uniquesMap.clear();
317
314
  }
318
315
  }
@@ -1,12 +1,12 @@
1
1
  import chalk from "chalk";
2
- import glob from "glob";
2
+ import { glob } from "fs/promises";
3
3
  import inflection from "inflection";
4
4
  import _ from "lodash";
5
5
  import path from "path";
6
6
  import { Entity } from "./entity";
7
7
  import { EntityJson } from "../types/types";
8
8
  import { Sonamu } from "../api/sonamu";
9
- import fs from "fs-extra";
9
+ import { readFile } from "fs/promises";
10
10
 
11
11
  export type EntityNamesRecord = Record<
12
12
  | "fs"
@@ -40,18 +40,10 @@ class EntityManagerClass {
40
40
  );
41
41
  !doSilent && console.log(chalk.yellow(`autoload ${pathPattern}`));
42
42
 
43
- return new Promise((resolve) => {
44
- glob.glob(path.resolve(pathPattern!), (_err, files) => {
45
- Promise.all(
46
- files.map(async (file) => {
47
- await this.register(JSON.parse(fs.readFileSync(file).toString()));
48
- })
49
- ).then(() => {
50
- resolve("ok");
51
- this.isAutoloaded = true;
52
- });
53
- });
54
- });
43
+ for await (const file of glob(path.resolve(pathPattern!))) {
44
+ await this.register(JSON.parse((await readFile(file)).toString()));
45
+ }
46
+ this.isAutoloaded = true;
55
47
  }
56
48
 
57
49
  async reload(doSilent: boolean = false) {
@@ -63,7 +55,7 @@ class EntityManagerClass {
63
55
 
64
56
  const sonamuPath = path.join(
65
57
  Sonamu.apiRootPath,
66
- `dist/application/sonamu.generated.js?t=${Date.now()}`
58
+ `dist/application/sonamu.generated.js`
67
59
  );
68
60
  // CJS
69
61
  if (require?.cache && require.cache[sonamuPath]) {
@@ -19,11 +19,12 @@ import {
19
19
  } from "../types/types";
20
20
  import inflection from "inflection";
21
21
  import path from "path";
22
- import fs from "fs-extra";
22
+ import { writeFile } from "fs/promises";
23
23
  import { z } from "zod";
24
24
  import { Sonamu } from "../api/sonamu";
25
25
  import prettier from "prettier";
26
26
  import { nonNullable } from "../utils/utils";
27
+ import { exists } from "../utils/fs-utils";
27
28
 
28
29
  export class Entity {
29
30
  id: string;
@@ -329,37 +330,14 @@ export class Entity {
329
330
  toCol: relation.joinColumn,
330
331
  };
331
332
  } else if (isManyToManyRelationProp(relation)) {
332
- const [table1, table2] = relation.joinTable.split("__");
333
- const throughTables = (() => {
334
- // 동일 테이블 릴레이션인 경우
335
- if (this.table === relEntity.table) {
336
- if (table1 === this.table) {
337
- return {
338
- fromCol: `${inflection.singularize(table1)}_id`,
339
- toCol: `${inflection.singularize(table2)}_id`,
340
- };
341
- } else {
342
- return {
343
- fromCol: `${inflection.singularize(table2)}_id`,
344
- toCol: `${inflection.singularize(table1)}_id`,
345
- };
346
- }
347
- } else {
348
- // 서로 다른 테이블인 경우 릴레이션 테이블 유지
349
- return {
350
- fromCol: `${inflection.singularize(this.table)}_id`,
351
- toCol: `${inflection.singularize(relEntity.table)}_id`,
352
- };
353
- }
354
- })();
355
-
356
333
  manyJoin = {
357
334
  fromTable: this.table,
358
335
  fromCol: "id",
359
336
  idField: prefix === "" ? `id` : `${prefix}__id`,
360
337
  through: {
361
338
  table: relation.joinTable,
362
- ...throughTables,
339
+ fromCol: `${inflection.singularize(this.table)}_id`,
340
+ toCol: `${inflection.singularize(relEntity.table)}_id`,
363
341
  },
364
342
  toTable: relEntity.table,
365
343
  toCol: "id",
@@ -523,7 +501,7 @@ export class Entity {
523
501
  .filter((f) => f !== null) as string[];
524
502
  }
525
503
 
526
- getTableColumns(): string[] {
504
+ getTableColumns(): { name: string; type: string }[] {
527
505
  return this.props
528
506
  .map((prop) => {
529
507
  if (prop.type === "relation") {
@@ -531,12 +509,12 @@ export class Entity {
531
509
  prop.relationType === "BelongsToOne" ||
532
510
  (prop.relationType === "OneToOne" && prop.hasJoinColumn === true)
533
511
  ) {
534
- return `${prop.name}_id`;
512
+ return { name: `${prop.name}_id`, type: "int_unsigned" };
535
513
  } else {
536
514
  return null;
537
515
  }
538
516
  }
539
- return prop.name;
517
+ return { name: prop.name, type: prop.type };
540
518
  })
541
519
  .filter(nonNullable);
542
520
  }
@@ -574,7 +552,7 @@ export class Entity {
574
552
  `dist/application/${typesModulePath}.js`
575
553
  );
576
554
 
577
- if (fs.existsSync(typesFileDistPath)) {
555
+ if (await exists(typesFileDistPath)) {
578
556
  const importPath = path.relative(__dirname, typesFileDistPath);
579
557
  const t = await import(importPath);
580
558
  this.types = Object.keys(t).reduce((result, key) => {
@@ -627,7 +605,7 @@ export class Entity {
627
605
  `src/application/${this.names.parentFs}/${this.names.fs}.entity.json`
628
606
  );
629
607
  const json = this.toJson();
630
- fs.writeFileSync(
608
+ await writeFile(
631
609
  jsonPath,
632
610
  await prettier.format(JSON.stringify(json), {
633
611
  parser: "json",
@@ -0,0 +1 @@
1
+ [여기](../migration/migrator.ts)로 이사갔어요.
@@ -0,0 +1,121 @@
1
+ import {
2
+ DeleteObjectCommand,
3
+ GetObjectCommand,
4
+ PutObjectCommand,
5
+ S3Client,
6
+ S3ClientConfig,
7
+ } from "@aws-sdk/client-s3";
8
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
+ import fs from "fs/promises";
10
+ import path from "path";
11
+
12
+ /**
13
+ * 파일 저장소의 공통 인터페이스
14
+ */
15
+ export interface Driver {
16
+ put(
17
+ key: string,
18
+ contents: Buffer,
19
+ options?: { contentType?: string; visibility?: "public" | "private" }
20
+ ): Promise<void>;
21
+
22
+ del(key: string): Promise<void>;
23
+
24
+ getUrl(key: string): string;
25
+
26
+ getSignedUrl(key: string, expiresIn?: number): Promise<string>;
27
+ }
28
+
29
+ export type FSDriverConfig = {
30
+ location: string;
31
+ urlPrefix: string;
32
+ };
33
+
34
+ /**
35
+ * 로컬 파일시스템
36
+ */
37
+ export class FSDriver implements Driver {
38
+ constructor(private config: FSDriverConfig) {}
39
+
40
+ async put(key: string, contents: Buffer): Promise<void> {
41
+ const filePath = path.join(this.config.location, key);
42
+ const dir = path.dirname(filePath);
43
+
44
+ await fs.mkdir(dir, { recursive: true });
45
+
46
+ await fs.writeFile(filePath, contents);
47
+ }
48
+
49
+ async del(key: string) {
50
+ await fs.rm(path.join(this.config.location, key));
51
+ }
52
+
53
+ getUrl(key: string): string {
54
+ return `${this.config.urlPrefix}/${key}`;
55
+ }
56
+
57
+ // 로컬 파일시스템은 signed URL을 지원하지 않으므로 일반 URL 반환
58
+ async getSignedUrl(key: string, _expiresIn?: number): Promise<string> {
59
+ return this.getUrl(key);
60
+ }
61
+ }
62
+
63
+ export type S3DriverConfig = S3ClientConfig & {
64
+ bucket: string;
65
+ };
66
+
67
+ export class S3Driver implements Driver {
68
+ s3: S3Client;
69
+
70
+ constructor(private config: S3DriverConfig) {
71
+ this.s3 = new S3Client(config);
72
+ }
73
+
74
+ async put(
75
+ key: string,
76
+ contents: Buffer,
77
+ options?: { contentType?: string; visibility?: "public" | "private" }
78
+ ): Promise<void> {
79
+ await this.s3.send(
80
+ new PutObjectCommand({
81
+ Bucket: this.config.bucket,
82
+ Key: key,
83
+ Body: contents,
84
+ ContentType: options?.contentType,
85
+ ACL: this.getAcl(options?.visibility),
86
+ })
87
+ );
88
+ }
89
+
90
+ async del(key: string): Promise<void> {
91
+ await this.s3.send(
92
+ new DeleteObjectCommand({
93
+ Bucket: this.config.bucket,
94
+ Key: key,
95
+ })
96
+ );
97
+ }
98
+
99
+ getUrl(key: string): string {
100
+ return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${key}`;
101
+ }
102
+
103
+ async getSignedUrl(key: string, expiresIn?: number): Promise<string> {
104
+ const command = new GetObjectCommand({
105
+ Bucket: this.config.bucket,
106
+ Key: key,
107
+ });
108
+
109
+ return getSignedUrl(this.s3, command, {
110
+ expiresIn: expiresIn ?? 60 * 60 * 24 * 7,
111
+ });
112
+ }
113
+
114
+ private getAcl(visibility?: "public" | "private") {
115
+ if (visibility === "public") {
116
+ return "public-read";
117
+ }
118
+
119
+ return visibility;
120
+ }
121
+ }