@supabase/postgrest-js 2.118.0 → 2.119.0-canary.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/postgrest-js",
3
- "version": "2.118.0",
3
+ "version": "2.119.0-canary.1",
4
4
  "description": "Isomorphic PostgREST client",
5
5
  "keywords": [
6
6
  "postgrest",
@@ -6,6 +6,7 @@ import PostgrestError from './PostgrestError'
6
6
  import { fetchWithRetry } from './fetchWithRetry'
7
7
  import {
8
8
  PostgrestOpenApiSpec,
9
+ PostgrestQueryBuilderOptions,
9
10
  PostgrestResponseFailure,
10
11
  PostgrestSingleResponse,
11
12
  } from './types/types'
@@ -59,6 +60,51 @@ function toTransportFailure(
59
60
  }
60
61
  }
61
62
 
63
+ /**
64
+ * Bounds every call of `fetch` to `timeout` milliseconds by aborting it through
65
+ * an `AbortController`. A signal already present on the request keeps working:
66
+ * its abort also aborts the timed request. A missing or non-positive timeout
67
+ * returns `fetch` unchanged.
68
+ */
69
+ function withTimeout(fetch: Fetch, timeout?: number): Fetch {
70
+ if (timeout === undefined || timeout <= 0) return fetch
71
+
72
+ return (input, init) => {
73
+ const controller = new AbortController()
74
+ const timeoutId = setTimeout(() => controller.abort(), timeout)
75
+
76
+ // Merge abort signals if one already exists
77
+ const existingSignal = init?.signal
78
+ if (existingSignal) {
79
+ // If the existing signal is already aborted, use it directly
80
+ if (existingSignal.aborted) {
81
+ clearTimeout(timeoutId)
82
+ return fetch(input, init)
83
+ }
84
+
85
+ // Listen to existing signal and abort our controller too
86
+ const abortHandler = () => {
87
+ clearTimeout(timeoutId)
88
+ controller.abort()
89
+ }
90
+ existingSignal.addEventListener('abort', abortHandler, { once: true })
91
+
92
+ return fetch(input, {
93
+ ...init,
94
+ signal: controller.signal,
95
+ }).finally(() => {
96
+ clearTimeout(timeoutId)
97
+ existingSignal.removeEventListener('abort', abortHandler)
98
+ })
99
+ }
100
+
101
+ return fetch(input, {
102
+ ...init,
103
+ signal: controller.signal,
104
+ }).finally(() => clearTimeout(timeoutId))
105
+ }
106
+ }
107
+
62
108
  /**
63
109
  * PostgREST client.
64
110
  *
@@ -96,6 +142,11 @@ export default class PostgrestClient<
96
142
  // Retry configuration - enabled by default
97
143
  retry?: boolean
98
144
 
145
+ protected timeout?: number
146
+ protected wrapFetch?: (fetch: Fetch) => Fetch
147
+ /** The fetch given at construction, before `wrapFetch` and the timeout guard. */
148
+ protected baseFetch?: Fetch
149
+
99
150
  // TODO: Add back shouldThrowOnError once we figure out the typings
100
151
  /**
101
152
  * Creates a PostgREST client.
@@ -111,6 +162,10 @@ export default class PostgrestClient<
111
162
  * When enabled, idempotent requests (GET, HEAD, OPTIONS) that fail with network
112
163
  * errors or HTTP 503/520 responses will be automatically retried up to 3 times
113
164
  * with exponential backoff (1s, 2s, 4s). Defaults to `true`.
165
+ * @param options.wrapFetch - Wraps every fetch this client uses, including a per-request
166
+ * `fetch` passed to `from()`, and is carried over to clients returned by `schema()`.
167
+ * Use it for headers or instrumentation that must reach every request regardless of
168
+ * which fetch performs it.
114
169
  * @example Using supabase-js (recommended)
115
170
  * ```ts
116
171
  * import { createClient } from '@supabase/supabase-js'
@@ -145,6 +200,7 @@ export default class PostgrestClient<
145
200
  timeout,
146
201
  urlLengthLimit = 8000,
147
202
  retry,
203
+ wrapFetch,
148
204
  }: {
149
205
  headers?: HeadersInit
150
206
  schema?: SchemaName
@@ -152,72 +208,52 @@ export default class PostgrestClient<
152
208
  timeout?: number
153
209
  urlLengthLimit?: number
154
210
  retry?: boolean
211
+ wrapFetch?: (fetch: Fetch) => Fetch
155
212
  } = {}
156
213
  ) {
157
214
  this.url = url
158
215
  this.headers = new Headers(headers)
159
216
  this.schemaName = schema
160
217
  this.urlLengthLimit = urlLengthLimit
161
-
162
- const originalFetch = fetch ?? globalThis.fetch
163
-
164
- // Wrap fetch with timeout if specified
165
- if (timeout !== undefined && timeout > 0) {
166
- this.fetch = (input, init) => {
167
- const controller = new AbortController()
168
- const timeoutId = setTimeout(() => controller.abort(), timeout)
169
-
170
- // Merge abort signals if one already exists
171
- const existingSignal = init?.signal
172
- if (existingSignal) {
173
- // If the existing signal is already aborted, use it directly
174
- if (existingSignal.aborted) {
175
- clearTimeout(timeoutId)
176
- return originalFetch(input, init)
177
- }
178
-
179
- // Listen to existing signal and abort our controller too
180
- const abortHandler = () => {
181
- clearTimeout(timeoutId)
182
- controller.abort()
183
- }
184
- existingSignal.addEventListener('abort', abortHandler, { once: true })
185
-
186
- return originalFetch(input, {
187
- ...init,
188
- signal: controller.signal,
189
- }).finally(() => {
190
- clearTimeout(timeoutId)
191
- existingSignal.removeEventListener('abort', abortHandler)
192
- })
193
- }
194
-
195
- return originalFetch(input, {
196
- ...init,
197
- signal: controller.signal,
198
- }).finally(() => clearTimeout(timeoutId))
199
- }
200
- } else {
201
- this.fetch = originalFetch
202
- }
218
+ this.timeout = timeout
219
+ this.wrapFetch = wrapFetch
220
+ this.baseFetch = fetch ?? globalThis.fetch
221
+ this.fetch = this.baseFetch ? this.decorateFetch(this.baseFetch) : undefined
203
222
  this.retry = retry
204
223
  }
224
+
225
+ /**
226
+ * Applies the client's decorations to a fetch implementation: `wrapFetch`
227
+ * first, then the timeout guard, so the timeout bounds the whole wrapped call.
228
+ * The client-level fetch and every per-request `fetch` passed to `from()` go
229
+ * through this.
230
+ */
231
+ protected decorateFetch(fetch: Fetch): Fetch {
232
+ const wrapped = this.wrapFetch ? this.wrapFetch(fetch) : fetch
233
+ return withTimeout(wrapped, this.timeout)
234
+ }
205
235
  /**
206
236
  * Perform a query on a table or a view.
207
237
  *
208
238
  * @param relation - The table or view name to query
239
+ * @param options - Per-request options that override client-level defaults
209
240
  *
210
241
  * @category Database
211
242
  */
