@aurabase/aurabase-js 0.3.0 → 0.4.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 (343) hide show
  1. package/README.md +26 -795
  2. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts → dist/AuraClient-BcFE8u1v.d.cts} +1 -1
  3. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts → dist/AuraClient-BcFE8u1v.d.ts} +1 -1
  4. package/{packages/aurabase/dist → dist}/index.d.cts +1 -1
  5. package/{packages/aurabase/dist → dist}/index.d.ts +1 -1
  6. package/{packages/aurabase/dist → dist}/ssr.d.cts +1 -1
  7. package/{packages/aurabase/dist → dist}/ssr.d.ts +1 -1
  8. package/package.json +72 -82
  9. package/.changeset/config.json +0 -11
  10. package/CHANGELOG.md +0 -30
  11. package/packages/ai/LICENSE +0 -21
  12. package/packages/ai/README.md +0 -106
  13. package/packages/ai/dist/index.cjs +0 -229
  14. package/packages/ai/dist/index.d.cts +0 -139
  15. package/packages/ai/dist/index.d.ts +0 -139
  16. package/packages/ai/dist/index.js +0 -226
  17. package/packages/ai/node_modules/.bin/esbuild +0 -19
  18. package/packages/ai/node_modules/.bin/tsc +0 -22
  19. package/packages/ai/node_modules/.bin/tsserver +0 -22
  20. package/packages/ai/node_modules/.bin/tsup +0 -22
  21. package/packages/ai/node_modules/.bin/tsup-node +0 -22
  22. package/packages/ai/node_modules/.bin/vite +0 -22
  23. package/packages/ai/node_modules/.bin/vitest +0 -22
  24. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  25. package/packages/ai/package.json +0 -61
  26. package/packages/ai/src/AiService.ts +0 -226
  27. package/packages/ai/src/index.ts +0 -18
  28. package/packages/ai/src/sse.ts +0 -141
  29. package/packages/ai/src/types.ts +0 -30
  30. package/packages/ai/tests/chat-stream.test.ts +0 -151
  31. package/packages/ai/tests/rag-namespaces.test.ts +0 -54
  32. package/packages/ai/tests/sse.test.ts +0 -69
  33. package/packages/ai/tsconfig.json +0 -5
  34. package/packages/ai/tsup.config.ts +0 -11
  35. package/packages/aurabase/LICENSE +0 -21
  36. package/packages/aurabase/README.md +0 -64
  37. package/packages/aurabase/node_modules/.bin/esbuild +0 -19
  38. package/packages/aurabase/node_modules/.bin/tsc +0 -22
  39. package/packages/aurabase/node_modules/.bin/tsserver +0 -22
  40. package/packages/aurabase/node_modules/.bin/tsup +0 -22
  41. package/packages/aurabase/node_modules/.bin/tsup-node +0 -22
  42. package/packages/aurabase/node_modules/.bin/vite +0 -22
  43. package/packages/aurabase/node_modules/.bin/vitest +0 -22
  44. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  45. package/packages/aurabase/package.json +0 -80
  46. package/packages/aurabase/src/AuraClient.ts +0 -237
  47. package/packages/aurabase/src/index.ts +0 -152
  48. package/packages/aurabase/src/ssr.ts +0 -156
  49. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +0 -82
  50. package/packages/aurabase/tests/ai.test.ts +0 -111
  51. package/packages/aurabase/tests/auth-admin.test.ts +0 -515
  52. package/packages/aurabase/tests/auth-e2e.test.ts +0 -135
  53. package/packages/aurabase/tests/auth-hardening.test.ts +0 -206
  54. package/packages/aurabase/tests/auth.test.ts +0 -947
  55. package/packages/aurabase/tests/database.test.ts +0 -230
  56. package/packages/aurabase/tests/db-types-e2e.test.ts +0 -217
  57. package/packages/aurabase/tests/dispose.test.ts +0 -23
  58. package/packages/aurabase/tests/foreign-token.e2e.test.ts +0 -129
  59. package/packages/aurabase/tests/functions.test.ts +0 -107
  60. package/packages/aurabase/tests/integration.test.ts +0 -413
  61. package/packages/aurabase/tests/live-production-validation.js +0 -241
  62. package/packages/aurabase/tests/live-sdk-production-validation.js +0 -293
  63. package/packages/aurabase/tests/mocks.ts +0 -139
  64. package/packages/aurabase/tests/notifications.test.ts +0 -314
  65. package/packages/aurabase/tests/observability-backup.test.ts +0 -186
  66. package/packages/aurabase/tests/observability-hook.test.ts +0 -130
  67. package/packages/aurabase/tests/production-robustness.test.ts +0 -268
  68. package/packages/aurabase/tests/public-api-surface.test.ts +0 -53
  69. package/packages/aurabase/tests/query-builder.test.ts +0 -329
  70. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +0 -231
  71. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +0 -344
  72. package/packages/aurabase/tests/refresh-locks.test.ts +0 -210
  73. package/packages/aurabase/tests/retry-option.test.ts +0 -17
  74. package/packages/aurabase/tests/rpc-e2e.test.ts +0 -254
  75. package/packages/aurabase/tests/rs256-local-e2e.test.ts +0 -114
  76. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +0 -235
  77. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +0 -124
  78. package/packages/aurabase/tests/service-role-guard.test.ts +0 -99
  79. package/packages/aurabase/tests/ssr.test.ts +0 -91
  80. package/packages/aurabase/tests/storage-backend-e2e.test.ts +0 -481
  81. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +0 -104
  82. package/packages/aurabase/tests/storage-transform-e2e.test.ts +0 -684
  83. package/packages/aurabase/tests/storage.test.ts +0 -55
  84. package/packages/aurabase/tests/types/client-generic.test-d.ts +0 -79
  85. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +0 -571
  86. package/packages/aurabase/tests/types/error-code.test-d.ts +0 -53
  87. package/packages/aurabase/tsconfig.json +0 -5
  88. package/packages/aurabase/tsconfig.test-d.json +0 -5
  89. package/packages/aurabase/tsup.config.ts +0 -11
  90. package/packages/aurabase/vitest.config.ts +0 -16
  91. package/packages/aurabase/vitest.e2e.config.ts +0 -9
  92. package/packages/auth/LICENSE +0 -21
  93. package/packages/auth/README.md +0 -215
  94. package/packages/auth/dist/index.cjs +0 -1175
  95. package/packages/auth/dist/index.d.cts +0 -397
  96. package/packages/auth/dist/index.d.ts +0 -397
  97. package/packages/auth/dist/index.js +0 -1170
  98. package/packages/auth/node_modules/.bin/esbuild +0 -19
  99. package/packages/auth/node_modules/.bin/tsc +0 -22
  100. package/packages/auth/node_modules/.bin/tsserver +0 -22
  101. package/packages/auth/node_modules/.bin/tsup +0 -22
  102. package/packages/auth/node_modules/.bin/tsup-node +0 -22
  103. package/packages/auth/node_modules/.bin/vite +0 -22
  104. package/packages/auth/node_modules/.bin/vitest +0 -22
  105. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  106. package/packages/auth/package.json +0 -61
  107. package/packages/auth/src/AuthAdminService.ts +0 -149
  108. package/packages/auth/src/AuthService.ts +0 -1166
  109. package/packages/auth/src/SessionManager.ts +0 -92
  110. package/packages/auth/src/index.ts +0 -17
  111. package/packages/auth/src/jwks.ts +0 -337
  112. package/packages/auth/src/pkce.ts +0 -27
  113. package/packages/auth/src/types.ts +0 -89
  114. package/packages/auth/tests/auth-lifecycle.test.ts +0 -149
  115. package/packages/auth/tests/detect-session.test.ts +0 -361
  116. package/packages/auth/tests/getclaims.test.ts +0 -780
  117. package/packages/auth/tests/is-mfa-challenge.test.ts +0 -27
  118. package/packages/auth/tests/password-recovery.test.ts +0 -124
  119. package/packages/auth/tests/pkce.test.ts +0 -192
  120. package/packages/auth/tests/session-manager.test.ts +0 -103
  121. package/packages/auth/tests/set-session.test.ts +0 -139
  122. package/packages/auth/tests/verify-otp.test.ts +0 -153
  123. package/packages/auth/tsconfig.json +0 -5
  124. package/packages/auth/tsup.config.ts +0 -11
  125. package/packages/core/LICENSE +0 -21
  126. package/packages/core/README.md +0 -132
  127. package/packages/core/dist/index.cjs +0 -695
  128. package/packages/core/dist/index.d.cts +0 -1137
  129. package/packages/core/dist/index.d.ts +0 -1137
  130. package/packages/core/dist/index.js +0 -673
  131. package/packages/core/node_modules/.bin/esbuild +0 -22
  132. package/packages/core/node_modules/.bin/tsc +0 -22
  133. package/packages/core/node_modules/.bin/tsserver +0 -22
  134. package/packages/core/node_modules/.bin/tsup +0 -22
  135. package/packages/core/node_modules/.bin/tsup-node +0 -22
  136. package/packages/core/node_modules/.bin/vite +0 -22
  137. package/packages/core/node_modules/.bin/vitest +0 -22
  138. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  139. package/packages/core/package.json +0 -57
  140. package/packages/core/src/AuraError.ts +0 -89
  141. package/packages/core/src/BaseService.ts +0 -44
  142. package/packages/core/src/client.ts +0 -34
  143. package/packages/core/src/cookie-utils.ts +0 -42
  144. package/packages/core/src/fetch.ts +0 -435
  145. package/packages/core/src/index.ts +0 -22
  146. package/packages/core/src/retry.ts +0 -63
  147. package/packages/core/src/serialize.ts +0 -6
  148. package/packages/core/src/stores/BaseAuthStore.ts +0 -164
  149. package/packages/core/src/stores/LocalAuthStore.ts +0 -199
  150. package/packages/core/src/types.ts +0 -1042
  151. package/packages/core/tests/auth-store.test.ts +0 -300
  152. package/packages/core/tests/error-code.test.ts +0 -61
  153. package/packages/core/tests/error.test.ts +0 -24
  154. package/packages/core/tests/fetch.test.ts +0 -286
  155. package/packages/core/tests/mocks.ts +0 -139
  156. package/packages/core/tests/observability.test.ts +0 -86
  157. package/packages/core/tests/refresh-hardening.test.ts +0 -164
  158. package/packages/core/tests/refresh-observability.test.ts +0 -111
  159. package/packages/core/tests/retry.test.ts +0 -106
  160. package/packages/core/tests/serialize.test.ts +0 -12
  161. package/packages/core/tests/timeout.test.ts +0 -79
  162. package/packages/core/tsconfig.json +0 -5
  163. package/packages/core/tsup.config.ts +0 -11
  164. package/packages/db/LICENSE +0 -21
  165. package/packages/db/README.md +0 -152
  166. package/packages/db/VENDORED.md +0 -93
  167. package/packages/db/dist/index.cjs +0 -544
  168. package/packages/db/dist/index.d.cts +0 -1111
  169. package/packages/db/dist/index.d.ts +0 -1111
  170. package/packages/db/dist/index.js +0 -541
  171. package/packages/db/node_modules/.bin/esbuild +0 -19
  172. package/packages/db/node_modules/.bin/tsc +0 -22
  173. package/packages/db/node_modules/.bin/tsserver +0 -22
  174. package/packages/db/node_modules/.bin/tsup +0 -22
  175. package/packages/db/node_modules/.bin/tsup-node +0 -22
  176. package/packages/db/node_modules/.bin/vite +0 -22
  177. package/packages/db/node_modules/.bin/vitest +0 -22
  178. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  179. package/packages/db/package.json +0 -62
  180. package/packages/db/src/DatabaseService.ts +0 -170
  181. package/packages/db/src/QueryBuilder.ts +0 -830
  182. package/packages/db/src/index.ts +0 -2
  183. package/packages/db/src/pgrst-types/common/common.ts +0 -86
  184. package/packages/db/src/pgrst-types/common/rpc.ts +0 -151
  185. package/packages/db/src/pgrst-types/feature-flags.ts +0 -20
  186. package/packages/db/src/pgrst-types/types.ts +0 -134
  187. package/packages/db/src/select-query-parser/parser.ts +0 -479
  188. package/packages/db/src/select-query-parser/result.ts +0 -553
  189. package/packages/db/src/select-query-parser/types.ts +0 -132
  190. package/packages/db/src/select-query-parser/utils.ts +0 -707
  191. package/packages/db/tests/filters.test.ts +0 -81
  192. package/packages/db/tests/mutations-filtered.e2e.test.ts +0 -132
  193. package/packages/db/tests/mutations-filtered.test.ts +0 -188
  194. package/packages/db/tests/order.test.ts +0 -45
  195. package/packages/db/tests/rpc.test.ts +0 -41
  196. package/packages/db/tests/scale/build-large-schema.ts +0 -94
  197. package/packages/db/tests/scale/scale-compile.test.ts +0 -167
  198. package/packages/db/tests/schema.test.ts +0 -77
  199. package/packages/db/tests/single-accept.e2e.test.ts +0 -123
  200. package/packages/db/tests/single.test.ts +0 -211
  201. package/packages/db/tests/types/filters.test-d.ts +0 -52
  202. package/packages/db/tests/types/fixtures.ts +0 -40
  203. package/packages/db/tests/types/from-views.test-d.ts +0 -18
  204. package/packages/db/tests/types/mutations.test-d.ts +0 -23
  205. package/packages/db/tests/types/rpc.test-d.ts +0 -60
  206. package/packages/db/tests/types/schema-shape.test-d.ts +0 -8
  207. package/packages/db/tests/types/select-inference.test-d.ts +0 -38
  208. package/packages/db/tests/vendored/fixtures.ts +0 -83
  209. package/packages/db/tests/vendored/get-result.test-d.ts +0 -75
  210. package/packages/db/tsconfig.json +0 -5
  211. package/packages/db/tsconfig.test-d.json +0 -5
  212. package/packages/db/tsup.config.ts +0 -11
  213. package/packages/db/vitest.config.ts +0 -8
  214. package/packages/functions/LICENSE +0 -21
  215. package/packages/functions/README.md +0 -96
  216. package/packages/functions/dist/index.cjs +0 -173
  217. package/packages/functions/dist/index.d.cts +0 -81
  218. package/packages/functions/dist/index.d.ts +0 -81
  219. package/packages/functions/dist/index.js +0 -168
  220. package/packages/functions/node_modules/.bin/esbuild +0 -19
  221. package/packages/functions/node_modules/.bin/tsc +0 -22
  222. package/packages/functions/node_modules/.bin/tsserver +0 -22
  223. package/packages/functions/node_modules/.bin/tsup +0 -22
  224. package/packages/functions/node_modules/.bin/tsup-node +0 -22
  225. package/packages/functions/node_modules/.bin/vite +0 -22
  226. package/packages/functions/node_modules/.bin/vitest +0 -22
  227. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  228. package/packages/functions/package.json +0 -60
  229. package/packages/functions/src/FunctionsService.ts +0 -188
  230. package/packages/functions/src/errors.ts +0 -70
  231. package/packages/functions/src/index.ts +0 -6
  232. package/packages/functions/tests/invoke.test.ts +0 -97
  233. package/packages/functions/tests/response-type-errors.test.ts +0 -135
  234. package/packages/functions/tsconfig.json +0 -5
  235. package/packages/functions/tsup.config.ts +0 -11
  236. package/packages/gen/LICENSE +0 -21
  237. package/packages/gen/README.md +0 -84
  238. package/packages/gen/dist/cli.cjs +0 -334
  239. package/packages/gen/dist/cli.d.cts +0 -1
  240. package/packages/gen/dist/cli.d.ts +0 -1
  241. package/packages/gen/dist/cli.js +0 -332
  242. package/packages/gen/dist/index.cjs +0 -257
  243. package/packages/gen/dist/index.d.cts +0 -12
  244. package/packages/gen/dist/index.d.ts +0 -12
  245. package/packages/gen/dist/index.js +0 -255
  246. package/packages/gen/node_modules/.bin/esbuild +0 -19
  247. package/packages/gen/node_modules/.bin/tsc +0 -22
  248. package/packages/gen/node_modules/.bin/tsserver +0 -22
  249. package/packages/gen/node_modules/.bin/tsup +0 -22
  250. package/packages/gen/node_modules/.bin/tsup-node +0 -22
  251. package/packages/gen/node_modules/.bin/vite +0 -22
  252. package/packages/gen/node_modules/.bin/vitest +0 -22
  253. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  254. package/packages/gen/package.json +0 -66
  255. package/packages/gen/src/cli.ts +0 -67
  256. package/packages/gen/src/generate.ts +0 -0
  257. package/packages/gen/src/index.ts +0 -2
  258. package/packages/gen/tests/compile.test.ts +0 -213
  259. package/packages/gen/tests/e2e.test.ts +0 -130
  260. package/packages/gen/tests/generate.test.ts +0 -396
  261. package/packages/gen/tsconfig.json +0 -5
  262. package/packages/gen/tsup.config.ts +0 -11
  263. package/packages/notifications/LICENSE +0 -21
  264. package/packages/notifications/README.md +0 -134
  265. package/packages/notifications/dist/index.cjs +0 -128
  266. package/packages/notifications/dist/index.d.cts +0 -81
  267. package/packages/notifications/dist/index.d.ts +0 -81
  268. package/packages/notifications/dist/index.js +0 -125
  269. package/packages/notifications/node_modules/.bin/esbuild +0 -19
  270. package/packages/notifications/node_modules/.bin/tsc +0 -22
  271. package/packages/notifications/node_modules/.bin/tsserver +0 -22
  272. package/packages/notifications/node_modules/.bin/tsup +0 -22
  273. package/packages/notifications/node_modules/.bin/tsup-node +0 -22
  274. package/packages/notifications/node_modules/.bin/vite +0 -22
  275. package/packages/notifications/node_modules/.bin/vitest +0 -22
  276. package/packages/notifications/package.json +0 -60
  277. package/packages/notifications/src/NotificationsService.ts +0 -230
  278. package/packages/notifications/src/index.ts +0 -19
  279. package/packages/notifications/tsconfig.json +0 -5
  280. package/packages/notifications/tsup.config.ts +0 -11
  281. package/packages/realtime/LICENSE +0 -21
  282. package/packages/realtime/README.md +0 -183
  283. package/packages/realtime/dist/index.cjs +0 -977
  284. package/packages/realtime/dist/index.d.cts +0 -259
  285. package/packages/realtime/dist/index.d.ts +0 -259
  286. package/packages/realtime/dist/index.js +0 -973
  287. package/packages/realtime/node_modules/.bin/esbuild +0 -19
  288. package/packages/realtime/node_modules/.bin/tsc +0 -22
  289. package/packages/realtime/node_modules/.bin/tsserver +0 -22
  290. package/packages/realtime/node_modules/.bin/tsup +0 -22
  291. package/packages/realtime/node_modules/.bin/tsup-node +0 -22
  292. package/packages/realtime/node_modules/.bin/vite +0 -22
  293. package/packages/realtime/node_modules/.bin/vitest +0 -22
  294. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  295. package/packages/realtime/package.json +0 -60
  296. package/packages/realtime/src/AuraChannel.ts +0 -485
  297. package/packages/realtime/src/HeartbeatManager.ts +0 -73
  298. package/packages/realtime/src/RealtimeService.ts +0 -505
  299. package/packages/realtime/src/columnFilter.ts +0 -301
  300. package/packages/realtime/src/index.ts +0 -4
  301. package/packages/realtime/tests/ack-routing.test.ts +0 -81
  302. package/packages/realtime/tests/column-filter.test.ts +0 -144
  303. package/packages/realtime/tests/dispatch.test.ts +0 -191
  304. package/packages/realtime/tests/heartbeat.test.ts +0 -67
  305. package/packages/realtime/tests/http-broadcast.test.ts +0 -68
  306. package/packages/realtime/tests/realtime-token.test.ts +0 -43
  307. package/packages/realtime/tests/reconnect-race.test.ts +0 -149
  308. package/packages/realtime/tests/ref-id.test.ts +0 -34
  309. package/packages/realtime/tests/set-auth.test.ts +0 -70
  310. package/packages/realtime/tsconfig.json +0 -5
  311. package/packages/realtime/tsup.config.ts +0 -11
  312. package/packages/storage/LICENSE +0 -21
  313. package/packages/storage/README.md +0 -141
  314. package/packages/storage/dist/index.cjs +0 -333
  315. package/packages/storage/dist/index.d.cts +0 -185
  316. package/packages/storage/dist/index.d.ts +0 -185
  317. package/packages/storage/dist/index.js +0 -329
  318. package/packages/storage/node_modules/.bin/esbuild +0 -19
  319. package/packages/storage/node_modules/.bin/tsc +0 -22
  320. package/packages/storage/node_modules/.bin/tsserver +0 -22
  321. package/packages/storage/node_modules/.bin/tsup +0 -22
  322. package/packages/storage/node_modules/.bin/tsup-node +0 -22
  323. package/packages/storage/node_modules/.bin/vite +0 -22
  324. package/packages/storage/node_modules/.bin/vitest +0 -22
  325. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  326. package/packages/storage/package.json +0 -60
  327. package/packages/storage/src/DownloadBuilder.ts +0 -60
  328. package/packages/storage/src/StorageService.ts +0 -323
  329. package/packages/storage/src/index.ts +0 -2
  330. package/packages/storage/src/path.ts +0 -35
  331. package/packages/storage/src/transform.ts +0 -15
  332. package/packages/storage/tests/download.test.ts +0 -64
  333. package/packages/storage/tests/parity.test.ts +0 -154
  334. package/packages/storage/tests/path.test.ts +0 -87
  335. package/packages/storage/tests/transform.test.ts +0 -79
  336. package/packages/storage/tsconfig.json +0 -5
  337. package/packages/storage/tsup.config.ts +0 -11
  338. package/pnpm-workspace.yaml +0 -24
  339. package/tsconfig.base.json +0 -15
  340. /package/{packages/aurabase/dist → dist}/index.cjs +0 -0
  341. /package/{packages/aurabase/dist → dist}/index.js +0 -0
  342. /package/{packages/aurabase/dist → dist}/ssr.cjs +0 -0
  343. /package/{packages/aurabase/dist → dist}/ssr.js +0 -0
