sonamu 0.10.12 → 0.10.13

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 (513) hide show
  1. package/dist/ai/agents/agent.d.ts +1 -1
  2. package/dist/ai/agents/agent.d.ts.map +1 -1
  3. package/dist/ai/agents/agent.js +5 -6
  4. package/dist/ai/agents/types.d.ts +1 -1
  5. package/dist/ai/agents/types.d.ts.map +1 -1
  6. package/dist/ai/providers/rtzr/api.js +18 -18
  7. package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
  8. package/dist/ai/providers/rtzr/error.js +17 -15
  9. package/dist/ai/providers/rtzr/model.d.ts.map +1 -1
  10. package/dist/ai/providers/rtzr/model.js +3 -2
  11. package/dist/ai/providers/rtzr/options.d.ts.map +1 -1
  12. package/dist/ai/providers/rtzr/options.js +16 -16
  13. package/dist/ai/providers/rtzr/provider.d.ts.map +1 -1
  14. package/dist/ai/providers/rtzr/provider.js +9 -9
  15. package/dist/ai/providers/rtzr/utils.d.ts.map +1 -1
  16. package/dist/ai/providers/rtzr/utils.js +1 -1
  17. package/dist/api/caster.d.ts.map +1 -1
  18. package/dist/api/caster.js +17 -17
  19. package/dist/api/code-converters.d.ts +4 -2
  20. package/dist/api/code-converters.d.ts.map +1 -1
  21. package/dist/api/code-converters.js +100 -19
  22. package/dist/api/config.d.ts +16 -1
  23. package/dist/api/config.d.ts.map +1 -1
  24. package/dist/api/config.js +28 -21
  25. package/dist/api/context.d.ts +2 -2
  26. package/dist/api/context.d.ts.map +1 -1
  27. package/dist/api/context.js +1 -1
  28. package/dist/api/decorators.d.ts.map +1 -1
  29. package/dist/api/decorators.js +18 -11
  30. package/dist/api/http-validator.d.ts +36 -0
  31. package/dist/api/http-validator.d.ts.map +1 -0
  32. package/dist/api/http-validator.js +72 -0
  33. package/dist/api/index.js +6 -4
  34. package/dist/api/sonamu.d.ts +59 -20
  35. package/dist/api/sonamu.d.ts.map +1 -1
  36. package/dist/api/sonamu.js +250 -106
  37. package/dist/api/websocket-helpers.d.ts +7 -5
  38. package/dist/api/websocket-helpers.d.ts.map +1 -1
  39. package/dist/api/websocket-helpers.js +6 -6
  40. package/dist/auth/audit-log/builders.d.ts.map +1 -1
  41. package/dist/auth/audit-log/builders.js +1 -1
  42. package/dist/auth/audit-log/client-ip.d.ts +1 -1
  43. package/dist/auth/audit-log/client-ip.d.ts.map +1 -1
  44. package/dist/auth/audit-log/client-ip.js +4 -2
  45. package/dist/auth/audit-log/events.d.ts +5 -1
  46. package/dist/auth/audit-log/events.d.ts.map +1 -1
  47. package/dist/auth/audit-log/events.js +1 -1
  48. package/dist/auth/audit-log/plugin.d.ts +0 -8
  49. package/dist/auth/audit-log/plugin.d.ts.map +1 -1
  50. package/dist/auth/audit-log/plugin.js +48 -52
  51. package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
  52. package/dist/auth/audit-log-ingestor.js +16 -12
  53. package/dist/auth/auth-generator.d.ts.map +1 -1
  54. package/dist/auth/auth-generator.js +1 -1
  55. package/dist/auth/better-auth-request.d.ts.map +1 -1
  56. package/dist/auth/better-auth-request.js +6 -5
  57. package/dist/auth/knex-adapter.js +1 -1
  58. package/dist/auth/plugins/entity-definitions/index.d.ts +13 -1
  59. package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
  60. package/dist/auth/plugins/entity-definitions/index.js +1 -1
  61. package/dist/auth/plugins/wrappers/admin.d.ts +1 -1
  62. package/dist/bin/build-config.d.ts +25 -0
  63. package/dist/bin/build-config.d.ts.map +1 -1
  64. package/dist/bin/build-config.js +105 -2
  65. package/dist/bin/cli.js +131 -27
  66. package/dist/bin/compiler-policy.d.ts +3 -0
  67. package/dist/bin/compiler-policy.d.ts.map +1 -0
  68. package/dist/bin/compiler-policy.js +24 -0
  69. package/dist/bin/fixture.d.ts.map +1 -1
  70. package/dist/bin/fixture.js +5 -3
  71. package/dist/bin/test-command.d.ts +22 -1
  72. package/dist/bin/test-command.d.ts.map +1 -1
  73. package/dist/bin/test-command.js +18 -10
  74. package/dist/bin/ts-loader-registration.d.ts +7 -1
  75. package/dist/bin/ts-loader-registration.d.ts.map +1 -1
  76. package/dist/bin/ts-loader-registration.js +13 -4
  77. package/dist/cache/decorator.d.ts.map +1 -1
  78. package/dist/cache/decorator.js +11 -4
  79. package/dist/compress/compress.d.ts.map +1 -1
  80. package/dist/compress/compress.js +1 -1
  81. package/dist/cone/cone-generator.d.ts +14 -1
  82. package/dist/cone/cone-generator.d.ts.map +1 -1
  83. package/dist/cone/cone-generator.js +13 -9
  84. package/dist/database/_batch_update.js +1 -1
  85. package/dist/database/base-model.d.ts +22 -10
  86. package/dist/database/base-model.d.ts.map +1 -1
  87. package/dist/database/base-model.js +26 -16
  88. package/dist/database/base-model.types.d.ts +15 -3
  89. package/dist/database/base-model.types.d.ts.map +1 -1
  90. package/dist/database/db.d.ts +12 -2
  91. package/dist/database/db.d.ts.map +1 -1
  92. package/dist/database/db.js +10 -7
  93. package/dist/database/knex.d.ts.map +1 -1
  94. package/dist/database/knex.js +17 -14
  95. package/dist/database/puri.d.ts +11 -9
  96. package/dist/database/puri.d.ts.map +1 -1
  97. package/dist/database/puri.js +93 -79
  98. package/dist/database/puri.types.d.ts +21 -21
  99. package/dist/database/puri.types.d.ts.map +1 -1
  100. package/dist/database/puri.types.js +1 -1
  101. package/dist/database/upsert-builder.d.ts +12 -10
  102. package/dist/database/upsert-builder.d.ts.map +1 -1
  103. package/dist/database/upsert-builder.js +20 -12
  104. package/dist/dict/sd.d.ts.map +1 -1
  105. package/dist/dict/sd.js +1 -1
  106. package/dist/dict/sonamu-dictionary.d.ts.map +1 -1
  107. package/dist/dict/sonamu-dictionary.js +1 -1
  108. package/dist/dict/utils.d.ts.map +1 -1
  109. package/dist/dict/utils.js +7 -2
  110. package/dist/entity/entity-manager.d.ts +13 -4
  111. package/dist/entity/entity-manager.d.ts.map +1 -1
  112. package/dist/entity/entity-manager.js +19 -11
  113. package/dist/entity/entity-template-cone.d.ts +73 -5
  114. package/dist/entity/entity-template-cone.d.ts.map +1 -1
  115. package/dist/entity/entity-template-cone.js +4 -2
  116. package/dist/entity/entity.d.ts +6 -2
  117. package/dist/entity/entity.d.ts.map +1 -1
  118. package/dist/entity/entity.js +30 -28
  119. package/dist/env.d.ts +3 -1
  120. package/dist/env.d.ts.map +1 -1
  121. package/dist/env.js +1 -1
  122. package/dist/exceptions/error-handler.d.ts.map +1 -1
  123. package/dist/exceptions/error-handler.js +7 -2
  124. package/dist/exceptions/so-exceptions.d.ts +24 -20
  125. package/dist/exceptions/so-exceptions.d.ts.map +1 -1
  126. package/dist/exceptions/so-exceptions.js +4 -2
  127. package/dist/filter/utils.d.ts +7 -2
  128. package/dist/filter/utils.d.ts.map +1 -1
  129. package/dist/filter/utils.js +8 -6
  130. package/dist/index.d.ts +3 -0
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +12 -6
  133. package/dist/logger/configure.d.ts.map +1 -1
  134. package/dist/logger/configure.js +10 -10
  135. package/dist/migration/code-generation.d.ts.map +1 -1
  136. package/dist/migration/code-generation.js +50 -46
  137. package/dist/migration/index-where-predicate.d.ts.map +1 -1
  138. package/dist/migration/index-where-predicate.js +22 -8
  139. package/dist/migration/migration-set.d.ts.map +1 -1
  140. package/dist/migration/migration-set.js +1 -1
  141. package/dist/migration/migrator.d.ts +3 -2
  142. package/dist/migration/migrator.d.ts.map +1 -1
  143. package/dist/migration/migrator.js +27 -21
  144. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  145. package/dist/migration/postgresql-schema-reader.js +51 -36
  146. package/dist/migration/slack-confirm.d.ts.map +1 -1
  147. package/dist/migration/slack-confirm.js +1 -1
  148. package/dist/naite/naite-reporter.js +2 -2
  149. package/dist/naite/naite.d.ts +74 -15
  150. package/dist/naite/naite.d.ts.map +1 -1
  151. package/dist/naite/naite.js +5 -19
  152. package/dist/runtime-type.d.ts +6 -0
  153. package/dist/runtime-type.d.ts.map +1 -0
  154. package/dist/runtime-type.js +26 -0
  155. package/dist/ssr/hmr-opt-out.d.ts +26 -0
  156. package/dist/ssr/hmr-opt-out.d.ts.map +1 -0
  157. package/dist/ssr/hmr-opt-out.js +59 -0
  158. package/dist/ssr/renderer.d.ts.map +1 -1
  159. package/dist/ssr/renderer.js +4 -1
  160. package/dist/storage/base-file.js +14 -14
  161. package/dist/storage/buffered-file.d.ts +2 -2
  162. package/dist/storage/buffered-file.d.ts.map +1 -1
  163. package/dist/storage/buffered-file.js +12 -12
  164. package/dist/storage/s3-driver.d.ts.map +1 -1
  165. package/dist/storage/s3-driver.js +1 -1
  166. package/dist/storage/uploaded-file.d.ts +2 -2
  167. package/dist/storage/uploaded-file.d.ts.map +1 -1
  168. package/dist/storage/uploaded-file.js +8 -8
  169. package/dist/stream/sse.js +11 -11
  170. package/dist/stream/ws-core.d.ts +1 -1
  171. package/dist/stream/ws-core.d.ts.map +1 -1
  172. package/dist/stream/ws-delivery.d.ts +1 -1
  173. package/dist/stream/ws-delivery.d.ts.map +1 -1
  174. package/dist/stream/ws-delivery.js +6 -4
  175. package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
  176. package/dist/stream/ws-local-connection-store.js +4 -2
  177. package/dist/stream/ws-presence-store.js +1 -1
  178. package/dist/stream/ws-registry.d.ts +4 -4
  179. package/dist/stream/ws-registry.d.ts.map +1 -1
  180. package/dist/stream/ws-registry.js +4 -2
  181. package/dist/stream/ws-telemetry.d.ts +16 -12
  182. package/dist/stream/ws-telemetry.d.ts.map +1 -1
  183. package/dist/stream/ws-telemetry.js +22 -18
  184. package/dist/stream/ws.d.ts +18 -12
  185. package/dist/stream/ws.d.ts.map +1 -1
  186. package/dist/stream/ws.js +26 -22
  187. package/dist/syncer/api-parser.js +1 -1
  188. package/dist/syncer/checksum.d.ts.map +1 -1
  189. package/dist/syncer/checksum.js +7 -8
  190. package/dist/syncer/code-generator.d.ts.map +1 -1
  191. package/dist/syncer/code-generator.js +13 -14
  192. package/dist/syncer/entity-operations.d.ts.map +1 -1
  193. package/dist/syncer/entity-operations.js +5 -6
  194. package/dist/syncer/file-copy.d.ts +5 -0
  195. package/dist/syncer/file-copy.d.ts.map +1 -0
  196. package/dist/syncer/file-copy.js +36 -0
  197. package/dist/syncer/file-patterns.d.ts +1 -0
  198. package/dist/syncer/file-patterns.d.ts.map +1 -1
  199. package/dist/syncer/file-patterns.js +5 -2
  200. package/dist/syncer/file-tracking.d.ts.map +1 -1
  201. package/dist/syncer/file-tracking.js +5 -4
  202. package/dist/syncer/filesystem-dependencies.d.ts +15 -0
  203. package/dist/syncer/filesystem-dependencies.d.ts.map +1 -0
  204. package/dist/syncer/filesystem-dependencies.js +53 -0
  205. package/dist/syncer/module-loader.d.ts +1 -1
  206. package/dist/syncer/module-loader.d.ts.map +1 -1
  207. package/dist/syncer/module-loader.js +6 -4
  208. package/dist/syncer/syncer-actions.d.ts +4 -0
  209. package/dist/syncer/syncer-actions.d.ts.map +1 -1
  210. package/dist/syncer/syncer-actions.js +34 -17
  211. package/dist/syncer/syncer.d.ts +17 -6
  212. package/dist/syncer/syncer.d.ts.map +1 -1
  213. package/dist/syncer/syncer.js +71 -29
  214. package/dist/syncer/watcher.d.ts.map +1 -1
  215. package/dist/syncer/watcher.js +3 -3
  216. package/dist/tasks/decorator.d.ts +6 -6
  217. package/dist/tasks/decorator.d.ts.map +1 -1
  218. package/dist/tasks/decorator.js +4 -4
  219. package/dist/tasks/step-wrapper.d.ts +6 -4
  220. package/dist/tasks/step-wrapper.d.ts.map +1 -1
  221. package/dist/tasks/step-wrapper.js +5 -5
  222. package/dist/tasks/workflow-manager.d.ts.map +1 -1
  223. package/dist/tasks/workflow-manager.js +13 -7
  224. package/dist/template/entity-converter.d.ts +1 -1
  225. package/dist/template/entity-converter.d.ts.map +1 -1
  226. package/dist/template/entity-converter.js +2 -2
  227. package/dist/template/helpers.d.ts +8 -6
  228. package/dist/template/helpers.d.ts.map +1 -1
  229. package/dist/template/helpers.js +1 -1
  230. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  231. package/dist/template/implementations/generated.template.js +4 -4
  232. package/dist/template/implementations/generated_http.template.d.ts +7 -4
  233. package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
  234. package/dist/template/implementations/generated_http.template.js +21 -21
  235. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  236. package/dist/template/implementations/generated_sso.template.js +6 -3
  237. package/dist/template/implementations/http_validators.template.d.ts +18 -0
  238. package/dist/template/implementations/http_validators.template.d.ts.map +1 -0
  239. package/dist/template/implementations/http_validators.template.js +127 -0
  240. package/dist/template/implementations/model.template.d.ts.map +1 -1
  241. package/dist/template/implementations/model.template.js +1 -1
  242. package/dist/template/implementations/queries.template.js +1 -1
  243. package/dist/template/implementations/sd.template.d.ts.map +1 -1
  244. package/dist/template/implementations/sd.template.js +79 -39
  245. package/dist/template/implementations/services.template.d.ts.map +1 -1
  246. package/dist/template/implementations/services.template.js +6 -4
  247. package/dist/template/implementations/view_form.template.d.ts +6 -1
  248. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  249. package/dist/template/implementations/view_form.template.js +23 -15
  250. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  251. package/dist/template/implementations/view_list.template.js +11 -6
  252. package/dist/template/template-manager.d.ts.map +1 -1
  253. package/dist/template/template-manager.js +9 -7
  254. package/dist/template/template-types.d.ts +12 -5
  255. package/dist/template/template-types.d.ts.map +1 -1
  256. package/dist/template/template-types.js +1 -1
  257. package/dist/template/template.d.ts +2 -2
  258. package/dist/template/template.d.ts.map +1 -1
  259. package/dist/template/template.js +6 -4
  260. package/dist/template/zod-converter.d.ts +0 -19
  261. package/dist/template/zod-converter.d.ts.map +1 -1
  262. package/dist/template/zod-converter.js +100 -84
  263. package/dist/testing/_relation-graph.d.ts.map +1 -1
  264. package/dist/testing/_relation-graph.js +2 -1
  265. package/dist/testing/bootstrap.d.ts.map +1 -1
  266. package/dist/testing/bootstrap.js +9 -6
  267. package/dist/testing/data-explorer.d.ts +32 -7
  268. package/dist/testing/data-explorer.d.ts.map +1 -1
  269. package/dist/testing/data-explorer.js +77 -31
  270. package/dist/testing/dev-test-routes.d.ts +8 -1
  271. package/dist/testing/dev-test-routes.d.ts.map +1 -1
  272. package/dist/testing/dev-test-routes.js +125 -108
  273. package/dist/testing/dev-vitest-manager.d.ts +54 -8
  274. package/dist/testing/dev-vitest-manager.d.ts.map +1 -1
  275. package/dist/testing/dev-vitest-manager.js +24 -9
  276. package/dist/testing/faker-mappings.d.ts +412 -1
  277. package/dist/testing/faker-mappings.d.ts.map +1 -1
  278. package/dist/testing/faker-mappings.js +1 -1
  279. package/dist/testing/filesystem.d.ts +2 -0
  280. package/dist/testing/filesystem.d.ts.map +1 -0
  281. package/dist/testing/filesystem.js +7 -0
  282. package/dist/testing/fixture-generator.d.ts +25 -8
  283. package/dist/testing/fixture-generator.d.ts.map +1 -1
  284. package/dist/testing/fixture-generator.js +132 -73
  285. package/dist/testing/fixture-loader.d.ts +3 -1
  286. package/dist/testing/fixture-loader.d.ts.map +1 -1
  287. package/dist/testing/fixture-loader.js +1 -20
  288. package/dist/testing/fixture-manager.d.ts +8 -7
  289. package/dist/testing/fixture-manager.d.ts.map +1 -1
  290. package/dist/testing/fixture-manager.js +91 -59
  291. package/dist/testing/global-setup.d.ts.map +1 -1
  292. package/dist/testing/global-setup.js +5 -2
  293. package/dist/testing/index.d.ts +2 -0
  294. package/dist/testing/index.d.ts.map +1 -1
  295. package/dist/testing/index.js +6 -3
  296. package/dist/types/types.d.ts +101 -99
  297. package/dist/types/types.d.ts.map +1 -1
  298. package/dist/types/types.js +234 -225
  299. package/dist/ui/ai-api.d.ts.map +1 -1
  300. package/dist/ui/ai-api.js +1 -1
  301. package/dist/ui/ai-client.d.ts.map +1 -1
  302. package/dist/ui/ai-client.js +28 -18
  303. package/dist/ui/api.d.ts.map +1 -1
  304. package/dist/ui/api.js +135 -128
  305. package/dist/ui/async-route.d.ts +3 -0
  306. package/dist/ui/async-route.d.ts.map +1 -0
  307. package/dist/ui/async-route.js +12 -0
  308. package/dist/ui/cdd-service.d.ts +9 -7
  309. package/dist/ui/cdd-service.d.ts.map +1 -1
  310. package/dist/ui/cdd-service.js +7 -5
  311. package/dist/ui/migrations-api.d.ts.map +1 -1
  312. package/dist/ui/migrations-api.js +17 -15
  313. package/dist/ui-web/assets/index-DV1AAq6W.js +178 -0
  314. package/dist/ui-web/index.html +1 -1
  315. package/dist/utils/formatter.d.ts.map +1 -1
  316. package/dist/utils/formatter.js +7 -5
  317. package/dist/utils/fs-utils.d.ts +3 -3
  318. package/dist/utils/fs-utils.d.ts.map +1 -1
  319. package/dist/utils/fs-utils.js +6 -6
  320. package/dist/utils/model.d.ts +3 -5
  321. package/dist/utils/model.d.ts.map +1 -1
  322. package/dist/utils/model.js +1 -1
  323. package/dist/utils/object-utils.d.ts +1 -1
  324. package/dist/utils/object-utils.d.ts.map +1 -1
  325. package/dist/utils/object-utils.js +17 -16
  326. package/dist/utils/process-utils.d.ts.map +1 -1
  327. package/dist/utils/process-utils.js +4 -2
  328. package/dist/utils/runtime-value.d.ts +13 -0
  329. package/dist/utils/runtime-value.d.ts.map +1 -0
  330. package/dist/utils/runtime-value.js +40 -0
  331. package/dist/utils/sql-parser.d.ts +8 -1
  332. package/dist/utils/sql-parser.d.ts.map +1 -1
  333. package/dist/utils/sql-parser.js +6 -3
  334. package/dist/utils/type-utils.d.ts.map +1 -1
  335. package/dist/utils/type-utils.js +10 -8
  336. package/dist/utils/utils.d.ts +2 -2
  337. package/dist/utils/utils.d.ts.map +1 -1
  338. package/dist/utils/utils.js +11 -8
  339. package/dist/utils/zod-error.d.ts.map +1 -1
  340. package/dist/utils/zod-error.js +7 -4
  341. package/dist/vector/chunking.d.ts.map +1 -1
  342. package/dist/vector/chunking.js +1 -1
  343. package/dist/vector/embedding.d.ts.map +1 -1
  344. package/dist/vector/embedding.js +1 -1
  345. package/dist/vector/types.d.ts +3 -2
  346. package/dist/vector/types.d.ts.map +1 -1
  347. package/package.json +5 -3
  348. package/src/ai/agents/agent.ts +5 -5
  349. package/src/ai/agents/types.ts +1 -1
  350. package/src/ai/providers/rtzr/__tests__/model.test.ts +2 -2
  351. package/src/ai/providers/rtzr/error.ts +5 -3
  352. package/src/ai/providers/rtzr/model.ts +3 -2
  353. package/src/ai/providers/rtzr/options.ts +10 -5
  354. package/src/ai/providers/rtzr/provider.ts +10 -10
  355. package/src/ai/providers/rtzr/utils.ts +3 -1
  356. package/src/api/__tests__/config.test.ts +11 -21
  357. package/src/api/__tests__/sonamu.http-validator.test.ts +512 -0
  358. package/src/api/__tests__/sonamu.websocket.test.ts +51 -41
  359. package/src/api/__tests__/zod-compiler-policy.test.ts +95 -0
  360. package/src/api/caster.ts +6 -8
  361. package/src/api/code-converters.ts +136 -36
  362. package/src/api/config.ts +62 -4
  363. package/src/api/context.ts +3 -3
  364. package/src/api/decorators.ts +28 -17
  365. package/src/api/http-validator.ts +149 -0
  366. package/src/api/sonamu.ts +459 -152
  367. package/src/api/websocket-helpers.ts +24 -17
  368. package/src/auth/audit-log/builders.ts +3 -2
  369. package/src/auth/audit-log/client-ip.ts +4 -2
  370. package/src/auth/audit-log/events.ts +15 -1
  371. package/src/auth/audit-log/plugin.ts +129 -200
  372. package/src/auth/audit-log-ingestor.ts +19 -15
  373. package/src/auth/auth-generator.ts +10 -5
  374. package/src/auth/better-auth-request.ts +4 -3
  375. package/src/auth/knex-adapter.ts +8 -2
  376. package/src/auth/plugins/entity-definitions/index.ts +6 -5
  377. package/src/bin/__tests__/http-validator-benchmark.test.ts +94 -0
  378. package/src/bin/__tests__/test-command.test.ts +9 -15
  379. package/src/bin/__tests__/ts-loader-register.test.ts +15 -17
  380. package/src/bin/__tests__/zod-compiler-build.test.ts +307 -0
  381. package/src/bin/build-config.ts +167 -0
  382. package/src/bin/cli.ts +153 -36
  383. package/src/bin/compiler-policy.ts +35 -0
  384. package/src/bin/fixture.ts +14 -3
  385. package/src/bin/test-command.ts +74 -29
  386. package/src/bin/ts-loader-registration.ts +22 -4
  387. package/src/cache/decorator.ts +10 -3
  388. package/src/compress/compress.ts +17 -4
  389. package/src/cone/cone-generator.ts +34 -6
  390. package/src/database/__tests__/db.test.ts +1 -0
  391. package/src/database/__tests__/puri.test.ts +16 -15
  392. package/src/database/__tests__/transaction-scope.test.ts +5 -0
  393. package/src/database/_batch_update.ts +1 -0
  394. package/src/database/base-model.ts +81 -50
  395. package/src/database/base-model.types.ts +6 -6
  396. package/src/database/db.ts +29 -19
  397. package/src/database/knex.ts +30 -17
  398. package/src/database/puri-subset.test-d.ts +17 -0
  399. package/src/database/puri.ts +181 -143
  400. package/src/database/puri.types.test-d.ts +31 -1
  401. package/src/database/puri.types.ts +23 -21
  402. package/src/database/upsert-builder.ts +75 -53
  403. package/src/dict/sd.ts +15 -5
  404. package/src/dict/sonamu-dictionary.ts +6 -6
  405. package/src/dict/utils.ts +9 -1
  406. package/src/entity/entity-manager.ts +26 -9
  407. package/src/entity/entity-template-cone.ts +5 -10
  408. package/src/entity/entity.ts +50 -34
  409. package/src/env.ts +7 -3
  410. package/src/exceptions/error-handler.ts +17 -2
  411. package/src/exceptions/so-exceptions.ts +26 -20
  412. package/src/filter/utils.ts +21 -11
  413. package/src/index.ts +3 -0
  414. package/src/logger/configure.ts +26 -18
  415. package/src/migration/__tests__/migrator.test.ts +50 -57
  416. package/src/migration/code-generation.ts +132 -104
  417. package/src/migration/index-where-predicate.ts +51 -24
  418. package/src/migration/migration-set.ts +11 -5
  419. package/src/migration/migrator.ts +68 -41
  420. package/src/migration/postgresql-schema-reader.ts +52 -47
  421. package/src/migration/slack-confirm.ts +17 -15
  422. package/src/naite/naite-reporter.ts +1 -1
  423. package/src/naite/naite.ts +97 -24
  424. package/src/runtime-type.ts +34 -0
  425. package/src/ssr/hmr-opt-out.ts +78 -0
  426. package/src/ssr/renderer.ts +6 -0
  427. package/src/storage/base-file.ts +13 -13
  428. package/src/storage/buffered-file.ts +11 -11
  429. package/src/storage/s3-driver.ts +4 -1
  430. package/src/storage/uploaded-file.ts +8 -8
  431. package/src/stream/__tests__/ws-contracts.test.ts +4 -2
  432. package/src/stream/__tests__/ws-telemetry.test.ts +1 -1
  433. package/src/stream/__tests__/ws.test.ts +30 -6
  434. package/src/stream/sse.ts +12 -11
  435. package/src/stream/ws-core.ts +1 -1
  436. package/src/stream/ws-delivery.ts +16 -7
  437. package/src/stream/ws-local-connection-store.ts +2 -3
  438. package/src/stream/ws-presence-store.ts +6 -1
  439. package/src/stream/ws-registry.ts +16 -7
  440. package/src/stream/ws-telemetry.ts +66 -43
  441. package/src/stream/ws.ts +73 -39
  442. package/src/syncer/__tests__/api-parser.websocket-type-ref.test.ts +3 -2
  443. package/src/syncer/__tests__/http-validator-registry.test.ts +137 -0
  444. package/src/syncer/api-parser.ts +67 -27
  445. package/src/syncer/checksum.ts +17 -11
  446. package/src/syncer/code-generator.ts +25 -17
  447. package/src/syncer/entity-operations.ts +3 -5
  448. package/src/syncer/file-copy.ts +46 -0
  449. package/src/syncer/file-patterns.ts +11 -8
  450. package/src/syncer/file-tracking.ts +3 -4
  451. package/src/syncer/filesystem-dependencies.ts +73 -0
  452. package/src/syncer/module-loader.ts +8 -4
  453. package/src/syncer/syncer-actions.ts +56 -18
  454. package/src/syncer/syncer.ts +116 -43
  455. package/src/syncer/watcher.ts +8 -4
  456. package/src/tasks/decorator.ts +20 -12
  457. package/src/tasks/step-wrapper.ts +19 -9
  458. package/src/tasks/workflow-manager.ts +15 -9
  459. package/src/template/__tests__/http_validators.template.test.ts +160 -0
  460. package/src/template/__tests__/services.template.mutation.test.ts +24 -23
  461. package/src/template/__tests__/services.template.websocket.test.ts +21 -25
  462. package/src/template/entity-converter.ts +5 -2
  463. package/src/template/helpers.ts +8 -9
  464. package/src/template/implementations/generated.template.ts +5 -4
  465. package/src/template/implementations/generated_http.template.ts +61 -15
  466. package/src/template/implementations/generated_sso.template.ts +15 -4
  467. package/src/template/implementations/http_validators.template.ts +175 -0
  468. package/src/template/implementations/model.template.ts +3 -1
  469. package/src/template/implementations/sd.template.ts +81 -41
  470. package/src/template/implementations/services.template.ts +2 -2
  471. package/src/template/implementations/view_form.template.ts +56 -39
  472. package/src/template/implementations/view_list.template.ts +31 -23
  473. package/src/template/template-manager.ts +15 -13
  474. package/src/template/template-types.ts +13 -5
  475. package/src/template/template.ts +4 -3
  476. package/src/template/zod-converter.ts +206 -91
  477. package/src/testing/__tests__/dev-test-routes.test.ts +85 -25
  478. package/src/testing/__tests__/dev-vitest-manager.test.ts +15 -15
  479. package/src/testing/_relation-graph.ts +4 -1
  480. package/src/testing/bootstrap.ts +12 -5
  481. package/src/testing/data-explorer.ts +102 -53
  482. package/src/testing/dev-test-routes.ts +87 -50
  483. package/src/testing/dev-vitest-manager.ts +88 -32
  484. package/src/testing/faker-mappings.ts +2 -2
  485. package/src/testing/filesystem.ts +1 -0
  486. package/src/testing/fixture-generator.ts +191 -131
  487. package/src/testing/fixture-loader.ts +5 -1
  488. package/src/testing/fixture-manager.ts +129 -90
  489. package/src/testing/global-setup.ts +7 -3
  490. package/src/testing/index.ts +5 -0
  491. package/src/types/__tests__/entity-json-schema-search-text.test.ts +9 -3
  492. package/src/types/types.ts +184 -158
  493. package/src/ui/ai-api.ts +9 -7
  494. package/src/ui/ai-client.ts +61 -56
  495. package/src/ui/api.ts +727 -616
  496. package/src/ui/async-route.ts +4 -0
  497. package/src/ui/cdd-service.ts +35 -12
  498. package/src/ui/migrations-api.ts +63 -51
  499. package/src/utils/formatter.ts +7 -3
  500. package/src/utils/fs-utils.ts +5 -5
  501. package/src/utils/model.ts +4 -5
  502. package/src/utils/object-utils.ts +32 -22
  503. package/src/utils/process-utils.ts +4 -1
  504. package/src/utils/runtime-value.ts +43 -0
  505. package/src/utils/sql-parser.ts +13 -6
  506. package/src/utils/type-utils.ts +9 -7
  507. package/src/utils/utils.ts +19 -21
  508. package/src/utils/zod-error.ts +4 -2
  509. package/src/vector/chunking.ts +6 -1
  510. package/src/vector/embedding.ts +2 -1
  511. package/src/vector/types.ts +4 -2
  512. package/tsdown.api.config.ts +5 -2
  513. package/dist/ui-web/assets/index-C0m1pk6K.js +0 -177
