@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.
- package/README.md +26 -795
- package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts → dist/AuraClient-BcFE8u1v.d.cts} +1 -1
- package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts → dist/AuraClient-BcFE8u1v.d.ts} +1 -1
- package/{packages/aurabase/dist → dist}/index.d.cts +1 -1
- package/{packages/aurabase/dist → dist}/index.d.ts +1 -1
- package/{packages/aurabase/dist → dist}/ssr.d.cts +1 -1
- package/{packages/aurabase/dist → dist}/ssr.d.ts +1 -1
- package/package.json +72 -82
- package/.changeset/config.json +0 -11
- package/CHANGELOG.md +0 -30
- package/packages/ai/LICENSE +0 -21
- package/packages/ai/README.md +0 -106
- package/packages/ai/dist/index.cjs +0 -229
- package/packages/ai/dist/index.d.cts +0 -139
- package/packages/ai/dist/index.d.ts +0 -139
- package/packages/ai/dist/index.js +0 -226
- package/packages/ai/node_modules/.bin/esbuild +0 -19
- package/packages/ai/node_modules/.bin/tsc +0 -22
- package/packages/ai/node_modules/.bin/tsserver +0 -22
- package/packages/ai/node_modules/.bin/tsup +0 -22
- package/packages/ai/node_modules/.bin/tsup-node +0 -22
- package/packages/ai/node_modules/.bin/vite +0 -22
- package/packages/ai/node_modules/.bin/vitest +0 -22
- package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/ai/package.json +0 -61
- package/packages/ai/src/AiService.ts +0 -226
- package/packages/ai/src/index.ts +0 -18
- package/packages/ai/src/sse.ts +0 -141
- package/packages/ai/src/types.ts +0 -30
- package/packages/ai/tests/chat-stream.test.ts +0 -151
- package/packages/ai/tests/rag-namespaces.test.ts +0 -54
- package/packages/ai/tests/sse.test.ts +0 -69
- package/packages/ai/tsconfig.json +0 -5
- package/packages/ai/tsup.config.ts +0 -11
- package/packages/aurabase/LICENSE +0 -21
- package/packages/aurabase/README.md +0 -64
- package/packages/aurabase/node_modules/.bin/esbuild +0 -19
- package/packages/aurabase/node_modules/.bin/tsc +0 -22
- package/packages/aurabase/node_modules/.bin/tsserver +0 -22
- package/packages/aurabase/node_modules/.bin/tsup +0 -22
- package/packages/aurabase/node_modules/.bin/tsup-node +0 -22
- package/packages/aurabase/node_modules/.bin/vite +0 -22
- package/packages/aurabase/node_modules/.bin/vitest +0 -22
- package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/aurabase/package.json +0 -80
- package/packages/aurabase/src/AuraClient.ts +0 -237
- package/packages/aurabase/src/index.ts +0 -152
- package/packages/aurabase/src/ssr.ts +0 -156
- package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +0 -82
- package/packages/aurabase/tests/ai.test.ts +0 -111
- package/packages/aurabase/tests/auth-admin.test.ts +0 -515
- package/packages/aurabase/tests/auth-e2e.test.ts +0 -135
- package/packages/aurabase/tests/auth-hardening.test.ts +0 -206
- package/packages/aurabase/tests/auth.test.ts +0 -947
- package/packages/aurabase/tests/database.test.ts +0 -230
- package/packages/aurabase/tests/db-types-e2e.test.ts +0 -217
- package/packages/aurabase/tests/dispose.test.ts +0 -23
- package/packages/aurabase/tests/foreign-token.e2e.test.ts +0 -129
- package/packages/aurabase/tests/functions.test.ts +0 -107
- package/packages/aurabase/tests/integration.test.ts +0 -413
- package/packages/aurabase/tests/live-production-validation.js +0 -241
- package/packages/aurabase/tests/live-sdk-production-validation.js +0 -293
- package/packages/aurabase/tests/mocks.ts +0 -139
- package/packages/aurabase/tests/notifications.test.ts +0 -314
- package/packages/aurabase/tests/observability-backup.test.ts +0 -186
- package/packages/aurabase/tests/observability-hook.test.ts +0 -130
- package/packages/aurabase/tests/production-robustness.test.ts +0 -268
- package/packages/aurabase/tests/public-api-surface.test.ts +0 -53
- package/packages/aurabase/tests/query-builder.test.ts +0 -329
- package/packages/aurabase/tests/readme-smoke-e2e.test.ts +0 -231
- package/packages/aurabase/tests/realtime-filter-e2e.test.ts +0 -344
- package/packages/aurabase/tests/refresh-locks.test.ts +0 -210
- package/packages/aurabase/tests/retry-option.test.ts +0 -17
- package/packages/aurabase/tests/rpc-e2e.test.ts +0 -254
- package/packages/aurabase/tests/rs256-local-e2e.test.ts +0 -114
- package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +0 -235
- package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +0 -124
- package/packages/aurabase/tests/service-role-guard.test.ts +0 -99
- package/packages/aurabase/tests/ssr.test.ts +0 -91
- package/packages/aurabase/tests/storage-backend-e2e.test.ts +0 -481
- package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +0 -104
- package/packages/aurabase/tests/storage-transform-e2e.test.ts +0 -684
- package/packages/aurabase/tests/storage.test.ts +0 -55
- package/packages/aurabase/tests/types/client-generic.test-d.ts +0 -79
- package/packages/aurabase/tests/types/docs-snippets.test-d.ts +0 -571
- package/packages/aurabase/tests/types/error-code.test-d.ts +0 -53
- package/packages/aurabase/tsconfig.json +0 -5
- package/packages/aurabase/tsconfig.test-d.json +0 -5
- package/packages/aurabase/tsup.config.ts +0 -11
- package/packages/aurabase/vitest.config.ts +0 -16
- package/packages/aurabase/vitest.e2e.config.ts +0 -9
- package/packages/auth/LICENSE +0 -21
- package/packages/auth/README.md +0 -215
- package/packages/auth/dist/index.cjs +0 -1175
- package/packages/auth/dist/index.d.cts +0 -397
- package/packages/auth/dist/index.d.ts +0 -397
- package/packages/auth/dist/index.js +0 -1170
- package/packages/auth/node_modules/.bin/esbuild +0 -19
- package/packages/auth/node_modules/.bin/tsc +0 -22
- package/packages/auth/node_modules/.bin/tsserver +0 -22
- package/packages/auth/node_modules/.bin/tsup +0 -22
- package/packages/auth/node_modules/.bin/tsup-node +0 -22
- package/packages/auth/node_modules/.bin/vite +0 -22
- package/packages/auth/node_modules/.bin/vitest +0 -22
- package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/auth/package.json +0 -61
- package/packages/auth/src/AuthAdminService.ts +0 -149
- package/packages/auth/src/AuthService.ts +0 -1166
- package/packages/auth/src/SessionManager.ts +0 -92
- package/packages/auth/src/index.ts +0 -17
- package/packages/auth/src/jwks.ts +0 -337
- package/packages/auth/src/pkce.ts +0 -27
- package/packages/auth/src/types.ts +0 -89
- package/packages/auth/tests/auth-lifecycle.test.ts +0 -149
- package/packages/auth/tests/detect-session.test.ts +0 -361
- package/packages/auth/tests/getclaims.test.ts +0 -780
- package/packages/auth/tests/is-mfa-challenge.test.ts +0 -27
- package/packages/auth/tests/password-recovery.test.ts +0 -124
- package/packages/auth/tests/pkce.test.ts +0 -192
- package/packages/auth/tests/session-manager.test.ts +0 -103
- package/packages/auth/tests/set-session.test.ts +0 -139
- package/packages/auth/tests/verify-otp.test.ts +0 -153
- package/packages/auth/tsconfig.json +0 -5
- package/packages/auth/tsup.config.ts +0 -11
- package/packages/core/LICENSE +0 -21
- package/packages/core/README.md +0 -132
- package/packages/core/dist/index.cjs +0 -695
- package/packages/core/dist/index.d.cts +0 -1137
- package/packages/core/dist/index.d.ts +0 -1137
- package/packages/core/dist/index.js +0 -673
- package/packages/core/node_modules/.bin/esbuild +0 -22
- package/packages/core/node_modules/.bin/tsc +0 -22
- package/packages/core/node_modules/.bin/tsserver +0 -22
- package/packages/core/node_modules/.bin/tsup +0 -22
- package/packages/core/node_modules/.bin/tsup-node +0 -22
- package/packages/core/node_modules/.bin/vite +0 -22
- package/packages/core/node_modules/.bin/vitest +0 -22
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/core/package.json +0 -57
- package/packages/core/src/AuraError.ts +0 -89
- package/packages/core/src/BaseService.ts +0 -44
- package/packages/core/src/client.ts +0 -34
- package/packages/core/src/cookie-utils.ts +0 -42
- package/packages/core/src/fetch.ts +0 -435
- package/packages/core/src/index.ts +0 -22
- package/packages/core/src/retry.ts +0 -63
- package/packages/core/src/serialize.ts +0 -6
- package/packages/core/src/stores/BaseAuthStore.ts +0 -164
- package/packages/core/src/stores/LocalAuthStore.ts +0 -199
- package/packages/core/src/types.ts +0 -1042
- package/packages/core/tests/auth-store.test.ts +0 -300
- package/packages/core/tests/error-code.test.ts +0 -61
- package/packages/core/tests/error.test.ts +0 -24
- package/packages/core/tests/fetch.test.ts +0 -286
- package/packages/core/tests/mocks.ts +0 -139
- package/packages/core/tests/observability.test.ts +0 -86
- package/packages/core/tests/refresh-hardening.test.ts +0 -164
- package/packages/core/tests/refresh-observability.test.ts +0 -111
- package/packages/core/tests/retry.test.ts +0 -106
- package/packages/core/tests/serialize.test.ts +0 -12
- package/packages/core/tests/timeout.test.ts +0 -79
- package/packages/core/tsconfig.json +0 -5
- package/packages/core/tsup.config.ts +0 -11
- package/packages/db/LICENSE +0 -21
- package/packages/db/README.md +0 -152
- package/packages/db/VENDORED.md +0 -93
- package/packages/db/dist/index.cjs +0 -544
- package/packages/db/dist/index.d.cts +0 -1111
- package/packages/db/dist/index.d.ts +0 -1111
- package/packages/db/dist/index.js +0 -541
- package/packages/db/node_modules/.bin/esbuild +0 -19
- package/packages/db/node_modules/.bin/tsc +0 -22
- package/packages/db/node_modules/.bin/tsserver +0 -22
- package/packages/db/node_modules/.bin/tsup +0 -22
- package/packages/db/node_modules/.bin/tsup-node +0 -22
- package/packages/db/node_modules/.bin/vite +0 -22
- package/packages/db/node_modules/.bin/vitest +0 -22
- package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/db/package.json +0 -62
- package/packages/db/src/DatabaseService.ts +0 -170
- package/packages/db/src/QueryBuilder.ts +0 -830
- package/packages/db/src/index.ts +0 -2
- package/packages/db/src/pgrst-types/common/common.ts +0 -86
- package/packages/db/src/pgrst-types/common/rpc.ts +0 -151
- package/packages/db/src/pgrst-types/feature-flags.ts +0 -20
- package/packages/db/src/pgrst-types/types.ts +0 -134
- package/packages/db/src/select-query-parser/parser.ts +0 -479
- package/packages/db/src/select-query-parser/result.ts +0 -553
- package/packages/db/src/select-query-parser/types.ts +0 -132
- package/packages/db/src/select-query-parser/utils.ts +0 -707
- package/packages/db/tests/filters.test.ts +0 -81
- package/packages/db/tests/mutations-filtered.e2e.test.ts +0 -132
- package/packages/db/tests/mutations-filtered.test.ts +0 -188
- package/packages/db/tests/order.test.ts +0 -45
- package/packages/db/tests/rpc.test.ts +0 -41
- package/packages/db/tests/scale/build-large-schema.ts +0 -94
- package/packages/db/tests/scale/scale-compile.test.ts +0 -167
- package/packages/db/tests/schema.test.ts +0 -77
- package/packages/db/tests/single-accept.e2e.test.ts +0 -123
- package/packages/db/tests/single.test.ts +0 -211
- package/packages/db/tests/types/filters.test-d.ts +0 -52
- package/packages/db/tests/types/fixtures.ts +0 -40
- package/packages/db/tests/types/from-views.test-d.ts +0 -18
- package/packages/db/tests/types/mutations.test-d.ts +0 -23
- package/packages/db/tests/types/rpc.test-d.ts +0 -60
- package/packages/db/tests/types/schema-shape.test-d.ts +0 -8
- package/packages/db/tests/types/select-inference.test-d.ts +0 -38
- package/packages/db/tests/vendored/fixtures.ts +0 -83
- package/packages/db/tests/vendored/get-result.test-d.ts +0 -75
- package/packages/db/tsconfig.json +0 -5
- package/packages/db/tsconfig.test-d.json +0 -5
- package/packages/db/tsup.config.ts +0 -11
- package/packages/db/vitest.config.ts +0 -8
- package/packages/functions/LICENSE +0 -21
- package/packages/functions/README.md +0 -96
- package/packages/functions/dist/index.cjs +0 -173
- package/packages/functions/dist/index.d.cts +0 -81
- package/packages/functions/dist/index.d.ts +0 -81
- package/packages/functions/dist/index.js +0 -168
- package/packages/functions/node_modules/.bin/esbuild +0 -19
- package/packages/functions/node_modules/.bin/tsc +0 -22
- package/packages/functions/node_modules/.bin/tsserver +0 -22
- package/packages/functions/node_modules/.bin/tsup +0 -22
- package/packages/functions/node_modules/.bin/tsup-node +0 -22
- package/packages/functions/node_modules/.bin/vite +0 -22
- package/packages/functions/node_modules/.bin/vitest +0 -22
- package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/functions/package.json +0 -60
- package/packages/functions/src/FunctionsService.ts +0 -188
- package/packages/functions/src/errors.ts +0 -70
- package/packages/functions/src/index.ts +0 -6
- package/packages/functions/tests/invoke.test.ts +0 -97
- package/packages/functions/tests/response-type-errors.test.ts +0 -135
- package/packages/functions/tsconfig.json +0 -5
- package/packages/functions/tsup.config.ts +0 -11
- package/packages/gen/LICENSE +0 -21
- package/packages/gen/README.md +0 -84
- package/packages/gen/dist/cli.cjs +0 -334
- package/packages/gen/dist/cli.d.cts +0 -1
- package/packages/gen/dist/cli.d.ts +0 -1
- package/packages/gen/dist/cli.js +0 -332
- package/packages/gen/dist/index.cjs +0 -257
- package/packages/gen/dist/index.d.cts +0 -12
- package/packages/gen/dist/index.d.ts +0 -12
- package/packages/gen/dist/index.js +0 -255
- package/packages/gen/node_modules/.bin/esbuild +0 -19
- package/packages/gen/node_modules/.bin/tsc +0 -22
- package/packages/gen/node_modules/.bin/tsserver +0 -22
- package/packages/gen/node_modules/.bin/tsup +0 -22
- package/packages/gen/node_modules/.bin/tsup-node +0 -22
- package/packages/gen/node_modules/.bin/vite +0 -22
- package/packages/gen/node_modules/.bin/vitest +0 -22
- package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/gen/package.json +0 -66
- package/packages/gen/src/cli.ts +0 -67
- package/packages/gen/src/generate.ts +0 -0
- package/packages/gen/src/index.ts +0 -2
- package/packages/gen/tests/compile.test.ts +0 -213
- package/packages/gen/tests/e2e.test.ts +0 -130
- package/packages/gen/tests/generate.test.ts +0 -396
- package/packages/gen/tsconfig.json +0 -5
- package/packages/gen/tsup.config.ts +0 -11
- package/packages/notifications/LICENSE +0 -21
- package/packages/notifications/README.md +0 -134
- package/packages/notifications/dist/index.cjs +0 -128
- package/packages/notifications/dist/index.d.cts +0 -81
- package/packages/notifications/dist/index.d.ts +0 -81
- package/packages/notifications/dist/index.js +0 -125
- package/packages/notifications/node_modules/.bin/esbuild +0 -19
- package/packages/notifications/node_modules/.bin/tsc +0 -22
- package/packages/notifications/node_modules/.bin/tsserver +0 -22
- package/packages/notifications/node_modules/.bin/tsup +0 -22
- package/packages/notifications/node_modules/.bin/tsup-node +0 -22
- package/packages/notifications/node_modules/.bin/vite +0 -22
- package/packages/notifications/node_modules/.bin/vitest +0 -22
- package/packages/notifications/package.json +0 -60
- package/packages/notifications/src/NotificationsService.ts +0 -230
- package/packages/notifications/src/index.ts +0 -19
- package/packages/notifications/tsconfig.json +0 -5
- package/packages/notifications/tsup.config.ts +0 -11
- package/packages/realtime/LICENSE +0 -21
- package/packages/realtime/README.md +0 -183
- package/packages/realtime/dist/index.cjs +0 -977
- package/packages/realtime/dist/index.d.cts +0 -259
- package/packages/realtime/dist/index.d.ts +0 -259
- package/packages/realtime/dist/index.js +0 -973
- package/packages/realtime/node_modules/.bin/esbuild +0 -19
- package/packages/realtime/node_modules/.bin/tsc +0 -22
- package/packages/realtime/node_modules/.bin/tsserver +0 -22
- package/packages/realtime/node_modules/.bin/tsup +0 -22
- package/packages/realtime/node_modules/.bin/tsup-node +0 -22
- package/packages/realtime/node_modules/.bin/vite +0 -22
- package/packages/realtime/node_modules/.bin/vitest +0 -22
- package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/realtime/package.json +0 -60
- package/packages/realtime/src/AuraChannel.ts +0 -485
- package/packages/realtime/src/HeartbeatManager.ts +0 -73
- package/packages/realtime/src/RealtimeService.ts +0 -505
- package/packages/realtime/src/columnFilter.ts +0 -301
- package/packages/realtime/src/index.ts +0 -4
- package/packages/realtime/tests/ack-routing.test.ts +0 -81
- package/packages/realtime/tests/column-filter.test.ts +0 -144
- package/packages/realtime/tests/dispatch.test.ts +0 -191
- package/packages/realtime/tests/heartbeat.test.ts +0 -67
- package/packages/realtime/tests/http-broadcast.test.ts +0 -68
- package/packages/realtime/tests/realtime-token.test.ts +0 -43
- package/packages/realtime/tests/reconnect-race.test.ts +0 -149
- package/packages/realtime/tests/ref-id.test.ts +0 -34
- package/packages/realtime/tests/set-auth.test.ts +0 -70
- package/packages/realtime/tsconfig.json +0 -5
- package/packages/realtime/tsup.config.ts +0 -11
- package/packages/storage/LICENSE +0 -21
- package/packages/storage/README.md +0 -141
- package/packages/storage/dist/index.cjs +0 -333
- package/packages/storage/dist/index.d.cts +0 -185
- package/packages/storage/dist/index.d.ts +0 -185
- package/packages/storage/dist/index.js +0 -329
- package/packages/storage/node_modules/.bin/esbuild +0 -19
- package/packages/storage/node_modules/.bin/tsc +0 -22
- package/packages/storage/node_modules/.bin/tsserver +0 -22
- package/packages/storage/node_modules/.bin/tsup +0 -22
- package/packages/storage/node_modules/.bin/tsup-node +0 -22
- package/packages/storage/node_modules/.bin/vite +0 -22
- package/packages/storage/node_modules/.bin/vitest +0 -22
- package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/packages/storage/package.json +0 -60
- package/packages/storage/src/DownloadBuilder.ts +0 -60
- package/packages/storage/src/StorageService.ts +0 -323
- package/packages/storage/src/index.ts +0 -2
- package/packages/storage/src/path.ts +0 -35
- package/packages/storage/src/transform.ts +0 -15
- package/packages/storage/tests/download.test.ts +0 -64
- package/packages/storage/tests/parity.test.ts +0 -154
- package/packages/storage/tests/path.test.ts +0 -87
- package/packages/storage/tests/transform.test.ts +0 -79
- package/packages/storage/tsconfig.json +0 -5
- package/packages/storage/tsup.config.ts +0 -11
- package/pnpm-workspace.yaml +0 -24
- package/tsconfig.base.json +0 -15
- /package/{packages/aurabase/dist → dist}/index.cjs +0 -0
- /package/{packages/aurabase/dist → dist}/index.js +0 -0
- /package/{packages/aurabase/dist → dist}/ssr.cjs +0 -0
- /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,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
|
-
})
|