sonamu 0.4.14 → 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 (348) 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/database/_batch_update.d.ts +15 -0
  47. package/dist/database/_batch_update.d.ts.map +1 -0
  48. package/dist/database/_batch_update.js +2 -0
  49. package/dist/database/_batch_update.js.map +1 -0
  50. package/dist/database/base-model.d.ts +41 -0
  51. package/dist/database/base-model.d.ts.map +1 -0
  52. package/dist/database/base-model.js +2 -0
  53. package/dist/database/base-model.js.map +1 -0
  54. package/dist/database/code-generator.d.ts +13 -0
  55. package/dist/database/code-generator.d.ts.map +1 -0
  56. package/dist/database/code-generator.js +2 -0
  57. package/dist/database/code-generator.js.map +1 -0
  58. package/dist/database/db.d.ts +40 -0
  59. package/dist/database/db.d.ts.map +1 -0
  60. package/dist/database/db.js +2 -0
  61. package/dist/database/db.js.map +1 -0
  62. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
  63. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
  64. package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
  65. package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
  66. package/dist/database/puri-wrapper.d.ts +34 -0
  67. package/dist/database/puri-wrapper.d.ts.map +1 -0
  68. package/dist/database/puri-wrapper.js +2 -0
  69. package/dist/database/puri-wrapper.js.map +1 -0
  70. package/dist/database/puri.d.ts +83 -0
  71. package/dist/database/puri.d.ts.map +1 -0
  72. package/dist/database/puri.js +2 -0
  73. package/dist/database/puri.js.map +1 -0
  74. package/dist/database/puri.types.d.ts +60 -0
  75. package/dist/database/puri.types.d.ts.map +1 -0
  76. package/dist/database/puri.types.js +2 -0
  77. package/dist/database/puri.types.js.map +1 -0
  78. package/dist/database/transaction-context.d.ts +9 -0
  79. package/dist/database/transaction-context.d.ts.map +1 -0
  80. package/dist/database/transaction-context.js +2 -0
  81. package/dist/database/transaction-context.js.map +1 -0
  82. package/dist/database/types.d.ts +39 -0
  83. package/dist/database/types.d.ts.map +1 -0
  84. package/dist/database/types.js +2 -0
  85. package/dist/database/types.js.map +1 -0
  86. package/dist/database/upsert-builder.d.ts +34 -0
  87. package/dist/database/upsert-builder.d.ts.map +1 -0
  88. package/dist/database/upsert-builder.js +2 -0
  89. package/dist/database/upsert-builder.js.map +1 -0
  90. package/dist/entity/entity-manager.d.ts +32 -0
  91. package/dist/entity/entity-manager.d.ts.map +1 -0
  92. package/dist/entity/entity-manager.js +2 -0
  93. package/dist/entity/entity-manager.js.map +1 -0
  94. package/dist/entity/entity-utils.d.ts +61 -0
  95. package/dist/entity/entity-utils.d.ts.map +1 -0
  96. package/dist/entity/entity-utils.js +2 -0
  97. package/dist/entity/entity-utils.js.map +1 -0
  98. package/dist/entity/entity.d.ts +62 -0
  99. package/dist/entity/entity.d.ts.map +1 -0
  100. package/dist/entity/entity.js +2 -0
  101. package/dist/entity/entity.js.map +1 -0
  102. package/dist/entity/migrator.d.ts +135 -0
  103. package/dist/entity/migrator.d.ts.map +1 -0
  104. package/dist/entity/migrator.js +2 -0
  105. package/dist/entity/migrator.js.map +1 -0
  106. package/dist/exceptions/error-handler.d.ts +3 -0
  107. package/dist/exceptions/error-handler.d.ts.map +1 -0
  108. package/dist/exceptions/error-handler.js +2 -0
  109. package/dist/exceptions/error-handler.js.map +1 -0
  110. package/dist/exceptions/so-exceptions.d.ts +48 -0
  111. package/dist/exceptions/so-exceptions.d.ts.map +1 -0
  112. package/dist/exceptions/so-exceptions.js +2 -0
  113. package/dist/exceptions/so-exceptions.js.map +1 -0
  114. package/dist/file-storage/driver.d.ts +45 -0
  115. package/dist/file-storage/driver.d.ts.map +1 -0
  116. package/dist/file-storage/driver.js +2 -0
  117. package/dist/file-storage/driver.js.map +1 -0
  118. package/dist/file-storage/file-storage.d.ts +50 -0
  119. package/dist/file-storage/file-storage.d.ts.map +1 -0
  120. package/dist/file-storage/file-storage.js +2 -0
  121. package/dist/file-storage/file-storage.js.map +1 -0
  122. package/dist/index.d.ts +22 -813
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +1 -433
  125. package/dist/index.js.map +1 -1
  126. package/dist/migration/code-generation.d.ts +15 -0
  127. package/dist/migration/code-generation.d.ts.map +1 -0
  128. package/dist/migration/code-generation.js +2 -0
  129. package/dist/migration/code-generation.js.map +1 -0
  130. package/dist/migration/migration-set.d.ts +17 -0
  131. package/dist/migration/migration-set.d.ts.map +1 -0
  132. package/dist/migration/migration-set.js +2 -0
  133. package/dist/migration/migration-set.js.map +1 -0
  134. package/dist/migration/migrator.d.ts +130 -0
  135. package/dist/migration/migrator.d.ts.map +1 -0
  136. package/dist/migration/migrator.js +2 -0
  137. package/dist/migration/migrator.js.map +1 -0
  138. package/dist/migration/types.d.ts +52 -0
  139. package/dist/migration/types.d.ts.map +1 -0
  140. package/dist/migration/types.js +2 -0
  141. package/dist/migration/types.js.map +1 -0
  142. package/dist/smd/smd-manager.d.ts +28 -0
  143. package/dist/smd/smd-manager.d.ts.map +1 -0
  144. package/dist/smd/smd-manager.js +2 -0
  145. package/dist/smd/smd-manager.js.map +1 -0
  146. package/dist/smd/smd.d.ts +40 -0
  147. package/dist/smd/smd.d.ts.map +1 -0
  148. package/dist/smd/smd.js +2 -0
  149. package/dist/smd/smd.js.map +1 -0
  150. package/dist/syncer/index.d.ts +2 -0
  151. package/dist/syncer/index.d.ts.map +1 -0
  152. package/dist/syncer/index.js +2 -0
  153. package/dist/syncer/index.js.map +1 -0
  154. package/dist/syncer/syncer.d.ts +127 -0
  155. package/dist/syncer/syncer.d.ts.map +1 -0
  156. package/dist/syncer/syncer.js +2 -0
  157. package/dist/syncer/syncer.js.map +1 -0
  158. package/dist/templates/base-template.d.ts +13 -0
  159. package/dist/templates/base-template.d.ts.map +1 -0
  160. package/dist/templates/base-template.js +2 -0
  161. package/dist/templates/base-template.js.map +1 -0
  162. package/dist/templates/entity.template.d.ts +17 -0
  163. package/dist/templates/entity.template.d.ts.map +1 -0
  164. package/dist/templates/entity.template.js +2 -0
  165. package/dist/templates/entity.template.js.map +1 -0
  166. package/dist/templates/generated.template.d.ts +27 -0
  167. package/dist/templates/generated.template.d.ts.map +1 -0
  168. package/dist/templates/generated.template.js +2 -0
  169. package/dist/templates/generated.template.js.map +1 -0
  170. package/dist/templates/generated_http.template.d.ts +24 -0
  171. package/dist/templates/generated_http.template.d.ts.map +1 -0
  172. package/dist/templates/generated_http.template.js +2 -0
  173. package/dist/templates/generated_http.template.js.map +1 -0
  174. package/dist/templates/generated_sso.template.d.ts +20 -0
  175. package/dist/templates/generated_sso.template.d.ts.map +1 -0
  176. package/dist/templates/generated_sso.template.js +2 -0
  177. package/dist/templates/generated_sso.template.js.map +1 -0
  178. package/dist/templates/index.d.ts +2 -0
  179. package/dist/templates/index.d.ts.map +1 -0
  180. package/dist/templates/index.js +2 -0
  181. package/dist/templates/index.js.map +1 -0
  182. package/dist/templates/init_types.template.d.ts +17 -0
  183. package/dist/templates/init_types.template.d.ts.map +1 -0
  184. package/dist/templates/init_types.template.js +2 -0
  185. package/dist/templates/init_types.template.js.map +1 -0
  186. package/dist/templates/model.template.d.ts +17 -0
  187. package/dist/templates/model.template.d.ts.map +1 -0
  188. package/dist/templates/model.template.js +2 -0
  189. package/dist/templates/model.template.js.map +1 -0
  190. package/dist/templates/model_test.template.d.ts +17 -0
  191. package/dist/templates/model_test.template.d.ts.map +1 -0
  192. package/dist/templates/model_test.template.js +2 -0
  193. package/dist/templates/model_test.template.js.map +1 -0
  194. package/dist/templates/service.template.d.ts +29 -0
  195. package/dist/templates/service.template.d.ts.map +1 -0
  196. package/dist/templates/service.template.js +2 -0
  197. package/dist/templates/service.template.js.map +1 -0
  198. package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
  199. package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
  200. package/dist/templates/view_enums_buttonset.template.js +2 -0
  201. package/dist/templates/view_enums_buttonset.template.js.map +1 -0
  202. package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
  203. package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
  204. package/dist/templates/view_enums_dropdown.template.js +2 -0
  205. package/dist/templates/view_enums_dropdown.template.js.map +1 -0
  206. package/dist/templates/view_enums_select.template.d.ts +17 -0
  207. package/dist/templates/view_enums_select.template.d.ts.map +1 -0
  208. package/dist/templates/view_enums_select.template.js +2 -0
  209. package/dist/templates/view_enums_select.template.js.map +1 -0
  210. package/dist/templates/view_form.template.d.ts +26 -0
  211. package/dist/templates/view_form.template.d.ts.map +1 -0
  212. package/dist/templates/view_form.template.js +2 -0
  213. package/dist/templates/view_form.template.js.map +1 -0
  214. package/dist/templates/view_id_all_select.template.d.ts +17 -0
  215. package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
  216. package/dist/templates/view_id_all_select.template.js +2 -0
  217. package/dist/templates/view_id_all_select.template.js.map +1 -0
  218. package/dist/templates/view_id_async_select.template.d.ts +17 -0
  219. package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
  220. package/dist/templates/view_id_async_select.template.js +2 -0
  221. package/dist/templates/view_id_async_select.template.js.map +1 -0
  222. package/dist/templates/view_list.template.d.ts +38 -0
  223. package/dist/templates/view_list.template.d.ts.map +1 -0
  224. package/dist/templates/view_list.template.js +2 -0
  225. package/dist/templates/view_list.template.js.map +1 -0
  226. package/dist/templates/view_list_columns.template.d.ts +17 -0
  227. package/dist/templates/view_list_columns.template.d.ts.map +1 -0
  228. package/dist/templates/view_list_columns.template.js +2 -0
  229. package/dist/templates/view_list_columns.template.js.map +1 -0
  230. package/dist/templates/view_search_input.template.d.ts +17 -0
  231. package/dist/templates/view_search_input.template.d.ts.map +1 -0
  232. package/dist/templates/view_search_input.template.js +2 -0
  233. package/dist/templates/view_search_input.template.js.map +1 -0
  234. package/dist/testing/_relation-graph.d.ts +7 -0
  235. package/dist/testing/_relation-graph.d.ts.map +1 -0
  236. package/dist/testing/_relation-graph.js +2 -0
  237. package/dist/testing/_relation-graph.js.map +1 -0
  238. package/dist/testing/fixture-manager.d.ts +35 -0
  239. package/dist/testing/fixture-manager.d.ts.map +1 -0
  240. package/dist/testing/fixture-manager.js +2 -0
  241. package/dist/testing/fixture-manager.js.map +1 -0
  242. package/dist/types/types.d.ts +609 -0
  243. package/dist/types/types.d.ts.map +1 -0
  244. package/dist/types/types.js +2 -0
  245. package/dist/types/types.js.map +1 -0
  246. package/dist/typings/knex.d.js +2 -0
  247. package/dist/typings/knex.d.js.map +1 -0
  248. package/dist/utils/async-utils.d.ts +25 -0
  249. package/dist/utils/async-utils.d.ts.map +1 -0
  250. package/dist/utils/async-utils.js +2 -0
  251. package/dist/utils/async-utils.js.map +1 -0
  252. package/dist/utils/controller.d.ts +9 -0
  253. package/dist/utils/controller.d.ts.map +1 -0
  254. package/dist/utils/controller.js +2 -0
  255. package/dist/utils/controller.js.map +1 -0
  256. package/dist/utils/fs-utils.d.ts +9 -0
  257. package/dist/utils/fs-utils.d.ts.map +1 -0
  258. package/dist/utils/fs-utils.js +2 -0
  259. package/dist/utils/fs-utils.js.map +1 -0
  260. package/dist/utils/lodash-able.d.ts +2 -0
  261. package/dist/utils/lodash-able.d.ts.map +1 -0
  262. package/dist/utils/lodash-able.js +2 -0
  263. package/dist/utils/lodash-able.js.map +1 -0
  264. package/dist/utils/model.d.ts +17 -0
  265. package/dist/utils/model.d.ts.map +1 -0
  266. package/dist/utils/model.js +2 -0
  267. package/dist/utils/model.js.map +1 -0
  268. package/dist/utils/sql-parser.d.ts +4 -0
  269. package/dist/utils/sql-parser.d.ts.map +1 -0
  270. package/dist/utils/sql-parser.js +2 -0
  271. package/dist/utils/sql-parser.js.map +1 -0
  272. package/dist/utils/utils.d.ts +9 -0
  273. package/dist/utils/utils.d.ts.map +1 -0
  274. package/dist/utils/utils.js +2 -0
  275. package/dist/utils/utils.js.map +1 -0
  276. package/dist/utils/zod-error.d.ts +8 -0
  277. package/dist/utils/zod-error.d.ts.map +1 -0
  278. package/dist/utils/zod-error.js +2 -0
  279. package/dist/utils/zod-error.js.map +1 -0
  280. package/nodemon.json +6 -0
  281. package/package.json +29 -44
  282. package/src/api/base-frame.ts +3 -4
  283. package/src/api/caster.ts +22 -23
  284. package/src/api/code-converters.ts +170 -134
  285. package/src/api/context.ts +13 -6
  286. package/src/api/decorators.ts +146 -20
  287. package/src/api/index.ts +2 -0
  288. package/src/api/sonamu.ts +374 -165
  289. package/src/bin/build-config.ts +5 -0
  290. package/src/bin/cli-wrapper.ts +29 -40
  291. package/src/bin/cli.ts +132 -190
  292. package/src/database/_batch_update.ts +10 -15
  293. package/src/database/base-model.ts +300 -216
  294. package/src/database/db.ts +191 -21
  295. package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
  296. package/src/database/puri-wrapper.ts +129 -0
  297. package/src/database/puri.ts +808 -0
  298. package/src/database/puri.types.ts +222 -0
  299. package/src/database/transaction-context.ts +18 -0
  300. package/src/database/upsert-builder.ts +32 -35
  301. package/src/entity/entity-manager.ts +7 -15
  302. package/src/entity/entity.ts +9 -31
  303. package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
  304. package/src/file-storage/driver.ts +121 -0
  305. package/src/file-storage/file-storage.ts +100 -0
  306. package/src/index.ts +14 -11
  307. package/src/migration/code-generation.ts +777 -0
  308. package/src/migration/migration-set.ts +453 -0
  309. package/src/migration/migrator.ts +823 -0
  310. package/src/migration/types.ts +53 -0
  311. package/src/shared/web.shared.ts.txt +33 -2
  312. package/src/syncer/syncer.ts +294 -127
  313. package/src/templates/generated.template.ts +13 -1
  314. package/src/templates/generated_http.template.ts +15 -12
  315. package/src/templates/generated_sso.template.ts +50 -2
  316. package/src/templates/model.template.ts +138 -2
  317. package/src/templates/service.template.ts +0 -1
  318. package/src/templates/view_form.template.ts +11 -7
  319. package/src/templates/view_list.template.ts +12 -4
  320. package/src/testing/fixture-manager.ts +229 -174
  321. package/src/types/types.ts +102 -14
  322. package/src/utils/async-utils.ts +64 -0
  323. package/src/utils/fs-utils.ts +17 -0
  324. package/src/utils/model.ts +0 -2
  325. package/src/utils/utils.ts +14 -58
  326. package/src/utils/zod-error.ts +12 -176
  327. package/tsconfig.json +2 -0
  328. package/tsup.config.js +4 -2
  329. package/.pnp.cjs +0 -14363
  330. package/.pnp.loader.mjs +0 -2047
  331. package/.vscode/extensions.json +0 -6
  332. package/.vscode/settings.json +0 -9
  333. package/.yarnrc.yml +0 -5
  334. package/src/database/base-model.abstract.ts +0 -97
  335. package/src/database/db.abstract.ts +0 -75
  336. package/src/database/drivers/knex/base-model.ts +0 -55
  337. package/src/database/drivers/knex/client.ts +0 -209
  338. package/src/database/drivers/knex/db.ts +0 -232
  339. package/src/database/drivers/knex/generator.ts +0 -659
  340. package/src/database/drivers/kysely/base-model.ts +0 -89
  341. package/src/database/drivers/kysely/client.ts +0 -309
  342. package/src/database/drivers/kysely/db.ts +0 -238
  343. package/src/database/drivers/kysely/generator.ts +0 -714
  344. package/src/database/types.ts +0 -118
  345. package/src/entity/migrator.ts +0 -1400
  346. package/src/smd/smd-manager.ts +0 -139
  347. package/src/smd/smd.ts +0 -571
  348. package/src/templates/kysely_types.template.ts +0 -205
