@supabase/postgrest-js 2.101.1 → 2.102.0-canary.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/dist/index.cjs CHANGED
@@ -1,5 +1,34 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
+ //#region src/types/common/common.ts
4
+ /**
5
+ * Default number of retry attempts.
6
+ */
7
+ const DEFAULT_MAX_RETRIES = 3;
8
+ /**
9
+ * Default exponential backoff delay function.
10
+ * Delays: 1s, 2s, 4s, 8s, ... (max 30s)
11
+ *
12
+ * @param attemptIndex - Zero-based index of the retry attempt
13
+ * @returns Delay in milliseconds before the next retry
14
+ */
15
+ const getRetryDelay = (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 3e4);
16
+ /**
17
+ * Status codes that are safe to retry.
18
+ * 520 = Cloudflare timeout/connection errors (transient)
19
+ * 503 = PostgREST schema cache not yet loaded (transient, signals retry via Retry-After header)
20
+ */
21
+ const RETRYABLE_STATUS_CODES = [520, 503];
22
+ /**
23
+ * HTTP methods that are safe to retry (idempotent operations).
24
+ */
25
+ const RETRYABLE_METHODS = [
26
+ "GET",
27
+ "HEAD",
28
+ "OPTIONS"
29
+ ];
30
+
31
+ //#endregion
3
32
  //#region src/PostgrestError.ts