@@ -1,301 +0,0 @@
1
- /**
2
- * Fallback client (second rideau) du filtre colonne `postgres_changes`.
3
- *
4
- * Le serveur `aura-realtime` est **autoritaire** : il filtre déjà les events CDC
5
- * par colonne avant de les envoyer (`services/aura-realtime/src/protocol.rs`).
6
- * Ce module reproduit fidèlement la même sémantique côté client, en defense-in-depth :
7
- * il ne peut que sur-filtrer (jamais laisser passer un event que le serveur aurait dû
8
- * exclure).
9
- *
10
- * Sémantique portée depuis le backend Rust (parité stricte) :
11
- * - Opérateurs : eq, neq, lt, lte, gt, gte, in, like, ilike, is, match, imatch, isdistinct.
12
- * - Syntaxe : `column=operator.value`, négation `column=not.operator.value`.
13
- * - AND par virgules : `a=eq.1,b=gt.2` (les virgules internes d'un `in.(a,b,c)` ne
14
- * séparent PAS les conditions — split respectant la profondeur des parenthèses).
15
- * - **Fail-CLOSED** : toute condition non parsable (syntaxe invalide, opérateur inconnu,
16
- * regex invalide, trop de valeurs `in`, pattern `match`/`imatch` trop long) => `false`,
17
- * jamais laissé passer.
18
- * - Champ absent du record => résultat non évaluable => exclusion ferme, **jamais
19
- * inversée** par `not.` (sémantique SQL : `NOT (col = x)` avec `col` NULL/absent
20
- * => NULL => exclu). Seul un résultat sémantique évaluable est inversé par `not.`.
21
- */
22
-
23
- type FilterOp =
24
- | 'eq'
25
- | 'neq'
26
- | 'lt'
27
- | 'lte'
28
- | 'gt'
29
- | 'gte'
30
- | 'in'
31
- | 'like'
32
- | 'ilike'
33
- | 'is'
34
- | 'match'
35
- | 'imatch'
36
- | 'isdistinct'
37
-
38
- const VALID_OPS: ReadonlySet<string> = new Set([
39
- 'eq',
40
- 'neq',
41
- 'lt',
42
- 'lte',
43
- 'gt',
44
- 'gte',
45
- 'in',
46
- 'like',
47
- 'ilike',
48
- 'is',
49
- 'match',
50
- 'imatch',
51
- 'isdistinct',
52
- ])
53
-
54
- /** Longueur max d'un pattern `match`/`imatch` — borne anti-ReDoS (parité backend). */
55
- const MAX_REGEX_PATTERN_LEN = 512
56
-
57
- /** Nombre max de valeurs dans un `in.(...)` — parité backend/PostgREST. */
58
- const MAX_IN_VALUES = 100
59
-
60
- interface ParsedColumnFilter {
61
- column: string
62
- op: FilterOp
63
- value: string
64
- /** `true` si le filtre était préfixé par `not.` — le résultat sémantique est inversé. */
65
- negated: boolean
66
- }
67
-
68
- /**
69
- * Parse une condition unique "column=op.value" ou "column=not.op.value".
70
- * Retourne `null` si non parsable (fail-closed — voir `matchesColumnFilter`).
71
- */
72
- function parseColumnFilter(filterStr: string): ParsedColumnFilter | null {
73
- const eqIdx = filterStr.indexOf('=')
74
- if (eqIdx === -1) return null
75
- const column = filterStr.slice(0, eqIdx)
76
- if (!column) return null
77
- let rest = filterStr.slice(eqIdx + 1)
78
-
79
- let negated = false
80
- if (rest.startsWith('not.')) {
81
- negated = true
82
- rest = rest.slice(4)
83
- }
84
-
85
- const dotIdx = rest.indexOf('.')
86
- if (dotIdx === -1) return null
87
- const opStr = rest.slice(0, dotIdx)
88
- let value = rest.slice(dotIdx + 1)
89
-
90
- if (!VALID_OPS.has(opStr)) return null
91
- const op = opStr as FilterOp
92
-
93
- if (op === 'in') {
94
- const stripped = value.replace(/^\(+/, '').replace(/\)+$/, '')
95
- if (stripped.split(',').length > MAX_IN_VALUES) return null
96
- value = stripped
97
- } else if (op === 'is') {
98
- // `is` n'accepte que null/true/false (parité PostgREST) : toute autre valeur est
99
- // rejetée DÈS LE PARSE. Sinon `is.<garbage>` serait accepté et `matchesRaw`
100
- // renverrait `false` (défaut) — un `not.is.<garbage>` inverserait ce `false` en
101
- // `true` => fail-OPEN (matche tout). On refuse ce filtre en amont (parité Rust).
102
- if (value !== 'null' && value !== 'true' && value !== 'false') return null
103
- } else if (op === 'match' || op === 'imatch') {
104
- if (value.length > MAX_REGEX_PATTERN_LEN) return null
105
- // Compiler la regex DÈS LE PARSE : un pattern invalide => filtre rejeté (null).
106
- // Sinon un `not.match.(invalide` verrait son échec de compilation transformé
107
- // en un `true` inversé => fail-OPEN. On refuse ce filtre en amont (parité Rust).
108
- try {
109
- // eslint-disable-next-line no-new
110
- new RegExp(value, op === 'imatch' ? 'i' : undefined)
111
- } catch {
112
- return null
113
- }
114
- }
115
-
116
- return { column, op, value, negated }
117
- }
118
-
119
- /**
120
- * Découpe un ensemble de conditions AND ("a=eq.1,b=in.(x,y)") en conditions
121
- * individuelles, en respectant les virgules internes d'un `in.(...)` (profondeur
122
- * de parenthèses). Ne fait PAS de parsing ici — juste le split structurel.
123
- */
124
- function splitTopLevel(s: string): string[] {
125
- const parts: string[] = []
126
- let depth = 0
127
- let start = 0
128
- for (let i = 0; i < s.length; i++) {
129
- const c = s[i]
130
- if (c === '(') depth += 1
131
- else if (c === ')') depth = Math.max(0, depth - 1)
132
- else if (c === ',' && depth === 0) {
133
- parts.push(s.slice(start, i))
134
- start = i + 1
135
- }
136
- }
137
- parts.push(s.slice(start))
138
- return parts
139
- }
140
-
141
- /**
142
- * Découpe puis parse un ensemble de conditions AND. **Fail-closed** : retourne
143
- * `null` dès qu'UNE condition est invalide (propage l'échec, n'ignore jamais
144
- * silencieusement une condition malformée).
145
- */
146
- function parseFilterSet(s: string): ParsedColumnFilter[] | null {
147
- const parts = splitTopLevel(s)
148
- const out: ParsedColumnFilter[] = []
149
- for (const part of parts) {
150
- const cf = parseColumnFilter(part.trim())
151
- if (!cf) return null
152
- out.push(cf)
153
- }
154
- return out
155
- }
156
-
157
- /** Convertit une valeur JSON en sa représentation string (comparaison textuelle/numérique). */
158
- function jsonValueToString(field: unknown): string {
159
- if (typeof field === 'string') return field
160
- if (typeof field === 'number' || typeof field === 'boolean') return String(field)
161
- if (field === null) return 'null'
162
- try {
163
- return JSON.stringify(field) ?? ''
164
- } catch {
165
- return ''
166
- }
167
- }
168
-
169
- /** Parse une valeur en `number` strict (rejette les chaînes vides/non-numériques). */
170
- function parseStrictNumber(s: string): number | null {
171
- if (s.trim() === '') return null
172
- const n = Number(s)
173
- return Number.isFinite(n) ? n : null
174
- }
175
-
176
- function compareNumeric(a: string, b: string, cmp: (a: number, b: number) => boolean): boolean {
177
- const an = parseStrictNumber(a)
178
- const bn = parseStrictNumber(b)
179
- if (an === null || bn === null) return false // types incompatibles => fail-closed
180
- return cmp(an, bn)
181
- }
182
-
183
- /** Convertit un motif SQL like/ilike (`%`→sous-chaîne, `_`→1 caractère) en RegExp ancrée. */
184
- function likeToRegExp(pattern: string, caseInsensitive: boolean): RegExp | null {
185
- let re = '^'
186
- for (const ch of pattern) {
187
- if (ch === '%') re += '.*'
188
- else if (ch === '_') re += '.'
189
- else re += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
190
- }
191
- re += '$'
192
- try {
193
- return new RegExp(re, caseInsensitive ? 'i' : undefined)
194
- } catch {
195
- return null
196
- }
197
- }
198
-
199
- /**
200
- * Évalue le prédicat brut (sans tenir compte de `negated`).
201
- * Retourne `null` si non évaluable (champ absent, regex invalide) — distinct
202
- * d'un `false` qui est un « faux » sémantique (inversable par `not.`).
203
- */
204
- function matchesRaw(cf: ParsedColumnFilter, record: Record<string, unknown>): boolean | null {
205
- // `is` compare la valeur JSON réelle (null/true/false) — toujours évaluable.
206
- if (cf.op === 'is') {
207
- const field = record[cf.column]
208
- switch (cf.value) {
209
- case 'null':
210
- return field === undefined || field === null
211
- case 'true':
212
- return field === true
213
- case 'false':
214
- return field === false
215
- default:
216
- // Invariant garanti par `parseColumnFilter` : `is.<autre>` est rejeté au parse
217
- // (null), ce bras est donc défensif uniquement (jamais atteint en pratique).
218
- return false
219
- }
220
- }
221
-
222
- // `isdistinct` : NULL est distinct de toute valeur non-NULL — toujours évaluable.
223
- if (cf.op === 'isdistinct') {
224
- const field = record[cf.column]
225
- const fieldIsNull = field === undefined || field === null
226
- if (fieldIsNull) return true
227
- return jsonValueToString(field) !== cf.value
228
- }
229
-
230
- // Champ absent => non évaluable (null), JAMAIS un false inversable.
231
- if (!(cf.column in record) || record[cf.column] === undefined) return null
232
- const field = record[cf.column]
233
- const fieldStr = jsonValueToString(field)
234
-
235
- switch (cf.op) {
236
- case 'eq':
237
- return fieldStr === cf.value
238
- case 'neq':
239
- return fieldStr !== cf.value
240
- case 'lt':
241
- return compareNumeric(fieldStr, cf.value, (a, b) => a < b)
242
- case 'lte':
243
- return compareNumeric(fieldStr, cf.value, (a, b) => a <= b)
244
- case 'gt':
245
- return compareNumeric(fieldStr, cf.value, (a, b) => a > b)
246
- case 'gte':
247
- return compareNumeric(fieldStr, cf.value, (a, b) => a >= b)
248
- case 'in':
249
- return cf.value
250
- .split(',')
251
- .map((v) => v.trim())
252
- .some((v) => v === fieldStr)
253
- case 'like': {
254
- const re = likeToRegExp(cf.value, false)
255
- return re ? re.test(fieldStr) : null
256
- }
257
- case 'ilike': {
258
- const re = likeToRegExp(cf.value, true)
259
- return re ? re.test(fieldStr) : null
260
- }
261
- case 'match': {
262
- try {
263
- return new RegExp(cf.value).test(fieldStr)
264
- } catch {
265
- return null
266
- }
267
- }
268
- case 'imatch': {
269
- try {
270
- return new RegExp(cf.value, 'i').test(fieldStr)
271
- } catch {
272
- return null
273
- }
274
- }
275
- default:
276
- return null
277
- }
278
- }
279
-
280
- /** Applique la négation UNIQUEMENT au résultat sémantique évaluable (jamais à `null`). */
281
- function matchesSingle(cf: ParsedColumnFilter, record: Record<string, unknown>): boolean {
282
- const raw = matchesRaw(cf, record)
283
- if (raw === null) return false // non évaluable => exclusion ferme, pas d'inversion
284
- return cf.negated ? !raw : raw
285
- }
286
-
287
- /**
288
- * Vérifie si `record` matche le filtre colonne `filter` (syntaxe Supabase/PostgREST) :
289
- * `column=operator.value`, négation `not.`, conditions AND séparées par virgules.
290
- *
291
- * **Fail-CLOSED** : si `filter` n'est pas parsable dans son ensemble (une seule
292
- * condition invalide suffit), retourne `false` — jamais un match par défaut.
293
- *
294
- * Ce helper est un second rideau client (defense-in-depth) : le serveur
295
- * `aura-realtime` a déjà filtré l'event de façon autoritaire.
296
- */
297
- export function matchesColumnFilter(filter: string, record: Record<string, unknown>): boolean {
298
- const set = parseFilterSet(filter)
299
- if (!set) return false
300
- return set.every((cf) => matchesSingle(cf, record))
301
- }
@@ -1,4 +0,0 @@
1
- export { RealtimeService } from './RealtimeService'
2
- export type { ConnectionState } from './RealtimeService'
3
- export { AuraChannel } from './AuraChannel'
4
- export { HeartbeatManager } from './HeartbeatManager'
@@ -1,81 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { RealtimeService } from '../src/RealtimeService'
3
- import { AuraChannel } from '../src/AuraChannel'
4
- import { BaseAuthStore } from '@aurabase/core'
5
- import type { AuraClientLike } from '@aurabase/core'
6
-
7
- function client(): AuraClientLike {
8
- return {
9
- baseUrl: 'http://x',
10
- projectId: 'proj',
11
- apiKey: 'k',
12
- authStore: new BaseAuthStore(),
13
- fetchFn: (async () => new Response('{}')) as unknown as typeof globalThis.fetch,
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- auth: { refreshSession: async () => ({ data: null, error: null }) as any },
16
- }
17
- }
18
-
19
- function seedChannels(rt: RealtimeService, names: string[]): Map<string, AuraChannel> {
20
- const map = (rt as unknown as { _channels: Map<string, AuraChannel> })._channels
21
- for (const n of names) map.set(n, new AuraChannel(n, 'proj'))
22
- return map
23
- }
24
-
25
- function dispatch(rt: RealtimeService, msg: unknown): void {
26
- ;(rt as unknown as { _dispatch: (m: unknown) => void })._dispatch(msg)
27
- }
28
-
29
- describe('RealtimeService._dispatch — routage broadcast_ack par ref_id', () => {
30
- it("un broadcast_ack n'est dispatché qu'au channel émetteur (extrait du préfixe ref_id)", () => {
31
- const rt = new RealtimeService(client())
32
- const map = seedChannels(rt, ['room-1', 'room-2'])
33
- const spy1 = vi.spyOn(map.get('room-1')!, '_onMessage').mockImplementation(() => {})
34
- const spy2 = vi.spyOn(map.get('room-2')!, '_onMessage').mockImplementation(() => {})
35
-
36
- dispatch(rt, { type: 'broadcast_ack', message_id: 'm1', ref_id: 'proj:room-1:r3' })
37
-
38
- expect(spy1).toHaveBeenCalledTimes(1)
39
- expect(spy2).not.toHaveBeenCalled()
40
- })
41
-
42
- it('résout par message_id absent de ref_id ? — fallback dispatch à tous si ref_id non résoluble', () => {
43
- const rt = new RealtimeService(client())
44
- const map = seedChannels(rt, ['room-1', 'room-2'])
45
- const spy1 = vi.spyOn(map.get('room-1')!, '_onMessage').mockImplementation(() => {})
46
- const spy2 = vi.spyOn(map.get('room-2')!, '_onMessage').mockImplementation(() => {})
47
-
48
- // Pas de ref_id (message_id brut, aucun ':r') → non résoluble → fallback à tous.
49
- dispatch(rt, { type: 'broadcast_ack', message_id: 'uuid-sans-prefixe' })
50
-
51
- expect(spy1).toHaveBeenCalledTimes(1)
52
- expect(spy2).toHaveBeenCalledTimes(1)
53
- })
54
-
55
- it("la promesse channel.send(ack:true) se résout 'ok' sur réception de l'ACK routé", async () => {
56
- const rt = new RealtimeService(client())
57
- const map = (rt as unknown as { _channels: Map<string, AuraChannel> })._channels
58
- const ch = new AuraChannel('room-1', 'proj', { broadcast: { ack: true } })
59
- const sent: string[] = []
60
- ch._bindSend((d) => sent.push(d))
61
- map.set('room-1', ch)
62
-
63
- const p = ch.send({ type: 'broadcast', event: 'ping', payload: { a: 1 } })
64
-
65
- // Récupérer le ref_id réellement émis par le channel.
66
- const outbound = JSON.parse(sent[0]!) as { ref_id: string }
67
- dispatch(rt, { type: 'broadcast_ack', message_id: 'm', ref_id: outbound.ref_id })
68
-
69
- await expect(p).resolves.toBe('ok')
70
- })
71
-
72
- it('nom de channel contenant ":r" — lastIndexOf isole bien le suffixe :r{n}', () => {
73
- const rt = new RealtimeService(client())
74
- const map = seedChannels(rt, ['weird:r-name'])
75
- const spy = vi.spyOn(map.get('weird:r-name')!, '_onMessage').mockImplementation(() => {})
76
-
77
- dispatch(rt, { type: 'broadcast_ack', message_id: 'm', ref_id: 'proj:weird:r-name:r7' })
78
-
79
- expect(spy).toHaveBeenCalledTimes(1)
80
- })
81
- })
@@ -1,144 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { matchesColumnFilter } from '../src/columnFilter'
3
-
4
- // Ces tests portent la même sémantique que le backend Rust
5
- // (services/aura-realtime/src/protocol.rs) : eq/neq/lt/lte/gt/gte/in/like/ilike/
6
- // is/match/imatch/isdistinct, négation `not.`, AND par virgules (hors `in.(...)`),
7
- // fail-closed sur filtre non parsable, champ absent = exclusion ferme (jamais inversée).
8
-
9
- describe('matchesColumnFilter — opérateurs de base', () => {
10
- it('eq', () => {
11
- expect(matchesColumnFilter('room_id=eq.42', { room_id: 42 })).toBe(true)
12
- expect(matchesColumnFilter('room_id=eq.42', { room_id: 7 })).toBe(false)
13
- })
14
-
15
- it('neq', () => {
16
- expect(matchesColumnFilter('status=neq.deleted', { status: 'active' })).toBe(true)
17
- expect(matchesColumnFilter('status=neq.deleted', { status: 'deleted' })).toBe(false)
18
- })
19
-
20
- it('lt/lte/gt/gte numérique', () => {
21
- expect(matchesColumnFilter('age=lt.18', { age: 10 })).toBe(true)
22
- expect(matchesColumnFilter('age=lt.18', { age: 18 })).toBe(false)
23
- expect(matchesColumnFilter('age=lte.18', { age: 18 })).toBe(true)
24
- expect(matchesColumnFilter('qty=gt.5', { qty: 10 })).toBe(true)
25
- expect(matchesColumnFilter('qty=gt.5', { qty: 5 })).toBe(false)
26
- expect(matchesColumnFilter('score=gte.50', { score: 50 })).toBe(true)
27
- expect(matchesColumnFilter('score=gte.50', { score: 49 })).toBe(false)
28
- })
29
-
30
- it('lt/gt sur types incompatibles => false (fail-closed)', () => {
31
- expect(matchesColumnFilter('name=gt.100', { name: 'hello' })).toBe(false)
32
- })
33
-
34
- it('in — respecte les parenthèses et les espaces', () => {
35
- expect(matchesColumnFilter('status=in.(a,b)', { status: 'b' })).toBe(true)
36
- expect(matchesColumnFilter('status=in.(a,b)', { status: 'c' })).toBe(false)
37
- expect(matchesColumnFilter('status=in.(active, pending, done)', { status: 'pending' })).toBe(true)
38
- })
39
-
40
- it('like / ilike', () => {
41
- expect(matchesColumnFilter('name=like.jo%', { name: 'john' })).toBe(true)
42
- expect(matchesColumnFilter('name=like.jo%', { name: 'alice' })).toBe(false)
43
- expect(matchesColumnFilter('name=ilike.JO%', { name: 'john' })).toBe(true)
44
- expect(matchesColumnFilter('name=ilike.JO%', { name: 'JOHN' })).toBe(true)
45
- expect(matchesColumnFilter('name=ilike.JO%', { name: 'alice' })).toBe(false)
46
- })
47
-
48
- it('is — null/true/false sur la valeur réelle', () => {
49
- expect(matchesColumnFilter('deleted_at=is.null', { deleted_at: null })).toBe(true)
50
- expect(matchesColumnFilter('deleted_at=is.null', { deleted_at: '2026' })).toBe(false)
51
- expect(matchesColumnFilter('active=is.true', { active: true })).toBe(true)
52
- expect(matchesColumnFilter('active=is.true', { active: false })).toBe(false)
53
- expect(matchesColumnFilter('active=is.false', { active: false })).toBe(true)
54
- expect(matchesColumnFilter('active=is.false', { active: true })).toBe(false)
55
- })
56
-
57
- it('match / imatch — RegExp', () => {
58
- expect(matchesColumnFilter('email=match.^[a-z]+@example\\.com$', { email: 'alice@example.com' })).toBe(true)
59
- expect(matchesColumnFilter('email=match.^[a-z]+@example\\.com$', { email: 'ALICE@example.com' })).toBe(false)
60
- expect(matchesColumnFilter('email=imatch.^[a-z]+@example\\.com$', { email: 'ALICE@example.com' })).toBe(true)
61
- })
62
-
63
- it('isdistinct — NULL distinct de toute valeur non-NULL', () => {
64
- expect(matchesColumnFilter('status=isdistinct.done', { status: 'open' })).toBe(true)
65
- expect(matchesColumnFilter('status=isdistinct.done', { status: 'done' })).toBe(false)
66
- expect(matchesColumnFilter('status=isdistinct.done', { status: null })).toBe(true)
67
- })
68
- })
69
-
70
- describe('matchesColumnFilter — négation not.', () => {
71
- it('inverse le résultat sémantique', () => {
72
- expect(matchesColumnFilter('status=not.eq.done', { status: 'open' })).toBe(true)
73
- expect(matchesColumnFilter('status=not.eq.done', { status: 'done' })).toBe(false)
74
- })
75
-
76
- it('ne fail-open jamais sur champ absent (non évaluable => exclusion ferme)', () => {
77
- // Sémantique SQL : NOT (col = x) avec col NULL/absent => NULL => exclu, pas inversé en true.
78
- expect(matchesColumnFilter('status=not.eq.done', { id: 1 })).toBe(false)
79
- })
80
- })
81
-
82
- describe('matchesColumnFilter — AND (virgules)', () => {
83
- it('toutes les conditions doivent matcher', () => {
84
- expect(matchesColumnFilter('room_id=eq.42,qty=gt.5', { room_id: 42, qty: 9 })).toBe(true)
85
- expect(matchesColumnFilter('room_id=eq.42,qty=gt.5', { room_id: 42, qty: 1 })).toBe(false)
86
- })
87
-
88
- it("respecte les virgules internes d'un in.(...)", () => {
89
- expect(matchesColumnFilter('status=in.(a,b),qty=gt.5', { status: 'b', qty: 9 })).toBe(true)
90
- expect(matchesColumnFilter('status=in.(a,b),qty=gt.5', { status: 'c', qty: 9 })).toBe(false)
91
- })
92
- })
93
-
94
- describe('matchesColumnFilter — fail-closed', () => {
95
- it('filtre non parsable (syntaxe invalide) => false', () => {
96
- expect(matchesColumnFilter('garbage', { a: 1 })).toBe(false)
97
- })
98
-
99
- it('opérateur inconnu => false', () => {
100
- expect(matchesColumnFilter('col=unknown.val', { col: 'val' })).toBe(false)
101
- })
102
-
103
- it('une condition AND invalide invalide tout le set => false', () => {
104
- expect(matchesColumnFilter('room_id=eq.42,garbage', { room_id: 42 })).toBe(false)
105
- })
106
-
107
- it('regex match/imatch invalide => filtre rejeté (false), jamais inversé par not.', () => {
108
- expect(matchesColumnFilter('name=match.(unclosed', { name: 'anything' })).toBe(false)
109
- expect(matchesColumnFilter('name=not.match.(unclosed', { name: 'anything' })).toBe(false)
110
- })
111
-
112
- it('pattern match trop long (>512) => rejeté', () => {
113
- const longPattern = 'a'.repeat(513)
114
- expect(matchesColumnFilter(`name=match.${longPattern}`, { name: 'a'.repeat(513) })).toBe(false)
115
- })
116
-
117
- it('in avec plus de 100 valeurs => rejeté', () => {
118
- const values = Array.from({ length: 101 }, (_, i) => String(i + 1)).join(',')
119
- expect(matchesColumnFilter(`id=in.(${values})`, { id: 1 })).toBe(false)
120
- })
121
-
122
- it('is.<valeur invalide> => rejeté au parse (pas null/true/false)', () => {
123
- expect(matchesColumnFilter('active=is.garbage', { active: true })).toBe(false)
124
- expect(matchesColumnFilter('active=is.garbage', { active: false })).toBe(false)
125
- })
126
-
127
- it('not.is.<valeur invalide> ne fail-open jamais (recoin fermé)', () => {
128
- // Avant le fix : is.garbage était accepté au parse, matchesRaw renvoyait false,
129
- // et `not.` inversait ce false en true => le filtre matchait tout event.
130
- expect(matchesColumnFilter('active=not.is.garbage', { active: true })).toBe(false)
131
- expect(matchesColumnFilter('active=not.is.garbage', { active: false })).toBe(false)
132
- })
133
- })
134
-
135
- describe('matchesColumnFilter — champ absent', () => {
136
- it('champ absent => exclusion ferme (jamais un match)', () => {
137
- expect(matchesColumnFilter('xyz=eq.1', { other: 1 })).toBe(false)
138
- })
139
-
140
- it('champ null (présent) sur eq => comparaison textuelle "null", pas une exclusion', () => {
141
- expect(matchesColumnFilter('name=eq.test', { name: null })).toBe(false)
142
- expect(matchesColumnFilter('name=eq.null', { name: null })).toBe(true)
143
- })
144
- })