@@ -0,0 +1,808 @@
1
+ import type { Knex } from "knex";
2
+ import type {
3
+ AvailableColumns,
4
+ ComparisonOperator,
5
+ EmptyRecord,
6
+ Expand,
7
+ ExtractColumnType,
8
+ FulltextColumns,
9
+ InsertData,
10
+ MergeJoined,
11
+ ParseSelectObject,
12
+ ResultAvailableColumns,
13
+ SelectObject,
14
+ SqlFunction,
15
+ WhereCondition,
16
+ } from "./puri.types";
17
+ import chalk from "chalk";
18
+
19
+ // 메인 Puri 클래스
20
+ export class Puri<
21
+ TSchema,
22
+ TTable extends keyof TSchema | string,
23
+ TOriginal = TTable extends keyof TSchema ? TSchema[TTable] : unknown,
24
+ TResult = TTable extends keyof TSchema ? TSchema[TTable] : unknown,
25
+ TJoined = EmptyRecord,
26
+ > {
27
+ private knexQuery: Knex.QueryBuilder;
28
+
29
+ // 생성자 시그니처들
30
+ constructor(
31
+ knex: Knex,
32
+ tableName: TTable extends keyof TSchema ? TTable : unknown
33
+ );
34
+ constructor(
35
+ knex: Knex,
36
+ subquery: Puri<TSchema, any, any, TOriginal, any>,
37
+ alias: TTable extends string ? TTable : never
38
+ );
39
+ constructor(
40
+ private knex: Knex,
41
+ tableNameOrSubquery: any,
42
+ alias?: TTable extends string ? TTable : never
43
+ ) {
44
+ if (typeof tableNameOrSubquery === "string") {
45
+ // 일반 테이블로 시작
46
+ this.knexQuery = knex(tableNameOrSubquery).from(tableNameOrSubquery);
47
+ } else {
48
+ // 서브쿼리로 시작
49
+ this.knexQuery = knex.from(tableNameOrSubquery.raw().as(alias));
50
+ }
51
+ }
52
+
53
+ // Static SQL helper functions
54
+ static count(column: string = "*"): SqlFunction<"number"> {
55
+ return {
56
+ _type: "sql_function",
57
+ _return: "number",
58
+ _sql: `COUNT(${column})`,
59
+ };
60
+ }
61
+
62
+ static sum(column: string): SqlFunction<"number"> {
63
+ return { _type: "sql_function", _return: "number", _sql: `SUM(${column})` };
64
+ }
65
+
66
+ static avg(column: string): SqlFunction<"number"> {
67
+ return { _type: "sql_function", _return: "number", _sql: `AVG(${column})` };
68
+ }
69
+
70
+ static max(column: string): SqlFunction<"number"> {
71
+ return { _type: "sql_function", _return: "number", _sql: `MAX(${column})` };
72
+ }
73
+
74
+ static min(column: string): SqlFunction<"number"> {
75
+ return { _type: "sql_function", _return: "number", _sql: `MIN(${column})` };
76
+ }
77
+
78
+ static concat(...args: string[]): SqlFunction<"string"> {
79
+ return {
80
+ _type: "sql_function",
81
+ _return: "string",
82
+ _sql: `CONCAT(${args.join(", ")})`,
83
+ };
84
+ }
85
+
86
+ static upper(column: string): SqlFunction<"string"> {
87
+ return {
88
+ _type: "sql_function",
89
+ _return: "string",
90
+ _sql: `UPPER(${column})`,
91
+ };
92
+ }
93
+
94
+ static lower(column: string): SqlFunction<"string"> {
95
+ return {
96
+ _type: "sql_function",
97
+ _return: "string",
98
+ _sql: `LOWER(${column})`,
99
+ };
100
+ }
101
+
102
+ // Raw functions
103
+ static rawString(sql: string): SqlFunction<"string"> {
104
+ return { _type: "sql_function", _return: "string", _sql: sql };
105
+ }
106
+
107
+ static rawNumber(sql: string): SqlFunction<"number"> {
108
+ return { _type: "sql_function", _return: "number", _sql: sql };
109
+ }
110
+
111
+ static rawBoolean(sql: string): SqlFunction<"boolean"> {
112
+ return { _type: "sql_function", _return: "boolean", _sql: sql };
113
+ }
114
+
115
+ static rawDate(sql: string): SqlFunction<"date"> {
116
+ return { _type: "sql_function", _return: "date", _sql: sql };
117
+ }
118
+
119
+ // Alias 기반 Select
120
+ select<TSelect extends SelectObject<TSchema, TTable, TOriginal, TJoined>>(
121
+ selectObj: TSelect
122
+ ): Puri<
123
+ TSchema,
124
+ TTable,
125
+ TOriginal,
126
+ ParseSelectObject<TSchema, TTable, TSelect, TOriginal, TJoined>,
127
+ TJoined
128
+ > {
129
+ const selectClauses: (string | Knex.Raw)[] = [];
130
+
131
+ for (const [alias, columnOrFunction] of Object.entries(selectObj)) {
132
+ if (
133
+ typeof columnOrFunction === "object" &&
134
+ columnOrFunction._type === "sql_function"
135
+ ) {
136
+ // SQL 함수인 경우
137
+ selectClauses.push(
138
+ this.knex.raw(`${columnOrFunction._sql} as ${alias}`)
139
+ );
140
+ } else {
141
+ // 일반 컬럼인 경우
142
+ const columnPath = columnOrFunction as string;
143
+ if (alias === columnPath) {
144
+ // alias와 컬럼명이 같으면 alias 생략
145
+ selectClauses.push(columnPath);
146
+ } else {
147
+ // alias 지정
148
+ selectClauses.push(`${columnPath} as ${alias}`);
149
+ }
150
+ }
151
+ }
152
+
153
+ this.knexQuery.select(selectClauses);
154
+ return this as any;
155
+ }
156
+
157
+ // 전체 선택 (편의 메서드)
158
+ selectAll(): Puri<
159
+ TSchema,
160
+ TTable,
161
+ TOriginal,
162
+ TTable extends keyof TSchema
163
+ ? TSchema[TTable] & TJoined
164
+ : TResult & TJoined,
165
+ TJoined
166
+ > {
167
+ this.knexQuery.select("*");
168
+ return this as any;
169
+ }
170
+
171
+ // Where 조건 (조인된 테이블 컬럼도 지원)
172
+ where(
173
+ conditions: WhereCondition<TSchema, TTable, TOriginal, TJoined>
174
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
175
+ where<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
176
+ column: TColumn,
177
+ value: ExtractColumnType<
178
+ TSchema,
179
+ TTable,
180
+ TColumn & string,
181
+ TOriginal,
182
+ TJoined
183
+ >
184
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
185
+ where<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
186
+ column: TColumn,
187
+ operator: ComparisonOperator | "like",
188
+ value: ExtractColumnType<
189
+ TSchema,
190
+ TTable,
191
+ TColumn & string,
192
+ TOriginal,
193
+ TJoined
194
+ >
195
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
196
+ where(
197
+ columnOrConditions: any,
198
+ operatorOrValue?: any,
199
+ value?: any
200
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
201
+ if (typeof columnOrConditions === "object") {
202
+ this.knexQuery.where(columnOrConditions);
203
+ } else if (arguments.length === 2) {
204
+ if (operatorOrValue === null) {
205
+ this.knexQuery.whereNull(columnOrConditions);
206
+ return this;
207
+ }
208
+ this.knexQuery.where(columnOrConditions, operatorOrValue);
209
+ } else if (arguments.length === 3) {
210
+ if (value === null) {
211
+ if (operatorOrValue === "!=") {
212
+ this.knexQuery.whereNotNull(columnOrConditions);
213
+ return this;
214
+ } else if (operatorOrValue === "=") {
215
+ this.knexQuery.whereNull(columnOrConditions);
216
+ return this;
217
+ }
218
+ }
219
+ this.knexQuery.where(columnOrConditions, operatorOrValue, value);
220
+ } else {
221
+ this.knexQuery.where(columnOrConditions);
222
+ }
223
+ return this;
224
+ }
225
+
226
+ // WhereIn (조인된 테이블 컬럼도 지원)
227
+ whereIn<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
228
+ column: TColumn,
229
+ values: ExtractColumnType<
230
+ TSchema,
231
+ TTable,
232
+ TColumn & string,
233
+ TOriginal,
234
+ TJoined
235
+ >[]
236
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
237
+ whereIn(
238
+ column: string,
239
+ values: any[]
240
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
241
+ this.knexQuery.whereIn(column, values);
242
+ return this;
243
+ }
244
+
245
+ whereNotIn<
246
+ TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>,
247
+ >(
248
+ column: TColumn,
249
+ values: ExtractColumnType<
250
+ TSchema,
251
+ TTable,
252
+ TColumn & string,
253
+ TOriginal,
254
+ TJoined
255
+ >[]
256
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
257
+ whereNotIn(
258
+ column: string,
259
+ values: any[]
260
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
261
+ this.knexQuery.whereNotIn(column, values);
262
+ return this;
263
+ }
264
+
265
+ whereMatch<
266
+ TColumn extends FulltextColumns<TSchema, TTable, TOriginal, TJoined>,
267
+ >(column: TColumn, value: string): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
268
+ this.knexQuery.whereRaw(`MATCH (${String(column)}) AGAINST (?)`, [value]);
269
+ return this;
270
+ }
271
+
272
+ // WhereGroup (괄호 그룹핑 지원)
273
+ whereGroup(
274
+ callback: (
275
+ group: WhereGroup<TSchema, TTable, TOriginal, TJoined>
276
+ ) => WhereGroup<TSchema, TTable, TOriginal, TJoined>
277
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
278
+ this.knexQuery.where((builder) => {
279
+ const group = new WhereGroup<TSchema, TTable, TOriginal, TJoined>(builder);
280
+ callback(group);
281
+ });
282
+ return this;
283
+ }
284
+
285
+ orWhereGroup(
286
+ callback: (
287
+ group: WhereGroup<TSchema, TTable, TOriginal, TJoined>
288
+ ) => WhereGroup<TSchema, TTable, TOriginal, TJoined>
289
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
290
+ this.knexQuery.orWhere((builder) => {
291
+ const group = new WhereGroup<TSchema, TTable, TOriginal, TJoined>(builder);
292
+ callback(group);
293
+ });
294
+ return this;
295
+ }
296
+
297
+ // Join
298
+ join<
299
+ TJoinTable extends keyof TSchema,
300
+ TLColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined & Record<TJoinTable, TSchema[TJoinTable]>>,
301
+ TRColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined & Record<TJoinTable, TSchema[TJoinTable]>>,
302
+ >(
303
+ table: TJoinTable,
304
+ left: TLColumn,
305
+ right: TRColumn,
306
+ ): Puri<
307
+ TSchema,
308
+ TTable,
309
+ TOriginal,
310
+ TResult,
311
+ MergeJoined<TJoined, Record<TJoinTable, TSchema[TJoinTable]>>
312
+ >;
313
+ join<TJoinTable extends keyof TSchema>(
314
+ table: TJoinTable,
315
+ joinCallback: (
316
+ joinClause: JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>
317
+ ) => void
318
+ ): Puri<
319
+ TSchema,
320
+ TTable,
321
+ TOriginal,
322
+ TResult,
323
+ MergeJoined<TJoined, Record<TJoinTable, TSchema[TJoinTable]>>
324
+ >;
325
+ join<TSubResult, TAlias extends string>(
326
+ subquery: Puri<TSchema, any, any, TSubResult, any>,
327
+ alias: TAlias,
328
+ left: string,
329
+ right: string
330
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined & Record<TAlias, TSubResult>>;
331
+ join(
332
+ table: string,
333
+ left: string,
334
+ right: string
335
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
336
+ join(
337
+ tableOrSubquery: string | keyof TSchema | Puri<TSchema, any, any, any, any>,
338
+ ...args: any[]
339
+ ): Puri<TSchema, TTable, TOriginal, TResult, any> {
340
+ if (tableOrSubquery instanceof Puri) {
341
+ // 서브쿼리 조인: join(subquery, alias, left, right)
342
+ const [alias, left, right] = args;
343
+ this.knexQuery.join(tableOrSubquery.raw().as(alias), left, right);
344
+ } else if (
345
+ args.length === 2 &&
346
+ typeof args[0] === "string" &&
347
+ typeof args[1] === "string"
348
+ ) {
349
+ const [left, right] = args;
350
+ this.knexQuery.join(tableOrSubquery as string, left, right);
351
+ } else if (args.length === 1 && typeof args[0] === "function") {
352
+ const joinCallback = args[0];
353
+ this.knexQuery.join(tableOrSubquery as string, (joinClause) => {
354
+ joinCallback(new JoinClauseGroup(joinClause));
355
+ });
356
+ } else {
357
+ throw new Error("Invalid arguments");
358
+ }
359
+ return this as any;
360
+ }
361
+
362
+ leftJoin<
363
+ TJoinTable extends keyof TSchema,
364
+ TLColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined & Record<TJoinTable, TSchema[TJoinTable]>>,
365
+ TRColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined & Record<TJoinTable, TSchema[TJoinTable]>>,
366
+ >(
367
+ table: TJoinTable,
368
+ left: TLColumn,
369
+ right: TRColumn,
370
+ ): Puri<
371
+ TSchema,
372
+ TTable,
373
+ TOriginal,
374
+ TResult,
375
+ TJoined & Record<TJoinTable, Partial<TSchema[TJoinTable]>>
376
+ >;
377
+ leftJoin<TSubResult, TAlias extends string>(
378
+ subquery: Puri<TSchema, any, any, TSubResult, any>,
379
+ alias: TAlias,
380
+ left: string,
381
+ right: string
382
+ ): Puri<
383
+ TSchema,
384
+ TTable,
385
+ TOriginal,
386
+ TResult,
387
+ TJoined & Record<TAlias, Partial<TSubResult>>
388
+ >;
389
+ leftJoin(
390
+ table: string,
391
+ left: string,
392
+ right: string
393
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
394
+ leftJoin(
395
+ tableOrSubquery: string | keyof TSchema | Puri<TSchema, any, any, any, any>,
396
+ ...args: any[]
397
+ ): Puri<TSchema, TTable, TOriginal, TResult, any> {
398
+ if (tableOrSubquery instanceof Puri) {
399
+ // 서브쿼리 조인: leftJoin(subquery, alias, left, right)
400
+ const [alias, left, right] = args;
401
+ this.knexQuery.leftJoin(tableOrSubquery.raw().as(alias), left, right);
402
+ } else {
403
+ const [left, right] = args;
404
+ this.knexQuery.leftJoin(tableOrSubquery as string, left, right);
405
+ }
406
+ return this as any;
407
+ }
408
+
409
+ // OrderBy
410
+ orderBy<TColumn extends ResultAvailableColumns<TSchema, TTable, TOriginal, TResult, TJoined>>(
411
+ column: TColumn,
412
+ direction: "asc" | "desc"
413
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
414
+ orderBy(
415
+ column: string,
416
+ direction: "asc" | "desc" = "asc"
417
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
418
+ this.knexQuery.orderBy(column, direction);
419
+ return this;
420
+ }
421
+
422
+ // 기본 쿼리 메서드들
423
+ limit(count: number): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
424
+ this.knexQuery.limit(count);
425
+ return this;
426
+ }
427
+
428
+ offset(count: number): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
429
+ this.knexQuery.offset(count);
430
+ return this;
431
+ }
432
+
433
+ // Group by (조인된 테이블 컬럼도 지원)
434
+ groupBy<TColumns extends ResultAvailableColumns<TSchema, TTable, TOriginal, TResult, TJoined>>(
435
+ ...columns: TColumns[]
436
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
437
+ groupBy(...columns: string[]): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
438
+ this.knexQuery.groupBy(...(columns as string[]));
439
+ return this;
440
+ }
441
+
442
+ having(condition: string): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
443
+ having<TColumn extends ResultAvailableColumns<TSchema, TTable, TOriginal, TResult, TJoined>>(
444
+ condition: TColumn,
445
+ operator: ComparisonOperator,
446
+ value: any
447
+ ): Puri<TSchema, TTable, TOriginal, TResult, TJoined>;
448
+ having(...conditions: string[]): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
449
+ this.knexQuery.having(...(conditions as [string, string, string]));
450
+ return this;
451
+ }
452
+ // 실행 메서드들 - thenable 구현
453
+ then<TResult1, TResult2 = never>(
454
+ onfulfilled?:
455
+ | ((
456
+ value: Expand<TResult>[]
457
+ ) => Expand<TResult1> | PromiseLike<Expand<TResult1>>)
458
+ | null,
459
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
460
+ ): Promise<Expand<TResult1> | TResult2> {
461
+ return this.knexQuery.then(onfulfilled as any, onrejected);
462
+ }
463
+
464
+ catch<TResult2 = never>(
465
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
466
+ ): Promise<Expand<TResult> | TResult2> {
467
+ return this.knexQuery.catch(onrejected);
468
+ }
469
+
470
+ finally(onfinally?: (() => void) | null): Promise<Expand<TResult>> {
471
+ return this.knexQuery.finally(onfinally);
472
+ }
473
+
474
+ // 안전한 실행 메서드들
475
+ async first(): Promise<Expand<TResult> | undefined> {
476
+ return this.knexQuery.first() as Promise<Expand<TResult> | undefined>;
477
+ }
478
+
479
+ async firstOrFail(): Promise<TResult> {
480
+ const result = await this.knexQuery.first();
481
+ if (!result) {
482
+ throw new Error("No results found");
483
+ }
484
+ return result as TResult;
485
+ }
486
+
487
+ async at(index: number): Promise<Expand<TResult> | undefined> {
488
+ const results = await this;
489
+ return results[index] as Expand<TResult> | undefined;
490
+ }
491
+
492
+ async assertAt(index: number): Promise<Expand<TResult>> {
493
+ const results = await this;
494
+ const result = results[index];
495
+ if (result === undefined) {
496
+ throw new Error(`No result found at index ${index}`);
497
+ }
498
+ return result;
499
+ }
500
+
501
+ // Insert/Update/Delete
502
+ // TODO(Haze, 251030): InsertData<T>에서 nullable type을 제대로 처리하지 못하는 것 같음.
503
+ async insert(
504
+ data: TTable extends keyof TSchema ? InsertData<TSchema[TTable]> : unknown
505
+ ): Promise<number[]> {
506
+ return this.knexQuery.insert(data);
507
+ }
508
+
509
+ async update(
510
+ data: Partial<TTable extends keyof TSchema ? TSchema[TTable] : unknown>
511
+ ): Promise<number> {
512
+ return this.knexQuery.update(data);
513
+ }
514
+
515
+ async delete(): Promise<number> {
516
+ return this.knexQuery.delete();
517
+ }
518
+
519
+ toQuery(): string {
520
+ return this.knexQuery.toQuery();
521
+ }
522
+
523
+ debug(): Puri<TSchema, TTable, TOriginal, TResult, TJoined> {
524
+ console.log(
525
+ `${chalk.cyan("[Puri Debug]")} ${chalk.yellow(this.formatSQL(this.toQuery()))}`
526
+ );
527
+ return this;
528
+ }
529
+
530
+ formatSQL(unformatted: string): string {
531
+ // SQL 예약어 목록
532
+ const keywords = [
533
+ "SELECT",
534
+ "FROM",
535
+ "WHERE",
536
+ "INSERT",
537
+ "INTO",
538
+ "VALUES",
539
+ "UPDATE",
540
+ "DELETE",
541
+ "CREATE",
542
+ "TABLE",
543
+ "ALTER",
544
+ "DROP",
545
+ "JOIN",
546
+ "ON",
547
+ "INNER",
548
+ "LEFT",
549
+ "RIGHT",
550
+ "FULL",
551
+ "OUTER",
552
+ "GROUP",
553
+ "BY",
554
+ "ORDER",
555
+ "HAVING",
556
+ "DISTINCT",
557
+ "LIMIT",
558
+ "OFFSET",
559
+ "AS",
560
+ "AND",
561
+ "OR",
562
+ "NOT",
563
+ "IN",
564
+ "LIKE",
565
+ "IS",
566
+ "NULL",
567
+ "CASE",
568
+ "WHEN",
569
+ "THEN",
570
+ "ELSE",
571
+ "END",
572
+ "UNION",
573
+ "ALL",
574
+ "EXISTS",
575
+ "BETWEEN",
576
+ ];
577
+
578
+ let formatted = unformatted;
579
+
580
+ // 예약어를 대문자로 변환
581
+ keywords.forEach((keyword) => {
582
+ const regex = new RegExp(`\\b${keyword}\\b`, "gi");
583
+ formatted = formatted.replace(regex, keyword.toUpperCase());
584
+ });
585
+
586
+ // 주요 절 앞에 줄바꿈 추가
587
+ const majorClauses = [
588
+ "SELECT",
589
+ "FROM",
590
+ "WHERE",
591
+ "GROUP BY",
592
+ "ORDER BY",
593
+ "HAVING",
594
+ "LIMIT",
595
+ "UNION",
596
+ ];
597
+ majorClauses.forEach((clause) => {
598
+ const regex = new RegExp(`\\s+(${clause})\\s+`, "gi");
599
+ formatted = formatted.replace(regex, `\n${clause.toUpperCase()} `);
600
+ });
601
+
602
+ // JOIN 절 처리
603
+ formatted = formatted.replace(
604
+ /\s+((?:INNER|LEFT|RIGHT|FULL OUTER)\s+)?JOIN\s+/gi,
605
+ "\n$1JOIN "
606
+ );
607
+
608
+ // AND, OR 조건 처리
609
+ formatted = formatted.replace(/\s+(AND|OR)\s+/gi, "\n $1 ");
610
+
611
+ // 괄호 처리 및 들여쓰기
612
+ const lines = formatted.split("\n");
613
+ const indentedLines = [];
614
+ let indentLevel = 0;
615
+
616
+ for (let line of lines) {
617
+ const trimmedLine = line.trim();
618
+ if (!trimmedLine) continue;
619
+
620
+ // 닫는 괄호가 있으면 들여쓰기 레벨 감소
621
+ const closingParens = (trimmedLine.match(/\)/g) || []).length;
622
+ const openingParens = (trimmedLine.match(/\(/g) || []).length;
623
+
624
+ if (closingParens > 0 && openingParens === 0) {
625
+ indentLevel = Math.max(0, indentLevel - closingParens);
626
+ }
627
+
628
+ // 현재 들여쓰기 적용
629
+ const indent = " ".repeat(indentLevel);
630
+ indentedLines.push(indent + trimmedLine);
631
+
632
+ // 여는 괄호가 있으면 들여쓰기 레벨 증가
633
+ if (openingParens > closingParens) {
634
+ indentLevel += openingParens - closingParens;
635
+ }
636
+ }
637
+
638
+ return indentedLines.join("\n").trim();
639
+ }
640
+
641
+ raw(): Knex.QueryBuilder {
642
+ return this.knexQuery;
643
+ }
644
+ }
645
+
646
+ // 11. Database 클래스
647
+ class WhereGroup<
648
+ TSchema,
649
+ TTable extends keyof TSchema | string,
650
+ TOriginal = any,
651
+ TJoined = EmptyRecord,
652
+ > {
653
+ constructor(private builder: Knex.QueryBuilder) {}
654
+
655
+ where(
656
+ conditions: WhereCondition<TSchema, TTable, TOriginal, TJoined>
657
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
658
+ where<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
659
+ column: TColumn,
660
+ value: ExtractColumnType<
661
+ TSchema,
662
+ TTable,
663
+ TColumn & string,
664
+ TOriginal,
665
+ TJoined
666
+ >
667
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
668
+ where<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
669
+ column: TColumn,
670
+ operator: ComparisonOperator | "like",
671
+ value: ExtractColumnType<
672
+ TSchema,
673
+ TTable,
674
+ TColumn & string,
675
+ TOriginal,
676
+ TJoined
677
+ >
678
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
679
+ where(raw: string): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
680
+ where(...args: any[]): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
681
+ this.builder.where(args[0], ...args.slice(1));
682
+ return this;
683
+ }
684
+
685
+ orWhere(
686
+ conditions: WhereCondition<TSchema, TTable, TOriginal, TJoined>
687
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
688
+ orWhere<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
689
+ column: TColumn,
690
+ value: ExtractColumnType<
691
+ TSchema,
692
+ TTable,
693
+ TColumn & string,
694
+ TOriginal,
695
+ TJoined
696
+ >
697
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
698
+ orWhere<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
699
+ column: TColumn,
700
+ operator: ComparisonOperator | "like",
701
+ value: ExtractColumnType<
702
+ TSchema,
703
+ TTable,
704
+ TColumn & string,
705
+ TOriginal,
706
+ TJoined
707
+ >
708
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
709
+ orWhere(raw: string): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
710
+ orWhere(...args: any[]): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
711
+ this.builder.orWhere(args[0], ...args.slice(1));
712
+ return this;
713
+ }
714
+
715
+ whereIn<TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>>(
716
+ column: TColumn,
717
+ values: ExtractColumnType<
718
+ TSchema,
719
+ TTable,
720
+ TColumn & string,
721
+ TOriginal,
722
+ TJoined
723
+ >[]
724
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
725
+ whereIn(
726
+ column: string,
727
+ values: any[]
728
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
729
+ this.builder.whereIn(column, values);
730
+ return this;
731
+ }
732
+
733
+ orWhereIn<
734
+ TColumn extends AvailableColumns<TSchema, TTable, TOriginal, TJoined>,
735
+ >(
736
+ column: TColumn,
737
+ values: ExtractColumnType<
738
+ TSchema,
739
+ TTable,
740
+ TColumn & string,
741
+ TOriginal,
742
+ TJoined
743
+ >[]
744
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined>;
745
+ orWhereIn(
746
+ column: string,
747
+ values: any[]
748
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
749
+ this.builder.orWhereIn(column, values);
750
+ return this;
751
+ }
752
+
753
+ // 중첩 그룹 지원
754
+ whereGroup(
755
+ callback: (
756
+ group: WhereGroup<TSchema, TTable, TOriginal, TJoined>
757
+ ) => WhereGroup<TSchema, TTable, TOriginal, TJoined>
758
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
759
+ this.builder.where((subBuilder) => {
760
+ const subGroup = new WhereGroup<TSchema, TTable, TOriginal, TJoined>(
761
+ subBuilder
762
+ );
763
+ callback(subGroup);
764
+ });
765
+ return this;
766
+ }
767
+
768
+ orWhereGroup(
769
+ callback: (
770
+ group: WhereGroup<TSchema, TTable, TOriginal, TJoined>
771
+ ) => WhereGroup<TSchema, TTable, TOriginal, TJoined>
772
+ ): WhereGroup<TSchema, TTable, TOriginal, TJoined> {
773
+ this.builder.orWhere((subBuilder) => {
774
+ const subGroup = new WhereGroup<TSchema, TTable, TOriginal, TJoined>(
775
+ subBuilder
776
+ );
777
+ callback(subGroup);
778
+ });
779
+ return this;
780
+ }
781
+ }
782
+
783
+ export class JoinClauseGroup<
784
+ TSchema,
785
+ TTable extends keyof TSchema | string,
786
+ TOriginal = any,
787
+ TJoined = EmptyRecord,
788
+ > {
789
+ constructor(private callback: Knex.JoinClause) {}
790
+
791
+ on(
792
+ callback: (joinClause: JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>) => void
793
+ ): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>;
794
+ on(column: string, value: any): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>;
795
+ on(...args: any[]): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined> {
796
+ this.callback.on(...(args as [string, string]));
797
+ return this;
798
+ }
799
+
800
+ orOn(
801
+ callback: (joinClause: JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>) => void
802
+ ): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>;
803
+ orOn(column: string, value: any): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined>;
804
+ orOn(...args: any[]): JoinClauseGroup<TSchema, TTable, TOriginal, TJoined> {
805
+ this.callback.orOn(...(args as [string, string]));
806
+ return this;
807
+ }
808
+ }