@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.
- package/CHANGELOG.md +19 -0
- package/README.md +113 -1
- package/dist/client/authQuery.d.ts +5 -0
- package/dist/client/authQuery.d.ts.map +1 -0
- package/dist/client/authQuery.js +10 -0
- package/dist/client/authQuery.js.map +1 -0
- package/dist/client/permissionBoundary.d.ts +11 -0
- package/dist/client/permissionBoundary.d.ts.map +1 -0
- package/dist/client/permissionBoundary.js +20 -0
- package/dist/client/permissionBoundary.js.map +1 -0
- package/dist/client/resultState.d.ts.map +1 -1
- package/dist/client/resultState.js +4 -1
- package/dist/client/resultState.js.map +1 -1
- package/dist/client/sessionBoundary.d.ts.map +1 -1
- package/dist/client/sessionBoundary.js +4 -2
- package/dist/client/sessionBoundary.js.map +1 -1
- package/dist/client/useNow.d.ts +5 -0
- package/dist/client/useNow.d.ts.map +1 -0
- package/dist/client/useNow.js +20 -0
- package/dist/client/useNow.js.map +1 -0
- package/dist/client/useSignOut.d.ts +18 -0
- package/dist/client/useSignOut.d.ts.map +1 -0
- package/dist/client/useSignOut.js +26 -0
- package/dist/client/useSignOut.js.map +1 -0
- package/dist/internal/PermissionContext.d.ts +7 -0
- package/dist/internal/PermissionContext.d.ts.map +1 -0
- package/dist/internal/PermissionContext.js +3 -0
- package/dist/internal/PermissionContext.js.map +1 -0
- package/dist/internal/useLatestCallback.d.ts +2 -0
- package/dist/internal/useLatestCallback.d.ts.map +1 -0
- package/dist/internal/useLatestCallback.js +9 -0
- package/dist/internal/useLatestCallback.js.map +1 -0
- package/dist/internal/usePermissionFailure.d.ts +3 -0
- package/dist/internal/usePermissionFailure.d.ts.map +1 -0
- package/dist/internal/usePermissionFailure.js +13 -0
- package/dist/internal/usePermissionFailure.js.map +1 -0
- package/dist/permissionGuard.d.ts +22 -0
- package/dist/permissionGuard.d.ts.map +1 -0
- package/dist/permissionGuard.js +22 -0
- package/dist/permissionGuard.js.map +1 -0
- package/dist/permissions.d.ts +8 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +4 -0
- package/dist/permissions.js.map +1 -0
- package/dist/signInSearch.d.ts +8 -0
- package/dist/signInSearch.d.ts.map +1 -0
- package/dist/signInSearch.js +5 -0
- package/dist/signInSearch.js.map +1 -0
- package/package.json +13 -10
- package/src/client/authQuery.ts +17 -0
- package/src/client/permissionBoundary.ts +33 -0
- package/src/client/resultState.ts +4 -1
- package/src/client/sessionBoundary.ts +4 -2
- package/src/client/useNow.ts +26 -0
- package/src/client/useSignOut.ts +33 -0
- package/src/internal/PermissionContext.ts +6 -0
- package/src/internal/useLatestCallback.ts +9 -0
- package/src/internal/usePermissionFailure.ts +13 -0
- package/src/permissionGuard.ts +39 -0
- package/src/permissions.ts +11 -0
- 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/
|
|
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":"AACA,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAC;AAC9E,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAIpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
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
|
-
|
|
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;
|
|
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,
|
|
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:
|
|
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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAkB,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChH,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUnD,MAAM,cAAc,GAAG,aAAa,CAAa,GAAS,EAAE,CAAC,SAAS,CAAC,CAAC;AACxE,MAAM,UAAU,iBAAiB;IAChC,OAAO,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,CAAC;AAMD,SAAS,MAAM,CAAU,OAAyD;IACjF,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9D,IAAI,MAA+C,CAAC;IACpD,OAAO;QACN,IAAI,MAAM;YACT,MAAM,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,OAAO,MAAM,CAAC,KAAK,CAAC;QACrB,CAAC;QACD,OAAO,EAAE,CAAC;QACV,QAAQ;KACR,CAAC;AACH,CAAC;AACD,uHAAuH;AACvH,SAAS,MAAM,CAAU,OAAyD;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,CAAC,QAAQ,CAAC,WAAW,GAAG,GAAG,EAAE;QACjC,IAAI,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO;QACR,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;QACf,UAAU,CAAC,GAAG,EAAE;YACf,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;QACF,CAAC,EAAE,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACd,CAAC;AAQD,SAAS,UAAU,CAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAsC;IACrI,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,QAAmC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnG,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,SAAS,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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.
|
|
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/
|
|
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/
|
|
59
|
-
"@shivaedev/
|
|
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.
|
|
62
|
-
"@shivaedev/server": "0.
|
|
64
|
+
"@shivaedev/quality": "0.43.0",
|
|
65
|
+
"@shivaedev/server": "0.3.0",
|
|
63
66
|
"@shivaedev/service": "0.5.0",
|
|
64
|
-
"@shivaedev/sql": "0.
|
|
65
|
-
"@shivaedev/test-sql": "0.10.
|
|
66
|
-
"@shivaedev/test-story": "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.
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
+
}
|