wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

View File

@@ -0,0 +1,208 @@
import { resolveFetch } from './helper'
import {
Fetch,
FunctionInvokeOptions,
FunctionRegion,
FunctionsFetchError,
FunctionsHttpError,
FunctionsRelayError,
FunctionsResponse,
} from './types'
/**
* Client for invoking Supabase Edge Functions.
*/
export class FunctionsClient {
protected url: string
protected headers: Record<string, string>
protected region: FunctionRegion
protected fetch: Fetch
/**
* Creates a new Functions client bound to an Edge Functions URL.
*
* @example
* ```ts
* import { FunctionsClient, FunctionRegion } from '@supabase/functions-js'
*
* const functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {
* headers: { apikey: 'public-anon-key' },
* region: FunctionRegion.UsEast1,
* })
* ```
*/
constructor(
url: string,
{
headers = {},
customFetch,
region = FunctionRegion.Any,
}: {
headers?: Record<string, string>
customFetch?: Fetch
region?: FunctionRegion
} = {}
) {
this.url = url
this.headers = headers
this.region = region
this.fetch = resolveFetch(customFetch)
}
/**
* Updates the authorization header
* @param token - the new jwt token sent in the authorisation header
* @example
* ```ts
* functions.setAuth(session.access_token)
* ```
*/
setAuth(token: string) {
this.headers.Authorization = `Bearer ${token}`
}
/**
* Invokes a function
* @param functionName - The name of the Function to invoke.
* @param options - Options for invoking the Function.
* @example
* ```ts
* const { data, error } = await functions.invoke('hello-world', {
* body: { name: 'Ada' },
* })
* ```
*/
async invoke<T = any>(
functionName: string,
options: FunctionInvokeOptions = {}
): Promise<FunctionsResponse<T>> {
let timeoutId: ReturnType<typeof setTimeout> | undefined
let timeoutController: AbortController | undefined
try {
const { headers, method, body: functionArgs, signal, timeout } = options
let _headers: Record<string, string> = {}
let { region } = options
if (!region) {
region = this.region
}
// Add region as query parameter using URL API
const url = new URL(`${this.url}/${functionName}`)
if (region && region !== 'any') {
_headers['x-region'] = region
url.searchParams.set('forceFunctionRegion', region)
}
let body: any
if (
functionArgs &&
((headers && !Object.prototype.hasOwnProperty.call(headers, 'Content-Type')) || !headers)
) {
if (
(typeof Blob !== 'undefined' && functionArgs instanceof Blob) ||
functionArgs instanceof ArrayBuffer
) {
// will work for File as File inherits Blob
// also works for ArrayBuffer as it is the same underlying structure as a Blob
_headers['Content-Type'] = 'application/octet-stream'
body = functionArgs
} else if (typeof functionArgs === 'string') {
// plain string
_headers['Content-Type'] = 'text/plain'
body = functionArgs
} else if (typeof FormData !== 'undefined' && functionArgs instanceof FormData) {
// don't set content-type headers
// Request will automatically add the right boundary value
body = functionArgs
} else {
// default, assume this is JSON
_headers['Content-Type'] = 'application/json'
body = JSON.stringify(functionArgs)
}
} else {
if (
functionArgs &&
typeof functionArgs !== 'string' &&
!(typeof Blob !== 'undefined' && functionArgs instanceof Blob) &&
!(functionArgs instanceof ArrayBuffer) &&
!(typeof FormData !== 'undefined' && functionArgs instanceof FormData)
) {
body = JSON.stringify(functionArgs)
} else {
body = functionArgs
}
}
// Handle timeout by creating an AbortController
let effectiveSignal = signal
if (timeout) {
timeoutController = new AbortController()
timeoutId = setTimeout(() => timeoutController!.abort(), timeout)
// If user provided their own signal, we need to respect both
if (signal) {
effectiveSignal = timeoutController.signal
// If the user's signal is aborted, abort our timeout controller too
signal.addEventListener('abort', () => timeoutController!.abort())
} else {
effectiveSignal = timeoutController.signal
}
}
const response = await this.fetch(url.toString(), {
method: method || 'POST',
// headers priority is (high to low):
// 1. invoke-level headers
// 2. client-level headers
// 3. default Content-Type header
headers: { ..._headers, ...this.headers, ...headers },
body,
signal: effectiveSignal,
}).catch((fetchError) => {
throw new FunctionsFetchError(fetchError)
})
const isRelayError = response.headers.get('x-relay-error')
if (isRelayError && isRelayError === 'true') {
throw new FunctionsRelayError(response)
}
if (!response.ok) {
throw new FunctionsHttpError(response)
}
let responseType = (response.headers.get('Content-Type') ?? 'text/plain').split(';')[0].trim()
let data: any
if (responseType === 'application/json') {
data = await response.json()
} else if (
responseType === 'application/octet-stream' ||
responseType === 'application/pdf'
) {
data = await response.blob()
} else if (responseType === 'text/event-stream') {
data = response
} else if (responseType === 'multipart/form-data') {
data = await response.formData()
} else {
// default to text
data = await response.text()
}
return { data, error: null, response }
} catch (error) {
return {
data: null,
error,
response:
error instanceof FunctionsHttpError || error instanceof FunctionsRelayError
? error.context
: undefined,
}
} finally {
// Clear the timeout if it was set
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}
}

View File

@@ -0,0 +1,208 @@
declare type BeforeunloadReason = 'cpu' | 'memory' | 'wall_clock' | 'early_drop' | 'termination'
declare interface WindowEventMap {
load: Event
unload: Event
beforeunload: CustomEvent<BeforeunloadReason>
drain: Event
}
// TODO(Nyannyacha): These two type defs will be provided later.
// deno-lint-ignore no-explicit-any
type S3FsConfig = any
// deno-lint-ignore no-explicit-any
type TmpFsConfig = any
type OtelPropagators = 'TraceContext' | 'Baggage'
type OtelConsoleConfig = 'Ignore' | 'Capture' | 'Replace'
type OtelConfig = {
tracing_enabled?: boolean
metrics_enabled?: boolean
console?: OtelConsoleConfig
propagators?: OtelPropagators[]
}
interface UserWorkerFetchOptions {
signal?: AbortSignal
}
interface PermissionsOptions {
allow_all?: boolean | null
allow_env?: string[] | null
deny_env?: string[] | null
allow_net?: string[] | null
deny_net?: string[] | null
allow_ffi?: string[] | null
deny_ffi?: string[] | null
allow_read?: string[] | null
deny_read?: string[] | null
allow_run?: string[] | null
deny_run?: string[] | null
allow_sys?: string[] | null
deny_sys?: string[] | null
allow_write?: string[] | null
deny_write?: string[] | null
allow_import?: string[] | null
}
interface UserWorkerCreateContext {
sourceMap?: boolean | null
importMapPath?: string | null
shouldBootstrapMockFnThrowError?: boolean | null
suppressEszipMigrationWarning?: boolean | null
useReadSyncFileAPI?: boolean | null
supervisor?: {
requestAbsentTimeoutMs?: number | null
}
otel?: {
[attribute: string]: string
}
}
interface UserWorkerCreateOptions {
servicePath?: string | null
envVars?: string[][] | [string, string][] | null
noModuleCache?: boolean | null
forceCreate?: boolean | null
allowRemoteModules?: boolean | null
customModuleRoot?: string | null
permissions?: PermissionsOptions | null
maybeEszip?: Uint8Array | null
maybeEntrypoint?: string | null
maybeModuleCode?: string | null
memoryLimitMb?: number | null
lowMemoryMultiplier?: number | null
workerTimeoutMs?: number | null
cpuTimeSoftLimitMs?: number | null
cpuTimeHardLimitMs?: number | null
staticPatterns?: string[] | null
s3FsConfig?: S3FsConfig | null
tmpFsConfig?: TmpFsConfig | null
otelConfig?: OtelConfig | null
context?: UserWorkerCreateContext | null
}
interface HeapStatistics {
totalHeapSize: number
totalHeapSizeExecutable: number
totalPhysicalSize: number
totalAvailableSize: number
totalGlobalHandlesSize: number
usedGlobalHandlesSize: number
usedHeapSize: number
mallocedMemory: number
externalMemory: number
peakMallocedMemory: number
}
interface RuntimeMetrics {
mainWorkerHeapStats: HeapStatistics
eventWorkerHeapStats?: HeapStatistics
}
interface MemInfo {
total: number
free: number
available: number
buffers: number
cached: number
swapTotal: number
swapFree: number
}
declare namespace EdgeRuntime {
export namespace ai {
function tryCleanupUnusedSession(): Promise<number>
}
class UserWorker {
constructor(key: string)
fetch(request: Request, options?: UserWorkerFetchOptions): Promise<Response>
static create(opts: UserWorkerCreateOptions): Promise<UserWorker>
static tryCleanupIdleWorkers(timeoutMs: number): Promise<number>
}
export function scheduleTermination(): void
export function waitUntil<T>(promise: Promise<T>): Promise<T>
export function getRuntimeMetrics(): Promise<RuntimeMetrics>
export function applySupabaseTag(src: Request, dest: Request): void
export function systemMemoryInfo(): MemInfo
export function raiseSegfault(): void
export { UserWorker as userWorkers }
}
declare namespace Supabase {
export namespace ai {
interface ModelOptions {
/**
* Pool embeddings by taking their mean. Applies only for `gte-small` model
*/
mean_pool?: boolean
/**
* Normalize the embeddings result. Applies only for `gte-small` model
*/
normalize?: boolean
/**
* Stream response from model. Applies only for LLMs like `mistral` (default: false)
*/
stream?: boolean
/**
* Automatically abort the request to the model after specified time (in seconds). Applies only for LLMs like `mistral` (default: 60)
*/
timeout?: number
/**
* Mode for the inference API host. (default: 'ollama')
*/
mode?: 'ollama' | 'openaicompatible'
signal?: AbortSignal
}
export class Session {
/**
* Create a new model session using given model
*/
constructor(model: string)
/**
* Execute the given prompt in model session
*/
run(
prompt:
| string
| Omit<import('openai').OpenAI.Chat.ChatCompletionCreateParams, 'model' | 'stream'>,
modelOptions?: ModelOptions
): unknown
}
}
}
declare namespace Deno {
export namespace errors {
class WorkerRequestCancelled extends Error {}
class WorkerAlreadyRetired extends Error {}
/** Thrown when an outbound HTTP request is blocked by the rate limiter. */
class RateLimitError extends Error {
/**
* Number of milliseconds until the rate-limit window resets.
* `null` if the reset time could not be determined.
*/
retryAfterMs: number | null;
constructor(message: string, retryAfterMs?: number);
}
}
}

View File

@@ -0,0 +1,8 @@
import { Fetch } from './types'
export const resolveFetch = (customFetch?: Fetch): Fetch => {
if (customFetch) {
return (...args) => customFetch(...args)
}
return (...args) => fetch(...args)
}

View File

@@ -0,0 +1,10 @@
export { FunctionsClient } from './FunctionsClient'
export {
type FunctionInvokeOptions,
FunctionsError,
FunctionsFetchError,
FunctionsHttpError,
FunctionsRelayError,
FunctionRegion,
type FunctionsResponse,
} from './types'

View File

@@ -0,0 +1,138 @@
export type Fetch = typeof fetch
/**
* Response format
*/
export interface FunctionsResponseSuccess<T> {
data: T
error: null
response?: Response
}
export interface FunctionsResponseFailure {
data: null
error: any
response?: Response
}
export type FunctionsResponse<T> = FunctionsResponseSuccess<T> | FunctionsResponseFailure
/**
* Base error for Supabase Edge Function invocations.
*
* @example
* ```ts
* import { FunctionsError } from '@supabase/functions-js'
*
* throw new FunctionsError('Unexpected error invoking function', 'FunctionsError', {
* requestId: 'abc123',
* })
* ```
*/
export class FunctionsError extends Error {
context: any
constructor(message: string, name = 'FunctionsError', context?: any) {
super(message)
this.name = name
this.context = context
}
}
/**
* Error thrown when the network request to an Edge Function fails.
*
* @example
* ```ts
* import { FunctionsFetchError } from '@supabase/functions-js'
*
* throw new FunctionsFetchError({ requestId: 'abc123' })
* ```
*/
export class FunctionsFetchError extends FunctionsError {
constructor(context: any) {
super('Failed to send a request to the Edge Function', 'FunctionsFetchError', context)
}
}
/**
* Error thrown when the Supabase relay cannot reach the Edge Function.
*
* @example
* ```ts
* import { FunctionsRelayError } from '@supabase/functions-js'
*
* throw new FunctionsRelayError({ region: 'us-east-1' })
* ```
*/
export class FunctionsRelayError extends FunctionsError {
constructor(context: any) {
super('Relay Error invoking the Edge Function', 'FunctionsRelayError', context)
}
}
/**
* Error thrown when the Edge Function returns a non-2xx status code.
*
* @example
* ```ts
* import { FunctionsHttpError } from '@supabase/functions-js'
*
* throw new FunctionsHttpError({ status: 500 })
* ```
*/
export class FunctionsHttpError extends FunctionsError {
constructor(context: any) {
super('Edge Function returned a non-2xx status code', 'FunctionsHttpError', context)
}
}
// Define the enum for the 'region' property
export enum FunctionRegion {
Any = 'any',
ApNortheast1 = 'ap-northeast-1',
ApNortheast2 = 'ap-northeast-2',
ApSouth1 = 'ap-south-1',
ApSoutheast1 = 'ap-southeast-1',
ApSoutheast2 = 'ap-southeast-2',
CaCentral1 = 'ca-central-1',
EuCentral1 = 'eu-central-1',
EuWest1 = 'eu-west-1',
EuWest2 = 'eu-west-2',
EuWest3 = 'eu-west-3',
SaEast1 = 'sa-east-1',
UsEast1 = 'us-east-1',
UsWest1 = 'us-west-1',
UsWest2 = 'us-west-2',
}
export type FunctionInvokeOptions = {
/**
* Object representing the headers to send with the request.
*/
headers?: { [key: string]: string }
/**
* The HTTP verb of the request
*/
method?: 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE'
/**
* The Region to invoke the function in.
*/
region?: FunctionRegion
/**
* The body of the request.
*/
body?:
| File
| Blob
| ArrayBuffer
| FormData
| ReadableStream<Uint8Array>
| Record<string, any>
| string
/**
* The AbortSignal to use for the request.
* */
signal?: AbortSignal
/**
* The timeout for the request in milliseconds.
* If the function takes longer than this, the request will be aborted.
* */
timeout?: number
}

View File

@@ -0,0 +1,7 @@
// Generated automatically during releases by scripts/update-version-files.ts
// This file provides runtime access to the package version for:
// - HTTP request headers (e.g., X-Client-Info header for API requests)
// - Debugging and support (identifying which version is running)
// - Telemetry and logging (version reporting in errors/analytics)
// - Ensuring build artifacts match the published package version
export const version = '2.99.1'