@gusnips/react 0.9.22 → 0.9.24

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.
@@ -1,4 +1,4 @@
1
- import type { ApiError as ApiErrorBody } from "@gusnips/http";
1
+ import type { ApiErrorBody } from "@gusnips/http";
2
2
  /**
3
3
  * A refusal, with the whole envelope intact.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5C;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;gBAGzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,EAClC,IAAI,GAAE;QACJ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;KACpB;CAaT;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEpD"}
1
+ {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;;;;GAUG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5C;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;gBAGzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,EAClC,IAAI,GAAE;QACJ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;KACpB;CAaT;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"internal-path.d.ts","sourceRoot":"","sources":["../src/internal-path.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU9D;AAeD,mGAAmG;AACnG,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,GAAG,MAAM,CAKpF"}
1
+ {"version":3,"file":"internal-path.d.ts","sourceRoot":"","sources":["../src/internal-path.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU9D;AAiBD,mGAAmG;AACnG,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,GAAG,MAAM,CAKpF"}
@@ -30,8 +30,9 @@ export function safeInternalPath(value) {
30
30
  }
31
31
  /**
32
32
  * The keys an OAuth or OIDC implicit response puts in the fragment that are themselves credentials
33
- * (RFC 6749 §4.2.2, plus the two a provider token is passed through under). The rest of that
34
- * response — `expires_in`, `token_type`, `state`, `type` — is metadata, worthless on its own.
33
+ * (RFC 6749 §4.2.2, plus the two a provider token is passed through under, plus `token_hash`, the
34
+ * single-use hash a Supabase email link carries and `verifyOtp` trades for a session). The rest of
35
+ * that response — `expires_in`, `token_type`, `state`, `type` — is metadata, worthless on its own.
35
36
  */
