@shivaedev/effect-react 0.6.0 → 0.8.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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +64 -11
  3. package/dist/client/editor.d.ts +8 -0
  4. package/dist/client/editor.d.ts.map +1 -1
  5. package/dist/client/editor.js +16 -7
  6. package/dist/client/editor.js.map +1 -1
  7. package/dist/client/mountLive.d.ts +3 -0
  8. package/dist/client/mountLive.d.ts.map +1 -0
  9. package/dist/client/mountLive.js +29 -0
  10. package/dist/client/mountLive.js.map +1 -0
  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.d.ts.map +1 -1
  19. package/dist/client/sessionBoundary.js +4 -1
  20. package/dist/client/sessionBoundary.js.map +1 -1
  21. package/dist/client/testing.d.ts +5 -2
  22. package/dist/client/testing.d.ts.map +1 -1
  23. package/dist/client/testing.js +22 -2
  24. package/dist/client/testing.js.map +1 -1
  25. package/dist/client/useLoaderQuery.d.ts +8 -0
  26. package/dist/client/useLoaderQuery.d.ts.map +1 -0
  27. package/dist/client/useLoaderQuery.js +6 -0
  28. package/dist/client/useLoaderQuery.js.map +1 -0
  29. package/dist/internal/loadedQuery.d.ts +16 -0
  30. package/dist/internal/loadedQuery.d.ts.map +1 -0
  31. package/dist/internal/loadedQuery.js +11 -0
  32. package/dist/internal/loadedQuery.js.map +1 -0
  33. package/dist/internal/queryHandoff.d.ts +5 -0
  34. package/dist/internal/queryHandoff.d.ts.map +1 -0
  35. package/dist/internal/queryHandoff.js +35 -0
  36. package/dist/internal/queryHandoff.js.map +1 -0
  37. package/dist/loadQuery.d.ts.map +1 -1
  38. package/dist/loadQuery.js +3 -0
  39. package/dist/loadQuery.js.map +1 -1
  40. package/package.json +10 -9
  41. package/src/client/editor.ts +29 -8
  42. package/src/client/mountLive.ts +30 -0
  43. package/src/client/pendingCommand.ts +42 -0
  44. package/src/client/resultState.ts +2 -0
  45. package/src/client/sessionBoundary.ts +4 -1
  46. package/src/client/testing.ts +25 -3
  47. package/src/client/useLoaderQuery.ts +12 -0
  48. package/src/internal/loadedQuery.ts +18 -0
  49. package/src/internal/queryHandoff.ts +38 -0
  50. package/src/loadQuery.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 - 2026-10-18
4
+
5
+ ### Added
6
+
7
+ - Session boundaries mount automatic live connections on bound contract APIs returned by `connect`, either directly or as properties such as `client.api`. Connections follow the session registry lifetime and query keys. `client/mountLive.ts` also exports `mountLive(registry, client)`, returning the release function for a custom owner.
8
+
9
+ ### Breaking
10
+
11
+ - `SessionBoundary` now connects live queries automatically when its client contains a contract binding. Remove application `live()` calls and connection mounts; serve the contract through a Platform database host with NDJSON serialization.
12
+
13
+ ## 0.7.0 - 2026-10-18
14
+
15
+ ### Added
16
+
17
+ - Pass React's `RootOptions` to test `render(node, options)`, including `onCaughtError`, `onUncaughtError` and `onRecoverableError`.
18
+ - 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.
19
+ - `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.
20
+ - `useEditor({ ...config, loaded: routeLoaderData })` and `LoadedEditor` provide a defined form and direct query data for loader-backed editors.
21
+ - `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.
22
+ - 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.
23
+
24
+ ### Fixed
25
+
26
+ - 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.
27
+
3
28
  ## 0.6.0 - 2026-10-18
4
29
 
5
30
  ### Added
package/README.md CHANGED
@@ -28,7 +28,7 @@ State belongs to an **atom in a registry**. A query hook reads that atom's nativ
28
28
 
29
29
  An **editor** joins a query atom, an effect-form field schema and a save Effect. The query provides the row; `values(row)` converts it to input values; the schema decodes those values before the save runs. effect-form owns the draft and the per-field merge. A **create form** uses the same submission rules without a query and starts a fresh form after success.
