@gusnips/react 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -34,9 +34,11 @@ token when you use it, so the losers of that race each invalidate the winner —
34
34
  signed out in the middle of a load that was working. Three separate codebases arrived at
35
35
  single-flight refresh independently. This is that.
36
36
 
37
- **A refresh that never reached the server is not a "no".** Dropping a packet tells you nothing
38
- about whether a session is good. So `refresh()` answers `{ token, reachedAuth }`, and only a real
39
- refusal signs anyone out. Before that split, a Wi-Fi blip logged people out mid-load.
37
+ **Only auth ANSWERING is a "no".** Dropping a packet tells you nothing about whether a session is
38
+ good, and neither does a 500 — that is auth failing, not auth answering. So `refresh()` answers
39
+ `{ token, reachedAuth }`, and only a real refusal signs anyone out. Before that split, a Wi-Fi
40
+ blip logged people out mid-load; without the second half, one bad minute at the auth server signs
41
+ out everybody whose token happened to need refreshing.
40
42
 
41
43
  **A sign-out has a fail-safe timer.** Awaiting `signOut()` before redirecting covers a rejection,
42
44
  not a hang — and a hang leaves someone signed out in name only: every request 401ing, nothing
@@ -55,6 +57,24 @@ out. More importantly, it keeps a network failure separate from GoTrue refusing
55
57
  so losing Wi-Fi does not become a logout. It lives behind a subpath because it imports
56
58
  `@supabase/supabase-js`; the main entry does not.
57
59
 
60
+ The test it makes that decision with is exported, because a refresh is not the only place an app
61
+ asks it:
62
+
63
+ ```ts
64
+ import { isAuthOutage } from "@gusnips/react/supabase";
65
+
66
+ // A callback that just failed to trade its one-use link.
67
+ if (isAuthOutage(error))
68
+ showRetry(); // the link was never spent — the same one still works
69
+ else showLinkSpent(); // auth said no: only a new link helps
70
+ ```
71
+
72
+ `isAuthOutage` is true when auth FAILED rather than answered — a request that never landed, or a
73
+ 5xx. Only a real refusal is an answer, and only an answer may end a session or a link. Use it
74
+ anywhere that decision is made; the sentence you show is a separate question, and a narrower one
75
+ — "check your connection" is true of a request that never landed and false of a 502, which is
76
+ ours. auth-js marks "nothing came back", and only that, with status 0.
77
+
58
78
  Supabase email links have two complete patterns. Keep either one, never half of each:
59
79
 
60
80
  - A link carrying `token_hash` needs one explicit `verifyOtp` call. Guard it against React
@@ -63,8 +83,9 @@ Supabase email links have two complete patterns. Keep either one, never half of
63
83
  implicit `#access_token` or a PKCE `?code`; it does not consume `token_hash`.
64
84
 
65
85
  Recovery and invite links continue to the new-password screen after the session opens. A callback
66
- must also tell a used or expired link apart from a request that never reached GoTrue: the first
67
- needs a new link, while the second can retry the same one.
86
+ must also tell a used or expired link apart from a request that never landed — or one that landed
87
+ and came back 5xx: the first needs a new link, and the other two can retry the same one, because
88
+ the hash was never spent.
68
89
 
69
90
  There is also a deadline on every request, which none of the codebases this came from had. A
70
91
  request with no timeout is a spinner with no end.
@@ -16,12 +16,40 @@ export interface SupabaseSessionAuth {
16
16
  error: unknown;
17
17
  }>;
18
18
  }
