@aurabase/aurabase-js 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/.changeset/config.json +11 -0
  2. package/CHANGELOG.md +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +833 -0
  5. package/package.json +90 -0
  6. package/packages/ai/LICENSE +21 -0
  7. package/packages/ai/README.md +106 -0
  8. package/packages/ai/dist/index.cjs +229 -0
  9. package/packages/ai/dist/index.d.cts +139 -0
  10. package/packages/ai/dist/index.d.ts +139 -0
  11. package/packages/ai/dist/index.js +226 -0
  12. package/packages/ai/node_modules/.bin/esbuild +19 -0
  13. package/packages/ai/node_modules/.bin/tsc +22 -0
  14. package/packages/ai/node_modules/.bin/tsserver +22 -0
  15. package/packages/ai/node_modules/.bin/tsup +22 -0
  16. package/packages/ai/node_modules/.bin/tsup-node +22 -0
  17. package/packages/ai/node_modules/.bin/vite +22 -0
  18. package/packages/ai/node_modules/.bin/vitest +22 -0
  19. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  20. package/packages/ai/package.json +61 -0
  21. package/packages/ai/src/AiService.ts +226 -0
  22. package/packages/ai/src/index.ts +18 -0
  23. package/packages/ai/src/sse.ts +141 -0
  24. package/packages/ai/src/types.ts +30 -0
  25. package/packages/ai/tests/chat-stream.test.ts +151 -0
  26. package/packages/ai/tests/rag-namespaces.test.ts +54 -0
  27. package/packages/ai/tests/sse.test.ts +69 -0
  28. package/packages/ai/tsconfig.json +5 -0
  29. package/packages/ai/tsup.config.ts +11 -0
  30. package/packages/aurabase/LICENSE +21 -0
  31. package/packages/aurabase/README.md +64 -0
  32. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
  33. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
  34. package/packages/aurabase/dist/index.cjs +237 -0
  35. package/packages/aurabase/dist/index.d.cts +46 -0
  36. package/packages/aurabase/dist/index.d.ts +46 -0
  37. package/packages/aurabase/dist/index.js +177 -0
  38. package/packages/aurabase/dist/ssr.cjs +224 -0
  39. package/packages/aurabase/dist/ssr.d.cts +65 -0
  40. package/packages/aurabase/dist/ssr.d.ts +65 -0
  41. package/packages/aurabase/dist/ssr.js +214 -0
  42. package/packages/aurabase/node_modules/.bin/esbuild +19 -0
  43. package/packages/aurabase/node_modules/.bin/tsc +22 -0
  44. package/packages/aurabase/node_modules/.bin/tsserver +22 -0
  45. package/packages/aurabase/node_modules/.bin/tsup +22 -0
  46. package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
  47. package/packages/aurabase/node_modules/.bin/vite +22 -0
  48. package/packages/aurabase/node_modules/.bin/vitest +22 -0
  49. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  50. package/packages/aurabase/package.json +80 -0
  51. package/packages/aurabase/src/AuraClient.ts +237 -0
  52. package/packages/aurabase/src/index.ts +152 -0
  53. package/packages/aurabase/src/ssr.ts +156 -0
  54. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
  55. package/packages/aurabase/tests/ai.test.ts +111 -0
  56. package/packages/aurabase/tests/auth-admin.test.ts +515 -0
  57. package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
  58. package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
  59. package/packages/aurabase/tests/auth.test.ts +947 -0
  60. package/packages/aurabase/tests/database.test.ts +230 -0
  61. package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
  62. package/packages/aurabase/tests/dispose.test.ts +23 -0
  63. package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
  64. package/packages/aurabase/tests/functions.test.ts +107 -0
  65. package/packages/aurabase/tests/integration.test.ts +413 -0
  66. package/packages/aurabase/tests/live-production-validation.js +241 -0
  67. package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
  68. package/packages/aurabase/tests/mocks.ts +139 -0
  69. package/packages/aurabase/tests/notifications.test.ts +314 -0
  70. package/packages/aurabase/tests/observability-backup.test.ts +186 -0
  71. package/packages/aurabase/tests/observability-hook.test.ts +130 -0
  72. package/packages/aurabase/tests/production-robustness.test.ts +268 -0
  73. package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
  74. package/packages/aurabase/tests/query-builder.test.ts +329 -0
  75. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
  76. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
  77. package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
  78. package/packages/aurabase/tests/retry-option.test.ts +17 -0
  79. package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
  80. package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
  81. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
  82. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
  83. package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
  84. package/packages/aurabase/tests/ssr.test.ts +91 -0
  85. package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
  86. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
  87. package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
  88. package/packages/aurabase/tests/storage.test.ts +55 -0
  89. package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
  90. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
  91. package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
  92. package/packages/aurabase/tsconfig.json +5 -0
  93. package/packages/aurabase/tsconfig.test-d.json +5 -0
  94. package/packages/aurabase/tsup.config.ts +11 -0
  95. package/packages/aurabase/vitest.config.ts +16 -0
  96. package/packages/aurabase/vitest.e2e.config.ts +9 -0
  97. package/packages/auth/LICENSE +21 -0
  98. package/packages/auth/README.md +215 -0
  99. package/packages/auth/dist/index.cjs +1175 -0
  100. package/packages/auth/dist/index.d.cts +397 -0
  101. package/packages/auth/dist/index.d.ts +397 -0
  102. package/packages/auth/dist/index.js +1170 -0
  103. package/packages/auth/node_modules/.bin/esbuild +19 -0
  104. package/packages/auth/node_modules/.bin/tsc +22 -0
  105. package/packages/auth/node_modules/.bin/tsserver +22 -0
  106. package/packages/auth/node_modules/.bin/tsup +22 -0
  107. package/packages/auth/node_modules/.bin/tsup-node +22 -0
  108. package/packages/auth/node_modules/.bin/vite +22 -0
  109. package/packages/auth/node_modules/.bin/vitest +22 -0
  110. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  111. package/packages/auth/package.json +61 -0
  112. package/packages/auth/src/AuthAdminService.ts +149 -0
  113. package/packages/auth/src/AuthService.ts +1166 -0
  114. package/packages/auth/src/SessionManager.ts +92 -0
  115. package/packages/auth/src/index.ts +17 -0
  116. package/packages/auth/src/jwks.ts +337 -0
  117. package/packages/auth/src/pkce.ts +27 -0
  118. package/packages/auth/src/types.ts +89 -0
  119. package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
  120. package/packages/auth/tests/detect-session.test.ts +361 -0
  121. package/packages/auth/tests/getclaims.test.ts +780 -0
  122. package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
  123. package/packages/auth/tests/password-recovery.test.ts +124 -0
  124. package/packages/auth/tests/pkce.test.ts +192 -0
  125. package/packages/auth/tests/session-manager.test.ts +103 -0
  126. package/packages/auth/tests/set-session.test.ts +139 -0
  127. package/packages/auth/tests/verify-otp.test.ts +153 -0
  128. package/packages/auth/tsconfig.json +5 -0
  129. package/packages/auth/tsup.config.ts +11 -0
  130. package/packages/core/LICENSE +21 -0
  131. package/packages/core/README.md +132 -0
  132. package/packages/core/dist/index.cjs +695 -0
  133. package/packages/core/dist/index.d.cts +1137 -0
  134. package/packages/core/dist/index.d.ts +1137 -0
  135. package/packages/core/dist/index.js +673 -0
  136. package/packages/core/node_modules/.bin/esbuild +22 -0
  137. package/packages/core/node_modules/.bin/tsc +22 -0
  138. package/packages/core/node_modules/.bin/tsserver +22 -0
  139. package/packages/core/node_modules/.bin/tsup +22 -0
  140. package/packages/core/node_modules/.bin/tsup-node +22 -0
  141. package/packages/core/node_modules/.bin/vite +22 -0
  142. package/packages/core/node_modules/.bin/vitest +22 -0
  143. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  144. package/packages/core/package.json +57 -0
  145. package/packages/core/src/AuraError.ts +89 -0
  146. package/packages/core/src/BaseService.ts +44 -0
  147. package/packages/core/src/client.ts +34 -0
  148. package/packages/core/src/cookie-utils.ts +42 -0
  149. package/packages/core/src/fetch.ts +435 -0
  150. package/packages/core/src/index.ts +22 -0
  151. package/packages/core/src/retry.ts +63 -0
  152. package/packages/core/src/serialize.ts +6 -0
  153. package/packages/core/src/stores/BaseAuthStore.ts +164 -0
  154. package/packages/core/src/stores/LocalAuthStore.ts +199 -0
  155. package/packages/core/src/types.ts +1042 -0
  156. package/packages/core/tests/auth-store.test.ts +300 -0
  157. package/packages/core/tests/error-code.test.ts +61 -0
  158. package/packages/core/tests/error.test.ts +24 -0
  159. package/packages/core/tests/fetch.test.ts +286 -0
  160. package/packages/core/tests/mocks.ts +139 -0
  161. package/packages/core/tests/observability.test.ts +86 -0
  162. package/packages/core/tests/refresh-hardening.test.ts +164 -0
  163. package/packages/core/tests/refresh-observability.test.ts +111 -0
  164. package/packages/core/tests/retry.test.ts +106 -0
  165. package/packages/core/tests/serialize.test.ts +12 -0
  166. package/packages/core/tests/timeout.test.ts +79 -0
  167. package/packages/core/tsconfig.json +5 -0
  168. package/packages/core/tsup.config.ts +11 -0
  169. package/packages/db/LICENSE +21 -0
  170. package/packages/db/README.md +152 -0
  171. package/packages/db/VENDORED.md +93 -0
  172. package/packages/db/dist/index.cjs +544 -0
  173. package/packages/db/dist/index.d.cts +1111 -0
  174. package/packages/db/dist/index.d.ts +1111 -0
  175. package/packages/db/dist/index.js +541 -0
  176. package/packages/db/node_modules/.bin/esbuild +19 -0
  177. package/packages/db/node_modules/.bin/tsc +22 -0
  178. package/packages/db/node_modules/.bin/tsserver +22 -0
  179. package/packages/db/node_modules/.bin/tsup +22 -0
  180. package/packages/db/node_modules/.bin/tsup-node +22 -0
  181. package/packages/db/node_modules/.bin/vite +22 -0
  182. package/packages/db/node_modules/.bin/vitest +22 -0
  183. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  184. package/packages/db/package.json +62 -0
  185. package/packages/db/src/DatabaseService.ts +170 -0
  186. package/packages/db/src/QueryBuilder.ts +830 -0
  187. package/packages/db/src/index.ts +2 -0
  188. package/packages/db/src/pgrst-types/common/common.ts +86 -0
  189. package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
  190. package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
  191. package/packages/db/src/pgrst-types/types.ts +134 -0
  192. package/packages/db/src/select-query-parser/parser.ts +479 -0
  193. package/packages/db/src/select-query-parser/result.ts +553 -0
  194. package/packages/db/src/select-query-parser/types.ts +132 -0
  195. package/packages/db/src/select-query-parser/utils.ts +707 -0
  196. package/packages/db/tests/filters.test.ts +81 -0
  197. package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
  198. package/packages/db/tests/mutations-filtered.test.ts +188 -0
  199. package/packages/db/tests/order.test.ts +45 -0
  200. package/packages/db/tests/rpc.test.ts +41 -0
  201. package/packages/db/tests/scale/build-large-schema.ts +94 -0
  202. package/packages/db/tests/scale/scale-compile.test.ts +167 -0
  203. package/packages/db/tests/schema.test.ts +77 -0
  204. package/packages/db/tests/single-accept.e2e.test.ts +123 -0
  205. package/packages/db/tests/single.test.ts +211 -0
  206. package/packages/db/tests/types/filters.test-d.ts +52 -0
  207. package/packages/db/tests/types/fixtures.ts +40 -0
  208. package/packages/db/tests/types/from-views.test-d.ts +18 -0
  209. package/packages/db/tests/types/mutations.test-d.ts +23 -0
  210. package/packages/db/tests/types/rpc.test-d.ts +60 -0
  211. package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
  212. package/packages/db/tests/types/select-inference.test-d.ts +38 -0
  213. package/packages/db/tests/vendored/fixtures.ts +83 -0
  214. package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
  215. package/packages/db/tsconfig.json +5 -0
  216. package/packages/db/tsconfig.test-d.json +5 -0
  217. package/packages/db/tsup.config.ts +11 -0
  218. package/packages/db/vitest.config.ts +8 -0
  219. package/packages/functions/LICENSE +21 -0
  220. package/packages/functions/README.md +96 -0
  221. package/packages/functions/dist/index.cjs +173 -0
  222. package/packages/functions/dist/index.d.cts +81 -0
  223. package/packages/functions/dist/index.d.ts +81 -0
  224. package/packages/functions/dist/index.js +168 -0
  225. package/packages/functions/node_modules/.bin/esbuild +19 -0
  226. package/packages/functions/node_modules/.bin/tsc +22 -0
  227. package/packages/functions/node_modules/.bin/tsserver +22 -0
  228. package/packages/functions/node_modules/.bin/tsup +22 -0
  229. package/packages/functions/node_modules/.bin/tsup-node +22 -0
  230. package/packages/functions/node_modules/.bin/vite +22 -0
  231. package/packages/functions/node_modules/.bin/vitest +22 -0
  232. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  233. package/packages/functions/package.json +60 -0
  234. package/packages/functions/src/FunctionsService.ts +188 -0
  235. package/packages/functions/src/errors.ts +70 -0
  236. package/packages/functions/src/index.ts +6 -0
  237. package/packages/functions/tests/invoke.test.ts +97 -0
  238. package/packages/functions/tests/response-type-errors.test.ts +135 -0
  239. package/packages/functions/tsconfig.json +5 -0
  240. package/packages/functions/tsup.config.ts +11 -0
  241. package/packages/gen/LICENSE +21 -0
  242. package/packages/gen/README.md +84 -0
  243. package/packages/gen/dist/cli.cjs +334 -0
  244. package/packages/gen/dist/cli.d.cts +1 -0
  245. package/packages/gen/dist/cli.d.ts +1 -0
  246. package/packages/gen/dist/cli.js +332 -0
  247. package/packages/gen/dist/index.cjs +257 -0
  248. package/packages/gen/dist/index.d.cts +12 -0
  249. package/packages/gen/dist/index.d.ts +12 -0
  250. package/packages/gen/dist/index.js +255 -0
  251. package/packages/gen/node_modules/.bin/esbuild +19 -0
  252. package/packages/gen/node_modules/.bin/tsc +22 -0
  253. package/packages/gen/node_modules/.bin/tsserver +22 -0
  254. package/packages/gen/node_modules/.bin/tsup +22 -0
  255. package/packages/gen/node_modules/.bin/tsup-node +22 -0
  256. package/packages/gen/node_modules/.bin/vite +22 -0
  257. package/packages/gen/node_modules/.bin/vitest +22 -0
  258. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  259. package/packages/gen/package.json +66 -0
  260. package/packages/gen/src/cli.ts +67 -0
  261. package/packages/gen/src/generate.ts +0 -0
  262. package/packages/gen/src/index.ts +2 -0
  263. package/packages/gen/tests/compile.test.ts +213 -0
  264. package/packages/gen/tests/e2e.test.ts +130 -0
  265. package/packages/gen/tests/generate.test.ts +396 -0
  266. package/packages/gen/tsconfig.json +5 -0
  267. package/packages/gen/tsup.config.ts +11 -0
  268. package/packages/notifications/LICENSE +21 -0
  269. package/packages/notifications/README.md +134 -0
  270. package/packages/notifications/dist/index.cjs +128 -0
  271. package/packages/notifications/dist/index.d.cts +81 -0
  272. package/packages/notifications/dist/index.d.ts +81 -0
  273. package/packages/notifications/dist/index.js +125 -0
  274. package/packages/notifications/node_modules/.bin/esbuild +19 -0
  275. package/packages/notifications/node_modules/.bin/tsc +22 -0
  276. package/packages/notifications/node_modules/.bin/tsserver +22 -0
  277. package/packages/notifications/node_modules/.bin/tsup +22 -0
  278. package/packages/notifications/node_modules/.bin/tsup-node +22 -0
  279. package/packages/notifications/node_modules/.bin/vite +22 -0
  280. package/packages/notifications/node_modules/.bin/vitest +22 -0
  281. package/packages/notifications/package.json +60 -0
  282. package/packages/notifications/src/NotificationsService.ts +230 -0
  283. package/packages/notifications/src/index.ts +19 -0
  284. package/packages/notifications/tsconfig.json +5 -0
  285. package/packages/notifications/tsup.config.ts +11 -0
  286. package/packages/realtime/LICENSE +21 -0
  287. package/packages/realtime/README.md +183 -0
  288. package/packages/realtime/dist/index.cjs +977 -0
  289. package/packages/realtime/dist/index.d.cts +259 -0
  290. package/packages/realtime/dist/index.d.ts +259 -0
  291. package/packages/realtime/dist/index.js +973 -0
  292. package/packages/realtime/node_modules/.bin/esbuild +19 -0
  293. package/packages/realtime/node_modules/.bin/tsc +22 -0
  294. package/packages/realtime/node_modules/.bin/tsserver +22 -0
  295. package/packages/realtime/node_modules/.bin/tsup +22 -0
  296. package/packages/realtime/node_modules/.bin/tsup-node +22 -0
  297. package/packages/realtime/node_modules/.bin/vite +22 -0
  298. package/packages/realtime/node_modules/.bin/vitest +22 -0
  299. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  300. package/packages/realtime/package.json +60 -0
  301. package/packages/realtime/src/AuraChannel.ts +485 -0
  302. package/packages/realtime/src/HeartbeatManager.ts +73 -0
  303. package/packages/realtime/src/RealtimeService.ts +505 -0
  304. package/packages/realtime/src/columnFilter.ts +301 -0
  305. package/packages/realtime/src/index.ts +4 -0
  306. package/packages/realtime/tests/ack-routing.test.ts +81 -0
  307. package/packages/realtime/tests/column-filter.test.ts +144 -0
  308. package/packages/realtime/tests/dispatch.test.ts +191 -0
  309. package/packages/realtime/tests/heartbeat.test.ts +67 -0
  310. package/packages/realtime/tests/http-broadcast.test.ts +68 -0
  311. package/packages/realtime/tests/realtime-token.test.ts +43 -0
  312. package/packages/realtime/tests/reconnect-race.test.ts +149 -0
  313. package/packages/realtime/tests/ref-id.test.ts +34 -0
  314. package/packages/realtime/tests/set-auth.test.ts +70 -0
  315. package/packages/realtime/tsconfig.json +5 -0
  316. package/packages/realtime/tsup.config.ts +11 -0
  317. package/packages/storage/LICENSE +21 -0
  318. package/packages/storage/README.md +141 -0
  319. package/packages/storage/dist/index.cjs +333 -0
  320. package/packages/storage/dist/index.d.cts +185 -0
  321. package/packages/storage/dist/index.d.ts +185 -0
  322. package/packages/storage/dist/index.js +329 -0
  323. package/packages/storage/node_modules/.bin/esbuild +19 -0
  324. package/packages/storage/node_modules/.bin/tsc +22 -0
  325. package/packages/storage/node_modules/.bin/tsserver +22 -0
  326. package/packages/storage/node_modules/.bin/tsup +22 -0
  327. package/packages/storage/node_modules/.bin/tsup-node +22 -0
  328. package/packages/storage/node_modules/.bin/vite +22 -0
  329. package/packages/storage/node_modules/.bin/vitest +22 -0
  330. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  331. package/packages/storage/package.json +60 -0
  332. package/packages/storage/src/DownloadBuilder.ts +60 -0
  333. package/packages/storage/src/StorageService.ts +323 -0
  334. package/packages/storage/src/index.ts +2 -0
  335. package/packages/storage/src/path.ts +35 -0
  336. package/packages/storage/src/transform.ts +15 -0
  337. package/packages/storage/tests/download.test.ts +64 -0
  338. package/packages/storage/tests/parity.test.ts +154 -0
  339. package/packages/storage/tests/path.test.ts +87 -0
  340. package/packages/storage/tests/transform.test.ts +79 -0
  341. package/packages/storage/tsconfig.json +5 -0
  342. package/packages/storage/tsup.config.ts +11 -0
  343. package/pnpm-workspace.yaml +24 -0
  344. package/tsconfig.base.json +15 -0