212
243
  from<
213
244
  TableName extends string & keyof Schema['Tables'],
214
245
  Table extends Schema['Tables'][TableName],
215
- >(relation: TableName): PostgrestQueryBuilder<ClientOptions, Schema, Table, TableName>
246
+ >(
247
+ relation: TableName,
248
+ options?: PostgrestQueryBuilderOptions
249
+ ): PostgrestQueryBuilder<ClientOptions, Schema, Table, TableName>
216
250
  from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
217
- relation: ViewName
251
+ relation: ViewName,
252
+ options?: PostgrestQueryBuilderOptions
218
253
  ): PostgrestQueryBuilder<ClientOptions, Schema, View, ViewName>
219
254
  from(
220
- relation: (string & keyof Schema['Tables']) | (string & keyof Schema['Views'])
255
+ relation: (string & keyof Schema['Tables']) | (string & keyof Schema['Views']),
256
+ options?: PostgrestQueryBuilderOptions
221
257
  ): PostgrestQueryBuilder<ClientOptions, Schema, any, any> {
222
258
  if (!relation || typeof relation !== 'string' || relation.trim() === '') {
223
259
  throw new Error('Invalid relation name: relation must be a non-empty string.')
@@ -227,9 +263,9 @@ export default class PostgrestClient<
227
263
  return new PostgrestQueryBuilder(url, {
228
264
  headers: new Headers(this.headers),
229
265
  schema: this.schemaName,
230
- fetch: this.fetch,
231
- urlLengthLimit: this.urlLengthLimit,
232
- retry: this.retry,
266
+ fetch: options?.fetch ? this.decorateFetch(options.fetch) : this.fetch,
267
+ urlLengthLimit: options?.urlLengthLimit ?? this.urlLengthLimit,
268
+ retry: options?.retry ?? this.retry,
233
269
  })
234
270
  }
235
271
 
@@ -253,7 +289,9 @@ export default class PostgrestClient<
253
289
  return new PostgrestClient(this.url, {
254
290
  headers: this.headers,
255
291
  schema,
256
- fetch: this.fetch,
292
+ fetch: this.baseFetch,
293
+ timeout: this.timeout,
294
+ wrapFetch: this.wrapFetch,
257
295
  urlLengthLimit: this.urlLengthLimit,
258
296
  retry: this.retry,
259
297
  })
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export type {
28
28
  PostgrestSingleResponse,
29
29
  PostgrestMaybeSingleResponse,
30
30
  PostgrestOpenApiSpec,
31
+ PostgrestQueryBuilderOptions,
31
32
  } from './types/types'
32
33
  export type { ClientServerOptions as PostgrestClientOptions } from './types/common/common'
33
34
  // https://github.com/supabase/postgrest-js/issues/551
@@ -1,7 +1,7 @@
1
1
  import PostgrestError from '../PostgrestError'
2
2
  import { ContainsNull } from '../select-query-parser/types'
3
3
  import { SelectQueryError } from '../select-query-parser/utils'
4
- import { ClientServerOptions } from './common/common'
4
+ import { ClientServerOptions, Fetch } from './common/common'
5
5
 
6
6
  /**
7
7
  * Response format
@@ -62,6 +62,19 @@ export interface PostgrestOpenApiSpec {
62
62
  [key: string]: unknown
63
63
  }
64
64
 
65
+ /**
66
+ * Per-request options for `.from()` queries. These override the
67
+ * corresponding client-level defaults for a single query.
68
+ */
69
+ export type PostgrestQueryBuilderOptions = {
70
+ /** A custom fetch implementation for this request only. The client's `wrapFetch` and `timeout` apply to it as well. */
71
+ fetch?: Fetch
72
+ /** Override the client-level URL length limit for this request. */
73
+ urlLengthLimit?: number
74
+ /** Override the client-level retry setting for this request. */
75
+ retry?: boolean
76
+ }
77
+
65
78
  export type DatabaseWithOptions<Database, Options extends ClientServerOptions> = {
66
79
  db: Database
67
80
  options: Options
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  // - Debugging and support (identifying which version is running)
5
5
  // - Telemetry and logging (version reporting in errors/analytics)
6
6
  // - Ensuring build artifacts match the published package version
7
- export const version = '2.118.0'
7
+ export const version = '2.119.0-canary.1'