sonamu 0.6.0 → 0.7.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 (406) hide show
  1. package/.swcrc.project-default +18 -0
  2. package/bin/cli.js +24 -0
  3. package/dist/ai/agents/agent.d.ts +11 -0
  4. package/dist/ai/agents/agent.d.ts.map +1 -0
  5. package/dist/ai/agents/agent.js +65 -0
  6. package/dist/ai/agents/index.d.ts +3 -0
  7. package/dist/ai/agents/index.d.ts.map +1 -0
  8. package/dist/ai/agents/index.js +4 -0
  9. package/dist/ai/agents/types.d.ts +43 -0
  10. package/dist/ai/agents/types.d.ts.map +1 -0
  11. package/dist/ai/agents/types.js +3 -0
  12. package/dist/ai/index.d.ts +2 -0
  13. package/dist/ai/index.d.ts.map +1 -0
  14. package/dist/ai/index.js +3 -0
  15. package/dist/ai/providers/rtzr/api.d.ts +22 -0
  16. package/dist/ai/providers/rtzr/api.d.ts.map +1 -0
  17. package/dist/ai/providers/rtzr/api.js +28 -0
  18. package/dist/ai/providers/rtzr/error.d.ts +18 -0
  19. package/dist/ai/providers/rtzr/error.d.ts.map +1 -0
  20. package/dist/ai/providers/rtzr/error.js +29 -0
  21. package/dist/ai/providers/rtzr/index.d.ts +5 -0
  22. package/dist/ai/providers/rtzr/index.d.ts.map +1 -0
  23. package/dist/ai/providers/rtzr/index.js +6 -0
  24. package/dist/ai/providers/rtzr/model.d.ts +52 -0
  25. package/dist/ai/providers/rtzr/model.d.ts.map +1 -0
  26. package/dist/ai/providers/rtzr/model.js +137 -0
  27. package/dist/ai/providers/rtzr/options.d.ts +7 -0
  28. package/dist/ai/providers/rtzr/options.d.ts.map +1 -0
  29. package/dist/ai/providers/rtzr/options.js +47 -0
  30. package/dist/ai/providers/rtzr/provider.d.ts +18 -0
  31. package/dist/ai/providers/rtzr/provider.d.ts.map +1 -0
  32. package/dist/ai/providers/rtzr/provider.js +54 -0
  33. package/dist/ai/providers/rtzr/utils.d.ts +19 -0
  34. package/dist/ai/providers/rtzr/utils.d.ts.map +1 -0
  35. package/dist/ai/providers/rtzr/utils.js +88 -0
  36. package/dist/api/base-frame.d.ts +2 -2
  37. package/dist/api/base-frame.d.ts.map +1 -1
  38. package/dist/api/base-frame.js +2 -1
  39. package/dist/api/caster.d.ts.map +1 -1
  40. package/dist/api/caster.js +6 -1
  41. package/dist/api/code-converters.d.ts +58 -14
  42. package/dist/api/code-converters.d.ts.map +1 -1
  43. package/dist/api/code-converters.js +178 -409
  44. package/dist/api/config.d.ts +27 -13
  45. package/dist/api/config.d.ts.map +1 -1
  46. package/dist/api/config.js +19 -26
  47. package/dist/api/context.d.ts +4 -3
  48. package/dist/api/context.d.ts.map +1 -1
  49. package/dist/api/context.js +1 -1
  50. package/dist/api/decorators.d.ts +20 -6
  51. package/dist/api/decorators.d.ts.map +1 -1
  52. package/dist/api/decorators.js +111 -18
  53. package/dist/api/index.d.ts +2 -2
  54. package/dist/api/index.d.ts.map +1 -1
  55. package/dist/api/index.js +3 -3
  56. package/dist/api/sonamu.d.ts +7 -7
  57. package/dist/api/sonamu.d.ts.map +1 -1
  58. package/dist/api/sonamu.js +83 -51
  59. package/dist/api/validator.d.ts +6 -0
  60. package/dist/api/validator.d.ts.map +1 -0
  61. package/dist/api/validator.js +81 -0
  62. package/dist/bin/build-config.d.ts +5 -1
  63. package/dist/bin/build-config.d.ts.map +1 -1
  64. package/dist/bin/build-config.js +5 -2
  65. package/dist/bin/cli.js +165 -64
  66. package/dist/bin/loader-register.d.ts +2 -0
  67. package/dist/bin/loader-register.d.ts.map +1 -0
  68. package/dist/bin/loader-register.js +34 -0
  69. package/dist/database/_batch_update.d.ts +5 -3
  70. package/dist/database/_batch_update.d.ts.map +1 -1
  71. package/dist/database/_batch_update.js +30 -13
  72. package/dist/database/base-model.d.ts +96 -10
  73. package/dist/database/base-model.d.ts.map +1 -1
  74. package/dist/database/base-model.js +232 -89
  75. package/dist/database/base-model.types.d.ts +93 -0
  76. package/dist/database/base-model.types.d.ts.map +1 -0
  77. package/dist/database/base-model.types.js +10 -0
  78. package/dist/database/code-generator.d.ts +1 -1
  79. package/dist/database/code-generator.d.ts.map +1 -1
  80. package/dist/database/code-generator.js +11 -10
  81. package/dist/database/db.d.ts +5 -6
  82. package/dist/database/db.d.ts.map +1 -1
  83. package/dist/database/db.js +22 -25
  84. package/dist/database/puri-subset.test-d.js +81 -0
  85. package/dist/database/puri-subset.types.d.ts +123 -0
  86. package/dist/database/puri-subset.types.d.ts.map +1 -0
  87. package/dist/database/puri-subset.types.js +16 -0
  88. package/dist/database/puri-wrapper.d.ts +13 -11
  89. package/dist/database/puri-wrapper.d.ts.map +1 -1
  90. package/dist/database/puri-wrapper.js +2 -2
  91. package/dist/database/puri.d.ts +25 -14
  92. package/dist/database/puri.d.ts.map +1 -1
  93. package/dist/database/puri.js +83 -21
  94. package/dist/database/puri.types.d.ts +21 -7
  95. package/dist/database/puri.types.d.ts.map +1 -1
  96. package/dist/database/puri.types.js +4 -1
  97. package/dist/database/transaction-context.d.ts +1 -1
  98. package/dist/database/transaction-context.d.ts.map +1 -1
  99. package/dist/database/transaction-context.js +1 -1
  100. package/dist/database/upsert-builder.d.ts +9 -3
  101. package/dist/database/upsert-builder.d.ts.map +1 -1
  102. package/dist/database/upsert-builder.js +228 -78
  103. package/dist/entity/entity-manager.d.ts +165 -2
  104. package/dist/entity/entity-manager.d.ts.map +1 -1
  105. package/dist/entity/entity-manager.js +26 -10
  106. package/dist/entity/entity.d.ts +5 -3
  107. package/dist/entity/entity.d.ts.map +1 -1
  108. package/dist/entity/entity.js +153 -54
  109. package/dist/exceptions/error-handler.d.ts +1 -1
  110. package/dist/exceptions/error-handler.d.ts.map +1 -1
  111. package/dist/exceptions/error-handler.js +1 -1
  112. package/dist/exceptions/so-exceptions.d.ts +1 -1
  113. package/dist/exceptions/so-exceptions.d.ts.map +1 -1
  114. package/dist/exceptions/so-exceptions.js +1 -1
  115. package/dist/file-storage/driver.d.ts +1 -1
  116. package/dist/file-storage/driver.d.ts.map +1 -1
  117. package/dist/file-storage/driver.js +1 -1
  118. package/dist/file-storage/file-storage.js +2 -2
  119. package/dist/index.d.ts +18 -11
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +19 -13
  122. package/dist/migration/code-generation.d.ts +1 -1
  123. package/dist/migration/code-generation.d.ts.map +1 -1
  124. package/dist/migration/code-generation.js +123 -67
  125. package/dist/migration/migration-set.d.ts +2 -10
  126. package/dist/migration/migration-set.d.ts.map +1 -1
  127. package/dist/migration/migration-set.js +67 -218
  128. package/dist/migration/migrator.d.ts +24 -73
  129. package/dist/migration/migrator.d.ts.map +1 -1
  130. package/dist/migration/migrator.js +121 -301
  131. package/dist/migration/postgresql-schema-reader.d.ts +51 -0
  132. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -0
  133. package/dist/migration/postgresql-schema-reader.js +245 -0
  134. package/dist/migration/types.d.ts +6 -38
  135. package/dist/migration/types.d.ts.map +1 -1
  136. package/dist/migration/types.js +1 -1
  137. package/dist/naite/messaging-types.d.ts +43 -0
  138. package/dist/naite/messaging-types.d.ts.map +1 -0
  139. package/dist/naite/messaging-types.js +7 -0
  140. package/dist/naite/naite-reporter.d.ts +41 -0
  141. package/dist/naite/naite-reporter.d.ts.map +1 -0
  142. package/dist/naite/naite-reporter.js +102 -0
  143. package/dist/naite/naite.d.ts +91 -8
  144. package/dist/naite/naite.d.ts.map +1 -1
  145. package/dist/naite/naite.js +285 -41
  146. package/dist/stream/sse.d.ts +2 -2
  147. package/dist/stream/sse.d.ts.map +1 -1
  148. package/dist/stream/sse.js +1 -1
  149. package/dist/syncer/api-parser.d.ts +3 -13
  150. package/dist/syncer/api-parser.d.ts.map +1 -1
  151. package/dist/syncer/api-parser.js +67 -56
  152. package/dist/syncer/checksum.d.ts +2 -2
  153. package/dist/syncer/checksum.d.ts.map +1 -1
  154. package/dist/syncer/checksum.js +11 -11
  155. package/dist/syncer/code-generator.d.ts +3 -3
  156. package/dist/syncer/code-generator.d.ts.map +1 -1
  157. package/dist/syncer/code-generator.js +37 -17
  158. package/dist/syncer/entity-operations.d.ts +2 -2
  159. package/dist/syncer/entity-operations.d.ts.map +1 -1
  160. package/dist/syncer/entity-operations.js +9 -8
  161. package/dist/syncer/file-patterns.d.ts +1 -1
  162. package/dist/syncer/file-patterns.d.ts.map +1 -1
  163. package/dist/syncer/file-patterns.js +1 -1
  164. package/dist/syncer/index.d.ts +4 -4
  165. package/dist/syncer/index.d.ts.map +1 -1
  166. package/dist/syncer/index.js +5 -5
  167. package/dist/syncer/module-loader.d.ts +4 -4
  168. package/dist/syncer/module-loader.d.ts.map +1 -1
  169. package/dist/syncer/module-loader.js +17 -12
  170. package/dist/syncer/syncer.d.ts +31 -24
  171. package/dist/syncer/syncer.d.ts.map +1 -1
  172. package/dist/syncer/syncer.js +92 -45
  173. package/dist/template/entity-converter.d.ts +1 -1
  174. package/dist/template/entity-converter.d.ts.map +1 -1
  175. package/dist/template/entity-converter.js +15 -8
  176. package/dist/template/helpers.d.ts +2 -2
  177. package/dist/template/helpers.d.ts.map +1 -1
  178. package/dist/template/helpers.js +3 -3
  179. package/dist/template/implementations/entity.template.d.ts +2 -2
  180. package/dist/template/implementations/entity.template.d.ts.map +1 -1
  181. package/dist/template/implementations/entity.template.js +4 -5
  182. package/dist/template/implementations/generated.template.d.ts +2 -3
  183. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  184. package/dist/template/implementations/generated.template.js +46 -29
  185. package/dist/template/implementations/generated_http.template.d.ts +2 -3
  186. package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
  187. package/dist/template/implementations/generated_http.template.js +9 -9
  188. package/dist/template/implementations/generated_sso.template.d.ts +3 -4
  189. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  190. package/dist/template/implementations/generated_sso.template.js +54 -25
  191. package/dist/template/implementations/init_types.template.d.ts +2 -2
  192. package/dist/template/implementations/init_types.template.d.ts.map +1 -1
  193. package/dist/template/implementations/init_types.template.js +2 -2
  194. package/dist/template/implementations/model.template.d.ts +2 -2
  195. package/dist/template/implementations/model.template.d.ts.map +1 -1
  196. package/dist/template/implementations/model.template.js +47 -37
  197. package/dist/template/implementations/model_test.template.d.ts +2 -2
  198. package/dist/template/implementations/model_test.template.d.ts.map +1 -1
  199. package/dist/template/implementations/model_test.template.js +2 -2
  200. package/dist/template/implementations/service.template.d.ts +4 -4
  201. package/dist/template/implementations/service.template.d.ts.map +1 -1
  202. package/dist/template/implementations/service.template.js +24 -16
  203. package/dist/template/implementations/view_enums_buttonset.template.d.ts +2 -2
  204. package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +1 -1
  205. package/dist/template/implementations/view_enums_buttonset.template.js +1 -1
  206. package/dist/template/implementations/view_enums_dropdown.template.d.ts +2 -2
  207. package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +1 -1
  208. package/dist/template/implementations/view_enums_dropdown.template.js +2 -2
  209. package/dist/template/implementations/view_enums_select.template.d.ts +2 -2
  210. package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -1
  211. package/dist/template/implementations/view_enums_select.template.js +2 -2
  212. package/dist/template/implementations/view_form.template.d.ts +2 -2
  213. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  214. package/dist/template/implementations/view_form.template.js +4 -4
  215. package/dist/template/implementations/view_id_all_select.template.d.ts +2 -2
  216. package/dist/template/implementations/view_id_all_select.template.d.ts.map +1 -1
  217. package/dist/template/implementations/view_id_all_select.template.js +1 -1
  218. package/dist/template/implementations/view_id_async_select.template.d.ts +2 -2
  219. package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -1
  220. package/dist/template/implementations/view_id_async_select.template.js +1 -1
  221. package/dist/template/implementations/view_list.template.d.ts +2 -2
  222. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  223. package/dist/template/implementations/view_list.template.js +29 -19
  224. package/dist/template/implementations/view_list_columns.template.d.ts +3 -3
  225. package/dist/template/implementations/view_list_columns.template.d.ts.map +1 -1
  226. package/dist/template/implementations/view_list_columns.template.js +1 -1
  227. package/dist/template/implementations/view_search_input.template.d.ts +2 -2
  228. package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
  229. package/dist/template/implementations/view_search_input.template.js +1 -1
  230. package/dist/template/index.d.ts +4 -2
  231. package/dist/template/index.d.ts.map +1 -1
  232. package/dist/template/index.js +5 -3
  233. package/dist/template/template-manager.d.ts +56 -0
  234. package/dist/template/template-manager.d.ts.map +1 -0
  235. package/dist/template/template-manager.js +125 -0
  236. package/dist/template/template-types.d.ts +16 -0
  237. package/dist/template/template-types.d.ts.map +1 -0
  238. package/dist/template/template-types.js +7 -0
  239. package/dist/template/template.d.ts +12 -2
  240. package/dist/template/template.d.ts.map +1 -1
  241. package/dist/template/template.js +19 -6
  242. package/dist/template/zod-converter.d.ts +40 -7
  243. package/dist/template/zod-converter.d.ts.map +1 -1
  244. package/dist/template/zod-converter.js +341 -58
  245. package/dist/testing/_relation-graph.d.ts +1 -1
  246. package/dist/testing/_relation-graph.d.ts.map +1 -1
  247. package/dist/testing/_relation-graph.js +12 -3
  248. package/dist/testing/fixture-manager.d.ts +42 -11
  249. package/dist/testing/fixture-manager.d.ts.map +1 -1
  250. package/dist/testing/fixture-manager.js +338 -236
  251. package/dist/types/types.d.ts +709 -104
  252. package/dist/types/types.d.ts.map +1 -1
  253. package/dist/types/types.js +309 -52
  254. package/dist/typings/knex.d.js +2 -2
  255. package/dist/utils/async-utils.d.ts.map +1 -1
  256. package/dist/utils/async-utils.js +3 -3
  257. package/dist/utils/console-util.js +1 -1
  258. package/dist/utils/controller.d.ts +1 -0
  259. package/dist/utils/controller.d.ts.map +1 -1
  260. package/dist/utils/controller.js +4 -1
  261. package/dist/utils/esm-utils.d.ts +0 -6
  262. package/dist/utils/esm-utils.d.ts.map +1 -1
  263. package/dist/utils/esm-utils.js +2 -9
  264. package/dist/utils/formatter.d.ts +3 -0
  265. package/dist/utils/formatter.d.ts.map +1 -0
  266. package/dist/utils/formatter.js +110 -0
  267. package/dist/utils/fs-utils.d.ts +1 -1
  268. package/dist/utils/fs-utils.d.ts.map +1 -1
  269. package/dist/utils/fs-utils.js +1 -1
  270. package/dist/utils/lodash-able.d.ts.map +1 -1
  271. package/dist/utils/lodash-able.js +1 -1
  272. package/dist/utils/object-utils.d.ts +44 -0
  273. package/dist/utils/object-utils.d.ts.map +1 -0
  274. package/dist/utils/object-utils.js +191 -0
  275. package/dist/utils/path-utils.d.ts +1 -1
  276. package/dist/utils/path-utils.d.ts.map +1 -1
  277. package/dist/utils/path-utils.js +3 -3
  278. package/dist/utils/process-utils.js +1 -1
  279. package/dist/utils/sql-parser.d.ts +5 -1
  280. package/dist/utils/sql-parser.d.ts.map +1 -1
  281. package/dist/utils/sql-parser.js +14 -3
  282. package/dist/utils/type-utils.d.ts +23 -0
  283. package/dist/utils/type-utils.d.ts.map +1 -0
  284. package/dist/utils/type-utils.js +45 -0
  285. package/dist/utils/utils.d.ts +7 -1
  286. package/dist/utils/utils.d.ts.map +1 -1
  287. package/dist/utils/utils.js +44 -5
  288. package/dist/utils/zod-error.d.ts +1 -1
  289. package/dist/utils/zod-error.d.ts.map +1 -1
  290. package/dist/utils/zod-error.js +1 -1
  291. package/package.json +54 -29
  292. package/src/ai/agents/agent.ts +87 -0
  293. package/src/ai/agents/index.ts +2 -0
  294. package/src/ai/agents/types.ts +47 -0
  295. package/src/ai/index.ts +1 -0
  296. package/src/ai/providers/rtzr/api.ts +37 -0
  297. package/src/ai/providers/rtzr/error.ts +34 -0
  298. package/src/ai/providers/rtzr/index.ts +4 -0
  299. package/src/ai/providers/rtzr/model.ts +201 -0
  300. package/src/ai/providers/rtzr/options.ts +49 -0
  301. package/src/ai/providers/rtzr/provider.ts +91 -0
  302. package/src/ai/providers/rtzr/utils.ts +127 -0
  303. package/src/api/base-frame.ts +4 -2
  304. package/src/api/caster.ts +17 -23
  305. package/src/api/code-converters.ts +176 -533
  306. package/src/api/config.ts +39 -56
  307. package/src/api/context.ts +7 -18
  308. package/src/api/decorators.ts +175 -46
  309. package/src/api/index.ts +2 -2
  310. package/src/api/sonamu.ts +133 -124
  311. package/src/api/validator.ts +83 -0
  312. package/src/bin/build-config.ts +7 -1
  313. package/src/bin/cli.ts +192 -110
  314. package/src/bin/loader-register.ts +38 -0
  315. package/src/database/_batch_update.ts +46 -31
  316. package/src/database/base-model.ts +390 -182
  317. package/src/database/base-model.types.ts +155 -0
  318. package/src/database/code-generator.ts +13 -32
  319. package/src/database/db.ts +36 -50
  320. package/src/database/puri-subset.test-d.ts +471 -0
  321. package/src/database/puri-subset.types.ts +195 -0
  322. package/src/database/puri-wrapper.ts +58 -67
  323. package/src/database/puri.ts +182 -126
  324. package/src/database/puri.types.ts +64 -31
  325. package/src/database/transaction-context.ts +1 -1
  326. package/src/database/upsert-builder.ts +262 -132
  327. package/src/entity/entity-manager.ts +36 -28
  328. package/src/entity/entity.ts +330 -249
  329. package/src/exceptions/error-handler.ts +3 -3
  330. package/src/exceptions/so-exceptions.ts +11 -11
  331. package/src/file-storage/driver.ts +5 -5
  332. package/src/file-storage/file-storage.ts +2 -2
  333. package/src/index.ts +18 -12
  334. package/src/migration/code-generation.ts +185 -172
  335. package/src/migration/migration-set.ts +80 -293
  336. package/src/migration/migrator.ts +182 -425
  337. package/src/migration/mysql-schema-reader.ts.txt +272 -0
  338. package/src/migration/postgresql-schema-reader.ts +310 -0
  339. package/src/migration/types.ts +6 -39
  340. package/src/naite/messaging-types.ts +51 -0
  341. package/src/naite/naite-reporter.ts +128 -0
  342. package/src/naite/naite.ts +378 -33
  343. package/src/shared/web.shared.ts.txt +20 -24
  344. package/src/stream/sse.ts +5 -5
  345. package/src/syncer/api-parser.ts +52 -69
  346. package/src/syncer/checksum.ts +25 -37
  347. package/src/syncer/code-generator.ts +58 -62
  348. package/src/syncer/entity-operations.ts +12 -15
  349. package/src/syncer/file-patterns.ts +2 -2
  350. package/src/syncer/index.ts +4 -4
  351. package/src/syncer/module-loader.ts +28 -25
  352. package/src/syncer/syncer.ts +155 -162
  353. package/src/template/entity-converter.ts +18 -27
  354. package/src/template/helpers.ts +8 -11
  355. package/src/template/implementations/entity.template.ts +6 -6
  356. package/src/template/implementations/generated.template.ts +99 -99
  357. package/src/template/implementations/generated_http.template.ts +21 -54
  358. package/src/template/implementations/generated_sso.template.ts +78 -65
  359. package/src/template/implementations/init_types.template.ts +4 -6
  360. package/src/template/implementations/model.template.ts +47 -38
  361. package/src/template/implementations/model_test.template.ts +3 -3
  362. package/src/template/implementations/service.template.ts +56 -80
  363. package/src/template/implementations/view_enums_buttonset.template.ts +2 -2
  364. package/src/template/implementations/view_enums_dropdown.template.ts +4 -4
  365. package/src/template/implementations/view_enums_select.template.ts +3 -3
  366. package/src/template/implementations/view_form.template.ts +34 -75
  367. package/src/template/implementations/view_id_all_select.template.ts +2 -2
  368. package/src/template/implementations/view_id_async_select.template.ts +9 -23
  369. package/src/template/implementations/view_list.template.ts +54 -95
  370. package/src/template/implementations/view_list_columns.template.ts +4 -10
  371. package/src/template/implementations/view_search_input.template.ts +2 -2
  372. package/src/template/index.ts +4 -2
  373. package/src/template/template-manager.ts +166 -0
  374. package/src/template/template-types.ts +16 -0
  375. package/src/template/template.ts +29 -10
  376. package/src/template/zod-converter.ts +407 -101
  377. package/src/testing/_relation-graph.ts +18 -11
  378. package/src/testing/fixture-manager.ts +468 -362
  379. package/src/types/types.ts +516 -248
  380. package/src/typings/knex.d.ts +7 -9
  381. package/src/utils/async-utils.ts +8 -12
  382. package/src/utils/console-util.ts +1 -1
  383. package/src/utils/controller.ts +3 -0
  384. package/src/utils/esm-utils.ts +8 -18
  385. package/src/utils/formatter.ts +109 -0
  386. package/src/utils/fs-utils.ts +1 -1
  387. package/src/utils/lodash-able.ts +1 -4
  388. package/src/utils/object-utils.ts +217 -0
  389. package/src/utils/path-utils.ts +3 -6
  390. package/src/utils/process-utils.ts +1 -1
  391. package/src/utils/sql-parser.ts +23 -5
  392. package/src/utils/type-utils.ts +83 -0
  393. package/src/utils/utils.ts +58 -9
  394. package/src/utils/zod-error.ts +3 -3
  395. package/dist/bin/cli-wrapper.d.ts +0 -3
  396. package/dist/bin/cli-wrapper.d.ts.map +0 -1
  397. package/dist/bin/cli-wrapper.js +0 -72
  398. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +0 -2
  399. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +0 -1
  400. package/dist/database/knex-plugins/knex-on-duplicate-update.js +0 -39
  401. package/dist/entity/entity-utils.d.ts +0 -61
  402. package/dist/entity/entity-utils.d.ts.map +0 -1
  403. package/dist/entity/entity-utils.js +0 -210
  404. package/src/bin/cli-wrapper.ts +0 -82
  405. package/src/database/knex-plugins/knex-on-duplicate-update.ts +0 -45
  406. package/src/entity/entity-utils.ts +0 -291
