@shivaedev/effect-react 0.5.0 → 0.5.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 +6 -0
- package/README.md +6 -30
- package/dist/client/sessionBoundary.d.ts.map +1 -1
- package/dist/client/sessionBoundary.js +2 -0
- package/dist/client/sessionBoundary.js.map +1 -1
- package/dist/internal/sessionRecheck.d.ts +4 -0
- package/dist/internal/sessionRecheck.d.ts.map +1 -0
- package/dist/internal/sessionRecheck.js +14 -0
- package/dist/internal/sessionRecheck.js.map +1 -0
- package/dist/loadQuery.d.ts.map +1 -1
- package/dist/loadQuery.js +2 -0
- package/dist/loadQuery.js.map +1 -1
- package/package.json +7 -8
- package/src/client/sessionBoundary.ts +2 -0
- package/src/internal/sessionRecheck.ts +17 -0
- package/src/loadQuery.ts +2 -0
- package/docs/north-star.md +0 -45
- package/docs/roadmap.md +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Route loaders and `loadQuery` recheck the owning `SessionBoundary` session once when a query fails with `Unauthorized`, so sign-in redirects can see an ended session.
|
|
8
|
+
|
|
3
9
|
## 0.5.0 - 2026-10-18
|
|
4
10
|
|
|
5
11
|
### Breaking
|
package/README.md
CHANGED
|
@@ -137,7 +137,7 @@ create.dispatch({
|
|
|
137
137
|
});
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
`NotesClient` is an application's `AtomRpc.Service` with `ListNotes` and `CreateNote` RPCs. The native mutation's reactivity keys refresh the matching query after success.
|
|
140
|
+
`NotesClient` is an application's `AtomRpc.Service` with `ListNotes` and `CreateNote` RPCs. The native mutation's reactivity keys refresh the matching query after success.
|
|
141
141
|
|
|
142
142
|
An action's `dispatch(input)` returns `void`. Read the atom's latest result from `data`, `cause` or `result`. With the default `Atom.fn` action, a later dispatch interrupts the earlier work and the shared state reports the later result. Disable duplicate submissions or give independent work distinct native action atoms. When one caller needs its own success or failure, run the actual RPC Effect through its runtime, as the editor's `save` does; shared atom state cannot identify one invocation's completion.
|
|
143
143
|
|
|
@@ -195,7 +195,7 @@ export function App() {
|
|
|
195
195
|
}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
`defaultPendingMs: Infinity` keeps the current route's data visible during navigation; the shell reads TanStack's `isLoading` to display pending state. The new route commits when its loader settles. This is TanStack's pending policy, separate from `useQuery`'s retained display. The
|
|
198
|
+
`defaultPendingMs: Infinity` keeps the current route's data visible during navigation; the shell reads TanStack's `isLoading` to display pending state. The new route commits when its loader settles. This is TanStack's pending policy, separate from `useQuery`'s retained display. The loader and the component share one query execution.
|
|
199
199
|
|
|
200
200
|
`loadQuery` waits through a native query refresh instead of returning its retained success. Typed failures are thrown directly, so the route can inspect `_tag` rather than an Effect fiber wrapper. Defects remain failed Promises. TanStack's loader `abortController` cancels the waiting subscription and rejects with `name: "AbortError"`; other readers of the same query can still complete. The application owns the registry's lifetime and disposes it when its client or session ends. Use the same registry for router context and `RegistryContext.Provider`.
|
|
201
201
|
|
|
@@ -356,22 +356,9 @@ const query = Atom.swr(atom, {
|
|
|
356
356
|
|
|
357
357
|
`useQuery(atom, options?)` adds `refresh(): void`; `useAction` adds `dispatch(input): void`. `QueryOptions.keepPreviousData` defaults to `false` and controls retention across query atom changes. Same-identity refresh retention is always enabled. `result` and `cause` describe the current native query even when `data` displays a previous atom's success. Success, input and typed failure types are inferred from the supplied atom.
|
|
358
358
|
|
|
359
|
-
|
|
360
|
-
| --- | --- |
|
|
361
|
-
| `client/resultState.ts` | `useQuery`, `useAction`, `isUnauthorized`; `ResultState`, `QueryOptions`, `QueryState`, `ActionState`. |
|
|
362
|
-
| `client/sessionBoundary.ts` | `SessionBoundary`, `useSessionRecheck`; `SessionBoundaryProps`. |
|
|
363
|
-
| `client/editor.ts` | `useEditor`; `Editor`, `EditorConfig`. |
|
|
364
|
-
| `client/create.ts` | `useCreate`; `Create`, `CreateConfig`. |
|
|
365
|
-
| `resumeSignal.ts` | `resumeSignal`; `ResumeSource`, `ResumeWindow`, `ResumeOptions`. |
|
|
366
|
-
| `loadQuery.ts` | `loadQuery`. |
|
|
367
|
-
| `client/saveState.ts` | `useSaveState`; `SaveState`. |
|
|
368
|
-
| `fieldRejection.ts` | `fieldRejectionOf`, `rejecting`; `FieldRejection`, `RejectField`, `FieldRejectionMapping`, `AtomServices`. |
|
|
369
|
-
| `client/testing.ts` | `render`, `eventually`, `expectNoReactWarnings`; `Rendered`. |
|
|
370
|
-
| `submission.ts` | `submission`; `Submission`, `SubmissionConfig`. |
|
|
371
|
-
|
|
372
|
-
Import the defining `.ts` module, such as `@shivaedev/effect-react/client/editor.ts`. There is no root import. `SessionBoundaryProps` supplies `session`, `identify`, `connect`, `recheck`, a client-to-children function and optional `signedOut`; `useSessionRecheck()` reads that boundary's callback.
|
|
359
|
+
Import the defining `.ts` module, such as `@shivaedev/effect-react/client/editor.ts`. There is no root import. `useSessionRecheck()` from `client/sessionBoundary.ts` reads the boundary's `recheck` callback.
|
|
373
360
|
|
|
374
|
-
|
|
361
|
+
Prefer `useEditor` and `useCreate` when binding a form to a command. The lower-level modules expose the same composition pieces: `submission` from `submission.ts` makes a form and its submission snapshot reader, `rejecting` from `fieldRejection.ts` maps save failures through a submitter, and `useSaveState` from `client/saveState.ts` observes submission and invokes a success callback.
|
|
375
362
|
|
|
376
363
|
### Render tests in happy-dom
|
|
377
364
|
|
|
@@ -458,13 +445,7 @@ it("renders a query and refetches it after a command through a test RPC layer",
|
|
|
458
445
|
|
|
459
446
|
`render(node)` returns the attached `container`, its fresh native `registry`, `render(nextNode)` to update the same root and registry, and `unmount()` to release the root, registry and container. Always unmount in `finally`. Keep component interactions inside React's `act`. `eventually(assert)` retries the assertion for up to three seconds, allowing pending responses to settle inside `act` between attempts; it throws the last assertion failure on timeout. `expectNoReactWarnings()` registers a Vitest hook that fails a test if it logged to `console.error`.
|
|
460
447
|
|
|
461
|
-
The RPC layer stays application-owned: replace these in-memory handlers with the service Layer your component needs.
|
|
462
|
-
|
|
463
|
-
### Testing a feature through its stack
|
|
464
|
-
|
|
465
|
-
The [order feature’s application-owned kit](https://github.com/ShivaeDev/platform/tree/main/packages/effect-react/src/test-support/order-example) uses `test-story` above `effect-test` to compose real PostgreSQL, HTTP RPC and the actual React components. Its screen traits mount fresh native atom registries and scoped roots; its verbs dispatch DOM events and wait for rendered output. The [stories](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/orderStory.dom.spec.tsx) verify list refresh through `useAction` and rejected-input retention and retry through `useEditor`, with persistence read from a separate PostgreSQL pool. These tests require `PLATFORM_EFFECT_SQL_TEST_DATABASE_URL`; DOM and loopback HTTP establish no physical browser, SSR or mobile behavior.
|
|
466
|
-
|
|
467
|
-
The [reference PostgreSQL feature](https://github.com/ShivaeDev/platform/blob/main/docs/framework/composed-example.md) uses the same leased database infrastructure and a scoped story verb for its real server and rendered clients. Its stories verify commit visibility, live refresh of a second editor, retained local edits and rollback without publication. To start an application, use the [copyable starter](https://github.com/ShivaeDev/platform/tree/main/examples/starter) with `pnpm create @shivaedev/app my-app`.
|
|
448
|
+
The RPC layer stays application-owned: replace these in-memory handlers with the service Layer your component needs.
|
|
468
449
|
|
|
469
450
|
### Install, setup and limits
|
|
470
451
|
|
|
@@ -478,12 +459,7 @@ For editor, create or lower-level form modules, also install the optional form p
|
|
|
478
459
|
pnpm add @shivaedev/effect-form
|
|
479
460
|
```
|
|
480
461
|
|
|
481
|
-
Query, action, session and resume modules do not require effect-form. The package
|
|
462
|
+
Query, action, session and resume modules do not require effect-form. The package needs Node 24 or newer.
|
|
482
463
|
|
|
483
464
|
- Query identity, TTL, invalidation and action concurrency remain native atom policies. Put the registry at the lifetime boundary those policies need.
|
|
484
|
-
- Action state describes the atom's latest result. A `dispatch` is not a Promise or a separate result handle.
|
|
485
|
-
- A session generation is application-defined. Credentials captured by `connect` require a changed generation when they rotate.
|
|
486
465
|
- The package has no SSR registry/hydration adapter, offline write queue or optimistic reconciliation policy.
|
|
487
|
-
- DOM tests and an injected resume source do not prove real-browser or mobile lifecycle behavior. The real HTTP order fixture proves its local composition, not a deployed host's auth, cancellation or rendering policy.
|
|
488
|
-
|
|
489
|
-
The full design is in [the north star](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/docs/north-star.md). Implementation status and unresolved choices are in [the roadmap](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/docs/roadmap.md).
|
|
@@ -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;
|
|
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"}
|
|
@@ -2,6 +2,7 @@ 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
4
|
import { DraftSessionContext } from "@shivaedev/effect-form/client/react.ts";
|
|
5
|
+
import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
|
|
5
6
|
import { useDraftOwner } from "./useDraftOwner.js";
|
|
6
7
|
const RecheckContext = createContext(() => undefined);
|
|
7
8
|
export function useSessionRecheck() {
|
|
@@ -30,6 +31,7 @@ function hidden(client) {
|
|
|
30
31
|
}
|
|
31
32
|
function Generation({ session, connect, recheck, children }) {
|
|
32
33
|
const [owned, own] = useState(() => owning(connect(session)));
|
|
34
|
+
useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
owned.mounted += 1;
|
|
35
37
|
return () => {
|
|
@@ -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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAkB,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,MAAM,cAAc,GAAG,aAAa,CAAa,GAAS,EAAE,CAAC,SAAS,CAAC,CAAC;AACxE,MAAM,UAAU,iBAAiB;IAChC,OAAO,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,CAAC;AAMD,SAAS,MAAM,CAAU,MAAe;IACvC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;AAC9D,CAAC;AACD,uHAAuH;AACvH,SAAS,MAAM,CAAU,MAAe;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,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;AAOD,SAAS,UAAU,CAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAsC;IACjH,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9D,SAAS,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IACnF,SAAS,CAAC,GAAG,EAAE;QACd,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACnB,OAAO,GAAG,EAAE;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,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACxE,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,OAAO,aAAa,CACnB,cAAc,CAAC,QAAQ,EACvB,EAAE,KAAK,EAAE,OAAO,EAAE,EAClB,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,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,OAAO,EAAE,OAAO,EAAE,CAAC,CAC9F,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
|
+
export declare function registerSessionRecheck(registry: AtomRegistry.AtomRegistry, recheck: () => void): () => void;
|
|
3
|
+
export declare function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=sessionRecheck.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
const rechecks = new WeakMap();
|
|
3
|
+
export function registerSessionRecheck(registry, recheck) {
|
|
4
|
+
rechecks.set(registry, recheck);
|
|
5
|
+
return () => {
|
|
6
|
+
rechecks.delete(registry);
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export async function recheckUnauthorized(registry, error) {
|
|
10
|
+
if (Predicate.isTagged(error, "Unauthorized")) {
|
|
11
|
+
await rechecks.get(registry)?.();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=sessionRecheck.js.map
|
|
@@ -0,0 +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"}
|
package/dist/loadQuery.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadQuery.d.ts","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"loadQuery.d.ts","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;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"}
|
package/dist/loadQuery.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
|
+
import { recheckUnauthorized } from "#internal/sessionRecheck.ts";
|
|
3
4
|
export async function loadQuery(registry, atom, { abortController }) {
|
|
4
5
|
const { signal } = abortController;
|
|
5
6
|
try {
|
|
6
7
|
const result = await Effect.runPromise(Effect.result(AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true })), { signal });
|
|
7
8
|
if (result._tag === "Failure") {
|
|
9
|
+
await recheckUnauthorized(registry, result.failure);
|
|
8
10
|
throw result.failure;
|
|
9
11
|
}
|
|
10
12
|
return result.success;
|
package/dist/loadQuery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadQuery.js","sourceRoot":"","sources":["../src/loadQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-react",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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": {
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"types": "./dist/*.d.ts",
|
|
29
29
|
"default": "./dist/*.js"
|
|
30
30
|
},
|
|
31
|
+
"./internal/*": null,
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"dist",
|
|
35
|
-
"docs",
|
|
36
36
|
"src",
|
|
37
37
|
"!src/**/*.test.ts",
|
|
38
38
|
"!src/**/*.test.tsx",
|
|
@@ -53,22 +53,21 @@
|
|
|
53
53
|
"@effect/sql-sqlite-node": "4.0.4",
|
|
54
54
|
"@shivaedev/effect-pg-boss": "0.9.0",
|
|
55
55
|
"@shivaedev/effect-service": "0.4.0",
|
|
56
|
-
"@shivaedev/effect-sql": "0.5.
|
|
56
|
+
"@shivaedev/effect-sql": "0.5.2",
|
|
57
57
|
"@shivaedev/effect-test": "0.6.0",
|
|
58
58
|
"@shivaedev/local-postgres": "0.4.0",
|
|
59
|
-
"@shivaedev/platform": "0.16.
|
|
60
|
-
"@shivaedev/quality": "0.
|
|
61
|
-
"@shivaedev/test-story": "0.
|
|
59
|
+
"@shivaedev/platform": "0.16.1",
|
|
60
|
+
"@shivaedev/quality": "0.26.0",
|
|
61
|
+
"@shivaedev/test-story": "0.8.0",
|
|
62
62
|
"@tanstack/react-router": "1.170.42",
|
|
63
63
|
"@types/node": "24.19.2",
|
|
64
64
|
"@types/react": "19.3.0",
|
|
65
65
|
"@types/react-dom": "19.3.0",
|
|
66
|
-
"@typescript/native": "npm:typescript@7.0.2",
|
|
67
66
|
"effect": "4.0.4",
|
|
68
67
|
"happy-dom": "20.14.6",
|
|
69
68
|
"react": "19.3.0",
|
|
70
69
|
"react-dom": "19.3.0",
|
|
71
|
-
"typescript": "
|
|
70
|
+
"typescript": "7.0.2",
|
|
72
71
|
"vitest": "5.0.3"
|
|
73
72
|
},
|
|
74
73
|
"peerDependencies": {
|
|
@@ -2,6 +2,7 @@ import { RegistryContext } from "@effect/atom-react";
|
|
|
2
2
|
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
|
+
import { registerSessionRecheck } from "#internal/sessionRecheck.ts";
|
|
5
6
|
import { useDraftOwner } from "./useDraftOwner.ts";
|
|
6
7
|
export interface SessionBoundaryProps<TSession, TClient> {
|
|
7
8
|
readonly children: (client: TClient) => ReactNode;
|
|
@@ -49,6 +50,7 @@ interface GenerationProps<TSession, TClient> {
|
|
|
49
50
|
}
|
|
50
51
|
function Generation<TSession, TClient>({ session, connect, recheck, children }: GenerationProps<TSession, TClient>): ReactNode {
|
|
51
52
|
const [owned, own] = useState(() => owning(connect(session)));
|
|
53
|
+
useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
|
|
52
54
|
useEffect(() => {
|
|
53
55
|
owned.mounted += 1;
|
|
54
56
|
return () => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
|
+
|
|
4
|
+
const rechecks = new WeakMap<AtomRegistry.AtomRegistry, () => void>();
|
|
5
|
+
|
|
6
|
+
export function registerSessionRecheck(registry: AtomRegistry.AtomRegistry, recheck: () => void): () => void {
|
|
7
|
+
rechecks.set(registry, recheck);
|
|
8
|
+
return () => {
|
|
9
|
+
rechecks.delete(registry);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function recheckUnauthorized(registry: AtomRegistry.AtomRegistry, error: unknown): Promise<void> {
|
|
14
|
+
if (Predicate.isTagged(error, "Unauthorized")) {
|
|
15
|
+
await rechecks.get(registry)?.();
|
|
16
|
+
}
|
|
17
|
+
}
|
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 { recheckUnauthorized } from "#internal/sessionRecheck.ts";
|
|
6
7
|
|
|
7
8
|
export async function loadQuery<A, E>(
|
|
8
9
|
registry: AtomRegistry.AtomRegistry,
|
|
@@ -13,6 +14,7 @@ export async function loadQuery<A, E>(
|
|
|
13
14
|
try {
|
|
14
15
|
const result = await Effect.runPromise(Effect.result(AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true })), { signal });
|
|
15
16
|
if (result._tag === "Failure") {
|
|
17
|
+
await recheckUnauthorized(registry, result.failure);
|
|
16
18
|
throw result.failure;
|
|
17
19
|
}
|
|
18
20
|
return result.success;
|
package/docs/north-star.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# North star
|
|
2
|
-
|
|
3
|
-
## The problem
|
|
4
|
-
|
|
5
|
-
Native Effect atoms already know how to run, refresh and share an asynchronous result. A React application still needs to decide what to display while they work, what to keep when a request fails and which lifetime owns their registry. Repeating those decisions in every screen makes them drift. One screen blanks during a refresh, another loses a draft when a response arrives, and a third keeps the previous session's state after the user changes.
|
|
6
|
-
|
|
7
|
-
Editing combines two kinds of state. A server row can change independently, while the user types encoded input values that a schema must decode before saving. The latest server response is not necessarily the user's latest intent. Whole-form replacement makes a simple screen overwrite newer edits; leaving the form alone makes untouched fields stale. A create form adds another race: clearing the successful entry must not clear what the user has already begun typing for the next one.
|
|
8
|
-
|
|
9
|
-
## The ideal
|
|
10
|
-
|
|
11
|
-
React components read state in the words they render: data, failure, pending and refreshing. An editor says which query supplies its row, which fields describe its inputs and which Effect saves the decoded values. A session root says which identity owns those screens. Each definition stays small because the package supplies repeated coordination, while native Effect and effect-form remain the owners of the underlying work.
|
|
12
|
-
|
|
13
|
-
Good client state has these properties:
|
|
14
|
-
|
|
15
|
-
- Useful data can stay on screen during refresh and after a refresh failure. Showing retained data does not hide the failed result.
|
|
16
|
-
- Query identity changes reset displayed data by default. Applications can opt into `keepPreviousData` for payload transitions; displayed data still clears when the registry owner changes.
|
|
17
|
-
- A draft belongs to one query identity and one session generation. Changing either must not transfer edits into a different record or person's screen.
|
|
18
|
-
- A refresh, a normalized save result and a create reset respect edits made after the operation began. Untouched fields can adopt new server values without replacing newer input.
|
|
19
|
-
- Failure types reach the component. A field rejection names its field; another failure remains available for application-owned wording.
|
|
20
|
-
- An action reports the native atom's current state honestly. An imperative caller that needs one invocation's outcome runs an Effect with that lifetime instead of guessing from a shared latest-result atom.
|
|
21
|
-
- A registry has an owner. Session replacement and unmount release the old generation, including work created around React's StrictMode and Activity lifecycles.
|
|
22
|
-
|
|
23
|
-
## Boundaries
|
|
24
|
-
|
|
25
|
-
The root repository's one path for client state is this package. It should add React coordination where applications repeat it, without taking a neighbouring package's job:
|
|
26
|
-
|
|
27
|
-
- Native atoms own execution, identity, cache retention, invalidation and action scheduling.
|
|
28
|
-
- effect-contract owns query and command declarations, declared rejections and native client bindings. Its shared resume source also works without React.
|
|
29
|
-
- effect-form owns encoded fields, schema decoding, validation, submission and draft merge rules. The editor and create hooks arrange those pieces around rows and command results.
|
|
30
|
-
- The platform request modules and the application's auth integration verify identity. The session boundary owns a client-side generation; its `recheck` callback asks the auth owner for a verdict.
|
|
31
|
-
- The application owns transport, loading/error wording, credential rotation policy and the point where a runtime runs a particular Effect.
|
|
32
|
-
|
|
33
|
-
## Trade-offs
|
|
34
|
-
|
|
35
|
-
1. **Native ownership before convenience.** A second query cache or parallel form model would make easy call sites look simpler while giving the application two places to understand identity and cleanup. Thin bindings keep the native state visible.
|
|
36
|
-
2. **Identity isolation before draft continuity.** A draft is valuable only within the record and session that created it. Switching identities starts fresh even if retaining a previous draft would save typing.
|
|
37
|
-
3. **Newer intent before replacing the whole form.** A response updates what it can without erasing fields the user edited meanwhile. A user who wants the server's latest values can explicitly revert.
|
|
38
|
-
4. **Truthful shared state before per-call ergonomics.** One atom's result cannot describe several concurrent invocations independently. The action hook preserves its native semantics rather than implying a Promise belongs to one dispatch.
|
|
39
|
-
5. **Observable evidence before brevity.** A helper earns its place by removing repeated application code and having tests for the races and lifetimes it claims to handle. A DOM fixture proves that fixture, not another runtime or deployment.
|
|
40
|
-
|
|
41
|
-
## What it leaves out
|
|
42
|
-
|
|
43
|
-
The package does not define another RPC contract language, replace native atom scheduling or decide an application's auth policy. It does not supply a general form language, an optimistic reconciliation strategy or an offline write queue. Server rendering and mobile host integration need their own architecture and evidence; a resume callback alone cannot establish them.
|
|
44
|
-
|
|
45
|
-
Use [README.md](../README.md) for examples and [roadmap.md](./roadmap.md) for what is implemented and which choices remain with the maintainer.
|
package/docs/roadmap.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
This roadmap owns the package's hooks, form coordination and session-registry implementation. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns composed feature fixtures, adoption and host/deployment validation. A local hook test is evidence for its own runtime and scenario.
|
|
4
|
-
|
|
5
|
-
## Built
|
|
6
|
-
|
|
7
|
-
- [x] Opt-in `keepPreviousData` retains the last successful query value across payload changes, pending requests and failures; the default resets it. Registry replacement clears retained data, superseded responses do not replace the latest result, and disabling retention drops an inherited value. [Rendered query evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/queryPayload.dom.spec.ts).
|
|
8
|
-
- [x] Public `client/testing.ts` render helpers supply an isolated native registry per mount, retain it on rerender, and dispose native resources and remove the container on idempotent unmount. [DOM evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/client/testing.dom.test.tsx) also exercises query refetch after a command through a test RPC Layer; it does not establish browser networking.
|
|
9
|
-
- [x] Native query and action hooks with inferred input/success/failure types, retained data, pending/refresh state and explicit refresh/dispatch methods. DOM tests cover query identity changes, shared query execution and latest-dispatch interruption; compiler fixtures cover types.
|
|
10
|
-
- [x] A session generation owning its client, registry and subtree, with replacement/sign-out cleanup, query Unauthorized rechecking and retained screens until the auth owner changes the session.
|
|
11
|
-
- [x] Unauthorized saves in `useCreate` and `useEditor` invoke the session callback exactly once and retain the draft and typed failure, including after rerendering and further edits. [Rendered save evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/client/saveState.dom.test.tsx).
|
|
12
|
-
- [x] Unauthorized action failures invoke the session callback once per failed result and retain successful data and the typed cause, including after rerendering. [Rendered action evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/client/sessionBoundary.dom.test.tsx).
|
|
13
|
-
- [x] Queries and actions preserve a successful `undefined` as `Option.some(undefined)` with settled pending/refreshing flags. Defect and interruption causes remain available, are not Unauthorized and do not request a session recheck. [Rendered result-state evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/client/resultState.dom.test.ts).
|
|
14
|
-
- [x] `eventually` rethrows the last assertion failure after its three-second deadline; `expectNoReactWarnings` fails a test that logs to `console.error` and restores the spy even after that assertion fails. [Testing-helper evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-react/src/client/testing.dom.test.tsx).
|
|
15
|
-
- [x] Session cleanup across StrictMode effect replay and Activity hiding, reveal and hidden unmount, with credential rotation tested against the chosen identity key.
|
|
16
|
-
- [x] `useEditor` composing query, fields and save Effect, with initial loading, per-query forms, per-field refresh/saved-result merges, normalized results and in-flight edits covered by rendered tests.
|
|
17
|
-
- [x] `useCreate` starting a fresh form after success while retaining fields edited during schema decoding or the request; submissions through the form API settle the hooks too.
|
|
18
|
-
- [x] Tagged field-rejection mapping, a typed custom mapper and compile-time requirements for rejection field types that exceed the form's names; other save failures remain separately available.
|
|
19
|
-
- [x] A resume module using effect-contract's shared implementation, with visible browser events, an injected native source, listener disposal and native stale-while-revalidate policy covered by tests.
|
|
20
|
-
- [x] TanStack Router query loaders sharing the native registry, with tagged RPC failures, refresh waiting and abort handling. Render tests cover shared query execution, the route error component and previous data during navigation.
|
|
21
|
-
|
|
22
|
-
## Next
|
|
23
|
-
|
|
24
|
-
- [ ] Verify changing hook configuration during one form lifetime before documenting any supported reconfiguration policy. Creation currently captures its initial configuration, and an editor holds submission functions per query identity.
|
|
25
|
-
|
|
26
|
-
## Open questions
|
|
27
|
-
|
|
28
|
-
- Do consumers need an independently scoped action helper, or are distinct native atoms and actual Effects enough for imperative per-invocation outcomes? Keep native shared-action semantics unless a demonstrated use case requires more.
|
|
29
|
-
- What per-request registry and hydration wiring does the TanStack Router host need for SSR? Client render fixtures do not establish SSR isolation.
|
|
30
|
-
- Which hook configuration values should be allowed to change without replacing the form? A rule for changing schemas, runtimes, save functions or initial values needs consumer evidence and explicit lifetime tests.
|