@@ -9,6 +9,12 @@ import { type Knex } from "knex";
9
9
  import { EntityManager } from "../entity/entity-manager";
10
10
  import { type TableSpec } from "../entity/entity-manager";
11
11
  import { Naite } from "../naite/naite";
12
+ import {
13
+ isFunctionValue,
14
+ isNumberValue,
15
+ isObjectValue,
16
+ isStringValue,
17
+ } from "../utils/runtime-value";
12
18
  import { type ClearStatements } from "./puri-subset.types";
13
19
  import {
14
20
  type AvailableColumns,
@@ -30,6 +36,7 @@ import {
30
36
  type ResultAvailableColumns,
31
37
  type SelectAllResult,
32
38
  type SelectObject,
39
+ type SelectValue,
33
40
  type SingleTableValue,
34
41
  type SqlExpression,
35
42
  type TsHighlightOptions,
@@ -79,7 +86,7 @@ type JoinRegistration = {
79
86
 
80
87
  type CorrelationRegistration = FromRegistration | JoinRegistration;
81
88
 
82
- function normalizeFuzzyOperator(operator?: string): FuzzyOperator {
89
+ export function parseFuzzyOperator(operator?: string): FuzzyOperator {
83
90
  const normalized = operator?.trim() ?? "<%";
84
91
  const fuzzyOperator = FUZZY_OPERATORS.find((candidate) => candidate === normalized);
85
92
 
@@ -90,7 +97,7 @@ function normalizeFuzzyOperator(operator?: string): FuzzyOperator {
90
97
  return fuzzyOperator;
91
98
  }
92
99
 
93
- function serializeJsonSupersetValue(value: unknown): string {
100
+ function serializeJsonSupersetValue<Value>(value: Value): string {
94
101
  const serialized = JSON.stringify(value);
95
102
 
96
103
  if (serialized === undefined) {
@@ -102,11 +109,11 @@ function serializeJsonSupersetValue(value: unknown): string {
102
109
  return serialized;
103
110
  }
104
111
 
105
- function serializeJsonColumnValue(
112
+ function serializeJsonColumnValue<Value>(
106
113
  tableSpec: TableSpec | null,
107
114
  column: string,
108
- value: unknown,
109
- ): unknown {
115
+ value: Value,
116
+ ): Value | string {
110
117
  if (!tableSpec?.jsonColumns.includes(column) || value === undefined || value === null) {
111
118
  return value;
112
119
  }
@@ -142,15 +149,23 @@ function formatNullsSuffix(nulls?: PuriOrderByNulls): string {
142
149
  return ` NULLS ${nulls.toUpperCase()}`;
143
150
  }
144
151
 
145
- function isOrderByEntries(value: unknown): value is readonly PuriOrderByRuntimeEntry[] {
152
+ function isOrderByEntries<Value>(
153
+ value: Value,
154
+ ): value is Value & readonly PuriOrderByRuntimeEntry[] {
146
155
  return Array.isArray(value);
147
156
  }
148
157
 
149
- function isSqlExpression(value: PuriOrderByRuntimeEntry): value is PuriOrderByExpression {
150
- return typeof value === "object" && "_type" in value && value._type === "sql_expression";
158
+ function isSqlExpression<Value>(value: Value): value is Value & PuriOrderByExpression {
159
+ return isObjectValue(value) && "_type" in value && value["_type"] === "sql_expression";
160
+ }
161
+
162
+ function isNestedSelectObject<TTables extends object>(
163
+ value: SelectValue<TTables> | SelectObject<TTables>,
164
+ ): value is SelectObject<TTables> {
165
+ return isObjectValue(value) && !("_type" in value);
151
166
  }
152
167
 
153
- export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
168
+ export class Puri<TSchema, TTables extends object, TResult> {
154
169
  private knexQuery: Knex.QueryBuilder;
155
170
  private tableSpec: TableSpec | null = null;
156
171
  private correlationRegistry = new Map<string, CorrelationRegistration>();
@@ -162,7 +177,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
162
177
  public knex: Knex,
163
178
  tableNameOrSource: any,
164
179
  ) {
165
- if (typeof tableNameOrSource === "string") {
180
+ if (isStringValue(tableNameOrSource)) {
166
181
  // Case: new Puri(knex, "users")
167
182
  this.knexQuery = this.knex(tableNameOrSource).from(tableNameOrSource);
168
183
  this.tableSpec = this.safeGetTableSpec(tableNameOrSource);
@@ -171,14 +186,14 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
171
186
  alias: tableNameOrSource,
172
187
  table: tableNameOrSource,
173
188
  });
174
- } else if (typeof tableNameOrSource === "object") {
189
+ } else if (isObjectValue(tableNameOrSource)) {
175
190
  const entries = Object.entries(tableNameOrSource);
176
191
  if (entries.length !== 1) {
177
192
  throw new Error("Table spec must have exactly one entry");
178
193
  }
179
194
  assert(entries[0]);
180
195
  const [alias, source] = entries[0];
181
- if (typeof source === "string") {
196
+ if (isStringValue(source)) {
182
197
  this.knexQuery = this.knex(source).from({ [alias]: source });
183
198
  this.tableSpec = this.safeGetTableSpec(source);
184
199
  this.correlationRegistry.set(alias, { kind: "from", alias, table: source });
@@ -272,7 +287,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
272
287
  column: string | SqlExpression<"string">,
273
288
  query: string,
274
289
  ): SqlExpression<"number"> {
275
- if (typeof column === "string") {
290
+ if (isStringValue(column)) {
276
291
  return {
277
292
  _type: "sql_expression",
278
293
  _return: "number",
@@ -284,8 +299,8 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
284
299
  return {
285
300
  _type: "sql_expression",
286
301
  _return: "number",
287
- _sql: `word_similarity(?, ${column._sql})`,
288
- _params: [query, ...column._params],
302
+ _sql: `word_similarity(?, ${column["_sql"]})`,
303
+ _params: [query, ...column["_params"]],
289
304
  };
290
305
  }
291
306
 
@@ -293,7 +308,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
293
308
  column: string | SqlExpression<"string">,
294
309
  query: string,
295
310
  ): SqlExpression<"number"> {
296
- if (typeof column === "string") {
311
+ if (isStringValue(column)) {
297
312
  return {
298
313
  _type: "sql_expression",
299
314
  _return: "number",
@@ -305,8 +320,8 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
305
320
  return {
306
321
  _type: "sql_expression",
307
322
  _return: "number",
308
- _sql: `similarity(${column._sql}, ?)`,
309
- _params: [...column._params, query],
323
+ _sql: `similarity(${column["_sql"]}, ?)`,
324
+ _params: [...column["_params"], query],
310
325
  };
311
326
  }
312
327
 
@@ -314,7 +329,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
314
329
  column: string | SqlExpression<"string">,
315
330
  query: string,
316
331
  ): SqlExpression<"number"> {
317
- if (typeof column === "string") {
332
+ if (isStringValue(column)) {
318
333
  return {
319
334
  _type: "sql_expression",
320
335
  _return: "number",
@@ -326,8 +341,8 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
326
341
  return {
327
342
  _type: "sql_expression",
328
343
  _return: "number",
329
- _sql: `strict_word_similarity(?, ${column._sql})`,
330
- _params: [query, ...column._params],
344
+ _sql: `strict_word_similarity(?, ${column["_sql"]})`,
345
+ _params: [query, ...column["_params"]],
331
346
  };
332
347
  }
333
348
 
@@ -392,7 +407,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
392
407
  query: string,
393
408
  options?: TsRankOptions,
394
409
  ): SqlExpression<"number"> {
395
- return Puri._tsRank("ts_rank", column, query, options);
410
+ return Puri["_tsRank"]("ts_rank", column, query, options);
396
411
  }
397
412
 
398
413
  // ts_rank_cd
@@ -401,7 +416,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
401
416
  query: string,
402
417
  options?: TsRankOptions,
403
418
  ): SqlExpression<"number"> {
404
- return Puri._tsRank("ts_rank_cd", column, query, options);
419
+ return Puri["_tsRank"]("ts_rank_cd", column, query, options);
405
420
  }
406
421
 
407
422
  static toTsVector(column: string, config: string = "simple"): SqlExpression<"tsvector"> {
@@ -434,12 +449,12 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
434
449
  params.push(...weights);
435
450
  }
436
451
 
437
- if (typeof column === "string") {
452
+ if (isStringValue(column)) {
438
453
  sqlTemplate += `??, ${parser}(?, ?)`;
439
454
  params.push(column, config, query);
440
455
  } else {
441
- sqlTemplate += `${column._sql}, ${parser}(?, ?)`;
442
- params.push(...column._params, config, query);
456
+ sqlTemplate += `${column["_sql"]}, ${parser}(?, ?)`;
457
+ params.push(...column["_params"], config, query);
443
458
  }
444
459
 
445
460
  if (normalization) {
@@ -483,7 +498,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
483
498
  const queryClause = `ARRAY[${queryArr.map(() => "?").join(", ")}]`;
484
499
 
485
500
  // 단일 컬럼인 경우
486
- if (typeof columnOrColumns === "string") {
501
+ if (isStringValue(columnOrColumns)) {
487
502
  return Puri.rawString(`pgroonga_highlight_html(??, ${queryClause})`, [
488
503
  columnOrColumns,
489
504
  ...queryArr,
@@ -507,13 +522,14 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
507
522
  const selectClauses: (string | Knex.Raw)[] = [];
508
523
 
509
524
  for (const [alias, columnOrFunction] of Object.entries(flatSelect)) {
510
- if (typeof columnOrFunction === "object" && columnOrFunction._type === "sql_expression") {
525
+ if (isSqlExpression(columnOrFunction)) {
511
526
  // SQL 함수인 경우
512
527
  selectClauses.push(
513
- this.knex.raw(`${columnOrFunction._sql} AS "${alias}"`, columnOrFunction._params),
528
+ this.knex.raw(`${columnOrFunction["_sql"]} AS "${alias}"`, columnOrFunction["_params"]),
514
529
  );
515
530
  } else {
516
531
  // 일반 컬럼인 경우
532
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
517
533
  const columnPath = columnOrFunction as string;
518
534
  if (alias === columnPath) {
519
535
  // alias와 컬럼명이 같으면 alias 생략
@@ -526,6 +542,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
526
542
  }
527
543
 
528
544
  this.knexQuery.select(selectClauses);
545
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
529
546
  return this as any;
530
547
  }
531
548
 
@@ -534,23 +551,23 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
534
551
  * 예: { parent: { id: "parent.id", name: "parent.name" } }
535
552
  * → { parent__id: "parent.id", parent__name: "parent.name" }
536
553
  */
537
- private flattenSelect(selectObj: Record<string, any>, prefix = ""): Record<string, any> {
538
- const flatSelect: Record<string, any> = {};
539
-
554
+ private *flattenSelectEntries(
555
+ selectObj: SelectObject<TTables>,
556
+ prefix = "",
557
+ ): Generator<[string, SelectValue<TTables>], void> {
540
558
  for (const [key, value] of Object.entries(selectObj)) {
541
559
  const fullKey = prefix ? `${prefix}__${key}` : key;
542
560
 
543
- if (typeof value === "object" && value !== null && !("_type" in value)) {
544
- // 중첩 객체인 경우 - 재귀 처리
545
- const nested = this.flattenSelect(value, fullKey);
546
- Object.assign(flatSelect, nested);
561
+ if (isNestedSelectObject(value)) {
562
+ yield* this.flattenSelectEntries(value, fullKey);
547
563
  } else {
548
- // 일반 값인 경우 (컬럼 경로 또는 SqlExpression)
549
- flatSelect[fullKey] = value;
564
+ yield [fullKey, value];
550
565
  }
551
566
  }
567
+ }
552
568
 
553
- return flatSelect;
569
+ private flattenSelect(selectObj: SelectObject<TTables>, prefix = "") {
570
+ return Object.fromEntries(this.flattenSelectEntries(selectObj, prefix));
554
571
  }
555
572
 
556
573
  // SELECT (select는 overwrite, appendSelect는 append)
@@ -563,11 +580,12 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
563
580
  const selectClauses: (string | Knex.Raw)[] = [];
564
581
 
565
582
  for (const [alias, columnOrFunction] of Object.entries(flatSelect)) {
566
- if (typeof columnOrFunction === "object" && columnOrFunction._type === "sql_expression") {
583
+ if (isSqlExpression(columnOrFunction)) {
567
584
  selectClauses.push(
568
- this.knex.raw(`${columnOrFunction._sql} AS ${alias}`, columnOrFunction._params),
585
+ this.knex.raw(`${columnOrFunction["_sql"]} AS ${alias}`, columnOrFunction["_params"]),
569
586
  );
570
587
  } else {
588
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
571
589
  const columnPath = columnOrFunction as string;
572
590
  if (alias === columnPath) {
573
591
  selectClauses.push(columnPath);
@@ -578,12 +596,14 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
578
596
  }
579
597
 
580
598
  this.knexQuery.select(selectClauses);
599
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
581
600
  return this as any;
582
601
  }
583
602
 
584
603
  // SELECT *
585
604
  selectAll(): Puri<TSchema, TTables, SelectAllResult<TTables>> {
586
605
  this.knexQuery.select("*");
606
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
587
607
  return this as any;
588
608
  }
589
609
 
@@ -606,15 +626,18 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
606
626
  // knex에 없어서 직접 구현함
607
627
  clearJoin(alias: string): this {
608
628
  let removed = false;
609
- (this.knexQuery as any)._statements = (this.knexQuery as any)._statements.filter((s: any) => {
610
- if ("joinType" in s) {
611
- const shouldRemove = this.getKnexJoinAlias(s.table) === alias;
612
- removed ||= shouldRemove;
613
- return !shouldRemove;
614
- } else {
615
- return true;
616
- }
617
- });
629
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
630
+ (this.knexQuery as any)["_statements"] = (this.knexQuery as any)["_statements"].filter(
631
+ (s: any) => {
632
+ if ("joinType" in s) {
633
+ const shouldRemove = this.getKnexJoinAlias(s.table) === alias;
634
+ removed ||= shouldRemove;
635
+ return !shouldRemove;
636
+ } else {
637
+ return true;
638
+ }
639
+ },
640
+ );
618
641
  if (removed && this.correlationRegistry.get(alias)?.kind === "join") {
619
642
  this.correlationRegistry.delete(alias);
620
643
  }
@@ -668,7 +691,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
668
691
  ): Puri<TSchema, TTables & Record<TJoinTable, TSchema[TJoinTable]>, TResult>;
669
692
  // JOIN 실제 구현
670
693
  join(tableNameOrSpec: any, ...args: any[]): any {
671
- return this.__commonJoin("join", tableNameOrSpec, ...args);
694
+ return this["__commonJoin"]("join", tableNameOrSpec, ...args);
672
695
  }
673
696
 
674
697
  // ENSURE JOIN: 테이블 + Alias
@@ -684,7 +707,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
684
707
  right: `${TJoinTable & string}.${ColumnKeys<TSchema[TJoinTable]>}`,
685
708
  ): Puri<TSchema, TTables & Record<TJoinTable, TSchema[TJoinTable]>, TResult>;
686
709
  ensureJoin(tableNameOrSpec: any, left: string, right: string): any {
687
- return this.__ensureJoin("join", tableNameOrSpec, left, right);
710
+ return this["__ensureJoin"]("join", tableNameOrSpec, left, right);
688
711
  }
689
712
 
690
713
  // LEFT JOIN: 서브쿼리 + Alias
@@ -735,7 +758,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
735
758
  ): Puri<TSchema, TTables & Record<TJoinTable, TSchema[TJoinTable] & LeftJoinedMarker>, TResult>;
736
759
  // LEFT JOIN 실제 구현
737
760
  leftJoin(tableNameOrSpec: any, ...args: any[]): any {
738
- return this.__commonJoin("leftJoin", tableNameOrSpec, ...args);
761
+ return this["__commonJoin"]("leftJoin", tableNameOrSpec, ...args);
739
762
  }
740
763
 
741
764
  // ENSURE LEFT JOIN: 테이블 + Alias
@@ -763,18 +786,18 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
763
786
  TResult
764
787
  >;
765
788
  ensureLeftJoin(tableNameOrSpec: any, left: string, right: string): any {
766
- return this.__ensureJoin("leftJoin", tableNameOrSpec, left, right);
789
+ return this["__ensureJoin"]("leftJoin", tableNameOrSpec, left, right);
767
790
  }
768
791
 
769
792
  __commonJoin(joinType: "join" | "leftJoin", tableNameOrSpec: any, ...args: any[]): this {
770
793
  const registration = this.createJoinRegistration(joinType, tableNameOrSpec, args);
771
794
  this.assertCorrelationAvailable(registration);
772
795
 
773
- if (typeof tableNameOrSpec === "string") {
796
+ if (isStringValue(tableNameOrSpec)) {
774
797
  // Case 1: join("posts", ...)
775
798
  const tableName = tableNameOrSpec;
776
799
 
777
- if (args.length === 1 && typeof args[0] === "function") {
800
+ if (args.length === 1 && isFunctionValue(args[0])) {
778
801
  // join("posts", callback)
779
802
  const callback = args[0];
780
803
  this.knexQuery[joinType](tableName, (joinClause) => {
@@ -785,7 +808,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
785
808
  const [left, right] = args;
786
809
  this.knexQuery[joinType](tableName, left, right);
787
810
  }
788
- } else if (typeof tableNameOrSpec === "object") {
811
+ } else if (isObjectValue(tableNameOrSpec)) {
789
812
  // Case 2: join({ alias: "table" }, ...) or join({ alias: subquery }, ...)
790
813
  const entries = Object.entries(tableNameOrSpec);
791
814
  if (entries.length !== 1) {
@@ -794,9 +817,9 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
794
817
  assert(entries[0]);
795
818
  const [[alias, spec]] = entries;
796
819
 
797
- if (typeof spec === "string") {
820
+ if (isStringValue(spec)) {
798
821
  // 테이블: join({ p: "posts" }, ...)
799
- if (args.length === 1 && typeof args[0] === "function") {
822
+ if (args.length === 1 && isFunctionValue(args[0])) {
800
823
  // Callback
801
824
  const callback = args[0];
802
825
  this.knexQuery[joinType]({ [alias]: spec }, (joinClause) => {
@@ -809,7 +832,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
809
832
  }
810
833
  } else if (spec instanceof Puri) {
811
834
  // 서브쿼리: join({ sq: subquery }, ...)
812
- if (args.length === 1 && typeof args[0] === "function") {
835
+ if (args.length === 1 && isFunctionValue(args[0])) {
813
836
  // Callback
814
837
  const callback = args[0];
815
838
  this.knexQuery[joinType](spec.rawQuery().as(alias), (joinClause) => {
@@ -846,7 +869,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
846
869
  const existing = this.correlationRegistry.get(requested.alias);
847
870
 
848
871
  if (!existing) {
849
- return this.__commonJoin(joinType, tableNameOrSpec, left, right);
872
+ return this["__commonJoin"](joinType, tableNameOrSpec, left, right);
850
873
  }
851
874
 
852
875
  if (this.isSameJoinRegistration(existing, requested)) {
@@ -864,10 +887,10 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
864
887
  let alias: string;
865
888
  let table: string | null;
866
889
 
867
- if (typeof tableNameOrSpec === "string") {
890
+ if (isStringValue(tableNameOrSpec)) {
868
891
  alias = tableNameOrSpec;
869
892
  table = tableNameOrSpec;
870
- } else if (typeof tableNameOrSpec === "object") {
893
+ } else if (isObjectValue(tableNameOrSpec)) {
871
894
  const entries = Object.entries(tableNameOrSpec);
872
895
  if (entries.length !== 1) {
873
896
  throw new Error("Table spec must have exactly one entry");
@@ -875,14 +898,14 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
875
898
  assert(entries[0]);
876
899
  const [[joinAlias, spec]] = entries;
877
900
  alias = joinAlias;
878
- table = typeof spec === "string" ? spec : null;
901
+ table = isStringValue(spec) ? spec : null;
879
902
  } else {
880
903
  throw new Error("Invalid arguments");
881
904
  }
882
905
 
883
906
  const [left, right] = args;
884
907
  const reusable =
885
- table !== null && args.length === 2 && typeof left === "string" && typeof right === "string";
908
+ table !== null && args.length === 2 && isStringValue(left) && isStringValue(right);
886
909
 
887
910
  return {
888
911
  kind: "join",
@@ -956,19 +979,19 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
956
979
  }
957
980
  }
958
981
 
959
- private getKnexJoinAlias(table: unknown): string | null {
960
- if (typeof table === "string") {
982
+ private getKnexJoinAlias<Value>(table: Value): string | null {
983
+ if (isStringValue(table)) {
961
984
  return table;
962
985
  }
963
- if (typeof table === "object" && table !== null) {
986
+ if (isObjectValue(table) && table !== null) {
964
987
  if (
965
988
  "_single" in table &&
966
- typeof table._single === "object" &&
967
- table._single !== null &&
968
- "as" in table._single &&
969
- typeof table._single.as === "string"
989
+ isObjectValue(table["_single"]) &&
990
+ table["_single"] !== null &&
991
+ "as" in table["_single"] &&
992
+ isStringValue(table["_single"].as)
970
993
  ) {
971
- return table._single.as;
994
+ return table["_single"].as;
972
995
  }
973
996
  return Object.keys(table)[0] ?? null;
974
997
  }
@@ -993,15 +1016,15 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
993
1016
  // WHERE: 컬럼 - 사용: .where("u.id", "like", "%test%")
994
1017
  where(...args: [columnOrConditions: any, operatorOrValue?: any, value?: any]): this {
995
1018
  const [columnOrConditions, operatorOrValue, value] = args;
996
- if (typeof columnOrConditions === "object") {
1019
+ if (isObjectValue(columnOrConditions)) {
997
1020
  this.knexQuery.where(columnOrConditions);
998
- } else if (typeof value === "undefined") {
1021
+ } else if (value === undefined) {
999
1022
  if (operatorOrValue === null) {
1000
1023
  this.knexQuery.whereNull(columnOrConditions);
1001
1024
  return this;
1002
1025
  }
1003
1026
  this.knexQuery.where(columnOrConditions, operatorOrValue);
1004
- } else if (typeof value !== "undefined") {
1027
+ } else if (value !== undefined) {
1005
1028
  if (value === null) {
1006
1029
  if (operatorOrValue === "!=") {
1007
1030
  this.knexQuery.whereNotNull(columnOrConditions);
@@ -1021,18 +1044,32 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1021
1044
  // WHERE IN
1022
1045
  whereIn<TColumn extends AvailableColumns<TTables>>(
1023
1046
  column: TColumn,
1024
- values: ExtractColumnType<TTables, TColumn & string>[],
1047
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1025
1048
  ): Puri<TSchema, TTables, TResult> {
1026
- this.knexQuery.whereIn(column, values);
1049
+ // Knex의 비어 있는 IN 배열 처리와 동일하게 항상 거짓인 조건을 생성합니다.
1050
+ if (values.length === 0) {
1051
+ this.knexQuery.whereRaw("1 = 0");
1052
+ } else {
1053
+ const placeholders = values.map(() => "?").join(", ");
1054
+ this.knexQuery.whereRaw(`?? in (${placeholders})`, [String(column), ...values]);
1055
+ }
1056
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1027
1057
  return this as any;
1028
1058
  }
1029
1059
 
1030
1060
  // WHERE NOT IN
1031
1061
  whereNotIn<TColumn extends AvailableColumns<TTables>>(
1032
1062
  column: TColumn,
1033
- values: ExtractColumnType<TTables, TColumn & string>[],
1063
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1034
1064
  ): Puri<TSchema, TTables, TResult> {
1035
- this.knexQuery.whereNotIn(column, values);
1065
+ // Knex의 비어 있는 NOT IN 배열 처리와 동일하게 항상 참인 조건을 생성합니다.
1066
+ if (values.length === 0) {
1067
+ this.knexQuery.whereRaw("1 = 1");
1068
+ } else {
1069
+ const placeholders = values.map(() => "?").join(", ");
1070
+ this.knexQuery.whereRaw(`?? not in (${placeholders})`, [String(column), ...values]);
1071
+ }
1072
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1036
1073
  return this as any;
1037
1074
  }
1038
1075
 
@@ -1090,13 +1127,14 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1090
1127
  options?: TsQueryOptions | TsQueryConfig,
1091
1128
  ): this {
1092
1129
  const opts =
1093
- typeof options === "string" ? ({ config: options } as TsQueryOptions) : (options ?? {});
1130
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1131
+ isStringValue(options) ? ({ config: options } as TsQueryOptions) : (options ?? {});
1094
1132
 
1095
1133
  const parser = opts.parser ?? "websearch_to_tsquery";
1096
1134
  const config = opts.config ?? "simple";
1097
1135
  const columnExpr =
1098
- typeof column === "object" && column._type === "sql_expression"
1099
- ? column._sql
1136
+ isSqlExpression(column) && column["_type"] === "sql_expression"
1137
+ ? column["_sql"]
1100
1138
  : String(column);
1101
1139
 
1102
1140
  this.knexQuery.whereRaw(`${columnExpr} @@ ${parser}(?, ?)`, [config, value]);
@@ -1110,19 +1148,19 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1110
1148
  operator?: FuzzyOperator;
1111
1149
  },
1112
1150
  ): this {
1113
- const operator = normalizeFuzzyOperator(options?.operator);
1151
+ const operator = parseFuzzyOperator(options?.operator);
1114
1152
 
1115
1153
  if (operator === "%") {
1116
- if (typeof column === "object") {
1117
- this.knexQuery.whereRaw(`${column._sql} ${operator} ?`, [...column._params, value]);
1154
+ if (isSqlExpression(column)) {
1155
+ this.knexQuery.whereRaw(`${column["_sql"]} ${operator} ?`, [...column["_params"], value]);
1118
1156
  } else {
1119
1157
  this.knexQuery.whereRaw(`?? ${operator} ?`, [column, value]);
1120
1158
  }
1121
1159
  return this;
1122
1160
  }
1123
1161
 
1124
- if (typeof column === "object") {
1125
- this.knexQuery.whereRaw(`? ${operator} ${column._sql}`, [value, ...column._params]);
1162
+ if (isSqlExpression(column)) {
1163
+ this.knexQuery.whereRaw(`? ${operator} ${column["_sql"]}`, [value, ...column["_params"]]);
1126
1164
  } else {
1127
1165
  this.knexQuery.whereRaw(`? ${operator} ??`, [value, column]);
1128
1166
  }
@@ -1167,7 +1205,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1167
1205
  ): this {
1168
1206
  if (isOrderByEntries(columnOrColumns)) {
1169
1207
  for (const entry of columnOrColumns) {
1170
- if (typeof entry === "string" || isSqlExpression(entry)) {
1208
+ if (isStringValue(entry) || isSqlExpression(entry)) {
1171
1209
  this.applyOrderBy(entry);
1172
1210
  } else {
1173
1211
  this.applyOrderBy(entry.column, entry.order, entry.nulls);
@@ -1188,10 +1226,10 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1188
1226
  const normalizedDirection = normalizeOrderByDirection(direction);
1189
1227
  const normalizedNulls = normalizeOrderByNulls(nulls);
1190
1228
 
1191
- if (typeof column === "object") {
1229
+ if (isSqlExpression(column)) {
1192
1230
  this.knexQuery.orderByRaw(
1193
- `${column._sql} ${normalizedDirection}${formatNullsSuffix(normalizedNulls)}`,
1194
- column._params,
1231
+ `${column["_sql"]} ${normalizedDirection}${formatNullsSuffix(normalizedNulls)}`,
1232
+ column["_params"],
1195
1233
  );
1196
1234
  } else {
1197
1235
  this.knexQuery.orderBy(column, normalizedDirection, normalizedNulls);
@@ -1281,7 +1319,8 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1281
1319
  this.knexQuery.clear("order");
1282
1320
  if (distinctOn) {
1283
1321
  // DISTINCT ON은 SELECT 절의 맨 앞에 와야 하므로, 기존 select(subset 필드들)를 보존 후 clear하고 다시 추가
1284
- const existingSubsetCols = (this.knexQuery as any)._statements
1322
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1323
+ const existingSubsetCols = (this.knexQuery as any)["_statements"]
1285
1324
  .filter((s: any) => s.grouping === "columns")
1286
1325
  .flatMap((s: any) => s.value);
1287
1326
  this.knexQuery.clear("select");
@@ -1304,7 +1343,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1304
1343
  }
1305
1344
 
1306
1345
  // threshold
1307
- if (typeof threshold === "number") {
1346
+ if (isNumberValue(threshold)) {
1308
1347
  if (!Number.isFinite(threshold)) {
1309
1348
  throw new Error(`Invalid vectorSimilarity threshold: ${threshold}`);
1310
1349
  }
@@ -1323,6 +1362,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1323
1362
  }
1324
1363
  }
1325
1364
 
1365
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1326
1366
  return this as any;
1327
1367
  }
1328
1368
 
@@ -1381,6 +1421,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1381
1421
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
1382
1422
  ): Promise<TResult1 | TResult2> {
1383
1423
  Naite.t("puri:executed-query", this.toQuery());
1424
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1384
1425
  return this.knexQuery.then(onfulfilled as any, onrejected);
1385
1426
  }
1386
1427
  catch<TResult2 = never>(
@@ -1407,6 +1448,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1407
1448
  : ExtractColumnType<TTables, TColumn & string>[],
1408
1449
  never
1409
1450
  > {
1451
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1410
1452
  this.knexQuery.pluck(column as string);
1411
1453
  return new ResolvedPuri(this.knexQuery, this.knex, this.tableSpec);
1412
1454
  }
@@ -1442,9 +1484,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1442
1484
  * object 또는 object 배열을 받고, JSON 컬럼이 있으면 직렬화하여 반환합니다.
1443
1485
  * 직접 값을 변경하므로 side effect가 있습니다.
1444
1486
  */
1445
- private refineJsonColumns(
1446
- data: Record<string, unknown> | Record<string, unknown>[],
1447
- ): typeof data {
1487
+ private refineJsonColumns<Row extends object>(data: Row | Row[]): typeof data {
1448
1488
  // tableSpec이나 jsonColumns 없는 경우 바로 반환
1449
1489
  if (!this.tableSpec || !this.tableSpec.jsonColumns.length) {
1450
1490
  return data;
@@ -1455,17 +1495,21 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1455
1495
  if (Array.isArray(data)) {
1456
1496
  for (const item of data) {
1457
1497
  for (const column of jsonColumns) {
1458
- const value = item[column];
1498
+ const value = Object.entries(item).find(([key]) => key === column)?.[1];
1459
1499
  if (value !== undefined && value !== null) {
1460
- item[column] = serializeJsonColumnValue(this.tableSpec, column, value);
1500
+ Object.assign(item, {
1501
+ [column]: serializeJsonColumnValue(this.tableSpec, column, value),
1502
+ });
1461
1503
  }
1462
1504
  }
1463
1505
  }
1464
1506
  } else {
1465
1507
  for (const column of jsonColumns) {
1466
- const value = data[column];
1508
+ const value = Object.entries(data).find(([key]) => key === column)?.[1];
1467
1509
  if (value !== undefined && value !== null) {
1468
- data[column] = serializeJsonColumnValue(this.tableSpec, column, value);
1510
+ Object.assign(data, {
1511
+ [column]: serializeJsonColumnValue(this.tableSpec, column, value),
1512
+ });
1469
1513
  }
1470
1514
  }
1471
1515
  }
@@ -1639,7 +1683,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
1639
1683
  }
1640
1684
  }
1641
1685
 
1642
- export class WhereGroup<TTables extends Record<string, any>> {
1686
+ export class WhereGroup<TTables extends object> {
1643
1687
  constructor(private builder: Knex.QueryBuilder) {}
1644
1688
 
1645
1689
  // where 메서드들
@@ -1661,7 +1705,7 @@ export class WhereGroup<TTables extends Record<string, any>> {
1661
1705
  // whereIn / whereNotIn 메서드들
1662
1706
  whereIn<TColumn extends AvailableColumns<TTables>>(
1663
1707
  column: TColumn,
1664
- values: ExtractColumnType<TTables, TColumn & string>[],
1708
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1665
1709
  ): this;
1666
1710
  whereIn(...args: any[]): WhereGroup<TTables> {
1667
1711
  this.builder.whereIn(args[0], args[1]);
@@ -1670,7 +1714,7 @@ export class WhereGroup<TTables extends Record<string, any>> {
1670
1714
 
1671
1715
  whereNotIn<TColumn extends AvailableColumns<TTables>>(
1672
1716
  column: TColumn,
1673
- values: ExtractColumnType<TTables, TColumn & string>[],
1717
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1674
1718
  ): this;
1675
1719
  whereNotIn(...args: any[]): WhereGroup<TTables> {
1676
1720
  this.builder.whereNotIn(args[0], args[1]);
@@ -1696,7 +1740,7 @@ export class WhereGroup<TTables extends Record<string, any>> {
1696
1740
  // orWhereIn / orWhereNotIn 메서드들
1697
1741
  orWhereIn<TColumn extends AvailableColumns<TTables>>(
1698
1742
  column: TColumn,
1699
- values: ExtractColumnType<TTables, TColumn & string>[],
1743
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1700
1744
  ): this;
1701
1745
  orWhereIn(...args: any[]): WhereGroup<TTables> {
1702
1746
  this.builder.orWhereIn(args[0], args[1]);
@@ -1705,7 +1749,7 @@ export class WhereGroup<TTables extends Record<string, any>> {
1705
1749
 
1706
1750
  orWhereNotIn<TColumn extends AvailableColumns<TTables>>(
1707
1751
  column: TColumn,
1708
- values: ExtractColumnType<TTables, TColumn & string>[],
1752
+ values: (ExtractColumnType<TTables, TColumn & string> & Knex.Value)[],
1709
1753
  ): this;
1710
1754
  orWhereNotIn(...args: any[]): WhereGroup<TTables> {
1711
1755
  this.builder.orWhereNotIn(args[0], args[1]);
@@ -1787,14 +1831,12 @@ export class WhereGroup<TTables extends Record<string, any>> {
1787
1831
  ): this;
1788
1832
  whereTsSearch(...args: any[]): this {
1789
1833
  const opts =
1790
- typeof args[2] === "string" ? ({ config: args[2] } as TsQueryOptions) : (args[2] ?? {});
1834
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1835
+ isStringValue(args[2]) ? ({ config: args[2] } as TsQueryOptions) : (args[2] ?? {});
1791
1836
 
1792
1837
  const parser = opts.parser ?? "websearch_to_tsquery";
1793
1838
  const config = opts.config ?? "simple";
1794
- const columnExpr =
1795
- typeof args[0] === "object" && args[0]._type === "sql_expression"
1796
- ? args[0]._sql
1797
- : String(args[0]);
1839
+ const columnExpr = isSqlExpression(args[0]) ? args[0]["_sql"] : String(args[0]);
1798
1840
 
1799
1841
  this.builder.whereRaw(`${columnExpr} @@ ${parser}(?, ?)`, [config, args[1]]);
1800
1842
  return this;
@@ -1807,14 +1849,12 @@ export class WhereGroup<TTables extends Record<string, any>> {
1807
1849
  ): this;
1808
1850
  orWhereTsSearch(...args: any[]): this {
1809
1851
  const opts =
1810
- typeof args[2] === "string" ? ({ config: args[2] } as TsQueryOptions) : (args[2] ?? {});
1852
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1853
+ isStringValue(args[2]) ? ({ config: args[2] } as TsQueryOptions) : (args[2] ?? {});
1811
1854
 
1812
1855
  const parser = opts.parser ?? "websearch_to_tsquery";
1813
1856
  const config = opts.config ?? "simple";
1814
- const columnExpr =
1815
- typeof args[0] === "object" && args[0]._type === "sql_expression"
1816
- ? args[0]._sql
1817
- : String(args[0]);
1857
+ const columnExpr = isSqlExpression(args[0]) ? args[0]["_sql"] : String(args[0]);
1818
1858
 
1819
1859
  this.builder.orWhereRaw(`${columnExpr} @@ ${parser}(?, ?)`, [config, args[1]]);
1820
1860
  return this;
@@ -1828,19 +1868,19 @@ export class WhereGroup<TTables extends Record<string, any>> {
1828
1868
  },
1829
1869
  ): this;
1830
1870
  whereFuzzy(...args: any[]): this {
1831
- const operator = normalizeFuzzyOperator(args[2]?.operator);
1871
+ const operator = parseFuzzyOperator(args[2]?.operator);
1832
1872
 
1833
1873
  if (operator === "%") {
1834
- if (typeof args[0] === "object") {
1835
- this.builder.whereRaw(`${args[0]._sql} ${operator} ?`, [...args[0]._params, args[1]]);
1874
+ if (isSqlExpression(args[0])) {
1875
+ this.builder.whereRaw(`${args[0]["_sql"]} ${operator} ?`, [...args[0]["_params"], args[1]]);
1836
1876
  } else {
1837
1877
  this.builder.whereRaw(`?? ${operator} ?`, [args[0], args[1]]);
1838
1878
  }
1839
1879
  return this;
1840
1880
  }
1841
1881
 
1842
- if (typeof args[0] === "object") {
1843
- this.builder.whereRaw(`? ${operator} ${args[0]._sql}`, [args[1], ...args[0]._params]);
1882
+ if (isSqlExpression(args[0])) {
1883
+ this.builder.whereRaw(`? ${operator} ${args[0]["_sql"]}`, [args[1], ...args[0]["_params"]]);
1844
1884
  } else {
1845
1885
  this.builder.whereRaw(`? ${operator} ??`, [args[1], args[0]]);
1846
1886
  }
@@ -1855,19 +1895,22 @@ export class WhereGroup<TTables extends Record<string, any>> {
1855
1895
  },
1856
1896
  ): this;
1857
1897
  orWhereFuzzy(...args: any[]): this {
1858
- const operator = normalizeFuzzyOperator(args[2]?.operator);
1898
+ const operator = parseFuzzyOperator(args[2]?.operator);
1859
1899
 
1860
1900
  if (operator === "%") {
1861
- if (typeof args[0] === "object") {
1862
- this.builder.orWhereRaw(`${args[0]._sql} ${operator} ?`, [...args[0]._params, args[1]]);
1901
+ if (isSqlExpression(args[0])) {
1902
+ this.builder.orWhereRaw(`${args[0]["_sql"]} ${operator} ?`, [
1903
+ ...args[0]["_params"],
1904
+ args[1],
1905
+ ]);
1863
1906
  } else {
1864
1907
  this.builder.orWhereRaw(`?? ${operator} ?`, [args[0], args[1]]);
1865
1908
  }
1866
1909
  return this;
1867
1910
  }
1868
1911
 
1869
- if (typeof args[0] === "object") {
1870
- this.builder.orWhereRaw(`? ${operator} ${args[0]._sql}`, [args[1], ...args[0]._params]);
1912
+ if (isSqlExpression(args[0])) {
1913
+ this.builder.orWhereRaw(`? ${operator} ${args[0]["_sql"]}`, [args[1], ...args[0]["_params"]]);
1871
1914
  } else {
1872
1915
  this.builder.orWhereRaw(`? ${operator} ??`, [args[1], args[0]]);
1873
1916
  }
@@ -1894,10 +1937,7 @@ export class WhereGroup<TTables extends Record<string, any>> {
1894
1937
  }
1895
1938
 
1896
1939
  // JOIN 절 그룹에는 Left와 Right에 대한 순서가 필요하지 않으므로, 모든 경우의 수를 계산해야함.
1897
- export class JoinClauseGroup<
1898
- TLeft extends Record<string, any>,
1899
- TRight extends Record<string, any>,
1900
- > {
1940
+ export class JoinClauseGroup<TLeft extends object, TRight extends object> {
1901
1941
  constructor(private callback: Knex.JoinClause) {}
1902
1942
 
1903
1943
  // ON(AND): 컬럼 = 컬럼
@@ -1939,6 +1979,7 @@ export class JoinClauseGroup<
1939
1979
  on(callback: (nested: JoinClauseGroup<TRight, TLeft>) => void): this;
1940
1980
  // ON(AND) 구현
1941
1981
  on(...args: any[]): this {
1982
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1942
1983
  this.callback.on(...(args as [string, string]));
1943
1984
  return this;
1944
1985
  }
@@ -1982,6 +2023,7 @@ export class JoinClauseGroup<
1982
2023
  orOn(callback: (nested: JoinClauseGroup<TRight, TLeft>) => void): this;
1983
2024
  // ON(OR) 구현
1984
2025
  orOn(...args: any[]): this {
2026
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1985
2027
  this.callback.orOn(...(args as [string, string]));
1986
2028
  return this;
1987
2029
  }
@@ -1992,6 +2034,7 @@ export class JoinClauseGroup<
1992
2034
  onVal(column: AvailableColumns<TLeft>, operator: ComparisonOperator, value: any): this;
1993
2035
  onVal(column: AvailableColumns<TRight>, operator: ComparisonOperator, value: any): this;
1994
2036
  onVal(...args: any[]): this {
2037
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
1995
2038
  (this.callback as any).onVal(...args);
1996
2039
  return this;
1997
2040
  }
@@ -2002,6 +2045,7 @@ export class JoinClauseGroup<
2002
2045
  andOnVal(column: AvailableColumns<TLeft>, operator: ComparisonOperator, value: any): this;
2003
2046
  andOnVal(column: AvailableColumns<TRight>, operator: ComparisonOperator, value: any): this;
2004
2047
  andOnVal(...args: any[]): this {
2048
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
2005
2049
  (this.callback as any).andOnVal(...args);
2006
2050
  return this;
2007
2051
  }
@@ -2012,6 +2056,7 @@ export class JoinClauseGroup<
2012
2056
  orOnVal(column: AvailableColumns<TLeft>, operator: ComparisonOperator, value: any): this;
2013
2057
  orOnVal(column: AvailableColumns<TRight>, operator: ComparisonOperator, value: any): this;
2014
2058
  orOnVal(...args: any[]): this {
2059
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
2015
2060
  (this.callback as any).orOnVal(...args);
2016
2061
  return this;
2017
2062
  }
@@ -2044,6 +2089,7 @@ export class ResolvedPuri<TResolved, TReturning> implements Promise<TResolved> {
2044
2089
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
2045
2090
  ): Promise<TResult1 | TResult2> {
2046
2091
  Naite.t("puri:executed-query", this.toQuery());
2092
+ // SAFETY: 쿼리 빌더의 제네릭 계약과 선행 검증이 이 타입을 보장합니다.
2047
2093
  return this.knexQuery.then(onfulfilled as any, onrejected);
2048
2094
  }
2049
2095
 
@@ -2076,21 +2122,13 @@ export class ResolvedPuri<TResolved, TReturning> implements Promise<TResolved> {
2076
2122
  this.knexQuery.onConflict(target).merge(update);
2077
2123
  } else {
2078
2124
  // action.update 객체 형태: { name: "John", count: raw(...) }
2079
- const mergeObj: Record<string, any> = {};
2125
+ const mergeObj = {};
2080
2126
 
2081
2127
  for (const [key, value] of Object.entries(update)) {
2082
- if (
2083
- value &&
2084
- typeof value === "object" &&
2085
- "_type" in value &&
2086
- value._type === "sql_expression"
2087
- ) {
2088
- // SqlExpression → knex.raw()로 변환
2089
- mergeObj[key] = this.knex.raw((value as SqlExpression<any>)._sql);
2090
- } else {
2091
- // 일반 값
2092
- mergeObj[key] = serializeJsonColumnValue(this.tableSpec, key, value);
2093
- }
2128
+ const mergedValue = isSqlExpression(value)
2129
+ ? this.knex.raw(value["_sql"])
2130
+ : serializeJsonColumnValue(this.tableSpec, key, value);
2131
+ Object.assign(mergeObj, { [key]: mergedValue });
2094
2132
  }
2095
2133
 
2096
2134
  this.knexQuery.onConflict(target).merge(mergeObj);