@@ -1,20 +1,48 @@
1
- import { DateTime } from "luxon";
2
- import { Knex } from "knex";
3
- import { chunk, groupBy, isObject, omit, set, uniq } from "lodash-es";
4
- import { DBPreset, DB } from "./db";
5
- import { isCustomJoinClause, type SubsetQuery } from "../types/types";
6
- import type { BaseListParams } from "../utils/model";
1
+ import assert from "assert";
7
2
  import inflection from "inflection";
8
- import chalk from "chalk";
9
- import { UpsertBuilder } from "./upsert-builder";
10
- import SqlParser from "node-sql-parser";
11
- import { getTableName, getTableNamesFromWhere } from "../utils/sql-parser";
3
+ import type { Knex } from "knex";
4
+ import { group, isObject, omit, set, unique } from "radashi";
5
+ import { Sonamu } from "../api";
6
+ import { type DatabaseSchemaExtend, isCustomJoinClause, type SubsetQuery } from "../types/types";
7
+ import type { BaseListParams } from "../utils/model";
8
+ import { getJoinTables, getTableNamesFromWhere } from "../utils/sql-parser";
9
+ import { chunk } from "../utils/utils";
10
+ import type {
11
+ EnhancerMap,
12
+ ExecuteSubsetQueryResult,
13
+ ResolveSubsetIntersection,
14
+ UnionExtractedTTables,
15
+ } from "./base-model.types";
16
+ import type { DBPreset } from "./db";
17
+ import { DB } from "./db";
18
+ import { Puri } from "./puri";
19
+ import type { InferAllSubsets, PuriLoaderQueries, PuriSubsetFn } from "./puri-subset.types";
12
20
  import { PuriWrapper } from "./puri-wrapper";
