@gusnips/react 0.1.0 → 0.2.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/guards.d.ts +10 -2
- package/dist/guards.d.ts.map +1 -1
- package/dist/guards.js +1 -1
- package/dist/guards.js.map +1 -1
- package/package.json +1 -1
- package/src/guards.tsx +23 -3
package/dist/guards.d.ts
CHANGED
|
@@ -21,13 +21,21 @@ export interface SessionState {
|
|
|
21
21
|
/**
|
|
22
22
|
* The answer to a "who is this?" query, in the three states it actually has.
|
|
23
23
|
*
|
|
24
|
-
* This shape is invariant 4. A guard reading `!me?.isStaff` collapses "
|
|
24
|
+
* This shape is invariant 4. A guard reading `!me?.isStaff` collapses "pending", "failed" and
|
|
25
25
|
* "no" into one branch — so an operator arriving while `/auth/me` is 500ing is told the page
|
|
26
26
|
* does not exist. Wrong cause, no retry, and no request id to quote to support. One donor hit
|
|
27
27
|
* that and fixed it; the other still has the collapsed version.
|
|
28
|
+
*
|
|
29
|
+
* `"pending"` and not `"loading"` for one measured reason: it is react-query's own word for this
|
|
30
|
+
* state since v5, and with the words matching a `UseQueryResult<TMe>` satisfies this type
|
|
31
|
+
* STRUCTURALLY — `createRequireProfile(useMe, …)` takes the hook directly, no adapter. With
|
|
32
|
+
* `"loading"` it does not typecheck and every adopter hand-writes the same six-line mapping;
|
|
33
|
+
* eight of the twelve repos this serves are on react-query, and the first migration wrote that
|
|
34
|
+
* mapping before anyone noticed. Nothing here imports react-query at runtime — the type stays
|
|
35
|
+
* structural, so an app on SWR or a plain `useState` still answers it in three lines.
|
|
28
36
|
*/
|
|
29
37
|
export type MeQuery<TMe> = {
|
|
30
|
-
status: "
|
|
38
|
+
status: "pending";
|
|
31
39
|
} | {
|
|
32
40
|
status: "error";
|
|
33
41
|
error: unknown;
|
package/dist/guards.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;;;;;;;;;;;;GAaG;AAEH,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,OAAO,CAAC,GAAG,IACrB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,GAAG,CAAA;CAAE,CAAC;AAajG,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,OAAO,EAAE,SAAS,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,YAAY,EAC9B,UAAU,EAAE,MAAM,EAClB,EAAE,OAAO,EAAE,EAAE,YAAY,IAEG,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,iCAUlE;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,YAAY,EAC9B,QAAQ,EAAE,MAAM,EAChB,EAAE,OAAO,EAAE,EAAE,YAAY,IAEQ,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,iCAMvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,YAAY,IAE1F,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,EAC3B,UAAU,SAAS,GAAG,CAAC,MAAM,SAAS,CAAC,EACvC,SAAS,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,MAEP,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,iCAQvE"}
|
package/dist/guards.js
CHANGED
|
@@ -39,7 +39,7 @@ export function createRequireProfile(useMe, { loading }) {
|
|
|
39
39
|
return function requireProfile(allow, onDenied, onError) {
|
|
40
40
|
return function RequireProfile({ children }) {
|
|
41
41
|
const query = useMe();
|
|
42
|
-
if (query.status === "
|
|
42
|
+
if (query.status === "pending")
|
|
43
43
|
return _jsx(_Fragment, { children: loading });
|
|
44
44
|
if (query.status === "error")
|
|
45
45
|
return _jsx(_Fragment, { children: onError(query.error) });
|
package/dist/guards.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA0DzD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAA8B,EAC9B,UAAkB,EAClB,EAAE,OAAO,EAAgB;IAEzB,OAAO,SAAS,WAAW,CAAC,EAAE,QAAQ,EAA2B;QAC/D,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,IAAI,SAAS;YAAE,OAAO,4BAAG,OAAO,GAAI,CAAC;QACrC,IAAI,CAAC,eAAe;YAClB,OAAO,CACL,KAAC,QAAQ,IAAC,EAAE,EAAE,UAAU,EAAE,OAAO,QAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAI,CAC3F,CAAC;QACJ,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACzB,CAAC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,sBAAsB,CACpC,UAA8B,EAC9B,QAAgB,EAChB,EAAE,OAAO,EAAgB;IAEzB,OAAO,SAAS,gBAAgB,CAAC,EAAE,QAAQ,EAA2B;QACpE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CAAC;QACpD,IAAI,SAAS;YAAE,OAAO,4BAAG,OAAO,GAAI,CAAC;QACrC,IAAI,eAAe;YAAE,OAAO,KAAC,QAAQ,IAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,SAAG,CAAC;QAC/D,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACzB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAM,KAAyB,EAAE,EAAE,OAAO,EAAgB;IAC5F,OAAO,SAAS,cAAc,CAC5B,KAA2B,EAC3B,QAAuC,EACvC,OAAsC;QAEtC,OAAO,SAAS,cAAc,CAAC,EAAE,QAAQ,EAA2B;YAClE,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC;YACtB,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;gBAAE,OAAO,4BAAG,OAAO,GAAI,CAAC;YACtD,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO;gBAAE,OAAO,4BAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAI,CAAC;YACjE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,4BAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAI,CAAC;YAC7F,OAAO,4BAAG,QAAQ,GAAI,CAAC;QACzB,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gusnips/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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, chunk-reload recovery, i18n bootstrap.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/guards.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UseQueryResult } from "@tanstack/react-query";
|
|
1
2
|
import type { ReactNode } from "react";
|
|
2
3
|
import { Navigate, useLocation } from "react-router-dom";
|
|
3
4
|
|
|
@@ -25,13 +26,32 @@ export interface SessionState {
|
|
|
25
26
|
/**
|
|
26
27
|
* The answer to a "who is this?" query, in the three states it actually has.
|
|
27
28
|
*
|
|
28
|
-
* This shape is invariant 4. A guard reading `!me?.isStaff` collapses "
|
|
29
|
+
* This shape is invariant 4. A guard reading `!me?.isStaff` collapses "pending", "failed" and
|
|
29
30
|
* "no" into one branch — so an operator arriving while `/auth/me` is 500ing is told the page
|
|
30
31
|
* does not exist. Wrong cause, no retry, and no request id to quote to support. One donor hit
|
|
31
32
|
* that and fixed it; the other still has the collapsed version.
|
|
33
|
+
*
|
|
34
|
+
* `"pending"` and not `"loading"` for one measured reason: it is react-query's own word for this
|
|
35
|
+
* state since v5, and with the words matching a `UseQueryResult<TMe>` satisfies this type
|
|
36
|
+
* STRUCTURALLY — `createRequireProfile(useMe, …)` takes the hook directly, no adapter. With
|
|
37
|
+
* `"loading"` it does not typecheck and every adopter hand-writes the same six-line mapping;
|
|
38
|
+
* eight of the twelve repos this serves are on react-query, and the first migration wrote that
|
|
39
|
+
* mapping before anyone noticed. Nothing here imports react-query at runtime — the type stays
|
|
40
|
+
* structural, so an app on SWR or a plain `useState` still answers it in three lines.
|
|
32
41
|
*/
|
|
33
42
|
export type MeQuery<TMe> =
|
|
34
|
-
{ status: "
|
|
43
|
+
{ status: "pending" } | { status: "error"; error: unknown } | { status: "success"; data: TMe };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The paragraph above, pinned so it cannot quietly stop being true. A renamed state or a newly
|
|
47
|
+
* required field stops compiling HERE, which is cheaper than eight repos each finding out by
|
|
48
|
+
* writing an adapter. `import type` erases, so the built barrel never mentions react-query —
|
|
49
|
+
* `bun run exports` is what proves that, and it is why the peer stays optional.
|
|
50
|
+
*/
|
|
51
|
+
type Satisfied<T extends true> = T;
|
|
52
|
+
type _QueryResultIsAMeQuery = Satisfied<
|
|
53
|
+
UseQueryResult<{ id: string }, Error> extends MeQuery<{ id: string }> ? true : false
|
|
54
|
+
>;
|
|
35
55
|
|
|
36
56
|
export interface GuardOptions {
|
|
37
57
|
/** Drawn while the session or the profile is still resolving. */
|
|
@@ -90,7 +110,7 @@ export function createRequireProfile<TMe>(useMe: () => MeQuery<TMe>, { loading }
|
|
|
90
110
|
) {
|
|
91
111
|
return function RequireProfile({ children }: { children: ReactNode }) {
|
|
92
112
|
const query = useMe();
|
|
93
|
-
if (query.status === "
|
|
113
|
+
if (query.status === "pending") return <>{loading}</>;
|
|
94
114
|
if (query.status === "error") return <>{onError(query.error)}</>;
|
|
95
115
|
if (!allow(query.data)) return <>{typeof onDenied === "function" ? onDenied() : onDenied}</>;
|
|
96
116
|
return <>{children}</>;
|