@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221

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 (337) hide show
  1. package/CHANGELOG.md +1648 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +12 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +16 -12
  6. package/dist/Emailer/Sendgrid.d.ts +15 -15
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +20 -16
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/fake.js +2 -2
  11. package/dist/Emailer/service.d.ts +13 -4
  12. package/dist/Emailer/service.d.ts.map +1 -1
  13. package/dist/Emailer/service.js +4 -3
  14. package/dist/Emailer.d.ts +1 -1
  15. package/dist/MainFiberSet.d.ts +12 -9
  16. package/dist/MainFiberSet.d.ts.map +1 -1
  17. package/dist/MainFiberSet.js +7 -3
  18. package/dist/Model/Repository/Registry.d.ts +21 -0
  19. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  20. package/dist/Model/Repository/Registry.js +18 -0
  21. package/dist/Model/Repository/ext.d.ts +35 -16
  22. package/dist/Model/Repository/ext.d.ts.map +1 -1
  23. package/dist/Model/Repository/ext.js +60 -3
  24. package/dist/Model/Repository/internal/internal.d.ts +9 -6
  25. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  26. package/dist/Model/Repository/internal/internal.js +115 -51
  27. package/dist/Model/Repository/legacy.d.ts +4 -2
  28. package/dist/Model/Repository/legacy.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.d.ts +10 -6
  30. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  31. package/dist/Model/Repository/makeRepo.js +5 -2
  32. package/dist/Model/Repository/service.d.ts +32 -24
  33. package/dist/Model/Repository/service.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.d.ts +47 -18
  35. package/dist/Model/Repository/validation.d.ts.map +1 -1
  36. package/dist/Model/Repository/validation.js +6 -6
  37. package/dist/Model/Repository.d.ts +2 -1
  38. package/dist/Model/Repository.d.ts.map +1 -1
  39. package/dist/Model/Repository.js +2 -1
  40. package/dist/Model/dsl.d.ts +6 -5
  41. package/dist/Model/dsl.d.ts.map +1 -1
  42. package/dist/Model/dsl.js +2 -3
  43. package/dist/Model/filter/filterApi.d.ts +5 -5
  44. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  45. package/dist/Model/filter/types/errors.d.ts +1 -1
  46. package/dist/Model/filter/types/fields.d.ts +1 -1
  47. package/dist/Model/filter/types/path/common.d.ts +1 -1
  48. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  49. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  50. package/dist/Model/filter/types/path/eager.js +1 -1
  51. package/dist/Model/filter/types/path/index.d.ts +1 -1
  52. package/dist/Model/filter/types/utils.d.ts +1 -1
  53. package/dist/Model/filter/types/validator.d.ts +1 -1
  54. package/dist/Model/filter/types.d.ts +1 -1
  55. package/dist/Model/query/dsl.d.ts +142 -17
  56. package/dist/Model/query/dsl.d.ts.map +1 -1
  57. package/dist/Model/query/dsl.js +190 -5
  58. package/dist/Model/query/new-kid-interpreter.d.ts +77 -8
  59. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  60. package/dist/Model/query/new-kid-interpreter.js +127 -6
  61. package/dist/Model/query.d.ts +1 -1
  62. package/dist/Model.d.ts +2 -1
  63. package/dist/Model.d.ts.map +1 -1
  64. package/dist/Model.js +2 -1
  65. package/dist/QueueMaker/SQLQueue.d.ts +7 -8
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +135 -117
  68. package/dist/QueueMaker/errors.d.ts +5 -3
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/errors.js +4 -2
  71. package/dist/QueueMaker/memQueue.d.ts +9 -5
  72. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  73. package/dist/QueueMaker/memQueue.js +81 -65
  74. package/dist/QueueMaker/sbqueue.d.ts +8 -4
  75. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  76. package/dist/QueueMaker/sbqueue.js +57 -55
  77. package/dist/QueueMaker/service.d.ts +4 -2
  78. package/dist/QueueMaker/service.d.ts.map +1 -1
  79. package/dist/QueueMaker/service.js +1 -1
  80. package/dist/RequestContext.d.ts +75 -35
  81. package/dist/RequestContext.d.ts.map +1 -1
  82. package/dist/RequestContext.js +14 -14
  83. package/dist/RequestFiberSet.d.ts +10 -7
  84. package/dist/RequestFiberSet.d.ts.map +1 -1
  85. package/dist/RequestFiberSet.js +8 -3
  86. package/dist/Store/ContextMapContainer.d.ts +22 -3
  87. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  88. package/dist/Store/ContextMapContainer.js +17 -3
  89. package/dist/Store/Cosmos/query.d.ts +7 -2
  90. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  91. package/dist/Store/Cosmos/query.js +115 -35
  92. package/dist/Store/Cosmos.d.ts +2 -2
  93. package/dist/Store/Cosmos.d.ts.map +1 -1
  94. package/dist/Store/Cosmos.js +343 -244
  95. package/dist/Store/Disk.d.ts +3 -3
  96. package/dist/Store/Disk.d.ts.map +1 -1
  97. package/dist/Store/Disk.js +76 -36
  98. package/dist/Store/Memory.d.ts +7 -4
  99. package/dist/Store/Memory.d.ts.map +1 -1
  100. package/dist/Store/Memory.js +251 -58
  101. package/dist/Store/SQL/Pg.d.ts +4 -0
  102. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  103. package/dist/Store/SQL/Pg.js +233 -0
  104. package/dist/Store/SQL/query.d.ts +43 -0
  105. package/dist/Store/SQL/query.d.ts.map +1 -0
  106. package/dist/Store/SQL/query.js +478 -0
  107. package/dist/Store/SQL.d.ts +21 -0
  108. package/dist/Store/SQL.d.ts.map +1 -0
  109. package/dist/Store/SQL.js +450 -0
  110. package/dist/Store/codeFilter.d.ts +2 -2
  111. package/dist/Store/codeFilter.d.ts.map +1 -1
  112. package/dist/Store/codeFilter.js +6 -3
  113. package/dist/Store/index.d.ts +6 -3
  114. package/dist/Store/index.d.ts.map +1 -1
  115. package/dist/Store/index.js +18 -4
  116. package/dist/Store/service.d.ts +26 -8
  117. package/dist/Store/service.d.ts.map +1 -1
  118. package/dist/Store/service.js +25 -6
  119. package/dist/Store/utils.d.ts +3 -2
  120. package/dist/Store/utils.d.ts.map +1 -1
  121. package/dist/Store/utils.js +5 -5
  122. package/dist/Store.d.ts +1 -1
  123. package/dist/adapters/SQL/Model.d.ts +32 -43
  124. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  125. package/dist/adapters/SQL/Model.js +30 -39
  126. package/dist/adapters/SQL.d.ts +1 -1
  127. package/dist/adapters/ServiceBus.d.ts +14 -11
  128. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  129. package/dist/adapters/ServiceBus.js +30 -21
  130. package/dist/adapters/cosmos-client.d.ts +5 -3
  131. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  132. package/dist/adapters/cosmos-client.js +5 -3
  133. package/dist/adapters/index.d.ts +8 -2
  134. package/dist/adapters/index.d.ts.map +1 -1
  135. package/dist/adapters/index.js +8 -2
  136. package/dist/adapters/logger.d.ts +2 -2
  137. package/dist/adapters/logger.d.ts.map +1 -1
  138. package/dist/adapters/memQueue.d.ts +5 -3
  139. package/dist/adapters/memQueue.d.ts.map +1 -1
  140. package/dist/adapters/memQueue.js +6 -5
  141. package/dist/adapters/mongo-client.d.ts +4 -3
  142. package/dist/adapters/mongo-client.d.ts.map +1 -1
  143. package/dist/adapters/mongo-client.js +5 -3
  144. package/dist/adapters/redis-client.d.ts +6 -3
  145. package/dist/adapters/redis-client.d.ts.map +1 -1
  146. package/dist/adapters/redis-client.js +7 -3
  147. package/dist/api/ContextProvider.d.ts +12 -8
  148. package/dist/api/ContextProvider.d.ts.map +1 -1
  149. package/dist/api/ContextProvider.js +9 -7
  150. package/dist/api/codec.d.ts +1 -1
  151. package/dist/api/internal/RequestContextMiddleware.d.ts +3 -3
  152. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  153. package/dist/api/internal/RequestContextMiddleware.js +10 -6
  154. package/dist/api/internal/auth.d.ts +45 -7
  155. package/dist/api/internal/auth.d.ts.map +1 -1
  156. package/dist/api/internal/auth.js +162 -29
  157. package/dist/api/internal/events.d.ts +6 -4
  158. package/dist/api/internal/events.d.ts.map +1 -1
  159. package/dist/api/internal/events.js +16 -9
  160. package/dist/api/internal/health.d.ts +1 -1
  161. package/dist/api/layerUtils.d.ts +10 -6
  162. package/dist/api/layerUtils.d.ts.map +1 -1
  163. package/dist/api/layerUtils.js +7 -6
  164. package/dist/api/middlewares.d.ts +1 -1
  165. package/dist/api/reportError.d.ts +2 -2
  166. package/dist/api/reportError.d.ts.map +1 -1
  167. package/dist/api/reportError.js +3 -2
  168. package/dist/api/routing/middleware/RouterMiddleware.d.ts +5 -4
  169. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware/middleware.d.ts +42 -3
  171. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  172. package/dist/api/routing/middleware/middleware.js +53 -17
  173. package/dist/api/routing/middleware.d.ts +1 -2
  174. package/dist/api/routing/middleware.d.ts.map +1 -1
  175. package/dist/api/routing/middleware.js +1 -2
  176. package/dist/api/routing/schema/jwt.d.ts +1 -1
  177. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  178. package/dist/api/routing/schema/jwt.js +3 -2
  179. package/dist/api/routing/tsort.d.ts +1 -1
  180. package/dist/api/routing/tsort.d.ts.map +1 -1
  181. package/dist/api/routing/utils.d.ts +4 -4
  182. package/dist/api/routing/utils.d.ts.map +1 -1
  183. package/dist/api/routing/utils.js +3 -2
  184. package/dist/api/routing.d.ts +84 -37
  185. package/dist/api/routing.d.ts.map +1 -1
  186. package/dist/api/routing.js +115 -45
  187. package/dist/api/setupRequest.d.ts +10 -6
  188. package/dist/api/setupRequest.d.ts.map +1 -1
  189. package/dist/api/setupRequest.js +15 -7
  190. package/dist/api/util.d.ts +1 -1
  191. package/dist/arbs.d.ts +2 -2
  192. package/dist/arbs.d.ts.map +1 -1
  193. package/dist/arbs.js +5 -3
  194. package/dist/errorReporter.d.ts +7 -5
  195. package/dist/errorReporter.d.ts.map +1 -1
  196. package/dist/errorReporter.js +22 -26
  197. package/dist/errors.d.ts +1 -1
  198. package/dist/fileUtil.d.ts +2 -2
  199. package/dist/fileUtil.d.ts.map +1 -1
  200. package/dist/fileUtil.js +2 -2
  201. package/dist/index.d.ts +1 -1
  202. package/dist/logger/jsonLogger.d.ts +2 -2
  203. package/dist/logger/jsonLogger.d.ts.map +1 -1
  204. package/dist/logger/jsonLogger.js +4 -2
  205. package/dist/logger/logFmtLogger.d.ts +2 -2
  206. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  207. package/dist/logger/logFmtLogger.js +2 -2
  208. package/dist/logger/shared.d.ts +2 -2
  209. package/dist/logger/shared.d.ts.map +1 -1
  210. package/dist/logger/shared.js +3 -3
  211. package/dist/logger.d.ts +1 -1
  212. package/dist/logger.d.ts.map +1 -1
  213. package/dist/otel.d.ts +75 -0
  214. package/dist/otel.d.ts.map +1 -0
  215. package/dist/otel.js +65 -0
  216. package/dist/rateLimit.d.ts +12 -4
  217. package/dist/rateLimit.d.ts.map +1 -1
  218. package/dist/rateLimit.js +7 -12
  219. package/dist/test.d.ts +3 -3
  220. package/dist/test.d.ts.map +1 -1
  221. package/dist/test.js +2 -2
  222. package/dist/vitest.d.ts +1 -1
  223. package/examples/query.ts +46 -38
  224. package/package.json +46 -37
  225. package/src/CUPS.ts +15 -11
  226. package/src/Emailer/Sendgrid.ts +21 -15
  227. package/src/Emailer/fake.ts +1 -1
  228. package/src/Emailer/service.ts +13 -3
  229. package/src/MainFiberSet.ts +9 -6
  230. package/src/Model/Repository/Registry.ts +34 -0
  231. package/src/Model/Repository/ext.ts +103 -11
  232. package/src/Model/Repository/internal/internal.ts +231 -149
  233. package/src/Model/Repository/legacy.ts +3 -1
  234. package/src/Model/Repository/makeRepo.ts +15 -10
  235. package/src/Model/Repository/service.ts +35 -23
  236. package/src/Model/Repository/validation.ts +5 -5
  237. package/src/Model/Repository.ts +1 -0
  238. package/src/Model/dsl.ts +5 -4
  239. package/src/Model/filter/types/path/eager.ts +1 -2
  240. package/src/Model/query/dsl.ts +353 -19
  241. package/src/Model/query/new-kid-interpreter.ts +211 -6
  242. package/src/Model.ts +1 -0
  243. package/src/QueueMaker/SQLQueue.ts +150 -153
  244. package/src/QueueMaker/errors.ts +3 -1
  245. package/src/QueueMaker/memQueue.ts +111 -105
  246. package/src/QueueMaker/sbqueue.ts +76 -88
  247. package/src/QueueMaker/service.ts +3 -1
  248. package/src/RequestContext.ts +15 -16
  249. package/src/RequestFiberSet.ts +8 -2
  250. package/src/Store/ContextMapContainer.ts +45 -2
  251. package/src/Store/Cosmos/query.ts +143 -44
  252. package/src/Store/Cosmos.ts +491 -350
  253. package/src/Store/Disk.ts +106 -66
  254. package/src/Store/Memory.ts +285 -87
  255. package/src/Store/SQL/Pg.ts +364 -0
  256. package/src/Store/SQL/query.ts +540 -0
  257. package/src/Store/SQL.ts +736 -0
  258. package/src/Store/codeFilter.ts +5 -2
  259. package/src/Store/index.ts +20 -3
  260. package/src/Store/service.ts +45 -10
  261. package/src/Store/utils.ts +25 -23
  262. package/src/adapters/SQL/Model.ts +42 -41
  263. package/src/adapters/ServiceBus.ts +131 -121
  264. package/src/adapters/cosmos-client.ts +4 -2
  265. package/src/adapters/index.ts +7 -0
  266. package/src/adapters/memQueue.ts +5 -4
  267. package/src/adapters/mongo-client.ts +4 -2
  268. package/src/adapters/redis-client.ts +6 -2
  269. package/src/api/ContextProvider.ts +17 -13
  270. package/src/api/internal/RequestContextMiddleware.ts +16 -5
  271. package/src/api/internal/auth.ts +248 -44
  272. package/src/api/internal/events.ts +19 -10
  273. package/src/api/layerUtils.ts +12 -8
  274. package/src/api/reportError.ts +2 -1
  275. package/src/api/routing/middleware/RouterMiddleware.ts +5 -4
  276. package/src/api/routing/middleware/middleware.ts +60 -15
  277. package/src/api/routing/middleware.ts +0 -2
  278. package/src/api/routing/schema/jwt.ts +2 -1
  279. package/src/api/routing/utils.ts +2 -1
  280. package/src/api/routing.ts +304 -131
  281. package/src/api/setupRequest.ts +31 -8
  282. package/src/arbs.ts +5 -3
  283. package/src/errorReporter.ts +65 -75
  284. package/src/fileUtil.ts +1 -1
  285. package/src/logger/jsonLogger.ts +3 -1
  286. package/src/logger/logFmtLogger.ts +1 -1
  287. package/src/logger/shared.ts +3 -2
  288. package/src/otel.ts +152 -0
  289. package/src/rateLimit.ts +34 -23
  290. package/src/test.ts +2 -2
  291. package/test/auth.test.ts +101 -0
  292. package/test/contextProvider.test.ts +14 -11
  293. package/test/controller.test.ts +25 -29
  294. package/test/dist/auth.test.d.ts.map +1 -0
  295. package/test/dist/contextProvider.test.d.ts.map +1 -1
  296. package/test/dist/controller.test.d.ts.map +1 -1
  297. package/test/dist/date-query.test.d.ts.map +1 -0
  298. package/test/dist/fixtures.d.ts +30 -12
  299. package/test/dist/fixtures.d.ts.map +1 -1
  300. package/test/dist/fixtures.js +17 -10
  301. package/test/dist/query.test.d.ts.map +1 -1
  302. package/test/dist/rawQuery.test.d.ts.map +1 -1
  303. package/test/dist/repository-ext.test.d.ts.map +1 -0
  304. package/test/dist/requires.test.d.ts.map +1 -1
  305. package/test/dist/router-generator.test.d.ts.map +1 -0
  306. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  307. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  308. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  309. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  310. package/test/dist/sql-store.test.d.ts.map +1 -0
  311. package/test/fixtures.ts +16 -9
  312. package/test/layerUtils.test.ts +1 -1
  313. package/test/query.test.ts +819 -38
  314. package/test/rawQuery.test.ts +312 -20
  315. package/test/repository-ext.test.ts +62 -0
  316. package/test/requires.test.ts +10 -5
  317. package/test/router-generator.test.ts +187 -0
  318. package/test/routing-interruptibility.test.ts +66 -0
  319. package/test/rpc-e2e-invalidation.test.ts +256 -0
  320. package/test/rpc-multi-middleware.test.ts +84 -9
  321. package/test/rpc-stream-fullstack.test.ts +304 -0
  322. package/test/sql-store.test.ts +1592 -0
  323. package/test/validateSample.test.ts +17 -12
  324. package/tsconfig.examples.json +1 -1
  325. package/tsconfig.json +0 -1
  326. package/tsconfig.json.bak +2 -2
  327. package/tsconfig.src.json +35 -35
  328. package/tsconfig.test.json +2 -2
  329. package/dist/Operations.d.ts +0 -55
  330. package/dist/Operations.d.ts.map +0 -1
  331. package/dist/Operations.js +0 -102
  332. package/dist/OperationsRepo.d.ts +0 -41
  333. package/dist/OperationsRepo.d.ts.map +0 -1
  334. package/dist/OperationsRepo.js +0 -14
  335. package/eslint.config.mjs +0 -24
  336. package/src/Operations.ts +0 -235
  337. package/src/OperationsRepo.ts +0 -16
