@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,188 @@
|
|
|
1
|
+
import { BaseService, AuraError, isTokenExpired, refreshOnce, isAbortError } from '@aurabase/core'
|
|
2
|
+
import type { AuraResponse, CronJob, FunctionDef, Job } from '@aurabase/core'
|
|
3
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
4
|
+
import { FunctionsRelayError, toFunctionsError } from './errors'
|
|
5
|
+
|
|
6
|
+
/** Type de réponse attendu de l'invocation (parité `@supabase/functions-js`). */
|
|
7
|
+
export type FunctionsResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer'
|
|
8
|
+
|
|
9
|
+
export class FunctionsService extends BaseService {
|
|
10
|
+
constructor(client: AuraClientLike) {
|
|
11
|
+
super(client)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private get prefix(): string {
|
|
15
|
+
return `/v1/functions/${this.client.projectId}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// -- Function invocation ----------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Invoque une edge function.
|
|
22
|
+
*
|
|
23
|
+
* `responseType` (parité `@supabase/functions-js`, défaut `'json'`) :
|
|
24
|
+
* - `'json'` : chemin PIPELINE inchangé (refresh proactif centralisé, injection
|
|
25
|
+
* apikey/Authorization, dé-wrapping `{data,meta}`, retryConfig). Non-régression.
|
|
26
|
+
* - `'text' | 'blob' | 'arrayBuffer'` : chemin TRANSPORT BRUT `client.fetchFn`
|
|
27
|
+
* (patron `StorageService`, SANS toucher `core/fetch.ts`) — refresh proactif
|
|
28
|
+
* manuel, pose de `Authorization`/`apikey`, puis `res.text()/blob()/arrayBuffer()`
|
|
29
|
+
* sans dé-wrapping (le corps HTTP est renvoyé tel quel).
|
|
30
|
+
*
|
|
31
|
+
* Erreurs typées (parité) : une réponse HTTP non-2xx → `FunctionsHttpError`, une
|
|
32
|
+
* panne de relais/infra (réseau, 502/503/504) → `FunctionsRelayError` ; les deux
|
|
33
|
+
* sous-classent `AuraError` (rétrocompat `isAuraError`/champ `error`). `timeout`/
|
|
34
|
+
* `signal` continuent de produire le code `timeout`.
|
|
35
|
+
*/
|
|
36
|
+
async invoke<T = unknown>(
|
|
37
|
+
slug: string,
|
|
38
|
+
options?: {
|
|
39
|
+
body?: unknown
|
|
40
|
+
method?: string
|
|
41
|
+
path?: string
|
|
42
|
+
headers?: Record<string, string>
|
|
43
|
+
signal?: AbortSignal
|
|
44
|
+
timeout?: number
|
|
45
|
+
responseType?: FunctionsResponseType
|
|
46
|
+
},
|
|
47
|
+
): Promise<AuraResponse<T>> {
|
|
48
|
+
const controller = new AbortController()
|
|
49
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
50
|
+
const userSignal = options?.signal
|
|
51
|
+
const onAbort = () => controller.abort()
|
|
52
|
+
if (options?.timeout) timeoutId = setTimeout(() => controller.abort(), options.timeout)
|
|
53
|
+
if (userSignal) {
|
|
54
|
+
// Un signal déjà avorté à l'entrée doit couper immédiatement (addEventListener ne
|
|
55
|
+
// rejouerait pas un 'abort' déjà émis).
|
|
56
|
+
if (userSignal.aborted) controller.abort()
|
|
57
|
+
else userSignal.addEventListener('abort', onAbort, { once: true })
|
|
58
|
+
}
|
|
59
|
+
// Nettoyage exécuté en succès ET en erreur — évite de fuir le listener quand l'appelant
|
|
60
|
+
// réutilise un même AbortSignal longue-durée sur plusieurs invoke().
|
|
61
|
+
const cleanup = () => {
|
|
62
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
63
|
+
if (userSignal) userSignal.removeEventListener('abort', onAbort)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const envelope = { method: options?.method, path: options?.path, headers: options?.headers, body: options?.body }
|
|
67
|
+
const responseType = options?.responseType ?? 'json'
|
|
68
|
+
const timeoutError = (): AuraResponse<T> => ({
|
|
69
|
+
data: null,
|
|
70
|
+
error: new AuraError(0, {
|
|
71
|
+
error: { code: 'timeout', message: 'Function invocation aborted or timed out' },
|
|
72
|
+
}),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
if (responseType === 'json') {
|
|
77
|
+
// Passe par le pipeline unifié (fetchWithAuth via BaseService.request) : refresh
|
|
78
|
+
// proactif à marge CENTRALISÉE, injection apikey/Authorization, dé-wrapping
|
|
79
|
+
// `{data,meta}` et respect de `retryConfig`. Un POST n'est jamais rejoué.
|
|
80
|
+
const res = await this.request<T>('POST', `${this.prefix}/${slug}/invoke`, envelope, {
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
})
|
|
83
|
+
if (res.error && controller.signal.aborted) return timeoutError()
|
|
84
|
+
// Taxonomie d'erreurs : mappe une erreur HTTP vers FunctionsHttpError/RelayError.
|
|
85
|
+
if (res.error) return { data: null, error: toFunctionsError(res.error) }
|
|
86
|
+
return res
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Chemin transport BRUT (text/blob/arrayBuffer) ──────────────────────
|
|
90
|
+
const client = this.client
|
|
91
|
+
const store = client.authStore
|
|
92
|
+
if (store.token && store.refreshToken && isTokenExpired(store.token, 30)) {
|
|
93
|
+
await refreshOnce(client)
|
|
94
|
+
}
|
|
95
|
+
const headers: Record<string, string> = {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
// Négocie le mode passthrough côté backend : réponse HTTP BRUTE (statut réel de la
|
|
98
|
+
// fonction + content-type réel + corps brut) au lieu de l'enveloppe `{data,meta}`.
|
|
99
|
+
// Indispensable pour que `res.text()/blob()/arrayBuffer()` reçoive le corps de la
|
|
100
|
+
// fonction (et non l'enveloppe JSON) et que `res.status` porte le statut fonction.
|
|
101
|
+
'X-Aurabase-Invoke-Mode': 'raw',
|
|
102
|
+
}
|
|
103
|
+
if (store.token) headers['Authorization'] = `Bearer ${store.token}`
|
|
104
|
+
if (client.apiKey) headers['apikey'] = client.apiKey
|
|
105
|
+
|
|
106
|
+
const res = await client.fetchFn(this.url(`${this.prefix}/${slug}/invoke`), {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers,
|
|
109
|
+
body: JSON.stringify(envelope),
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
})
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
let parsed: { error?: { code?: string; message?: string }; message?: string } | null = null
|
|
114
|
+
try {
|
|
115
|
+
parsed = (await res.json()) as typeof parsed
|
|
116
|
+
} catch {
|
|
117
|
+
// corps non-JSON → AuraError générique sur le status
|
|
118
|
+
}
|
|
119
|
+
return { data: null, error: toFunctionsError(new AuraError(res.status, parsed)) }
|
|
120
|
+
}
|
|
121
|
+
let data: unknown
|
|
122
|
+
if (responseType === 'text') data = await res.text()
|
|
123
|
+
else if (responseType === 'blob') data = await res.blob()
|
|
124
|
+
else data = await res.arrayBuffer()
|
|
125
|
+
return { data: data as T, error: null }
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Annulation/timeout : conserve le code `timeout` (non-régression).
|
|
128
|
+
if (controller.signal.aborted || isAbortError(err)) return timeoutError()
|
|
129
|
+
// Toute autre exception (panne réseau : fonction injoignable) → relais.
|
|
130
|
+
return {
|
|
131
|
+
data: null,
|
|
132
|
+
error: new FunctionsRelayError(0, {
|
|
133
|
+
error: { code: 'network_error', message: err instanceof Error ? err.message : 'Function relay error' },
|
|
134
|
+
}),
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
cleanup()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// -- Function CRUD ----------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
async list(): Promise<AuraResponse<FunctionDef[]>> {
|
|
144
|
+
return this.request('GET', `${this.prefix}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async get(id: string): Promise<AuraResponse<FunctionDef>> {
|
|
148
|
+
return this.request('GET', `${this.prefix}/${id}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// -- Jobs -------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
async listJobs(options?: { status?: string; limit?: number }): Promise<AuraResponse<Job[]>> {
|
|
154
|
+
const params = new URLSearchParams()
|
|
155
|
+
if (options?.status) params.set('status', options.status)
|
|
156
|
+
if (options?.limit !== undefined) params.set('limit', String(options.limit))
|
|
157
|
+
const qs = params.toString() ? `?${params.toString()}` : ''
|
|
158
|
+
return this.request('GET', `${this.prefix}/jobs${qs}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async getJob(jobId: string): Promise<AuraResponse<Job>> {
|
|
162
|
+
return this.request('GET', `${this.prefix}/jobs/${jobId}`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// -- Cron -------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
async listCronJobs(): Promise<AuraResponse<CronJob[]>> {
|
|
168
|
+
return this.request('GET', `${this.prefix}/cron`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createCronJob(
|
|
172
|
+
name: string,
|
|
173
|
+
schedule: string,
|
|
174
|
+
functionName: string,
|
|
175
|
+
options?: { payload?: unknown },
|
|
176
|
+
): Promise<AuraResponse<CronJob>> {
|
|
177
|
+
return this.request('POST', `${this.prefix}/cron`, {
|
|
178
|
+
name,
|
|
179
|
+
schedule,
|
|
180
|
+
function_name: functionName,
|
|
181
|
+
payload: options?.payload,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async deleteCronJob(name: string): Promise<AuraResponse<null>> {
|
|
186
|
+
return this.request('DELETE', `${this.prefix}/cron/${name}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AuraError } from '@aurabase/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Statuts HTTP considérés comme des pannes de RELAIS/infra (passerelle, cold
|
|
5
|
+
* start, service indisponible) plutôt que des erreurs applicatives de la fonction.
|
|
6
|
+
* `0` = échec réseau (aucune réponse HTTP).
|
|
7
|
+
*/
|
|
8
|
+
const RELAY_STATUSES = new Set([502, 503, 504])
|
|
9
|
+
|
|
10
|
+
export function isRelayStatus(status: number): boolean {
|
|
11
|
+
return status === 0 || RELAY_STATUSES.has(status)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Erreur HTTP APPLICATIVE renvoyée par l'invocation d'une edge function
|
|
16
|
+
* (réponse HTTP non-2xx). Parité `FunctionsHttpError` de `@supabase/functions-js`.
|
|
17
|
+
* Sous-classe `AuraError` → `isAuraError()` reste vrai, `status` porte le code HTTP.
|
|
18
|
+
* `context` conserve le corps déjà lu (JSON/texte) pour inspection.
|
|
19
|
+
*/
|
|
20
|
+
export class FunctionsHttpError extends AuraError {
|
|
21
|
+
readonly context: unknown
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
status: number,
|
|
25
|
+
body?: { error?: { code?: string; message?: string }; message?: string } | null,
|
|
26
|
+
context?: unknown,
|
|
27
|
+
) {
|
|
28
|
+
super(status, body)
|
|
29
|
+
this.name = 'FunctionsHttpError'
|
|
30
|
+
this.context = context
|
|
31
|
+
Object.setPrototypeOf(this, FunctionsHttpError.prototype)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Erreur de RELAIS/infra : fonction injoignable (échec réseau), 502/503/504 de la
|
|
37
|
+
* passerelle, cold start échoué, corps non exploitable. Parité `FunctionsRelayError`
|
|
38
|
+
* de `@supabase/functions-js`. Sous-classe `AuraError`.
|
|
39
|
+
*/
|
|
40
|
+
export class FunctionsRelayError extends AuraError {
|
|
41
|
+
readonly context: unknown
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
status: number,
|
|
45
|
+
body?: { error?: { code?: string; message?: string }; message?: string } | null,
|
|
46
|
+
context?: unknown,
|
|
47
|
+
) {
|
|
48
|
+
super(status, body)
|
|
49
|
+
this.name = 'FunctionsRelayError'
|
|
50
|
+
this.context = context
|
|
51
|
+
Object.setPrototypeOf(this, FunctionsRelayError.prototype)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convertit une `AuraError` générique (produite par le pipeline ou un `!res.ok`)
|
|
57
|
+
* en la sous-classe Functions appropriée, selon le statut HTTP :
|
|
58
|
+
* - relais (0/502/503/504) → `FunctionsRelayError` ;
|
|
59
|
+
* - toute autre erreur HTTP → `FunctionsHttpError`.
|
|
60
|
+
* Préserve le corps original (`details`) → code/message conservés.
|
|
61
|
+
*/
|
|
62
|
+
export function toFunctionsError(err: AuraError, context?: unknown): AuraError {
|
|
63
|
+
const body =
|
|
64
|
+
err.details != null
|
|
65
|
+
? (err.details as { error?: { code?: string; message?: string }; message?: string })
|
|
66
|
+
: { error: { code: err.code, message: err.message } }
|
|
67
|
+
return isRelayStatus(err.status)
|
|
68
|
+
? new FunctionsRelayError(err.status, body, context ?? err.details)
|
|
69
|
+
: new FunctionsHttpError(err.status, body, context ?? err.details)
|
|
70
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FunctionsService } from './FunctionsService'
|
|
2
|
+
export type { FunctionsResponseType } from './FunctionsService'
|
|
3
|
+
export { FunctionsHttpError, FunctionsRelayError, isRelayStatus } from './errors'
|
|
4
|
+
|
|
5
|
+
// Types publics (utilisables en consommant `@aurabase/functions` seul).
|
|
6
|
+
export type { AuraResponse, FunctionDef, Job, CronJob } from '@aurabase/core'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
+
import { FunctionsService } from '../src/FunctionsService'
|
|
3
|
+
import { BaseAuthStore } from '@aurabase/core'
|
|
4
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
5
|
+
|
|
6
|
+
afterEach(() => vi.useRealTimers())
|
|
7
|
+
|
|
8
|
+
function b64url(o: unknown) { return btoa(JSON.stringify(o)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
|
|
9
|
+
function expiredJwt() { return `${b64url({ alg: 'HS256' })}.${b64url({ exp: Math.floor(Date.now() / 1000) - 60 })}.s` }
|
|
10
|
+
function validJwt() { return `${b64url({ alg: 'HS256' })}.${b64url({ exp: Math.floor(Date.now() / 1000) + 3600 })}.s` }
|
|
11
|
+
function fakeUser() { return { id: 'u', email: null, role: 'authenticated', email_verified: true, is_banned: false, is_anonymous: false, mfa_enabled: false, user_metadata: {}, app_metadata: {}, created_at: '2025-01-01T00:00:00Z' } }
|
|
12
|
+
|
|
13
|
+
function make(responder: (url: string, init: RequestInit) => Response | Promise<Response>, store = new BaseAuthStore()) {
|
|
14
|
+
const calls: string[] = []
|
|
15
|
+
const client: AuraClientLike = {
|
|
16
|
+
baseUrl: 'http://x', projectId: 'p', apiKey: 'k', authStore: store,
|
|
17
|
+
fetchFn: (async (url: string, init: RequestInit) => { calls.push(String(url)); return responder(String(url), init) }) as unknown as typeof globalThis.fetch,
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
20
|
+
}
|
|
21
|
+
return { fns: new FunctionsService(client), calls }
|
|
22
|
+
}
|
|
23
|
+
const json = (o: unknown, s = 200) => new Response(JSON.stringify(o), { status: s, headers: { 'Content-Type': 'application/json' } })
|
|
24
|
+
|
|
25
|
+
describe('functions.invoke — dé-wrap enveloppe + timeout', () => {
|
|
26
|
+
it('dé-wrappe l’enveloppe ApiResponse {data, meta}', async () => {
|
|
27
|
+
const { fns } = make(() => json({ data: { status: 200, body: { ok: true } }, meta: null }))
|
|
28
|
+
const { data, error } = await fns.invoke('hello')
|
|
29
|
+
expect(error).toBeNull()
|
|
30
|
+
expect(data).toEqual({ status: 200, body: { ok: true } }) // contenu, pas l'enveloppe
|
|
31
|
+
})
|
|
32
|
+
it('réponse sans enveloppe → renvoyée telle quelle (fallback data ?? parsed)', async () => {
|
|
33
|
+
const { fns } = make(() => json({ ok: true }))
|
|
34
|
+
const { data } = await fns.invoke('hello')
|
|
35
|
+
expect(data).toEqual({ ok: true })
|
|
36
|
+
})
|
|
37
|
+
it('propage meta', async () => {
|
|
38
|
+
const { fns } = make(() => json({ data: { x: 1 }, meta: { total: 5 } }))
|
|
39
|
+
const { meta } = await fns.invoke('hello')
|
|
40
|
+
expect(meta).toEqual({ total: 5 })
|
|
41
|
+
})
|
|
42
|
+
it('signal déjà avorté à l’entrée → coupe la requête (controller interne aborté)', async () => {
|
|
43
|
+
const ac = new AbortController()
|
|
44
|
+
ac.abort()
|
|
45
|
+
// Le responder rejette UNIQUEMENT si le signal reçu est déjà aborté → prouve le pré-abort.
|
|
46
|
+
const { fns } = make((_url, init) =>
|
|
47
|
+
init.signal?.aborted
|
|
48
|
+
? Promise.reject(Object.assign(new Error('aborted'), { name: 'AbortError' }))
|
|
49
|
+
: Promise.resolve(json({ data: { ok: 1 } })),
|
|
50
|
+
)
|
|
51
|
+
const { error } = await fns.invoke('hello', { signal: ac.signal })
|
|
52
|
+
expect(error?.code).toBe('timeout') // aborté, pas résolu
|
|
53
|
+
})
|
|
54
|
+
it('!ok → erreur status', async () => {
|
|
55
|
+
const { fns } = make(() => json({ error: { code: 'boom', message: 'x' } }, 500))
|
|
56
|
+
const { error } = await fns.invoke('hello')
|
|
57
|
+
expect(error?.status).toBe(500)
|
|
58
|
+
})
|
|
59
|
+
it('timeout → error code timeout', async () => {
|
|
60
|
+
vi.useFakeTimers()
|
|
61
|
+
const { fns } = make((_url, init) => new Promise<Response>((_res, rej) => {
|
|
62
|
+
init.signal?.addEventListener('abort', () => rej(Object.assign(new Error('aborted'), { name: 'AbortError' })))
|
|
63
|
+
}))
|
|
64
|
+
const p = fns.invoke('slow', { timeout: 100 })
|
|
65
|
+
await vi.advanceTimersByTimeAsync(100)
|
|
66
|
+
const { error } = await p
|
|
67
|
+
expect(error?.code).toBe('timeout')
|
|
68
|
+
})
|
|
69
|
+
it('passe par le pipeline unifié : injecte apikey/Authorization et ne rejoue PAS un POST (503)', async () => {
|
|
70
|
+
const store = new BaseAuthStore()
|
|
71
|
+
store.save(validJwt(), 'rt', fakeUser())
|
|
72
|
+
let count = 0
|
|
73
|
+
const seen: Record<string, string> = {}
|
|
74
|
+
const { fns } = make((_url, init) => {
|
|
75
|
+
count++
|
|
76
|
+
Object.assign(seen, init.headers as Record<string, string>)
|
|
77
|
+
return json({ error: { code: 'unavailable', message: 'x' } }, 503)
|
|
78
|
+
}, store)
|
|
79
|
+
const { error } = await fns.invoke('hello')
|
|
80
|
+
expect(count).toBe(1) // POST non-idempotent → jamais rejoué même sur 503
|
|
81
|
+
expect(error?.status).toBe(503)
|
|
82
|
+
expect(seen['apikey']).toBe('k')
|
|
83
|
+
expect(seen['Authorization']).toBe(`Bearer ${store.token}`)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('refresh proactif si token expiré', async () => {
|
|
87
|
+
const store = new BaseAuthStore()
|
|
88
|
+
store.save(expiredJwt(), 'rt', fakeUser())
|
|
89
|
+
const { fns, calls } = make((url) => {
|
|
90
|
+
if (url.includes('/v1/auth/')) return json({ access_token: validJwt(), refresh_token: 'rt2', token_type: 'bearer', expires_in: 3600, user: fakeUser() })
|
|
91
|
+
return json({ ok: 1 })
|
|
92
|
+
}, store)
|
|
93
|
+
await fns.invoke('hello')
|
|
94
|
+
expect(calls[0]).toContain('/v1/auth/p/refresh')
|
|
95
|
+
expect(calls[1]).toContain('/hello/invoke')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { FunctionsService } from '../src/FunctionsService'
|
|
3
|
+
import { FunctionsHttpError, FunctionsRelayError } from '../src/errors'
|
|
4
|
+
import { BaseAuthStore, isAuraError } from '@aurabase/core'
|
|
5
|
+
import type { AuraClientLike } from '@aurabase/core'
|
|
6
|
+
|
|
7
|
+
function make(responder: (url: string, init: RequestInit) => Response | Promise<Response>, store = new BaseAuthStore()) {
|
|
8
|
+
const calls: Array<{ url: string; init: RequestInit }> = []
|
|
9
|
+
const client: AuraClientLike = {
|
|
10
|
+
baseUrl: 'http://x', projectId: 'p', apiKey: 'k', authStore: store,
|
|
11
|
+
fetchFn: (async (url: string, init: RequestInit) => { calls.push({ url: String(url), init }); return responder(String(url), init) }) as unknown as typeof globalThis.fetch,
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
auth: { refreshSession: async () => ({ data: null, error: null }) as any },
|
|
14
|
+
}
|
|
15
|
+
return { fns: new FunctionsService(client), calls }
|
|
16
|
+
}
|
|
17
|
+
const json = (o: unknown, s = 200) => new Response(JSON.stringify(o), { status: s, headers: { 'Content-Type': 'application/json' } })
|
|
18
|
+
|
|
19
|
+
describe('functions.invoke — responseType', () => {
|
|
20
|
+
it("défaut = json : dé-wrappe l'enveloppe (non-régression)", async () => {
|
|
21
|
+
const { fns } = make(() => json({ data: { ok: true } }))
|
|
22
|
+
const { data, error } = await fns.invoke('hello')
|
|
23
|
+
expect(error).toBeNull()
|
|
24
|
+
expect(data).toEqual({ ok: true })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("responseType 'text' → data est la chaîne brute (pas de dé-wrapping)", async () => {
|
|
28
|
+
const { fns } = make(() => new Response('bonjour brut', { status: 200, headers: { 'Content-Type': 'text/plain' } }))
|
|
29
|
+
const { data, error } = await fns.invoke<string>('hello', { responseType: 'text' })
|
|
30
|
+
expect(error).toBeNull()
|
|
31
|
+
expect(data).toBe('bonjour brut')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("responseType 'blob' → data est un Blob", async () => {
|
|
35
|
+
const { fns } = make(() => new Response(new Blob([new Uint8Array([1, 2, 3])]), { status: 200 }))
|
|
36
|
+
const { data, error } = await fns.invoke<Blob>('hello', { responseType: 'blob' })
|
|
37
|
+
expect(error).toBeNull()
|
|
38
|
+
expect(data).toBeInstanceOf(Blob)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("responseType 'arrayBuffer' → data est un ArrayBuffer", async () => {
|
|
42
|
+
const { fns } = make(() => new Response(new Uint8Array([1, 2, 3]), { status: 200 }))
|
|
43
|
+
const { data, error } = await fns.invoke<ArrayBuffer>('hello', { responseType: 'arrayBuffer' })
|
|
44
|
+
expect(error).toBeNull()
|
|
45
|
+
expect(data).toBeInstanceOf(ArrayBuffer)
|
|
46
|
+
expect(new Uint8Array(data as ArrayBuffer)).toEqual(new Uint8Array([1, 2, 3]))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("chemin brut : POST vers /invoke avec apikey + Authorization", async () => {
|
|
50
|
+
const store = new BaseAuthStore()
|
|
51
|
+
const jwt = `${btoa(JSON.stringify({ alg: 'HS256' }))}.${btoa(JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 3600 }))}.s`.replace(/=+/g, '')
|
|
52
|
+
store.save(jwt, 'rt', { id: 'u', email: null, role: 'authenticated', email_verified: true, is_banned: false, is_anonymous: false, mfa_enabled: false, user_metadata: {}, app_metadata: {}, created_at: '2025-01-01T00:00:00Z' })
|
|
53
|
+
const { fns, calls } = make(() => new Response('x', { status: 200 }), store)
|
|
54
|
+
await fns.invoke('hello', { responseType: 'text' })
|
|
55
|
+
expect(calls[0]!.url).toBe('http://x/v1/functions/p/hello/invoke')
|
|
56
|
+
expect(calls[0]!.init.method).toBe('POST')
|
|
57
|
+
const h = calls[0]!.init.headers as Record<string, string>
|
|
58
|
+
expect(h['apikey']).toBe('k')
|
|
59
|
+
expect(h['Authorization']).toBe(`Bearer ${store.token}`)
|
|
60
|
+
// Négocie le mode passthrough backend (réponse brute, pas l'enveloppe).
|
|
61
|
+
expect(h['X-Aurabase-Invoke-Mode']).toBe('raw')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("chemin json (défaut) : n'envoie PAS X-Aurabase-Invoke-Mode (enveloppe, non-régression)", async () => {
|
|
65
|
+
const { fns, calls } = make(() => json({ data: { ok: true } }))
|
|
66
|
+
await fns.invoke('hello')
|
|
67
|
+
const h = (calls[0]!.init.headers ?? {}) as Record<string, string>
|
|
68
|
+
expect(h['X-Aurabase-Invoke-Mode']).toBeUndefined()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe('functions.invoke — erreurs typées', () => {
|
|
73
|
+
it('4xx applicatif (json) → FunctionsHttpError, status correct, isAuraError', async () => {
|
|
74
|
+
const { fns } = make(() => json({ error: { code: 'bad', message: 'nope' } }, 400))
|
|
75
|
+
const { data, error } = await fns.invoke('hello')
|
|
76
|
+
expect(data).toBeNull()
|
|
77
|
+
expect(error).toBeInstanceOf(FunctionsHttpError)
|
|
78
|
+
expect(error?.status).toBe(400)
|
|
79
|
+
expect(isAuraError(error)).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('4xx applicatif (chemin brut text) → FunctionsHttpError', async () => {
|
|
83
|
+
const { fns } = make(() => new Response('nope', { status: 422, headers: { 'Content-Type': 'text/plain' } }))
|
|
84
|
+
const { error } = await fns.invoke('hello', { responseType: 'text' })
|
|
85
|
+
expect(error).toBeInstanceOf(FunctionsHttpError)
|
|
86
|
+
expect(error?.status).toBe(422)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('503 relais → FunctionsRelayError', async () => {
|
|
90
|
+
const { fns } = make(() => json({ error: { code: 'unavailable', message: 'x' } }, 503))
|
|
91
|
+
const { error } = await fns.invoke('hello')
|
|
92
|
+
expect(error).toBeInstanceOf(FunctionsRelayError)
|
|
93
|
+
expect(error?.status).toBe(503)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('502 relais (chemin brut) → FunctionsRelayError', async () => {
|
|
97
|
+
const { fns } = make(() => new Response('bad gateway', { status: 502 }))
|
|
98
|
+
const { error } = await fns.invoke('hello', { responseType: 'text' })
|
|
99
|
+
expect(error).toBeInstanceOf(FunctionsRelayError)
|
|
100
|
+
expect(error?.status).toBe(502)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('panne réseau (chemin brut) → FunctionsRelayError', async () => {
|
|
104
|
+
const { fns } = make(() => Promise.reject(new TypeError('fetch failed')))
|
|
105
|
+
const { error } = await fns.invoke('hello', { responseType: 'text' })
|
|
106
|
+
expect(error).toBeInstanceOf(FunctionsRelayError)
|
|
107
|
+
expect(error?.status).toBe(0)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('timeout → code timeout (non-régression, PAS une FunctionsError)', async () => {
|
|
111
|
+
const ac = new AbortController()
|
|
112
|
+
ac.abort()
|
|
113
|
+
const { fns } = make((_url, init) =>
|
|
114
|
+
init.signal?.aborted
|
|
115
|
+
? Promise.reject(Object.assign(new Error('aborted'), { name: 'AbortError' }))
|
|
116
|
+
: Promise.resolve(json({ data: { ok: 1 } })),
|
|
117
|
+
)
|
|
118
|
+
const { error } = await fns.invoke('hello', { signal: ac.signal })
|
|
119
|
+
expect(error?.code).toBe('timeout')
|
|
120
|
+
expect(error).not.toBeInstanceOf(FunctionsHttpError)
|
|
121
|
+
expect(error).not.toBeInstanceOf(FunctionsRelayError)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("timeout sur chemin brut (text) → code timeout", async () => {
|
|
125
|
+
const ac = new AbortController()
|
|
126
|
+
ac.abort()
|
|
127
|
+
const { fns } = make((_url, init) =>
|
|
128
|
+
init.signal?.aborted
|
|
129
|
+
? Promise.reject(Object.assign(new Error('aborted'), { name: 'AbortError' }))
|
|
130
|
+
: Promise.resolve(new Response('x', { status: 200 })),
|
|
131
|
+
)
|
|
132
|
+
const { error } = await fns.invoke('hello', { responseType: 'text', signal: ac.signal })
|
|
133
|
+
expect(error?.code).toBe('timeout')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aurabase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @aurabase/gen
|
|
2
|
+
|
|
3
|
+
Générateur de types TypeScript `Database` pour le SDK Aurabase : introspecte le schéma d'un projet et produit un fichier de types compatible avec l'inférence de `@aurabase/db` (parité `Database` façon Supabase).
|
|
4
|
+
|
|
5
|
+
 
|
|
6
|
+
|
|
7
|
+
> Pre-release — pas encore publié sur npm.
|
|
8
|
+
|
|
9
|
+
Fait partie de l'écosystème du SDK **[aurabase](../aurabase)**, mais publié et installé **séparément** : contrairement aux autres packages `@aurabase/*`, le méta-package `aurabase` ne dépend pas de `@aurabase/gen` — c'est un outil de développement (CLI de génération de types), pas une dépendance runtime de votre application.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -D @aurabase/gen
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> S'installe typiquement en devDependency (ou s'exécute via `npx` sans installation) : c'est un outil exécuté ponctuellement (local ou CI) pour régénérer un fichier `.ts`, pas un module importé par votre application en production.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### CLI `aurabase-gen`
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx aurabase-gen --url http://localhost:8080 --project my-project --key aura_anon_xxx --out database.types.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Options complètes :
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
aurabase-gen --url <baseUrl> --project <id> [--key <apiKey>] [--out database.types.ts] [--schema public] [--type-name Database]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Sans `--out`, la sortie est écrite sur `stdout` plutôt que dans un fichier.
|
|
34
|
+
|
|
35
|
+
### Utilisation programmatique
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { generateDatabaseTypes } from '@aurabase/gen'
|
|
39
|
+
import type { SchemaTable } from '@aurabase/core'
|
|
40
|
+
|
|
41
|
+
const tables: SchemaTable[] = [
|
|
42
|
+
{
|
|
43
|
+
name: 'posts',
|
|
44
|
+
primary_key: 'id',
|
|
45
|
+
columns: [
|
|
46
|
+
{ name: 'id', col_type: 'uuid', nullable: false, unique: true, default_val: 'gen_random_uuid()', references: null },
|
|
47
|
+
{ name: 'title', col_type: 'text', nullable: false, unique: false, default_val: null, references: null },
|
|
48
|
+
],
|
|
49
|
+
indexes: [],
|
|
50
|
+
rls_enabled: false,
|
|
51
|
+
realtime_enabled: false,
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const source = generateDatabaseTypes(tables, { schemaName: 'public', typeName: 'Database' })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Workflow complet (typage bout-en-bout avec `@aurabase/db`)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx aurabase-gen --url http://localhost:8080 --project my-project --key aura_anon_xxx --out database.types.ts
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createClient } from 'aurabase'
|
|
66
|
+
import type { Database } from './database.types' // fichier généré à l'étape précédente
|
|
67
|
+
|
|
68
|
+
const aura = createClient<Database>('http://localhost:8080', 'my-project', 'aura_anon_xxx')
|
|
69
|
+
|
|
70
|
+
// select()/insert()/update() sont désormais inférés depuis les colonnes réelles de `posts`.
|
|
71
|
+
const { data } = await aura.db.from('posts').select('id,title').eq('id', 'post-id').single()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Limitations
|
|
75
|
+
|
|
76
|
+
- `Views`, `Functions`, `Enums` et `CompositeTypes` sont toujours générés vides (`Record<string, never>`) — seules les `Tables` sont introspectées et typées.
|
|
77
|
+
- Un seul schéma à la fois (`--schema`, défaut `public`) : pas de génération multi-schémas en une seule invocation.
|
|
78
|
+
- Dépend de l'endpoint `/schema` exposé par `aura.db.schema()` côté backend — un projet ou une clé sans droit de lecture du schéma ne peut pas générer de types (erreur explicite, pas de repli silencieux).
|
|
79
|
+
- Pas de détection de types enum Postgres dédiés : un `col_type` non reconnu retombe sur `unknown`.
|
|
80
|
+
- Le fichier généré est un instantané : il ne se met pas à jour automatiquement quand le schéma change côté backend (pas de mode watch).
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
Référence complète du SDK (tous les services, types partagés, SSR) : [README racine](../../README.md).
|