21
+ import { UpsertBuilder } from "./upsert-builder";
13
22
 
14
- export class BaseModelClass {
23
+ type UnknownDBRecord = Record<string, unknown>;
24
+
25
+ /**
26
+ * 모든 Model 클래스의 기본 클래스
27
+ *
28
+ * @template TSubsetKey - 서브셋 키 유니온 (예: "A" | "P" | "SS")
29
+ * @template TSubsetMapping - 서브셋별 최종 결과 타입 매핑
30
+ * @template TSubsetQueries - 서브셋 쿼리 함수 객체
31
+ * @template TLoaderQueries - 서브셋별 로더 쿼리 배열 객체
32
+ */
33
+ export class BaseModelClass<
34
+ TSubsetKey extends string = never,
35
+ TSubsetMapping extends Record<string, any> = never,
36
+ TSubsetQueries extends Record<TSubsetKey, PuriSubsetFn> = never,
37
+ TLoaderQueries extends PuriLoaderQueries<TSubsetKey> = never,
38
+ > {
15
39
  public modelName: string = "Unknown";
16
40
 
17
- /* DB 인스턴스 get, destroy */
41
+ constructor(
42
+ protected subsetQueries?: TSubsetQueries,
43
+ protected loaderQueries?: TLoaderQueries,
44
+ ) {}
45
+
18
46
  getDB(which: DBPreset): Knex {
19
47
  return DB.getDB(which);
20
48
  }
@@ -35,160 +63,281 @@ export class BaseModelClass {
35
63
  return DB.destroy();
36
64
  }
37
65
 
38
- myNow(timestamp?: number): string {
39
- const dt: DateTime =
40
- timestamp === undefined
41
- ? DateTime.local()
42
- : DateTime.fromSeconds(timestamp);
43
- return dt.toFormat("yyyy-MM-dd HH:mm:ss");
44
- }
45
-
46
66
  async getInsertedIds(
47
67
  wdb: Knex,
48
- rows: any[],
68
+ rows: UnknownDBRecord[],
49
69
  tableName: string,
50
70
  unqKeyFields: string[],
51
- chunkSize: number = 500
71
+ chunkSize: number = 500,
52
72
  ) {
53
73
  if (!wdb) {
54
74
  wdb = this.getDB("w");
55
75
  }
56
76
 
57
77
  let unqKeys: string[];
58
- let whereInField: any, selectField: string;
78
+ let whereInField: string | Knex.Raw;
79
+ let selectField: string;
80
+
59
81
  if (unqKeyFields.length > 1) {
60
82
  whereInField = wdb.raw(`CONCAT_WS('_', '${unqKeyFields.join(",")}')`);
61
83
  selectField = `${whereInField} as tmpUid`;
62
- unqKeys = rows.map((row) =>
63
- unqKeyFields.map((field) => row[field]).join("_")
64
- );
84
+ unqKeys = rows.map((row) => unqKeyFields.map((field) => row[field]).join("_"));
65
85
  } else {
66
86
  whereInField = unqKeyFields[0];
67
87
  selectField = unqKeyFields[0];
68
- unqKeys = rows.map((row) => row[unqKeyFields[0]]);
88
+ unqKeys = rows.map((row) => row[unqKeyFields[0]] as string);
69
89
  }
70
- const chunks = chunk(unqKeys, chunkSize);
71
90
 
72
91
  let resultIds: number[] = [];
73
- for (let chunk of chunks) {
92
+ for (const items of chunk(unqKeys, chunkSize)) {
74
93
  const dbRows = await wdb(tableName)
75
94
  .select("id", wdb.raw(selectField))
76
- .whereIn(whereInField, chunk);
95
+ .whereIn(whereInField as string, items);
77
96
  resultIds = resultIds.concat(
78
- dbRows.map((dbRow: any) => parseInt(dbRow.id))
97
+ dbRows.map((dbRow: UnknownDBRecord) => parseInt(String(dbRow.id))),
79
98
  );
80
99
  }
81
100
 
82
101
  return resultIds;
83
102
  }
84
103
 
85
- async useLoaders(db: Knex, rows: any[], loaders: SubsetQuery["loaders"]) {
86
- if (loaders.length === 0) {
87
- return rows;
104
+ /**
105
+ * 특정 서브셋에 대한 쿼리 빌더 획득
106
+ *
107
+ * @returns qb - 쿼리 빌더 (조건 추가용)
108
+ * @returns onSubset - 특정 서브셋 전용 타입이 필요할 때 사용
109
+ */
110
+ getSubsetQueries<T extends TSubsetKey>(subset: T) {
111
+ if (!this.subsetQueries) {
112
+ throw new Error("subsetQueries is not defined");
88
113
  }
89
114
 
90
- for (let loader of loaders) {
91
- let subQ: any;
92
- let subRows: any[];
93
- let toCol: string;
115
+ const puriWrapper = new PuriWrapper(this.getDB("r"), new UpsertBuilder());
116
+ const qb = this.subsetQueries[subset]?.(puriWrapper);
94
117
 
95
- const fromIds = rows.map((row) => row[loader.manyJoin.idField]);
118
+ // NonAllowedAsSingleTable: 단일 테이블 컬럼 접근 방지용 마커
119
+ type QBTables = UnionExtractedTTables<TSubsetKey, TSubsetQueries> & {
120
+ NonAllowedAsSingleTable: { __fulltext__: true };
121
+ };
96
122
 
97
- if (loader.manyJoin.through === undefined) {
98
- // HasMany
99
- const idColumn = `${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`;
100
- subQ = db(loader.manyJoin.toTable)
101
- .whereIn(idColumn, fromIds)
102
- .select([...loader.select, idColumn]);
123
+ return {
124
+ qb: qb as unknown as Puri<DatabaseSchemaExtend, QBTables, {}>,
125
+ onSubset: ((_subset: TSubsetKey | readonly TSubsetKey[]) => qb) as {
126
+ // 단일
127
+ <S extends TSubsetKey>(subset: S): ReturnType<TSubsetQueries[S]>;
128
+ // 키 배열 -> 교집합 반환
129
+ <Arr extends readonly TSubsetKey[]>(
130
+ subsets: [...Arr],
131
+ ): ResolveSubsetIntersection<Arr, TSubsetQueries>;
132
+ },
133
+ };
134
+ }
103
135
 
104
- // HasMany에서 OneJoin이 있는 경우
105
- loader.oneJoins.map((join) => {
106
- if (join.join == "inner") {
107
- subQ.innerJoin(
108
- `${join.table} as ${join.as}`,
109
- this.getJoinClause(db, join)
110
- );
111
- } else if (join.join == "outer") {
112
- subQ.leftOuterJoin(
113
- `${join.table} as ${join.as}`,
114
- this.getJoinClause(db, join)
115
- );
116
- }
117
- });
118
- toCol = loader.manyJoin.toCol;
119
- } else {
120
- // ManyToMany
121
- const idColumn = `${loader.manyJoin.through.table}.${loader.manyJoin.through.fromCol}`;
122
- subQ = db(loader.manyJoin.through.table)
123
- .join(
124
- loader.manyJoin.toTable,
125
- `${loader.manyJoin.through.table}.${loader.manyJoin.through.toCol}`,
126
- `${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`
127
- )
128
- .whereIn(idColumn, fromIds)
129
- .select(uniq([...loader.select, idColumn]));
130
-
131
- // ManyToMany에서 OneJoin이 있는 경우
132
- loader.oneJoins.map((join) => {
133
- if (join.join == "inner") {
134
- subQ.innerJoin(
135
- `${join.table} as ${join.as}`,
136
- this.getJoinClause(db, join)
137
- );
138
- } else if (join.join == "outer") {
139
- subQ.leftOuterJoin(
140
- `${join.table} as ${join.as}`,
141
- this.getJoinClause(db, join)
142
- );
143
- }
144
- });
145
- toCol = loader.manyJoin.through.fromCol;
136
+ /**
137
+ * Enhancer 객체 생성 헬퍼
138
+ * 타입 검증 추론을 도와줌
139
+ */
140
+ createEnhancers<T extends TSubsetKey>(
141
+ enhancers: EnhancerMap<T, InferAllSubsets<TSubsetQueries, TLoaderQueries>, TSubsetMapping>,
142
+ ) {
143
+ return enhancers;
144
+ }
145
+
146
+ /**
147
+ * 서브셋 쿼리 실행
148
+ *
149
+ * 1. 쿼리 실행 (pagination 적용)
150
+ * 2. 로더 실행 (1:N, N:M 관계 데이터 로딩)
151
+ * 3. Hydrate (flat → 중첩 객체)
152
+ * 4. Enhancer 적용 (virtual 필드 계산)
153
+ */
154
+ async executeSubsetQuery<
155
+ T extends TSubsetKey,
156
+ TComputedResults extends InferAllSubsets<TSubsetQueries, TLoaderQueries>,
157
+ >(
158
+ params: {
159
+ subset: T;
160
+ qb: Puri<any, any, any>;
161
+ params: {
162
+ num?: number;
163
+ page?: number;
164
+ queryMode?: "list" | "count" | "both";
165
+ };
166
+ debug?: boolean;
167
+ optimizeCountQuery?: boolean;
168
+ } & EnhancerParam<TSubsetKey, TComputedResults, TSubsetMapping>,
169
+ ): Promise<ExecuteSubsetQueryResult<TSubsetMapping, T>> {
170
+ const { subset, qb, params: queryParams, debug = false, optimizeCountQuery = false } = params;
171
+
172
+ if (!this.loaderQueries) {
173
+ throw new Error("loaderQueries is not defined");
174
+ }
175
+
176
+ if (!queryParams.num || !queryParams.page) {
177
+ throw new Error("num and page are required");
178
+ }
179
+
180
+ const { num, page } = queryParams;
181
+
182
+ // COUNT 쿼리 실행
183
+ const total = await this.executeCountQuery(qb, queryParams, debug, optimizeCountQuery);
184
+
185
+ // LIST 쿼리 실행
186
+ const computedRows = await this.executeListQuery(subset, qb, queryParams, num, page, debug);
187
+
188
+ // Enhancer 적용
189
+ const enhancer = (params as any).enhancers?.[subset];
190
+ const rows = (await Promise.all(
191
+ computedRows.map((row) => enhancer?.(row) ?? row),
192
+ )) as TSubsetMapping[T][];
193
+
194
+ return { rows, total };
195
+ }
196
+
197
+ /**
198
+ * COUNT 쿼리 실행 (내부 메서드)
199
+ */
200
+ private async executeCountQuery(
201
+ qb: Puri<any, any, any>,
202
+ params: { queryMode?: "list" | "count" | "both" },
203
+ debug: boolean,
204
+ optimizeCountQuery: boolean,
205
+ ): Promise<number> {
206
+ if (params.queryMode === "list") {
207
+ return 0;
208
+ }
209
+
210
+ const countPuri = qb.clone().clear("order").clear("limit").clear("offset");
211
+
212
+ if (optimizeCountQuery) {
213
+ const { default: SqlParser } = await import("node-sql-parser");
214
+ const parser = new SqlParser.Parser();
215
+ const parsedQuery = parser.astify(countPuri.toQuery(), {
216
+ database: Sonamu.config.database.database,
217
+ });
218
+
219
+ const leftJoinTables = getJoinTables(parsedQuery, ["LEFT JOIN"]);
220
+ const whereTables = getTableNamesFromWhere(parsedQuery);
221
+
222
+ const tablesToRemove = leftJoinTables.filter((j) => !whereTables.includes(j));
223
+ tablesToRemove.forEach((table) => {
224
+ countPuri.clearJoin(table);
225
+ });
226
+ }
227
+
228
+ // COUNT(*)로 전체 레코드 수를 계산
229
+ // TODO: qb의 DISTINCT가 있는 경우 처리해야 함
230
+ const countResult: { total?: number } = await countPuri
231
+ .clear("select")
232
+ .select({ total: Puri.rawNumber(`COUNT(*)`) })
233
+ .first();
234
+
235
+ if (debug) {
236
+ countPuri.debug();
237
+ }
238
+
239
+ return countResult?.total ?? 0;
240
+ }
241
+
242
+ /**
243
+ * LIST 쿼리 실행 (내부 메서드)
244
+ */
245
+ private async executeListQuery<T extends TSubsetKey>(
246
+ subset: T,
247
+ qb: Puri<any, any, any>,
248
+ params: { queryMode?: "list" | "count" | "both" },
249
+ num: number,
250
+ page: number,
251
+ debug: boolean,
252
+ ): Promise<any[]> {
253
+ if (params.queryMode === "count") {
254
+ return [];
255
+ }
256
+
257
+ let unloadedRows = (await qb.limit(num).offset(num * (page - 1))) as any[];
258
+
259
+ if (debug) {
260
+ qb.debug();
261
+ }
262
+
263
+ // 로더 처리
264
+ const loaders = (this.loaderQueries as any)[subset];
265
+ if (loaders && Array.isArray(loaders)) {
266
+ unloadedRows = await this.processLoaders(unloadedRows, loaders, debug);
267
+ }
268
+
269
+ return this.hydrate(unloadedRows);
270
+ }
271
+
272
+ /**
273
+ * 재귀적 로더 처리
274
+ */
275
+ private async processLoaders(rows: any[], loaders: any[], debug: boolean): Promise<any[]> {
276
+ for (const resolveLoader of loaders) {
277
+ const { as, refId, qb: resolveLoaderQbFn, loaders: nestedLoaders } = resolveLoader;
278
+
279
+ const resolveLoaderQb = resolveLoaderQbFn(
280
+ new PuriWrapper(this.getDB("r"), new UpsertBuilder()),
281
+ rows.map((row) => row[refId]),
282
+ );
283
+
284
+ if (debug) {
285
+ resolveLoaderQb.debug();
146
286
  }
147
- subRows = await subQ;
148
287
 
149
- if (loader.loaders) {
150
- // 추가 -Many 케이스가 있는 경우 recursion 처리
151
- subRows = await this.useLoaders(db, subRows, loader.loaders);
288
+ let loadedRows = (await resolveLoaderQb) as any[];
289
+
290
+ // 중첩 loaders가 있으면 재귀 처리
291
+ if (nestedLoaders && nestedLoaders.length > 0) {
292
+ loadedRows = await this.processLoaders(loadedRows, nestedLoaders, debug);
152
293
  }
153
294
 
154
- // 불러온 row들을 참조ID 기준으로 분류 배치
155
- const subRowGroups = groupBy(subRows, toCol);
295
+ const subRowGroups = group(loadedRows, (row) => row.refId);
296
+
156
297
  rows = rows.map((row) => {
157
- row[loader.as] = (subRowGroups[row[loader.manyJoin.idField]] ?? []).map(
158
- (r) => omit(r, toCol)
159
- );
298
+ row[as] = (subRowGroups[row[refId]] ?? []).map((r) => omit(r, ["refId"]));
160
299
  return row;
161
300
  });
162
301
  }
302
+
163
303
  return rows;
164
304
  }
165
305
 
166
- hydrate<T>(rows: T[]): T[] {
167
- return rows.map((row: any) => {
168
- // nullable relation인 경우 관련된 필드가 전부 null로 생성되는 것 방지하는 코드
306
+ /**
307
+ * Flat 레코드를 중첩 객체로 변환
308
+ *
309
+ * - `user__name` → `{ user: { name } }`
310
+ * - nullable relation의 경우 모든 필드가 null이면 객체 자체를 null로
311
+ */
312
+ hydrate<T extends UnknownDBRecord>(rows: T[]): T[] {
313
+ return rows.map((row: T) => {
314
+ // nullable relation 처리: 관련 필드가 전부 null인 경우 방지
169
315
  const nestedKeys = Object.keys(row).filter((key) => key.includes("__"));
170
- const groups = groupBy(nestedKeys, (key) => key.split("__")[0]);
171
- const nullKeys = Object.keys(groups).filter(
172
- (key) =>
173
- groups[key].length > 1 &&
174
- groups[key].every(
175
- (field) =>
176
- row[field] === null ||
177
- (Array.isArray(row[field]) && row[field].length === 0)
178
- )
179
- );
316
+ const groups = Object.groupBy(nestedKeys, (key) => key.split("__")[0]);
317
+ const nullKeys = Object.entries(groups)
318
+ .filter(
319
+ ([_, data]) =>
320
+ data &&
321
+ data.length > 1 &&
322
+ data.every(
323
+ (field) =>
324
+ row[field] === null || (Array.isArray(row[field]) && row[field].length === 0),
325
+ ),
326
+ )
327
+ .map(([key]) => key);
180
328
 
181
329
  const hydrated = Object.keys(row).reduce((r, field) => {
182
330
  if (!field.includes("__")) {
331
+ // 일반 필드: 배열 내 객체면 재귀 hydrate
183
332
  if (Array.isArray(row[field]) && isObject(row[field][0])) {
184
333
  r[field] = this.hydrate(row[field]);
185
- return r;
186
334
  } else {
187
335
  r[field] = row[field];
188
- return r;
189
336
  }
337
+ return r;
190
338
  }
191
339
 
340
+ // 중첩 필드 처리: user__name → user[name]
192
341
  const parts = field.split("__");
193
342
  const objPath =
194
343
  parts[0] +
@@ -196,22 +345,28 @@ export class BaseModelClass {
196
345
  .slice(1)
197
346
  .map((part) => `[${part}]`)
198
347
  .join("");
199
- set(
348
+
349
+ r = set(
200
350
  r,
201
351
  objPath,
202
352
  row[field] && Array.isArray(row[field]) && isObject(row[field][0])
203
353
  ? this.hydrate(row[field])
204
- : row[field]
354
+ : row[field],
205
355
  );
206
356
 
207
357
  return r;
208
- }, {} as any);
209
- nullKeys.map((nullKey) => (hydrated[nullKey] = null));
358
+ }, {} as UnknownDBRecord);
359
+
360
+ // null relation 처리
361
+ nullKeys.forEach((nullKey) => {
362
+ hydrated[nullKey] = null;
363
+ });
210
364
 
211
365
  return hydrated;
212
- });
366
+ }) as T[];
213
367
  }
214
368
 
369
+ // Legacy SubsetQuery 실행 (Puri 도입 전 호환용)
215
370
  async runSubsetQuery<T extends BaseListParams, U extends string>({
216
371
  params,
217
372
  baseTable,
@@ -245,16 +400,19 @@ export class BaseModelClass {
245
400
  db?: Knex;
246
401
  optimizeCountQuery?: boolean;
247
402
  }): Promise<{
403
+ // biome-ignore lint/suspicious/noExplicitAny: Puri 도입 전까지 any로 유지
248
404
  rows: any[];
249
405
  total?: number | undefined;
250
406
  subsetQuery: SubsetQuery;
251
407
  qb: Knex.QueryBuilder;
252
408
  }> {
409
+ const chalk = (await import("chalk")).default;
410
+ const SqlParser = (await import("node-sql-parser")).default;
411
+ const { getTableName, getTableNamesFromWhere } = await import("../utils/sql-parser");
412
+
253
413
  const db = _db ?? this.getDB(subset.startsWith("A") ? "w" : "r");
254
- baseTable =
255
- baseTable ?? inflection.pluralize(inflection.underscore(this.modelName));
256
- const queryMode =
257
- params.queryMode ?? (params.id !== undefined ? "list" : "both");
414
+ baseTable = baseTable ?? inflection.pluralize(inflection.underscore(this.modelName));
415
+ const queryMode = params.queryMode ?? (params.id !== undefined ? "list" : "both");
258
416
 
259
417
  const { select, virtual, joins, loaders } = subsetQuery;
260
418
  const qb = build({
@@ -265,21 +423,12 @@ export class BaseModelClass {
265
423
  virtual,
266
424
  });
267
425
 
268
- const applyJoinClause = (
269
- qb: Knex.QueryBuilder,
270
- joins: SubsetQuery["joins"]
271
- ) => {
272
- joins.map((join) => {
273
- if (join.join == "inner") {
274
- qb.innerJoin(
275
- `${join.table} as ${join.as}`,
276
- this.getJoinClause(db, join)
277
- );
278
- } else if (join.join == "outer") {
279
- qb.leftOuterJoin(
280
- `${join.table} as ${join.as}`,
281
- this.getJoinClause(db, join)
282
- );
426
+ const applyJoinClause = (qb: Knex.QueryBuilder, joins: SubsetQuery["joins"]) => {
427
+ joins.forEach((join) => {
428
+ if (join.join === "inner") {
429
+ qb.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
430
+ } else if (join.join === "outer") {
431
+ qb.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
283
432
  }
284
433
  });
285
434
  };
@@ -293,34 +442,27 @@ export class BaseModelClass {
293
442
  const clonedQb = qb.clone().clear("order").clear("offset").clear("limit");
294
443
  const parser = new SqlParser.Parser();
295
444
 
296
- // optmizeCountQuery가 true인 경우 다른 clause에 영향을 주지 않는 모든 join을 제외함
297
445
  if (optimizeCountQuery) {
298
- const parsedQuery = parser.astify(clonedQb.toQuery());
446
+ const parsedQuery = parser.astify(clonedQb.toQuery(), {
447
+ database: Sonamu.config.database.database,
448
+ });
299
449
  const tables = getTableNamesFromWhere(parsedQuery);
300
- // where절에 사용되는 테이블의 조인을 위해 사용되는 테이블
301
- const needToJoin = uniq(
302
- tables.flatMap((table) =>
303
- table.split("__").map((t) => inflection.pluralize(t))
304
- )
450
+ const needToJoin = unique(
451
+ tables.flatMap((table) => table.split("__").map((t) => inflection.pluralize(t))),
305
452
  );
306
453
  applyJoinClause(
307
454
  clonedQb,
308
- joins.filter((j) => needToJoin.includes(j.table))
455
+ joins.filter((j) => needToJoin.includes(j.table)),
309
456
  );
310
457
  } else {
311
458
  applyJoinClause(clonedQb, joins);
312
459
  }
313
460
 
314
- const processedQb =
315
- afterBuild?.({
316
- qb: clonedQb,
317
- db,
318
- select,
319
- joins,
320
- virtual,
321
- }) ?? clonedQb;
461
+ const processedQb = afterBuild?.({ qb: clonedQb, db, select, joins, virtual }) ?? clonedQb;
322
462
 
323
- const parsedQuery = parser.astify(processedQb.toQuery());
463
+ const parsedQuery = parser.astify(processedQb.toQuery(), {
464
+ database: Sonamu.config.database.database,
465
+ });
324
466
  const q = Array.isArray(parsedQuery) ? parsedQuery[0] : parsedQuery;
325
467
  if (q.type !== "select") {
326
468
  throw new Error("Invalid query");
@@ -332,19 +474,15 @@ export class BaseModelClass {
332
474
  .clear("select")
333
475
  .select(
334
476
  db.raw(
335
- `COUNT(DISTINCT \`${getTableName(q.columns[0].expr)}\`.\`${q.columns[0].expr.column}\`) as total`
336
- )
477
+ `COUNT(DISTINCT \`${getTableName(q.columns[0].expr)}\`.\`${q.columns[0].expr.column}\`) as total`,
478
+ ),
337
479
  )
338
480
  .first()
339
481
  : clonedQb.clear("select").count("*", { as: "total" }).first();
340
482
  const countRow: { total?: number } = await countQuery;
341
483
 
342
- // debug: countQuery
343
484
  if (debug === true || debug === "count") {
344
- console.debug(
345
- "DEBUG: count query",
346
- chalk.blue(countQuery.toQuery().toString())
347
- );
485
+ console.debug("DEBUG: count query", chalk.blue(countQuery.toQuery().toString()));
348
486
  }
349
487
 
350
488
  return countRow?.total ?? 0;
@@ -356,34 +494,20 @@ export class BaseModelClass {
356
494
  return [];
357
495
  }
358
496
 
359
- // limit, offset
360
497
  if (params.num !== 0) {
361
- qb.limit(params.num!);
362
- qb.offset(params.num! * (params.page! - 1));
498
+ assert(params.num);
499
+ qb.limit(params.num);
500
+ qb.offset(params.num * ((params.page ?? 1) - 1));
363
501
  }
364
502
 
365
- // select, rows
366
503
  const clonedQb = qb.clone().select(select);
367
-
368
- // join
369
504
  applyJoinClause(clonedQb, joins);
370
505
 
371
- const listQuery =
372
- afterBuild?.({
373
- qb: clonedQb,
374
- db,
375
- select,
376
- joins,
377
- virtual,
378
- }) ?? clonedQb;
506
+ const listQuery = afterBuild?.({ qb: clonedQb, db, select, joins, virtual }) ?? clonedQb;
379
507
 
380
508
  let rows = await listQuery;
381
- // debug: listQuery
382
509
  if (debug === true || debug === "list") {
383
- console.debug(
384
- "DEBUG: list query",
385
- chalk.blue(listQuery.toQuery().toString())
386
- );
510
+ console.debug("DEBUG: list query", chalk.blue(listQuery.toQuery().toString()));
387
511
  }
388
512
 
389
513
  rows = await this.useLoaders(db, rows, loaders);
@@ -394,10 +518,73 @@ export class BaseModelClass {
394
518
  return { rows, total, subsetQuery, qb };
395
519
  }
396
520
 
397
- getJoinClause(
398
- db: Knex<any, unknown>,
399
- join: SubsetQuery["joins"][number]
400
- ): Knex.Raw<any> {
521
+ // Legacy Loader 처리 (Puri 도입 전 호환용)
522
+ async useLoaders(db: Knex, rows: UnknownDBRecord[], loaders: SubsetQuery["loaders"]) {
523
+ if (loaders.length === 0) {
524
+ return rows;
525
+ }
526
+
527
+ for (const loader of loaders) {
528
+ let subQ: Knex.QueryBuilder;
529
+ let subRows: UnknownDBRecord[];
530
+ let toCol: string;
531
+
532
+ const fromIds = rows.map((row) => row[loader.manyJoin.idField]);
533
+
534
+ if (loader.manyJoin.through === undefined) {
535
+ // HasMany
536
+ const idColumn = `${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`;
537
+ subQ = db(loader.manyJoin.toTable)
538
+ .whereIn(idColumn as string, fromIds as string[])
539
+ .select([...loader.select, idColumn]);
540
+
541
+ loader.oneJoins.forEach((join) => {
542
+ if (join.join === "inner") {
543
+ subQ.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
544
+ } else if (join.join === "outer") {
545
+ subQ.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
546
+ }
547
+ });
548
+ toCol = loader.manyJoin.toCol;
549
+ } else {
550
+ // ManyToMany
551
+ const idColumn = `${loader.manyJoin.through.table}.${loader.manyJoin.through.fromCol}`;
552
+ subQ = db(loader.manyJoin.through.table)
553
+ .join(
554
+ loader.manyJoin.toTable,
555
+ `${loader.manyJoin.through.table}.${loader.manyJoin.through.toCol}`,
556
+ `${loader.manyJoin.toTable}.${loader.manyJoin.toCol}`,
557
+ )
558
+ .whereIn(idColumn as string, fromIds as string[])
559
+ .select(unique([...loader.select, idColumn]));
560
+
561
+ loader.oneJoins.forEach((join) => {
562
+ if (join.join === "inner") {
563
+ subQ.innerJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
564
+ } else if (join.join === "outer") {
565
+ subQ.leftOuterJoin(`${join.table} as ${join.as}`, this.getJoinClause(db, join));
566
+ }
567
+ });
568
+ toCol = loader.manyJoin.through.fromCol;
569
+ }
570
+ subRows = await subQ;
571
+
572
+ if (loader.loaders) {
573
+ subRows = await this.useLoaders(db, subRows, loader.loaders);
574
+ }
575
+
576
+ const subRowGroups = group(subRows, (row) => row[toCol] as string);
577
+ rows = rows.map((row) => {
578
+ row[loader.as] = (subRowGroups[row[loader.manyJoin.idField] as string] ?? []).map((r) =>
579
+ omit(r, [toCol]),
580
+ );
581
+ return row;
582
+ });
583
+ }
584
+ return rows;
585
+ }
586
+
587
+ getJoinClause(db: Knex<any, unknown>, join: SubsetQuery["joins"][number]): Knex.Raw<any> {
401
588
  if (!isCustomJoinClause(join)) {
402
589
  return db.raw(`${join.from} = ${join.to}`);
403
590
  } else {
@@ -409,4 +596,25 @@ export class BaseModelClass {
409
596
  return new UpsertBuilder();
410
597
  }
411
598
  }
599
+
600
+ /**
601
+ * Enhancer 파라미터 조건부 타입
602
+ * RequiredEnhancerKeys가 없으면 enhancers 선택적, 있으면 필수
603
+ */
604
+ type EnhancerParam<
605
+ TSubsetKey extends string,
606
+ TComputedResults extends Record<TSubsetKey, any>,
607
+ TSubsetMapping extends Record<TSubsetKey, any>,
608
+ > = [RequiredEnhancerKeys<TSubsetKey, TComputedResults, TSubsetMapping>] extends [never]
609
+ ? { enhancers?: EnhancerMap<TSubsetKey, TComputedResults, TSubsetMapping> }
610
+ : { enhancers: EnhancerMap<TSubsetKey, TComputedResults, TSubsetMapping> };
611
+
612
+ type RequiredEnhancerKeys<
613
+ TSubsetKey extends string,
614
+ TComputedResults extends Record<TSubsetKey, any>,
615
+ TSubsetMapping extends Record<TSubsetKey, any>,
616
+ > = {
617
+ [K in TSubsetKey]: TComputedResults[K] extends TSubsetMapping[K] ? never : K;
618
+ }[TSubsetKey];
619
+
412
620
  export const BaseModel = new BaseModelClass();