36
37
  const CREDENTIAL_FRAGMENT_KEYS = [
37
38
  "access_token",
@@ -39,6 +40,7 @@ const CREDENTIAL_FRAGMENT_KEYS = [
39
40
  "id_token",
40
41
  "provider_token",
41
42
  "provider_refresh_token",
43
+ "token_hash",
42
44
  ];
43
45
  /**
44
46
  * Where someone is right now, written down so signing in can bring them back.
@@ -1 +1 @@
1
- {"version":3,"file":"internal-path.js","sourceRoot":"","sources":["../src/internal-path.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD,SAAS,eAAe,CAAC,KAAa;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/F,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,eAAe;YAAE,OAAO,IAAI,CAAC;QAChD,OAAO,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,wBAAwB,GAAG;IAC/B,cAAc;IACd,eAAe;IACf,UAAU;IACV,gBAAgB;IAChB,wBAAwB;CACzB,CAAC;AASF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAa;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAClF,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChE,CAAC"}
1
+ {"version":3,"file":"internal-path.js","sourceRoot":"","sources":["../src/internal-path.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD,SAAS,eAAe,CAAC,KAAa;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/F,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,eAAe;YAAE,OAAO,IAAI,CAAC;QAChD,OAAO,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,wBAAwB,GAAG;IAC/B,cAAc;IACd,eAAe;IACf,UAAU;IACV,gBAAgB;IAChB,wBAAwB;IACxB,YAAY;CACb,CAAC;AASF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAa;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAClF,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChE,CAAC"}
package/dist/query.js CHANGED
@@ -1,4 +1,4 @@
1
- import { retryDelayMs as delayFor, shouldRetry as retryRule } from "@gusnips/http/retry";
1
+ import { retryDelayMs as delayFor, isRetryable as retryRule } from "@gusnips/http/retry";
2
2
  import { ApiError } from "./api-error.js";
3
3
  /**
4
4
  * Should this failure be retried?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gusnips/react",
3
- "version": "0.9.22",
3
+ "version": "0.9.24",
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",
@@ -80,7 +80,7 @@
80
80
  "release:major": "bun pm version major && bun publish --access public"
81
81
  },
82
82
  "dependencies": {
83
- "@gusnips/http": "0.1.6",
83
+ "@gusnips/http": "0.2.0",
84
84
  "clsx": "^2.1.1",
85
85
  "tailwind-merge": "^3.6.0"
86
86
  },
package/src/api-error.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApiError as ApiErrorBody } from "@gusnips/http";
1
+ import type { ApiErrorBody } from "@gusnips/http";
2
2
 
3
3
  /**
4
4
  * A refusal, with the whole envelope intact.
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ErrorBoundary } from "./error-boundary.tsx";
3
+
4
+ /**
5
+ * The two statics are pure, which is why the reset rule lives in them: it can be checked with no
6
+ * DOM, no renderer and no thrown error.
7
+ *
8
+ * What is worth pinning is that a caught error belongs to the screen it happened on. Wrong one way,
9
+ * navigating away carries the wreckage to every page after it. Wrong the other way, the fallback
10
+ * clears itself at once and re-renders the component that just threw, which throws again: a
11
+ * flicker instead of a state. Lifted from an adopter's copy of this class, deleted when it moved
12
+ * onto the kit's.
13
+ */
14
+ const props = (resetKey: string | undefined) => ({
15
+ resetKey,
16
+ fallback: () => null,
17
+ children: null,
18
+ });
19
+
20
+ describe("ErrorBoundary", () => {
21
+ it("records the error that was thrown", () => {
22
+ const error = new TypeError("outcome is not a key of TONE");
23
+ expect(ErrorBoundary.getDerivedStateFromError(error)).toEqual({ error });
24
+ });
25
+
26
+ it("keeps the error while the route stays put", () => {
27
+ const state = { error: new Error("boom"), key: "/jobs/abc" };
28
+ expect(ErrorBoundary.getDerivedStateFromProps(props("/jobs/abc"), state)).toBeNull();
29
+ });
30
+
31
+ it("clears the error when the route changes", () => {
32
+ const state = { error: new Error("boom"), key: "/jobs/abc" };
33
+ expect(ErrorBoundary.getDerivedStateFromProps(props("/keys"), state)).toEqual({
34
+ error: null,
35
+ key: "/keys",
36
+ });
37
+ });
38
+
39
+ it("tracks the route while nothing has thrown, so the first error is scoped to it", () => {
40
+ // Without this the key would still hold the route the boundary mounted on, and the first
41
+ // error on any later route would clear itself on the next re-render.
42
+ const state = { error: null, key: "/" };
43
+ expect(ErrorBoundary.getDerivedStateFromProps(props("/usage"), state)).toEqual({
44
+ error: null,
45
+ key: "/usage",
46
+ });
47
+ });
48
+ });
@@ -44,6 +44,7 @@ describe("returnPathFromLocation", () => {
44
44
  "id_token",
45
45
  "provider_token",
46
46
  "provider_refresh_token",
47
+ "token_hash",
47
48
  ])("drops a fragment carrying %s, and keeps the page", (key) => {
48
49
  expect(
49
50
  returnPathFromLocation({
@@ -30,8 +30,9 @@ export function safeInternalPath(value: unknown): string | null {
30
30
 
31
31
  /**
32
32
  * The keys an OAuth or OIDC implicit response puts in the fragment that are themselves credentials
33
- * (RFC 6749 §4.2.2, plus the two a provider token is passed through under). The rest of that
34
- * response — `expires_in`, `token_type`, `state`, `type` — is metadata, worthless on its own.
33
+ * (RFC 6749 §4.2.2, plus the two a provider token is passed through under, plus `token_hash`, the
34
+ * single-use hash a Supabase email link carries and `verifyOtp` trades for a session). The rest of
35
+ * that response — `expires_in`, `token_type`, `state`, `type` — is metadata, worthless on its own.
35
36
  */
36
37
  const CREDENTIAL_FRAGMENT_KEYS = [
37
38
  "access_token",
@@ -39,6 +40,7 @@ const CREDENTIAL_FRAGMENT_KEYS = [
39
40
  "id_token",
40
41
  "provider_token",
41
42
  "provider_refresh_token",
43
+ "token_hash",
42
44
  ];
43
45
 
44
46
  /** Anything shaped like the current page: `window.location` and react-router's `useLocation()`. */
package/src/query.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { retryDelayMs as delayFor, shouldRetry as retryRule } from "@gusnips/http/retry";
1
+ import { retryDelayMs as delayFor, isRetryable as retryRule } from "@gusnips/http/retry";
2
2
  import type { DefaultOptions } from "@tanstack/react-query";
3
3
  import { ApiError } from "./api-error.ts";
4
4