@@ -0,0 +1,830 @@
1
+ import type { AuraResponse, CountMode, AuraClientLike } from '@aurabase/core'
2
+ import { fetchWithAuth, AuraError } from '@aurabase/core'
3
+ import type { GetResult } from './select-query-parser/result'
4
+ import type {
5
+ GenericSchema,
6
+ GenericTable,
7
+ GenericView,
8
+ ClientServerOptions,
9
+ } from './pgrst-types/common/common'
10
+
11
+ /**
12
+ * `ClientOptions` figé pour aurabase — PostgREST v12. Satisfait `ClientServerOptions`
13
+ * et sert de défaut au threading de types (pas de configuration exposée côté SDK).
14
+ */
15
+ export type AuraClientOptionsT = { PostgrestVersion: '12' }
16
+
17
+ interface StoredFilter {
18
+ column: string
19
+ op: string
20
+ value: unknown
21
+ }
22
+
23
+ /**
24
+ * Quote une valeur PostgREST contenant des caractères réservés (`,` `(` `)` `"`).
25
+ * Sans ça, une valeur comme `"Aix, FR"` dans un `in.(…)` serait lue comme deux valeurs.
26
+ * Les `"` internes sont doublés (échappement PostgREST).
27
+ */
28
+ function quoteReserved(v: unknown): string {
29
+ const s = String(v)
30
+ return /[,()"]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s
31
+ }
32
+
33
+ /** Encode une valeur de filtre en fragment PostgREST `op.value` (gère array-syntax, jsonb, ranges). */
34
+ function encodeFilterValue(op: string, value: unknown): string {
35
+ if (op === 'in' || op === 'not.in') {
36
+ const arr = Array.isArray(value) ? value : [value]
37
+ return `${op}.(${arr.map(quoteReserved).join(',')})`
38
+ }
39
+ if (op === 'cs' || op === 'cd' || op === 'ov') {
40
+ if (Array.isArray(value)) return `${op}.{${value.map(quoteReserved).join(',')}}`
41
+ if (value !== null && typeof value === 'object') return `${op}.${JSON.stringify(value)}`
42
+ return `${op}.${value}`
43
+ }
44
+ if (op.endsWith('(all)') || op.endsWith('(any)')) {
45
+ const arr = Array.isArray(value) ? value : [value]
46
+ return `${op}.{${arr.map(quoteReserved).join(',')}}`
47
+ }
48
+ return `${op}.${value}`
49
+ }
50
+
51
+ interface OrGroup {
52
+ filters: StoredFilter[]
53
+ }
54
+
55
+ /** En-tête PostgREST demandant un objet unique (parité postgrest-js `.single()`). */
56
+ const PGRST_OBJECT_ACCEPT = 'application/vnd.pgrst.object+json'
57
+
58
+ /**
59
+ * Détecte un 406 « 0 lignes » (vs « plusieurs lignes ») pour discriminer les deux cas
60
+ * de cardinalité de `single()`/`maybeSingle()`. Le gateway réécrit PGRST116 en code
61
+ * `NOT_SINGULAR` mais préserve le détail PostgREST « The result contains N rows » (imbriqué
62
+ * sous `error.details`). On cherche donc « 0 rows » dans message + détails sérialisés.
63
+ * `\b0 rows\b` évite un faux positif sur « 10 rows », « 20 rows », etc.
64
+ */
65
+ function mentionsZeroRows(err: { message?: string; details?: unknown } | null): boolean {
66
+ if (!err) return false
67
+ const details = typeof err.details === 'string' ? err.details : JSON.stringify(err.details ?? '')
68
+ return /\b0 rows\b/.test(`${err.message ?? ''} ${details}`)
69
+ }
70
+
71
+ /** Construit l'erreur de cardinalité PGRST116 (parité postgrest-js/Supabase). */
72
+ function pgrst116(zeroRows: boolean): AuraError {
73
+ return new AuraError(406, {
74
+ error: {
75
+ code: 'PGRST116',
76
+ message: zeroRows
77
+ ? 'JSON object requested, 0 rows returned'
78
+ : 'JSON object requested, multiple rows returned',
79
+ },
80
+ })
81
+ }
82
+
83
+ // `id` non-null → mode COMPAT id-based (route `/{basePath}/{id}`, rétro-compat) ;
84
+ // `id: null` → mode FILTRÉ (route Table `/{basePath}?...` + filtres, parité PostgREST).
85
+ type MutationMode =
86
+ | { type: 'insert'; body: unknown }
87
+ | { type: 'update'; id: string | null; body: unknown }
88
+ | { type: 'delete'; id: string | null }
89
+ | { type: 'upsert'; body: unknown; onConflict: string }
90
+
91
+ /** État d'un builder en mode `rpc` (`DatabaseService.rpc()`) : args + verbe HTTP. */
92
+ export type RpcMode = { args: unknown; verb: 'POST' | 'GET' | 'HEAD' }
93
+
94
+ /**
95
+ * Base class : état partagé, constructeur, exécution HTTP (`then`/`_execute`),
96
+ * `throwOnError` et `buildQueryString`. Implements PromiseLike so it can be
97
+ * awaited directly.
98
+ */
99
+ export class AuraPostgrestBuilder<
100
+ // `ClientOptions`/`ThrowOnError` sont des paramètres fantômes : ils portent la
101
+ // signature (parité postgrest-js) et le threading des sous-classes, sans affecter
102
+ // le runtime ni la forme d'`AuraResponse`.
103
+ ClientOptions extends ClientServerOptions = AuraClientOptionsT,
104
+ Result = Record<string, unknown>,
105
+ ThrowOnError extends boolean = false,
106
+ > implements PromiseLike<AuraResponse<Result>>
107
+ {
108
+ protected readonly _client: AuraClientLike
109
+ protected readonly _basePath: string
110
+ protected _filters: StoredFilter[] = []
111
+ protected _orGroups: OrGroup[] = []
112
+ protected _selectColumns: string[] | null = null
113
+ // `select()` a-t-il été appelé ? (distinct de `_selectColumns`, que `select('*')`
114
+ // remet à `null`) — sert à poser `Prefer: return=representation` sur une mutation
115
+ // même avec `select('*')`.
116
+ protected _selectInvoked = false
117
+ protected _sortColumn: string | null = null
118
+ protected _sortDirection: 'asc' | 'desc' = 'asc'
119
+ protected _limit: number | null = null
120
+ protected _offset: number | null = null
121
+ protected _cursor: string | null = null
122
+ protected _count: CountMode | null = null
123
+ protected _rawSelect = false
124
+ protected _single = false
125
+ protected _maybeSingle = false
126
+ protected _throwOnError = false
127
+ protected _mutation: MutationMode | null = null
128
+ protected _rpc: RpcMode | null = null
129
+
130
+ constructor(client: AuraClientLike, basePath: string) {
131
+ this._client = client
132
+ this._basePath = basePath
133
+ }
134
+
135
+ throwOnError(): this {
136
+ this._throwOnError = true
137
+ return this
138
+ }
139
+
140
+ /**
141
+ * Positionne l'état interne `rpc` (args + verbe HTTP) — méthode package-interne
142
+ * utilisée par `DatabaseService.rpc()` pour éviter un cast sur un champ `protected`.
143
+ *
144
+ * `count` accepte `'planned'` (parité stricte `RpcOptions`, cf. `DatabaseService.rpc`)
145
+ * en plus de `CountMode` (`@aurabase/core`, qui ne connaît que `exact`/`estimated` —
146
+ * package partagé, non modifié ici). Le cast vers `_count` reste sans risque runtime :
147
+ * `buildQueryString` sérialise la valeur telle quelle en query param `count=`.
148
+ */
149
+ _initRpc(args: unknown, verb: 'POST' | 'GET' | 'HEAD', count?: 'exact' | 'planned' | 'estimated'): this {
150
+ this._rpc = { args, verb }
151
+ if (count) this._count = count as CountMode
152
+ return this
153
+ }
154
+
155
+ // -- Query string builder ---------------------------------------------------
156
+
157
+ buildQueryString(): string {
158
+ const params = new URLSearchParams()
159
+
160
+ // Select (raw string preserves embed syntax with parentheses)
161
+ if (this._selectColumns && this._selectColumns.length > 0) {
162
+ params.set('select', this._rawSelect ? this._selectColumns[0]! : this._selectColumns.join(','))
163
+ }
164
+
165
+ // Filters: col=op.value (encodage centralisé)
166
+ for (const f of this._filters) {
167
+ params.append(f.column, encodeFilterValue(f.op, f.value))
168
+ }
169
+
170
+ // OR groups — PostgREST syntax: or=(col.op.val,col2.op.val2)
171
+ for (const group of this._orGroups) {
172
+ const parts = group.filters.map((f) => `${f.column}.${encodeFilterValue(f.op, f.value)}`)
173
+ params.append('or', `(${parts.join(',')})`)
174
+ }
175
+
176
+ // Sort
177
+ if (this._sortColumn) {
178
+ params.set('sort_by', this._sortColumn)
179
+ params.set('sort_order', this._sortDirection)
180
+ }
181
+
182
+ // Pagination
183
+ if (this._limit !== null) params.set('limit', String(this._limit))
184
+ if (this._offset !== null) params.set('offset', String(this._offset))
185
+ if (this._cursor !== null) params.set('cursor', this._cursor)
186
+
187
+ // Count
188
+ if (this._count) params.set('count', this._count)
189
+
190
+ // Args RPC en query params quand le verbe est GET (get:true) ou HEAD (head:true)
191
+ // — parité PostgREST/postgrest-js : args en query pour les deux verbes sans corps.
192
+ if (
193
+ this._rpc &&
194
+ (this._rpc.verb === 'GET' || this._rpc.verb === 'HEAD') &&
195
+ this._rpc.args &&
196
+ typeof this._rpc.args === 'object'
197
+ ) {
198
+ for (const [k, v] of Object.entries(this._rpc.args as Record<string, unknown>)) {
199
+ params.set(k, typeof v === 'string' ? v : JSON.stringify(v))
200
+ }
201
+ }
202
+
203
+ return params.toString()
204
+ }
205
+
206
+ // -- Execution via PromiseLike ----------------------------------------------
207
+
208
+ then<TResult1 = AuraResponse<Result>, TResult2 = never>(
209
+ onfulfilled?: ((value: AuraResponse<Result>) => TResult1 | PromiseLike<TResult1>) | null,
210
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
211
+ ): Promise<TResult1 | TResult2> {
212
+ return this._execute().then(onfulfilled, onrejected)
213
+ }
214
+
215
+ private async _execute(): Promise<AuraResponse<Result>> {
216
+ let res: AuraResponse<Result>
217
+
218
+ // Parité postgrest-js : `single()`/`maybeSingle()` demandent un objet unique via l'en-tête
219
+ // Accept (et NON plus `limit=2`, qui casse sur les mutations filtrées). Fusionné avec les
220
+ // autres en-têtes (Prefer) dans chaque branche ci-dessous.
221
+ const acceptSingle: Record<string, string> | undefined = this._single
222
+ ? { Accept: PGRST_OBJECT_ACCEPT }
223
+ : undefined
224
+
225
+ if (this._rpc) {
226
+ const qs = this.buildQueryString()
227
+ const sep = qs ? '?' : ''
228
+ const url = `${this._client.baseUrl}${this._basePath}${sep}${qs}`
229
+ const init: { method: string; body?: unknown; headers?: Record<string, string> } =
230
+ this._rpc.verb === 'POST'
231
+ ? { method: 'POST', body: this._rpc.args, headers: acceptSingle }
232
+ : { method: this._rpc.verb, headers: acceptSingle }
233
+ res = await fetchWithAuth<Result>(url, init, this._client)
234
+ } else if (this._mutation) {
235
+ const m = this._mutation
236
+
237
+ // GARDE-FOU ANTI-WIPE : un update()/delete() en mode filtré SANS aucun
238
+ // filtre effacerait/modifierait toute la table (le gateway ne l'empêche
239
+ // pas). On refuse AVANT tout appel réseau. Le mode compat id-based
240
+ // (`m.id !== null`) porte implicitement un critère → non concerné.
241
+ //
242
+ // DB-F2 : on compte les filtres EFFECTIFS, pas la seule présence d'entrées.
243
+ // `.or([])` pousse un OrGroup vide (`{filters:[]}`) et `.filter('','','')`
244
+ // pousse un filtre à colonne vide — aucun des deux n'émet de critère
245
+ // PostgREST. Les ignorer ferme le contournement du garde-fou.
246
+ const hasEffectiveFilter =
247
+ this._filters.some((f) => f.column !== '') ||
248
+ this._orGroups.some((g) => g.filters.length > 0)
249
+ if (
250
+ (m.type === 'update' || m.type === 'delete') &&
251
+ m.id === null &&
252
+ !hasEffectiveFilter
253
+ ) {
254
+ return {
255
+ data: null,
256
+ error: new AuraError(0, {
257
+ error: {
258
+ code: 'unfiltered_mutation',
259
+ message:
260
+ 'update()/delete() sans filtre refusé (protection anti-wipe) — ajoutez un filtre .eq()/... ou utilisez une mutation id-based',
261
+ },
262
+ }),
263
+ }
264
+ }
265
+
266
+ // En-tête Prefer : `return=representation` si `.select()` (indispensable
267
+ // pour récupérer les lignes sur un DELETE ; inoffensif sur PATCH/POST) ;
268
+ // `count=<mode>` si `.count()` (parité PostgREST — sur mutation le comptage
269
+ // passe par Prefer, pas par le query param).
270
+ const prefer: string[] = []
271
+ if (this._selectInvoked) prefer.push('return=representation')
272
+ if (this._count) prefer.push(`count=${this._count}`)
273
+ // Fusion Prefer (mutation) + Accept object+json (single) — `.eq().update().select().single()`.
274
+ const merged: Record<string, string> = { ...acceptSingle }
275
+ if (prefer.length > 0) merged.Prefer = prefer.join(', ')
276
+ const headers = Object.keys(merged).length > 0 ? merged : undefined
277
+
278
+ // Query string commune (select + filtres) via le builder partagé.
279
+ const qs = this.buildQueryString()
280
+ const sep = qs ? '?' : ''
281
+
282
+ switch (m.type) {
283
+ case 'insert': {
284
+ res = await fetchWithAuth<Result>(
285
+ `${this._client.baseUrl}${this._basePath}${sep}${qs}`,
286
+ { method: 'POST', body: m.body, headers },
287
+ this._client,
288
+ )
289
+ break
290
+ }
291
+ case 'update': {
292
+ // Compat : route id-based `/{basePath}/{id}` (rétro-compat exacte) ;
293
+ // sinon route Table filtrée `/{basePath}?filtres[&select]`.
294
+ const path = m.id !== null ? `${this._basePath}/${m.id}` : this._basePath
295
+ res = await fetchWithAuth<Result>(
296
+ `${this._client.baseUrl}${path}${sep}${qs}`,
297
+ { method: 'PATCH', body: m.body, headers },
298
+ this._client,
299
+ )
300
+ break
301
+ }
302
+ case 'delete': {
303
+ const path = m.id !== null ? `${this._basePath}/${m.id}` : this._basePath
304
+ res = await fetchWithAuth<Result>(
305
+ `${this._client.baseUrl}${path}${sep}${qs}`,
306
+ { method: 'DELETE', headers },
307
+ this._client,
308
+ )
309
+ break
310
+ }
311
+ case 'upsert': {
312
+ // `on_conflict` fusionné avec les éventuels filtres/select.
313
+ const params = new URLSearchParams(qs)
314
+ params.set('on_conflict', m.onConflict)
315
+ res = await fetchWithAuth<Result>(
316
+ `${this._client.baseUrl}${this._basePath}?${params.toString()}`,
317
+ { method: 'POST', body: m.body, headers },
318
+ this._client,
319
+ )
320
+ break
321
+ }
322
+ }
323
+ } else {
324
+ // SELECT
325
+ const qs = this.buildQueryString()
326
+ const sep = qs ? '?' : ''
327
+ res = await fetchWithAuth<Result>(
328
+ `${this._client.baseUrl}${this._basePath}${sep}${qs}`,
329
+ { method: 'GET', headers: acceptSingle },
330
+ this._client,
331
+ )
332
+ }
333
+
334
+ // DB-F3 : parité postgrest-js — `insert()`/`upsert()` sans `.single()` promettent
335
+ // TOUJOURS un tableau (`Row[]`). Le gateway déballe le 201 mono-ligne en objet
336
+ // (postgrest_proxy : branche `is_created`, arr.len()==1 → unwrap). On re-emballe
337
+ // en tableau à 1 élément pour honorer le type de retour. N'affecte ni les inserts
338
+ // multi-lignes (déjà un tableau), ni `.single()` (bloc ci-dessous), ni un 204
339
+ // sans représentation (`res.data === null`).
340
+ if (
341
+ !this._single &&
342
+ (this._mutation?.type === 'insert' || this._mutation?.type === 'upsert') &&
343
+ res.data !== null &&
344
+ typeof res.data === 'object' &&
345
+ !Array.isArray(res.data)
346
+ ) {
347
+ res = { ...res, data: [res.data] as unknown as Result }
348
+ }
349
+
350
+ // Single / maybeSingle : cardinalité stricte (parité PostgREST/Supabase).
351
+ //
352
+ // Chemin nominal (backend honore Accept object+json) :
353
+ // - 1 ligne → `res.data` est déjà l'objet (pas un tableau) → renvoyé tel quel.
354
+ // - 0 / >1 → 406 (gateway : code `NOT_SINGULAR`, détail PostgREST « N rows »).
355
+ // • single → erreur PGRST116 (exige exactement 1 ligne).
356
+ // • maybeSingle → 0 ligne = `{data:null,error:null}` ; >1 = erreur PGRST116.
357
+ // Chemin défensif : si le backend renvoie malgré tout un TABLEAU (en-tête ignoré,
358
+ // mock de test…), on réduit avec la même sémantique.
359
+ if (this._single) {
360
+ if (res.error) {
361
+ // Cardinalité 0-ligne, selon la route empruntée par la mutation/lecture :
362
+ // - 406 NOT_SINGULAR (route Table/filtrée) : discrimine 0 vs >1 via le
363
+ // détail PostgREST (« N rows »).
364
+ // - 404 RECORD_NOT_FOUND (DB-F1) : le gateway réécrit PGRST116 en 404 sur
365
+ // la route id-based `/{table}/{id}` (postgrest_proxy `is_single_object`).
366
+ // C'est TOUJOURS un cas 0-ligne (l'id n'existe pas) — jamais « >1 ».
367
+ // `zero === null` → autre erreur (401/500…), propagée telle quelle.
368
+ const zero =
369
+ res.error.status === 406
370
+ ? mentionsZeroRows(res.error)
371
+ : res.error.status === 404 && res.error.code === 'RECORD_NOT_FOUND'
372
+ ? true
373
+ : null
374
+ if (zero !== null) {
375
+ res =
376
+ this._maybeSingle && zero
377
+ ? { ...res, data: null, error: null }
378
+ : { ...res, data: null, error: pgrst116(zero) }
379
+ }
380
+ } else if (Array.isArray(res.data)) {
381
+ const rows = res.data as unknown[]
382
+ if (this._maybeSingle) {
383
+ res =
384
+ rows.length > 1
385
+ ? { ...res, data: null, error: pgrst116(false) }
386
+ : { ...res, data: (rows[0] as Result | undefined) ?? null }
387
+ } else if (rows.length === 1) {
388
+ res = { ...res, data: rows[0] as Result }
389
+ } else {
390
+ res = { ...res, data: null, error: pgrst116(rows.length === 0) }
391
+ }
392
+ }
393
+ // Sinon : `res.data` est déjà l'objet unique → renvoyé tel quel.
394
+ }
395
+
396
+ // Throw mode
397
+ if (this._throwOnError && res.error) {
398
+ const err = new Error(res.error.message)
399
+ ;(err as unknown as Record<string, unknown>).code = res.error.code
400
+ ;(err as unknown as Record<string, unknown>).details = res.error.details
401
+ throw err
402
+ }
403
+
404
+ return res
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Sélection de colonnes, tri, pagination et modificateurs de cardinalité
410
+ * (`single`/`maybeSingle`/`count`).
411
+ */
412
+ export class AuraTransformBuilder<
413
+ Schema extends GenericSchema = GenericSchema,
414
+ Row extends Record<string, unknown> = Record<string, unknown>,
415
+ RelationName = unknown,
416
+ Relationships = unknown,
417
+ Result = Record<string, unknown>,
418
+ ClientOptions extends ClientServerOptions = AuraClientOptionsT,
419
+ ThrowOnError extends boolean = false,
420
+ > extends AuraPostgrestBuilder<ClientOptions, Result, ThrowOnError> {
421
+ // -- Column selection & resource embedding -----------------------------------
422
+
423
+ /**
424
+ * Select columns and optionally embed related resources (PostgREST parity).
425
+ *
426
+ * @example
427
+ * // Simple columns
428
+ * db.from('users').select('id, name, email')
429
+ *
430
+ * // All columns (default)
431
+ * db.from('users').select('*')
432
+ *
433
+ * // Embed related table (many-to-one)
434
+ * db.from('orders').select('*, customer:customers(name, email)')
435
+ *
436
+ * // Embed related table (one-to-many)
437
+ * db.from('directors').select('last_name, films(title, year)')
438
+ *
439
+ * // FK disambiguation
440
+ * db.from('orders').select('*, billing:addresses!billing_address_id(name)')
441
+ *
442
+ * // Nested embedding
443
+ * db.from('actors').select('first_name, roles(character, films(title))')
444
+ */
445
+ select<
446
+ Query extends string = '*',
447
+ NewResultOne = GetResult<Schema, Row, RelationName, Relationships, Query, ClientOptions>,
448
+ >(
449
+ columns?: Query,
450
+ ): AuraFilterBuilder<
451
+ Schema,
452
+ Row,
453
+ RelationName,
454
+ Relationships,
455
+ NewResultOne[],
456
+ ClientOptions,
457
+ ThrowOnError
458
+ > {
459
+ // Runtime inchangé : accepte aussi un tableau de colonnes (parité historique),
460
+ // le typage n'expose que la forme string literal pour l'inférence PostgREST.
461
+ this._selectInvoked = true
462
+ const cols = columns as unknown as string | string[] | undefined
463
+ if (cols === undefined || cols === '*') {
464
+ this._selectColumns = null
465
+ this._rawSelect = false
466
+ } else if (typeof cols === 'string') {
467
+ // If string contains parentheses → embed syntax, pass raw to backend
468
+ // Otherwise → simple comma-separated column list
469
+ if (cols.includes('(')) {
470
+ this._selectColumns = [cols]
471
+ this._rawSelect = true
472
+ } else {
473
+ this._selectColumns = cols.split(',').map((c) => c.trim())
474
+ this._rawSelect = false
475
+ }
476
+ } else {
477
+ this._selectColumns = cols
478
+ this._rawSelect = false
479
+ }
480
+ return this as unknown as AuraFilterBuilder<
481
+ Schema,
482
+ Row,
483
+ RelationName,
484
+ Relationships,
485
+ NewResultOne[],
486
+ ClientOptions,
487
+ ThrowOnError
488
+ >
489
+ }
490
+
491
+ // -- Sort / pagination ------------------------------------------------------
492
+
493
+ order(column: string, direction: 'asc' | 'desc' = 'asc'): this {
494
+ this._sortColumn = column
495
+ this._sortDirection = direction
496
+ return this
497
+ }
498
+
499
+ limit(n: number): this {
500
+ this._limit = n
501
+ return this
502
+ }
503
+
504
+ offset(n: number): this {
505
+ this._offset = n
506
+ return this
507
+ }
508
+
509
+ /**
510
+ * Pagination keyset : passer le `next_cursor` opaque de la page précédente (préférable à
511
+ * `offset` sur les grandes tables : stable, sans dup/skip). Renvoyer tel quel `meta.next_cursor` ;
512
+ * ne pas le construire/parser (format opaque).
513
+ */
514
+ cursor(cursor: string): this {
515
+ this._cursor = cursor
516
+ return this
517
+ }
518
+
519
+ count(mode: CountMode = 'exact'): this {
520
+ this._count = mode
521
+ return this
522
+ }
523
+
524
+ // -- Modifiers --------------------------------------------------------------
525
+
526
+ single<ResultOne = Result extends (infer U)[] ? U : Result>(): AuraPostgrestBuilder<
527
+ ClientOptions,
528
+ ResultOne,
529
+ ThrowOnError
530
+ > {
531
+ this._single = true
532
+ // Parité postgrest-js : `_execute` envoie `Accept: application/vnd.pgrst.object+json`.
533
+ // PostgREST renvoie alors l'objet unique (200) ou 406 PGRST116 (0/>1 lignes). On
534
+ // n'ajoute PLUS `limit=2` : PostgREST rejette `limit` sans `order` sur une mutation
535
+ // filtrée (`.eq().update().select().single()` cassait). L'en-tête traverse le gateway
536
+ // (route Table) jusqu'à PostgREST — cf. postgrest_proxy `req.into_parts()`.
537
+ return this as unknown as AuraPostgrestBuilder<ClientOptions, ResultOne, ThrowOnError>
538
+ }
539
+
540
+ maybeSingle<ResultOne = Result extends (infer U)[] ? U : Result>(): AuraPostgrestBuilder<
541
+ ClientOptions,
542
+ ResultOne | null,
543
+ ThrowOnError
544
+ > {
545
+ this._single = true
546
+ this._maybeSingle = true
547
+ // Même en-tête `Accept: object+json`. Différence : sur 406 « 0 rows » → `{data:null,error:null}`
548
+ // (0 ligne n'est pas une erreur pour maybeSingle) ; sur >1 ligne → erreur PGRST116.
549
+ return this as unknown as AuraPostgrestBuilder<ClientOptions, ResultOne | null, ThrowOnError>
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Tous les filtres PostgREST (`eq`, `neq`, ... `ilikeAnyOf`).
555
+ */
556
+ export class AuraFilterBuilder<
557
+ Schema extends GenericSchema = GenericSchema,
558
+ Row extends Record<string, unknown> = Record<string, unknown>,
559
+ RelationName = unknown,
560
+ Relationships = unknown,
561
+ Result = Record<string, unknown>,
562
+ ClientOptions extends ClientServerOptions = AuraClientOptionsT,
563
+ ThrowOnError extends boolean = false,
564
+ > extends AuraTransformBuilder<
565
+ Schema,
566
+ Row,
567
+ RelationName,
568
+ Relationships,
569
+ Result,
570
+ ClientOptions,
571
+ ThrowOnError
572
+ > {
573
+ // -- Filter methods (each returns this for chaining) ------------------------
574
+ //
575
+ // Typées sur `Row` : `column` est contraint à `keyof Row & string` (autocomplete +
576
+ // erreur de compilation sur colonne inconnue) et `value`/`values` à `Row[C]` (erreur
577
+ // sur mauvais type). Runtime inchangé — seules les signatures changent.
578
+
579
+ eq<C extends keyof Row & string>(column: C, value: Row[C]): this {
580
+ this._filters.push({ column, op: 'eq', value })
581
+ return this
582
+ }
583
+
584
+ neq<C extends keyof Row & string>(column: C, value: Row[C]): this {
585
+ this._filters.push({ column, op: 'neq', value })
586
+ return this
587
+ }
588
+
589
+ gt<C extends keyof Row & string>(column: C, value: Row[C]): this {
590
+ this._filters.push({ column, op: 'gt', value })
591
+ return this
592
+ }
593
+
594
+ gte<C extends keyof Row & string>(column: C, value: Row[C]): this {
595
+ this._filters.push({ column, op: 'gte', value })
596
+ return this
597
+ }
598
+
599
+ lt<C extends keyof Row & string>(column: C, value: Row[C]): this {
600
+ this._filters.push({ column, op: 'lt', value })
601
+ return this
602
+ }
603
+
604
+ lte<C extends keyof Row & string>(column: C, value: Row[C]): this {
605
+ this._filters.push({ column, op: 'lte', value })
606
+ return this
607
+ }
608
+
609
+ like(column: keyof Row & string, pattern: string): this {
610
+ this._filters.push({ column, op: 'like', value: pattern })
611
+ return this
612
+ }
613
+
614
+ ilike(column: keyof Row & string, pattern: string): this {
615
+ this._filters.push({ column, op: 'ilike', value: pattern })
616
+ return this
617
+ }
618
+
619
+ /**
620
+ * IS NULL or IS NOT NULL filter.
621
+ * PostgREST accepts: is.null, is.true, is.false (no "not_null" — use neq for that).
622
+ * For IS NOT NULL, use `neq(column, null)` or `is(column, 'not.null')`.
623
+ */
624
+ is(column: keyof Row & string, value: 'null' | 'true' | 'false' | 'unknown' | 'not.null'): this {
625
+ if (value === 'not.null') {
626
+ // IS NOT NULL en PostgREST = `col=not.is.null`. (`neq.null` produit `col <> NULL`,
627
+ // toujours NULL/faux en SQL → renverrait 0 ligne.)
628
+ this._filters.push({ column, op: 'not.is', value: 'null' })
629
+ } else {
630
+ this._filters.push({ column, op: 'is', value })
631
+ }
632
+ return this
633
+ }
634
+
635
+ in<C extends keyof Row & string>(column: C, values: Row[C][]): this {
636
+ this._filters.push({ column, op: 'in', value: values })
637
+ return this
638
+ }
639
+
640
+ /**
641
+ * Add an OR group: `or=(col1.eq.val1,col2.gt.val2)`
642
+ */
643
+ or(filters: Array<{ column: string; op: string; value: unknown }>): this {
644
+ this._orGroups.push({
645
+ filters: filters.map((f) => ({ column: f.column, op: f.op, value: f.value })),
646
+ })
647
+ return this
648
+ }
649
+
650
+ // -- Filtres étendus (parité PostgREST) -------------------------------------
651
+
652
+ notIn<C extends keyof Row & string>(column: C, values: Row[C][]): this { this._filters.push({ column, op: 'not.in', value: values }); return this }
653
+ contains(column: keyof Row & string, value: unknown): this { this._filters.push({ column, op: 'cs', value }); return this }
654
+ containedBy(column: keyof Row & string, value: unknown): this { this._filters.push({ column, op: 'cd', value }); return this }
655
+ overlaps(column: keyof Row & string, value: unknown): this { this._filters.push({ column, op: 'ov', value }); return this }
656
+ rangeGt(column: keyof Row & string, range: string): this { this._filters.push({ column, op: 'sr', value: range }); return this }
657
+ rangeGte(column: keyof Row & string, range: string): this { this._filters.push({ column, op: 'nxl', value: range }); return this }
658
+ rangeLt(column: keyof Row & string, range: string): this { this._filters.push({ column, op: 'sl', value: range }); return this }
659
+ rangeLte(column: keyof Row & string, range: string): this { this._filters.push({ column, op: 'nxr', value: range }); return this }
660
+ rangeAdjacent(column: keyof Row & string, range: string): this { this._filters.push({ column, op: 'adj', value: range }); return this }
661
+ isDistinct(column: keyof Row & string, value: unknown): this { this._filters.push({ column, op: 'isdistinct', value }); return this }
662
+ // Échappatoires (parité postgrest-js) : colonne `string` permissive, pas de contrainte sur `Row`.
663
+ not(column: string, op: string, value: unknown): this { this._filters.push({ column, op: `not.${op}`, value }); return this }
664
+ filter(column: string, op: string, value: unknown): this { this._filters.push({ column, op, value }); return this }
665
+ match(query: Record<string, unknown>): this {
666
+ for (const [column, value] of Object.entries(query)) this._filters.push({ column, op: 'eq', value })
667
+ return this
668
+ }
669
+ textSearch(column: keyof Row & string, query: string, options?: { type?: 'plain' | 'phrase' | 'websearch'; config?: string }): this {
670
+ const t = options?.type
671
+ const token = t === 'plain' ? 'plfts' : t === 'phrase' ? 'phfts' : t === 'websearch' ? 'wfts' : 'fts'
672
+ const op = options?.config ? `${token}(${options.config})` : token
673
+ this._filters.push({ column, op, value: query })
674
+ return this
675
+ }
676
+ likeAllOf(column: keyof Row & string, patterns: string[]): this { this._filters.push({ column, op: 'like(all)', value: patterns }); return this }
677
+ likeAnyOf(column: keyof Row & string, patterns: string[]): this { this._filters.push({ column, op: 'like(any)', value: patterns }); return this }
678
+ ilikeAllOf(column: keyof Row & string, patterns: string[]): this { this._filters.push({ column, op: 'ilike(all)', value: patterns }); return this }
679
+ ilikeAnyOf(column: keyof Row & string, patterns: string[]): this { this._filters.push({ column, op: 'ilike(any)', value: patterns }); return this }
680
+ }
681
+
682
+ /**
683
+ * Chainable query builder that compiles to URL query parameters
684
+ * matching the backend's QueryOptions / FilterOp format.
685
+ *
686
+ * Point d'entrée concret retourné par `DatabaseService.from()` : ajoute les
687
+ * mutations (`insert`/`update`/`delete`/`upsert`) au-dessus de la chaîne
688
+ * Base → Transform → Filter.
689
+ */
690
+ export class AuraQueryBuilder<
691
+ Schema extends GenericSchema = GenericSchema,
692
+ Relation extends GenericTable | GenericView = GenericTable,
693
+ RelationName = unknown,
694
+ ClientOptions extends ClientServerOptions = AuraClientOptionsT,
695
+ > extends AuraFilterBuilder<
696
+ Schema,
697
+ Relation['Row'],
698
+ RelationName,
699
+ Relation['Relationships'],
700
+ Relation['Row'][],
701
+ ClientOptions,
702
+ false
703
+ > {
704
+ // -- Mutations --------------------------------------------------------------
705
+ //
706
+ // Typées sur `Relation['Insert']`/`Relation['Update']` (parité postgrest-js) :
707
+ // un champ requis manquant côté insert/upsert, ou une colonne inconnue côté
708
+ // update, est une erreur de compilation. Runtime inchangé.
709
+ //
710
+ // Extraction par type conditionnel (plutôt qu'un accès indexé direct
711
+ // `Relation['Insert']`) : `Relation` est contraint à `GenericTable | GenericView`
712
+ // et `GenericNonUpdatableView` ne porte pas `Insert`/`Update`. `infer` distribue
713
+ // sur cette union et retombe sur `never` pour la branche non-updatable, ce qui
714
+ // évite l'erreur TS2536 tout en gardant le typage strict pour les tables.
715
+
716
+ insert(
717
+ body: Relation extends { Insert: infer I } ? I | I[] : never,
718
+ ): this {
719
+ this._mutation = { type: 'insert', body }
720
+ return this
721
+ }
722
+
723
+ // `update`/`delete`/`upsert` retournent un `AuraFilterBuilder` (au lieu de
724
+ // `this`) : le mode filtré chaîne `.eq(...)` AVANT et `.select()` APRÈS la
725
+ // mutation (parité PostgREST). Deux formes pour `update` :
726
+ // - COMPAT `update(id: string, body)` → route id-based `/{table}/{id}` ;
727
+ // - FILTRÉ `update(body)` → route Table `/{table}?filtres`, exige un filtre
728
+ // (garde-fou anti-wipe dans `_execute`).
729
+ update(
730
+ id: string,
731
+ body: Relation extends { Update: infer U } ? U : never,
732
+ ): AuraFilterBuilder<
733
+ Schema,
734
+ Relation['Row'],
735
+ RelationName,
736
+ Relation['Relationships'],
737
+ Relation['Row'][],
738
+ ClientOptions,
739
+ false
740
+ >
741
+ update(
742
+ body: Relation extends { Update: infer U } ? U : never,
743
+ ): AuraFilterBuilder<
744
+ Schema,
745
+ Relation['Row'],
746
+ RelationName,
747
+ Relation['Relationships'],
748
+ Relation['Row'][],
749
+ ClientOptions,
750
+ false
751
+ >
752
+ update(
753
+ idOrBody: string | (Relation extends { Update: infer U } ? U : never),
754
+ body?: Relation extends { Update: infer U } ? U : never,
755
+ ): AuraFilterBuilder<
756
+ Schema,
757
+ Relation['Row'],
758
+ RelationName,
759
+ Relation['Relationships'],
760
+ Relation['Row'][],
761
+ ClientOptions,
762
+ false
763
+ > {
764
+ if (typeof idOrBody === 'string') {
765
+ this._mutation = { type: 'update', id: idOrBody, body }
766
+ } else {
767
+ this._mutation = { type: 'update', id: null, body: idOrBody }
768
+ }
769
+ return this as unknown as AuraFilterBuilder<
770
+ Schema,
771
+ Relation['Row'],
772
+ RelationName,
773
+ Relation['Relationships'],
774
+ Relation['Row'][],
775
+ ClientOptions,
776
+ false
777
+ >
778
+ }
779
+
780
+ // COMPAT `delete(id)` → route id-based ; FILTRÉ `delete()` → route Table
781
+ // (exige un filtre, garde-fou anti-wipe).
782
+ delete(id?: string): AuraFilterBuilder<
783
+ Schema,
784
+ Relation['Row'],
785
+ RelationName,
786
+ Relation['Relationships'],
787
+ Relation['Row'][],
788
+ ClientOptions,
789
+ false
790
+ > {
791
+ this._mutation = { type: 'delete', id: typeof id === 'string' ? id : null }
792
+ return this as unknown as AuraFilterBuilder<
793
+ Schema,
794
+ Relation['Row'],
795
+ RelationName,
796
+ Relation['Relationships'],
797
+ Relation['Row'][],
798
+ ClientOptions,
799
+ false
800
+ >
801
+ }
802
+
803
+ upsert(
804
+ body: Relation extends { Insert: infer I } ? I | I[] : never,
805
+ options?: { onConflict?: string },
806
+ ): AuraFilterBuilder<
807
+ Schema,
808
+ Relation['Row'],
809
+ RelationName,
810
+ Relation['Relationships'],
811
+ Relation['Row'][],
812
+ ClientOptions,
813
+ false
814
+ > {
815
+ this._mutation = { type: 'upsert', body, onConflict: options?.onConflict ?? 'id' }
816
+ return this as unknown as AuraFilterBuilder<
817
+ Schema,
818
+ Relation['Row'],
819
+ RelationName,
820
+ Relation['Relationships'],
821
+ Relation['Row'][],
822
+ ClientOptions,
823
+ false
824
+ >
825
+ }
826
+ }
827
+
828
+ // Alias rétro-compatible : le nom `QueryBuilder` reste l'export public utilisé
829
+ // par la façade et les tests existants.
830
+ export { AuraQueryBuilder as QueryBuilder }