@shivaedev/effect-react 0.5.1 → 0.7.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 +21 -0
- package/README.md +88 -10
- package/dist/client/authAction.d.ts +15 -0
- package/dist/client/authAction.d.ts.map +1 -0
- package/dist/client/authAction.js +11 -0
- package/dist/client/authAction.js.map +1 -0
- package/dist/client/editor.d.ts +8 -0
- package/dist/client/editor.d.ts.map +1 -1
- package/dist/client/editor.js +16 -7
- package/dist/client/editor.js.map +1 -1
- package/dist/client/pendingCommand.d.ts +13 -0
- package/dist/client/pendingCommand.d.ts.map +1 -0
- package/dist/client/pendingCommand.js +30 -0
- package/dist/client/pendingCommand.js.map +1 -0
- package/dist/client/resultState.d.ts.map +1 -1
- package/dist/client/resultState.js +2 -0
- package/dist/client/resultState.js.map +1 -1
- package/dist/client/sessionBoundary.js +1 -1
- package/dist/client/sessionBoundary.js.map +1 -1
- package/dist/client/testing.d.ts +5 -2
- package/dist/client/testing.d.ts.map +1 -1
- package/dist/client/testing.js +22 -2
- package/dist/client/testing.js.map +1 -1
- package/dist/client/useLoaderQuery.d.ts +8 -0
- package/dist/client/useLoaderQuery.d.ts.map +1 -0
- package/dist/client/useLoaderQuery.js +6 -0
- package/dist/client/useLoaderQuery.js.map +1 -0
- package/dist/internal/loadedQuery.d.ts +16 -0
- package/dist/internal/loadedQuery.d.ts.map +1 -0
- package/dist/internal/loadedQuery.js +11 -0
- package/dist/internal/loadedQuery.js.map +1 -0
- package/dist/internal/queryHandoff.d.ts +5 -0
- package/dist/internal/queryHandoff.d.ts.map +1 -0
- package/dist/internal/queryHandoff.js +35 -0
- package/dist/internal/queryHandoff.js.map +1 -0
- package/dist/loadQuery.d.ts.map +1 -1
- package/dist/loadQuery.js +3 -0
- package/dist/loadQuery.js.map +1 -1
- package/package.json +7 -7
- package/src/client/authAction.ts +22 -0
- package/src/client/editor.ts +29 -8
- package/src/client/pendingCommand.ts +42 -0
- package/src/client/resultState.ts +2 -0
- package/src/client/sessionBoundary.ts +1 -1
- package/src/client/testing.ts +25 -3
- package/src/client/useLoaderQuery.ts +12 -0
- package/src/internal/loadedQuery.ts +18 -0
- package/src/internal/queryHandoff.ts +38 -0
- package/src/loadQuery.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Pass React's `RootOptions` to test `render(node, options)`, including `onCaughtError`, `onUncaughtError` and `onRecoverableError`.
|
|
8
|
+
- Declare expected boundary errors with `expectNoReactWarnings().expectCaughtError(predicate)`, using its returned `onCaughtError` handler in `render`. Unexpected caught errors, console errors, React warnings and missing expected errors still fail the guard.
|
|
9
|
+
- `useLoaderQuery(atom, routeLoaderData)` and `LoadedQueryState` expose loaded query data directly and retain the last value during refetches, without an Initial result or per-screen loading branch.
|
|
10
|
+
- `useEditor({ ...config, loaded: routeLoaderData })` and `LoadedEditor` provide a defined form and direct query data for loader-backed editors.
|
|
11
|
+
- `usePendingCommand`, `PendingCommandOptions` and `PendingCommandState` from `client/pendingCommand.ts` expose a command's wanted display value while it runs and its watched queries refetch, then adopt the server value. Typed command failures revert the display and remain in the native action result.
|
|
12
|
+
- Bind independently controlled list rows to separate native action atoms with `Atom.family`, so a typed rejection on one row leaves another row's pending display intact.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Keep loader queries mounted until a component subscribes or their route owner aborts, so delayed route mounts share the loader's execution. Give SessionBoundary registries the same idle window as atom-react's default registry; unread results are released when their route owner ends.
|
|
17
|
+
|
|
18
|
+
## 0.6.0 - 2026-10-18
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `authAction` in `client/authAction.ts` binds Better Auth client promises to native action atoms for `useAction`, with pending state and typed returned failures. Rejected promises fail with the exported `AuthActionUnavailable` error; `AuthActionResult` describes the structural client result.
|
|
23
|
+
|
|
3
24
|
## 0.5.1 - 2026-10-18
|
|
4
25
|
|
|
5
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -141,6 +141,63 @@ create.dispatch({
|
|
|
141
141
|
|
|
142
142
|
An action's `dispatch(input)` returns `void`. Read the atom's latest result from `data`, `cause` or `result`. With the default `Atom.fn` action, a later dispatch interrupts the earlier work and the shared state reports the later result. Disable duplicate submissions or give independent work distinct native action atoms. When one caller needs its own success or failure, run the actual RPC Effect through its runtime, as the editor's `save` does; shared atom state cannot identify one invocation's completion.
|
|
143
143
|
|
|
144
|
+
### Better Auth actions
|
|
145
|
+
|
|
146
|
+
`authAction` from `@shivaedev/effect-react/client/authAction.ts` turns a Better Auth client call into a native action atom. Create it with the client, then pass it to `useAction` for the same `pending`, `data`, `cause`, `result` and `dispatch` shape as commands:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
const signIn = authAction((credentials: { email: string; password: string }, signal) =>
|
|
150
|
+
authClient.signIn.email(credentials, { signal }),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
function SignInButton({ email, password }: { email: string; password: string }) {
|
|
154
|
+
const action = useAction(signIn);
|
|
155
|
+
const failure = AsyncResult.error(action.result);
|
|
156
|
+
return (
|
|
157
|
+
<>
|
|
158
|
+
<button disabled={action.pending} onClick={() => action.dispatch({ email, password })}>
|
|
159
|
+
{action.pending ? "Signing in…" : "Sign in"}
|
|
160
|
+
</button>
|
|
161
|
+
{Option.isSome(failure) && <p role="alert">Sign-in failed</p>}
|
|
162
|
+
</>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
### Commands with a pending display value
|
|
170
|
+
|
|
171
|
+
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:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
175
|
+
import { usePendingCommand } from "@shivaedev/effect-react/client/pendingCommand.ts";
|
|
176
|
+
|
|
177
|
+
const toggle = Atom.family((id: number) =>
|
|
178
|
+
ItemsClient.runtime.fn((input: { readonly enabled: boolean }) => api.toggle.run({ id, ...input })),
|
|
179
|
+
);
|
|
180
|
+
const list = api.list.query();
|
|
181
|
+
|
|
182
|
+
function EnabledToggle({ row }: { readonly row: { readonly id: number; readonly enabled: boolean } }) {
|
|
183
|
+
const command = usePendingCommand(toggle(row.id), {
|
|
184
|
+
queries: [list],
|
|
185
|
+
value: row.enabled,
|
|
186
|
+
wanted: (input) => input.enabled,
|
|
187
|
+
});
|
|
188
|
+
return <button aria-pressed={command.value} disabled={command.pending}
|
|
189
|
+
onClick={() => command.dispatch({ enabled: !command.value })} type="button">
|
|
190
|
+
Enabled
|
|
191
|
+
</button>;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Each row needs its own action atom because the native atom owns its current result, including failures. `Atom.family` keeps an independent action atom stable for each row ID. This lets one row fail while another keeps its requested display through the shared list refetch. Do not share one action atom across independently controlled rows.
|
|
196
|
+
|
|
197
|
+
The example assumes a bound `api.toggle.run` accepting `{ id, enabled }`, a bound list query and their native `ItemsClient`. `queries` lists the query atoms whose refetches the display waits for; it does not add invalidations. Include every relevant query and keep the action and query atom identities stable. `value` is the current server value; `wanted(input)` computes the requested display value.
|
|
198
|
+
|
|
199
|
+
`command.value` changes on dispatch and stays at the requested value while the command or any watched query is waiting. `pending` covers that whole interval. After the queries settle, `value` follows the server again, even if it differs from the requested value. A command failure immediately returns to the server value and remains typed in `result` and `cause`, just as with `useAction`. The other action fields describe the native command result, so `result` can already be successful while `pending` still waits for refetches. This binding holds a display value; it does not write to the query cache.
|
|
200
|
+
|
|
144
201
|
### Route loaders with TanStack Router
|
|
145
202
|
|
|
146
203
|
TanStack Router owns routes, navigation, loader data and error components. `@shivaedev/effect-react/loadQuery.ts` supplies `loadQuery(registry, atom, loaderContext)` to read a contract query atom in the same registry as React. The atom runs through its native RPC runtime; the loader does not create another runtime or query cache.
|
|
@@ -149,17 +206,17 @@ This example assumes an application-owned `api.get.query({ id })` returning `{ i
|
|
|
149
206
|
|
|
150
207
|
```tsx
|
|
151
208
|
import { RegistryContext } from "@effect/atom-react";
|
|
152
|
-
import { createRootRouteWithContext, createRoute, createRouter,
|
|
209
|
+
import { createRootRouteWithContext, createRoute, createRouter, RouterProvider, useRouterState } from "@tanstack/react-router";
|
|
153
210
|
import type { ErrorComponentProps } from "@tanstack/react-router";
|
|
154
211
|
import { Predicate } from "effect";
|
|
212
|
+
import type { ReactNode } from "react";
|
|
155
213
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
156
214
|
import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
|
|
215
|
+
import { useLoaderQuery } from "@shivaedev/effect-react/client/useLoaderQuery.ts";
|
|
157
216
|
import { api } from "./notes-client.ts";
|
|
158
217
|
|
|
159
218
|
const registry = AtomRegistry.make();
|
|
160
|
-
const root = createRootRouteWithContext<{ registry: AtomRegistry.AtomRegistry }>()(
|
|
161
|
-
component: Shell,
|
|
162
|
-
});
|
|
219
|
+
const root = createRootRouteWithContext<{ registry: AtomRegistry.AtomRegistry }>()();
|
|
163
220
|
const note = createRoute({
|
|
164
221
|
getParentRoute: () => root,
|
|
165
222
|
path: "/notes/$id",
|
|
@@ -171,6 +228,7 @@ const router = createRouter({
|
|
|
171
228
|
context: { registry },
|
|
172
229
|
routeTree: root.addChildren([note]),
|
|
173
230
|
defaultPendingMs: Infinity,
|
|
231
|
+
InnerWrap: Shell,
|
|
174
232
|
});
|
|
175
233
|
|
|
176
234
|
declare module "@tanstack/react-router" {
|
|
@@ -179,13 +237,13 @@ declare module "@tanstack/react-router" {
|
|
|
179
237
|
}
|
|
180
238
|
}
|
|
181
239
|
|
|
182
|
-
function Shell() {
|
|
240
|
+
function Shell({ children }: { readonly children: ReactNode }) {
|
|
183
241
|
const pending = useRouterState({ select: (state) => state.isLoading });
|
|
184
|
-
return <main>{pending && <p>Loading…</p>}
|
|
242
|
+
return <main>{pending && <p>Loading…</p>}{children}</main>;
|
|
185
243
|
}
|
|
186
244
|
function Note() {
|
|
187
|
-
const
|
|
188
|
-
return <h1>{
|
|
245
|
+
const query = useLoaderQuery(api.get.query(note.useParams()), note.useLoaderData());
|
|
246
|
+
return <h1>{query.data.title}</h1>;
|
|
189
247
|
}
|
|
190
248
|
function Failure({ error }: ErrorComponentProps) {
|
|
191
249
|
return <p role="alert">{Predicate.isTagged(error, "NoteMissing") ? "Note missing" : "Could not load"}</p>;
|
|
@@ -195,7 +253,11 @@ export function App() {
|
|
|
195
253
|
}
|
|
196
254
|
```
|
|
197
255
|
|
|
198
|
-
`defaultPendingMs: Infinity` keeps the current route's data visible during navigation; the shell reads TanStack's `isLoading` to display pending state. The new route commits when its loader settles. This is TanStack's pending policy, separate from `useQuery`'s retained display. The loader and the component share one query execution.
|
|
256
|
+
`defaultPendingMs: Infinity` keeps the current route's data visible during navigation; the shell reads TanStack's `isLoading` to display pending state. The new route commits when its loader settles. This is TanStack's pending policy, separate from `useQuery`'s retained display. The loader and the component share one query execution even when the route delays mounting for 650 ms, beyond the registry’s idle window. A DOM screen story proves the first render has the loaded value and the loopback RPC handler runs once.
|
|
257
|
+
|
|
258
|
+
`useLoaderQuery(atom, route.useLoaderData())` subscribes to the same atom and exposes `data` as its success type, with no `Option` or Initial result. Refetches keep the last value visible. For a loader-backed editor, pass the loader value as `useEditor({ ...config, loaded: route.useLoaderData() })`; its form is defined on the first render and `editor.query.data` is the row directly.
|
|
259
|
+
|
|
260
|
+
`SessionBoundary` gives idle atoms the default 400 ms window used by atom-react’s default registry. `loadQuery` also mounts its atom until a component commits its `useQuery` or `useLoaderQuery` subscription, or the loader’s abort signal ends its route ownership. No per-query `timeToLive` is needed. Leaving an unread route releases that mount; a DOM story verifies the atom is gone after the idle window.
|
|
199
261
|
|
|
200
262
|
`loadQuery` waits through a native query refresh instead of returning its retained success. Typed failures are thrown directly, so the route can inspect `_tag` rather than an Effect fiber wrapper. Defects remain failed Promises. TanStack's loader `abortController` cancels the waiting subscription and rejects with `name: "AbortError"`; other readers of the same query can still complete. The application owns the registry's lifetime and disposes it when its client or session ends. Use the same registry for router context and `RegistryContext.Provider`.
|
|
201
263
|
|
|
@@ -443,7 +505,23 @@ it("renders a query and refetches it after a command through a test RPC layer",
|
|
|
443
505
|
});
|
|
444
506
|
```
|
|
445
507
|
|
|
446
|
-
`render(node)` returns the attached `container`, its fresh native `registry`, `render(nextNode)` to update the same root and registry, and `unmount()` to release the root, registry and container. Always unmount in `finally`. Keep component interactions inside React's `act`. `eventually(assert)` retries the assertion for up to three seconds, allowing pending responses to settle inside `act` between attempts; it throws the last assertion failure on timeout. `expectNoReactWarnings()` registers a Vitest hook that fails a test if it logged to `console.error`.
|
|
508
|
+
`render(node, options?)` returns the attached `container`, its fresh native `registry`, `render(nextNode)` to update the same root and registry, and `unmount()` to release the root, registry and container. The options are React's own `RootOptions`, passed to `createRoot`, including `onCaughtError`, `onUncaughtError` and `onRecoverableError`. Always unmount in `finally`. Keep component interactions inside React's `act`. `eventually(assert)` retries the assertion for up to three seconds, allowing pending responses to settle inside `act` between attempts; it throws the last assertion failure on timeout. `expectNoReactWarnings()` registers a Vitest hook that fails a test if it logged to `console.error`.
|
|
509
|
+
|
|
510
|
+
For a boundary fallback test, keep the warnings guard and declare the caught error inside the test. `expectCaughtError(predicate)` returns an `onCaughtError` handler that accepts matching errors without logging them. The guard fails if the handler receives any other error or the expected error never arrives. Other console errors and React warnings still fail the guard.
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
const warnings = expectNoReactWarnings();
|
|
514
|
+
|
|
515
|
+
screenStories.it("shows the route failure", [], async () => {
|
|
516
|
+
const onCaughtError = warnings.expectCaughtError((error) => error instanceof RouteFailure);
|
|
517
|
+
const view = await render(<RouteFailureScreen />, { onCaughtError });
|
|
518
|
+
try {
|
|
519
|
+
expect(view.container.querySelector('[role="alert"]')?.textContent).toBe("Route unavailable");
|
|
520
|
+
} finally {
|
|
521
|
+
await view.unmount();
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
```
|
|
447
525
|
|
|
448
526
|
The RPC layer stays application-owned: replace these in-memory handlers with the service Layer your component needs.
|
|
449
527
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
declare const AuthActionUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
|
+
readonly _tag: "AuthActionUnavailable";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
export declare class AuthActionUnavailable extends AuthActionUnavailable_base<{
|
|
6
|
+
readonly cause: unknown;
|
|
7
|
+
}> {
|
|
8
|
+
}
|
|
9
|
+
export interface AuthActionResult<A, E> {
|
|
10
|
+
readonly data: A;
|
|
11
|
+
readonly error: E | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function authAction<TInput, A, E extends object = never>(run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>): Atom.AtomResultFn<TInput, A, E | AuthActionUnavailable>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=authAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authAction.d.ts","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;;;;AAE/C,qBAAa,qBAAsB,SAAQ,2BAA0C;IACpF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB,CAAC;CAAG;AAEL,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAC7D,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1E,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,qBAAqB,CAAC,CAOzD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Data, Effect } from "effect";
|
|
2
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
export class AuthActionUnavailable extends Data.TaggedError("AuthActionUnavailable") {
|
|
4
|
+
}
|
|
5
|
+
export function authAction(run) {
|
|
6
|
+
return Atom.fn()((input) => Effect.tryPromise({
|
|
7
|
+
catch: (cause) => new AuthActionUnavailable({ cause }),
|
|
8
|
+
try: (signal) => run(input, signal),
|
|
9
|
+
}).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error)))));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=authAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authAction.js","sourceRoot":"","sources":["../../src/client/authAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,OAAO,qBAAsB,SAAQ,IAAI,CAAC,WAAW,CAAC,uBAAuB,CAEjF;CAAG;AAOL,MAAM,UAAU,UAAU,CACzB,GAA4E;IAE5E,OAAO,IAAI,CAAC,EAAE,EAAU,CAAC,CAAC,KAAK,EAAE,EAAE,CAClC,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,CAAC;AACH,CAAC"}
|
package/dist/client/editor.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Decoded, Encoded, FieldFailure, Fields, Form, Services } from "@sh
|
|
|
6
6
|
import type { AtomServices, FieldRejectionMapping } from "#fieldRejection.ts";
|
|
7
7
|
import { type QueryState } from "./resultState.ts";
|
|
8
8
|
import { type SaveState } from "./saveState.ts";
|
|
9
|
+
import type { LoadedQueryState } from "./useLoaderQuery.ts";
|
|
9
10
|
export type EditorConfig<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError> = {
|
|
10
11
|
readonly persistedDraft?: BrowserDraftConfig | undefined;
|
|
11
12
|
readonly query: Atom.Atom<AsyncResult.AsyncResult<TRow, TQueryError>>;
|
|
@@ -18,5 +19,12 @@ export interface Editor<TFields extends Fields, TRow, TQueryError, TSaveError, T
|
|
|
18
19
|
readonly form: Form<TFields, TRow, TSaveError | FieldFailure, TRuntimeError> | undefined;
|
|
19
20
|
readonly query: QueryState<TRow, TQueryError>;
|
|
20
21
|
}
|
|
22
|
+
export interface LoadedEditor<TFields extends Fields, TRow, TQueryError, TSaveError, TRuntimeError> extends Omit<Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>, "form" | "query"> {
|
|
23
|
+
readonly form: Form<TFields, TRow, TSaveError | FieldFailure, TRuntimeError>;
|
|
24
|
+
readonly query: LoadedQueryState<TRow, TQueryError>;
|
|
25
|
+
}
|
|
26
|
+
export declare function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError>(config: EditorConfig<TFields, TRow, TQueryError, TSaveError, R, TRuntimeError> & {
|
|
27
|
+
readonly loaded: TRow;
|
|
28
|
+
}): LoadedEditor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>;
|
|
21
29
|
export declare function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError>(config: EditorConfig<TFields, TRow, TQueryError, TSaveError, R, TRuntimeError>): Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>;
|
|
22
30
|
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/client/editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,wCAAwC,CAAC;AACpG,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC9G,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/client/editor.ts"],"names":[],"mappings":"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"}
|
package/dist/client/editor.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { usePersistedDraft } from "@shivaedev/effect-form/client/react.ts";
|
|
4
|
+
import { loadedQuery } from "#internal/loadedQuery.ts";
|
|
4
5
|
import { submission } from "#submission.ts";
|
|
5
6
|
import { useQuery } from "./resultState.js";
|
|
6
7
|
import { useSaveState } from "./saveState.js";
|
|
7
8
|
export function useEditor(config) {
|
|
8
9
|
const persistedDraft = usePersistedDraft(config.persistedDraft);
|
|
9
10
|
const query = useQuery(config.query);
|
|
10
|
-
const row = Option.
|
|
11
|
+
const row = Option.orElse(query.data, () => ("loaded" in config ? Option.some(config.loaded) : Option.none()));
|
|
11
12
|
const [held, hold] = useState(undefined);
|
|
12
13
|
let current = held?.query === config.query ? held : undefined;
|
|
13
|
-
if (current === undefined && row
|
|
14
|
+
if (current === undefined && Option.isSome(row)) {
|
|
14
15
|
current = {
|
|
15
16
|
form: submission({
|
|
16
17
|
fields: config.fields,
|
|
17
|
-
initialValues: config.values(row),
|
|
18
|
+
initialValues: config.values(row.value),
|
|
18
19
|
persistedDraft,
|
|
19
20
|
rejectField: config.rejectField,
|
|
20
21
|
runtime: config.runtime,
|
|
@@ -27,12 +28,20 @@ export function useEditor(config) {
|
|
|
27
28
|
}
|
|
28
29
|
const form = current?.form;
|
|
29
30
|
const values = current?.values;
|
|
31
|
+
const received = Option.getOrUndefined(row);
|
|
30
32
|
useEffect(() => {
|
|
31
|
-
if (form && values &&
|
|
32
|
-
form.receive(values(
|
|
33
|
+
if (form && values && received !== undefined) {
|
|
34
|
+
form.receive(values(received));
|
|
33
35
|
}
|
|
34
|
-
}, [form, values,
|
|
36
|
+
}, [form, values, received]);
|
|
35
37
|
const settled = useCallback((saved) => (form && values ? form.receive(values(saved)) : undefined), [form, values]);
|
|
36
|
-
|
|
38
|
+
const saveState = useSaveState(form, settled);
|
|
39
|
+
if ("loaded" in config) {
|
|
40
|
+
if (!form) {
|
|
41
|
+
throw new Error("A loaded editor requires a row");
|
|
42
|
+
}
|
|
43
|
+
return { ...saveState, form, query: loadedQuery(query, config.loaded) };
|
|
44
|
+
}
|
|
45
|
+
return { ...saveState, form, query };
|
|
37
46
|
}
|
|
38
47
|
//# sourceMappingURL=editor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/client/editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAAe,MAAM,QAAQ,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAA2B,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAGpG,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAmB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAkB,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/client/editor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import { type ActionState } from "./resultState.ts";
|
|
4
|
+
export interface PendingCommandOptions<TInput, TValue> {
|
|
5
|
+
readonly queries: readonly Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>[];
|
|
6
|
+
readonly value: TValue;
|
|
7
|
+
readonly wanted: (input: TInput) => TValue;
|
|
8
|
+
}
|
|
9
|
+
export interface PendingCommandState<TInput, A, E, TValue> extends ActionState<TInput, A, E> {
|
|
10
|
+
readonly value: TValue;
|
|
11
|
+
}
|
|
12
|
+
export declare function usePendingCommand<TInput, A, E, TValue>(atom: Atom.AtomResultFn<TInput, A, E>, options: PendingCommandOptions<TInput, TValue>): PendingCommandState<TInput, A, E, TValue>;
|
|
13
|
+
//# sourceMappingURL=pendingCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingCommand.d.ts","sourceRoot":"","sources":["../../src/client/pendingCommand.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,kBAAkB,CAAC;AAE/D,MAAM,WAAW,qBAAqB,CAAC,MAAM,EAAE,MAAM;IACpD,QAAQ,CAAC,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAClF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3C;AACD,MAAM,WAAW,mBAAmB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAE,SAAQ,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EACrD,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EACrC,OAAO,EAAE,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5C,mBAAmB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAsB3C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RegistryContext, useAtomValue } from "@effect/atom-react";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
4
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
5
|
+
import { useContext, useMemo, useState } from "react";
|
|
6
|
+
import { useAction } from "./resultState.js";
|
|
7
|
+
export function usePendingCommand(atom, options) {
|
|
8
|
+
const action = useAction(atom);
|
|
9
|
+
const registry = useContext(RegistryContext);
|
|
10
|
+
const { queries, value, wanted } = options;
|
|
11
|
+
const waiting = useMemo(() => Atom.make((get) => queries.map((query) => get(query).waiting).some(Boolean)), [queries]);
|
|
12
|
+
const refreshing = useAtomValue(waiting);
|
|
13
|
+
const [held, hold] = useState({ atom, registry, value: Option.none() });
|
|
14
|
+
const sameOwner = held.atom === atom && held.registry === registry;
|
|
15
|
+
const failed = AsyncResult.isFailure(action.result) && !action.pending;
|
|
16
|
+
const pending = sameOwner && Option.isSome(held.value) && !failed && (action.pending || refreshing);
|
|
17
|
+
if (!sameOwner || (Option.isSome(held.value) && !pending)) {
|
|
18
|
+
hold({ atom, registry, value: Option.none() });
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...action,
|
|
22
|
+
dispatch: (input) => {
|
|
23
|
+
hold({ atom, registry, value: Option.some(wanted(input)) });
|
|
24
|
+
action.dispatch(input);
|
|
25
|
+
},
|
|
26
|
+
pending: action.pending || pending,
|
|
27
|
+
value: pending ? Option.getOrElse(held.value, () => value) : value,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=pendingCommand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingCommand.js","sourceRoot":"","sources":["../../src/client/pendingCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAoB,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAW/D,MAAM,UAAU,iBAAiB,CAChC,IAAqC,EACrC,OAA8C;IAE9C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC7C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACvH,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAU,EAAE,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC;IACnE,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IACvE,MAAM,OAAO,GAAG,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,UAAU,CAAC,CAAC;IACpG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,OAAO;QACN,GAAG,MAAM;QACT,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO;QAClC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK;KAClE,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,MAAM,EAAa,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"resultState.d.ts","sourceRoot":"","sources":["../../src/client/resultState.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAKpD,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC/C;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAEhE;AASD,MAAM,WAAW,UAAU,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AACD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACpC;AASD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAgB3H;AACD,MAAM,WAAW,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAIxG"}
|
|
@@ -2,6 +2,7 @@ import { RegistryContext, useAtomRefresh, useAtomSet, useAtomValue } from "@effe
|
|
|
2
2
|
import { Cause, Option, Predicate } from "effect";
|
|
3
3
|
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
4
4
|
import { useContext, useEffect, useState } from "react";
|
|
5
|
+
import { acceptLoaderQuery } from "#internal/queryHandoff.ts";
|
|
5
6
|
import { useSessionRecheck } from "./sessionBoundary.js";
|
|
6
7
|
function state(result, data = AsyncResult.value(result)) {
|
|
7
8
|
return {
|
|
@@ -26,6 +27,7 @@ function useRecheckOnUnauthorized(result) {
|
|
|
26
27
|
export function useQuery(atom, options = {}) {
|
|
27
28
|
const result = useAtomValue(atom);
|
|
28
29
|
const registry = useContext(RegistryContext);
|
|
30
|
+
useEffect(() => acceptLoaderQuery(registry, atom), [registry, atom]);
|
|
29
31
|
const keepPreviousData = options.keepPreviousData === true;
|
|
30
32
|
const nativeData = AsyncResult.value(result);
|
|
31
33
|
const [held, hold] = useState(() => ({ atom, data: nativeData, dataAtom: atom, keepPreviousData, registry, result }));
|
|
@@ -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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"}
|
|
@@ -9,7 +9,7 @@ export function useSessionRecheck() {
|
|
|
9
9
|
return useContext(RecheckContext);
|
|
10
10
|
}
|
|
11
11
|
function owning(client) {
|
|
12
|
-
return { client, mounted: 0, registry: AtomRegistry.make() };
|
|
12
|
+
return { client, mounted: 0, registry: AtomRegistry.make({ "defaultIdleTTL": 400 }) };
|
|
13
13
|
}
|
|
14
14
|
// No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
|
|
15
15
|
function hidden(client) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,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,wCAAwC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,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,MAAe;IACvC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
|
package/dist/client/testing.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
|
-
|
|
3
|
+
import { type RootOptions } from "react-dom/client";
|
|
4
|
+
export declare function expectNoReactWarnings(): {
|
|
5
|
+
expectCaughtError(matches: (error: unknown) => boolean): NonNullable<RootOptions["onCaughtError"]>;
|
|
6
|
+
};
|
|
4
7
|
export declare function eventually(assert: () => void): Promise<void>;
|
|
5
8
|
export interface Rendered {
|
|
6
9
|
readonly container: HTMLDivElement;
|
|
@@ -8,5 +11,5 @@ export interface Rendered {
|
|
|
8
11
|
readonly render: (node: ReactNode) => Promise<void>;
|
|
9
12
|
readonly unmount: () => Promise<void>;
|
|
10
13
|
}
|
|
11
|
-
export declare function render(node: ReactNode): Promise<Rendered>;
|
|
14
|
+
export declare function render(node: ReactNode, options?: RootOptions): Promise<Rendered>;
|
|
12
15
|
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEhE,wBAAgB,qBAAqB;IAmBnC,iBAAiB,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;EAYnG;AACD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,iBAgBlD;AACD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AACD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4BtF"}
|
package/dist/client/testing.js
CHANGED
|
@@ -5,14 +5,34 @@ import { act, createElement } from "react";
|
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
6
|
import { beforeEach, expect, vi } from "vitest";
|
|
7
7
|
export function expectNoReactWarnings() {
|
|
8
|
+
let expectations = [];
|
|
9
|
+
let unexpected = [];
|
|
8
10
|
beforeEach(({ onTestFinished }) => {
|
|
11
|
+
expectations = [];
|
|
12
|
+
unexpected = [];
|
|
9
13
|
const errors = vi.spyOn(console, "error");
|
|
10
14
|
onTestFinished(() => {
|
|
11
15
|
const calls = errors.mock.calls;
|
|
12
16
|
errors.mockRestore();
|
|
13
17
|
expect(calls).toEqual([]);
|
|
18
|
+
expect(unexpected, "Unexpected caught React errors").toEqual([]);
|
|
19
|
+
expect(expectations.every(({ caught }) => caught), "Expected caught React error did not occur").toBe(true);
|
|
14
20
|
});
|
|
15
21
|
});
|
|
22
|
+
return {
|
|
23
|
+
expectCaughtError(matches) {
|
|
24
|
+
const expectation = { caught: false };
|
|
25
|
+
expectations.push(expectation);
|
|
26
|
+
return (error) => {
|
|
27
|
+
if (matches(error)) {
|
|
28
|
+
expectation.caught = true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
unexpected.push(error);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
16
36
|
}
|
|
17
37
|
export async function eventually(assert) {
|
|
18
38
|
const deadline = Effect.runSync(Clock.currentTimeMillis) + 3000;
|
|
@@ -32,11 +52,11 @@ export async function eventually(assert) {
|
|
|
32
52
|
}
|
|
33
53
|
}
|
|
34
54
|
}
|
|
35
|
-
export async function render(node) {
|
|
55
|
+
export async function render(node, options) {
|
|
36
56
|
const registry = AtomRegistry.make();
|
|
37
57
|
const container = document.createElement("div");
|
|
38
58
|
document.body.append(container);
|
|
39
|
-
const root = createRoot(container);
|
|
59
|
+
const root = createRoot(container, options);
|
|
40
60
|
let mounted = true;
|
|
41
61
|
async function unmount() {
|
|
42
62
|
if (!mounted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,aAAa,EAAkB,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
|
+
import type * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import { type QueryState } from "./resultState.ts";
|
|
4
|
+
export interface LoadedQueryState<A, E> extends Omit<QueryState<A, E>, "data" | "result"> {
|
|
5
|
+
readonly data: A;
|
|
6
|
+
}
|
|
7
|
+
export declare function useLoaderQuery<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>, loaded: NoInfer<A>): LoadedQueryState<A, E>;
|
|
8
|
+
//# sourceMappingURL=useLoaderQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLoaderQuery.d.ts","sourceRoot":"","sources":["../../src/client/useLoaderQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAE7D,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxF,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAE/H"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLoaderQuery.js","sourceRoot":"","sources":["../../src/client/useLoaderQuery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAmB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM7D,MAAM,UAAU,cAAc,CAAO,IAA8C,EAAE,MAAkB;IACtG,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Cause, Option } from "effect";
|
|
2
|
+
interface QuerySnapshot<A, E> {
|
|
3
|
+
readonly cause: Option.Option<Cause.Cause<E>>;
|
|
4
|
+
readonly data: Option.Option<A>;
|
|
5
|
+
readonly pending: boolean;
|
|
6
|
+
readonly refresh: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function loadedQuery<A, E>(query: QuerySnapshot<A, E>, loaded: A): {
|
|
9
|
+
cause: Option.Option<Cause.Cause<E>>;
|
|
10
|
+
data: A;
|
|
11
|
+
pending: boolean;
|
|
12
|
+
refresh: () => void;
|
|
13
|
+
refreshing: boolean;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=loadedQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadedQuery.d.ts","sourceRoot":"","sources":["../../src/internal/loadedQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE5C,UAAU,aAAa,CAAC,CAAC,EAAE,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;IAErE,KAAK;IACL,IAAI;IACJ,OAAO;IACP,OAAO,QARgB,IAAI;IAS3B,UAAU;EAEX"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
export function loadedQuery(query, loaded) {
|
|
3
|
+
return {
|
|
4
|
+
cause: query.cause,
|
|
5
|
+
data: Option.getOrElse(query.data, () => loaded),
|
|
6
|
+
pending: query.pending,
|
|
7
|
+
refresh: query.refresh,
|
|
8
|
+
refreshing: query.pending,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=loadedQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadedQuery.js","sourceRoot":"","sources":["../../src/internal/loadedQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS5C,MAAM,UAAU,WAAW,CAAO,KAA0B,EAAE,MAAS;IACtE,OAAO;QACN,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC;QAChD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,UAAU,EAAE,KAAK,CAAC,OAAO;KACzB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
|
+
export declare function keepLoaderQuery(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom<unknown>, signal: AbortSignal): () => void;
|
|
4
|
+
export declare function acceptLoaderQuery(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom<unknown>): void;
|
|
5
|
+
//# sourceMappingURL=queryHandoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryHandoff.d.ts","sourceRoot":"","sources":["../../src/internal/queryHandoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAIpE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,IAAI,CA0B9H;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAIrG"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
const handoffs = new WeakMap();
|
|
3
|
+
export function keepLoaderQuery(registry, atom, signal) {
|
|
4
|
+
let atoms = handoffs.get(registry);
|
|
5
|
+
if (!atoms) {
|
|
6
|
+
atoms = new WeakMap();
|
|
7
|
+
handoffs.set(registry, atoms);
|
|
8
|
+
}
|
|
9
|
+
const readers = atoms.get(atom) ?? new Set();
|
|
10
|
+
atoms.set(atom, readers);
|
|
11
|
+
const key = Atom.isSerializable(atom) ? atom[Atom.SerializableTypeId].key : atom;
|
|
12
|
+
if (atom.initialValueTarget && !registry.getNodes().has(key)) {
|
|
13
|
+
// A removed reactive wrapper may have missed invalidations while its source stayed idle.
|
|
14
|
+
registry.refresh(atom);
|
|
15
|
+
}
|
|
16
|
+
const unmount = registry.mount(atom);
|
|
17
|
+
function release() {
|
|
18
|
+
if (readers.delete(release)) {
|
|
19
|
+
signal.removeEventListener("abort", release);
|
|
20
|
+
unmount();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
readers.add(release);
|
|
24
|
+
signal.addEventListener("abort", release, { once: true });
|
|
25
|
+
if (signal.aborted) {
|
|
26
|
+
release();
|
|
27
|
+
}
|
|
28
|
+
return release;
|
|
29
|
+
}
|
|
30
|
+
export function acceptLoaderQuery(registry, atom) {
|
|
31
|
+
for (const release of handoffs.get(registry)?.get(atom) ?? []) {
|
|
32
|
+
release();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=queryHandoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryHandoff.js","sourceRoot":"","sources":["../../src/internal/queryHandoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAG/C,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA2E,CAAC;AAExG,MAAM,UAAU,eAAe,CAAC,QAAmC,EAAE,IAAwB,EAAE,MAAmB;IACjH,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,KAAK,GAAG,IAAI,OAAO,EAAE,CAAC;QACtB,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAc,CAAC;IACzD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,IAAI,IAAI,CAAC,kBAAkB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9D,yFAAyF;QACzF,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,OAAO;QACf,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAmC,EAAE,IAAwB;IAC9F,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC"}
|
package/dist/loadQuery.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadQuery.d.ts","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"loadQuery.d.ts","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAI/D,wBAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,EACnC,QAAQ,EAAE,YAAY,CAAC,YAAY,EACnC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC9C,EAAE,eAAe,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,iBAAiB,CAAC,GAC3D,OAAO,CAAC,CAAC,CAAC,CAmBZ"}
|
package/dist/loadQuery.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
|
+
import { keepLoaderQuery } from "#internal/queryHandoff.ts";
|
|
3
4
|
import { recheckUnauthorized } from "#internal/sessionRecheck.ts";
|
|
4
5
|
export async function loadQuery(registry, atom, { abortController }) {
|
|
5
6
|
const { signal } = abortController;
|
|
7
|
+
const release = keepLoaderQuery(registry, atom, signal);
|
|
6
8
|
try {
|
|
7
9
|
const result = await Effect.runPromise(Effect.result(AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true })), { signal });
|
|
8
10
|
if (result._tag === "Failure") {
|
|
@@ -12,6 +14,7 @@ export async function loadQuery(registry, atom, { abortController }) {
|
|
|
12
14
|
return result.success;
|
|
13
15
|
}
|
|
14
16
|
catch (error) {
|
|
17
|
+
release();
|
|
15
18
|
if (signal.aborted) {
|
|
16
19
|
const canceled = new Error("Query loading canceled", { cause: error });
|
|
17
20
|
canceled.name = "AbortError";
|
package/dist/loadQuery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadQuery.js","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,QAAmC,EACnC,IAA8C,EAC9C,EAAE,eAAe,EAA4C;IAE7D,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC;IACnC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACtI,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YACpD,MAAM,MAAM,CAAC,OAAO,CAAC;QACtB,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACvE,QAAQ,CAAC,IAAI,GAAG,YAAY,CAAC;YAC7B,MAAM,QAAQ,CAAC;QAChB,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"loadQuery.js","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,QAAmC,EACnC,IAA8C,EAC9C,EAAE,eAAe,EAA4C;IAE7D,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACtI,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YACpD,MAAM,MAAM,CAAC,OAAO,CAAC;QACtB,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;QACV,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACvE,QAAQ,CAAC,IAAI,GAAG,YAAY,CAAC;YAC7B,MAAM,QAAQ,CAAC;QAChB,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "React query, action, editor and session hooks for native Effect atoms",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-react#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
"@effect/platform-node": "4.0.4",
|
|
52
52
|
"@effect/sql-pg": "4.0.4",
|
|
53
53
|
"@effect/sql-sqlite-node": "4.0.4",
|
|
54
|
-
"@shivaedev/effect-pg-boss": "0.
|
|
54
|
+
"@shivaedev/effect-pg-boss": "0.10.1",
|
|
55
55
|
"@shivaedev/effect-service": "0.4.0",
|
|
56
|
-
"@shivaedev/effect-sql": "0.
|
|
56
|
+
"@shivaedev/effect-sql": "0.6.0",
|
|
57
57
|
"@shivaedev/effect-test": "0.6.0",
|
|
58
|
-
"@shivaedev/local-postgres": "0.4.
|
|
59
|
-
"@shivaedev/platform": "0.
|
|
60
|
-
"@shivaedev/quality": "0.
|
|
61
|
-
"@shivaedev/test-story": "0.
|
|
58
|
+
"@shivaedev/local-postgres": "0.4.2",
|
|
59
|
+
"@shivaedev/platform": "0.18.0",
|
|
60
|
+
"@shivaedev/quality": "0.27.1",
|
|
61
|
+
"@shivaedev/test-story": "0.10.0",
|
|
62
62
|
"@tanstack/react-router": "1.170.42",
|
|
63
63
|
"@types/node": "24.19.2",
|
|
64
64
|
"@types/react": "19.3.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Data, Effect } from "effect";
|
|
2
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
|
|
4
|
+
export class AuthActionUnavailable extends Data.TaggedError("AuthActionUnavailable")<{
|
|
5
|
+
readonly cause: unknown;
|
|
6
|
+
}> {}
|
|
7
|
+
|
|
8
|
+
export interface AuthActionResult<A, E> {
|
|
9
|
+
readonly data: A;
|
|
10
|
+
readonly error: E | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function authAction<TInput, A, E extends object = never>(
|
|
14
|
+
run: (input: TInput, signal: AbortSignal) => Promise<AuthActionResult<A, E>>,
|
|
15
|
+
): Atom.AtomResultFn<TInput, A, E | AuthActionUnavailable> {
|
|
16
|
+
return Atom.fn<TInput>()((input) =>
|
|
17
|
+
Effect.tryPromise({
|
|
18
|
+
catch: (cause) => new AuthActionUnavailable({ cause }),
|
|
19
|
+
try: (signal) => run(input, signal),
|
|
20
|
+
}).pipe(Effect.flatMap((result) => (result.error === null ? Effect.succeed(result.data) : Effect.fail(result.error)))),
|
|
21
|
+
);
|
|
22
|
+
}
|
package/src/client/editor.ts
CHANGED
|
@@ -5,9 +5,11 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
5
5
|
import { type BrowserDraftConfig, usePersistedDraft } from "@shivaedev/effect-form/client/react.ts";
|
|
6
6
|
import type { Decoded, Encoded, FieldFailure, Fields, Form, Services } from "@shivaedev/effect-form/shape.ts";
|
|
7
7
|
import type { AtomServices, FieldRejectionMapping } from "#fieldRejection.ts";
|
|
8
|
+
import { loadedQuery } from "#internal/loadedQuery.ts";
|
|
8
9
|
import { submission } from "#submission.ts";
|
|
9
10
|
import { type QueryState, useQuery } from "./resultState.ts";
|
|
10
11
|
import { type SaveState, useSaveState } from "./saveState.ts";
|
|
12
|
+
import type { LoadedQueryState } from "./useLoaderQuery.ts";
|
|
11
13
|
export type EditorConfig<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError> = {
|
|
12
14
|
readonly persistedDraft?: BrowserDraftConfig | undefined;
|
|
13
15
|
readonly query: Atom.Atom<AsyncResult.AsyncResult<TRow, TQueryError>>;
|
|
@@ -20,24 +22,35 @@ export interface Editor<TFields extends Fields, TRow, TQueryError, TSaveError, T
|
|
|
20
22
|
readonly form: Form<TFields, TRow, TSaveError | FieldFailure, TRuntimeError> | undefined;
|
|
21
23
|
readonly query: QueryState<TRow, TQueryError>;
|
|
22
24
|
}
|
|
25
|
+
export interface LoadedEditor<TFields extends Fields, TRow, TQueryError, TSaveError, TRuntimeError>
|
|
26
|
+
extends Omit<Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>, "form" | "query"> {
|
|
27
|
+
readonly form: Form<TFields, TRow, TSaveError | FieldFailure, TRuntimeError>;
|
|
28
|
+
readonly query: LoadedQueryState<TRow, TQueryError>;
|
|
29
|
+
}
|
|
23
30
|
interface Held<TFields extends Fields, TRow, TSaveError, TRuntimeError> {
|
|
24
31
|
readonly form: Form<TFields, TRow, TSaveError | FieldFailure, TRuntimeError>;
|
|
25
32
|
readonly query: Atom.Atom<unknown>;
|
|
26
33
|
readonly values: (row: TRow) => Encoded<TFields>;
|
|
27
34
|
}
|
|
35
|
+
export function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError>(
|
|
36
|
+
config: EditorConfig<TFields, TRow, TQueryError, TSaveError, R, TRuntimeError> & { readonly loaded: TRow },
|
|
37
|
+
): LoadedEditor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>;
|
|
28
38
|
export function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError>(
|
|
29
39
|
config: EditorConfig<TFields, TRow, TQueryError, TSaveError, R, TRuntimeError>,
|
|
30
|
-
): Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError
|
|
40
|
+
): Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError>;
|
|
41
|
+
export function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError, R, TRuntimeError>(
|
|
42
|
+
config: EditorConfig<TFields, TRow, TQueryError, TSaveError, R, TRuntimeError> & { readonly loaded?: TRow },
|
|
43
|
+
): Editor<TFields, TRow, TQueryError, TSaveError, TRuntimeError> | LoadedEditor<TFields, TRow, TQueryError, TSaveError, TRuntimeError> {
|
|
31
44
|
const persistedDraft = usePersistedDraft(config.persistedDraft);
|
|
32
45
|
const query = useQuery(config.query);
|
|
33
|
-
const row = Option.
|
|
46
|
+
const row = Option.orElse(query.data, () => ("loaded" in config ? Option.some(config.loaded) : Option.none()));
|
|
34
47
|
const [held, hold] = useState<Held<TFields, TRow, TSaveError, TRuntimeError> | undefined>(undefined);
|
|
35
48
|
let current = held?.query === config.query ? held : undefined;
|
|
36
|
-
if (current === undefined && row
|
|
49
|
+
if (current === undefined && Option.isSome(row)) {
|
|
37
50
|
current = {
|
|
38
51
|
form: submission({
|
|
39
52
|
fields: config.fields,
|
|
40
|
-
initialValues: config.values(row),
|
|
53
|
+
initialValues: config.values(row.value),
|
|
41
54
|
persistedDraft,
|
|
42
55
|
rejectField: config.rejectField,
|
|
43
56
|
runtime: config.runtime,
|
|
@@ -50,11 +63,19 @@ export function useEditor<TFields extends Fields, TRow, TQueryError, TSaveError,
|
|
|
50
63
|
}
|
|
51
64
|
const form = current?.form;
|
|
52
65
|
const values = current?.values;
|
|
66
|
+
const received = Option.getOrUndefined(row);
|
|
53
67
|
useEffect(() => {
|
|
54
|
-
if (form && values &&
|
|
55
|
-
form.receive(values(
|
|
68
|
+
if (form && values && received !== undefined) {
|
|
69
|
+
form.receive(values(received));
|
|
56
70
|
}
|
|
57
|
-
}, [form, values,
|
|
71
|
+
}, [form, values, received]);
|
|
58
72
|
const settled = useCallback((saved: TRow) => (form && values ? form.receive(values(saved)) : undefined), [form, values]);
|
|
59
|
-
|
|
73
|
+
const saveState = useSaveState(form, settled);
|
|
74
|
+
if ("loaded" in config) {
|
|
75
|
+
if (!form) {
|
|
76
|
+
throw new Error("A loaded editor requires a row");
|
|
77
|
+
}
|
|
78
|
+
return { ...saveState, form, query: loadedQuery(query, config.loaded) };
|
|
79
|
+
}
|
|
80
|
+
return { ...saveState, form, query };
|
|
60
81
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RegistryContext, useAtomValue } from "@effect/atom-react";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
4
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
5
|
+
import { useContext, useMemo, useState } from "react";
|
|
6
|
+
import { type ActionState, useAction } from "./resultState.ts";
|
|
7
|
+
|
|
8
|
+
export interface PendingCommandOptions<TInput, TValue> {
|
|
9
|
+
readonly queries: readonly Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>[];
|
|
10
|
+
readonly value: TValue;
|
|
11
|
+
readonly wanted: (input: TInput) => TValue;
|
|
12
|
+
}
|
|
13
|
+
export interface PendingCommandState<TInput, A, E, TValue> extends ActionState<TInput, A, E> {
|
|
14
|
+
readonly value: TValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function usePendingCommand<TInput, A, E, TValue>(
|
|
18
|
+
atom: Atom.AtomResultFn<TInput, A, E>,
|
|
19
|
+
options: PendingCommandOptions<TInput, TValue>,
|
|
20
|
+
): PendingCommandState<TInput, A, E, TValue> {
|
|
21
|
+
const action = useAction(atom);
|
|
22
|
+
const registry = useContext(RegistryContext);
|
|
23
|
+
const { queries, value, wanted } = options;
|
|
24
|
+
const waiting = useMemo(() => Atom.make((get) => queries.map((query) => get(query).waiting).some(Boolean)), [queries]);
|
|
25
|
+
const refreshing = useAtomValue(waiting);
|
|
26
|
+
const [held, hold] = useState({ atom, registry, value: Option.none<TValue>() });
|
|
27
|
+
const sameOwner = held.atom === atom && held.registry === registry;
|
|
28
|
+
const failed = AsyncResult.isFailure(action.result) && !action.pending;
|
|
29
|
+
const pending = sameOwner && Option.isSome(held.value) && !failed && (action.pending || refreshing);
|
|
30
|
+
if (!sameOwner || (Option.isSome(held.value) && !pending)) {
|
|
31
|
+
hold({ atom, registry, value: Option.none() });
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...action,
|
|
35
|
+
dispatch: (input) => {
|
|
36
|
+
hold({ atom, registry, value: Option.some(wanted(input)) });
|
|
37
|
+
action.dispatch(input);
|
|
38
|
+
},
|
|
39
|
+
pending: action.pending || pending,
|
|
40
|
+
value: pending ? Option.getOrElse(held.value, () => value) : value,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -4,6 +4,7 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
|
4
4
|
import type * as Atom from "effect/reactivity/Atom";
|
|
5
5
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
6
6
|
import { useContext, useEffect, useState } from "react";
|
|
7
|
+
import { acceptLoaderQuery } from "#internal/queryHandoff.ts";
|
|
7
8
|
import { useSessionRecheck } from "./sessionBoundary.ts";
|
|
8
9
|
export interface ResultState<A, E> {
|
|
9
10
|
readonly cause: Option.Option<Cause.Cause<E>>;
|
|
@@ -49,6 +50,7 @@ interface HeldQuery<A, E> {
|
|
|
49
50
|
export function useQuery<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>, options: QueryOptions = {}): QueryState<A, E> {
|
|
50
51
|
const result = useAtomValue(atom);
|
|
51
52
|
const registry = useContext(RegistryContext);
|
|
53
|
+
useEffect(() => acceptLoaderQuery(registry, atom), [registry, atom]);
|
|
52
54
|
const keepPreviousData = options.keepPreviousData === true;
|
|
53
55
|
const nativeData = AsyncResult.value(result);
|
|
54
56
|
const [held, hold] = useState<HeldQuery<A, E>>(() => ({ atom, data: nativeData, dataAtom: atom, keepPreviousData, registry, result }));
|
|
@@ -22,7 +22,7 @@ interface Owned<TClient> {
|
|
|
22
22
|
readonly registry: AtomRegistry.AtomRegistry;
|
|
23
23
|
}
|
|
24
24
|
function owning<TClient>(client: TClient): Owned<TClient> {
|
|
25
|
-
return { client, mounted: 0, registry: AtomRegistry.make() };
|
|
25
|
+
return { client, mounted: 0, registry: AtomRegistry.make({ "defaultIdleTTL": 400 }) };
|
|
26
26
|
}
|
|
27
27
|
// No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
|
|
28
28
|
function hidden<TClient>(client: TClient): Owned<TClient> {
|
package/src/client/testing.ts
CHANGED
|
@@ -2,17 +2,39 @@ import { RegistryContext } from "@effect/atom-react";
|
|
|
2
2
|
import { Clock, Effect } from "effect";
|
|
3
3
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
4
|
import { act, createElement, type ReactNode } from "react";
|
|
5
|
-
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { createRoot, type RootOptions } from "react-dom/client";
|
|
6
6
|
import { beforeEach, expect, vi } from "vitest";
|
|
7
7
|
export function expectNoReactWarnings() {
|
|
8
|
+
let expectations: { caught: boolean }[] = [];
|
|
9
|
+
let unexpected: unknown[] = [];
|
|
8
10
|
beforeEach(({ onTestFinished }) => {
|
|
11
|
+
expectations = [];
|
|
12
|
+
unexpected = [];
|
|
9
13
|
const errors = vi.spyOn(console, "error");
|
|
10
14
|
onTestFinished(() => {
|
|
11
15
|
const calls = errors.mock.calls;
|
|
12
16
|
errors.mockRestore();
|
|
13
17
|
expect(calls).toEqual([]);
|
|
18
|
+
expect(unexpected, "Unexpected caught React errors").toEqual([]);
|
|
19
|
+
expect(
|
|
20
|
+
expectations.every(({ caught }) => caught),
|
|
21
|
+
"Expected caught React error did not occur",
|
|
22
|
+
).toBe(true);
|
|
14
23
|
});
|
|
15
24
|
});
|
|
25
|
+
return {
|
|
26
|
+
expectCaughtError(matches: (error: unknown) => boolean): NonNullable<RootOptions["onCaughtError"]> {
|
|
27
|
+
const expectation = { caught: false };
|
|
28
|
+
expectations.push(expectation);
|
|
29
|
+
return (error) => {
|
|
30
|
+
if (matches(error)) {
|
|
31
|
+
expectation.caught = true;
|
|
32
|
+
} else {
|
|
33
|
+
unexpected.push(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
16
38
|
}
|
|
17
39
|
export async function eventually(assert: () => void) {
|
|
18
40
|
const deadline = Effect.runSync(Clock.currentTimeMillis) + 3000;
|
|
@@ -37,11 +59,11 @@ export interface Rendered {
|
|
|
37
59
|
readonly render: (node: ReactNode) => Promise<void>;
|
|
38
60
|
readonly unmount: () => Promise<void>;
|
|
39
61
|
}
|
|
40
|
-
export async function render(node: ReactNode): Promise<Rendered> {
|
|
62
|
+
export async function render(node: ReactNode, options?: RootOptions): Promise<Rendered> {
|
|
41
63
|
const registry = AtomRegistry.make();
|
|
42
64
|
const container = document.createElement("div");
|
|
43
65
|
document.body.append(container);
|
|
44
|
-
const root = createRoot(container);
|
|
66
|
+
const root = createRoot(container, options);
|
|
45
67
|
let mounted = true;
|
|
46
68
|
async function unmount() {
|
|
47
69
|
if (!mounted) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
|
+
import type * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import { loadedQuery } from "#internal/loadedQuery.ts";
|
|
4
|
+
import { type QueryState, useQuery } from "./resultState.ts";
|
|
5
|
+
|
|
6
|
+
export interface LoadedQueryState<A, E> extends Omit<QueryState<A, E>, "data" | "result"> {
|
|
7
|
+
readonly data: A;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useLoaderQuery<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>, loaded: NoInfer<A>): LoadedQueryState<A, E> {
|
|
11
|
+
return loadedQuery(useQuery(atom), loaded);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Cause, Option } from "effect";
|
|
2
|
+
|
|
3
|
+
interface QuerySnapshot<A, E> {
|
|
4
|
+
readonly cause: Option.Option<Cause.Cause<E>>;
|
|
5
|
+
readonly data: Option.Option<A>;
|
|
6
|
+
readonly pending: boolean;
|
|
7
|
+
readonly refresh: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadedQuery<A, E>(query: QuerySnapshot<A, E>, loaded: A) {
|
|
11
|
+
return {
|
|
12
|
+
cause: query.cause,
|
|
13
|
+
data: Option.getOrElse(query.data, () => loaded),
|
|
14
|
+
pending: query.pending,
|
|
15
|
+
refresh: query.refresh,
|
|
16
|
+
refreshing: query.pending,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
|
+
|
|
4
|
+
const handoffs = new WeakMap<AtomRegistry.AtomRegistry, WeakMap<Atom.Atom<unknown>, Set<() => void>>>();
|
|
5
|
+
|
|
6
|
+
export function keepLoaderQuery(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom<unknown>, signal: AbortSignal): () => void {
|
|
7
|
+
let atoms = handoffs.get(registry);
|
|
8
|
+
if (!atoms) {
|
|
9
|
+
atoms = new WeakMap();
|
|
10
|
+
handoffs.set(registry, atoms);
|
|
11
|
+
}
|
|
12
|
+
const readers = atoms.get(atom) ?? new Set<() => void>();
|
|
13
|
+
atoms.set(atom, readers);
|
|
14
|
+
const key = Atom.isSerializable(atom) ? atom[Atom.SerializableTypeId].key : atom;
|
|
15
|
+
if (atom.initialValueTarget && !registry.getNodes().has(key)) {
|
|
16
|
+
// A removed reactive wrapper may have missed invalidations while its source stayed idle.
|
|
17
|
+
registry.refresh(atom);
|
|
18
|
+
}
|
|
19
|
+
const unmount = registry.mount(atom);
|
|
20
|
+
function release() {
|
|
21
|
+
if (readers.delete(release)) {
|
|
22
|
+
signal.removeEventListener("abort", release);
|
|
23
|
+
unmount();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
readers.add(release);
|
|
27
|
+
signal.addEventListener("abort", release, { once: true });
|
|
28
|
+
if (signal.aborted) {
|
|
29
|
+
release();
|
|
30
|
+
}
|
|
31
|
+
return release;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function acceptLoaderQuery(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom<unknown>): void {
|
|
35
|
+
for (const release of handoffs.get(registry)?.get(atom) ?? []) {
|
|
36
|
+
release();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/loadQuery.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Effect } from "effect";
|
|
|
3
3
|
import type * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
4
4
|
import type * as Atom from "effect/reactivity/Atom";
|
|
5
5
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
6
|
+
import { keepLoaderQuery } from "#internal/queryHandoff.ts";
|
|
6
7
|
import { recheckUnauthorized } from "#internal/sessionRecheck.ts";
|
|
7
8
|
|
|
8
9
|
export async function loadQuery<A, E>(
|
|
@@ -11,6 +12,7 @@ export async function loadQuery<A, E>(
|
|
|
11
12
|
{ abortController }: Pick<LoaderFnContext, "abortController">,
|
|
12
13
|
): Promise<A> {
|
|
13
14
|
const { signal } = abortController;
|
|
15
|
+
const release = keepLoaderQuery(registry, atom, signal);
|
|
14
16
|
try {
|
|
15
17
|
const result = await Effect.runPromise(Effect.result(AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true })), { signal });
|
|
16
18
|
if (result._tag === "Failure") {
|
|
@@ -19,6 +21,7 @@ export async function loadQuery<A, E>(
|
|
|
19
21
|
}
|
|
20
22
|
return result.success;
|
|
21
23
|
} catch (error) {
|
|
24
|
+
release();
|
|
22
25
|
if (signal.aborted) {
|
|
23
26
|
const canceled = new Error("Query loading canceled", { cause: error });
|
|
24
27
|
canceled.name = "AbortError";
|