19
+ /**
20
+ * Auth FAILED rather than answered, so nothing is known about the credential it was handed:
21
+ * either the request never landed, or GoTrue failed on its own.
22
+ *
23
+ * This is the question every site that ends a session has to ask — a token refresh, an API
24
+ * door, and a callback choosing between "try again" and "that link is spent". Without it one
25
+ * bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
26
+ * everybody clicking a recovery link that it has expired, which sends them to ask the service
27
+ * that is down for a replacement.
28
+ *
29
+ * Both clauses carry weight, and the second is why this is a function rather than one call.
30
+ * `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
31
+ * auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
32
+ * still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
33
+ * whichever version happens to be installed, which is not the same as being right, and a fleet
34
+ * installs several versions at once. It is also a LIST rather than a range, so it has holes at
35
+ * every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
36
+ * 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
37
+ * recent SDK does not make the status clause redundant.
38
+ *
39
+ * Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
40
+ * heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
41
+ *
42
+ * Anything that is not an auth error at all answers true: a failure from outside auth-js is not
43
+ * a verdict on the credential either.
44
+ */
45
+ export declare function isAuthOutage(error: unknown): boolean;
19
46
  /**
20
47
  * Connects a Supabase auth client to {@link SessionAdapter}.
21
48
  *
22
- * The distinction this preserves is the one that decides whether anybody is signed out: a
23
- * retryable fetch error never reached GoTrue and says nothing about the session; any other auth
24
- * error is an answer. A thrown network error stays on the first side of that line too.
49
+ * The distinction this preserves is the one that decides whether anybody is signed out: only
50
+ * auth ANSWERING proves a session is gone. A fetch that never landed says nothing about it, and
51
+ * neither does a 5xx, which is auth failing rather than auth answering. A thrown network error
52
+ * stays on that side of the line too.
25
53
  */
26
54
  export declare function createSupabaseSessionAdapter(auth: SupabaseSessionAuth): SessionAdapter;
27
55
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"supabase-session.d.ts","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,IAAI,EAAE;QAAE,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED,8FAA8F;AAC9F,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7C,cAAc,IAAI,OAAO,CAAC,qBAAqB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACxC;AAcD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAsBtF"}
1
+ {"version":3,"file":"supabase-session.d.ts","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,IAAI,EAAE;QAAE,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED,8FAA8F;AAC9F,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7C,cAAc,IAAI,OAAO,CAAC,qBAAqB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACxC;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGpD;AAaD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAsBtF"}
@@ -1,6 +1,41 @@
1
1
  import { isAuthError, isAuthRetryableFetchError } from "@supabase/supabase-js";
