@shivaedev/react 0.19.0 → 0.21.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +113 -1
  3. package/dist/client/authQuery.d.ts +5 -0
  4. package/dist/client/authQuery.d.ts.map +1 -0
  5. package/dist/client/authQuery.js +10 -0
  6. package/dist/client/authQuery.js.map +1 -0
  7. package/dist/client/permissionBoundary.d.ts +11 -0
  8. package/dist/client/permissionBoundary.d.ts.map +1 -0
  9. package/dist/client/permissionBoundary.js +20 -0
  10. package/dist/client/permissionBoundary.js.map +1 -0
  11. package/dist/client/resultState.d.ts.map +1 -1
  12. package/dist/client/resultState.js +4 -1
  13. package/dist/client/resultState.js.map +1 -1
  14. package/dist/client/sessionBoundary.d.ts.map +1 -1
  15. package/dist/client/sessionBoundary.js +4 -2
  16. package/dist/client/sessionBoundary.js.map +1 -1
  17. package/dist/client/useNow.d.ts +5 -0
  18. package/dist/client/useNow.d.ts.map +1 -0
  19. package/dist/client/useNow.js +20 -0
  20. package/dist/client/useNow.js.map +1 -0
  21. package/dist/client/useSignOut.d.ts +18 -0
  22. package/dist/client/useSignOut.d.ts.map +1 -0
  23. package/dist/client/useSignOut.js +26 -0
  24. package/dist/client/useSignOut.js.map +1 -0
  25. package/dist/internal/PermissionContext.d.ts +7 -0
  26. package/dist/internal/PermissionContext.d.ts.map +1 -0
  27. package/dist/internal/PermissionContext.js +3 -0
  28. package/dist/internal/PermissionContext.js.map +1 -0
  29. package/dist/internal/useLatestCallback.d.ts +2 -0
  30. package/dist/internal/useLatestCallback.d.ts.map +1 -0
  31. package/dist/internal/useLatestCallback.js +9 -0
  32. package/dist/internal/useLatestCallback.js.map +1 -0
  33. package/dist/internal/usePermissionFailure.d.ts +3 -0
  34. package/dist/internal/usePermissionFailure.d.ts.map +1 -0
  35. package/dist/internal/usePermissionFailure.js +13 -0
  36. package/dist/internal/usePermissionFailure.js.map +1 -0
  37. package/dist/permissionGuard.d.ts +22 -0
  38. package/dist/permissionGuard.d.ts.map +1 -0
  39. package/dist/permissionGuard.js +22 -0
  40. package/dist/permissionGuard.js.map +1 -0
  41. package/dist/permissions.d.ts +8 -0
  42. package/dist/permissions.d.ts.map +1 -0
  43. package/dist/permissions.js +4 -0
  44. package/dist/permissions.js.map +1 -0
  45. package/dist/signInSearch.d.ts +8 -0
  46. package/dist/signInSearch.d.ts.map +1 -0
  47. package/dist/signInSearch.js +5 -0
  48. package/dist/signInSearch.js.map +1 -0
  49. package/package.json +13 -10
  50. package/src/client/authQuery.ts +17 -0
  51. package/src/client/permissionBoundary.ts +33 -0
  52. package/src/client/resultState.ts +4 -1
  53. package/src/client/sessionBoundary.ts +4 -2
  54. package/src/client/useNow.ts +26 -0
  55. package/src/client/useSignOut.ts +33 -0
  56. package/src/internal/PermissionContext.ts +6 -0
  57. package/src/internal/useLatestCallback.ts +9 -0
  58. package/src/internal/usePermissionFailure.ts +13 -0
  59. package/src/permissionGuard.ts +39 -0
  60. package/src/permissions.ts +11 -0
  61. package/src/signInSearch.ts +9 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0 - 2026-10-28
4
+
5
+ ### Added
6
+
7
+ - `@shivaedev/contract/bind.ts` builds a bound HTTP RPC client and atom runtime from a contract and URL, with an inferred request transform for cookies or bearer tokens.
8
+ - `@shivaedev/react/client/useNow.ts` exports `useNow(interval, runtime?)` to render the runtime's Effect clock and refresh on the requested interval, including with TestClock.
9
+ - `useSignOut` coordinates explicit sign-out with TanStack Router so `permissionGuard` and `signInSearch` omit the return target during the following session refresh.
10
+ - `authQuery` turns Better Auth reads into input-keyed native query atoms, preserving typed auth errors, mapping rejected promises to `AuthActionUnavailable`, and aborting pending requests when their readers unmount.
11
+
12
+ ### Fixed
13
+
14
+ - Session boundaries deliver the latest committed `recheck` to query, action and save hooks without repeating a refused result when the callback changes identity.
15
+
16
+ ## 0.20.0 - 2026-10-27
17
+
18
+ ### Added
19
+
20
+ - Read session grants with `can` and `useCan`, guard TanStack routes with `staticData.permission` and `permissionGuard`, and render one `PermissionBoundary` fallback for refused routes and forbidden queries. Signed-out route visits redirect to the application's sign-in path with their return location.
21
+
3
22
  ## 0.19.0 - 2026-10-27
4
23
 
5
24
  ### Breaking