30
30
 
31
- A **session generation** is the string returned by `identify(session)`. `SessionBoundary` owns its client, registry and React subtree. Your auth owner supplies the session and decides when to replace or end it. Cache policy, RPC declarations and transport stay with native Effect and the application; this package supplies their React state and lifecycle boundary.
31
+ A **session generation** is the string returned by `identify(session)`. `SessionBoundary` owns its client, registry and React subtree. Your auth owner supplies the session and decides when to replace or end it. The boundary automatically mounts live connections on bound contract APIs returned by `connect`, directly or as client properties such as `api`. Use a Platform database host and NDJSON transport; the application needs no subscribe handler or `live()` call. Cache policy, RPC declarations and transport stay with native Effect and the application; this package supplies their React state and lifecycle boundary.
32
32
 
33
33
  `useEditor` and `useCreate` opt into persisted input with `persistedDraft: { key }`. Choose a stable key for the record or create request, independent of its server revision. The adapters use effect-form's browser storage and inherit ownership from `SessionBoundary`: reload and query refresh preserve edits, successful submission clears accepted input, and a committed session replacement drops tracked drafts. A suspended replacement that is canceled keeps the visible session's draft and future writes. Later edits made during a save remain persisted. Optional `storage`, `maxBytes` and `maxAgeMs` customize the provider and bounds; defaults are `localStorage`, 256 KiB of UTF-8 JSON and seven days. See [effect-form's persisted input API](https://github.com/ShivaeDev/platform/tree/main/packages/effect-form#persist-input-across-reloads) for direct form hooks and limits.
34
34
 
@@ -166,6 +166,38 @@ function SignInButton({ email, password }: { email: string; password: string })
166
166
 
167
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
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
+
169
201
  ### Route loaders with TanStack Router
170
202
 
171
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.
@@ -174,17 +206,17 @@ This example assumes an application-owned `api.get.query({ id })` returning `{ i
174
206
 
175
207
  ```tsx
176
208
  import { RegistryContext } from "@effect/atom-react";
177
- import { createRootRouteWithContext, createRoute, createRouter, Outlet, RouterProvider, useRouterState } from "@tanstack/react-router";
209
+ import { createRootRouteWithContext, createRoute, createRouter, RouterProvider, useRouterState } from "@tanstack/react-router";
178
210
  import type { ErrorComponentProps } from "@tanstack/react-router";
179
211
  import { Predicate } from "effect";
212
+ import type { ReactNode } from "react";
180
213
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
181
214
  import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
215
+ import { useLoaderQuery } from "@shivaedev/effect-react/client/useLoaderQuery.ts";
182
216
  import { api } from "./notes-client.ts";
183
217
 
184
218
  const registry = AtomRegistry.make();
185
- const root = createRootRouteWithContext<{ registry: AtomRegistry.AtomRegistry }>()({
186
- component: Shell,
187
- });
219
+ const root = createRootRouteWithContext<{ registry: AtomRegistry.AtomRegistry }>()();
188
220
  const note = createRoute({
189
221
  getParentRoute: () => root,
190
222
  path: "/notes/$id",
@@ -196,6 +228,7 @@ const router = createRouter({
196
228
  context: { registry },
197
229
  routeTree: root.addChildren([note]),
198
230
  defaultPendingMs: Infinity,
231
+ InnerWrap: Shell,
199
232
  });
200
233
 
201
234
  declare module "@tanstack/react-router" {
@@ -204,13 +237,13 @@ declare module "@tanstack/react-router" {
204
237
  }
205
238
  }
206
239
 
207
- function Shell() {
240
+ function Shell({ children }: { readonly children: ReactNode }) {
208
241
  const pending = useRouterState({ select: (state) => state.isLoading });
209
- return <main>{pending && <p>Loading…</p>}<Outlet /></main>;
242
+ return <main>{pending && <p>Loading…</p>}{children}</main>;
210
243
  }
211
244
  function Note() {
212
- const row = note.useLoaderData();
213
- return <h1>{row.title}</h1>;
245
+ const query = useLoaderQuery(api.get.query(note.useParams()), note.useLoaderData());
246
+ return <h1>{query.data.title}</h1>;
214
247
  }
215
248
  function Failure({ error }: ErrorComponentProps) {
216
249
  return <p role="alert">{Predicate.isTagged(error, "NoteMissing") ? "Note missing" : "Could not load"}</p>;
@@ -220,7 +253,11 @@ export function App() {
220
253
  }
221
254
  ```
222
255
 
223
- `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.
224
261
 
225
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`.
226
263
 
@@ -468,7 +505,23 @@ it("renders a query and refetches it after a command through a test RPC layer",
468
505
  });
469
506
  ```
470
507
 
471
- `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
+ ```
472
525
 
473
526
  The RPC layer stays application-owned: replace these in-memory handlers with the service Layer your component needs.
474
527
 
@@ -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":"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,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAmB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAkB,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8B9D,MAAM,UAAU,SAAS,CACxB,MAA2G;IAE3G,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,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/G,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,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,GAAG;YACT,IAAI,EAAE,UAAU,CAAC;gBAChB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBACvC,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,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAC5C,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,IAAI,IAAI,MAAM,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChC,CAAC;IACF,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7B,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,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
+ export declare function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown): () => void;
3
+ //# sourceMappingURL=mountLive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mountLive.d.ts","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAGpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,cAwB7E"}
@@ -0,0 +1,29 @@
1
+ import { Predicate } from "effect";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import { LiveConnection } from "@shivaedev/effect-contract/automaticLive.ts";
4
+ export function mountLive(registry, client) {
5
+ const releases = [];
6
+ const found = new Set();
7
+ function mount(value) {
8
+ if (!Predicate.hasProperty(value, LiveConnection)) {
9
+ return;
10
+ }
11
+ const live = value[LiveConnection];
12
+ if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
13
+ found.add(live.connection);
14
+ releases.push(registry.mount(live.connection));
15
+ }
16
+ }
17
+ mount(client);
18
+ if (typeof client === "object" && client !== null) {
19
+ for (const value of Object.values(client)) {
20
+ mount(value);
21
+ }
22
+ }
23
+ return () => {
24
+ for (const release of releases) {
25
+ release();
26
+ }
27
+ };
28
+ }
29
+ //# sourceMappingURL=mountLive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mountLive.js","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAE7E,MAAM,UAAU,SAAS,CAAC,QAAmC,EAAE,MAAe;IAC7E,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAW,CAAC;IACjC,SAAS,KAAK,CAAC,KAAc;QAC5B,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC;YACnD,OAAO;QACR,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;QACnC,IAAI,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9G,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAChD,CAAC;IACF,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACnD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3C,KAAK,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACF,CAAC;IACD,OAAO,GAAG,EAAE;QACX,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
@@ -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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAIhH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAuDD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAUrD"}
1
+ {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAKhH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAyDD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAUrD"}
@@ -3,13 +3,14 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import { createContext, createElement, Fragment, useContext, useEffect, useState } from "react";
4
4
  import { DraftSessionContext } from "@shivaedev/effect-form/client/react.ts";
5
5
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
6
+ import { mountLive } from "./mountLive.js";
6
7
  import { useDraftOwner } from "./useDraftOwner.js";
7
8
  const RecheckContext = createContext(() => undefined);
8
9
  export function useSessionRecheck() {
9
10
  return useContext(RecheckContext);
10
11
  }
11
12
  function owning(client) {
12
- return { client, mounted: 0, registry: AtomRegistry.make() };
13
+ return { client, mounted: 0, registry: AtomRegistry.make({ "defaultIdleTTL": 400 }) };
13
14
  }
14
15
  // No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
15
16
  function hidden(client) {
@@ -34,7 +35,9 @@ function Generation({ session, connect, recheck, children }) {
34
35
  useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
35
36
  useEffect(() => {
36
37
  owned.mounted += 1;
38
+ const stopLive = mountLive(owned.registry, owned.client);
37
39
  return () => {
40
+ stopLive();
38
41
  owned.mounted -= 1;
39
42
  queueMicrotask(() => {
40
43
  if (owned.mounted > 0) {
@@ -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.6.0",
3
+ "version": "0.8.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": {
@@ -43,7 +43,7 @@
43
43
  "README.md"
44
44
  ],
45
45
  "dependencies": {
46
- "@shivaedev/effect-contract": "0.5.1",
46
+ "@shivaedev/effect-contract": "0.6.0",
47
47
  "@shivaedev/effect-form": "0.5.0"
48
48
  },
49
49
  "devDependencies": {
@@ -51,14 +51,15 @@
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.10.0",
54
+ "@shivaedev/effect-pg-boss": "0.12.0",
55
55
  "@shivaedev/effect-service": "0.4.0",
56
- "@shivaedev/effect-sql": "0.5.2",
57
- "@shivaedev/effect-test": "0.6.0",
58
- "@shivaedev/local-postgres": "0.4.1",
59
- "@shivaedev/platform": "0.17.0",
60
- "@shivaedev/quality": "0.27.0",
61
- "@shivaedev/test-story": "0.9.0",
56
+ "@shivaedev/effect-sql": "0.7.0",
57
+ "@shivaedev/effect-test": "0.7.1",
58
+ "@shivaedev/effect-test-sql": "0.6.1",
59
+ "@shivaedev/local-postgres": "0.4.2",
60
+ "@shivaedev/platform": "0.19.0",
61
+ "@shivaedev/quality": "0.28.0",
62
+ "@shivaedev/test-story": "0.12.0",
62
63
  "@tanstack/react-router": "1.170.42",
63
64
  "@types/node": "24.19.2",
64
65
  "@types/react": "19.3.0",
@@ -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,30 @@
1
+ import { Predicate } from "effect";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
+ import { LiveConnection } from "@shivaedev/effect-contract/automaticLive.ts";
5
+
6
+ export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown) {
7
+ const releases: Array<() => void> = [];
8
+ const found = new Set<unknown>();
9
+ function mount(value: unknown) {
10
+ if (!Predicate.hasProperty(value, LiveConnection)) {
11
+ return;
12
+ }
13
+ const live = value[LiveConnection];
14
+ if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
15
+ found.add(live.connection);
16
+ releases.push(registry.mount(live.connection));
17
+ }
18
+ }
19
+ mount(client);
20
+ if (typeof client === "object" && client !== null) {
21
+ for (const value of Object.values(client)) {
22
+ mount(value);
23
+ }
24
+ }
25
+ return () => {
26
+ for (const release of releases) {
27
+ release();
28
+ }
29
+ };
30
+ }
@@ -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 }));
@@ -3,6 +3,7 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import { createContext, createElement, Fragment, type ReactNode, useContext, useEffect, useState } from "react";
4
4
  import { DraftSessionContext } from "@shivaedev/effect-form/client/react.ts";
5
5
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
6
+ import { mountLive } from "./mountLive.ts";
6
7
  import { useDraftOwner } from "./useDraftOwner.ts";
7
8
  export interface SessionBoundaryProps<TSession, TClient> {
8
9
  readonly children: (client: TClient) => ReactNode;
@@ -22,7 +23,7 @@ interface Owned<TClient> {
22
23
  readonly registry: AtomRegistry.AtomRegistry;
23
24
  }
24
25
  function owning<TClient>(client: TClient): Owned<TClient> {
25
- return { client, mounted: 0, registry: AtomRegistry.make() };
26
+ return { client, mounted: 0, registry: AtomRegistry.make({ "defaultIdleTTL": 400 }) };
26
27
  }
27
28
  // No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
28
29
  function hidden<TClient>(client: TClient): Owned<TClient> {
@@ -53,7 +54,9 @@ function Generation<TSession, TClient>({ session, connect, recheck, children }:
53
54
  useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
54
55
  useEffect(() => {
55
56
  owned.mounted += 1;
57
+ const stopLive = mountLive(owned.registry, owned.client);
56
58
  return () => {
59
+ stopLive();
57
60
  owned.mounted -= 1;
58
61
  queueMicrotask(() => {
59
62
  if (owned.mounted > 0) {
@@ -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";