2
+ /**
3
+ * Auth FAILED rather than answered, so nothing is known about the credential it was handed:
4
+ * either the request never landed, or GoTrue failed on its own.
5
+ *
6
+ * This is the question every site that ends a session has to ask — a token refresh, an API
7
+ * door, and a callback choosing between "try again" and "that link is spent". Without it one
8
+ * bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
9
+ * everybody clicking a recovery link that it has expired, which sends them to ask the service
10
+ * that is down for a replacement.
11
+ *
12
+ * Both clauses carry weight, and the second is why this is a function rather than one call.
13
+ * `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
14
+ * auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
15
+ * still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
16
+ * whichever version happens to be installed, which is not the same as being right, and a fleet
17
+ * installs several versions at once. It is also a LIST rather than a range, so it has holes at
18
+ * every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
19
+ * 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
20
+ * recent SDK does not make the status clause redundant.
21
+ *
22
+ * Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
23
+ * heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
24
+ *
25
+ * Anything that is not an auth error at all answers true: a failure from outside auth-js is not
26
+ * a verdict on the credential either.
27
+ */
28
+ export function isAuthOutage(error) {
29
+ if (!isAuthError(error))
30
+ return true;
31
+ return isAuthRetryableFetchError(error) || (error.status ?? 0) >= 500;
32
+ }
2
33
  function reachedAuth(error) {
3
- if (!isAuthError(error) || isAuthRetryableFetchError(error))
34
+ // Measured across the fleet: four backends answered 401 to their own auth provider's 500,
35
+ // and every client reads a 401 as a dead session, so one bad minute at auth signed out
36
+ // everybody who was signed in. `isAuthError` leads only for the narrowing below —
37
+ // `isAuthOutage` already answers true for anything that is not an auth error.
38
+ if (!isAuthError(error) || isAuthOutage(error))
4
39
  return false;
5
40
  // Newer clients name a refresh that reached GoTrue but was deliberately discarded because the
6
41
  // local session changed mid-flight. That race is a no-op, not proof that either session died.
@@ -9,9 +44,10 @@ function reachedAuth(error) {
9
44
  /**
10
45
  * Connects a Supabase auth client to {@link SessionAdapter}.
11
46
  *
12
- * The distinction this preserves is the one that decides whether anybody is signed out: a
13
- * retryable fetch error never reached GoTrue and says nothing about the session; any other auth
14
- * error is an answer. A thrown network error stays on the first side of that line too.
47
+ * The distinction this preserves is the one that decides whether anybody is signed out: only
48
+ * auth ANSWERING proves a session is gone. A fetch that never landed says nothing about it, and
49
+ * neither does a 5xx, which is auth failing rather than auth answering. A thrown network error
50
+ * stays on that side of the line too.
15
51
  */
16
52
  export function createSupabaseSessionAdapter(auth) {
17
53
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"supabase-session.js","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAuB,MAAM,uBAAuB,CAAC;AAmBpG,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,yBAAyB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1E,8FAA8F;IAC9F,8FAA8F;IAC9F,OAAO,KAAK,CAAC,IAAI,KAAK,2BAA2B,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAyB;IACpE,OAAO;QACL,QAAQ,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpD,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI;oBACzC,WAAW,EAAE,KAAK,KAAK,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC;iBAClD,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"supabase-session.js","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAuB,MAAM,uBAAuB,CAAC;AAmBpG;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;AACxE,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,0FAA0F;IAC1F,uFAAuF;IACvF,kFAAkF;IAClF,8EAA8E;IAC9E,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,8FAA8F;IAC9F,8FAA8F;IAC9F,OAAO,KAAK,CAAC,IAAI,KAAK,2BAA2B,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAyB;IACpE,OAAO;QACL,QAAQ,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpD,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI;oBACzC,WAAW,EAAE,KAAK,KAAK,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC;iBAClD,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gusnips/react",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "The headless runtime under a Vite + React SPA: typed fetch client with single-flight refresh, auth store, query defaults, route guards, error boundary, stale-tab recovery, i18n bootstrap.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -4,7 +4,11 @@ import {
4
4
  AuthRetryableFetchError,
5
5
  } from "@supabase/supabase-js";
6
6
  import { describe, expect, it, vi } from "vitest";
7
- import { createSupabaseSessionAdapter, type SupabaseSessionAuth } from "./supabase-session.ts";
7
+ import {
8
+ createSupabaseSessionAdapter,
9
+ isAuthOutage,
10
+ type SupabaseSessionAuth,
11
+ } from "./supabase-session.ts";
8
12
 
9
13
  function auth(overrides: Partial<SupabaseSessionAuth> = {}): SupabaseSessionAuth {
10
14
  return {
@@ -67,6 +71,20 @@ describe("createSupabaseSessionAdapter", () => {
67
71
  await expect(thrown.refresh()).resolves.toEqual({ token: null, reachedAuth: true });
68
72
  });
69
73
 
74
+ it("keeps the session when auth answers with a failure of its own", async () => {
75
+ // `isAuthRetryableFetchError` says false for this one — it is an API error, not a fetch
76
+ // failure — so what holds the session is the status clause beside it. A 5xx is auth
77
+ // failing, never auth answering, and reading it as an answer signs the person out.
78
+ const outage = new AuthApiError("Internal Server Error", 500, undefined);
79
+ const adapter = createSupabaseSessionAdapter(
80
+ auth({
81
+ refreshSession: async () => ({ data: { session: null }, error: outage }),
82
+ }),
83
+ );
84
+
85
+ await expect(adapter.refresh()).resolves.toEqual({ token: null, reachedAuth: false });
86
+ });
87
+
70
88
  it("does not kill a session whose rotated refresh was discarded after a local race", async () => {
71
89
  const adapter = createSupabaseSessionAdapter(
72
90
  auth({
@@ -89,3 +107,27 @@ describe("createSupabaseSessionAdapter", () => {
89
107
  expect(signOut).toHaveBeenCalledOnce();
90
108
  });
91
109
  });
110
+
111
+ describe("isAuthOutage", () => {
112
+ it("lets a real refusal through as an answer", () => {
113
+ expect(
114
+ isAuthOutage(new AuthApiError("Invalid login credentials", 400, "invalid_credentials")),
115
+ ).toBe(false);
116
+ expect(
117
+ isAuthOutage(new AuthApiError("Token has expired or is invalid", 403, "otp_expired")),
118
+ ).toBe(false);
119
+ });
120
+
121
+ it("refuses to read a failure as an answer", () => {
122
+ // Nothing came back, and the two shapes that carries.
123
+ expect(isAuthOutage(new AuthRetryableFetchError("Failed to fetch", 0))).toBe(true);
124
+ expect(isAuthOutage(new TypeError("Load failed"))).toBe(true);
125
+ // A gateway between us and GoTrue, which auth-js wraps by name.
126
+ expect(isAuthOutage(new AuthRetryableFetchError("Bad Gateway", 502))).toBe(true);
127
+ // And the reason the status clause is not redundant: the statuses earning that name are a
128
+ // LIST, so it has holes at every version. 507 and 599 are on no version of it, and arrive
129
+ // as ordinary AuthApiErrors that `isAuthRetryableFetchError` answers false for.
130
+ expect(isAuthOutage(new AuthApiError("Insufficient Storage", 507, undefined))).toBe(true);
131
+ expect(isAuthOutage(new AuthApiError("Network Connect Timeout", 599, undefined))).toBe(true);
132
+ });
133
+ });
@@ -17,8 +17,43 @@ type _SupabaseAuthFitsAdapter = Satisfied<
17
17
  SupabaseClient["auth"] extends SupabaseSessionAuth ? true : false
18
18
  >;
19
19
 
20
+ /**
21
+ * Auth FAILED rather than answered, so nothing is known about the credential it was handed:
22
+ * either the request never landed, or GoTrue failed on its own.
23
+ *
24
+ * This is the question every site that ends a session has to ask — a token refresh, an API
25
+ * door, and a callback choosing between "try again" and "that link is spent". Without it one
26
+ * bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
27
+ * everybody clicking a recovery link that it has expired, which sends them to ask the service
28
+ * that is down for a replacement.
29
+ *
30
+ * Both clauses carry weight, and the second is why this is a function rather than one call.
31
+ * `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
32
+ * auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
33
+ * still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
34
+ * whichever version happens to be installed, which is not the same as being right, and a fleet
35
+ * installs several versions at once. It is also a LIST rather than a range, so it has holes at
36
+ * every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
37
+ * 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
38
+ * recent SDK does not make the status clause redundant.
39
+ *
40
+ * Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
41
+ * heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
42
+ *
43
+ * Anything that is not an auth error at all answers true: a failure from outside auth-js is not
44
+ * a verdict on the credential either.
45
+ */
46
+ export function isAuthOutage(error: unknown): boolean {
47
+ if (!isAuthError(error)) return true;
48
+ return isAuthRetryableFetchError(error) || (error.status ?? 0) >= 500;
49
+ }
50
+
20
51
  function reachedAuth(error: unknown): boolean {
21
- if (!isAuthError(error) || isAuthRetryableFetchError(error)) return false;
52
+ // Measured across the fleet: four backends answered 401 to their own auth provider's 500,
53
+ // and every client reads a 401 as a dead session, so one bad minute at auth signed out
54
+ // everybody who was signed in. `isAuthError` leads only for the narrowing below —
55
+ // `isAuthOutage` already answers true for anything that is not an auth error.
56
+ if (!isAuthError(error) || isAuthOutage(error)) return false;
22
57
  // Newer clients name a refresh that reached GoTrue but was deliberately discarded because the
23
58
  // local session changed mid-flight. That race is a no-op, not proof that either session died.
24
59
  return error.name !== "AuthRefreshDiscardedError";
@@ -27,9 +62,10 @@ function reachedAuth(error: unknown): boolean {
27
62
  /**
28
63
  * Connects a Supabase auth client to {@link SessionAdapter}.
29
64
  *
30
- * The distinction this preserves is the one that decides whether anybody is signed out: a
31
- * retryable fetch error never reached GoTrue and says nothing about the session; any other auth
32
- * error is an answer. A thrown network error stays on the first side of that line too.
65
+ * The distinction this preserves is the one that decides whether anybody is signed out: only
66
+ * auth ANSWERING proves a session is gone. A fetch that never landed says nothing about it, and
67
+ * neither does a 5xx, which is auth failing rather than auth answering. A thrown network error
68
+ * stays on that side of the line too.
33
69
  */
34
70
  export function createSupabaseSessionAdapter(auth: SupabaseSessionAuth): SessionAdapter {
35
71
  return {