@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,81 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { QueryBuilder } from '../src/QueryBuilder'
|
|
3
|
+
import { BaseAuthStore } from '@aurabase/core'
|
|
4
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
5
|
+
|
|
6
|
+
function client(): AuraClientLike {
|
|
7
|
+
return {
|
|
8
|
+
baseUrl: 'http://x', projectId: 'p', apiKey: 'k',
|
|
9
|
+
fetchFn: (async () => new Response('[]')) as unknown as typeof globalThis.fetch,
|
|
10
|
+
authStore: new BaseAuthStore(),
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const qb = () => new QueryBuilder(client(), '/base')
|
|
16
|
+
const get = (b: QueryBuilder, key: string) => new URLSearchParams(b.buildQueryString()).get(key)
|
|
17
|
+
|
|
18
|
+
describe('QueryBuilder — filtres PostgREST', () => {
|
|
19
|
+
it('notIn', () => { expect(get(qb().notIn('id', [1, 2]), 'id')).toBe('not.in.(1,2)') })
|
|
20
|
+
it('contains array/objet/string', () => {
|
|
21
|
+
expect(get(qb().contains('tags', ['a', 'b']), 'tags')).toBe('cs.{a,b}')
|
|
22
|
+
expect(get(qb().contains('meta', { a: 1 }), 'meta')).toBe('cs.{"a":1}')
|
|
23
|
+
expect(get(qb().contains('r', '[1,2)'), 'r')).toBe('cs.[1,2)')
|
|
24
|
+
})
|
|
25
|
+
it('containedBy / overlaps', () => {
|
|
26
|
+
expect(get(qb().containedBy('tags', ['a']), 'tags')).toBe('cd.{a}')
|
|
27
|
+
expect(get(qb().overlaps('tags', ['a', 'b']), 'tags')).toBe('ov.{a,b}')
|
|
28
|
+
})
|
|
29
|
+
it('ranges', () => {
|
|
30
|
+
expect(get(qb().rangeGt('r', '[1,2)'), 'r')).toBe('sr.[1,2)')
|
|
31
|
+
expect(get(qb().rangeGte('r', '[1,2)'), 'r')).toBe('nxl.[1,2)')
|
|
32
|
+
expect(get(qb().rangeLt('r', '[1,2)'), 'r')).toBe('sl.[1,2)')
|
|
33
|
+
expect(get(qb().rangeLte('r', '[1,2)'), 'r')).toBe('nxr.[1,2)')
|
|
34
|
+
expect(get(qb().rangeAdjacent('r', '[1,2)'), 'r')).toBe('adj.[1,2)')
|
|
35
|
+
})
|
|
36
|
+
it('textSearch', () => {
|
|
37
|
+
expect(get(qb().textSearch('body', 'cat'), 'body')).toBe('fts.cat')
|
|
38
|
+
expect(get(qb().textSearch('body', 'cat', { type: 'plain' }), 'body')).toBe('plfts.cat')
|
|
39
|
+
expect(get(qb().textSearch('body', 'cat', { type: 'phrase' }), 'body')).toBe('phfts.cat')
|
|
40
|
+
expect(get(qb().textSearch('body', 'cat', { type: 'websearch' }), 'body')).toBe('wfts.cat')
|
|
41
|
+
expect(get(qb().textSearch('body', 'cat', { config: 'english' }), 'body')).toBe('fts(english).cat')
|
|
42
|
+
})
|
|
43
|
+
it('isDistinct / not / filter', () => {
|
|
44
|
+
expect(get(qb().isDistinct('x', 1), 'x')).toBe('isdistinct.1')
|
|
45
|
+
expect(get(qb().not('x', 'eq', 1), 'x')).toBe('not.eq.1')
|
|
46
|
+
expect(get(qb().not('id', 'in', [1, 2]), 'id')).toBe('not.in.(1,2)')
|
|
47
|
+
expect(get(qb().filter('x', 'gte', 5), 'x')).toBe('gte.5')
|
|
48
|
+
})
|
|
49
|
+
it('like/ilike all/any', () => {
|
|
50
|
+
expect(get(qb().likeAllOf('n', ['a%', 'b%']), 'n')).toBe('like(all).{a%,b%}')
|
|
51
|
+
expect(get(qb().likeAnyOf('n', ['a%']), 'n')).toBe('like(any).{a%}')
|
|
52
|
+
expect(get(qb().ilikeAllOf('n', ['a%']), 'n')).toBe('ilike(all).{a%}')
|
|
53
|
+
expect(get(qb().ilikeAnyOf('n', ['a%']), 'n')).toBe('ilike(any).{a%}')
|
|
54
|
+
})
|
|
55
|
+
it('match → multi eq', () => {
|
|
56
|
+
const b = qb().match({ a: 1, b: 'x' })
|
|
57
|
+
const p = new URLSearchParams(b.buildQueryString())
|
|
58
|
+
expect(p.get('a')).toBe('eq.1')
|
|
59
|
+
expect(p.get('b')).toBe('eq.x')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('QueryBuilder — correctifs post-audit', () => {
|
|
64
|
+
it('D5 : valeurs à caractères réservés quotées (in / contains / like-any)', () => {
|
|
65
|
+
expect(get(qb().in('city', ['Paris', 'Aix, FR']), 'city')).toBe('in.(Paris,"Aix, FR")')
|
|
66
|
+
expect(get(qb().notIn('city', ['A(x)']), 'city')).toBe('not.in.("A(x)")')
|
|
67
|
+
expect(get(qb().contains('tags', ['a,b', 'c']), 'tags')).toBe('cs.{"a,b",c}')
|
|
68
|
+
expect(get(qb().likeAnyOf('n', ['a,b']), 'n')).toBe('like(any).{"a,b"}')
|
|
69
|
+
// guillemets internes doublés
|
|
70
|
+
expect(get(qb().in('q', ['a"b']), 'q')).toBe('in.("a""b")')
|
|
71
|
+
})
|
|
72
|
+
it('D6 : is(not.null) → not.is.null (et pas neq.null)', () => {
|
|
73
|
+
expect(get(qb().is('deleted_at', 'not.null'), 'deleted_at')).toBe('not.is.null')
|
|
74
|
+
})
|
|
75
|
+
it('D1 : single()/maybeSingle() ne posent PLUS limit (Accept object+json — corrige la mutation filtrée)', () => {
|
|
76
|
+
// Le `limit=2` cassait `.eq().update().select().single()` (PostgREST rejette `limit`
|
|
77
|
+
// sans `order` sur une mutation). La cardinalité passe désormais par l'en-tête Accept.
|
|
78
|
+
expect(get(qb().single(), 'limit')).toBeNull()
|
|
79
|
+
expect(get(qb().maybeSingle(), 'limit')).toBeNull()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// E2E réel (stack live) — LOT A : mutations filtrées (parité PostgREST).
|
|
3
|
+
//
|
|
4
|
+
// Gaté par RUN_E2E + credentials (AURA_E2E_URL / AURA_E2E_PROJECT / AURA_E2E_KEY,
|
|
5
|
+
// clé service_role requise pour le SQL brut). Skip PROPRE sinon (describe.runIf).
|
|
6
|
+
//
|
|
7
|
+
// Le package `@aurabase/db` ne dépend que de `@aurabase/core` (pas de
|
|
8
|
+
// `createClient` — ce serait circulaire), donc on monte un `DatabaseService`
|
|
9
|
+
// directement au-dessus d'un `AuraClientLike` minimal pointant sur le backend
|
|
10
|
+
// live (auth data-plane via apikey service_role).
|
|
11
|
+
//
|
|
12
|
+
// Flux : crée une table de test (SQL brut NON qualifié — le schéma tenant est
|
|
13
|
+
// `project_<pid>`, pas `public`), GRANT + reload PostgREST, insère 2 lignes,
|
|
14
|
+
// puis vérifie :
|
|
15
|
+
// 1. `.eq(col,v).update({...}).select().single()` → renvoie la ligne modifiée projetée ;
|
|
16
|
+
// 2. `.eq(col,v).delete().select()` → renvoie la ligne supprimée ;
|
|
17
|
+
// 3. `update({...})` sans filtre → `unfiltered_mutation` (ZÉRO appel réseau).
|
|
18
|
+
// Nettoyage (drop table) en `finally`.
|
|
19
|
+
//
|
|
20
|
+
// Le run réel n'est PAS lancé ici (pas de creds) — le contrôleur le fera ; ce
|
|
21
|
+
// fichier doit seulement compiler et skip proprement.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
import { describe, it, expect } from 'vitest'
|
|
25
|
+
import { BaseAuthStore } from '@aurabase/core'
|
|
26
|
+
import type { AuraClientLike, AuraResponse } from '@aurabase/core'
|
|
27
|
+
import { DatabaseService } from '../src/DatabaseService'
|
|
28
|
+
|
|
29
|
+
const URL = process.env.AURA_E2E_URL
|
|
30
|
+
const PROJECT = process.env.AURA_E2E_PROJECT
|
|
31
|
+
const KEY = process.env.AURA_E2E_KEY
|
|
32
|
+
const RUN = Boolean(process.env.RUN_E2E && URL && PROJECT && KEY)
|
|
33
|
+
|
|
34
|
+
/** Monte un `DatabaseService` réel au-dessus d'un `AuraClientLike` minimal. */
|
|
35
|
+
function makeDb(): DatabaseService {
|
|
36
|
+
const client: AuraClientLike = {
|
|
37
|
+
baseUrl: URL!,
|
|
38
|
+
projectId: PROJECT!,
|
|
39
|
+
apiKey: KEY!,
|
|
40
|
+
fetchFn: globalThis.fetch,
|
|
41
|
+
authStore: new BaseAuthStore(),
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
44
|
+
}
|
|
45
|
+
return new DatabaseService(client)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rejoue tant que la réponse est transitoire, borné. Inclut **404** : une table
|
|
50
|
+
* fraîchement créée en SQL brut n'est visible par PostgREST qu'après propagation du
|
|
51
|
+
* `reload schema` (fenêtre pendant laquelle il renvoie 404 « not found »), en plus des
|
|
52
|
+
* 503 (reload/indispo). Aligné sur le smoke db E2E (commit 7dc9f72d).
|
|
53
|
+
*/
|
|
54
|
+
async function retry503<T>(fn: () => Promise<AuraResponse<T>>): Promise<AuraResponse<T>> {
|
|
55
|
+
const transient = (s?: number): boolean => s === 404 || s === 503
|
|
56
|
+
let res = await fn()
|
|
57
|
+
for (let i = 0; i < 25 && transient(res.error?.status); i++) {
|
|
58
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
59
|
+
res = await fn()
|
|
60
|
+
}
|
|
61
|
+
return res
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe.runIf(RUN)('mutations filtrées E2E — .eq().update()/.delete() + garde-fou', () => {
|
|
65
|
+
it(
|
|
66
|
+
'update/delete filtrés renvoient la ligne projetée ; mutation sans filtre refusée',
|
|
67
|
+
async () => {
|
|
68
|
+
const db = makeDb()
|
|
69
|
+
const suffix = process.env.AURA_E2E_SUFFIX ?? 'mut'
|
|
70
|
+
const table = `e2e_mut_${suffix}`
|
|
71
|
+
|
|
72
|
+
// Création via SQL brut (service_role requis). Noms NON qualifiés → schéma tenant.
|
|
73
|
+
await db.sql(`DROP TABLE IF EXISTS ${table}`)
|
|
74
|
+
await db.sql(
|
|
75
|
+
`CREATE TABLE ${table} (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, n int NOT NULL DEFAULT 0)`,
|
|
76
|
+
)
|
|
77
|
+
// GRANTs larges + reload du cache PostgREST (débloque la couche CRUD sur la table jetable).
|
|
78
|
+
await db.sql(`GRANT ALL ON ${table} TO PUBLIC`)
|
|
79
|
+
await db.sql(`SELECT pg_notify('pgrst', 'reload schema')`)
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// -- Seed : 2 lignes --------------------------------------------------
|
|
83
|
+
// TESTS-1.1 : PLUS de graceful-skip silencieux. `retry503` absorbe les 503/404
|
|
84
|
+
// TRANSITOIRES ; un 503 PERSISTANT après retries doit FAIRE ÉCHOUER le test.
|
|
85
|
+
const seeded = await retry503(() =>
|
|
86
|
+
db.from(table).insert([
|
|
87
|
+
{ name: 'alpha', n: 1 },
|
|
88
|
+
{ name: 'beta', n: 2 },
|
|
89
|
+
]),
|
|
90
|
+
)
|
|
91
|
+
expect(seeded.error).toBeNull()
|
|
92
|
+
|
|
93
|
+
// -- 1. UPDATE filtré + select() → ligne modifiée projetée ------------
|
|
94
|
+
// NB : pas de `.single()` sur une mutation — le mécanisme `single()` d'Aurabase
|
|
95
|
+
// ajoute `limit=2`, que PostgREST rejette sur une mutation sans `order`
|
|
96
|
+
// (limite pré-existante du single() limit-based ; postgrest-js utilise
|
|
97
|
+
// l'en-tête Accept object+json). LOT A = la mutation filtrée + projection.
|
|
98
|
+
const updated = await retry503(() =>
|
|
99
|
+
db.from(table).eq('name', 'alpha').update({ n: 99 }).select('name,n'),
|
|
100
|
+
)
|
|
101
|
+
expect(updated.error).toBeNull()
|
|
102
|
+
const updatedRows = updated.data as Array<{ name: string; n: number }> | null
|
|
103
|
+
expect(Array.isArray(updatedRows)).toBe(true)
|
|
104
|
+
expect(updatedRows!).toContainEqual({ name: 'alpha', n: 99 })
|
|
105
|
+
|
|
106
|
+
// -- 2. DELETE filtré + select() → ligne supprimée --------------------
|
|
107
|
+
const deleted = await retry503(() =>
|
|
108
|
+
db.from(table).eq('name', 'beta').delete().select('name,n'),
|
|
109
|
+
)
|
|
110
|
+
expect(deleted.error).toBeNull()
|
|
111
|
+
const deletedRows = deleted.data as Array<{ name: string; n: number }> | null
|
|
112
|
+
expect(Array.isArray(deletedRows)).toBe(true)
|
|
113
|
+
expect(deletedRows!.some((r) => r.name === 'beta')).toBe(true)
|
|
114
|
+
|
|
115
|
+
// -- 3. GARDE-FOU : update sans filtre → refusé sans appel réseau -----
|
|
116
|
+
const wipe = await db.from(table).update({ n: 0 })
|
|
117
|
+
expect(wipe.data).toBeNull()
|
|
118
|
+
expect(wipe.error?.code).toBe('unfiltered_mutation')
|
|
119
|
+
|
|
120
|
+
// La table doit être intacte (alpha toujours là, n=99) malgré le garde-fou.
|
|
121
|
+
const still = await retry503(() =>
|
|
122
|
+
db.from(table).eq('name', 'alpha').select('name,n').single(),
|
|
123
|
+
)
|
|
124
|
+
expect(still.error).toBeNull()
|
|
125
|
+
expect(still.data).toMatchObject({ name: 'alpha', n: 99 })
|
|
126
|
+
} finally {
|
|
127
|
+
await db.sql(`DROP TABLE IF EXISTS ${table}`)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
120_000,
|
|
131
|
+
)
|
|
132
|
+
})
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { QueryBuilder } from '../src/QueryBuilder'
|
|
3
|
+
import { BaseAuthStore } from '@aurabase/core'
|
|
4
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
5
|
+
|
|
6
|
+
interface RecordedCall {
|
|
7
|
+
url: string
|
|
8
|
+
method: string
|
|
9
|
+
headers: Record<string, string>
|
|
10
|
+
body: unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Client factice qui enregistre chaque appel (url/méthode/headers/body) et
|
|
15
|
+
* renvoie une réponse JSON contrôlée. Sert à asserter l'URL exacte (route
|
|
16
|
+
* Table vs id-based), le verbe HTTP et l'en-tête `Prefer` posés par les
|
|
17
|
+
* mutations, SANS appel réseau.
|
|
18
|
+
*/
|
|
19
|
+
function client(rows: unknown = []): { c: AuraClientLike; calls: RecordedCall[] } {
|
|
20
|
+
const calls: RecordedCall[] = []
|
|
21
|
+
const fetchFn = (async (url: string, init: RequestInit) => {
|
|
22
|
+
calls.push({
|
|
23
|
+
url,
|
|
24
|
+
method: (init.method ?? 'GET').toUpperCase(),
|
|
25
|
+
headers: (init.headers ?? {}) as Record<string, string>,
|
|
26
|
+
body: init.body,
|
|
27
|
+
})
|
|
28
|
+
return new Response(JSON.stringify(rows), {
|
|
29
|
+
status: 200,
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
})
|
|
32
|
+
}) as unknown as typeof globalThis.fetch
|
|
33
|
+
const c: AuraClientLike = {
|
|
34
|
+
baseUrl: 'http://x',
|
|
35
|
+
projectId: 'p',
|
|
36
|
+
apiKey: 'k',
|
|
37
|
+
fetchFn,
|
|
38
|
+
authStore: new BaseAuthStore(),
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
41
|
+
}
|
|
42
|
+
return { c, calls }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const qb = (rows?: unknown) => {
|
|
46
|
+
const { c, calls } = client(rows)
|
|
47
|
+
return { b: new QueryBuilder(c, '/base'), calls }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('QueryBuilder — mutations filtrées (parité PostgREST)', () => {
|
|
51
|
+
it('.eq().update() émet PATCH sur la route Table avec filtre (pas /id)', async () => {
|
|
52
|
+
const { b, calls } = qb([{ id: 'x', n: 1 }])
|
|
53
|
+
await b.eq('id', 'x').update({ n: 1 })
|
|
54
|
+
expect(calls).toHaveLength(1)
|
|
55
|
+
expect(calls[0]!.method).toBe('PATCH')
|
|
56
|
+
expect(calls[0]!.url).toBe('http://x/base?id=eq.x')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('.eq().delete() émet DELETE sur la route Table avec filtre', async () => {
|
|
60
|
+
const { b, calls } = qb([])
|
|
61
|
+
await b.eq('id', 'x').delete()
|
|
62
|
+
expect(calls).toHaveLength(1)
|
|
63
|
+
expect(calls[0]!.method).toBe('DELETE')
|
|
64
|
+
expect(calls[0]!.url).toBe('http://x/base?id=eq.x')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('compat : update(id, body) conserve la route id-based /base/{id}', async () => {
|
|
68
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
69
|
+
await b.update('x', { n: 2 })
|
|
70
|
+
expect(calls[0]!.method).toBe('PATCH')
|
|
71
|
+
expect(calls[0]!.url).toBe('http://x/base/x')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('compat : delete(id) conserve la route id-based /base/{id}', async () => {
|
|
75
|
+
const { b, calls } = qb([])
|
|
76
|
+
await b.delete('x')
|
|
77
|
+
expect(calls[0]!.method).toBe('DELETE')
|
|
78
|
+
expect(calls[0]!.url).toBe('http://x/base/x')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('.eq().update().select() ajoute select= + Prefer: return=representation', async () => {
|
|
82
|
+
const { b, calls } = qb([{ id: 'x', n: 1 }])
|
|
83
|
+
await b.eq('id', 'x').update({ n: 1 }).select('id,n')
|
|
84
|
+
const u = new URL(calls[0]!.url)
|
|
85
|
+
expect(u.searchParams.get('id')).toBe('eq.x')
|
|
86
|
+
expect(u.searchParams.get('select')).toBe('id,n')
|
|
87
|
+
expect(calls[0]!.headers['Prefer']).toContain('return=representation')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('.eq().delete().select() pose Prefer: return=representation (indispensable DELETE)', async () => {
|
|
91
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
92
|
+
await b.eq('id', 'x').delete().select('*')
|
|
93
|
+
expect(calls[0]!.headers['Prefer']).toContain('return=representation')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('insert().select() envoie select= + Prefer: return=representation', async () => {
|
|
97
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
98
|
+
await b.insert({ n: 1 }).select('id')
|
|
99
|
+
expect(calls[0]!.method).toBe('POST')
|
|
100
|
+
const u = new URL(calls[0]!.url)
|
|
101
|
+
expect(u.searchParams.get('select')).toBe('id')
|
|
102
|
+
expect(calls[0]!.headers['Prefer']).toContain('return=representation')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('insert() sans select : aucun Prefer, route Table nue', async () => {
|
|
106
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
107
|
+
await b.insert({ n: 1 })
|
|
108
|
+
expect(calls[0]!.url).toBe('http://x/base')
|
|
109
|
+
expect(calls[0]!.headers['Prefer']).toBeUndefined()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('count sur mutation → Prefer: count=<mode>', async () => {
|
|
113
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
114
|
+
await b.eq('id', 'x').update({ n: 1 }).count('exact')
|
|
115
|
+
expect(calls[0]!.headers['Prefer']).toContain('count=exact')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('upsert conserve on_conflict et accepte des filtres/select', async () => {
|
|
119
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
120
|
+
await b.upsert({ id: 'x', n: 1 }, { onConflict: 'id' }).select('id')
|
|
121
|
+
const u = new URL(calls[0]!.url)
|
|
122
|
+
expect(calls[0]!.method).toBe('POST')
|
|
123
|
+
expect(u.searchParams.get('on_conflict')).toBe('id')
|
|
124
|
+
expect(u.searchParams.get('select')).toBe('id')
|
|
125
|
+
expect(calls[0]!.headers['Prefer']).toContain('return=representation')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('GARDE-FOU : update() sans filtre → unfiltered_mutation, ZÉRO appel réseau', async () => {
|
|
129
|
+
const { b, calls } = qb()
|
|
130
|
+
const { data, error } = await b.update({ n: 1 })
|
|
131
|
+
expect(calls).toHaveLength(0)
|
|
132
|
+
expect(data).toBeNull()
|
|
133
|
+
expect(error?.code).toBe('unfiltered_mutation')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('GARDE-FOU : delete() sans filtre → unfiltered_mutation, ZÉRO appel réseau', async () => {
|
|
137
|
+
const { b, calls } = qb()
|
|
138
|
+
const { error } = await b.delete()
|
|
139
|
+
expect(calls).toHaveLength(0)
|
|
140
|
+
expect(error?.code).toBe('unfiltered_mutation')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('GARDE-FOU : un orGroup suffit à autoriser la mutation', async () => {
|
|
144
|
+
const { b, calls } = qb([{ id: 'x' }])
|
|
145
|
+
await b.or([{ column: 'id', op: 'eq', value: 'x' }]).delete()
|
|
146
|
+
expect(calls).toHaveLength(1)
|
|
147
|
+
expect(calls[0]!.method).toBe('DELETE')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// -- DB-F2 : le garde-fou compte les filtres EFFECTIFS (anti-contournement) ---
|
|
151
|
+
|
|
152
|
+
it('GARDE-FOU DB-F2 : .delete().or([]) (OrGroup vide) → refusé, ZÉRO appel réseau', async () => {
|
|
153
|
+
const { b, calls } = qb()
|
|
154
|
+
const { data, error } = await b.or([]).delete()
|
|
155
|
+
expect(calls).toHaveLength(0)
|
|
156
|
+
expect(data).toBeNull()
|
|
157
|
+
expect(error?.code).toBe('unfiltered_mutation')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it("GARDE-FOU DB-F2 : .filter('','','') (colonne vide) → refusé, ZÉRO appel réseau", async () => {
|
|
161
|
+
const { b, calls } = qb()
|
|
162
|
+
const { error } = await b.filter('', '', '').update({ n: 0 })
|
|
163
|
+
expect(calls).toHaveLength(0)
|
|
164
|
+
expect(error?.code).toBe('unfiltered_mutation')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// -- DB-F3 : insert 1 ligne + select renvoie un tableau (parité postgrest-js) --
|
|
168
|
+
|
|
169
|
+
it('DB-F3 : insert(uneLigne).select() → tableau à 1 élément (gateway déballe le 201)', async () => {
|
|
170
|
+
// Le gateway déballe le 201 mono-ligne en objet ; le SDK re-emballe en tableau.
|
|
171
|
+
const { b } = qb({ id: 'x', n: 1 })
|
|
172
|
+
const { data } = await b.insert({ n: 1 }).select('*')
|
|
173
|
+
expect(Array.isArray(data)).toBe(true)
|
|
174
|
+
expect(data).toEqual([{ id: 'x', n: 1 }])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('DB-F3 : insert multi-lignes.select() → tableau inchangé (pas de double emballage)', async () => {
|
|
178
|
+
const { b } = qb([{ id: 'x' }, { id: 'y' }])
|
|
179
|
+
const { data } = await b.insert([{ n: 1 }, { n: 2 }]).select('*')
|
|
180
|
+
expect(data).toEqual([{ id: 'x' }, { id: 'y' }])
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('DB-F3 : upsert(uneLigne).select() → tableau à 1 élément', async () => {
|
|
184
|
+
const { b } = qb({ id: 'x', n: 1 })
|
|
185
|
+
const { data } = await b.upsert({ id: 'x', n: 1 }).select('*')
|
|
186
|
+
expect(data).toEqual([{ id: 'x', n: 1 }])
|
|
187
|
+
})
|
|
188
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { QueryBuilder } from '../src/QueryBuilder'
|
|
3
|
+
import { BaseAuthStore } from '@aurabase/core'
|
|
4
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
5
|
+
|
|
6
|
+
function client(): AuraClientLike {
|
|
7
|
+
return {
|
|
8
|
+
baseUrl: 'http://x', projectId: 'p', apiKey: 'k',
|
|
9
|
+
fetchFn: (async () => new Response('[]')) as unknown as typeof globalThis.fetch,
|
|
10
|
+
authStore: new BaseAuthStore(),
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const qb = () => new QueryBuilder(client(), '/base')
|
|
16
|
+
const get = (b: QueryBuilder, key: string) => new URLSearchParams(b.buildQueryString()).get(key)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Verrouille le contrat de tri contre le backend `aura-db` : le handler CRUD
|
|
20
|
+
* (services/aura-db/src/handlers/crud.rs:79-97) lit `sort_by` + `sort_order`
|
|
21
|
+
* (et `order` en multi-colonnes, prioritaire). Le SDK émet `sort_by`/`sort_order`
|
|
22
|
+
* — ce test garantit qu'on ne dérive pas vers `order=col.dir` par mégarde, ce qui
|
|
23
|
+
* casserait silencieusement le tri single-colonne.
|
|
24
|
+
*/
|
|
25
|
+
describe('QueryBuilder — tri (contrat aura-db sort_by / sort_order)', () => {
|
|
26
|
+
it('order(col) émet sort_by=col + sort_order=asc par défaut', () => {
|
|
27
|
+
const b = qb().order('created_at')
|
|
28
|
+
expect(get(b, 'sort_by')).toBe('created_at')
|
|
29
|
+
expect(get(b, 'sort_order')).toBe('asc')
|
|
30
|
+
// Ne doit PAS émettre la syntaxe PostgREST `order=` (non lue en single-col).
|
|
31
|
+
expect(get(b, 'order')).toBeNull()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('order(col, "desc") émet sort_order=desc', () => {
|
|
35
|
+
const b = qb().order('created_at', 'desc')
|
|
36
|
+
expect(get(b, 'sort_by')).toBe('created_at')
|
|
37
|
+
expect(get(b, 'sort_order')).toBe('desc')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('sans order() : aucun paramètre de tri', () => {
|
|
41
|
+
const b = qb()
|
|
42
|
+
expect(get(b, 'sort_by')).toBeNull()
|
|
43
|
+
expect(get(b, 'sort_order')).toBeNull()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { DatabaseService } from '../src/DatabaseService'
|
|
3
|
+
|
|
4
|
+
function mkClient(capture: (url: string, init: any) => void) {
|
|
5
|
+
return {
|
|
6
|
+
baseUrl: 'http://x', projectId: 'p', apiKey: 'k',
|
|
7
|
+
fetchFn: async (url: string, init: any) => {
|
|
8
|
+
capture(url, init)
|
|
9
|
+
return new Response(JSON.stringify({ data: [{ id: 1 }], meta: {} }), { status: 200, headers: { 'content-type': 'application/json' } })
|
|
10
|
+
},
|
|
11
|
+
authStore: { token: null, refreshToken: null, save() {}, clear() {}, onChange() { return () => {} }, removalEpoch: 0, getItem() { return null }, setItem() {}, removeItem() {}, persistent: false },
|
|
12
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) },
|
|
13
|
+
} as any
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('rpc runtime', () => {
|
|
17
|
+
it('POST /rpc/{fn} avec args en body + filtre en query', async () => {
|
|
18
|
+
let url = '', init: any = null
|
|
19
|
+
const db = new DatabaseService(mkClient((u, i) => { url = u; init = i }))
|
|
20
|
+
await db.rpc('add', { a: 1, b: 2 }).eq('id', 1)
|
|
21
|
+
expect(url).toContain('/v1/db/p/rpc/add')
|
|
22
|
+
expect(url).toContain('id=eq.1')
|
|
23
|
+
expect(init.method).toBe('POST')
|
|
24
|
+
expect(JSON.parse(init.body)).toEqual({ a: 1, b: 2 })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('get:true → GET, pas de body', async () => {
|
|
28
|
+
let init: any = null
|
|
29
|
+
const db = new DatabaseService(mkClient((_u, i) => { init = i }))
|
|
30
|
+
await db.rpc('now', undefined, { get: true })
|
|
31
|
+
expect(init.method).toBe('GET')
|
|
32
|
+
expect(init.body).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('count → query param count=exact', async () => {
|
|
36
|
+
let url = ''
|
|
37
|
+
const db = new DatabaseService(mkClient((u) => { url = u }))
|
|
38
|
+
await db.rpc('list_things', {}, { count: 'exact' })
|
|
39
|
+
expect(url).toContain('count=exact')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// LOT 6 (B5 — TS2589 non testé à l'échelle) : fabrique un schéma `SchemaTable[]`
|
|
2
|
+
// synthétique mais réaliste, destiné à stresser l'inférence type-level du query
|
|
3
|
+
// builder (`@aurabase/db`) au même ordre de grandeur qu'une base de production
|
|
4
|
+
// (≥50 tables, colonnes larges sur certaines tables, FK croisées).
|
|
5
|
+
//
|
|
6
|
+
// Utilisé par `scale-compile.test.ts` : ce fichier ne produit que la matière
|
|
7
|
+
// première (SchemaTable[]) — la génération du `Database` TS passe par le vrai
|
|
8
|
+
// générateur `@aurabase/gen` (`generateDatabaseTypes`), pas une réimplémentation.
|
|
9
|
+
import type { SchemaColumn, SchemaTable } from '@aurabase/core'
|
|
10
|
+
|
|
11
|
+
const COLUMN_TYPE_POOL = ['text', 'int', 'bool', 'jsonb', 'numeric', 'timestamp'] as const
|
|
12
|
+
|
|
13
|
+
function col(
|
|
14
|
+
name: string,
|
|
15
|
+
col_type: string,
|
|
16
|
+
opts: Partial<Omit<SchemaColumn, 'name' | 'col_type'>> = {},
|
|
17
|
+
): SchemaColumn {
|
|
18
|
+
return { name, col_type, nullable: false, unique: false, default_val: null, references: null, ...opts }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fk(name: string, table: string, opts: Partial<Omit<SchemaColumn, 'name' | 'col_type'>> = {}): SchemaColumn {
|
|
22
|
+
return col(name, 'uuid', { references: { table, column: 'id' }, ...opts })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BuildLargeSchemaOptions {
|
|
26
|
+
/** Nombre total de tables (défaut 60, ≥50 exigé par LOT 6). */
|
|
27
|
+
tableCount?: number
|
|
28
|
+
/** Nombre de colonnes "larges" ajoutées sur les tables "wide" (une table sur dix). */
|
|
29
|
+
wideTableColumnCount?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fabrique un schéma synthétique pour la sonde de compile à l'échelle.
|
|
34
|
+
*
|
|
35
|
+
* Structure :
|
|
36
|
+
* - `t0 → t1 → t2 → t3` forment une chaîne de FK NOT NULL propre, dédiée à la
|
|
37
|
+
* sonde d'embed imbriqué sur 4 niveaux (`from('t0').select('*, t1(*, t2(*, t3(name))))')`),
|
|
38
|
+
* dans le sens "many-to-one" (la table courante porte la FK) — c'est le même
|
|
39
|
+
* sens que le cas déjà testé unitairement (`posts.author_id → users.id` dans
|
|
40
|
+
* `tests/types/fixtures.ts`), donc pas de risque d'emprunter un chemin de
|
|
41
|
+
* résolution du parser non couvert par ailleurs.
|
|
42
|
+
* - Une table sur dix (`t0`, `t10`, `t20`, ...) est "wide" : elle porte
|
|
43
|
+
* `wideTableColumnCount` colonnes en plus des 3 colonnes de base, pour stresser
|
|
44
|
+
* le cas d'un `select()` avec une longue liste de colonnes nommées (le chemin
|
|
45
|
+
* qui, empiriquement, fait le plus grossir le nombre d'instanciations — voir
|
|
46
|
+
* `scale-compile.test.ts`).
|
|
47
|
+
* - À partir de `t4`, chaque table porte deux FK "croisées" vers des tables
|
|
48
|
+
* antérieures (`t(i-1)` et `t(i-4)`) pour densifier le graphe de relations
|
|
49
|
+
* comme un schéma métier réel, sans jamais introduire d'ambiguïté de FK
|
|
50
|
+
* (une seule FK vers une même table donnée par table) qui ferait échouer la
|
|
51
|
+
* compilation pour une raison hors-sujet (désambiguïsation de hint).
|
|
52
|
+
*/
|
|
53
|
+
export function buildLargeSchemaTables(options: BuildLargeSchemaOptions = {}): SchemaTable[] {
|
|
54
|
+
const { tableCount = 60, wideTableColumnCount = 20 } = options
|
|
55
|
+
const tables: SchemaTable[] = []
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < tableCount; i++) {
|
|
58
|
+
const name = `t${i}`
|
|
59
|
+
const columns: SchemaColumn[] = [
|
|
60
|
+
col('id', 'uuid', { unique: true, default_val: 'gen_random_uuid()' }),
|
|
61
|
+
col('name', 'text'),
|
|
62
|
+
col('created_at', 'timestamp', { default_val: 'now()' }),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const isWideTable = i % 10 === 0
|
|
66
|
+
const extraCount = isWideTable ? wideTableColumnCount : 3
|
|
67
|
+
for (let w = 0; w < extraCount; w++) {
|
|
68
|
+
const type = COLUMN_TYPE_POOL[w % COLUMN_TYPE_POOL.length]
|
|
69
|
+
columns.push(col(`extra_${w}_${type}`, type, { nullable: true }))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Chaîne dédiée à la sonde d'embed profond (t0 → t1 → t2 → t3), NOT NULL.
|
|
73
|
+
if (i === 0) columns.push(fk('t1_id', 't1', { nullable: false }))
|
|
74
|
+
if (i === 1) columns.push(fk('t2_id', 't2', { nullable: false }))
|
|
75
|
+
if (i === 2) columns.push(fk('t3_id', 't3', { nullable: false }))
|
|
76
|
+
|
|
77
|
+
// FK croisées pour densifier le graphe au-delà de la chaîne dédiée.
|
|
78
|
+
if (i >= 4) {
|
|
79
|
+
columns.push(fk('ref_prev_id', `t${i - 1}`, { nullable: true }))
|
|
80
|
+
columns.push(fk('ref_cross_id', `t${i - 4}`, { nullable: true }))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
tables.push({
|
|
84
|
+
name,
|
|
85
|
+
columns,
|
|
86
|
+
primary_key: 'id',
|
|
87
|
+
indexes: [],
|
|
88
|
+
rls_enabled: true,
|
|
89
|
+
realtime_enabled: false,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return tables
|
|
94
|
+
}
|