@@ -0,0 +1,540 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { NonEmptyReadonlyArray } from "effect-app/Array"
3
+ import * as Effect from "effect-app/Effect"
4
+ import { assertUnreachable } from "effect-app/utils"
5
+ import { InfraLogger } from "../../logger.js"
6
+ import type { FilterR, FilterResult } from "../../Model/filter/filterApi.js"
7
+ import type { ComputedProjectionIrExpression, ComputedProjectionMathIrExpression } from "../../Model/query.js"
8
+ import { isRelationCheck } from "../codeFilter.js"
9
+
10
+ export interface SQLDialect {
11
+ readonly jsonExtract: (path: string) => string
12
+ readonly jsonExtractJson: (path: string) => string
13
+ readonly placeholder: (index: number) => string
14
+ readonly jsonArrayContains: (arrPath: string, valPlaceholder: string) => string
15
+ readonly jsonArrayNotContains: (arrPath: string, valPlaceholder: string) => string
16
+ readonly jsonArrayContainsAny: (arrPath: string, valPlaceholders: readonly string[]) => string
17
+ readonly jsonArrayNotContainsAny: (arrPath: string, valPlaceholders: readonly string[]) => string
18
+ readonly jsonArrayContainsAll: (arrPath: string, valPlaceholders: readonly string[]) => string
19
+ readonly jsonArrayNotContainsAll: (arrPath: string, valPlaceholders: readonly string[]) => string
20
+ readonly caseInsensitiveLike: (expr: string, valPlaceholder: string) => string
21
+ readonly caseInsensitiveNotLike: (expr: string, valPlaceholder: string) => string
22
+ readonly jsonColumnType: "JSON" | "JSONB"
23
+ readonly arrayLength: (path: string) => string
24
+ readonly jsonEachFrom: (arrPath: string, alias: string) => string
25
+ readonly jsonExtractElement: (alias: string, subPath: string) => string
26
+ readonly serializeJsonValue: (v: unknown) => unknown
27
+ readonly serializeScalar: (v: unknown) => unknown
28
+ }
29
+
30
+ export const sqliteDialect: SQLDialect = {
31
+ jsonExtract: (path) => `json_extract(data, '$.${path}')`,
32
+ jsonExtractJson: (path) =>
33
+ `CASE json_type(data, '$.${path}') WHEN 'true' THEN 'true' WHEN 'false' THEN 'false' ELSE json_quote(json_extract(data, '$.${path}')) END`,
34
+ placeholder: (_index) => "?",
35
+ jsonArrayContains: (arrPath, val) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${val})`,
36
+ jsonArrayNotContains: (arrPath, val) =>
37
+ `NOT EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${val})`,
38
+ jsonArrayContainsAny: (arrPath, vals) =>
39
+ `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value IN (${vals.join(", ")}))`,
40
+ jsonArrayNotContainsAny: (arrPath, vals) =>
41
+ `NOT EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value IN (${vals.join(", ")}))`,
42
+ jsonArrayContainsAll: (arrPath, vals) =>
43
+ vals.map((v) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${v})`).join(" AND "),
44
+ jsonArrayNotContainsAll: (arrPath, vals) =>
45
+ `NOT (${
46
+ vals.map((v) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${v})`).join(" AND ")
47
+ })`,
48
+ caseInsensitiveLike: (expr, val) => `LOWER(${expr}) LIKE LOWER(${val})`,
49
+ caseInsensitiveNotLike: (expr, val) => `LOWER(${expr}) NOT LIKE LOWER(${val})`,
50
+ jsonColumnType: "JSON",
51
+ arrayLength: (path) => `json_array_length(data, '$.${path}')`,
52
+ jsonEachFrom: (arrPath, alias) => `json_each(data, '$.${arrPath}') AS ${alias}`,
53
+ jsonExtractElement: (alias, subPath) => `json_extract(${alias}.value, '$.${subPath}')`,
54
+ serializeJsonValue: (v) => v,
55
+ // SQLite stores JSON booleans as integers (0/1) and better-sqlite3 refuses
56
+ // to bind JS booleans, so coerce them to integers for WHERE params.
57
+ serializeScalar: (v) => typeof v === "boolean" ? (v ? 1 : 0) : v
58
+ }
59
+
60
+ export const pgDialect: SQLDialect = {
61
+ jsonExtract: (path) => {
62
+ const parts = path.split(".")
63
+ if (parts.length === 1) return `data->>'${parts[0]}'`
64
+ const last = parts.pop()!
65
+ return `data${parts.map((p) => `->'${p}'`).join("")}->>'${last}'`
66
+ },
67
+ jsonExtractJson: (path) => {
68
+ const parts = path.split(".")
69
+ if (parts.length === 1) return `data->'${parts[0]}'`
70
+ return `data${parts.map((p) => `->'${p}'`).join("")}`
71
+ },
72
+ placeholder: (index) => `$${index}`,
73
+ jsonArrayContains: (arrPath, val) => {
74
+ const parts = arrPath.split(".")
75
+ const jsonPath = parts.length === 1
76
+ ? `data->'${parts[0]}'`
77
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
78
+ return `${jsonPath} @> ${val}::jsonb`
79
+ },
80
+ jsonArrayNotContains: (arrPath, val) => {
81
+ const parts = arrPath.split(".")
82
+ const jsonPath = parts.length === 1
83
+ ? `data->'${parts[0]}'`
84
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
85
+ return `NOT (${jsonPath} @> ${val}::jsonb)`
86
+ },
87
+ jsonArrayContainsAny: (arrPath, vals) => {
88
+ const parts = arrPath.split(".")
89
+ const jsonPath = parts.length === 1
90
+ ? `data->'${parts[0]}'`
91
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
92
+ return `(${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" OR ")})`
93
+ },
94
+ jsonArrayNotContainsAny: (arrPath, vals) => {
95
+ const parts = arrPath.split(".")
96
+ const jsonPath = parts.length === 1
97
+ ? `data->'${parts[0]}'`
98
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
99
+ return `NOT (${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" OR ")})`
100
+ },
101
+ jsonArrayContainsAll: (arrPath, vals) => {
102
+ const parts = arrPath.split(".")
103
+ const jsonPath = parts.length === 1
104
+ ? `data->'${parts[0]}'`
105
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
106
+ return vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" AND ")
107
+ },
108
+ jsonArrayNotContainsAll: (arrPath, vals) => {
109
+ const parts = arrPath.split(".")
110
+ const jsonPath = parts.length === 1
111
+ ? `data->'${parts[0]}'`
112
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
113
+ return `NOT (${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" AND ")})`
114
+ },
115
+ caseInsensitiveLike: (expr, val) => `${expr} ILIKE ${val}`,
116
+ caseInsensitiveNotLike: (expr, val) => `${expr} NOT ILIKE ${val}`,
117
+ jsonColumnType: "JSONB",
118
+ arrayLength: (path) => `jsonb_array_length(data->'${path}')`,
119
+ jsonEachFrom: (arrPath, alias) => {
120
+ const parts = arrPath.split(".")
121
+ const jsonPath = parts.length === 1
122
+ ? `data->'${parts[0]}'`
123
+ : `data${parts.map((p) => `->'${p}'`).join("")}`
124
+ return `jsonb_array_elements(${jsonPath}) AS ${alias}`
125
+ },
126
+ jsonExtractElement: (alias, subPath) => {
127
+ const parts = subPath.split(".")
128
+ if (parts.length === 1) return `${alias}->>'${parts[0]}'`
129
+ const last = parts.pop()!
130
+ return `${alias}${parts.map((p) => `->'${p}'`).join("")}->>'${last}'`
131
+ },
132
+ serializeJsonValue: (v) => JSON.stringify(v),
133
+ // PG's ->> operator yields text, so compare booleans as 'true'/'false' text.
134
+ serializeScalar: (v) => typeof v === "boolean" ? (v ? "true" : "false") : v
135
+ }
136
+
137
+ export function logQuery(q: { sql: string; params: unknown[] }) {
138
+ return InfraLogger
139
+ .logDebug("sql query")
140
+ .pipe(Effect.annotateLogs({
141
+ query: q.sql,
142
+ parameters: JSON.stringify(q.params, undefined, 2)
143
+ }))
144
+ }
145
+
146
+ const dottedToJsonPath = (path: string) =>
147
+ path
148
+ .split(".")
149
+ .filter((p) => p !== "-1")
150
+ .join(".")
151
+
152
+ const sqlStringLiteral = (value: string) => `'${value.replaceAll("'", "''")}'`
153
+
154
+ export function buildWhereSQLQuery(
155
+ dialect: SQLDialect,
156
+ idKey: PropertyKey,
157
+ filter: readonly FilterResult[],
158
+ tableName: string,
159
+ defaultValues: Record<string, unknown>,
160
+ select?: NonEmptyReadonlyArray<
161
+ string | {
162
+ key: string
163
+ subKeys: readonly string[]
164
+ } | {
165
+ key: string
166
+ computed: ComputedProjectionIrExpression
167
+ }
168
+ >,
169
+ order?: NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }>,
170
+ skip?: number,
171
+ limit?: number,
172
+ namespace?: string
173
+ ) {
174
+ const params: unknown[] = []
175
+ let paramIndex = 1
176
+
177
+ const addParam = (value: unknown): string => {
178
+ params.push(dialect.serializeScalar(value))
179
+ return dialect.placeholder(paramIndex++)
180
+ }
181
+
182
+ const fieldExpr = (path: string, relation?: string): string => {
183
+ if (path === idKey || path === "id") return "id"
184
+ if (relation && path.includes(".-1.")) {
185
+ const subPath = path.split(".-1.")[1]!
186
+ if (subPath.endsWith(".length")) {
187
+ // TODO: array length inside relation element
188
+ return dialect.jsonExtractElement(`_${relation}`, subPath.slice(0, -".length".length))
189
+ }
190
+ return dialect.jsonExtractElement(`_${relation}`, subPath)
191
+ }
192
+ if (path.endsWith(".length")) {
193
+ const arrPath = dottedToJsonPath(path.slice(0, -".length".length))
194
+ return dialect.arrayLength(arrPath)
195
+ }
196
+ const jsonPath = dottedToJsonPath(path)
197
+ const expr = dialect.jsonExtract(jsonPath)
198
+ const topKey = path.split(".")[0]
199
+ if (topKey in defaultValues) {
200
+ return `COALESCE(${expr}, ${addParam(defaultValues[topKey])})`
201
+ }
202
+ return expr
203
+ }
204
+
205
+ const statement = (x: FilterR, relation?: string): string => {
206
+ const resolvedPath = x.path === idKey ? "id" : x.path
207
+ const k = fieldExpr(resolvedPath, relation)
208
+
209
+ switch (x.op) {
210
+ case "in": {
211
+ const vals = x.value as unknown as readonly unknown[]
212
+ const hasNull = vals.some((v) => v == null)
213
+ const nonNullVals = vals.filter((v) => v != null)
214
+ const parts: string[] = []
215
+ if (nonNullVals.length > 0) {
216
+ const placeholders = nonNullVals.map((v) => addParam(v))
217
+ parts.push(`${k} IN (${placeholders.join(", ")})`)
218
+ }
219
+ if (hasNull) parts.push(`${k} IS NULL`)
220
+ return parts.length > 1 ? `(${parts.join(" OR ")})` : parts[0] ?? "1=0"
221
+ }
222
+ case "notIn": {
223
+ const vals = x.value as unknown as readonly unknown[]
224
+ const hasNull = vals.some((v) => v == null)
225
+ const nonNullVals = vals.filter((v) => v != null)
226
+ const parts: string[] = []
227
+ if (nonNullVals.length > 0) {
228
+ const placeholders = nonNullVals.map((v) => addParam(v))
229
+ parts.push(`${k} NOT IN (${placeholders.join(", ")})`)
230
+ }
231
+ if (hasNull) parts.push(`${k} IS NOT NULL`)
232
+ return parts.length > 1 ? `(${parts.join(" AND ")})` : parts[0] ?? "1=1"
233
+ }
234
+
235
+ case "includes": {
236
+ const arrPath = dottedToJsonPath(resolvedPath)
237
+ const v = addParam(x.value)
238
+ return dialect.jsonArrayContains(arrPath, v)
239
+ }
240
+ case "notIncludes": {
241
+ const arrPath = dottedToJsonPath(resolvedPath)
242
+ const v = addParam(x.value)
243
+ return dialect.jsonArrayNotContains(arrPath, v)
244
+ }
245
+
246
+ case "includes-any": {
247
+ const arrPath = dottedToJsonPath(resolvedPath)
248
+ const vals = x.value as unknown as readonly unknown[]
249
+ const placeholders = vals.map((v) => addParam(dialect.serializeJsonValue(v)))
250
+ return dialect.jsonArrayContainsAny(arrPath, placeholders)
251
+ }
252
+ case "notIncludes-any": {
253
+ const arrPath = dottedToJsonPath(resolvedPath)
254
+ const vals = x.value as unknown as readonly unknown[]
255
+ const placeholders = vals.map((v) => addParam(dialect.serializeJsonValue(v)))
256
+ return dialect.jsonArrayNotContainsAny(arrPath, placeholders)
257
+ }
258
+
259
+ case "includes-all": {
260
+ const arrPath = dottedToJsonPath(resolvedPath)
261
+ const vals = x.value as unknown as readonly unknown[]
262
+ const placeholders = vals.map((v) => addParam(dialect.serializeJsonValue(v)))
263
+ return dialect.jsonArrayContainsAll(arrPath, placeholders)
264
+ }
265
+ case "notIncludes-all": {
266
+ const arrPath = dottedToJsonPath(resolvedPath)
267
+ const vals = x.value as unknown as readonly unknown[]
268
+ const placeholders = vals.map((v) => addParam(dialect.serializeJsonValue(v)))
269
+ return dialect.jsonArrayNotContainsAll(arrPath, placeholders)
270
+ }
271
+
272
+ case "contains": {
273
+ const v = addParam(`%${x.value}%`)
274
+ return dialect.caseInsensitiveLike(k, v)
275
+ }
276
+ case "notContains": {
277
+ const v = addParam(`%${x.value}%`)
278
+ return dialect.caseInsensitiveNotLike(k, v)
279
+ }
280
+ case "startsWith": {
281
+ const v = addParam(`${x.value}%`)
282
+ return dialect.caseInsensitiveLike(k, v)
283
+ }
284
+ case "notStartsWith": {
285
+ const v = addParam(`${x.value}%`)
286
+ return dialect.caseInsensitiveNotLike(k, v)
287
+ }
288
+ case "endsWith": {
289
+ const v = addParam(`%${x.value}`)
290
+ return dialect.caseInsensitiveLike(k, v)
291
+ }
292
+ case "notEndsWith": {
293
+ const v = addParam(`%${x.value}`)
294
+ return dialect.caseInsensitiveNotLike(k, v)
295
+ }
296
+
297
+ case "lt": {
298
+ const v = addParam(x.value)
299
+ return `${k} < ${v}`
300
+ }
301
+ case "lte": {
302
+ const v = addParam(x.value)
303
+ return `${k} <= ${v}`
304
+ }
305
+ case "gt": {
306
+ const v = addParam(x.value)
307
+ return `${k} > ${v}`
308
+ }
309
+ case "gte": {
310
+ const v = addParam(x.value)
311
+ return `${k} >= ${v}`
312
+ }
313
+ case "neq": {
314
+ if (x.value === null) return `${k} IS NOT NULL`
315
+ const v = addParam(x.value)
316
+ return `${k} <> ${v}`
317
+ }
318
+ case undefined:
319
+ case "eq": {
320
+ if (x.value === null) return `${k} IS NULL`
321
+ const v = addParam(x.value)
322
+ return `${k} = ${v}`
323
+ }
324
+ default:
325
+ return assertUnreachable(x.op)
326
+ }
327
+ }
328
+
329
+ const wrapRelation = (rel: string, inner: string, every: boolean): string => {
330
+ // Optimize tautological/contradictory conditions
331
+ if (every && inner === "1=1") return "1=1"
332
+ if (!every && inner === "1=0") return "1=0"
333
+ const from = dialect.jsonEachFrom(rel, `_${rel}`)
334
+ // ∀x.P(x) ≡ ¬∃x.¬P(x), i.e. NOT EXISTS(... WHERE NOT P)
335
+ return every
336
+ ? `NOT EXISTS(SELECT 1 FROM ${from} WHERE NOT (${inner}))`
337
+ : `EXISTS(SELECT 1 FROM ${from} WHERE ${inner})`
338
+ }
339
+
340
+ const print = (state: readonly FilterResult[], isRelation: string | null, every: boolean): string => {
341
+ let s = ""
342
+ for (const e of state) {
343
+ switch (e.t) {
344
+ case "where":
345
+ s += statement(e, isRelation ?? undefined)
346
+ break
347
+ case "or":
348
+ s += ` OR ${statement(e, isRelation ?? undefined)}`
349
+ break
350
+ case "and":
351
+ s += ` AND ${statement(e, isRelation ?? undefined)}`
352
+ break
353
+ case "or-scope": {
354
+ if (!every) every = e.relation === "every"
355
+ const rel = isRelationCheck(e.result, isRelation)
356
+ if (rel) {
357
+ s += isRelation
358
+ ? ` OR (${print(e.result, rel, every)})`
359
+ : ` OR ${wrapRelation(rel, print(e.result, rel, every), every)}`
360
+ } else {
361
+ s += ` OR (${print(e.result, null, every)})`
362
+ }
363
+ break
364
+ }
365
+ case "and-scope": {
366
+ if (!every) every = e.relation === "every"
367
+ const rel = isRelationCheck(e.result, isRelation)
368
+ if (rel) {
369
+ s += isRelation
370
+ ? ` AND (${print(e.result, rel, every)})`
371
+ : ` AND ${wrapRelation(rel, print(e.result, rel, every), every)}`
372
+ } else {
373
+ s += ` AND (${print(e.result, null, every)})`
374
+ }
375
+ break
376
+ }
377
+ case "where-scope": {
378
+ if (!every) every = e.relation === "every"
379
+ const rel = isRelationCheck(e.result, isRelation)
380
+ if (rel) {
381
+ s += isRelation
382
+ ? `(${print(e.result, rel, every)})`
383
+ : wrapRelation(rel, print(e.result, rel, every), every)
384
+ } else {
385
+ s += `(${print(e.result, null, every)})`
386
+ }
387
+ break
388
+ }
389
+ }
390
+ }
391
+ return s
392
+ }
393
+
394
+ const computedSelectExpr = (key: string, computed: ComputedProjectionIrExpression): string => {
395
+ const relationPath = dottedToJsonPath(computed.path)
396
+ const relationAlias = `_${computed.path}`
397
+ const relationFrom = dialect.jsonEachFrom(relationPath, relationAlias)
398
+ const toNumber = (expr: string) =>
399
+ dialect.jsonColumnType === "JSON" ? `CAST(${expr} AS REAL)` : `(${expr})::numeric`
400
+ const compileExpr = (expression: ComputedProjectionMathIrExpression): string => {
401
+ switch (expression._tag) {
402
+ case "field":
403
+ return toNumber(dialect.jsonExtractElement(relationAlias, expression.field))
404
+ case "mul":
405
+ return `(${compileExpr(expression.left)} * ${compileExpr(expression.right)})`
406
+ default:
407
+ return assertUnreachable(expression)
408
+ }
409
+ }
410
+ const factorCaseExpr = (unitExpr: string, toBase: string, factors: Readonly<Record<string, number>>) => {
411
+ const entries = Object.entries(factors).filter(([, factor]) => Number.isFinite(factor))
412
+ const cases = entries.map(([unit, factor]) => ` WHEN ${sqlStringLiteral(unit)} THEN ${factor}`).join("")
413
+ return `CASE ${unitExpr} WHEN ${sqlStringLiteral(toBase)} THEN 1${cases} ELSE NULL END`
414
+ }
415
+ const whereClause = () =>
416
+ computed.filter.length > 0
417
+ ? ` WHERE ${print(computed.filter, computed.path, false)}`
418
+ : ""
419
+ const boolExpr = (sqlExpr: string) =>
420
+ dialect.jsonColumnType === "JSON"
421
+ ? `CASE WHEN ${sqlExpr} THEN 'true' ELSE 'false' END AS "${key}"`
422
+ : `${sqlExpr} AS "${key}"`
423
+ switch (computed._tag) {
424
+ case "relation-count":
425
+ return `(SELECT COUNT(1) FROM ${relationFrom}${whereClause()}) AS "${key}"`
426
+ case "relation-any":
427
+ return boolExpr(`EXISTS(SELECT 1 FROM ${relationFrom}${whereClause()})`)
428
+ case "relation-every":
429
+ // ∀x.P(x) ≡ ¬∃x.¬P(x). When no filter, no element exists that violates ⊤ → true.
430
+ return boolExpr(
431
+ computed.filter.length === 0
432
+ ? `1=1`
433
+ : `NOT EXISTS(SELECT 1 FROM ${relationFrom} WHERE NOT (${print(computed.filter, computed.path, false)}))`
434
+ )
435
+ case "relation-distinct-count": {
436
+ const fieldExtract = dialect.jsonExtractElement(relationAlias, computed.field)
437
+ return `(SELECT COUNT(DISTINCT ${fieldExtract}) FROM ${relationFrom}${whereClause()}) AS "${key}"`
438
+ }
439
+ case "relation-sum": {
440
+ const fieldExtract = dialect.jsonExtractElement(relationAlias, computed.field)
441
+ return `(SELECT COALESCE(SUM(${toNumber(fieldExtract)}), 0) FROM ${relationFrom}${whereClause()}) AS "${key}"`
442
+ }
443
+ case "relation-sum-expr": {
444
+ const expression = compileExpr(computed.expression)
445
+ return `(SELECT COALESCE(SUM(${expression}), 0) FROM ${relationFrom}${whereClause()}) AS "${key}"`
446
+ }
447
+ case "relation-sum-expr-by": {
448
+ const expression = compileExpr(computed.expression)
449
+ const unitExpr = dialect.jsonExtractElement(relationAlias, computed.unit)
450
+ if (dialect.jsonColumnType === "JSON") {
451
+ return `(SELECT COALESCE(json_group_array(json_object('unit', __unit, 'total', __total)), json_array()) FROM (SELECT ${unitExpr} AS __unit, COALESCE(SUM(${expression}), 0) AS __total FROM ${relationFrom}${whereClause()} GROUP BY ${unitExpr})) AS "${key}"`
452
+ }
453
+ return `(SELECT COALESCE(jsonb_agg(jsonb_build_object('unit', __unit, 'total', __total)), '[]'::jsonb) FROM (SELECT ${unitExpr} AS __unit, COALESCE(SUM(${expression}), 0) AS __total FROM ${relationFrom}${whereClause()} GROUP BY ${unitExpr}) __grouped) AS "${key}"`
454
+ }
455
+ case "relation-sum-expr-normalized": {
456
+ const expression = compileExpr(computed.expression)
457
+ const unitExpr = dialect.jsonExtractElement(relationAlias, computed.unit)
458
+ const factorExpr = factorCaseExpr(unitExpr, computed.toBase, computed.factors)
459
+ return `(SELECT COALESCE(SUM((${expression}) * (${factorExpr})), 0) FROM ${relationFrom}${whereClause()}) AS "${key}"`
460
+ }
461
+ case "relation-collect": {
462
+ const fieldExtract = dialect.jsonExtractElement(relationAlias, computed.field)
463
+ if (dialect.jsonColumnType === "JSON") {
464
+ // sqlite: json_group_array does not accept DISTINCT; emulate via inner DISTINCT subquery
465
+ if (computed.distinct) {
466
+ return `(SELECT COALESCE(json_group_array(__v), json_array()) FROM (SELECT DISTINCT ${fieldExtract} AS __v FROM ${relationFrom}${whereClause()})) AS "${key}"`
467
+ }
468
+ return `(SELECT COALESCE(json_group_array(${fieldExtract}), json_array()) FROM ${relationFrom}${whereClause()}) AS "${key}"`
469
+ }
470
+ const aggArg = computed.distinct ? `DISTINCT ${fieldExtract}` : fieldExtract
471
+ return `(SELECT COALESCE(jsonb_agg(${aggArg}), '[]'::jsonb) FROM ${relationFrom}${whereClause()}) AS "${key}"`
472
+ }
473
+ case "relation-collect-fields": {
474
+ const branches = computed.fields.map((field) => {
475
+ const fieldExtract = dialect.jsonExtractElement(relationAlias, field)
476
+ return `SELECT ${fieldExtract} AS __v FROM ${relationFrom}${whereClause()}`
477
+ })
478
+ const unionQuery = branches.join(" UNION ALL ")
479
+ if (dialect.jsonColumnType === "JSON") {
480
+ if (computed.distinct) {
481
+ return `(SELECT COALESCE(json_group_array(__v), json_array()) FROM (SELECT DISTINCT __v FROM (${unionQuery}))) AS "${key}"`
482
+ }
483
+ return `(SELECT COALESCE(json_group_array(__v), json_array()) FROM (${unionQuery})) AS "${key}"`
484
+ }
485
+ if (computed.distinct) {
486
+ return `(SELECT COALESCE(jsonb_agg(__v), '[]'::jsonb) FROM (SELECT DISTINCT __v FROM (${unionQuery}) inner_q) outer_q) AS "${key}"`
487
+ }
488
+ return `(SELECT COALESCE(jsonb_agg(__v), '[]'::jsonb) FROM (${unionQuery}) t) AS "${key}"`
489
+ }
490
+ default:
491
+ return assertUnreachable(computed)
492
+ }
493
+ }
494
+
495
+ const getSelectExpr = (): string => {
496
+ if (!select) return "id, _etag, data"
497
+ const fields = select.map((s) => {
498
+ if (typeof s === "string") {
499
+ if (s === idKey || s === "id") return `id`
500
+ if (s === "_etag") return `_etag`
501
+ return `${dialect.jsonExtractJson(s)} AS "${s}"`
502
+ }
503
+ if ("computed" in s) {
504
+ return computedSelectExpr(s.key, s.computed)
505
+ }
506
+ return `${dialect.jsonExtractJson(s.key)} AS "${s.key}"`
507
+ })
508
+ return fields.join(", ")
509
+ }
510
+
511
+ // Order matters: projection params must be emitted BEFORE user-filter
512
+ // params so positional `?` placeholders in SQLite match `params[]` order.
513
+ const selectExpr = getSelectExpr()
514
+
515
+ const namespaceClause = namespace !== undefined
516
+ ? `_namespace = ${addParam(namespace)}`
517
+ : ""
518
+ const userWhere = filter.length
519
+ ? print([{ t: "where-scope", result: filter, relation: "some" }], null, false)
520
+ : ""
521
+ const whereClause = namespaceClause && userWhere
522
+ ? `WHERE ${namespaceClause} AND ${userWhere}`
523
+ : namespaceClause
524
+ ? `WHERE ${namespaceClause}`
525
+ : userWhere
526
+ ? `WHERE ${userWhere}`
527
+ : ""
528
+
529
+ const orderClause = order
530
+ ? `ORDER BY ${order.map((_) => `${fieldExpr(_.key)} ${_.direction}`).join(", ")}`
531
+ : ""
532
+
533
+ const limitClause = limit !== undefined || skip !== undefined
534
+ ? `LIMIT ${addParam(limit ?? 999999)} OFFSET ${addParam(skip ?? 0)}`
535
+ : ""
536
+
537
+ const sql = `SELECT ${selectExpr} FROM "${tableName}" ${whereClause} ${orderClause} ${limitClause}`.trim()
538
+
539
+ return { sql, params }
540
+ }