@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +88 -10
  3. package/dist/client/authAction.d.ts +15 -0
  4. package/dist/client/authAction.d.ts.map +1 -0
  5. package/dist/client/authAction.js +11 -0
  6. package/dist/client/authAction.js.map +1 -0
  7. package/dist/client/editor.d.ts +8 -0
  8. package/dist/client/editor.d.ts.map +1 -1
  9. package/dist/client/editor.js +16 -7
  10. package/dist/client/editor.js.map +1 -1
  11. package/dist/client/pendingCommand.d.ts +13 -0
  12. package/dist/client/pendingCommand.d.ts.map +1 -0
  13. package/dist/client/pendingCommand.js +30 -0
  14. package/dist/client/pendingCommand.js.map +1 -0
  15. package/dist/client/resultState.d.ts.map +1 -1
  16. package/dist/client/resultState.js +2 -0
  17. package/dist/client/resultState.js.map +1 -1
  18. package/dist/client/sessionBoundary.js +1 -1
  19. package/dist/client/sessionBoundary.js.map +1 -1
  20. package/dist/client/testing.d.ts +5 -2
  21. package/dist/client/testing.d.ts.map +1 -1
  22. package/dist/client/testing.js +22 -2
  23. package/dist/client/testing.js.map +1 -1
  24. package/dist/client/useLoaderQuery.d.ts +8 -0
  25. package/dist/client/useLoaderQuery.d.ts.map +1 -0
  26. package/dist/client/useLoaderQuery.js +6 -0
  27. package/dist/client/useLoaderQuery.js.map +1 -0
  28. package/dist/internal/loadedQuery.d.ts +16 -0
  29. package/dist/internal/loadedQuery.d.ts.map +1 -0
  30. package/dist/internal/loadedQuery.js +11 -0
  31. package/dist/internal/loadedQuery.js.map +1 -0
  32. package/dist/internal/queryHandoff.d.ts +5 -0
  33. package/dist/internal/queryHandoff.d.ts.map +1 -0
  34. package/dist/internal/queryHandoff.js +35 -0
  35. package/dist/internal/queryHandoff.js.map +1 -0
  36. package/dist/loadQuery.d.ts.map +1 -1
  37. package/dist/loadQuery.js +3 -0
  38. package/dist/loadQuery.js.map +1 -1
  39. package/package.json +7 -7
  40. package/src/client/authAction.ts +22 -0
  41. package/src/client/editor.ts +29 -8
  42. package/src/client/pendingCommand.ts +42 -0
  43. package/src/client/resultState.ts +2 -0
  44. package/src/client/sessionBoundary.ts +1 -1
  45. package/src/client/testing.ts +25 -3
  46. package/src/client/useLoaderQuery.ts +12 -0
  47. package/src/internal/loadedQuery.ts +18 -0
  48. package/src/internal/queryHandoff.ts +38 -0
  49. 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, Outlet, RouterProvider, useRouterState } from "@tanstack/react-router";
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>}<Outlet /></main>;
242
+ return <main>{pending && <p>Loading…</p>}{children}</main>;
185
243
  }
186
244
  function Note() {
187
- const row = note.useLoaderData();
188
- return <h1>{row.title}</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"}
@@ -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;AAE9E,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,gBAAgB,CAAC;AAC9D,MAAM,MAAM,YAAY,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAE,aAAa,IAAI;IACnG,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;CACxE,GAAG,qBAAqB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AAC/C,MAAM,WAAW,MAAM,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAE,SAAQ,SAAS,CAAC,UAAU,GAAG,aAAa,CAAC;IAC1I,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,EAAE,aAAa,CAAC,GAAG,SAAS,CAAC;IACzF,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;CAC9C;AAMD,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAE,aAAa,EAChG,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAE,aAAa,CAAC,GAC5E,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,CA8B/D"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/client/editor.ts"],"names":[],"mappings":"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"}
@@ -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.getOrUndefined(query.data);
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 !== undefined) {
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 && row !== undefined) {
32
- form.receive(values(row));
33
+ if (form && values && received !== undefined) {
34
+ form.receive(values(received));
33
35
  }
34
- }, [form, values, row]);
36
+ }, [form, values, received]);
35
37
  const settled = useCallback((saved) => (form && values ? form.receive(values(saved)) : undefined), [form, values]);