package/README.md CHANGED
@@ -32,6 +32,19 @@ A **session generation** is the string returned by `identify(session)`. `Session
32
32
 
33
33
  `useEditor` and `useCreate` opt into persisted input with `persistedDraft: { key }`. Choose a stable key for the record or create request, independent of its server revision. The adapters use form's browser storage and inherit ownership from `SessionBoundary`: reload and query refresh preserve edits, successful submission clears accepted input, and a committed session replacement drops tracked drafts. A suspended replacement that is canceled keeps the visible session's draft and future writes. Later edits made during a save remain persisted. Optional `storage`, `maxBytes` and `maxAgeMs` customize the provider and bounds; defaults are `localStorage`, 256 KiB of UTF-8 JSON and seven days. See [form's persisted input API](https://github.com/ShivaeDev/platform/tree/main/packages/form#persist-input-across-reloads) for direct form hooks and limits.
34
34
 
35
+ ### Read the current time
36
+
37
+ `useNow(interval, runtime?)` from `@shivaedev/react/client/useNow.ts` returns epoch milliseconds from Effect's `Clock` and re-renders at the given interval in milliseconds. Pass the component's atom runtime to use its clock:
38
+
39
+ ```ts
40
+ import { useNow } from "@shivaedev/react/client/useNow.ts";
41
+
42
+ const now = useNow(60_000, client.runtime);
43
+ const minutesAgo = Math.floor((now - savedAt) / 60_000);
44
+ ```
45
+
46
+ Without a runtime it uses a native empty atom runtime. Stories supply a runtime with their TestClock service so advancing that clock updates the rendered time.
47
+
35
48
  ### 1. Once per application: own the registry
36
49
 
37
50
  For a screen without a session boundary, wrap it in the native provider:
@@ -384,6 +397,30 @@ function SignInButton({ email, password }: { email: string; password: string })
384
397
 
385
398
  The example uses `authAction`, `useAction`, `effect/reactivity/AsyncResult` and `effect/Option`, with an application-owned `authClient`. Returned auth errors keep their fields in the typed failure channel. Rejected promises become `AuthActionUnavailable` with the original rejection in `cause`. The application chooses error text and what happens after success.
386
399
 
400
+ `authQuery` from `@shivaedev/react/client/authQuery.ts` creates an input-keyed query atom family for Better Auth reads. Render it with `useQuery`, just like a contract query atom:
401
+
402
+ ```tsx
403
+ const authSession = authQuery((disableCookieCache: boolean, signal) =>
404
+ authClient.getSession({ query: { disableCookieCache }, fetchOptions: { signal } }),
405
+ );
406
+
407
+ function Account() {
408
+ const query = useQuery(authSession(true));
409
+ const failure = AsyncResult.error(query.result);
410
+ return (
411
+ <>
412
+ {query.pending && <p>Loading…</p>}
413
+ {Option.isSome(query.data) && <p>{query.data.value?.user.name ?? "Signed out"}</p>}
414
+ {Option.isSome(failure) && <p role="alert">Could not read the session</p>}
415
+ </>
416
+ );
417
+ }
418
+ ```
419
+
420
+ The example uses the application's Better Auth client. Reads preserve returned auth errors and use the same `AuthActionUnavailable` failure as actions for rejected promises. Releasing a pending read interrupts its Effect and aborts the supplied signal.
421
+
422
+ For an explicit sign-out in a TanStack route, use `useSignOut(signOut, { signIn: "/sign-in" })` from `@shivaedev/react/client/useSignOut.ts` in place of `useAction`. It exposes the same state and a `dispatch()` handler. The hook records sign-out intent before the request and navigates to sign-in after success. `permissionGuard` and `signInSearch(location)` omit `returnTo` for that intent, including when the auth owner's session refresh reruns guards first. Use `signInSearch` for module sign-in links too; ordinary visits retain the full requested location. Applications do not need history markers or Better Auth completion timing hooks.
423
+
387
424
  ### Commands with a pending display value
388
425
 
389
426
  Use `usePendingCommand` for a command acting on a query row, such as a list toggle. Give each row a stable action atom with the existing bound command, so the command's declared invalidation keys still drive refetches:
@@ -704,6 +741,81 @@ const query = Atom.swr(atom, {
704
741
 
705
742
  `atom` is the underlying native query. `Atom.swr` owns the staleness decision; the signal only reports that the application resumed.
706
743
 
744
+ ### Session permissions and guarded routes
745
+
746
+ Permissions come from the session payload's `user.permissions`, populated by `@shivaedev/access` through the server's role policy. `can(session, permission)` reads exact name and strength grants without making a request. `useCan()` supplies the same check from the nearest `PermissionBoundary` for component controls. A signed-out session has no grants. These checks control visibility; the server still authorizes every request.
747
+
748
+ Declare a route's requirement in TanStack's native `staticData.permission`. Install `permissionGuard` once as the root route's `beforeLoad`; it checks the matched routes before their loaders run. Signed-out visitors go to the app's `signIn` path with the full requested location in the `returnTo` search field. A signed-in session without a required grant throws the shared `Forbidden` failure.
749
+
750
+ ```tsx
751
+ import { RegistryContext } from "@effect/atom-react";
752
+ import { createRootRouteWithContext, createRoute, createRouter, RouterProvider } from "@tanstack/react-router";
753
+ import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
754
+ import { permission } from "@shivaedev/access/permission.ts";
755
+ import { PermissionBoundary, useCan } from "@shivaedev/react/client/permissionBoundary.ts";
756
+ import { useLoaderQuery } from "@shivaedev/react/client/useLoaderQuery.ts";
757
+ import { permissionGuard } from "@shivaedev/react/permissionGuard.ts";
758
+ import { can, type PermissionSession } from "@shivaedev/react/permissions.ts";
759
+ import { queryRoute } from "@shivaedev/react/queryRoute.ts";
760
+ import { api, registry } from "./notes-client.ts";
761
+ import { auth, SignIn } from "./auth.tsx";
762
+
763
+ const ReadNotes = permission("notes.read");
764
+ const root = createRootRouteWithContext<{ registry: AtomRegistry.AtomRegistry }>()({
765
+ beforeLoad: permissionGuard({ session: () => auth.session, signIn: "/login" }),
766
+ });
767
+ const note = createRoute({
768
+ getParentRoute: () => root,
769
+ path: "/notes/$id",
770
+ staticData: { permission: ReadNotes },
771
+ ...queryRoute(({ params }: { params: { id: string }; search: Record<string, never> }) =>
772
+ api.get.query({ id: params.id, page: 1 }),
773
+ ),
774
+ component: () => {
775
+ const query = useLoaderQuery(api.get.query({ ...note.useParams(), page: 1 }), note.useLoaderData());
776
+ return <h1>{query.data.title}</h1>;
777
+ },
778
+ });
779
+ const login = createRoute({
780
+ getParentRoute: () => root,
781
+ path: "/login",
782
+ validateSearch: (search: Record<string, unknown>) => ({
783
+ returnTo: typeof search.returnTo === "string" ? search.returnTo : "/",
784
+ }),
785
+ component: () => {
786
+ const { returnTo } = login.useSearch();
787
+ return <SignIn onSignedIn={() => router.navigate({ href: returnTo })} />;
788
+ },
789
+ });
790
+ const router = createRouter({
791
+ context: { registry },
792
+ routeTree: root.addChildren([note, login]),
793
+ disableGlobalCatchBoundary: true,
794
+ });
795
+
796
+ function Controls() {
797
+ const can = useCan();
798
+ return can(ReadNotes) ? <button type="button">Read notes</button> : null;
799
+ }
800
+
801
+ export function App({ session }: { readonly session: PermissionSession | undefined }) {
802
+ return (
803
+ <PermissionBoundary session={session} fallback={<h1>Permission denied</h1>}>
804
+ <RegistryContext.Provider value={registry}>
805
+ <Controls />
806
+ <RouterProvider router={router} />
807
+ </RegistryContext.Provider>
808
+ </PermissionBoundary>
809
+ );
810
+ }
811
+
812
+ const showNotes = can(auth.session, ReadNotes);
813
+ ```
814
+
815
+ `auth` owns the current session and passes it to `App`; `SignIn` updates that session before invoking `onSignedIn`. Use the session's registry for router context and React, as with other query routes. Importing `permissionGuard` augments TanStack's `StaticDataRouteOption`; route options and their inference remain native.
816
+
817
+ `PermissionBoundary` renders the app's one fallback at the denied URL for a refused route, a loader's `Forbidden`, or a `useQuery`/`useLoaderQuery` result tagged `Forbidden`. Other errors propagate to an enclosing error boundary. Set TanStack's `disableGlobalCatchBoundary: true` and leave permission failures to this boundary; a route-specific `errorComponent` that consumes an error owns its rendering instead. Without a permission boundary, query failures keep their normal typed state. Like a React error boundary, remount it to clear a caught failure.
818
+
707
819
  ### API
708
820
 
709
821
  `useQuery(atom)` and `useAction(atom)` return these shared fields:
@@ -741,7 +853,7 @@ import { act, createElement } from "react";
741
853
  import { expect } from "vitest";
742
854
  import { bind } from "@shivaedev/contract/bind.ts";
743
855
  import { contract } from "@shivaedev/contract/contract.ts";
744
- import { collection } from "@shivaedev/contract/keys.ts";
856
+ import { collection } from "@shivaedev/changes/keys.ts";
745
857
  import { command, query } from "@shivaedev/contract/operation.ts";
746
858
  import { useAction, useQuery } from "@shivaedev/react/client/resultState.ts";
747
859
  import { eventually, expectNoReactWarnings, render } from "@shivaedev/react/client/testing.ts";
@@ -0,0 +1,5 @@
1
+ import type * as AsyncResult from "effect/reactivity/AsyncResult";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import { type AuthActionResult, AuthActionUnavailable } from "./authAction.ts";
4
+ export declare function authQuery<TInput, A, E extends object = never>(run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>): (input: TInput) => Atom.Atom<AsyncResult.AsyncResult<A, E | AuthActionUnavailable>>;
5
+ //# sourceMappingURL=authQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authQuery.d.ts","sourceRoot":"","sources":["../../src/client/authQuery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,KAAK,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,wBAAgB,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC5D,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1E,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAAC,CASrF"}
@@ -0,0 +1,10 @@
1
+ import { Effect } from "effect";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import { AuthActionUnavailable } from "./authAction.js";
4
+ export function authQuery(run) {
5
+ return Atom.family((input) => Atom.make(Effect.tryPromise({
6
+ catch: (cause) => new AuthActionUnavailable({ cause }),
7
+ try: (signal) => run(input, signal),
8
+ }).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error))))));
9
+ }
10
+ //# sourceMappingURL=authQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authQuery.js","sourceRoot":"","sources":["../../src/client/authQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAyB,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,MAAM,UAAU,SAAS,CACxB,GAA4E;IAE5E,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,KAAa,EAAE,EAAE,CACpC,IAAI,CAAC,IAAI,CACR,MAAM,CAAC,UAAU,CAAC;QACjB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,qBAAqB,CAAC,EAAE,KAAK,EAAE,CAAC;QACtD,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC;KACnC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACtH,CACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Permission } from "@shivaedev/access/permission.ts";
3
+ import { type PermissionSession } from "#permissions.ts";
4
+ export interface PermissionBoundaryProps {
5
+ readonly children: ReactNode;
6
+ readonly fallback: ReactNode;
7
+ readonly session: PermissionSession | null | undefined;
8
+ }
9
+ export declare function useCan(): (permission: Permission) => boolean;
10
+ export declare function PermissionBoundary({ children, fallback, session }: PermissionBoundaryProps): ReactNode;
11
+ //# sourceMappingURL=permissionBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/permissionBoundary.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAO,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,CAAC;CACvD;AAED,wBAAgB,MAAM,IAAI,CAAC,UAAU,EAAE,UAAU,KAAK,OAAO,CAG5D;AAUD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,uBAAuB,GAAG,SAAS,CAMtG"}
@@ -0,0 +1,20 @@
1
+ import { CatchBoundary } from "@tanstack/react-router";
2
+ import { Predicate } from "effect";
3
+ import { createElement, useContext } from "react";
4
+ import { PermissionContext } from "#internal/PermissionContext.ts";
5
+ import { can } from "#permissions.ts";
6
+ export function useCan() {
7
+ const context = useContext(PermissionContext);
8
+ return (permission) => can(context?.session, permission);
9
+ }
10
+ function PermissionFailure({ error }) {
11
+ const context = useContext(PermissionContext);
12
+ if (!Predicate.isTagged(error, "Forbidden")) {
13
+ throw error;
14
+ }
15
+ return context?.fallback;
16
+ }
17
+ export function PermissionBoundary({ children, fallback, session }) {
18
+ return createElement(PermissionContext.Provider, { value: { fallback, session } }, createElement(CatchBoundary, { children, errorComponent: PermissionFailure, getResetKey: () => "permission" }));
19
+ }
20
+ //# sourceMappingURL=permissionBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionBoundary.js","sourceRoot":"","sources":["../../src/client/permissionBoundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,aAAa,EAAkB,UAAU,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,GAAG,EAA0B,MAAM,iBAAiB,CAAC;AAQ9D,MAAM,UAAU,MAAM;IACrB,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC9C,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,KAAK,EAA+B;IAChE,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC9C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,CAAC;IACb,CAAC;IACD,OAAO,OAAO,EAAE,QAAQ,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAA2B;IAC1F,OAAO,aAAa,CACnB,iBAAiB,CAAC,QAAQ,EAC1B,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAChC,aAAa,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,CAC9G,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"resultState.d.ts","sourceRoot":"","sources":["../../src/client/resultState.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resultState.d.ts","sourceRoot":"","sources":["../../src/client/resultState.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
4
4
  import { useContext, useEffect, useState } from "react";
5
5
  import { queryCoalesceWindow } from "@shivaedev/contract/queryCoalesceWindow.ts";
6
6
  import { acceptLoaderQuery } from "#internal/queryHandoff.ts";
7
+ import { usePermissionFailure } from "#internal/usePermissionFailure.ts";
7
8
  import { useQueryNotice } from "#internal/useQueryNotice.ts";
8
9
  import { useActionSource } from "./actionCompletion.js";
9
10
  import { useSessionRecheck } from "./sessionBoundary.js";
@@ -51,7 +52,9 @@ export function useQuery(atom, options = {}) {
51
52
  }
52
53
  useRecheckOnUnauthorized(result);
53
54
  useQueryNotice(result, Option.isSome(data), options.notice);
54
- return { ...state(result, data), refresh: useAtomRefresh(atom) };
55
+ const refresh = useAtomRefresh(atom);
56
+ usePermissionFailure(AsyncResult.cause(result));
57
+ return { ...state(result, data), refresh };
55
58
  }
56
59
  export function useAction(source, run, options = {}) {
57
60
  const atom = useActionSource(source, run, options);
@@ -1 +1 @@
1
- {"version":3,"file":"resultState.js","sourceRoot":"","sources":["../../src/client/resultState.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resultState.js","sourceRoot":"","sources":["../../src/client/resultState.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAKhH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,YAAY,KAAK,OAAO,CAAC;IACtF,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAoED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAUrD"}
1
+ {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAMhH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,YAAY,KAAK,OAAO,CAAC;IACtF,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAqED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAUrD"}
@@ -3,6 +3,7 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import { createContext, createElement, Fragment, useContext, useEffect, useState } from "react";
4
4
  import { DraftSessionContext } from "@shivaedev/form/client/react.ts";
5
5
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
6
+ import { useLatestCallback } from "#internal/useLatestCallback.ts";
6
7
  import { mountLive } from "./mountLive.js";
7
8
  import { useDraftOwner } from "./useDraftOwner.js";
8
9
  const RecheckContext = createContext(() => undefined);
@@ -40,9 +41,10 @@ function hidden(connect) {
40
41
  return owned;
41
42
  }
42
43
  function Generation({ session, connect, recheck, children, liveCoalesceWindow }) {
44
+ const recheckLatest = useLatestCallback(recheck);
43
45
  const [open] = useState(() => (registry) => connect(session, registry));
44
46
  const [owned, own] = useState(() => owning(open));
45
- useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
47
+ useEffect(() => registerSessionRecheck(owned.registry, recheckLatest), [owned, recheckLatest]);
46
48
  useEffect(() => {
47
49
  owned.mounted += 1;
48
50
  const stopLive = mountLive(owned.registry, owned.client, liveCoalesceWindow);
@@ -58,7 +60,7 @@ function Generation({ session, connect, recheck, children, liveCoalesceWindow })
58
60
  });
59
61
  };
60
62
  }, [owned, liveCoalesceWindow, open]);
61
- return createElement(RecheckContext.Provider, { value: recheck }, createElement(RegistryContext.Provider, { value: owned.registry }, children(owned.client)));
63
+ return createElement(RecheckContext.Provider, { value: recheckLatest }, createElement(RegistryContext.Provider, { value: owned.registry }, children(owned.client)));
62
64
  }
63
65
  export function SessionBoundary({ session, liveCoalesceWindow, identify, connect, recheck, signedOut, children, }) {
64
66
  const id = session === undefined ? undefined : identify(session);
@@ -1 +1 @@
1
- {"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import * as Atom from "effect/reactivity/Atom";
2
+ type ClockRuntime = Pick<Atom.AtomRuntime<never, unknown>, "atom">;
3
+ export declare function useNow(interval: number, runtime?: ClockRuntime): number;
4
+ export {};
5
+ //# sourceMappingURL=useNow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNow.d.ts","sourceRoot":"","sources":["../../src/client/useNow.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAI/C,KAAK,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAgBnE,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAA6B,GAAG,MAAM,CAEvF"}
@@ -0,0 +1,20 @@
1
+ import { useAtomSuspense } from "@effect/atom-react";
2
+ import { Clock, Effect, Layer } from "effect";
3
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
4
+ import * as Atom from "effect/reactivity/Atom";
5
+ const defaultRuntime = Atom.runtime(Layer.empty);
6
+ function ticking(runtime, interval) {
7
+ return runtime.atom((get) => Effect.gen(function* () {
8
+ const update = Effect.gen(function* () {
9
+ const time = yield* Clock.currentTimeMillis;
10
+ get.setSelf(AsyncResult.success(time));
11
+ });
12
+ yield* Effect.forkScoped(Effect.forever(Effect.sleep(interval).pipe(Effect.andThen(update))));
13
+ return yield* Clock.currentTimeMillis;
14
+ }));
15
+ }
16
+ const nowAtom = Atom.family((runtime) => Atom.family((interval) => ticking(runtime, interval)));
17
+ export function useNow(interval, runtime = defaultRuntime) {
18
+ return useAtomSuspense(nowAtom(runtime)(interval)).value;
19
+ }
20
+ //# sourceMappingURL=useNow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNow.js","sourceRoot":"","sources":["../../src/client/useNow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAIjD,SAAS,OAAO,CAAO,OAA6C,EAAE,QAAgB;IACrF,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAC3B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAC5C,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9F,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;IACvC,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AACD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,OAAqB,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAgB,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAEtH,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,OAAO,GAAiB,cAAc;IAC9E,OAAO,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,18 @@
1
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
2
+ import type * as Atom from "effect/reactivity/Atom";
3
+ declare module "@tanstack/react-router" {
4
+ interface HistoryState {
5
+ readonly __platformSignOut?: boolean;
6
+ }
7
+ }
8
+ export declare function useSignOut<A, E>(action: Atom.AtomResultFn<void, A, E>, options: {
9
+ readonly signIn: string;
10
+ }): {
11
+ cause: import("effect/Option").Option<import("effect/Cause").Cause<E>>;
12
+ data: import("effect/Option").Option<A>;
13
+ pending: boolean;
14
+ refreshing: boolean;
15
+ result: AsyncResult.AsyncResult<A, E>;
16
+ dispatch: () => void;
17
+ };
18
+ //# sourceMappingURL=useSignOut.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSignOut.d.ts","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAIpD,OAAO,QAAQ,wBAAwB,CAAC;IACvC,UAAU,YAAY;QACrB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;KACrC;CACD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;;;EAoB3G"}
@@ -0,0 +1,26 @@
1
+ import { useRouter } from "@tanstack/react-router";
2
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
3
+ import { useEffect } from "react";
4
+ import { useAction } from "./resultState.js";
5
+ export function useSignOut(action, options) {
6
+ const router = useRouter({ warn: false });
7
+ const state = useAction(action);
8
+ useEffect(() => {
9
+ if (router && AsyncResult.isSuccess(state.result) && !state.pending) {
10
+ void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
11
+ }
12
+ if (router && AsyncResult.isFailure(state.result) && !state.pending) {
13
+ router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: false });
14
+ }
15
+ }, [router, state.result, state.pending, options.signIn]);
16
+ return {
17
+ ...state,
18
+ dispatch: () => {
19
+ if (router) {
20
+ router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: true });
21
+ }
22
+ state.dispatch();
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=useSignOut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSignOut.js","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,MAAM,UAAU,UAAU,CAAO,MAAqC,EAAE,OAAoC;IAC3G,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;QACtH,CAAC;IACF,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,OAAO;QACN,GAAG,KAAK;QACR,QAAQ,EAAE,GAAG,EAAE;YACd,IAAI,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;YACrH,CAAC;YACD,KAAK,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from "react";
2
+ import type { PermissionSession } from "#permissions.ts";
3
+ export declare const PermissionContext: import("react").Context<{
4
+ readonly fallback: ReactNode;
5
+ readonly session: PermissionSession | null | undefined;
6
+ } | undefined>;
7
+ //# sourceMappingURL=PermissionContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionContext.d.ts","sourceRoot":"","sources":["../../src/internal/PermissionContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,eAAO,MAAM,iBAAiB;uBAAsC,SAAS;sBAAoB,iBAAiB,GAAG,IAAI,GAAG,SAAS;cAEpI,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+ export const PermissionContext = createContext(undefined);
3
+ //# sourceMappingURL=PermissionContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionContext.js","sourceRoot":"","sources":["../../src/internal/PermissionContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,OAAO,CAAC;AAGtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAC7C,SAAS,CACT,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function useLatestCallback(callback: () => void): () => void;
2
+ //# sourceMappingURL=useLatestCallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLatestCallback.d.ts","sourceRoot":"","sources":["../../src/internal/useLatestCallback.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAMlE"}
@@ -0,0 +1,9 @@
1
+ import { useCallback, useLayoutEffect, useRef } from "react";
2
+ export function useLatestCallback(callback) {
3
+ const latestRef = useRef(callback);
4
+ useLayoutEffect(() => {
5
+ latestRef.current = callback;
6
+ }, [callback]);
7
+ return useCallback(() => latestRef.current(), []);
8
+ }
9
+ //# sourceMappingURL=useLatestCallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLatestCallback.js","sourceRoot":"","sources":["../../src/internal/useLatestCallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE7D,MAAM,UAAU,iBAAiB,CAAC,QAAoB;IACrD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnC,eAAe,CAAC,GAAG,EAAE;QACpB,SAAS,CAAC,OAAO,GAAG,QAAQ,CAAC;IAC9B,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACf,OAAO,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Cause, Option } from "effect";
2
+ export declare function usePermissionFailure<E>(cause: Option.Option<Cause.Cause<E>>): void;
3
+ //# sourceMappingURL=usePermissionFailure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePermissionFailure.d.ts","sourceRoot":"","sources":["../../src/internal/usePermissionFailure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAC;AAIlD,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAQlF"}
@@ -0,0 +1,13 @@
1
+ import { Cause, Option, Predicate } from "effect";
2
+ import { useContext } from "react";
3
+ import { PermissionContext } from "./PermissionContext.js";
4
+ export function usePermissionFailure(cause) {
5
+ const context = useContext(PermissionContext);
6
+ if (context !== undefined) {
7
+ const failure = Option.flatMap(cause, Cause.findErrorOption);
8
+ if (Option.isSome(failure) && Predicate.isTagged(failure.value, "Forbidden")) {
9
+ throw failure.value;
10
+ }
11
+ }
12
+ }
13
+ //# sourceMappingURL=usePermissionFailure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePermissionFailure.js","sourceRoot":"","sources":["../../src/internal/usePermissionFailure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAI,KAAoC;IAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC9C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;YAC9E,MAAM,OAAO,CAAC,KAAK,CAAC;QACrB,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Permission } from "@shivaedev/access/permission.ts";
2
+ import { type PermissionSession } from "./permissions.ts";
3
+ import { type SignInLocation } from "./signInSearch.ts";
4
+ declare module "@tanstack/react-router" {
5
+ interface StaticDataRouteOption {
6
+ readonly permission?: Permission;
7
+ }
8
+ }
9
+ export interface PermissionGuardOptions {
10
+ readonly session: () => PermissionSession | null | undefined;
11
+ readonly signIn: string;
12
+ }
13
+ export interface PermissionGuardContext {
14
+ readonly location: SignInLocation;
15
+ readonly matches: readonly {
16
+ readonly staticData: {
17
+ readonly permission?: Permission;
18
+ };
19
+ }[];
20
+ }
21
+ export declare function permissionGuard({ session, signIn }: PermissionGuardOptions): (context: PermissionGuardContext) => void;
22
+ //# sourceMappingURL=permissionGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionGuard.d.ts","sourceRoot":"","sources":["../src/permissionGuard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAO,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,QAAQ,wBAAwB,CAAC;IACvC,UAAU,qBAAqB;QAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;KACjC;CACD;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,iBAAiB,GAAG,IAAI,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,UAAU,EAAE;YAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;SAAE,CAAA;KAAE,EAAE,CAAC;CAC3F;AAED,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,sBAAsB,GAAG,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAgBtH"}
@@ -0,0 +1,22 @@
1
+ import { redirect } from "@tanstack/react-router";
2
+ import { Forbidden } from "@shivaedev/errors/taxonomy.ts";
3
+ import { can } from "./permissions.js";
4
+ import { signInSearch } from "./signInSearch.js";
5
+ export function permissionGuard({ session, signIn }) {
6
+ return ({ matches, location }) => {
7
+ const required = matches.flatMap((match) => (match.staticData.permission === undefined ? [] : [match.staticData.permission]));
8
+ if (required.length === 0) {
9
+ return;
10
+ }
11
+ const current = session();
12
+ if (current === null || current === undefined) {
13
+ throw redirect({ search: signInSearch(location), to: signIn });
14
+ }
15
+ for (const permission of required) {
16
+ if (!can(current, permission)) {
17
+ throw new Forbidden({ message: `Permission required: ${permission.name} (${permission.strength})` });
18
+ }
19
+ }
20
+ };
21
+ }
22
+ //# sourceMappingURL=permissionGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionGuard.js","sourceRoot":"","sources":["../src/permissionGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,GAAG,EAA0B,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAuB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAkBtE,MAAM,UAAU,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,EAA0B;IAC1E,OAAO,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9H,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;QACR,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;QAC1B,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/C,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,KAAK,MAAM,UAAU,IAAI,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,wBAAwB,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;YACtG,CAAC;QACF,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Permission } from "@shivaedev/access/permission.ts";
2
+ export interface PermissionSession {
3
+ readonly user: {
4
+ readonly permissions?: readonly Permission[];
5
+ };
6
+ }
7
+ export declare function can(session: PermissionSession | null | undefined, permission: Permission): boolean;
8
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE;QACd,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;KAC7C,CAAC;CACF;AAED,wBAAgB,GAAG,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAElG"}
@@ -0,0 +1,4 @@
1
+ export function can(session, permission) {
2
+ return session?.user.permissions?.some((grant) => grant.name === permission.name && grant.strength === permission.strength) ?? false;
3
+ }
4
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,GAAG,CAAC,OAA6C,EAAE,UAAsB;IACxF,OAAO,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;AACtI,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface SignInLocation {
2
+ readonly href: string;
3
+ readonly state?: object | undefined;
4
+ }
5
+ export declare function signInSearch(location: SignInLocation): {
6
+ readonly returnTo?: string;
7
+ };
8
+ //# sourceMappingURL=signInSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signInSearch.d.ts","sourceRoot":"","sources":["../src/signInSearch.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAGrF"}
@@ -0,0 +1,5 @@
1
+ export function signInSearch(location) {
2
+ const state = location.state;
3
+ return state !== undefined && "__platformSignOut" in state && state.__platformSignOut === true ? {} : { returnTo: location.href };
4
+ }
5
+ //# sourceMappingURL=signInSearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signInSearch.js","sourceRoot":"","sources":["../src/signInSearch.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,YAAY,CAAC,QAAwB;IACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,OAAO,KAAK,KAAK,SAAS,IAAI,mBAAmB,IAAI,KAAK,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AACnI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/react",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "React query, action, editor and session hooks for native Effect atoms",
5
5
  "keywords": [
6
6
  "effect"
@@ -46,7 +46,9 @@
46
46
  "README.md"
47
47
  ],
48
48
  "dependencies": {
49
- "@shivaedev/contract": "0.17.0",
49
+ "@shivaedev/access": "0.1.1",
50
+ "@shivaedev/contract": "0.19.0",
51
+ "@shivaedev/errors": "0.1.0",
50
52
  "@shivaedev/form": "0.7.0",
51
53
  "@shivaedev/test": "0.10.0"
52
54
  },
@@ -55,21 +57,22 @@
55
57
  "@effect/platform-node": "4.0.5",
56
58
  "@effect/sql-pg": "4.0.5",
57
59
  "@effect/sql-sqlite-node": "4.0.5",
58
- "@shivaedev/identity": "0.1.0",
59
- "@shivaedev/jobs": "0.20.0",
60
+ "@shivaedev/changes": "0.5.0",
61
+ "@shivaedev/identity": "0.3.0",
62
+ "@shivaedev/jobs": "0.22.0",
60
63
  "@shivaedev/local-postgres": "0.7.0",
61
- "@shivaedev/quality": "0.42.0",
62
- "@shivaedev/server": "0.1.0",
64
+ "@shivaedev/quality": "0.43.0",
65
+ "@shivaedev/server": "0.3.0",
63
66
  "@shivaedev/service": "0.5.0",
64
- "@shivaedev/sql": "0.22.0",
65
- "@shivaedev/test-sql": "0.10.0",
66
- "@shivaedev/test-story": "0.21.0",
67
+ "@shivaedev/sql": "0.24.0",
68
+ "@shivaedev/test-sql": "0.10.2",
69
+ "@shivaedev/test-story": "0.22.0",
67
70
  "@tanstack/react-router": "1.170.45",
68
71
  "@types/node": "24.19.2",
69
72
  "@types/react": "19.3.0",
70
73
  "@types/react-dom": "19.3.0",
71
74
  "effect": "4.0.5",
72
- "happy-dom": "21.0.3",
75
+ "happy-dom": "21.0.4",
73
76
  "react": "19.3.1",
74
77
  "react-dom": "19.3.1",
75
78
  "typescript": "7.0.2",
@@ -0,0 +1,17 @@
1
+ import { Effect } from "effect";
2
+ import type * as AsyncResult from "effect/reactivity/AsyncResult";
3
+ import * as Atom from "effect/reactivity/Atom";
4
+ import { type AuthActionResult, AuthActionUnavailable } from "./authAction.ts";
5
+
6
+ export function authQuery<TInput, A, E extends object = never>(
7
+ run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>,
8
+ ): (input: TInput) => Atom.Atom<AsyncResult.AsyncResult<A, E | AuthActionUnavailable>> {
9
+ return Atom.family((input: TInput) =>
10
+ Atom.make(
11
+ Effect.tryPromise({
12
+ catch: (cause) => new AuthActionUnavailable({ cause }),
13
+ try: (signal) => run(input, signal),
14
+ }).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error)))),
15
+ ),
16
+ );
17
+ }
@@ -0,0 +1,33 @@
1
+ import { CatchBoundary } from "@tanstack/react-router";
2
+ import { Predicate } from "effect";
3
+ import { createElement, type ReactNode, useContext } from "react";
4
+ import type { Permission } from "@shivaedev/access/permission.ts";
5
+ import { PermissionContext } from "#internal/PermissionContext.ts";
6
+ import { can, type PermissionSession } from "#permissions.ts";
7
+
8
+ export interface PermissionBoundaryProps {
9
+ readonly children: ReactNode;
10
+ readonly fallback: ReactNode;
11
+ readonly session: PermissionSession | null | undefined;
12
+ }
13
+
14
+ export function useCan(): (permission: Permission) => boolean {
15
+ const context = useContext(PermissionContext);
16
+ return (permission) => can(context?.session, permission);
17
+ }
18
+
19
+ function PermissionFailure({ error }: { readonly error: unknown }): ReactNode {
20
+ const context = useContext(PermissionContext);
21
+ if (!Predicate.isTagged(error, "Forbidden")) {
22
+ throw error;
23
+ }
24
+ return context?.fallback;
25
+ }
26
+
27
+ export function PermissionBoundary({ children, fallback, session }: PermissionBoundaryProps): ReactNode {
28
+ return createElement(
29
+ PermissionContext.Provider,
30
+ { value: { fallback, session } },
31
+ createElement(CatchBoundary, { children, errorComponent: PermissionFailure, getResetKey: () => "permission" }),
32
+ );
33
+ }
@@ -7,6 +7,7 @@ import { useContext, useEffect, useState } from "react";
7
7
  import { queryCoalesceWindow } from "@shivaedev/contract/queryCoalesceWindow.ts";
8
8
  import type { AtomServices } from "#fieldRejection.ts";
9
9
  import { acceptLoaderQuery } from "#internal/queryHandoff.ts";
10
+ import { usePermissionFailure } from "#internal/usePermissionFailure.ts";
10
11
  import { useQueryNotice } from "#internal/useQueryNotice.ts";
11
12
  import type { NoticeDescriptor } from "#notices.ts";
12
13
  import { type ActionOptions, useActionSource } from "./actionCompletion.ts";
@@ -79,7 +80,9 @@ export function useQuery<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>, o
79
80
  }
80
81
  useRecheckOnUnauthorized(result);
81
82
  useQueryNotice(result, Option.isSome(data), options.notice);
82
- return { ...state(result, data), refresh: useAtomRefresh(atom) };
83
+ const refresh = useAtomRefresh(atom);
84
+ usePermissionFailure(AsyncResult.cause(result));
85
+ return { ...state(result, data), refresh };
83
86
  }
84
87
  export interface ActionState<TInput, A, E> extends ResultState<A, E> {
85
88
  readonly dispatch: (input: TInput) => void;
@@ -4,6 +4,7 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import { createContext, createElement, Fragment, type ReactNode, useContext, useEffect, useState } from "react";
5
5
  import { DraftSessionContext } from "@shivaedev/form/client/react.ts";
6
6
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
7
+ import { useLatestCallback } from "#internal/useLatestCallback.ts";
7
8
  import { mountLive } from "./mountLive.ts";
8
9
  import { useDraftOwner } from "./useDraftOwner.ts";
9
10
  export interface SessionBoundaryProps<TSession, TClient> {
@@ -62,9 +63,10 @@ interface GenerationProps<TSession, TClient> {
62
63
  readonly session: TSession;
63
64
  }
64
65
  function Generation<TSession, TClient>({ session, connect, recheck, children, liveCoalesceWindow }: GenerationProps<TSession, TClient>): ReactNode {
66
+ const recheckLatest = useLatestCallback(recheck);
65
67
  const [open] = useState(() => (registry: AtomRegistry.AtomRegistry) => connect(session, registry));
66
68
  const [owned, own] = useState(() => owning(open));
67
- useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
69
+ useEffect(() => registerSessionRecheck(owned.registry, recheckLatest), [owned, recheckLatest]);
68
70
  useEffect(() => {
69
71
  owned.mounted += 1;
70
72
  const stopLive = mountLive(owned.registry, owned.client, liveCoalesceWindow);
@@ -82,7 +84,7 @@ function Generation<TSession, TClient>({ session, connect, recheck, children, li
82
84
  }, [owned, liveCoalesceWindow, open]);
83
85
  return createElement(
84
86
  RecheckContext.Provider,
85
- { value: recheck },
87
+ { value: recheckLatest },
86
88
  createElement(RegistryContext.Provider, { value: owned.registry }, children(owned.client)),
87
89
  );
88
90
  }
@@ -0,0 +1,26 @@
1
+ import { useAtomSuspense } from "@effect/atom-react";
2
+ import { Clock, Effect, Layer } from "effect";
3
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
4
+ import * as Atom from "effect/reactivity/Atom";
5
+
6
+ const defaultRuntime = Atom.runtime(Layer.empty);
7
+
8
+ type ClockRuntime = Pick<Atom.AtomRuntime<never, unknown>, "atom">;
9
+
10
+ function ticking<R, E>(runtime: Pick<Atom.AtomRuntime<R, E>, "atom">, interval: number) {
11
+ return runtime.atom((get) =>
12
+ Effect.gen(function* () {
13
+ const update = Effect.gen(function* () {
14
+ const time = yield* Clock.currentTimeMillis;
15
+ get.setSelf(AsyncResult.success(time));
16
+ });
17
+ yield* Effect.forkScoped(Effect.forever(Effect.sleep(interval).pipe(Effect.andThen(update))));
18
+ return yield* Clock.currentTimeMillis;
19
+ }),
20
+ );
21
+ }
22
+ const nowAtom = Atom.family((runtime: ClockRuntime) => Atom.family((interval: number) => ticking(runtime, interval)));
23
+
24
+ export function useNow(interval: number, runtime: ClockRuntime = defaultRuntime): number {
25
+ return useAtomSuspense(nowAtom(runtime)(interval)).value;
26
+ }
@@ -0,0 +1,33 @@
1
+ import { useRouter } from "@tanstack/react-router";
2
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
3
+ import type * as Atom from "effect/reactivity/Atom";
4
+ import { useEffect } from "react";
5
+ import { useAction } from "./resultState.ts";
6
+
7
+ declare module "@tanstack/react-router" {
8
+ interface HistoryState {
9
+ readonly __platformSignOut?: boolean;
10
+ }
11
+ }
12
+
13
+ export function useSignOut<A, E>(action: Atom.AtomResultFn<void, A, E>, options: { readonly signIn: string }) {
14
+ const router = useRouter({ warn: false });
15
+ const state = useAction(action);
16
+ useEffect(() => {
17
+ if (router && AsyncResult.isSuccess(state.result) && !state.pending) {
18
+ void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
19
+ }
20
+ if (router && AsyncResult.isFailure(state.result) && !state.pending) {
21
+ router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: false });
22
+ }
23
+ }, [router, state.result, state.pending, options.signIn]);
24
+ return {
25
+ ...state,
26
+ dispatch: () => {
27
+ if (router) {
28
+ router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: true });
29
+ }
30
+ state.dispatch();
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,6 @@
1
+ import { createContext, type ReactNode } from "react";
2
+ import type { PermissionSession } from "#permissions.ts";
3
+
4
+ export const PermissionContext = createContext<{ readonly fallback: ReactNode; readonly session: PermissionSession | null | undefined } | undefined>(
5
+ undefined,
6
+ );
@@ -0,0 +1,9 @@
1
+ import { useCallback, useLayoutEffect, useRef } from "react";
2
+
3
+ export function useLatestCallback(callback: () => void): () => void {
4
+ const latestRef = useRef(callback);
5
+ useLayoutEffect(() => {
6
+ latestRef.current = callback;
7
+ }, [callback]);
8
+ return useCallback(() => latestRef.current(), []);
9
+ }
@@ -0,0 +1,13 @@
1
+ import { Cause, Option, Predicate } from "effect";
2
+ import { useContext } from "react";
3
+ import { PermissionContext } from "./PermissionContext.ts";
4
+
5
+ export function usePermissionFailure<E>(cause: Option.Option<Cause.Cause<E>>): void {
6
+ const context = useContext(PermissionContext);
7
+ if (context !== undefined) {
8
+ const failure = Option.flatMap(cause, Cause.findErrorOption);
9
+ if (Option.isSome(failure) && Predicate.isTagged(failure.value, "Forbidden")) {
10
+ throw failure.value;
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,39 @@
1
+ import { redirect } from "@tanstack/react-router";
2
+ import type { Permission } from "@shivaedev/access/permission.ts";
3
+ import { Forbidden } from "@shivaedev/errors/taxonomy.ts";
4
+ import { can, type PermissionSession } from "./permissions.ts";
5
+ import { type SignInLocation, signInSearch } from "./signInSearch.ts";
6
+
7
+ declare module "@tanstack/react-router" {
8
+ interface StaticDataRouteOption {
9
+ readonly permission?: Permission;
10
+ }
11
+ }
12
+
13
+ export interface PermissionGuardOptions {
14
+ readonly session: () => PermissionSession | null | undefined;
15
+ readonly signIn: string;
16
+ }
17
+
18
+ export interface PermissionGuardContext {
19
+ readonly location: SignInLocation;
20
+ readonly matches: readonly { readonly staticData: { readonly permission?: Permission } }[];
21
+ }
22
+
23
+ export function permissionGuard({ session, signIn }: PermissionGuardOptions): (context: PermissionGuardContext) => void {
24
+ return ({ matches, location }) => {
25
+ const required = matches.flatMap((match) => (match.staticData.permission === undefined ? [] : [match.staticData.permission]));
26
+ if (required.length === 0) {
27
+ return;
28
+ }
29
+ const current = session();
30
+ if (current === null || current === undefined) {
31
+ throw redirect({ search: signInSearch(location), to: signIn });
32
+ }
33
+ for (const permission of required) {
34
+ if (!can(current, permission)) {
35
+ throw new Forbidden({ message: `Permission required: ${permission.name} (${permission.strength})` });
36
+ }
37
+ }
38
+ };
39
+ }
@@ -0,0 +1,11 @@
1
+ import type { Permission } from "@shivaedev/access/permission.ts";
2
+
3
+ export interface PermissionSession {
4
+ readonly user: {
5
+ readonly permissions?: readonly Permission[];
6
+ };
7
+ }
8
+
9
+ export function can(session: PermissionSession | null | undefined, permission: Permission): boolean {
10
+ return session?.user.permissions?.some((grant) => grant.name === permission.name && grant.strength === permission.strength) ?? false;
11
+ }
@@ -0,0 +1,9 @@
1
+ export interface SignInLocation {
2
+ readonly href: string;
3
+ readonly state?: object | undefined;
4
+ }
5
+
6
+ export function signInSearch(location: SignInLocation): { readonly returnTo?: string } {
7
+ const state = location.state;
8
+ return state !== undefined && "__platformSignOut" in state && state.__platformSignOut === true ? {} : { returnTo: location.href };
9
+ }