@sanity/client 7.23.0 → 7.23.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.
@@ -906,11 +906,26 @@ export declare function connectEventSource<EventName extends string>(
906
906
 
907
907
  /**
908
908
  * @public
909
- * Thrown if the EventSource connection could not be established.
910
- * Note that ConnectionFailedErrors are rare, and disconnects will normally be handled by the EventSource instance itself and emitted as `reconnect` events.
909
+ * Thrown when the EventSource connection could not be established, or was rejected by the server.
910
+ * Transient failures (network drops, 5xx, 408, 429) are reconnected internally and emitted as
911
+ * `reconnect` events; a permanent rejection (any other 4xx, eg an expired token) errors the
912
+ * stream with this class so consumers can react — check `status` for the rejection code.
911
913
  */
912
914
  export declare class ConnectionFailedError extends Error {
913
915
  readonly name = 'ConnectionFailedError'
916
+ /**
917
+ * HTTP status code of the rejected connection attempt, if known.
918
+ * Only set when the EventSource implementation exposes it — the polyfill used in
919
+ * Node.js and when custom headers (eg authorization) are required does, while
920
+ * native EventSource implementations (browser and Node.js) do not.
921
+ */
922
+ readonly status?: number
923
+ constructor(
924
+ message?: string,
925
+ options?: ErrorOptions & {
926
+ status?: number
927
+ },
928
+ )
914
929
  }
915
930
 
916
931
  /**
@@ -906,11 +906,26 @@ export declare function connectEventSource<EventName extends string>(
906
906
 
907
907
  /**
908
908
  * @public
909
- * Thrown if the EventSource connection could not be established.
910
- * Note that ConnectionFailedErrors are rare, and disconnects will normally be handled by the EventSource instance itself and emitted as `reconnect` events.
909
+ * Thrown when the EventSource connection could not be established, or was rejected by the server.
910
+ * Transient failures (network drops, 5xx, 408, 429) are reconnected internally and emitted as
911
+ * `reconnect` events; a permanent rejection (any other 4xx, eg an expired token) errors the
912
+ * stream with this class so consumers can react — check `status` for the rejection code.
911
913
  */
912
914
  export declare class ConnectionFailedError extends Error {
913
915
  readonly name = 'ConnectionFailedError'
916
+ /**
917
+ * HTTP status code of the rejected connection attempt, if known.
918
+ * Only set when the EventSource implementation exposes it — the polyfill used in
919
+ * Node.js and when custom headers (eg authorization) are required does, while
920
+ * native EventSource implementations (browser and Node.js) do not.
921
+ */
922
+ readonly status?: number
923
+ constructor(
924
+ message?: string,
925
+ options?: ErrorOptions & {
926
+ status?: number
927
+ },
928
+ )
914
929
  }
915
930
 
916
931
  /**
package/dist/stega.d.cts CHANGED
@@ -906,11 +906,26 @@ export declare function connectEventSource<EventName extends string>(
906
906
 
907
907
  /**
908
908
  * @public
909
- * Thrown if the EventSource connection could not be established.
910
- * Note that ConnectionFailedErrors are rare, and disconnects will normally be handled by the EventSource instance itself and emitted as `reconnect` events.
909
+ * Thrown when the EventSource connection could not be established, or was rejected by the server.
910
+ * Transient failures (network drops, 5xx, 408, 429) are reconnected internally and emitted as
911
+ * `reconnect` events; a permanent rejection (any other 4xx, eg an expired token) errors the
912
+ * stream with this class so consumers can react — check `status` for the rejection code.
911
913
  */
912
914
  export declare class ConnectionFailedError extends Error {
913
915
  readonly name = 'ConnectionFailedError'
916
+ /**
917
+ * HTTP status code of the rejected connection attempt, if known.
918
+ * Only set when the EventSource implementation exposes it — the polyfill used in
919
+ * Node.js and when custom headers (eg authorization) are required does, while
920
+ * native EventSource implementations (browser and Node.js) do not.
921
+ */
922
+ readonly status?: number
923
+ constructor(
924
+ message?: string,
925
+ options?: ErrorOptions & {
926
+ status?: number
927
+ },
928
+ )
914
929
  }
915
930
 
916
931
  /**
package/dist/stega.d.ts CHANGED
@@ -906,11 +906,26 @@ export declare function connectEventSource<EventName extends string>(
906
906
 
907
907
  /**
908
908
  * @public
909
- * Thrown if the EventSource connection could not be established.
910
- * Note that ConnectionFailedErrors are rare, and disconnects will normally be handled by the EventSource instance itself and emitted as `reconnect` events.
909
+ * Thrown when the EventSource connection could not be established, or was rejected by the server.
910
+ * Transient failures (network drops, 5xx, 408, 429) are reconnected internally and emitted as
911
+ * `reconnect` events; a permanent rejection (any other 4xx, eg an expired token) errors the
912
+ * stream with this class so consumers can react — check `status` for the rejection code.
911
913
  */
912
914
  export declare class ConnectionFailedError extends Error {
913
915
  readonly name = 'ConnectionFailedError'
916
+ /**
917
+ * HTTP status code of the rejected connection attempt, if known.
918
+ * Only set when the EventSource implementation exposes it — the polyfill used in
919
+ * Node.js and when custom headers (eg authorization) are required does, while
920
+ * native EventSource implementations (browser and Node.js) do not.
921
+ */
922
+ readonly status?: number
923
+ constructor(
924
+ message?: string,
925
+ options?: ErrorOptions & {
926
+ status?: number
927
+ },
928
+ )
914
929
  }
915
930
 
916
931
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/client",
3
- "version": "7.23.0",
3
+ "version": "7.23.1",
4
4
  "description": "Client for retrieving, creating and patching data from Sanity.io",
5
5
  "keywords": [
6
6
  "sanity",
@@ -5,11 +5,25 @@ import {type Any} from '../types'
5
5
 
6
6
  /**
7
7
  * @public
8
- * Thrown if the EventSource connection could not be established.
9
- * Note that ConnectionFailedErrors are rare, and disconnects will normally be handled by the EventSource instance itself and emitted as `reconnect` events.
8
+ * Thrown when the EventSource connection could not be established, or was rejected by the server.
9
+ * Transient failures (network drops, 5xx, 408, 429) are reconnected internally and emitted as
10
+ * `reconnect` events; a permanent rejection (any other 4xx, eg an expired token) errors the
11
+ * stream with this class so consumers can react — check `status` for the rejection code.
10
12
  */
11
13
  export class ConnectionFailedError extends Error {
12
14
  readonly name = 'ConnectionFailedError'
15
+ /**
16
+ * HTTP status code of the rejected connection attempt, if known.
17
+ * Only set when the EventSource implementation exposes it — the polyfill used in
18
+ * Node.js and when custom headers (eg authorization) are required does, while
19
+ * native EventSource implementations (browser and Node.js) do not.
20
+ */
21
+ readonly status?: number
22
+ constructor(message?: string, options: ErrorOptions & {status?: number} = {}) {
23
+ const {status, ...errorOptions} = options
24
+ super(message, errorOptions)
25
+ this.status = status
26
+ }
13
27
  }
14
28
 
15
29
  /**
@@ -147,6 +161,18 @@ function connectWithESInstance<EventTypeName extends string>(
147
161
  // automatically, but in some cases (like when a laptop lid is closed), it will trigger onError
148
162
  // if it can't reconnect.
149
163
  // see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model
164
+ // The polyfills expose the HTTP status of a rejected connection on the error event
165
+ // (native EventSource implementations do not). A status means the server rejected the
166
+ // connection attempt, so error out regardless of readyState — the polyfills disagree
167
+ // on whether the connection closes before or after the error event is dispatched —
168
+ // and let `reconnectOnConnectionFailure` classify it (4xx fatal, otherwise retried).
169
+ const rawStatus = (evt as {status?: unknown}).status
170
+ const status = typeof rawStatus === 'number' ? rawStatus : undefined
171
+ if (status !== undefined) {
172
+ observer.error(new ConnectionFailedError('EventSource connection failed', {status}))
173
+ return
174
+ }
175
+
150
176
  if (es.readyState === es.CLOSED) {
151
177
  // In these cases we'll signal to consumers (via the error path) that a retry/reconnect is needed.
152
178
  observer.error(new ConnectionFailedError('EventSource connection failed'))
@@ -11,6 +11,8 @@ import {
11
11
 
12
12
  import {ConnectionFailedError} from './eventsource'
13
13
 
14
+ const RETRYABLE_STATUSES = new Set([408, 429])
15
+
14
16
  /**
15
17
  * Note: connection failure is not the same as network disconnect which may happen more frequent.
16
18
  * The EventSource instance will automatically reconnect in case of a network disconnect, however,
@@ -20,7 +22,19 @@ export function reconnectOnConnectionFailure<T>(): OperatorFunction<T, T | {type
20
22
  return function (source: Observable<T>) {
21
23
  return source.pipe(
22
24
  catchError((err, caught) => {
23
- if (err instanceof ConnectionFailedError) {
25
+ // Only reconnect on transient connection failures. A 4xx response is a
26
+ // rejection, not a transient failure — the server will keep rejecting
27
+ // (eg an expired token), so reconnecting would loop forever. The named
28
+ // exceptions are the explicitly transient 4xx statuses: 408 (request
29
+ // timeout) and 429 (rate limited). Anything else surfaces to the
30
+ // consumer instead.
31
+ if (
32
+ err instanceof ConnectionFailedError &&
33
+ (typeof err.status !== 'number' ||
34
+ err.status < 400 ||
35
+ err.status >= 500 ||
36
+ RETRYABLE_STATUSES.has(err.status))
37
+ ) {
24
38
  return concat(of({type: 'reconnect' as const}), timer(1000).pipe(mergeMap(() => caught)))
25
39
  }
26
40
  return throwError(() => err)
@@ -2406,6 +2406,17 @@ ${codeFrame(query, { start, end }, description)}${withTag}${withTraceId}`;
2406
2406
  };
2407
2407
  class ConnectionFailedError extends Error {
2408
2408
  name = "ConnectionFailedError";
2409
+ /**
2410
+ * HTTP status code of the rejected connection attempt, if known.
2411
+ * Only set when the EventSource implementation exposes it — the polyfill used in
2412
+ * Node.js and when custom headers (eg authorization) are required does, while
2413
+ * native EventSource implementations (browser and Node.js) do not.
2414
+ */
2415
+ status;
2416
+ constructor(message, options = {}) {
2417
+ const { status, ...errorOptions } = options;
2418
+ super(message, errorOptions), this.status = status;
2419
+ }
2409
2420
  }
2410
2421
  class DisconnectError extends Error {
2411
2422
  name = "DisconnectError";
@@ -2449,6 +2460,11 @@ ${codeFrame(query, { start, end }, description)}${withTag}${withTraceId}`;
2449
2460
  );
2450
2461
  return;
2451
2462
  }
2463
+ const rawStatus = evt.status, status = typeof rawStatus == "number" ? rawStatus : void 0;
2464
+ if (status !== void 0) {
2465
+ observer.error(new ConnectionFailedError("EventSource connection failed", { status }));
2466
+ return;
2467
+ }
2452
2468
  es.readyState === es.CLOSED ? observer.error(new ConnectionFailedError("EventSource connection failed")) : emitReconnect && observer.next({ type: "reconnect" });
2453
2469
  }
2454
2470
  function onOpen() {
@@ -3418,11 +3434,11 @@ ${selectionOpts}`);
3418
3434
  const pick = (obj, props) => props.reduce((selection, prop) => (typeof obj[prop] > "u" || (selection[prop] = obj[prop]), selection), {}), eventSourcePolyfill = defer(() => Promise.resolve().then(function () { return browser$1; })).pipe(
3419
3435
  map(({ default: EventSource2 }) => EventSource2),
3420
3436
  shareReplay(1)
3421
- );
3437
+ ), RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429]);
3422
3438
  function reconnectOnConnectionFailure() {
3423
3439
  return function(source) {
3424
3440
  return source.pipe(
3425
- catchError((err, caught) => err instanceof ConnectionFailedError ? concat(of({ type: "reconnect" }), timer(1e3).pipe(mergeMap(() => caught))) : throwError(() => err))
3441
+ catchError((err, caught) => err instanceof ConnectionFailedError && (typeof err.status != "number" || err.status < 400 || err.status >= 500 || RETRYABLE_STATUSES.has(err.status)) ? concat(of({ type: "reconnect" }), timer(1e3).pipe(mergeMap(() => caught))) : throwError(() => err))
3426
3442
  );
3427
3443
  };
3428
3444
  }