4
33
  /**
5
34
  * Error format
@@ -31,6 +60,36 @@ var PostgrestError = class extends Error {
31
60
 
32
61
  //#endregion
33
62
  //#region src/PostgrestBuilder.ts
63
+ /**
64
+ * Sleep for a given number of milliseconds.
65
+ * If an AbortSignal is provided, the sleep resolves early when the signal is aborted.
66
+ */
67
+ function sleep(ms, signal) {
68
+ return new Promise((resolve) => {
69
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
70
+ resolve();
71
+ return;
72
+ }
73
+ const id = setTimeout(() => {
74
+ signal === null || signal === void 0 || signal.removeEventListener("abort", onAbort);
75
+ resolve();
76
+ }, ms);
77
+ function onAbort() {
78
+ clearTimeout(id);
79
+ resolve();
80
+ }
81
+ signal === null || signal === void 0 || signal.addEventListener("abort", onAbort);
82
+ });
83
+ }
84
+ /**
85
+ * Check if a request should be retried based on method and status code.
86
+ */
87
+ function shouldRetry(method, status, attemptCount, retryEnabled) {
88
+ if (!retryEnabled || attemptCount >= DEFAULT_MAX_RETRIES) return false;
89
+ if (!RETRYABLE_METHODS.includes(method)) return false;
90
+ if (!RETRYABLE_STATUS_CODES.includes(status)) return false;
91
+ return true;
92
+ }
34
93
  var PostgrestBuilder = class {
35
94
  /**
36
95
  * Creates a builder configured for a specific PostgREST request.
@@ -58,8 +117,9 @@ var PostgrestBuilder = class {
58
117
  * ```
59
118
  */
60
119
  constructor(builder) {
61
- var _builder$shouldThrowO, _builder$isMaybeSingl, _builder$urlLengthLim;
120
+ var _builder$shouldThrowO, _builder$isMaybeSingl, _builder$urlLengthLim, _builder$retry;
62
121
  this.shouldThrowOnError = false;
122
+ this.retryEnabled = true;
63
123
  this.method = builder.method;
64
124
  this.url = builder.url;
65
125
  this.headers = new Headers(builder.headers);
@@ -69,6 +129,7 @@ var PostgrestBuilder = class {
69
129
  this.signal = builder.signal;
70
130
  this.isMaybeSingle = (_builder$isMaybeSingl = builder.isMaybeSingle) !== null && _builder$isMaybeSingl !== void 0 ? _builder$isMaybeSingl : false;
71
131
  this.urlLengthLimit = (_builder$urlLengthLim = builder.urlLengthLimit) !== null && _builder$urlLengthLim !== void 0 ? _builder$urlLengthLim : 8e3;
132
+ this.retryEnabled = (_builder$retry = builder.retry) !== null && _builder$retry !== void 0 ? _builder$retry : true;
72
133
  if (builder.fetch) this.fetch = builder.fetch;
73
134
  else this.fetch = fetch;
74
135
  }
@@ -94,77 +155,73 @@ var PostgrestBuilder = class {
94
155
  this.headers.set(name, value);
95
156
  return this;
96
157
  }
97
- /** *
158
+ /**
98
159
  * @category Database
160
+ *
161
+ * Configure retry behavior for this request.
162
+ *
163
+ * By default, retries are enabled for idempotent requests (GET, HEAD, OPTIONS)
164
+ * that fail with network errors or specific HTTP status codes (503, 520).
165
+ * Retries use exponential backoff (1s, 2s, 4s) with a maximum of 3 attempts.
166
+ *
167
+ * @param enabled - Whether to enable retries for this request
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * // Disable retries for a specific query
172
+ * const { data, error } = await supabase
173
+ * .from('users')
174
+ * .select()
175
+ * .retry(false)
176
+ * ```
99
177
  */
178
+ retry(enabled) {
179
+ this.retryEnabled = enabled;
180
+ return this;
181
+ }
100
182
  then(onfulfilled, onrejected) {
101
183
  var _this = this;
102
184
  if (this.schema === void 0) {} else if (["GET", "HEAD"].includes(this.method)) this.headers.set("Accept-Profile", this.schema);
103
185
  else this.headers.set("Content-Profile", this.schema);
104
186
  if (this.method !== "GET" && this.method !== "HEAD") this.headers.set("Content-Type", "application/json");
105
187
  const _fetch = this.fetch;
106
- let res = _fetch(this.url.toString(), {
107
- method: this.method,
108
- headers: this.headers,
109
- body: JSON.stringify(this.body),
110
- signal: this.signal
111
- }).then(async (res$1) => {
112
- let error = null;
113
- let data = null;
114
- let count = null;
115
- let status = res$1.status;
116
- let statusText = res$1.statusText;
117
- if (res$1.ok) {
118
- var _this$headers$get2, _res$headers$get;
119
- if (_this.method !== "HEAD") {
120
- var _this$headers$get;
121
- const body = await res$1.text();
122
- if (body === "") {} else if (_this.headers.get("Accept") === "text/csv") data = body;
123
- else if (_this.headers.get("Accept") && ((_this$headers$get = _this.headers.get("Accept")) === null || _this$headers$get === void 0 ? void 0 : _this$headers$get.includes("application/vnd.pgrst.plan+text"))) data = body;
124
- else data = JSON.parse(body);
125
- }
126
- const countHeader = (_this$headers$get2 = _this.headers.get("Prefer")) === null || _this$headers$get2 === void 0 ? void 0 : _this$headers$get2.match(/count=(exact|planned|estimated)/);
127
- const contentRange = (_res$headers$get = res$1.headers.get("content-range")) === null || _res$headers$get === void 0 ? void 0 : _res$headers$get.split("/");
128
- if (countHeader && contentRange && contentRange.length > 1) count = parseInt(contentRange[1]);
129
- if (_this.isMaybeSingle && Array.isArray(data)) if (data.length > 1) {
130
- error = {
131
- code: "PGRST116",
132
- details: `Results contain ${data.length} rows, application/vnd.pgrst.object+json requires 1 row`,
133
- hint: null,
134
- message: "JSON object requested, multiple (or no) rows returned"
135
- };
136
- data = null;
137
- count = null;
138
- status = 406;
139
- statusText = "Not Acceptable";
140
- } else if (data.length === 1) data = data[0];
141
- else data = null;
142
- } else {
143
- const body = await res$1.text();
188
+ const executeWithRetry = async () => {
189
+ let attemptCount = 0;
190
+ while (true) {
191
+ const requestHeaders = new Headers(_this.headers);
192
+ if (attemptCount > 0) requestHeaders.set("X-Retry-Count", String(attemptCount));
193
+ let res$1;
144
194
  try {
145
- error = JSON.parse(body);
146
- if (Array.isArray(error) && res$1.status === 404) {
147
- data = [];
148
- error = null;
149
- status = 200;
150
- statusText = "OK";
195
+ res$1 = await _fetch(_this.url.toString(), {
196
+ method: _this.method,
197
+ headers: requestHeaders,
198
+ body: JSON.stringify(_this.body),
199
+ signal: _this.signal
200
+ });
201
+ } catch (fetchError) {
202
+ if ((fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) === "AbortError" || (fetchError === null || fetchError === void 0 ? void 0 : fetchError.code) === "ABORT_ERR") throw fetchError;
203
+ if (!RETRYABLE_METHODS.includes(_this.method)) throw fetchError;
204
+ if (_this.retryEnabled && attemptCount < DEFAULT_MAX_RETRIES) {
205
+ const delay = getRetryDelay(attemptCount);
206
+ attemptCount++;
207
+ await sleep(delay, _this.signal);
208
+ continue;
151
209
  }
152
- } catch (_unused) {
153
- if (res$1.status === 404 && body === "") {
154
- status = 204;
155
- statusText = "No Content";
156
- } else error = { message: body };
210
+ throw fetchError;
211
+ }
212
+ if (shouldRetry(_this.method, res$1.status, attemptCount, _this.retryEnabled)) {
213
+ var _res$headers$get, _res$headers;
214
+ const retryAfterHeader = (_res$headers$get = (_res$headers = res$1.headers) === null || _res$headers === void 0 ? void 0 : _res$headers.get("Retry-After")) !== null && _res$headers$get !== void 0 ? _res$headers$get : null;
215
+ const delay = retryAfterHeader !== null ? Math.max(0, parseInt(retryAfterHeader, 10) || 0) * 1e3 : getRetryDelay(attemptCount);
216
+ await res$1.text();
217
+ attemptCount++;
218
+ await sleep(delay, _this.signal);
219
+ continue;
157
220
  }
158
- if (error && _this.shouldThrowOnError) throw new PostgrestError(error);
221
+ return await _this.processResponse(res$1);
159
222
  }
160
- return {
161
- error,
162
- data,
163
- count,
164
- status,
165
- statusText
166
- };
167
- });
223
+ };
224
+ let res = executeWithRetry();
168
225
  if (!this.shouldThrowOnError) res = res.catch((fetchError) => {
169
226
  var _fetchError$name2;
170
227
  let errorDetails = "";
@@ -209,6 +266,67 @@ var PostgrestBuilder = class {
209
266
  return res.then(onfulfilled, onrejected);
210
267
  }
211
268
  /**
269
+ * Process a fetch response and return the standardized postgrest response.
270
+ */
271
+ async processResponse(res) {
272
+ var _this2 = this;
273
+ let error = null;
274
+ let data = null;
275
+ let count = null;
276
+ let status = res.status;
277
+ let statusText = res.statusText;
278
+ if (res.ok) {
279
+ var _this$headers$get2, _res$headers$get2;
280
+ if (_this2.method !== "HEAD") {
281
+ var _this$headers$get;
282
+ const body = await res.text();
283
+ if (body === "") {} else if (_this2.headers.get("Accept") === "text/csv") data = body;
284
+ else if (_this2.headers.get("Accept") && ((_this$headers$get = _this2.headers.get("Accept")) === null || _this$headers$get === void 0 ? void 0 : _this$headers$get.includes("application/vnd.pgrst.plan+text"))) data = body;
285
+ else data = JSON.parse(body);
286
+ }
287
+ const countHeader = (_this$headers$get2 = _this2.headers.get("Prefer")) === null || _this$headers$get2 === void 0 ? void 0 : _this$headers$get2.match(/count=(exact|planned|estimated)/);
288
+ const contentRange = (_res$headers$get2 = res.headers.get("content-range")) === null || _res$headers$get2 === void 0 ? void 0 : _res$headers$get2.split("/");
289
+ if (countHeader && contentRange && contentRange.length > 1) count = parseInt(contentRange[1]);
290
+ if (_this2.isMaybeSingle && Array.isArray(data)) if (data.length > 1) {
291
+ error = {
292
+ code: "PGRST116",
293
+ details: `Results contain ${data.length} rows, application/vnd.pgrst.object+json requires 1 row`,
294
+ hint: null,
295
+ message: "JSON object requested, multiple (or no) rows returned"
296
+ };
297
+ data = null;
298
+ count = null;
299
+ status = 406;
300
+ statusText = "Not Acceptable";
301
+ } else if (data.length === 1) data = data[0];
302
+ else data = null;
303
+ } else {
304
+ const body = await res.text();
305
+ try {
306
+ error = JSON.parse(body);
307
+ if (Array.isArray(error) && res.status === 404) {
308
+ data = [];
309
+ error = null;
310
+ status = 200;
311
+ statusText = "OK";
312
+ }
313
+ } catch (_unused) {
314
+ if (res.status === 404 && body === "") {
315
+ status = 204;
316
+ statusText = "No Content";
317
+ } else error = { message: body };
318
+ }
319
+ if (error && _this2.shouldThrowOnError) throw new PostgrestError(error);
320
+ }
321
+ return {
322
+ error,
323
+ data,
324
+ count,
325
+ status,
326
+ statusText
327
+ };
328
+ }
329
+ /**
212
330
  * Override the type of the returned `data`.
213
331
  *
214
332
  * @typeParam NewResult - The new result type to override with
@@ -2891,22 +3009,31 @@ var PostgrestQueryBuilder = class {
2891
3009
  *
2892
3010
  * @category Database
2893
3011
  *
3012
+ * @param url - The URL for the query
3013
+ * @param options - Named parameters
3014
+ * @param options.headers - Custom headers
3015
+ * @param options.schema - Postgres schema to use
3016
+ * @param options.fetch - Custom fetch implementation
3017
+ * @param options.urlLengthLimit - Maximum URL length before warning
3018
+ * @param options.retry - Enable automatic retries for transient errors (default: true)
3019
+ *
2894
3020
  * @example Creating a Postgrest query builder
2895
3021
  * ```ts
2896
3022
  * import { PostgrestQueryBuilder } from '@supabase/postgrest-js'
2897
3023
  *
2898
3024
  * const query = new PostgrestQueryBuilder(
2899
3025
  * new URL('https://xyzcompany.supabase.co/rest/v1/users'),
2900
- * { headers: { apikey: 'public-anon-key' } }
3026
+ * { headers: { apikey: 'public-anon-key' }, retry: true }
2901
3027
  * )
2902
3028
  * ```
2903
3029
  */
2904
- constructor(url, { headers = {}, schema, fetch: fetch$1, urlLengthLimit = 8e3 }) {
3030
+ constructor(url, { headers = {}, schema, fetch: fetch$1, urlLengthLimit = 8e3, retry }) {
2905
3031
  this.url = url;
2906
3032
  this.headers = new Headers(headers);
2907
3033
  this.schema = schema;
2908
3034
  this.fetch = fetch$1;
2909
3035
  this.urlLengthLimit = urlLengthLimit;
3036
+ this.retry = retry;
2910
3037
  }
2911
3038
  /**
2912
3039
  * Clone URL and headers to prevent shared state between operations.
@@ -3714,7 +3841,8 @@ var PostgrestQueryBuilder = class {
3714
3841
  headers,
3715
3842
  schema: this.schema,
3716
3843
  fetch: this.fetch,
3717
- urlLengthLimit: this.urlLengthLimit
3844
+ urlLengthLimit: this.urlLengthLimit,
3845
+ retry: this.retry
3718
3846
  });
3719
3847
  }
3720
3848
  /**
@@ -3848,7 +3976,8 @@ var PostgrestQueryBuilder = class {
3848
3976
  schema: this.schema,
3849
3977
  body: values,
3850
3978
  fetch: (_this$fetch = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch,
3851
- urlLengthLimit: this.urlLengthLimit
3979
+ urlLengthLimit: this.urlLengthLimit,
3980
+ retry: this.retry
3852
3981
  });
3853
3982
  }
3854
3983
  /**
@@ -4081,7 +4210,8 @@ var PostgrestQueryBuilder = class {
4081
4210
  schema: this.schema,
4082
4211
  body: values,
4083
4212
  fetch: (_this$fetch2 = this.fetch) !== null && _this$fetch2 !== void 0 ? _this$fetch2 : fetch,
4084
- urlLengthLimit: this.urlLengthLimit
4213
+ urlLengthLimit: this.urlLengthLimit,
4214
+ retry: this.retry
4085
4215
  });
4086
4216
  }
4087
4217
  /**
@@ -4235,7 +4365,8 @@ var PostgrestQueryBuilder = class {
4235
4365
  schema: this.schema,
4236
4366
  body: values,
4237
4367
  fetch: (_this$fetch3 = this.fetch) !== null && _this$fetch3 !== void 0 ? _this$fetch3 : fetch,
4238
- urlLengthLimit: this.urlLengthLimit
4368
+ urlLengthLimit: this.urlLengthLimit,
4369
+ retry: this.retry
4239
4370
  });
4240
4371
  }
4241
4372
  /**
@@ -4367,7 +4498,8 @@ var PostgrestQueryBuilder = class {
4367
4498
  headers,
4368
4499
  schema: this.schema,
4369
4500
  fetch: (_this$fetch4 = this.fetch) !== null && _this$fetch4 !== void 0 ? _this$fetch4 : fetch,
4370
- urlLengthLimit: this.urlLengthLimit
4501
+ urlLengthLimit: this.urlLengthLimit,
4502
+ retry: this.retry
4371
4503
  });
4372
4504
  }
4373
4505
  };
@@ -4461,6 +4593,10 @@ var PostgrestClient = class PostgrestClient {
4461
4593
  * @param options.fetch - Custom fetch
4462
4594
  * @param options.timeout - Optional timeout in milliseconds for all requests. When set, requests will automatically abort after this duration to prevent indefinite hangs.
4463
4595
  * @param options.urlLengthLimit - Maximum URL length in characters before warnings/errors are triggered. Defaults to 8000.
4596
+ * @param options.retry - Enable or disable automatic retries for transient errors.
4597
+ * When enabled, idempotent requests (GET, HEAD, OPTIONS) that fail with network
4598
+ * errors or HTTP 503/520 responses will be automatically retried up to 3 times
4599
+ * with exponential backoff (1s, 2s, 4s). Defaults to `true`.
4464
4600
  * @example
4465
4601
  * ```ts
4466
4602
  * import { PostgrestClient } from '@supabase/postgrest-js'
@@ -4496,10 +4632,11 @@ var PostgrestClient = class PostgrestClient {
4496
4632
  * headers: { apikey: 'public-anon-key' },
4497
4633
  * schema: 'public',
4498
4634
  * timeout: 30000, // 30 second timeout
4635
+ * retry: false, // Disable automatic retries
4499
4636
  * })
4500
4637
  * ```
4501
4638
  */
4502
- constructor(url, { headers = {}, schema, fetch: fetch$1, timeout, urlLengthLimit = 8e3 } = {}) {
4639
+ constructor(url, { headers = {}, schema, fetch: fetch$1, timeout, urlLengthLimit = 8e3, retry } = {}) {
4503
4640
  this.url = url;
4504
4641
  this.headers = new Headers(headers);
4505
4642
  this.schemaName = schema;
@@ -4527,6 +4664,7 @@ var PostgrestClient = class PostgrestClient {
4527
4664
  return originalFetch(input, _objectSpread2(_objectSpread2({}, init), {}, { signal: controller.signal })).finally(() => clearTimeout(timeoutId));
4528
4665
  };
4529
4666
  else this.fetch = originalFetch;
4667
+ this.retry = retry;
4530
4668
  }
4531
4669
  /**
4532
4670
  * Perform a query on a table or a view.
@@ -4541,7 +4679,8 @@ var PostgrestClient = class PostgrestClient {
4541
4679
  headers: new Headers(this.headers),
4542
4680
  schema: this.schemaName,
4543
4681
  fetch: this.fetch,
4544
- urlLengthLimit: this.urlLengthLimit
4682
+ urlLengthLimit: this.urlLengthLimit,
4683
+ retry: this.retry
4545
4684
  });
4546
4685
  }
4547
4686
  /**
@@ -4558,7 +4697,8 @@ var PostgrestClient = class PostgrestClient {
4558
4697
  headers: this.headers,
4559
4698
  schema,
4560
4699
  fetch: this.fetch,
4561
- urlLengthLimit: this.urlLengthLimit
4700
+ urlLengthLimit: this.urlLengthLimit,
4701
+ retry: this.retry
4562
4702
  });
4563
4703
  }
4564
4704
  /**
@@ -4755,7 +4895,8 @@ var PostgrestClient = class PostgrestClient {
4755
4895
  schema: this.schemaName,
4756
4896
  body,
4757
4897
  fetch: (_this$fetch = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch,
4758
- urlLengthLimit: this.urlLengthLimit
4898
+ urlLengthLimit: this.urlLengthLimit,
4899
+ retry: this.retry
4759
4900
  });
4760
4901
  }
4761
4902
  };