36
- return { ...useSaveState(form, settled), form, query };
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;AAkB9D,MAAM,UAAU,SAAS,CACxB,MAA8E;IAE9E,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,QAAQ,CAA6D,SAAS,CAAC,CAAC;IACrG,IAAI,OAAO,GAAG,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAChD,OAAO,GAAG;YACT,IAAI,EAAE,UAAU,CAAC;gBAChB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;gBACjC,cAAc;gBACd,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;aACjB,CAAC,CAAC,IAAI;YACP,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;SACrB,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC3B,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,IAAI,IAAI,MAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3B,CAAC;IACF,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,KAAW,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACzH,OAAO,EAAE,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACxD,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;AAIpD,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,CAe3H;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"}
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":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAG7D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQzD,SAAS,KAAK,CAAO,MAAqC,EAAE,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3F,OAAO;QACN,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC;QAChC,IAAI;QACJ,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACjD,MAAM;KACN,CAAC;AACH,CAAC;AACD,MAAM,UAAU,cAAc,CAAI,KAAqB;IACtD,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC;AAC1I,CAAC;AACD,SAAS,wBAAwB,CAAO,MAAqC;IAC5E,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACnE,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AACvB,CAAC;AAeD,MAAM,UAAU,QAAQ,CAAO,IAA8C,EAAE,OAAO,GAAiB,EAAE;IACxG,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC7C,SAAS,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IACrE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,KAAK,IAAI,CAAC;IAC3D,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAkB,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACvI,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAK,CAAC;IAC3H,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;IAC7F,kGAAkG;IAClG,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,gBAAgB,KAAK,gBAAgB,EAAE,CAAC;QAC9H,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;AAClE,CAAC;AAID,6GAA6G;AAC7G,MAAM,UAAU,SAAS,CAAe,IAAqC;IAC5E,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;AACzD,CAAC"}
@@ -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":"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"}
1
+ {"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
2
  import { type ReactNode } from "react";
3
- export declare function expectNoReactWarnings(): void;
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;AAG3D,wBAAgB,qBAAqB,SASpC;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,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4B/D"}
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"}
@@ -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":"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"}
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,6 @@
1
+ import { loadedQuery } from "#internal/loadedQuery.ts";
2
+ import { useQuery } from "./resultState.js";
3
+ export function useLoaderQuery(atom, loaded) {
4
+ return loadedQuery(useQuery(atom), loaded);
5
+ }
6
+ //# sourceMappingURL=useLoaderQuery.js.map
@@ -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"}
@@ -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;AAG/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,CAiBZ"}
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";
@@ -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.5.1",
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.9.0",
54
+ "@shivaedev/effect-pg-boss": "0.10.1",
55
55
  "@shivaedev/effect-service": "0.4.0",
56
- "@shivaedev/effect-sql": "0.5.2",
56
+ "@shivaedev/effect-sql": "0.6.0",
57
57
  "@shivaedev/effect-test": "0.6.0",
58
- "@shivaedev/local-postgres": "0.4.0",
59
- "@shivaedev/platform": "0.16.1",
60
- "@shivaedev/quality": "0.26.0",
61
- "@shivaedev/test-story": "0.8.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
+ }
@@ -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.getOrUndefined(query.data);
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 !== undefined) {
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 && row !== undefined) {
55
- form.receive(values(row));
68
+ if (form && values && received !== undefined) {
69
+ form.receive(values(received));
56
70
  }
57
- }, [form, values, row]);
71
+ }, [form, values, received]);
58
72
  const settled = useCallback((saved: TRow) => (form && values ? form.receive(values(saved)) : undefined), [form, values]);
59
- return { ...useSaveState(form, settled), form, query };
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> {
@@ -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";