• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / functions-js / 16913704893

12 Aug 2025 03:39PM UTC coverage: 92.391%. Remained the same
16913704893

push

github

web-flow
chore: add preview release (#104)

32 of 40 branches covered (80.0%)

Branch coverage included in aggregate %.

223 of 236 relevant lines covered (94.49%)

7.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.36
/src/FunctionsClient.ts
1
import { resolveFetch } from './helper'
1✔
2
import {
1✔
3
  Fetch,
1✔
4
  FunctionsFetchError,
1✔
5
  FunctionsHttpError,
1✔
6
  FunctionsRelayError,
1✔
7
  FunctionsResponse,
1✔
8
  FunctionInvokeOptions,
1✔
9
  FunctionRegion,
1✔
10
} from './types'
1✔
11

1✔
12
export class FunctionsClient {
1✔
13
  protected url: string
1✔
14
  protected headers: Record<string, string>
1✔
15
  protected region: FunctionRegion
1✔
16
  protected fetch: Fetch
1✔
17

1✔
18
  constructor(
1✔
19
    url: string,
24✔
20
    {
24✔
21
      headers = {},
24✔
22
      customFetch,
24✔
23
      region = FunctionRegion.Any,
24✔
24
    }: {
24✔
25
      headers?: Record<string, string>
24✔
26
      customFetch?: Fetch
24✔
27
      region?: FunctionRegion
24✔
28
    } = {}
24✔
29
  ) {
24✔
30
    this.url = url
24✔
31
    this.headers = headers
24✔
32
    this.region = region
24✔
33
    this.fetch = resolveFetch(customFetch)
24✔
34
  }
24✔
35

1✔
36
  /**
1✔
37
   * Updates the authorization header
1✔
38
   * @param token - the new jwt token sent in the authorisation header
1✔
39
   */
1✔
40
  setAuth(token: string) {
1✔
41
    this.headers.Authorization = `Bearer ${token}`
10✔
42
  }
10✔
43

1✔
44
  /**
1✔
45
   * Invokes a function
1✔
46
   * @param functionName - The name of the Function to invoke.
1✔
47
   * @param options - Options for invoking the Function.
1✔
48
   */
1✔
49
  async invoke<T = any>(
1✔
50
    functionName: string,
24✔
51
    options: FunctionInvokeOptions = {}
24✔
52
  ): Promise<FunctionsResponse<T>> {
24✔
53
    try {
24✔
54
      const { headers, method, body: functionArgs } = options
24✔
55
      let _headers: Record<string, string> = {}
24✔
56
      let { region } = options
24✔
57
      if (!region) {
24✔
58
        region = this.region
21✔
59
      }
21✔
60
      // Add region as query parameter using URL API
24✔
61
      const url = new URL(`${this.url}/${functionName}`)
24✔
62
      if (region && region !== 'any') {
24✔
63
        _headers['x-region'] = region
3✔
64
        url.searchParams.set('forceFunctionRegion', region)
3✔
65
      }
3✔
66
      let body: any
24✔
67
      if (
24✔
68
        functionArgs &&
24✔
69
        ((headers && !Object.prototype.hasOwnProperty.call(headers, 'Content-Type')) || !headers)
4!
70
      ) {
24✔
71
        if (
4✔
72
          (typeof Blob !== 'undefined' && functionArgs instanceof Blob) ||
4✔
73
          functionArgs instanceof ArrayBuffer
4✔
74
        ) {
4✔
75
          // will work for File as File inherits Blob
2✔
76
          // also works for ArrayBuffer as it is the same underlying structure as a Blob
2✔
77
          _headers['Content-Type'] = 'application/octet-stream'
2✔
78
          body = functionArgs
2✔
79
        } else if (typeof functionArgs === 'string') {
2✔
80
          // plain string
1✔
81
          _headers['Content-Type'] = 'text/plain'
1✔
82
          body = functionArgs
1✔
83
        } else if (typeof FormData !== 'undefined' && functionArgs instanceof FormData) {
1✔
84
          // don't set content-type headers
1✔
85
          // Request will automatically add the right boundary value
1✔
86
          body = functionArgs
1✔
87
        } else {
1!
88
          // default, assume this is JSON
×
89
          _headers['Content-Type'] = 'application/json'
×
90
          body = JSON.stringify(functionArgs)
×
91
        }
×
92
      }
4✔
93

24✔
94
      const response = await this.fetch(url.toString(), {
24✔
95
        method: method || 'POST',
24✔
96
        // headers priority is (high to low):
24✔
97
        // 1. invoke-level headers
24✔
98
        // 2. client-level headers
24✔
99
        // 3. default Content-Type header
24✔
100
        headers: { ..._headers, ...this.headers, ...headers },
24✔
101
        body,
24✔
102
      }).catch((fetchError) => {
24✔
103
        throw new FunctionsFetchError(fetchError)
1✔
104
      })
24✔
105

23✔
106
      const isRelayError = response.headers.get('x-relay-error')
23✔
107
      if (isRelayError && isRelayError === 'true') {
24✔
108
        throw new FunctionsRelayError(response)
3✔
109
      }
3✔
110

20✔
111
      if (!response.ok) {
23!
112
        throw new FunctionsHttpError(response)
×
113
      }
×
114

20✔
115
      let responseType = (response.headers.get('Content-Type') ?? 'text/plain').split(';')[0].trim()
24!
116
      let data: any
24✔
117
      if (responseType === 'application/json') {
24✔
118
        data = await response.json()
12✔
119
      } else if (responseType === 'application/octet-stream') {
23!
120
        data = await response.blob()
×
121
      } else if (responseType === 'text/event-stream') {
8!
122
        data = response
×
123
      } else if (responseType === 'multipart/form-data') {
8!
124
        data = await response.formData()
×
125
      } else {
8✔
126
        // default to text
8✔
127
        data = await response.text()
8✔
128
      }
8✔
129

20✔
130
      return { data, error: null, response }
20✔
131
    } catch (error) {
24✔
132
      return { data: null, error, response: error instanceof FunctionsHttpError || error instanceof FunctionsRelayError ? error.context : undefined }
4✔
133
    }
4✔
134
  }
24✔
135
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc