@shivaedev/react 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.1 - 2026-10-30
4
+
5
+ ### Changed
6
+
7
+ - Require Effect 4.0.6 and align the Effect companion package peers and generated starter catalog to the same exact release. Install the 4.0.6 Effect stack together.
8
+
9
+ ### Fixed
10
+
11
+ - Register hosted RPC cache-control handling through Effect 4.0.6's request-scoped pre-response API so responses keep `private, no-store`.
12
+ - Accept native RPC's `application/x-ndjson` media type while preserving `application/ndjson` callers at the transport guard.
13
+ - Keep subscriptions open after their HTTP request body completes; closing the socket still cancels them.
14
+ - Keep sign-out requests and their sign-in redirects alive when the session boundary replaces its subtree before the response arrives.
15
+
16
+ ## 0.22.0 - 2026-10-29
17
+
18
+ ### Added
19
+
20
+ - Forwarding proxy fixtures relay WebSocket upgrades, including the same origin and trusted proxy headers as HTTP requests. Existing proxy URLs also work for socket clients.
21
+
22
+ ### Breaking
23
+
24
+ - Contract clients use Effect RPC over a shared WebSocket by default. Replace `httpClient`, `transformClient` and `serialization` client options with a native `protocol` Layer for custom transports. Node clients can supply upgrade `headers` and a `webSocketConstructor` Layer; browsers use their session cookie automatically.
25
+ - Mounting a bound query subscribes before its first authorized read; unmounting releases its subscription. Use `live: false` when binding an ordinary client without subscriptions. Query preparation alone no longer opens a change stream.
26
+ - Hosts serve live subscriptions on the WebSocket upgrade at the RPC path. Move HTTP `$changes` consumers to Effect RPC's socket protocol; the POST route serves ordinary RPC only. Configure development proxies to forward WebSocket upgrades.
27
+ - React session boundaries bind shared sockets to the identified session, so replacing an account upgrades with its current cookie. They also ask the auth owner to recheck when the live socket closes. Keep `recheck` safe to call after a network interruption; the auth owner still decides whether the session ends.
28
+
3
29
  ## 0.21.1 - 2026-10-29
4
30
 
5
31
  ### Changed
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, a 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. 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. 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. Live changed hints coalesce over 20 ms by default. Set `liveCoalesceWindow` on `SessionBoundary` to a native Duration to choose the window for that session, or `0` for immediate per-hint refreshes. A bound query can override that default with `useQuery(atom, { coalesceWindow: "2 seconds" })`. Changing this option applies to subsequent windows without reconnecting or refetching any query; a window already started keeps its deadline. A refetch in flight runs once more after settling when further hints arrive. 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 the default shared WebSocket client; the application needs no subscribe handler or `live()` call. Live changed hints coalesce over 20 ms by default. Set `liveCoalesceWindow` on `SessionBoundary` to a native Duration to choose the window for that session, or `0` for immediate per-hint refreshes. A bound query can override that default with `useQuery(atom, { coalesceWindow: "2 seconds" })`. Changing this option applies to subsequent windows without reconnecting or refetching any query; a window already started keeps its deadline. A refetch in flight runs once more after settling when further hints arrive. 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 form's browser storage and inherit ownership from `SessionBoundary`: reload and query refresh preserve edits, successful submission clears accepted input, and a committed session replacement drops tracked drafts. A suspended replacement that is canceled keeps the visible session's draft and future writes. Later edits made during a save remain persisted. Optional `storage`, `maxBytes` and `maxAgeMs` customize the provider and bounds; defaults are `localStorage`, 256 KiB of UTF-8 JSON and seven days. See [form's persisted input API](https://github.com/ShivaeDev/platform/tree/main/packages/form#persist-input-across-reloads) for direct form hooks and limits.
34
34
 
@@ -79,9 +79,9 @@ export function App({ session, recheck }: { readonly session: Session | undefine
79
79
  }
80
80
  ```
81
81
 
82
- `makeOrderClient` and `OrderScreens` belong to the application. A changed generation creates a new client and registry, remounts the screens and disposes the outgoing registry. `undefined` renders `signedOut`. Updating the session object with the same generation keeps the existing client: `connect` does not receive a rotated token until the identity changes. Include the credential generation when the client captures its token, as above.
82
+ `makeOrderClient` and `OrderScreens` belong to the application. A changed generation creates a new client and registry, acquires a socket with the current session cookie, remounts the screens and disposes the outgoing registry. Independent registries for the same generation share the socket. `undefined` renders `signedOut`. Updating the session object with the same generation keeps the existing client: `connect` does not receive a rotated token until the identity changes. Include the credential generation when the client captures its token, as above.
83
83
 
84
- A query that fails with a tagged `Unauthorized` asks `recheck` to run and keeps its retained data. An action does the same once per failed result, retaining its last successful `data` and the typed failure in `cause`; rerendering that result does not repeat the recheck. The auth owner can retry the same session or remove it; the hook does not sign the user out. The boundary also handles React StrictMode's effect replay and preserves component state across hiding and revealing an `<Activity>` subtree, supplying a live registry when revealed.
84
+ A query that fails with a tagged `Unauthorized` asks `recheck` to run and keeps its retained data. An action does the same once per failed result, retaining its last successful `data` and the typed failure in `cause`; rerendering that result does not repeat the recheck. A live socket failure also asks the auth owner to recheck, since the host closes revoked sessions. The auth owner can retry the same session or remove it; the hook does not sign the user out. The boundary also handles React StrictMode's effect replay and preserves component state across hiding and revealing an `<Activity>` subtree, supplying a live registry when revealed.
85
85
 
86
86
  #### Session-scoped bindings
87
87
 
@@ -1 +1 @@
1
- {"version":3,"file":"mountLive.d.ts","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,QAAQ,EAAqB,MAAM,QAAQ,CAAC;AAEjE,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAepE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,cAuC9G"}
1
+ {"version":3,"file":"mountLive.d.ts","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,QAAQ,EAAqB,MAAM,QAAQ,CAAC;AAEjE,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAYpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,cA8B9G"}
@@ -1,23 +1,18 @@
1
1
  import { Cause, Option, Predicate } from "effect";
2
2
  import * as Atom from "effect/reactivity/Atom";
3
3
  import { LiveConnection } from "@shivaedev/contract/automaticLive.ts";
4
+ import { liveBindings } from "#internal/liveBindings.ts";
4
5
  import { recheckUnauthorized } from "#internal/sessionRecheck.ts";
5
- function isLiveBound(value) {
6
- return Predicate.hasProperty(value, LiveConnection);
7
- }
8
6
  function recheckFailure(registry, failure) {
9
7
  const error = Option.flatMap(failure, Cause.findErrorOption);
10
8
  if (Option.isSome(error)) {
11
- void recheckUnauthorized(registry, error.value);
9
+ void recheckUnauthorized(registry, error.value, true);
12
10
  }
13
11
  }
14
12
  export function mountLive(registry, client, coalesceWindow) {
15
13
  const releases = [];
16
14
  const found = new Set();
17
- function mount(value) {
18
- if (!isLiveBound(value)) {
19
- return;
20
- }
15
+ for (const value of liveBindings(client)) {
21
16
  const live = value[LiveConnection];
22
17
  if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
23
18
  if (coalesceWindow !== undefined
@@ -31,12 +26,6 @@ export function mountLive(registry, client, coalesceWindow) {
31
26
  releases.push(registry.mount(live.connection));
32
27
  }
33
28
  }
34
- mount(client);
35
- if (typeof client === "object" && client !== null) {
36
- for (const value of Object.values(client)) {
37
- mount(value);
38
- }
39
- }
40
29
  return () => {
41
30
  for (const release of releases) {
42
31
  release();
@@ -1 +1 @@
1
- {"version":3,"file":"mountLive.js","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAkB,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,SAAS,WAAW,CAAC,KAAc;IAClC,OAAO,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,cAAc,CAAC,QAAmC,EAAE,OAA4C;IACxG,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;AACF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAmC,EAAE,MAAe,EAAE,cAA+B;IAC9G,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAW,CAAC;IACjC,SAAS,KAAK,CAAC,KAAc;QAC5B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,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,IACC,cAAc,KAAK,SAAS;mBACzB,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC;mBAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;mBAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,EACtC,CAAC;gBACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;YACnD,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3B,QAAQ,CAAC,IAAI,CACZ,QAAQ,CAAC,SAAS,CACjB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EACjD,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9C,EAAE,SAAS,EAAE,IAAI,EAAE,CACnB,CACD,CAAC;YACF,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"}
1
+ {"version":3,"file":"mountLive.js","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,SAAS,cAAc,CAAC,QAAmC,EAAE,OAA4C;IACxG,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvD,CAAC;AACF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAmC,EAAE,MAAe,EAAE,cAA+B;IAC9G,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAW,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,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,IACC,cAAc,KAAK,SAAS;mBACzB,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC;mBAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;mBAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,EACtC,CAAC;gBACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;YACnD,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3B,QAAQ,CAAC,IAAI,CACZ,QAAQ,CAAC,SAAS,CACjB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EACjD,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9C,EAAE,SAAS,EAAE,IAAI,EAAE,CACnB,CACD,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAChD,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAMhH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,YAAY,KAAK,OAAO,CAAC;IACtF,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAqED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAUrD"}
1
+ {"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAA0C,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAShH,MAAM,WAAW,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,YAAY,KAAK,OAAO,CAAC;IACtF,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAE9C;AAmFD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAClD,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACR,EAAE,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,SAAS,CAerD"}
@@ -1,7 +1,10 @@
1
1
  import { RegistryContext } from "@effect/atom-react";
2
2
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import { createContext, createElement, Fragment, useContext, useEffect, useState } from "react";
4
+ import { LiveConnection } from "@shivaedev/contract/automaticLive.ts";
4
5
  import { DraftSessionContext } from "@shivaedev/form/client/react.ts";
6
+ import { liveBindings } from "#internal/liveBindings.ts";
7
+ import { SignOutRegistryContext } from "#internal/SignOutRegistryContext.ts";
5
8
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
6
9
  import { useLatestCallback } from "#internal/useLatestCallback.ts";
7
10
  import { mountLive } from "./mountLive.js";
@@ -10,12 +13,18 @@ const RecheckContext = createContext(() => undefined);
10
13
  export function useSessionRecheck() {
11
14
  return useContext(RecheckContext);
12
15
  }
13
- function owning(connect) {
16
+ function owning(connect, identity) {
14
17
  const registry = AtomRegistry.make({ "defaultIdleTTL": 400 });
15
18
  let client;
16
19
  return {
17
20
  get client() {
18
21
  client ??= { value: connect(registry) };
22
+ for (const binding of liveBindings(client.value)) {
23
+ const session = binding[LiveConnection].identity;
24
+ if (session !== undefined) {
25
+ registry.set(session, identity);
26
+ }
27
+ }
19
28
  return client.value;
20
29
  },
21
30
  mounted: 0,
@@ -23,8 +32,8 @@ function owning(connect) {
23
32
  };
24
33
  }
25
34
  // No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
26
- function hidden(connect) {
27
- const owned = owning(connect);
35
+ function hidden(connect, identity) {
36
+ const owned = owning(connect, identity);
28
37
  let reaping = false;
29
38
  owned.registry.onNodeAdded = () => {
30
39
  if (reaping || owned.mounted > 0) {
@@ -40,10 +49,10 @@ function hidden(connect) {
40
49
  };
41
50
  return owned;
42
51
  }
43
- function Generation({ session, connect, recheck, children, liveCoalesceWindow }) {
52
+ function Generation({ session, connect, recheck, children, identity, liveCoalesceWindow, }) {
44
53
  const recheckLatest = useLatestCallback(recheck);
45
54
  const [open] = useState(() => (registry) => connect(session, registry));
46
- const [owned, own] = useState(() => owning(open));
55
+ const [owned, own] = useState(() => owning(open, identity));
47
56
  useEffect(() => registerSessionRecheck(owned.registry, recheckLatest), [owned, recheckLatest]);
48
57
  useEffect(() => {
49
58
  owned.mounted += 1;
@@ -56,17 +65,18 @@ function Generation({ session, connect, recheck, children, liveCoalesceWindow })
56
65
  return;
57
66
  }
58
67
  owned.registry.dispose();
59
- own((current) => (current === owned ? hidden(open) : current));
68
+ own((current) => (current === owned ? hidden(open, identity) : current));
60
69
  });
61
70
  };
62
- }, [owned, liveCoalesceWindow, open]);
71
+ }, [identity, owned, liveCoalesceWindow, open]);
63
72
  return createElement(RecheckContext.Provider, { value: recheckLatest }, createElement(RegistryContext.Provider, { value: owned.registry }, children(owned.client)));
64
73
  }
65
74
  export function SessionBoundary({ session, liveCoalesceWindow, identify, connect, recheck, signedOut, children, }) {
75
+ const registry = useContext(RegistryContext);
66
76
  const id = session === undefined ? undefined : identify(session);
67
77
  const current = useDraftOwner(id);
68
78
  return session === undefined
69
79
  ? createElement(Fragment, null, signedOut)
70
- : createElement(DraftSessionContext.Provider, { value: current }, createElement(Generation, { children, connect, key: id, liveCoalesceWindow, recheck, session }));
80
+ : createElement(SignOutRegistryContext.Provider, { value: registry }, createElement(DraftSessionContext.Provider, { value: current }, createElement(Generation, { children, connect, identity: id, key: id, liveCoalesceWindow, recheck, session })));
71
81
  }
72
82
  //# sourceMappingURL=sessionBoundary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAkB,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChH,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUnD,MAAM,cAAc,GAAG,aAAa,CAAa,GAAS,EAAE,CAAC,SAAS,CAAC,CAAC;AACxE,MAAM,UAAU,iBAAiB;IAChC,OAAO,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,CAAC;AAMD,SAAS,MAAM,CAAU,OAAyD;IACjF,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9D,IAAI,MAA+C,CAAC;IACpD,OAAO;QACN,IAAI,MAAM;YACT,MAAM,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,OAAO,MAAM,CAAC,KAAK,CAAC;QACrB,CAAC;QACD,OAAO,EAAE,CAAC;QACV,QAAQ;KACR,CAAC;AACH,CAAC;AACD,uHAAuH;AACvH,SAAS,MAAM,CAAU,OAAyD;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,CAAC,QAAQ,CAAC,WAAW,GAAG,GAAG,EAAE;QACjC,IAAI,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO;QACR,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;QACf,UAAU,CAAC,GAAG,EAAE;YACf,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;QACF,CAAC,EAAE,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACd,CAAC;AAQD,SAAS,UAAU,CAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAsC;IACrI,MAAM,aAAa,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,QAAmC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnG,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,SAAS,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC;IAC/F,SAAS,CAAC,GAAG,EAAE;QACd,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACnB,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;QAC7E,OAAO,GAAG,EAAE;YACX,QAAQ,EAAE,CAAC;YACX,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACnB,cAAc,CAAC,GAAG,EAAE;gBACnB,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;oBACvB,OAAO;gBACR,CAAC;gBACD,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACzB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC;IACtC,OAAO,aAAa,CACnB,cAAc,CAAC,QAAQ,EACvB,EAAE,KAAK,EAAE,aAAa,EAAE,EACxB,aAAa,CAAC,eAAe,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAC1F,CAAC;AACH,CAAC;AACD,MAAM,UAAU,eAAe,CAAoB,EAClD,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GACiC;IACzC,MAAM,EAAE,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IAClC,OAAO,OAAO,KAAK,SAAS;QAC3B,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC;QAC1C,CAAC,CAAC,aAAa,CACb,mBAAmB,CAAC,QAAQ,EAC5B,EAAE,KAAK,EAAE,OAAO,EAAE,EAClB,aAAa,CAAC,UAA6B,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAClH,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import * as AsyncResult from "effect/reactivity/AsyncResult";
2
- import type * as Atom from "effect/reactivity/Atom";
1
+ import * as Atom from "effect/reactivity/Atom";
3
2
  declare module "@tanstack/react-router" {
4
3
  interface HistoryState {
5
4
  readonly __platformSignOut?: boolean;
@@ -12,7 +11,7 @@ export declare function useSignOut<A, E>(action: Atom.AtomResultFn<void, A, E>,
12
11
  data: import("effect/Option").Option<A>;
13
12
  pending: boolean;
14
13
  refreshing: boolean;
15
- result: AsyncResult.AsyncResult<A, E>;
14
+ result: import("effect/reactivity/AsyncResult").AsyncResult<A, E>;
16
15
  dispatch: () => void;
17
16
  };
18
17
  //# sourceMappingURL=useSignOut.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSignOut.d.ts","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAIpD,OAAO,QAAQ,wBAAwB,CAAC;IACvC,UAAU,YAAY;QACrB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;KACrC;CACD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;;;EAoB3G"}
1
+ {"version":3,"file":"useSignOut.d.ts","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAM/C,OAAO,QAAQ,wBAAwB,CAAC;IACvC,UAAU,YAAY;QACrB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;KACrC;CACD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;;;EAsC3G"}
@@ -1,25 +1,38 @@
1
+ import { RegistryContext } from "@effect/atom-react";
1
2
  import { useRouter } from "@tanstack/react-router";
2
- import * as AsyncResult from "effect/reactivity/AsyncResult";
3
- import { useEffect } from "react";
3
+ import { Effect } from "effect";
4
+ import * as Atom from "effect/reactivity/Atom";
5
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
6
+ import { useContext, useMemo } from "react";
7
+ import { SignOutRegistryContext } from "#internal/SignOutRegistryContext.ts";
4
8
  import { useAction } from "./resultState.js";
5
9
  export function useSignOut(action, options) {
6
10
  const router = useRouter({ warn: false });
7
- const state = useAction(action);
8
- useEffect(() => {
9
- if (router && AsyncResult.isSuccess(state.result) && !state.pending) {
10
- void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
11
- }
12
- if (router && AsyncResult.isFailure(state.result) && !state.pending) {
11
+ const local = useContext(RegistryContext);
12
+ const registry = useContext(SignOutRegistryContext) ?? local;
13
+ const source = useMemo(() => Atom.writable((get) => {
14
+ get.addFinalizer(registry.subscribe(action, (result) => get.setSelf(result)));
15
+ return registry.get(action);
16
+ }, (_, input) => registry.set(action, input)), [registry, action]);
17
+ const state = useAction(source);
18
+ function failed() {
19
+ if (router) {
13
20
  router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: false });
14
21
  }
15
- }, [router, state.result, state.pending, options.signIn]);
22
+ }
23
+ function completed() {
24
+ if (router) {
25
+ void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
26
+ }
27
+ }
16
28
  return {
17
29
  ...state,
18
30
  dispatch: () => {
19
31
  if (router) {
20
32
  router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: true });
21
33
  }
22
- state.dispatch();
34
+ state.dispatch(undefined);
35
+ Effect.runFork(AtomRegistry.getResult(registry, action, { suspendOnWaiting: true }).pipe(Effect.match({ onFailure: failed, onSuccess: completed })));
23
36
  },
24
37
  };
25
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useSignOut.js","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,MAAM,UAAU,UAAU,CAAO,MAAqC,EAAE,OAAoC;IAC3G,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;QACtH,CAAC;IACF,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,OAAO;QACN,GAAG,KAAK;QACR,QAAQ,EAAE,GAAG,EAAE;YACd,IAAI,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;YACrH,CAAC;YACD,KAAK,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"useSignOut.js","sourceRoot":"","sources":["../../src/client/useSignOut.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,MAAM,UAAU,UAAU,CAAO,MAAqC,EAAE,OAAoC;IAC3G,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,UAAU,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CACrB,GAAG,EAAE,CACJ,IAAI,CAAC,QAAQ,CACZ,CAAC,GAAG,EAAE,EAAE;QACP,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9E,OAAO,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC,EACD,CAAC,CAAC,EAAE,KAA8C,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAClF,EACF,CAAC,QAAQ,EAAE,MAAM,CAAC,CAClB,CAAC;IACF,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,SAAS,MAAM;QACd,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;QACtH,CAAC;IACF,CAAC;IACD,SAAS,SAAS;QACjB,IAAI,MAAM,EAAE,CAAC;YACZ,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;IACF,CAAC;IACD,OAAO;QACN,GAAG,KAAK;QACR,QAAQ,EAAE,GAAG,EAAE;YACd,IAAI,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;YACrH,CAAC;YACD,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAC1B,MAAM,CAAC,OAAO,CACb,YAAY,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CACpI,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
+ export declare const SignOutRegistryContext: import("react").Context<AtomRegistry.AtomRegistry | undefined>;
3
+ //# sourceMappingURL=SignOutRegistryContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignOutRegistryContext.d.ts","sourceRoot":"","sources":["../../src/internal/SignOutRegistryContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAGpE,eAAO,MAAM,sBAAsB,gEAAkE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+ export const SignOutRegistryContext = createContext(undefined);
3
+ //# sourceMappingURL=SignOutRegistryContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignOutRegistryContext.js","sourceRoot":"","sources":["../../src/internal/SignOutRegistryContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,MAAM,CAAC,MAAM,sBAAsB,GAAG,aAAa,CAAwC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type LiveBound } from "@shivaedev/contract/automaticLive.ts";
2
+ export declare function liveBindings(client: unknown): readonly LiveBound[];
3
+ //# sourceMappingURL=liveBindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveBindings.d.ts","sourceRoot":"","sources":["../../src/internal/liveBindings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,sCAAsC,CAAC;AAMtF,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,SAAS,EAAE,CAGlE"}
@@ -0,0 +1,10 @@
1
+ import { Predicate } from "effect";
2
+ import { LiveConnection } from "@shivaedev/contract/automaticLive.ts";
3
+ function isLiveBound(value) {
4
+ return Predicate.hasProperty(value, LiveConnection);
5
+ }
6
+ export function liveBindings(client) {
7
+ const values = typeof client === "object" && client !== null ? [client, ...Object.values(client)] : [client];
8
+ return values.filter(isLiveBound);
9
+ }
10
+ //# sourceMappingURL=liveBindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveBindings.js","sourceRoot":"","sources":["../../src/internal/liveBindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAkB,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtF,SAAS,WAAW,CAAC,KAAc;IAClC,OAAO,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAe;IAC3C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC7G,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACnC,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
2
  export declare function registerSessionRecheck(registry: AtomRegistry.AtomRegistry, recheck: () => void): () => void;
3
- export declare function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown): Promise<void>;
3
+ export declare function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown, connection?: boolean): Promise<void>;
4
4
  //# sourceMappingURL=sessionRecheck.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionRecheck.d.ts","sourceRoot":"","sources":["../../src/internal/sessionRecheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAIpE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK3G;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5G"}
1
+ {"version":3,"file":"sessionRecheck.d.ts","sourceRoot":"","sources":["../../src/internal/sessionRecheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAIpE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK3G;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhI"}
@@ -6,8 +6,8 @@ export function registerSessionRecheck(registry, recheck) {
6
6
  rechecks.delete(registry);
7
7
  };
8
8
  }
9
- export async function recheckUnauthorized(registry, error) {
10
- if (Predicate.isTagged(error, "Unauthorized")) {
9
+ export async function recheckUnauthorized(registry, error, connection = false) {
10
+ if (Predicate.isTagged(error, "Unauthorized") || (connection && Predicate.isTagged(error, "RpcClientError"))) {
11
11
  await rechecks.get(registry)?.();
12
12
  }
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sessionRecheck.js","sourceRoot":"","sources":["../../src/internal/sessionRecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyC,CAAC;AAEtE,MAAM,UAAU,sBAAsB,CAAC,QAAmC,EAAE,OAAmB;IAC9F,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACX,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAmC,EAAE,KAAc;IAC5F,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC;QAC/C,MAAM,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IAClC,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"sessionRecheck.js","sourceRoot":"","sources":["../../src/internal/sessionRecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyC,CAAC;AAEtE,MAAM,UAAU,sBAAsB,CAAC,QAAmC,EAAE,OAAmB;IAC9F,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACX,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAmC,EAAE,KAAc,EAAE,UAAU,GAAG,KAAK;IAChH,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC;QAC9G,MAAM,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IAClC,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/react",
3
- "version": "0.21.1",
3
+ "version": "0.22.1",
4
4
  "description": "React query, action, editor and session hooks for native Effect atoms",
5
5
  "keywords": [
6
6
  "effect"
@@ -46,32 +46,32 @@
46
46
  "README.md"
47
47
  ],
48
48
  "dependencies": {
49
- "@shivaedev/access": "0.1.1",
50
- "@shivaedev/contract": "0.19.1",
51
- "@shivaedev/errors": "0.1.0",
52
- "@shivaedev/form": "0.7.0",
53
- "@shivaedev/test": "0.10.0"
49
+ "@shivaedev/access": "0.1.2",
50
+ "@shivaedev/contract": "0.21.0",
51
+ "@shivaedev/errors": "0.1.1",
52
+ "@shivaedev/form": "0.7.1",
53
+ "@shivaedev/test": "0.10.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@effect/atom-react": "4.0.5",
57
- "@effect/platform-node": "4.0.5",
58
- "@effect/sql-pg": "4.0.5",
59
- "@effect/sql-sqlite-node": "4.0.5",
60
- "@shivaedev/changes": "0.5.0",
61
- "@shivaedev/identity": "0.3.1",
62
- "@shivaedev/jobs": "0.22.0",
56
+ "@effect/atom-react": "4.0.6",
57
+ "@effect/platform-node": "4.0.6",
58
+ "@effect/sql-pg": "4.0.6",
59
+ "@effect/sql-sqlite-node": "4.0.6",
60
+ "@shivaedev/changes": "0.5.1",
61
+ "@shivaedev/identity": "0.4.0",
62
+ "@shivaedev/jobs": "0.22.1",
63
63
  "@shivaedev/local-postgres": "0.7.0",
64
- "@shivaedev/quality": "0.44.0",
65
- "@shivaedev/server": "0.3.1",
66
- "@shivaedev/service": "0.5.0",
67
- "@shivaedev/sql": "0.24.0",
68
- "@shivaedev/test-sql": "0.10.2",
69
- "@shivaedev/test-story": "0.22.0",
70
- "@tanstack/react-router": "1.170.45",
64
+ "@shivaedev/quality": "0.45.0",
65
+ "@shivaedev/server": "0.5.0",
66
+ "@shivaedev/service": "0.5.1",
67
+ "@shivaedev/sql": "0.25.0",
68
+ "@shivaedev/test-sql": "0.11.0",
69
+ "@shivaedev/test-story": "0.24.0",
70
+ "@tanstack/react-router": "1.170.46",
71
71
  "@types/node": "24.19.2",
72
72
  "@types/react": "19.3.0",
73
73
  "@types/react-dom": "19.3.0",
74
- "effect": "4.0.5",
74
+ "effect": "4.0.6",
75
75
  "happy-dom": "21.0.4",
76
76
  "react": "19.3.1",
77
77
  "react-dom": "19.3.1",
@@ -79,9 +79,9 @@
79
79
  "vitest": "5.0.5"
80
80
  },
81
81
  "peerDependencies": {
82
- "@effect/atom-react": "4.0.5",
83
- "@tanstack/react-router": "1.170.45",
84
- "effect": "4.0.5",
82
+ "@effect/atom-react": "4.0.6",
83
+ "@tanstack/react-router": "1.170.46",
84
+ "effect": "4.0.6",
85
85
  "react": "19.3.1",
86
86
  "react-dom": "19.3.1",
87
87
  "vitest": "5.0.5"
@@ -1,27 +1,21 @@
1
1
  import { Cause, type Duration, Option, Predicate } from "effect";
2
2
  import * as Atom from "effect/reactivity/Atom";
3
3
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
- import { type LiveBound, LiveConnection } from "@shivaedev/contract/automaticLive.ts";
4
+ import { LiveConnection } from "@shivaedev/contract/automaticLive.ts";
5
+ import { liveBindings } from "#internal/liveBindings.ts";
5
6
  import { recheckUnauthorized } from "#internal/sessionRecheck.ts";
6
7
 
7
- function isLiveBound(value: unknown): value is LiveBound {
8
- return Predicate.hasProperty(value, LiveConnection);
9
- }
10
-
11
8
  function recheckFailure(registry: AtomRegistry.AtomRegistry, failure: Option.Option<Cause.Cause<unknown>>) {
12
9
  const error = Option.flatMap(failure, Cause.findErrorOption);
13
10
  if (Option.isSome(error)) {
14
- void recheckUnauthorized(registry, error.value);
11
+ void recheckUnauthorized(registry, error.value, true);
15
12
  }
16
13
  }
17
14
 
18
15
  export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown, coalesceWindow?: Duration.Input) {
19
16
  const releases: Array<() => void> = [];
20
17
  const found = new Set<unknown>();
21
- function mount(value: unknown) {
22
- if (!isLiveBound(value)) {
23
- return;
24
- }
18
+ for (const value of liveBindings(client)) {
25
19
  const live = value[LiveConnection];
26
20
  if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
27
21
  if (
@@ -43,12 +37,6 @@ export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown,
43
37
  releases.push(registry.mount(live.connection));
44
38
  }
45
39
  }
46
- mount(client);
47
- if (typeof client === "object" && client !== null) {
48
- for (const value of Object.values(client)) {
49
- mount(value);
50
- }
51
- }
52
40
  return () => {
53
41
  for (const release of releases) {
54
42
  release();
@@ -2,7 +2,10 @@ import { RegistryContext } from "@effect/atom-react";
2
2
  import type { Duration } from "effect";
3
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import { createContext, createElement, Fragment, type ReactNode, useContext, useEffect, useState } from "react";
5
+ import { LiveConnection } from "@shivaedev/contract/automaticLive.ts";
5
6
  import { DraftSessionContext } from "@shivaedev/form/client/react.ts";
7
+ import { liveBindings } from "#internal/liveBindings.ts";
8
+ import { SignOutRegistryContext } from "#internal/SignOutRegistryContext.ts";
6
9
  import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
7
10
  import { useLatestCallback } from "#internal/useLatestCallback.ts";
8
11
  import { mountLive } from "./mountLive.ts";
@@ -25,12 +28,18 @@ interface Owned<TClient> {
25
28
  mounted: number;
26
29
  readonly registry: AtomRegistry.AtomRegistry;
27
30
  }
28
- function owning<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClient): Owned<TClient> {
31
+ function owning<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClient, identity: string | undefined): Owned<TClient> {
29
32
  const registry = AtomRegistry.make({ "defaultIdleTTL": 400 });
30
33
  let client: { readonly value: TClient } | undefined;
31
34
  return {
32
35
  get client() {
33
36
  client ??= { value: connect(registry) };
37
+ for (const binding of liveBindings(client.value)) {
38
+ const session = binding[LiveConnection].identity;
39
+ if (session !== undefined) {
40
+ registry.set(session, identity);
41
+ }
42
+ }
34
43
  return client.value;
35
44
  },
36
45
  mounted: 0,
@@ -38,8 +47,8 @@ function owning<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClie
38
47
  };
39
48
  }
40
49
  // No cleanup runs when a hidden <Activity> subtree is unmounted, so nodes a hidden render creates must not outlive it.
41
- function hidden<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClient): Owned<TClient> {
42
- const owned = owning(connect);
50
+ function hidden<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClient, identity: string | undefined): Owned<TClient> {
51
+ const owned = owning(connect, identity);
43
52
  let reaping = false;
44
53
  owned.registry.onNodeAdded = () => {
45
54
  if (reaping || owned.mounted > 0) {
@@ -58,14 +67,22 @@ function hidden<TClient>(connect: (registry: AtomRegistry.AtomRegistry) => TClie
58
67
  interface GenerationProps<TSession, TClient> {
59
68
  readonly children: (client: TClient) => ReactNode;
60
69
  readonly connect: (session: TSession, registry: AtomRegistry.AtomRegistry) => TClient;
70
+ readonly identity: string | undefined;
61
71
  readonly liveCoalesceWindow?: Duration.Input | undefined;
62
72
  readonly recheck: () => void;
63
73
  readonly session: TSession;
64
74
  }
65
- function Generation<TSession, TClient>({ session, connect, recheck, children, liveCoalesceWindow }: GenerationProps<TSession, TClient>): ReactNode {
75
+ function Generation<TSession, TClient>({
76
+ session,
77
+ connect,
78
+ recheck,
79
+ children,
80
+ identity,
81
+ liveCoalesceWindow,
82
+ }: GenerationProps<TSession, TClient>): ReactNode {
66
83
  const recheckLatest = useLatestCallback(recheck);
67
84
  const [open] = useState(() => (registry: AtomRegistry.AtomRegistry) => connect(session, registry));
68
- const [owned, own] = useState(() => owning(open));
85
+ const [owned, own] = useState(() => owning(open, identity));
69
86
  useEffect(() => registerSessionRecheck(owned.registry, recheckLatest), [owned, recheckLatest]);
70
87
  useEffect(() => {
71
88
  owned.mounted += 1;
@@ -78,10 +95,10 @@ function Generation<TSession, TClient>({ session, connect, recheck, children, li
78
95
  return;
79
96
  }
80
97
  owned.registry.dispose();
81
- own((current) => (current === owned ? hidden(open) : current));
98
+ own((current) => (current === owned ? hidden(open, identity) : current));
82
99
  });
83
100
  };
84
- }, [owned, liveCoalesceWindow, open]);
101
+ }, [identity, owned, liveCoalesceWindow, open]);
85
102
  return createElement(
86
103
  RecheckContext.Provider,
87
104
  { value: recheckLatest },
@@ -97,13 +114,18 @@ export function SessionBoundary<TSession, TClient>({
97
114
  signedOut,
98
115
  children,
99
116
  }: SessionBoundaryProps<TSession, TClient>): ReactNode {
117
+ const registry = useContext(RegistryContext);
100
118
  const id = session === undefined ? undefined : identify(session);
101
119
  const current = useDraftOwner(id);
102
120
  return session === undefined
103
121
  ? createElement(Fragment, null, signedOut)
104
122
  : createElement(
105
- DraftSessionContext.Provider,
106
- { value: current },
107
- createElement(Generation<TSession, TClient>, { children, connect, key: id, liveCoalesceWindow, recheck, session }),
123
+ SignOutRegistryContext.Provider,
124
+ { value: registry },
125
+ createElement(
126
+ DraftSessionContext.Provider,
127
+ { value: current },
128
+ createElement(Generation<TSession, TClient>, { children, connect, identity: id, key: id, liveCoalesceWindow, recheck, session }),
129
+ ),
108
130
  );
109
131
  }
@@ -1,7 +1,10 @@
1
+ import { RegistryContext } from "@effect/atom-react";
1
2
  import { useRouter } from "@tanstack/react-router";
2
- import * as AsyncResult from "effect/reactivity/AsyncResult";
3
- import type * as Atom from "effect/reactivity/Atom";
4
- import { useEffect } from "react";
3
+ import { Effect } from "effect";
4
+ import * as Atom from "effect/reactivity/Atom";
5
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
6
+ import { useContext, useMemo } from "react";
7
+ import { SignOutRegistryContext } from "#internal/SignOutRegistryContext.ts";
5
8
  import { useAction } from "./resultState.ts";
6
9
 
7
10
  declare module "@tanstack/react-router" {
@@ -12,22 +15,40 @@ declare module "@tanstack/react-router" {
12
15
 
13
16
  export function useSignOut<A, E>(action: Atom.AtomResultFn<void, A, E>, options: { readonly signIn: string }) {
14
17
  const router = useRouter({ warn: false });
15
- const state = useAction(action);
16
- useEffect(() => {
17
- if (router && AsyncResult.isSuccess(state.result) && !state.pending) {
18
- void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
19
- }
20
- if (router && AsyncResult.isFailure(state.result) && !state.pending) {
18
+ const local = useContext(RegistryContext);
19
+ const registry = useContext(SignOutRegistryContext) ?? local;
20
+ const source = useMemo(
21
+ () =>
22
+ Atom.writable(
23
+ (get) => {
24
+ get.addFinalizer(registry.subscribe(action, (result) => get.setSelf(result)));
25
+ return registry.get(action);
26
+ },
27
+ (_, input: undefined | Atom.Reset | Atom.Interrupt) => registry.set(action, input),
28
+ ),
29
+ [registry, action],
30
+ );
31
+ const state = useAction(source);
32
+ function failed() {
33
+ if (router) {
21
34
  router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: false });
22
35
  }
23
- }, [router, state.result, state.pending, options.signIn]);
36
+ }
37
+ function completed() {
38
+ if (router) {
39
+ void router.navigate({ replace: true, state: { __platformSignOut: true }, to: options.signIn });
40
+ }
41
+ }
24
42
  return {
25
43
  ...state,
26
44
  dispatch: () => {
27
45
  if (router) {
28
46
  router.history.replace(router.history.location.href, { ...router.history.location.state, __platformSignOut: true });
29
47
  }
30
- state.dispatch();
48
+ state.dispatch(undefined);
49
+ Effect.runFork(
50
+ AtomRegistry.getResult(registry, action, { suspendOnWaiting: true }).pipe(Effect.match({ onFailure: failed, onSuccess: completed })),
51
+ );
31
52
  },
32
53
  };
33
54
  }
@@ -0,0 +1,4 @@
1
+ import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
+ import { createContext } from "react";
3
+
4
+ export const SignOutRegistryContext = createContext<AtomRegistry.AtomRegistry | undefined>(undefined);
@@ -0,0 +1,11 @@
1
+ import { Predicate } from "effect";
2
+ import { type LiveBound, LiveConnection } from "@shivaedev/contract/automaticLive.ts";
3
+
4
+ function isLiveBound(value: unknown): value is LiveBound {
5
+ return Predicate.hasProperty(value, LiveConnection);
6
+ }
7
+
8
+ export function liveBindings(client: unknown): readonly LiveBound[] {
9
+ const values = typeof client === "object" && client !== null ? [client, ...Object.values(client)] : [client];
10
+ return values.filter(isLiveBound);
11
+ }
@@ -10,8 +10,8 @@ export function registerSessionRecheck(registry: AtomRegistry.AtomRegistry, rech
10
10
  };
11
11
  }
12
12
 
13
- export async function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown): Promise<void> {
14
- if (Predicate.isTagged(error, "Unauthorized")) {
13
+ export async function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown, connection = false): Promise<void> {
14
+ if (Predicate.isTagged(error, "Unauthorized") || (connection && Predicate.isTagged(error, "RpcClientError"))) {
15
15
  await rechecks.get(registry)?.();
16
16
  }
17
17
  }