@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.
- package/.changeset/config.json +11 -0
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/package.json +90 -0
- package/packages/ai/LICENSE +21 -0
- package/packages/ai/README.md +106 -0
- package/packages/ai/dist/index.cjs +229 -0
- package/packages/ai/dist/index.d.cts +139 -0
- package/packages/ai/dist/index.d.ts +139 -0
- package/packages/ai/dist/index.js +226 -0
- package/packages/ai/node_modules/.bin/esbuild +19 -0
- package/packages/ai/node_modules/.bin/tsc +22 -0
- package/packages/ai/node_modules/.bin/tsserver +22 -0
- package/packages/ai/node_modules/.bin/tsup +22 -0
- package/packages/ai/node_modules/.bin/tsup-node +22 -0
- package/packages/ai/node_modules/.bin/vite +22 -0
- package/packages/ai/node_modules/.bin/vitest +22 -0
- package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/ai/package.json +61 -0
- package/packages/ai/src/AiService.ts +226 -0
- package/packages/ai/src/index.ts +18 -0
- package/packages/ai/src/sse.ts +141 -0
- package/packages/ai/src/types.ts +30 -0
- package/packages/ai/tests/chat-stream.test.ts +151 -0
- package/packages/ai/tests/rag-namespaces.test.ts +54 -0
- package/packages/ai/tests/sse.test.ts +69 -0
- package/packages/ai/tsconfig.json +5 -0
- package/packages/ai/tsup.config.ts +11 -0
- package/packages/aurabase/LICENSE +21 -0
- package/packages/aurabase/README.md +64 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
- package/packages/aurabase/dist/index.cjs +237 -0
- package/packages/aurabase/dist/index.d.cts +46 -0
- package/packages/aurabase/dist/index.d.ts +46 -0
- package/packages/aurabase/dist/index.js +177 -0
- package/packages/aurabase/dist/ssr.cjs +224 -0
- package/packages/aurabase/dist/ssr.d.cts +65 -0
- package/packages/aurabase/dist/ssr.d.ts +65 -0
- package/packages/aurabase/dist/ssr.js +214 -0
- package/packages/aurabase/node_modules/.bin/esbuild +19 -0
- package/packages/aurabase/node_modules/.bin/tsc +22 -0
- package/packages/aurabase/node_modules/.bin/tsserver +22 -0
- package/packages/aurabase/node_modules/.bin/tsup +22 -0
- package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
- package/packages/aurabase/node_modules/.bin/vite +22 -0
- package/packages/aurabase/node_modules/.bin/vitest +22 -0
- package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/aurabase/package.json +80 -0
- package/packages/aurabase/src/AuraClient.ts +237 -0
- package/packages/aurabase/src/index.ts +152 -0
- package/packages/aurabase/src/ssr.ts +156 -0
- package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
- package/packages/aurabase/tests/ai.test.ts +111 -0
- package/packages/aurabase/tests/auth-admin.test.ts +515 -0
- package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
- package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
- package/packages/aurabase/tests/auth.test.ts +947 -0
- package/packages/aurabase/tests/database.test.ts +230 -0
- package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
- package/packages/aurabase/tests/dispose.test.ts +23 -0
- package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
- package/packages/aurabase/tests/functions.test.ts +107 -0
- package/packages/aurabase/tests/integration.test.ts +413 -0
- package/packages/aurabase/tests/live-production-validation.js +241 -0
- package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
- package/packages/aurabase/tests/mocks.ts +139 -0
- package/packages/aurabase/tests/notifications.test.ts +314 -0
- package/packages/aurabase/tests/observability-backup.test.ts +186 -0
- package/packages/aurabase/tests/observability-hook.test.ts +130 -0
- package/packages/aurabase/tests/production-robustness.test.ts +268 -0
- package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
- package/packages/aurabase/tests/query-builder.test.ts +329 -0
- package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
- package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
- package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
- package/packages/aurabase/tests/retry-option.test.ts +17 -0
- package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
- package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
- package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
- package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
- package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
- package/packages/aurabase/tests/ssr.test.ts +91 -0
- package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
- package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
- package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
- package/packages/aurabase/tests/storage.test.ts +55 -0
- package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
- package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
- package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
- package/packages/aurabase/tsconfig.json +5 -0
- package/packages/aurabase/tsconfig.test-d.json +5 -0
- package/packages/aurabase/tsup.config.ts +11 -0
- package/packages/aurabase/vitest.config.ts +16 -0
- package/packages/aurabase/vitest.e2e.config.ts +9 -0
- package/packages/auth/LICENSE +21 -0
- package/packages/auth/README.md +215 -0
- package/packages/auth/dist/index.cjs +1175 -0
- package/packages/auth/dist/index.d.cts +397 -0
- package/packages/auth/dist/index.d.ts +397 -0
- package/packages/auth/dist/index.js +1170 -0
- package/packages/auth/node_modules/.bin/esbuild +19 -0
- package/packages/auth/node_modules/.bin/tsc +22 -0
- package/packages/auth/node_modules/.bin/tsserver +22 -0
- package/packages/auth/node_modules/.bin/tsup +22 -0
- package/packages/auth/node_modules/.bin/tsup-node +22 -0
- package/packages/auth/node_modules/.bin/vite +22 -0
- package/packages/auth/node_modules/.bin/vitest +22 -0
- package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/auth/package.json +61 -0
- package/packages/auth/src/AuthAdminService.ts +149 -0
- package/packages/auth/src/AuthService.ts +1166 -0
- package/packages/auth/src/SessionManager.ts +92 -0
- package/packages/auth/src/index.ts +17 -0
- package/packages/auth/src/jwks.ts +337 -0
- package/packages/auth/src/pkce.ts +27 -0
- package/packages/auth/src/types.ts +89 -0
- package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
- package/packages/auth/tests/detect-session.test.ts +361 -0
- package/packages/auth/tests/getclaims.test.ts +780 -0
- package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
- package/packages/auth/tests/password-recovery.test.ts +124 -0
- package/packages/auth/tests/pkce.test.ts +192 -0
- package/packages/auth/tests/session-manager.test.ts +103 -0
- package/packages/auth/tests/set-session.test.ts +139 -0
- package/packages/auth/tests/verify-otp.test.ts +153 -0
- package/packages/auth/tsconfig.json +5 -0
- package/packages/auth/tsup.config.ts +11 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +132 -0
- package/packages/core/dist/index.cjs +695 -0
- package/packages/core/dist/index.d.cts +1137 -0
- package/packages/core/dist/index.d.ts +1137 -0
- package/packages/core/dist/index.js +673 -0
- package/packages/core/node_modules/.bin/esbuild +22 -0
- package/packages/core/node_modules/.bin/tsc +22 -0
- package/packages/core/node_modules/.bin/tsserver +22 -0
- package/packages/core/node_modules/.bin/tsup +22 -0
- package/packages/core/node_modules/.bin/tsup-node +22 -0
- package/packages/core/node_modules/.bin/vite +22 -0
- package/packages/core/node_modules/.bin/vitest +22 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/core/package.json +57 -0
- package/packages/core/src/AuraError.ts +89 -0
- package/packages/core/src/BaseService.ts +44 -0
- package/packages/core/src/client.ts +34 -0
- package/packages/core/src/cookie-utils.ts +42 -0
- package/packages/core/src/fetch.ts +435 -0
- package/packages/core/src/index.ts +22 -0
- package/packages/core/src/retry.ts +63 -0
- package/packages/core/src/serialize.ts +6 -0
- package/packages/core/src/stores/BaseAuthStore.ts +164 -0
- package/packages/core/src/stores/LocalAuthStore.ts +199 -0
- package/packages/core/src/types.ts +1042 -0
- package/packages/core/tests/auth-store.test.ts +300 -0
- package/packages/core/tests/error-code.test.ts +61 -0
- package/packages/core/tests/error.test.ts +24 -0
- package/packages/core/tests/fetch.test.ts +286 -0
- package/packages/core/tests/mocks.ts +139 -0
- package/packages/core/tests/observability.test.ts +86 -0
- package/packages/core/tests/refresh-hardening.test.ts +164 -0
- package/packages/core/tests/refresh-observability.test.ts +111 -0
- package/packages/core/tests/retry.test.ts +106 -0
- package/packages/core/tests/serialize.test.ts +12 -0
- package/packages/core/tests/timeout.test.ts +79 -0
- package/packages/core/tsconfig.json +5 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/db/LICENSE +21 -0
- package/packages/db/README.md +152 -0
- package/packages/db/VENDORED.md +93 -0
- package/packages/db/dist/index.cjs +544 -0
- package/packages/db/dist/index.d.cts +1111 -0
- package/packages/db/dist/index.d.ts +1111 -0
- package/packages/db/dist/index.js +541 -0
- package/packages/db/node_modules/.bin/esbuild +19 -0
- package/packages/db/node_modules/.bin/tsc +22 -0
- package/packages/db/node_modules/.bin/tsserver +22 -0
- package/packages/db/node_modules/.bin/tsup +22 -0
- package/packages/db/node_modules/.bin/tsup-node +22 -0
- package/packages/db/node_modules/.bin/vite +22 -0
- package/packages/db/node_modules/.bin/vitest +22 -0
- package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/db/package.json +62 -0
- package/packages/db/src/DatabaseService.ts +170 -0
- package/packages/db/src/QueryBuilder.ts +830 -0
- package/packages/db/src/index.ts +2 -0
- package/packages/db/src/pgrst-types/common/common.ts +86 -0
- package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
- package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
- package/packages/db/src/pgrst-types/types.ts +134 -0
- package/packages/db/src/select-query-parser/parser.ts +479 -0
- package/packages/db/src/select-query-parser/result.ts +553 -0
- package/packages/db/src/select-query-parser/types.ts +132 -0
- package/packages/db/src/select-query-parser/utils.ts +707 -0
- package/packages/db/tests/filters.test.ts +81 -0
- package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
- package/packages/db/tests/mutations-filtered.test.ts +188 -0
- package/packages/db/tests/order.test.ts +45 -0
- package/packages/db/tests/rpc.test.ts +41 -0
- package/packages/db/tests/scale/build-large-schema.ts +94 -0
- package/packages/db/tests/scale/scale-compile.test.ts +167 -0
- package/packages/db/tests/schema.test.ts +77 -0
- package/packages/db/tests/single-accept.e2e.test.ts +123 -0
- package/packages/db/tests/single.test.ts +211 -0
- package/packages/db/tests/types/filters.test-d.ts +52 -0
- package/packages/db/tests/types/fixtures.ts +40 -0
- package/packages/db/tests/types/from-views.test-d.ts +18 -0
- package/packages/db/tests/types/mutations.test-d.ts +23 -0
- package/packages/db/tests/types/rpc.test-d.ts +60 -0
- package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
- package/packages/db/tests/types/select-inference.test-d.ts +38 -0
- package/packages/db/tests/vendored/fixtures.ts +83 -0
- package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
- package/packages/db/tsconfig.json +5 -0
- package/packages/db/tsconfig.test-d.json +5 -0
- package/packages/db/tsup.config.ts +11 -0
- package/packages/db/vitest.config.ts +8 -0
- package/packages/functions/LICENSE +21 -0
- package/packages/functions/README.md +96 -0
- package/packages/functions/dist/index.cjs +173 -0
- package/packages/functions/dist/index.d.cts +81 -0
- package/packages/functions/dist/index.d.ts +81 -0
- package/packages/functions/dist/index.js +168 -0
- package/packages/functions/node_modules/.bin/esbuild +19 -0
- package/packages/functions/node_modules/.bin/tsc +22 -0
- package/packages/functions/node_modules/.bin/tsserver +22 -0
- package/packages/functions/node_modules/.bin/tsup +22 -0
- package/packages/functions/node_modules/.bin/tsup-node +22 -0
- package/packages/functions/node_modules/.bin/vite +22 -0
- package/packages/functions/node_modules/.bin/vitest +22 -0
- package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/functions/package.json +60 -0
- package/packages/functions/src/FunctionsService.ts +188 -0
- package/packages/functions/src/errors.ts +70 -0
- package/packages/functions/src/index.ts +6 -0
- package/packages/functions/tests/invoke.test.ts +97 -0
- package/packages/functions/tests/response-type-errors.test.ts +135 -0
- package/packages/functions/tsconfig.json +5 -0
- package/packages/functions/tsup.config.ts +11 -0
- package/packages/gen/LICENSE +21 -0
- package/packages/gen/README.md +84 -0
- package/packages/gen/dist/cli.cjs +334 -0
- package/packages/gen/dist/cli.d.cts +1 -0
- package/packages/gen/dist/cli.d.ts +1 -0
- package/packages/gen/dist/cli.js +332 -0
- package/packages/gen/dist/index.cjs +257 -0
- package/packages/gen/dist/index.d.cts +12 -0
- package/packages/gen/dist/index.d.ts +12 -0
- package/packages/gen/dist/index.js +255 -0
- package/packages/gen/node_modules/.bin/esbuild +19 -0
- package/packages/gen/node_modules/.bin/tsc +22 -0
- package/packages/gen/node_modules/.bin/tsserver +22 -0
- package/packages/gen/node_modules/.bin/tsup +22 -0
- package/packages/gen/node_modules/.bin/tsup-node +22 -0
- package/packages/gen/node_modules/.bin/vite +22 -0
- package/packages/gen/node_modules/.bin/vitest +22 -0
- package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/gen/package.json +66 -0
- package/packages/gen/src/cli.ts +67 -0
- package/packages/gen/src/generate.ts +0 -0
- package/packages/gen/src/index.ts +2 -0
- package/packages/gen/tests/compile.test.ts +213 -0
- package/packages/gen/tests/e2e.test.ts +130 -0
- package/packages/gen/tests/generate.test.ts +396 -0
- package/packages/gen/tsconfig.json +5 -0
- package/packages/gen/tsup.config.ts +11 -0
- package/packages/notifications/LICENSE +21 -0
- package/packages/notifications/README.md +134 -0
- package/packages/notifications/dist/index.cjs +128 -0
- package/packages/notifications/dist/index.d.cts +81 -0
- package/packages/notifications/dist/index.d.ts +81 -0
- package/packages/notifications/dist/index.js +125 -0
- package/packages/notifications/node_modules/.bin/esbuild +19 -0
- package/packages/notifications/node_modules/.bin/tsc +22 -0
- package/packages/notifications/node_modules/.bin/tsserver +22 -0
- package/packages/notifications/node_modules/.bin/tsup +22 -0
- package/packages/notifications/node_modules/.bin/tsup-node +22 -0
- package/packages/notifications/node_modules/.bin/vite +22 -0
- package/packages/notifications/node_modules/.bin/vitest +22 -0
- package/packages/notifications/package.json +60 -0
- package/packages/notifications/src/NotificationsService.ts +230 -0
- package/packages/notifications/src/index.ts +19 -0
- package/packages/notifications/tsconfig.json +5 -0
- package/packages/notifications/tsup.config.ts +11 -0
- package/packages/realtime/LICENSE +21 -0
- package/packages/realtime/README.md +183 -0
- package/packages/realtime/dist/index.cjs +977 -0
- package/packages/realtime/dist/index.d.cts +259 -0
- package/packages/realtime/dist/index.d.ts +259 -0
- package/packages/realtime/dist/index.js +973 -0
- package/packages/realtime/node_modules/.bin/esbuild +19 -0
- package/packages/realtime/node_modules/.bin/tsc +22 -0
- package/packages/realtime/node_modules/.bin/tsserver +22 -0
- package/packages/realtime/node_modules/.bin/tsup +22 -0
- package/packages/realtime/node_modules/.bin/tsup-node +22 -0
- package/packages/realtime/node_modules/.bin/vite +22 -0
- package/packages/realtime/node_modules/.bin/vitest +22 -0
- package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/realtime/package.json +60 -0
- package/packages/realtime/src/AuraChannel.ts +485 -0
- package/packages/realtime/src/HeartbeatManager.ts +73 -0
- package/packages/realtime/src/RealtimeService.ts +505 -0
- package/packages/realtime/src/columnFilter.ts +301 -0
- package/packages/realtime/src/index.ts +4 -0
- package/packages/realtime/tests/ack-routing.test.ts +81 -0
- package/packages/realtime/tests/column-filter.test.ts +144 -0
- package/packages/realtime/tests/dispatch.test.ts +191 -0
- package/packages/realtime/tests/heartbeat.test.ts +67 -0
- package/packages/realtime/tests/http-broadcast.test.ts +68 -0
- package/packages/realtime/tests/realtime-token.test.ts +43 -0
- package/packages/realtime/tests/reconnect-race.test.ts +149 -0
- package/packages/realtime/tests/ref-id.test.ts +34 -0
- package/packages/realtime/tests/set-auth.test.ts +70 -0
- package/packages/realtime/tsconfig.json +5 -0
- package/packages/realtime/tsup.config.ts +11 -0
- package/packages/storage/LICENSE +21 -0
- package/packages/storage/README.md +141 -0
- package/packages/storage/dist/index.cjs +333 -0
- package/packages/storage/dist/index.d.cts +185 -0
- package/packages/storage/dist/index.d.ts +185 -0
- package/packages/storage/dist/index.js +329 -0
- package/packages/storage/node_modules/.bin/esbuild +19 -0
- package/packages/storage/node_modules/.bin/tsc +22 -0
- package/packages/storage/node_modules/.bin/tsserver +22 -0
- package/packages/storage/node_modules/.bin/tsup +22 -0
- package/packages/storage/node_modules/.bin/tsup-node +22 -0
- package/packages/storage/node_modules/.bin/vite +22 -0
- package/packages/storage/node_modules/.bin/vitest +22 -0
- package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/storage/package.json +60 -0
- package/packages/storage/src/DownloadBuilder.ts +60 -0
- package/packages/storage/src/StorageService.ts +323 -0
- package/packages/storage/src/index.ts +2 -0
- package/packages/storage/src/path.ts +35 -0
- package/packages/storage/src/transform.ts +15 -0
- package/packages/storage/tests/download.test.ts +64 -0
- package/packages/storage/tests/parity.test.ts +154 -0
- package/packages/storage/tests/path.test.ts +87 -0
- package/packages/storage/tests/transform.test.ts +79 -0
- package/packages/storage/tsconfig.json +5 -0
- package/packages/storage/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +24 -0
- package/tsconfig.base.json +15 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// E2E réel (stack live) — Lot realtime / correctif `rt-alias-public-rls`.
|
|
3
|
+
// Réf. : docs/superpowers/specs/2026-07-13-rt-alias-public-rls-design.md
|
|
4
|
+
//
|
|
5
|
+
// PROUVE la correction du bug backend : un client abonné via l'ALIAS PUBLIC
|
|
6
|
+
// `db:public.{table}` (le canal auquel s'abonnent réellement Studio et le SDK)
|
|
7
|
+
// reçoit bien les events postgres_changes.
|
|
8
|
+
//
|
|
9
|
+
// AVANT le fix : le CDC ne collectait `allowed_sub_ids` que sur le canal
|
|
10
|
+
// GÉNÉRIQUE (schéma physique `project_<uuid>`) ; un abonné via l'alias public
|
|
11
|
+
// n'y figurait jamais → branche fail-closed déclenchée MÊME SANS policy RLS →
|
|
12
|
+
// 0 INSERT/UPDATE reçu. C'est d'ailleurs pourquoi le test frère
|
|
13
|
+
// `realtime-filter-e2e.test.ts` s'abonne, lui, au canal générique pour
|
|
14
|
+
// contourner ce bug (cf. son commentaire l.142-147).
|
|
15
|
+
//
|
|
16
|
+
// APRÈS le fix (cdc/postgres.rs → get_subscribers_for_channels sur les 3
|
|
17
|
+
// formes) : l'abonné via l'alias public est inclus dans le calcul RLS et reçoit
|
|
18
|
+
// les events autorisés. Ce test s'abonne DONC volontairement à `db:public.*`.
|
|
19
|
+
//
|
|
20
|
+
// Mécanisme d'activation realtime + auth WS : identiques au test frère
|
|
21
|
+
// (PUT /v1/db/{project}/schema/tables/{table}/realtime, JWT via signUp/signIn).
|
|
22
|
+
// Gaté par RUN_E2E + credentials ; skip gracieux sur infra transitoire.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
import { describe, it, expect } from 'vitest'
|
|
26
|
+
import { createClient, createAdminClient } from '../src/index'
|
|
27
|
+
import type { AuraResponse, RealtimeEvent, RealtimeSubscribeStatus } from '../src/index'
|
|
28
|
+
|
|
29
|
+
const URL = process.env.AURA_E2E_URL
|
|
30
|
+
const PROJECT = process.env.AURA_E2E_PROJECT
|
|
31
|
+
const ANON = process.env.AURA_E2E_ANON_KEY
|
|
32
|
+
const SERVICE = process.env.AURA_E2E_SERVICE_KEY
|
|
33
|
+
const RUN = Boolean(process.env.RUN_E2E && URL && PROJECT && ANON && SERVICE)
|
|
34
|
+
|
|
35
|
+
const TRANSIENT_STATUS = new Set([408, 429, 502, 503, 504])
|
|
36
|
+
|
|
37
|
+
async function retryTransient<T>(fn: () => Promise<AuraResponse<T>>): Promise<AuraResponse<T>> {
|
|
38
|
+
let res = await fn()
|
|
39
|
+
for (let i = 0; i < 10 && res.error && TRANSIENT_STATUS.has(res.error.status); i++) {
|
|
40
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
41
|
+
res = await fn()
|
|
42
|
+
}
|
|
43
|
+
return res
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function retryFetchTransient(fn: () => Promise<Response>): Promise<Response> {
|
|
47
|
+
let res = await fn()
|
|
48
|
+
for (let i = 0; i < 10 && TRANSIENT_STATUS.has(res.status); i++) {
|
|
49
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
50
|
+
res = await fn()
|
|
51
|
+
}
|
|
52
|
+
return res
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sleep(ms: number): Promise<void> {
|
|
56
|
+
return new Promise((r) => setTimeout(r, ms))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type CdcRecord = Record<string, unknown>
|
|
60
|
+
interface CdcPayload {
|
|
61
|
+
schema: string
|
|
62
|
+
table: string
|
|
63
|
+
type: 'INSERT' | 'UPDATE' | 'DELETE'
|
|
64
|
+
record: CdcRecord
|
|
65
|
+
old_record: CdcRecord | null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function waitForSubscribeStatus(
|
|
69
|
+
doSubscribe: (cb: (status: RealtimeSubscribeStatus, err?: Error) => void) => void,
|
|
70
|
+
timeoutMs: number,
|
|
71
|
+
): Promise<{ status: RealtimeSubscribeStatus; err?: Error } | 'timeout'> {
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
let done = false
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
if (!done) {
|
|
76
|
+
done = true
|
|
77
|
+
resolve('timeout')
|
|
78
|
+
}
|
|
79
|
+
}, timeoutMs)
|
|
80
|
+
doSubscribe((status, err) => {
|
|
81
|
+
if (done) return
|
|
82
|
+
done = true
|
|
83
|
+
clearTimeout(timer)
|
|
84
|
+
resolve({ status, err })
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe.runIf(RUN)('realtime alias public E2E réel — un abonné à db:public.{table} reçoit les events', () => {
|
|
90
|
+
it(
|
|
91
|
+
"l'alias public db:public.{table} délivre bien l'INSERT (régression du bug allowed_sub_ids)",
|
|
92
|
+
async () => {
|
|
93
|
+
const admin = createAdminClient(URL!, PROJECT!, SERVICE!)
|
|
94
|
+
const anon = createClient(URL!, PROJECT!, ANON!)
|
|
95
|
+
const suffix = process.env.AURA_E2E_SUFFIX ?? Math.random().toString(36).slice(2, 8)
|
|
96
|
+
const table = `rt_pub_${suffix}`
|
|
97
|
+
// On s'abonne à l'ALIAS PUBLIC (et NON au canal générique physique) :
|
|
98
|
+
// c'est précisément le chemin qui donnait 0 event avant le correctif.
|
|
99
|
+
const channelName = `db:public.${table}`
|
|
100
|
+
const email = `rt-alias-public-e2e-${suffix}@aurabase.dev`
|
|
101
|
+
const password = 'EndUserPassword123!'
|
|
102
|
+
|
|
103
|
+
let tableCreated = false
|
|
104
|
+
let userId: string | null = null
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// -- 1. Setup service_role : table jetable + GRANT + reload PostgREST ----
|
|
108
|
+
await admin.db.sql(`DROP TABLE IF EXISTS ${table}`)
|
|
109
|
+
const createRes = await admin.db.sql(
|
|
110
|
+
`CREATE TABLE ${table} (
|
|
111
|
+
id serial PRIMARY KEY,
|
|
112
|
+
body text NOT NULL DEFAULT ''
|
|
113
|
+
)`,
|
|
114
|
+
)
|
|
115
|
+
if (createRes.error) {
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
console.warn(
|
|
118
|
+
`[rt-alias-public-e2e] SKIP (infra: CREATE TABLE a échoué — ${createRes.error.status} ` +
|
|
119
|
+
`${createRes.error.message ?? ''}).`,
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
tableCreated = true
|
|
124
|
+
await admin.db.sql(`GRANT ALL ON ${table} TO PUBLIC`)
|
|
125
|
+
await admin.db.sql(`SELECT pg_notify('pgrst', 'reload schema')`)
|
|
126
|
+
|
|
127
|
+
// -- 2. Activer le realtime CDC pour cette table (service_role) ---------
|
|
128
|
+
// (aucune policy RLS n'est créée : le bug se déclenchait MÊME sans RLS)
|
|
129
|
+
const enableRes = await retryFetchTransient(() =>
|
|
130
|
+
fetch(`${admin.baseUrl}/v1/db/${admin.projectId}/schema/tables/${table}/realtime`, {
|
|
131
|
+
method: 'PUT',
|
|
132
|
+
headers: { apikey: SERVICE!, 'content-type': 'application/json' },
|
|
133
|
+
body: JSON.stringify({ enabled: true }),
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
if (!enableRes.ok) {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.warn(
|
|
139
|
+
`[rt-alias-public-e2e] SKIP (infra: PUT .../schema/tables/${table}/realtime → ` +
|
|
140
|
+
`${enableRes.status}).`,
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// -- 3. Utilisateur jetable (anon key) — le WS exige un vrai JWT --------
|
|
146
|
+
const signUpRes = await retryTransient(() => anon.auth.signUp({ email, password }))
|
|
147
|
+
if (signUpRes.error && TRANSIENT_STATUS.has(signUpRes.error.status)) {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.warn(`[rt-alias-public-e2e] SKIP (infra: signUp — ${signUpRes.error.status}).`)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
expect(signUpRes.error).toBeNull()
|
|
153
|
+
|
|
154
|
+
const loginRes = await retryTransient(() => anon.auth.signInWithPassword({ email, password }))
|
|
155
|
+
if (loginRes.error && TRANSIENT_STATUS.has(loginRes.error.status)) {
|
|
156
|
+
// eslint-disable-next-line no-console
|
|
157
|
+
console.warn(`[rt-alias-public-e2e] SKIP (infra: signIn — ${loginRes.error.status}).`)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
expect(loginRes.error).toBeNull()
|
|
161
|
+
const loginData = loginRes.data as { user: { id: string } } | null
|
|
162
|
+
userId = loginData?.user.id ?? null
|
|
163
|
+
|
|
164
|
+
// -- 4. Laisser le filtre CDC se rafraîchir (add-tables, ~3s) -----------
|
|
165
|
+
await sleep(4000)
|
|
166
|
+
|
|
167
|
+
// -- 5. Connexion WS + abonnement à l'ALIAS PUBLIC ----------------------
|
|
168
|
+
anon.realtime.connect()
|
|
169
|
+
const received: RealtimeEvent<CdcPayload>[] = []
|
|
170
|
+
const channel = anon.channel(channelName)
|
|
171
|
+
channel.on(
|
|
172
|
+
'postgres_changes',
|
|
173
|
+
// `schema` omis (le payload porte le schéma physique) — on isole la
|
|
174
|
+
// livraison via l'alias public, table filtrée par nom.
|
|
175
|
+
{ event: '*', table },
|
|
176
|
+
(payload) => received.push(payload as RealtimeEvent<CdcPayload>),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const result = await waitForSubscribeStatus((cb) => channel.subscribe(cb), 15_000)
|
|
180
|
+
if (result === 'timeout' || result.status !== 'SUBSCRIBED') {
|
|
181
|
+
// eslint-disable-next-line no-console
|
|
182
|
+
console.warn(
|
|
183
|
+
`[rt-alias-public-e2e] SKIP (WS/CDC pas prêt — ` +
|
|
184
|
+
`${result === 'timeout' ? 'timeout SUBSCRIBED' : `statut ${result.status}`}).`,
|
|
185
|
+
)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// -- 6. INSERT une ligne via service_role -------------------------------
|
|
190
|
+
const marker = `hello-${suffix}`
|
|
191
|
+
const ins = await admin.db.sql(`INSERT INTO ${table} (body) VALUES ($1)`, [marker])
|
|
192
|
+
expect(ins.error).toBeNull()
|
|
193
|
+
|
|
194
|
+
// -- 7. Fenêtre de collecte CDC (poll WAL + pub/sub) --------------------
|
|
195
|
+
await sleep(8_000)
|
|
196
|
+
|
|
197
|
+
// -- 8. ASSERTION : l'event a bien été livré via l'alias public ---------
|
|
198
|
+
const gotInsert = received.some(
|
|
199
|
+
(e) => e.payload?.type === 'INSERT' && e.payload?.record?.body === marker,
|
|
200
|
+
)
|
|
201
|
+
expect(
|
|
202
|
+
gotInsert,
|
|
203
|
+
`aucun INSERT reçu via l'alias public ${channelName} — le bug allowed_sub_ids n'est PAS corrigé ` +
|
|
204
|
+
`(events reçus: ${received.length})`,
|
|
205
|
+
).toBe(true)
|
|
206
|
+
} finally {
|
|
207
|
+
try {
|
|
208
|
+
anon.realtime.removeAllChannels()
|
|
209
|
+
} catch {
|
|
210
|
+
/* best-effort */
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
anon.realtime.disconnect()
|
|
214
|
+
} catch {
|
|
215
|
+
/* best-effort */
|
|
216
|
+
}
|
|
217
|
+
if (userId) {
|
|
218
|
+
try {
|
|
219
|
+
await admin.authAdmin.deleteUser(userId)
|
|
220
|
+
} catch {
|
|
221
|
+
/* best-effort */
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (tableCreated) {
|
|
225
|
+
try {
|
|
226
|
+
await admin.db.sql(`DROP TABLE IF EXISTS ${table}`)
|
|
227
|
+
} catch {
|
|
228
|
+
/* best-effort */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
120_000,
|
|
234
|
+
)
|
|
235
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// E2E réel (stack live) — Lot sdk-realtime / correctif `rt-http-broadcast`.
|
|
3
|
+
// Réf. : docs/superpowers/specs/2026-07-13-rt-http-broadcast-design.md
|
|
4
|
+
//
|
|
5
|
+
// PROUVE que `realtime.httpBroadcast(channel, event, payload)` (envoi REST hors
|
|
6
|
+
// WebSocket, POST /v1/realtime/{project}/broadcast) est bien distribué à un
|
|
7
|
+
// abonné connecté en WebSocket sur le même canal broadcast. Valide le contrat
|
|
8
|
+
// backend { channel (préfixé projet), payload:{ event, ... } } de bout en bout.
|
|
9
|
+
//
|
|
10
|
+
// Gaté par RUN_E2E + credentials ; skip gracieux sur infra transitoire.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from 'vitest'
|
|
14
|
+
import { createClient } from '../src/index'
|
|
15
|
+
import type { RealtimeSubscribeStatus } from '../src/index'
|
|
16
|
+
|
|
17
|
+
const URL = process.env.AURA_E2E_URL
|
|
18
|
+
const PROJECT = process.env.AURA_E2E_PROJECT
|
|
19
|
+
const ANON = process.env.AURA_E2E_ANON_KEY
|
|
20
|
+
const SERVICE = process.env.AURA_E2E_SERVICE_KEY
|
|
21
|
+
const RUN = Boolean(process.env.RUN_E2E && URL && PROJECT && ANON && SERVICE)
|
|
22
|
+
|
|
23
|
+
const TRANSIENT_STATUS = new Set([408, 429, 502, 503, 504])
|
|
24
|
+
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
|
|
25
|
+
|
|
26
|
+
function waitForSubscribeStatus(
|
|
27
|
+
doSubscribe: (cb: (status: RealtimeSubscribeStatus, err?: Error) => void) => void,
|
|
28
|
+
timeoutMs: number,
|
|
29
|
+
): Promise<{ status: RealtimeSubscribeStatus } | 'timeout'> {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
let done = false
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
if (!done) {
|
|
34
|
+
done = true
|
|
35
|
+
resolve('timeout')
|
|
36
|
+
}
|
|
37
|
+
}, timeoutMs)
|
|
38
|
+
doSubscribe((status) => {
|
|
39
|
+
if (done) return
|
|
40
|
+
done = true
|
|
41
|
+
clearTimeout(timer)
|
|
42
|
+
resolve({ status })
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe.runIf(RUN)('realtime httpBroadcast E2E réel — REST → WebSocket', () => {
|
|
48
|
+
it(
|
|
49
|
+
'un broadcast émis via httpBroadcast (REST) est livré à un abonné WebSocket du même canal',
|
|
50
|
+
async () => {
|
|
51
|
+
const anon = createClient(URL!, PROJECT!, ANON!)
|
|
52
|
+
const suffix = process.env.AURA_E2E_SUFFIX ?? Math.random().toString(36).slice(2, 8)
|
|
53
|
+
const channelName = `httpbcast-${suffix}`
|
|
54
|
+
const evt = 'ping'
|
|
55
|
+
const marker = `hello-${suffix}`
|
|
56
|
+
const email = `rt-httpbcast-e2e-${suffix}@aurabase.dev`
|
|
57
|
+
const password = 'EndUserPassword123!'
|
|
58
|
+
|
|
59
|
+
let userId: string | null = null
|
|
60
|
+
const received: unknown[] = []
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Le WS exige un vrai JWT → utilisateur jetable via la clé anon.
|
|
64
|
+
const signUp = await anon.auth.signUp({ email, password })
|
|
65
|
+
if (signUp.error && TRANSIENT_STATUS.has(signUp.error.status)) {
|
|
66
|
+
console.warn(`[rt-http-broadcast-e2e] SKIP (signUp ${signUp.error.status}).`)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
const login = await anon.auth.signInWithPassword({ email, password })
|
|
70
|
+
if (login.error && TRANSIENT_STATUS.has(login.error.status)) {
|
|
71
|
+
console.warn(`[rt-http-broadcast-e2e] SKIP (signIn ${login.error.status}).`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
expect(login.error).toBeNull()
|
|
75
|
+
userId = (login.data as { user?: { id: string } } | null)?.user?.id ?? null
|
|
76
|
+
|
|
77
|
+
anon.realtime.connect()
|
|
78
|
+
const channel = anon.channel(channelName)
|
|
79
|
+
channel.on('broadcast', { event: evt }, (payload) => received.push(payload))
|
|
80
|
+
|
|
81
|
+
const res = await waitForSubscribeStatus((cb) => channel.subscribe(cb), 15_000)
|
|
82
|
+
if (res === 'timeout' || res.status !== 'SUBSCRIBED') {
|
|
83
|
+
console.warn(
|
|
84
|
+
`[rt-http-broadcast-e2e] SKIP (WS pas prêt — ${res === 'timeout' ? 'timeout' : res.status}).`,
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Émission REST (hors WS) — le cœur du correctif.
|
|
90
|
+
const bcast = await anon.realtime.httpBroadcast(channelName, evt, { msg: marker })
|
|
91
|
+
if (bcast.error && TRANSIENT_STATUS.has(bcast.error.status)) {
|
|
92
|
+
console.warn(`[rt-http-broadcast-e2e] SKIP (httpBroadcast ${bcast.error.status}).`)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
expect(bcast.error, `httpBroadcast a échoué: ${JSON.stringify(bcast.error)}`).toBeNull()
|
|
96
|
+
|
|
97
|
+
// Fenêtre de livraison WS.
|
|
98
|
+
await sleep(6_000)
|
|
99
|
+
|
|
100
|
+
const got = received.some((r) => JSON.stringify(r).includes(marker))
|
|
101
|
+
expect(
|
|
102
|
+
got,
|
|
103
|
+
`le broadcast REST n'a pas été livré à l'abonné WS (reçus: ${JSON.stringify(received)})`,
|
|
104
|
+
).toBe(true)
|
|
105
|
+
} finally {
|
|
106
|
+
try {
|
|
107
|
+
anon.realtime.removeAllChannels()
|
|
108
|
+
anon.realtime.disconnect()
|
|
109
|
+
} catch {
|
|
110
|
+
/* best-effort */
|
|
111
|
+
}
|
|
112
|
+
if (userId) {
|
|
113
|
+
try {
|
|
114
|
+
const admin = createClient(URL!, PROJECT!, SERVICE!)
|
|
115
|
+
await admin.authAdmin?.deleteUser?.(userId)
|
|
116
|
+
} catch {
|
|
117
|
+
/* best-effort */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
90_000,
|
|
123
|
+
)
|
|
124
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|
2
|
+
import { createClient, createAdminClient, AuraClient } from '../src/index'
|
|
3
|
+
|
|
4
|
+
const BASE = 'http://guard.local'
|
|
5
|
+
const PROJECT = 'proj-guard'
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
delete (globalThis as unknown as { window?: unknown }).window
|
|
9
|
+
delete (globalThis as unknown as { DedicatedWorkerGlobalScope?: unknown }).DedicatedWorkerGlobalScope
|
|
10
|
+
delete (globalThis as unknown as { SharedWorkerGlobalScope?: unknown }).SharedWorkerGlobalScope
|
|
11
|
+
vi.restoreAllMocks()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('Garde service_role — authAdmin getter (I3, durci)', () => {
|
|
15
|
+
it('THROW en contexte navigateur (window défini) au lieu de warn', () => {
|
|
16
|
+
// Client construit hors navigateur (pas de window) : OK.
|
|
17
|
+
const client = createClient(BASE, PROJECT, 'aura_anon_x', {
|
|
18
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
19
|
+
})
|
|
20
|
+
// Simule un navigateur.
|
|
21
|
+
;(globalThis as unknown as { window: unknown }).window = {}
|
|
22
|
+
expect(() => client.authAdmin).toThrow(/navigateur/)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('accessible en contexte serveur (pas de window)', () => {
|
|
26
|
+
const client = createClient(BASE, PROJECT, 'aura_anon_x', {
|
|
27
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
28
|
+
})
|
|
29
|
+
// Ne throw pas hors navigateur.
|
|
30
|
+
expect(() => client.authAdmin).not.toThrow()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('Garde service_role — createClient côté navigateur (SEC-F5, throw par défaut)', () => {
|
|
35
|
+
it('clé aura_sk_ (service_role) en navigateur → THROW par défaut (refuseServiceRoleInBrowser)', () => {
|
|
36
|
+
;(globalThis as unknown as { window: unknown }).window = {}
|
|
37
|
+
|
|
38
|
+
expect(() =>
|
|
39
|
+
createClient(BASE, PROJECT, 'aura_sk_secret', {
|
|
40
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
41
|
+
}),
|
|
42
|
+
).toThrow(/service_role/)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('clé aura_sk_ en navigateur avec refuseServiceRoleInBrowser:false → warn seulement (échappatoire)', () => {
|
|
46
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
47
|
+
;(globalThis as unknown as { window: unknown }).window = {}
|
|
48
|
+
|
|
49
|
+
expect(() =>
|
|
50
|
+
createClient(BASE, PROJECT, 'aura_sk_secret', {
|
|
51
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
52
|
+
refuseServiceRoleInBrowser: false,
|
|
53
|
+
}),
|
|
54
|
+
).not.toThrow()
|
|
55
|
+
|
|
56
|
+
const warned = warn.mock.calls.some((c) => String(c[0]).includes('service_role'))
|
|
57
|
+
expect(warned).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('clé anon (aura_anon_) en navigateur → PAS de throw ni warning service_role (usage légitime)', () => {
|
|
61
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
62
|
+
;(globalThis as unknown as { window: unknown }).window = {}
|
|
63
|
+
|
|
64
|
+
createClient(BASE, PROJECT, 'aura_anon_public', {
|
|
65
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const warnedSrole = warn.mock.calls.some((c) => String(c[0]).includes('service_role'))
|
|
69
|
+
expect(warnedSrole).toBe(false)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('clé aura_sk_ hors navigateur (serveur) → PAS de warning', () => {
|
|
73
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
74
|
+
createClient(BASE, PROJECT, 'aura_sk_secret', {
|
|
75
|
+
auth: { autoRefresh: false, persistSession: false },
|
|
76
|
+
})
|
|
77
|
+
const warnedSrole = warn.mock.calls.some((c) => String(c[0]).includes('service_role'))
|
|
78
|
+
expect(warnedSrole).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('Garde service_role — createAdminClient (E21, étendue aux workers navigateur)', () => {
|
|
83
|
+
it('THROW en contexte navigateur (window)', () => {
|
|
84
|
+
;(globalThis as unknown as { window: unknown }).window = {}
|
|
85
|
+
expect(() => createAdminClient(BASE, PROJECT, 'aura_sk_x')).toThrow(/navigateur/)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('THROW dans un Web Worker navigateur (DedicatedWorkerGlobalScope, sans window)', () => {
|
|
89
|
+
;(globalThis as unknown as { DedicatedWorkerGlobalScope: unknown }).DedicatedWorkerGlobalScope =
|
|
90
|
+
function () {}
|
|
91
|
+
expect(() => createAdminClient(BASE, PROJECT, 'aura_sk_x')).toThrow(/navigateur/)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('OK en contexte serveur (ni window ni worker navigateur)', () => {
|
|
95
|
+
const admin = createAdminClient(BASE, PROJECT, 'aura_sk_x')
|
|
96
|
+
expect(admin).toBeInstanceOf(AuraClient)
|
|
97
|
+
expect(admin.apiKey).toBe('aura_sk_x')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createServerClient } from '../src/ssr'
|
|
3
|
+
import type { User } from '@aurabase/core'
|
|
4
|
+
|
|
5
|
+
function b64url(o: unknown) { return btoa(JSON.stringify(o)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
|
|
6
|
+
function validJwt() { return `${b64url({ alg: 'HS256' })}.${b64url({ sub: 'u1', exp: Math.floor(Date.now() / 1000) + 3600 })}.s` }
|
|
7
|
+
function user(metaSize = 0): User {
|
|
8
|
+
return {
|
|
9
|
+
id: 'u1', email: 't@e.com', role: 'authenticated', email_verified: true,
|
|
10
|
+
is_banned: false, is_anonymous: false, mfa_enabled: false,
|
|
11
|
+
user_metadata: metaSize ? { blob: 'x'.repeat(metaSize) } : {},
|
|
12
|
+
app_metadata: {}, created_at: '2025-01-01T00:00:00Z',
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Faux magasin de cookies : setAll écrit / expire (maxAge 0), getAll relit. */
|
|
17
|
+
function jar(initial: Array<{ name: string; value: string }> = []) {
|
|
18
|
+
const store = new Map(initial.map((c) => [c.name, c.value]))
|
|
19
|
+
return {
|
|
20
|
+
getAll: () => [...store].map(([name, value]) => ({ name, value })),
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
setAll: (cs: Array<{ name: string; value: string; options: any }>) =>
|
|
23
|
+
cs.forEach(({ name, value, options }) => {
|
|
24
|
+
if (options?.maxAge === 0) store.delete(name)
|
|
25
|
+
else store.set(name, value)
|
|
26
|
+
}),
|
|
27
|
+
store,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('createServerClient — cookies SSR', () => {
|
|
32
|
+
it('secure calé sur le protocole : false en http (dev), true en https', () => {
|
|
33
|
+
const httpCalls: Array<{ options: { secure?: boolean } }> = []
|
|
34
|
+
const http = createServerClient('http://localhost:8080', 'p', {
|
|
35
|
+
apiKey: 'k',
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
cookies: { getAll: () => [], setAll: (cs: any) => httpCalls.push(...cs) },
|
|
38
|
+
})
|
|
39
|
+
http.authStore.save(validJwt(), 'rt', user())
|
|
40
|
+
expect(httpCalls[0].options.secure).toBe(false)
|
|
41
|
+
|
|
42
|
+
const httpsCalls: Array<{ options: { secure?: boolean } }> = []
|
|
43
|
+
const https = createServerClient('https://api.example.com', 'p', {
|
|
44
|
+
apiKey: 'k',
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
cookies: { getAll: () => [], setAll: (cs: any) => httpsCalls.push(...cs) },
|
|
47
|
+
})
|
|
48
|
+
https.authStore.save(validJwt(), 'rt', user())
|
|
49
|
+
expect(httpsCalls[0].options.secure).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('session normale → un seul cookie ; round-trip write→read restaure la session', () => {
|
|
53
|
+
const j = jar()
|
|
54
|
+
const tok = validJwt()
|
|
55
|
+
const c1 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
56
|
+
c1.authStore.save(tok, 'rt', user())
|
|
57
|
+
// un seul cookie, pas de chunk
|
|
58
|
+
expect([...j.store.keys()]).toEqual(['aura-p-auth'])
|
|
59
|
+
|
|
60
|
+
const c2 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
61
|
+
expect(c2.authStore.token).toBe(tok)
|
|
62
|
+
expect(c2.authStore.user?.id).toBe('u1')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('session volumineuse → fragmentée en chunks .0/.1… ; round-trip la reconstitue', () => {
|
|
66
|
+
const j = jar()
|
|
67
|
+
const tok = validJwt()
|
|
68
|
+
const c1 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
69
|
+
c1.authStore.save(tok, 'rt', user(5000)) // > MAX_CHUNK_SIZE → chunks
|
|
70
|
+
const names = [...j.store.keys()]
|
|
71
|
+
expect(names).toContain('aura-p-auth.0')
|
|
72
|
+
expect(names).toContain('aura-p-auth.1')
|
|
73
|
+
expect(names).not.toContain('aura-p-auth') // pas de cookie unique quand chunké
|
|
74
|
+
|
|
75
|
+
const c2 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
76
|
+
expect(c2.authStore.token).toBe(tok)
|
|
77
|
+
expect(c2.authStore.user?.user_metadata?.blob).toBe('x'.repeat(5000))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('signout (clear) expire les cookies auth présents', () => {
|
|
81
|
+
const j = jar()
|
|
82
|
+
const c1 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
83
|
+
c1.authStore.save(validJwt(), 'rt', user())
|
|
84
|
+
expect(j.store.size).toBe(1)
|
|
85
|
+
|
|
86
|
+
// Nouveau client qui charge le cookie, puis signout.
|
|
87
|
+
const c2 = createServerClient('https://x', 'p', { apiKey: 'k', cookies: j })
|
|
88
|
+
c2.authStore.clear()
|
|
89
|
+
expect(j.store.size).toBe(0) // cookie expiré/supprimé
|
|
90
|
+
})
|
|
91
|
+
})
|