@shivaedev/effect-react 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/client/mountLive.d.ts +2 -1
- package/dist/client/mountLive.d.ts.map +1 -1
- package/dist/client/mountLive.js +7 -1
- package/dist/client/mountLive.js.map +1 -1
- package/dist/client/sessionBoundary.d.ts +3 -1
- package/dist/client/sessionBoundary.d.ts.map +1 -1
- package/dist/client/sessionBoundary.js +5 -5
- package/dist/client/sessionBoundary.js.map +1 -1
- package/package.json +6 -6
- package/src/client/mountLive.ts +10 -2
- package/src/client/sessionBoundary.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0 - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Coalesce live changed hints over a default 20 ms window, with one follow-up read when hints arrive during an in-flight refetch. Configure `live()` with `coalesceWindow`, or set its returned `coalesceWindow` atom; zero retains immediate invalidation. Reconciliation stays immediate.
|
|
8
|
+
- Configure automatic live connections per React `SessionBoundary` with `liveCoalesceWindow`.
|
|
9
|
+
|
|
3
10
|
## 0.10.2 - 2026-10-19
|
|
4
11
|
|
|
5
12
|
### 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, 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. 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.
|
|
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 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 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
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Duration } from "effect";
|
|
1
2
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
|
-
export declare function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown): () => void;
|
|
3
|
+
export declare function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown, coalesceWindow?: Duration.Input): () => void;
|
|
3
4
|
//# sourceMappingURL=mountLive.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountLive.d.ts","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mountLive.d.ts","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,QAAQ,CAAC;AAElD,OAAO,KAAK,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAGpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,cAgC9G"}
|
package/dist/client/mountLive.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Predicate } from "effect";
|
|
2
2
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import { LiveConnection } from "@shivaedev/effect-contract/automaticLive.ts";
|
|
4
|
-
export function mountLive(registry, client) {
|
|
4
|
+
export function mountLive(registry, client, coalesceWindow) {
|
|
5
5
|
const releases = [];
|
|
6
6
|
const found = new Set();
|
|
7
7
|
function mount(value) {
|
|
@@ -10,6 +10,12 @@ export function mountLive(registry, client) {
|
|
|
10
10
|
}
|
|
11
11
|
const live = value[LiveConnection];
|
|
12
12
|
if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
|
|
13
|
+
if (coalesceWindow !== undefined
|
|
14
|
+
&& Predicate.hasProperty(live, "coalesceWindow")
|
|
15
|
+
&& Atom.isAtom(live.coalesceWindow)
|
|
16
|
+
&& Atom.isWritable(live.coalesceWindow)) {
|
|
17
|
+
registry.set(live.coalesceWindow, coalesceWindow);
|
|
18
|
+
}
|
|
13
19
|
found.add(live.connection);
|
|
14
20
|
releases.push(registry.mount(live.connection));
|
|
15
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountLive.js","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"mountLive.js","sourceRoot":"","sources":["../../src/client/mountLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAE7E,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,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,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,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,12 +1,14 @@
|
|
|
1
|
+
import type { Duration } from "effect";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
3
|
export interface SessionBoundaryProps<TSession, TClient> {
|
|
3
4
|
readonly children: (client: TClient) => ReactNode;
|
|
4
5
|
readonly connect: (session: TSession) => TClient;
|
|
5
6
|
readonly identify: (session: TSession) => string;
|
|
7
|
+
readonly liveCoalesceWindow?: Duration.Input | undefined;
|
|
6
8
|
readonly recheck: () => void;
|
|
7
9
|
readonly session: TSession | undefined;
|
|
8
10
|
readonly signedOut?: ReactNode;
|
|
9
11
|
}
|
|
10
12
|
export declare function useSessionRecheck(): () => void;
|
|
11
|
-
export declare function SessionBoundary<TSession, TClient>({ session, identify, connect, recheck, signedOut, children, }: SessionBoundaryProps<TSession, TClient>): ReactNode;
|
|
13
|
+
export declare function SessionBoundary<TSession, TClient>({ session, liveCoalesceWindow, identify, connect, recheck, signedOut, children, }: SessionBoundaryProps<TSession, TClient>): ReactNode;
|
|
12
14
|
//# sourceMappingURL=sessionBoundary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessionBoundary.d.ts","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEvC,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,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;AA0DD,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"}
|
|
@@ -30,12 +30,12 @@ function hidden(client) {
|
|
|
30
30
|
};
|
|
31
31
|
return owned;
|
|
32
32
|
}
|
|
33
|
-
function Generation({ session, connect, recheck, children }) {
|
|
33
|
+
function Generation({ session, connect, recheck, children, liveCoalesceWindow }) {
|
|
34
34
|
const [owned, own] = useState(() => owning(connect(session)));
|
|
35
35
|
useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
|
|
36
36
|
useEffect(() => {
|
|
37
37
|
owned.mounted += 1;
|
|
38
|
-
const stopLive = mountLive(owned.registry, owned.client);
|
|
38
|
+
const stopLive = mountLive(owned.registry, owned.client, liveCoalesceWindow);
|
|
39
39
|
return () => {
|
|
40
40
|
stopLive();
|
|
41
41
|
owned.mounted -= 1;
|
|
@@ -47,14 +47,14 @@ function Generation({ session, connect, recheck, children }) {
|
|
|
47
47
|
own((current) => (current === owned ? hidden(owned.client) : current));
|
|
48
48
|
});
|
|
49
49
|
};
|
|
50
|
-
}, [owned]);
|
|
50
|
+
}, [owned, liveCoalesceWindow]);
|
|
51
51
|
return createElement(RecheckContext.Provider, { value: recheck }, createElement(RegistryContext.Provider, { value: owned.registry }, children(owned.client)));
|
|
52
52
|
}
|
|
53
|
-
export function SessionBoundary({ session, identify, connect, recheck, signedOut, children, }) {
|
|
53
|
+
export function SessionBoundary({ session, liveCoalesceWindow, identify, connect, recheck, signedOut, children, }) {
|
|
54
54
|
const id = session === undefined ? undefined : identify(session);
|
|
55
55
|
const current = useDraftOwner(id);
|
|
56
56
|
return session === undefined
|
|
57
57
|
? createElement(Fragment, null, signedOut)
|
|
58
|
-
: createElement(DraftSessionContext.Provider, { value: current }, createElement(Generation, { children, connect, key: id, recheck, session }));
|
|
58
|
+
: createElement(DraftSessionContext.Provider, { value: current }, createElement(Generation, { children, connect, key: id, liveCoalesceWindow, recheck, session }));
|
|
59
59
|
}
|
|
60
60
|
//# 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;
|
|
1
|
+
{"version":3,"file":"sessionBoundary.js","sourceRoot":"","sources":["../../src/client/sessionBoundary.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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.
|
|
46
|
+
"@shivaedev/effect-contract": "0.8.0",
|
|
47
47
|
"@shivaedev/effect-form": "0.5.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@effect/sql-sqlite-node": "4.0.4",
|
|
54
54
|
"@shivaedev/effect-pg-boss": "0.12.0",
|
|
55
55
|
"@shivaedev/effect-service": "0.4.0",
|
|
56
|
-
"@shivaedev/effect-sql": "0.
|
|
56
|
+
"@shivaedev/effect-sql": "0.10.0",
|
|
57
57
|
"@shivaedev/effect-test": "0.7.1",
|
|
58
|
-
"@shivaedev/effect-test-sql": "0.7.
|
|
58
|
+
"@shivaedev/effect-test-sql": "0.7.4",
|
|
59
59
|
"@shivaedev/local-postgres": "0.5.0",
|
|
60
|
-
"@shivaedev/platform": "0.
|
|
60
|
+
"@shivaedev/platform": "0.21.0",
|
|
61
61
|
"@shivaedev/quality": "0.29.1",
|
|
62
|
-
"@shivaedev/test-story": "0.
|
|
62
|
+
"@shivaedev/test-story": "0.16.0",
|
|
63
63
|
"@tanstack/react-router": "1.170.42",
|
|
64
64
|
"@types/node": "24.19.2",
|
|
65
65
|
"@types/react": "19.3.0",
|
package/src/client/mountLive.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Predicate } from "effect";
|
|
1
|
+
import { type Duration, Predicate } from "effect";
|
|
2
2
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
4
|
import { LiveConnection } from "@shivaedev/effect-contract/automaticLive.ts";
|
|
5
5
|
|
|
6
|
-
export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown) {
|
|
6
|
+
export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown, coalesceWindow?: Duration.Input) {
|
|
7
7
|
const releases: Array<() => void> = [];
|
|
8
8
|
const found = new Set<unknown>();
|
|
9
9
|
function mount(value: unknown) {
|
|
@@ -12,6 +12,14 @@ export function mountLive(registry: AtomRegistry.AtomRegistry, client: unknown)
|
|
|
12
12
|
}
|
|
13
13
|
const live = value[LiveConnection];
|
|
14
14
|
if (Predicate.hasProperty(live, "connection") && Atom.isAtom(live.connection) && !found.has(live.connection)) {
|
|
15
|
+
if (
|
|
16
|
+
coalesceWindow !== undefined
|
|
17
|
+
&& Predicate.hasProperty(live, "coalesceWindow")
|
|
18
|
+
&& Atom.isAtom(live.coalesceWindow)
|
|
19
|
+
&& Atom.isWritable(live.coalesceWindow)
|
|
20
|
+
) {
|
|
21
|
+
registry.set(live.coalesceWindow, coalesceWindow);
|
|
22
|
+
}
|
|
15
23
|
found.add(live.connection);
|
|
16
24
|
releases.push(registry.mount(live.connection));
|
|
17
25
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RegistryContext } from "@effect/atom-react";
|
|
2
|
+
import type { Duration } from "effect";
|
|
2
3
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
4
|
import { createContext, createElement, Fragment, type ReactNode, useContext, useEffect, useState } from "react";
|
|
4
5
|
import { DraftSessionContext } from "@shivaedev/effect-form/client/react.ts";
|
|
@@ -9,6 +10,7 @@ export interface SessionBoundaryProps<TSession, TClient> {
|
|
|
9
10
|
readonly children: (client: TClient) => ReactNode;
|
|
10
11
|
readonly connect: (session: TSession) => TClient;
|
|
11
12
|
readonly identify: (session: TSession) => string;
|
|
13
|
+
readonly liveCoalesceWindow?: Duration.Input | undefined;
|
|
12
14
|
readonly recheck: () => void;
|
|
13
15
|
readonly session: TSession | undefined;
|
|
14
16
|
readonly signedOut?: ReactNode;
|
|
@@ -46,15 +48,16 @@ function hidden<TClient>(client: TClient): Owned<TClient> {
|
|
|
46
48
|
interface GenerationProps<TSession, TClient> {
|
|
47
49
|
readonly children: (client: TClient) => ReactNode;
|
|
48
50
|
readonly connect: (session: TSession) => TClient;
|
|
51
|
+
readonly liveCoalesceWindow?: Duration.Input | undefined;
|
|
49
52
|
readonly recheck: () => void;
|
|
50
53
|
readonly session: TSession;
|
|
51
54
|
}
|
|
52
|
-
function Generation<TSession, TClient>({ session, connect, recheck, children }: GenerationProps<TSession, TClient>): ReactNode {
|
|
55
|
+
function Generation<TSession, TClient>({ session, connect, recheck, children, liveCoalesceWindow }: GenerationProps<TSession, TClient>): ReactNode {
|
|
53
56
|
const [owned, own] = useState(() => owning(connect(session)));
|
|
54
57
|
useEffect(() => registerSessionRecheck(owned.registry, recheck), [owned, recheck]);
|
|
55
58
|
useEffect(() => {
|
|
56
59
|
owned.mounted += 1;
|
|
57
|
-
const stopLive = mountLive(owned.registry, owned.client);
|
|
60
|
+
const stopLive = mountLive(owned.registry, owned.client, liveCoalesceWindow);
|
|
58
61
|
return () => {
|
|
59
62
|
stopLive();
|
|
60
63
|
owned.mounted -= 1;
|
|
@@ -66,7 +69,7 @@ function Generation<TSession, TClient>({ session, connect, recheck, children }:
|
|
|
66
69
|
own((current) => (current === owned ? hidden(owned.client) : current));
|
|
67
70
|
});
|
|
68
71
|
};
|
|
69
|
-
}, [owned]);
|
|
72
|
+
}, [owned, liveCoalesceWindow]);
|
|
70
73
|
return createElement(
|
|
71
74
|
RecheckContext.Provider,
|
|
72
75
|
{ value: recheck },
|
|
@@ -75,6 +78,7 @@ function Generation<TSession, TClient>({ session, connect, recheck, children }:
|
|
|
75
78
|
}
|
|
76
79
|
export function SessionBoundary<TSession, TClient>({
|
|
77
80
|
session,
|
|
81
|
+
liveCoalesceWindow,
|
|
78
82
|
identify,
|
|
79
83
|
connect,
|
|
80
84
|
recheck,
|
|
@@ -88,6 +92,6 @@ export function SessionBoundary<TSession, TClient>({
|
|
|
88
92
|
: createElement(
|
|
89
93
|
DraftSessionContext.Provider,
|
|
90
94
|
{ value: current },
|
|
91
|
-
createElement(Generation<TSession, TClient>, { children, connect, key: id, recheck, session }),
|
|
95
|
+
createElement(Generation<TSession, TClient>, { children, connect, key: id, liveCoalesceWindow, recheck, session }),
|
|
92
96
|
);
|
|
93
97
|
}
|