@farm.js/islx 0.0.1 → 0.0.3

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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.3
4
+
5
+ Server layouts as trees.
6
+
7
+ - A server layout around a client provider renders as a server tree, so a framework no longer
8
+ ships the layout's module to hydrate it. On the server, `renderServerTree(layout, { outlets,
9
+ scope })` with `<Outlet />` where the page goes; in the browser, `createServerTreeLayout(source,
10
+ scope, { load, ServerHtml, onUpdate })` and `layout.render({ children: page })`. Rendering it
11
+ again with another page keeps the provider's state.
12
+ - A layout's tree names its payload, chunks and server HTML by its scope (`f/chunk name="/:0"`),
13
+ so it and the page's tree at its outlet never read each other's parts. New tag `L` (outlet).
14
+
15
+ ## 0.0.2
16
+
17
+ Streaming and hydration fixes.
18
+
19
+ - Islands inside Suspense content that streams in after the browser entry has run now hydrate.
20
+ While the document streams, the runtime watches it: an island hydrates once React moves it out
21
+ of its hidden staging `<div>`, an island React removes (a replaced fallback) is released, and
22
+ watching stops once the document is parsed and every staged boundary is revealed. React 19.2
23
+ can reveal boundaries after parsing ends.
24
+ - Islands that share a value decode it once and share the result. Props and values without tags
25
+ skip decoding, and decoded objects are built with plain assignment (`__proto__` stays data).
26
+
3
27
  ## 0.0.1
4
28
 
5
29
  The first release: the islx wire format, server actions, React islands, and the Vite plugin.
package/README.md CHANGED
@@ -13,7 +13,7 @@ islx started as [Farm.js](https://github.com/farming-labs/farm.js)'s islands mod
13
13
  replaces React Server Components' Flight payload. On a 300-page docs app it ships 26% less
14
14
  JavaScript, and a client navigation sends 2.5 KB instead of 12 KB.
15
15
 
16
- > Status: 0.0.1, the first release. React and Vite are supported; other renderers and framework
16
+ > Status: 0.0.3. React and Vite are supported; other renderers and framework
17
17
  > integrations follow. The wire format is in [SPEC.md](./SPEC.md), changes in
18
18
  > [CHANGELOG.md](./CHANGELOG.md).
19
19
 
@@ -180,6 +180,7 @@ A framework that renders pages itself (Farm does) uses the pieces directly inste
180
180
  | Render islands in its own render | wrap the page in `<IslandsProvider held={parseHeldValues(request)}>` |
181
181
  | A layout that hydrates as one root | `withinClientRoot(layout)`; its server-HTML page: `isolateIslands(page)` |
182
182
  | An async page under such a layout | `renderServerTree(page, { held, payloadOnly })` on the server; `createServerTreeElement(source, { load, ServerHtml })` in the browser |
183
+ | A server layout around a client provider, without shipping the layout | `renderServerTree(<Layout><Outlet /></Layout>, { outlets: { children: page }, scope })` on the server; `createServerTreeLayout(source, scope, { load, ServerHtml, onUpdate })` in the browser, then `layout.render({ children: page })` |
183
184
  | Hydrate | `createHydrationRuntime({ load, schedule?, wrap?, report? })`, `createServerHtml(runtime)` |
184
185
  | Hold early clicks | inline `PREHYDRATION_SCRIPT` in the page |
185
186
  | Actions | `handleAction(request, { allowedOrigins, bodySizeLimit, run, mapError, renderFormState })`; `setActionNavigator({ refresh, navigate })` |
package/SPEC.md CHANGED
@@ -32,9 +32,9 @@ Anything that writes and reads it, in any language or framework, is a islx imple
32
32
  | `<script type="f/props" for>` | An island's props, as JSON. `for` names the island's `props` id. |
33
33
  | `<script type="f/value" name>` | A value sent once. `name` is its key. Written before the first island that uses it. |
34
34
  | `<f-slot name>` | Server output passed to an island as children. It stays server HTML; the island renders it in place. |
35
- | `<script type="f/tree" [partial]>` | A page's server tree, so the browser can rebuild the page inside an existing root without its HTML. `partial` marks a navigation that sent only the tree. |
36
- | `<script type="f/chunk" name>` | A part of a server tree that streamed in after the tree (a resolved Suspense boundary). |
37
- | `<f-html name>` | Server HTML inside a server tree. |
35
+ | `<script type="f/tree" [name] [partial]>` | A server tree, so the browser can rebuild the page inside an existing root without its HTML. A page's tree has no `name`; a layout's carries its scope and sits at its `children` outlet, or after the layout when it has none. `partial` marks a navigation that sent only the tree. |
36
+ | `<script type="f/chunk" name>` | A part of a server tree that streamed in after the tree (a resolved Suspense boundary). `name` is its number, after `scope:` in a layout's tree. |
37
+ | `<f-html name>` | Server HTML inside a server tree, named like a chunk. |
38
38
 
39
39
  A value script's and props script's JSON escapes `<`, U+2028 and U+2029.
40
40
 
@@ -59,6 +59,7 @@ with any other keys is data.
59
59
  | `R` | `RegExp` | `[source, flags]` |
60
60
  | `Y` | bytes | `[type, base64]`; type is `ArrayBuffer`, `DataView` or a typed array name |
61
61
  | `p` | a Suspense boundary inside a server tree | |
62
+ | `L` | a layout tree's outlet, where the page renders | `n`: the outlet's name, `children` for the page |
62
63
  | `F` | a `FormData` action argument | its index among the call's attachments |
63
64
  | `K` | a `Blob` or `File` action argument | its index among the call's attachments |
64
65
 
@@ -1,5 +1,5 @@
1
1
  import { A as VALUE_SCRIPT_TYPE, N as getTag, O as VALUES_HEADER, j as attachmentField, k as VALUE_KEY_PATTERN, o as BOUND_FIELD, s as CALL_FIELD, t as ACTION_HEADER } from "./wire-CXOodE06.mjs";
2
- import { a as encode, r as decode } from "./codec-BTDkzzPP.mjs";
2
+ import { a as encode, r as decode, u as setOwn } from "./codec-DqfVYKsx.mjs";
3
3
  // src/client/value-store.ts
4
4
  /**
5
5
  * The browser's store of values sent once. It keeps every value it has read, parses each once,
@@ -21,7 +21,8 @@ function createValueStore() {
21
21
  if (entry.state === "resolving") throw new Error(`islx value ${key} refers to itself`);
22
22
  entry.state = "resolving";
23
23
  try {
24
- entry.value = resolve(JSON.parse(entry.json));
24
+ const parsed = JSON.parse(entry.json);
25
+ entry.value = entry.json.includes(`"\$"`) ? resolve(parsed) : parsed;
25
26
  } catch (error) {
26
27
  entry.state = "raw";
27
28
  throw error;
@@ -44,12 +45,7 @@ function createValueStore() {
44
45
  };
45
46
  const resolveEntries = (object) => {
46
47
  const resolved = {};
47
- for (const key of Object.keys(object)) Object.defineProperty(resolved, key, {
48
- value: resolve(object[key]),
49
- enumerable: true,
50
- writable: true,
51
- configurable: true
52
- });
48
+ for (const key of Object.keys(object)) setOwn(resolved, key, resolve(object[key]));
53
49
  return resolved;
54
50
  };
55
51
  return {
@@ -1,5 +1,5 @@
1
1
  import { M as formFieldName, O as VALUES_HEADER, d as FORM_REF_PREFIX, k as VALUE_KEY_PATTERN } from "./wire-CXOodE06.mjs";
2
- import { a as encode, c as needsObjectTag, o as encodeLeaf, r as decode, t as EncodeError } from "./codec-BTDkzzPP.mjs";
2
+ import { a as encode, c as needsObjectTag, o as encodeLeaf, r as decode, t as EncodeError } from "./codec-DqfVYKsx.mjs";
3
3
  import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
4
4
  // src/server/values.ts
5
5
  /**
@@ -88,13 +88,21 @@ const NOT_A_VALUE = Symbol.for("islx.codec.not-a-value");
88
88
  /** A plain object from entries; `defineProperty`, so a "__proto__" key stays an ordinary key. */
89
89
  function plainObject(entries) {
90
90
  const object = {};
91
- for (const [key, value] of entries) Object.defineProperty(object, key, {
91
+ for (const [key, value] of entries) setOwn(object, key, value);
92
+ return object;
93
+ }
94
+ /**
95
+ * Sets an own property. Plain assignment keeps the object fast; only a `"__proto__"` key needs
96
+ * `defineProperty`, which assignment would turn into a prototype change.
97
+ */
98
+ function setOwn(object, key, value) {
99
+ if (key === "__proto__") Object.defineProperty(object, key, {
92
100
  value,
93
101
  enumerable: true,
94
102
  writable: true,
95
103
  configurable: true
96
104
  });
97
- return object;
105
+ else object[key] = value;
98
106
  }
99
107
  /**
100
108
  * The value a tagged object stands for, decoding children (a `Map`'s or `Set`'s entries, an `o`
@@ -268,4 +276,4 @@ function decode(value, options = {}) {
268
276
  };
269
277
  return visit(value);
270
278
  }
271
- export { encode as a, needsObjectTag as c, toBase64 as d, decodeTagged as i, plainObject as l, NOT_A_VALUE as n, encodeLeaf as o, decode as r, fromBase64 as s, EncodeError as t, tagged as u };
279
+ export { encode as a, needsObjectTag as c, tagged as d, toBase64 as f, decodeTagged as i, plainObject as l, NOT_A_VALUE as n, encodeLeaf as o, decode as r, fromBase64 as s, EncodeError as t, setOwn as u };
@@ -58,6 +58,13 @@ declare function createIsland<P extends object>(Component: React.ComponentType<P
58
58
  * page in its layout's React root and never shows its HTML, so the server leaves the HTML out.
59
59
  */
60
60
  declare const PAYLOAD_ONLY_ATTRIBUTE = "partial";
61
+ /**
62
+ * Where a layout rendered as a server tree places the page (or another outlet, by `name`). The
63
+ * tree records the outlet, and the server and the browser render what they are given there.
64
+ */
65
+ declare function Outlet(_props: {
66
+ name?: string;
67
+ }): null;
61
68
  interface ServerTreeRenderOptions {
62
69
  /**
63
70
  * Renders a part the resolver cannot represent, as server HTML whose client components are
@@ -71,6 +78,17 @@ interface ServerTreeRenderOptions {
71
78
  * streams and the server HTML parts it cannot represent, without the page's rendered HTML.
72
79
  */
73
80
  payloadOnly?: boolean;
81
+ /**
82
+ * A layout rendered as a server tree: what each `<Outlet>` renders, by name. The payload script
83
+ * then sits at the `children` outlet, inside the layout's markup, so a layout that renders the
84
+ * whole document keeps it in the body.
85
+ */
86
+ outlets?: Record<string, React.ReactNode>;
87
+ /**
88
+ * Names this tree's payload, chunks and server HTML parts, so a layout's tree and the trees at
89
+ * its outlet do not read each other's. A page's tree has none.
90
+ */
91
+ scope?: string;
74
92
  }
75
93
  /**
76
94
  * Resolves a server page and returns what renders in its place: the resolved tree and its
@@ -99,4 +117,23 @@ interface ServerTreeElementOptions {
99
117
  * navigation), after loading its client modules. Returns null for a page without a tree.
100
118
  */
101
119
  declare function createServerTreeElement(source: ParentNode | string, options: ServerTreeElementOptions): Promise<React.ReactElement | null>;
102
- export { renderServerTree as a, IslandRenderState as c, getClientReference as d, isolateIslands as f, createServerTreeElement as i, IslandsProvider as l, withinClientRoot as m, ServerTreeElementOptions as n, updateServerTrees as o, setRenderStateResolver as p, ServerTreeRenderOptions as r, ClientReference as s, PAYLOAD_ONLY_ATTRIBUTE as t, createIsland as u };
120
+ /** A layout rebuilt from its server tree. */
121
+ interface ServerTreeLayout {
122
+ /** The layout around these outlets: plain elements, the same tree the server rendered. */
123
+ render(outlets: Record<string, React.ReactNode>): React.ReactNode;
124
+ /** Stops following hot module updates. */
125
+ dispose(): void;
126
+ }
127
+ interface ServerTreeLayoutOptions extends Omit<ServerTreeElementOptions, "key"> {
128
+ /** Runs after a hot update to one of the layout's client modules: render the route again. */
129
+ onUpdate?(): void;
130
+ }
131
+ /**
132
+ * Rebuilds a layout the server rendered with `renderServerTree(..., { outlets, scope })`, from
133
+ * its server HTML or a navigation's HTML. The layout's own module never reaches the browser: its
134
+ * client components load from the tree, and `render(outlets)` places the page at its outlet.
135
+ * Rendering it with the same component types at the same place keeps its client state across
136
+ * navigations. Returns null when `source` has no tree for `scope`.
137
+ */
138
+ declare function createServerTreeLayout(source: ParentNode | string, scope: string, options: ServerTreeLayoutOptions): Promise<ServerTreeLayout | null>;
139
+ export { setRenderStateResolver as _, ServerTreeLayoutOptions as a, createServerTreeLayout as c, ClientReference as d, IslandRenderState as f, isolateIslands as g, getClientReference as h, ServerTreeLayout as i, renderServerTree as l, createIsland as m, PAYLOAD_ONLY_ATTRIBUTE as n, ServerTreeRenderOptions as o, IslandsProvider as p, ServerTreeElementOptions as r, createServerTreeElement as s, Outlet as t, updateServerTrees as u, withinClientRoot as v };
@@ -1,6 +1,6 @@
1
- import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-BTDkzzPP.mjs";
1
+ import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-DqfVYKsx.mjs";
2
2
  import { A as VALUE_SCRIPT_TYPE, D as TREE_SCRIPT_TYPE, S as SLOT_ELEMENT, c as CHUNK_SCRIPT_TYPE, h as ISLAND_ELEMENT, p as HTML_ELEMENT, y as PROPS_SCRIPT_TYPE } from "./wire-CXOodE06.mjs";
3
- import { l as getValueStore, t as createServerReference } from "./actions-CA_K-In7.mjs";
3
+ import { l as getValueStore, t as createServerReference } from "./actions-2sTj0A2T.mjs";
4
4
  import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
5
5
  import * as React from "react";
6
6
  import * as ReactDOM from "react-dom";
@@ -228,6 +228,15 @@ const SERVER_HTML_TAG = HTML_ELEMENT;
228
228
  */
229
229
  const PAYLOAD_ONLY_ATTRIBUTE = "partial";
230
230
  const SERVER_HTML_ATTRIBUTE = "name";
231
+ const OUTLET = Symbol.for("islx.react.outlet");
232
+ /**
233
+ * Where a layout rendered as a server tree places the page (or another outlet, by `name`). The
234
+ * tree records the outlet, and the server and the browser render what they are given there.
235
+ */
236
+ function Outlet(_props) {
237
+ return null;
238
+ }
239
+ Outlet[OUTLET] = true;
231
240
  const PARSER_SENSITIVE_TAGS = /* @__PURE__ */ new Set([
232
241
  "caption",
233
242
  "colgroup",
@@ -414,6 +423,10 @@ async function resolveElement(state, element, context) {
414
423
  i: id
415
424
  });
416
425
  }
426
+ if (typeof type === "function" && type[OUTLET]) return withKey({
427
+ $: "L",
428
+ n: String(props.name ?? "children")
429
+ });
417
430
  const clientReference = getClientReference(type);
418
431
  if (clientReference) {
419
432
  const { children, ...rest } = props;
@@ -511,6 +524,7 @@ function createTreeValue(value, resolver) {
511
524
  fallback: createTreeValue(value.f, resolver)
512
525
  }, React.createElement(getChunkComponent(), { chunk: resolver.chunk(value.i) }));
513
526
  case "x": return resolver.serverHtml(value.i, typeof value.k === "string" ? value.k : null);
527
+ case "L": return resolver.outlet(value.n);
514
528
  case "n": return Number(value.v);
515
529
  case "a": return resolver.action(value.v, typeof value.b === "string" ? value.b : void 0);
516
530
  case "P": return fulfilledThenable(createTreeValue(value.v, resolver));
@@ -571,6 +585,22 @@ async function createPayloadOnlyPage(state, payload, encode, resolver) {
571
585
  return React.createElement(React.Fragment, { key: `c${id}` }, createTreeScript(CHUNK_SCRIPT_TYPE, String(id), chunk.json, chunk.values));
572
586
  }));
573
587
  }
588
+ /** The name of a tree part: its number, after the tree's scope. */
589
+ function scopedName(scope, id) {
590
+ return scope ? `${scope}:${id}` : String(id);
591
+ }
592
+ function hasChildrenOutlet(value) {
593
+ if (value === null || typeof value !== "object") return false;
594
+ if (Array.isArray(value)) return value.some(hasChildrenOutlet);
595
+ if (value.$ === "L") return value.n === "children";
596
+ return Object.values(value).some(hasChildrenOutlet);
597
+ }
598
+ /** What renders at a layout's outlet: its content, after the layout's payload at `children`. */
599
+ function outletContent(outlets, name, script) {
600
+ const content = outlets?.[name] ?? null;
601
+ if (name !== "children" || !script) return content;
602
+ return React.createElement(React.Fragment, null, script, content);
603
+ }
574
604
  /**
575
605
  * Resolves a server page and returns what renders in its place: the resolved tree and its
576
606
  * payload. Errors from the page's own code (a framework's redirect or not-found) propagate
@@ -588,6 +618,7 @@ async function renderServerTree(element, options = {}) {
588
618
  chunks: /* @__PURE__ */ new Map(),
589
619
  actions: /* @__PURE__ */ new Map()
590
620
  };
621
+ const scope = options.scope;
591
622
  const tree = await resolveValue(state, element, { parserSensitive: false }, /* @__PURE__ */ new Set());
592
623
  const held = options.held && options.held.size > 0 ? options.held : null;
593
624
  const encode = (payload) => {
@@ -597,6 +628,7 @@ async function renderServerTree(element, options = {}) {
597
628
  values: addressed.values.map((value) => [value.key, hook.escape(value.json)])
598
629
  };
599
630
  };
631
+ let layoutScript = null;
600
632
  const resolver = {
601
633
  component(reference, exportName) {
602
634
  const Component = state.components.get(`${reference}#${exportName}`);
@@ -610,7 +642,7 @@ async function renderServerTree(element, options = {}) {
610
642
  status: "pending",
611
643
  promise: entry.encoded.then((encoded) => {
612
644
  const payload = encode({ t: encoded });
613
- chunk.value = React.createElement(React.Fragment, null, createTreeValue(encoded, resolver), createTreeScript(CHUNK_SCRIPT_TYPE, String(id), payload.json, payload.values));
645
+ chunk.value = React.createElement(React.Fragment, null, createTreeValue(encoded, resolver), createTreeScript(CHUNK_SCRIPT_TYPE, scopedName(scope, id), payload.json, payload.values));
614
646
  chunk.status = "fulfilled";
615
647
  return chunk.value;
616
648
  }, (error) => {
@@ -626,10 +658,13 @@ async function renderServerTree(element, options = {}) {
626
658
  serverHtml(id, key) {
627
659
  return React.createElement(SERVER_HTML_TAG, {
628
660
  ...key === null ? {} : { key },
629
- [SERVER_HTML_ATTRIBUTE]: String(id),
661
+ [SERVER_HTML_ATTRIBUTE]: scopedName(scope, id),
630
662
  style: { display: "contents" }
631
663
  }, isolate(state.serverHtml.get(id)));
632
664
  },
665
+ outlet(name) {
666
+ return outletContent(options.outlets, name, layoutScript);
667
+ },
633
668
  action(id) {
634
669
  return state.actions.get(id);
635
670
  }
@@ -638,6 +673,16 @@ async function renderServerTree(element, options = {}) {
638
673
  v: 1,
639
674
  t: tree
640
675
  });
676
+ if (options.outlets) {
677
+ const script = createTreeScript(TREE_SCRIPT_TYPE, scope ?? "", payload.json, payload.values);
678
+ const atOutlet = hasChildrenOutlet(tree);
679
+ if (atOutlet) layoutScript = script;
680
+ const layout = createTreeValue(tree, resolver);
681
+ const content = atOutlet ? layout : React.createElement(React.Fragment, null, layout, script);
682
+ return React.createElement(function ServerTreeLayout() {
683
+ return content;
684
+ });
685
+ }
641
686
  const page = options.payloadOnly && await createPayloadOnlyPage(state, payload, encode, resolver) || createTreePage(tree, payload.json, resolver, void 0, payload.values);
642
687
  return React.createElement(function ServerTreePage() {
643
688
  return page;
@@ -673,14 +718,10 @@ function exportOf(module, src, exportName) {
673
718
  if (typeof Component !== "function" && (typeof Component !== "object" || Component === null)) throw new Error(`${src} has no export ${exportName}`);
674
719
  return Component;
675
720
  }
676
- /**
677
- * Rebuilds a server-tree page in the browser from its server HTML (an element, or the HTML of a
678
- * navigation), after loading its client modules. Returns null for a page without a tree.
679
- */
680
- async function createServerTreeElement(source, options) {
721
+ /** Reads a tree's payload, chunks and server HTML from `root` and loads its client modules. */
722
+ async function prepareTree(root, scope, options) {
681
723
  const values = options.values ?? getValueStore();
682
- const root = typeof source === "string" ? parseHtml(source) : source;
683
- const script = root.querySelector(`script[type="${TREE_SCRIPT_TYPE}"]`);
724
+ const script = Array.from(root.querySelectorAll(`script[type="${TREE_SCRIPT_TYPE}"]`)).find((candidate) => (candidate.getAttribute("name") ?? void 0) === scope);
684
725
  if (!script) return null;
685
726
  const json = script.textContent ?? "";
686
727
  let ingested = false;
@@ -696,10 +737,19 @@ async function createServerTreeElement(source, options) {
696
737
  const payload = parsePayload(json);
697
738
  if (payload.v !== 1) throw new Error("unsupported server tree payload version");
698
739
  const scripts = readTreeValueScripts(script);
740
+ const partId = (name) => {
741
+ if (name === null) return null;
742
+ const prefix = scope === void 0 ? "" : `${scope}:`;
743
+ if (!name.startsWith(prefix)) return null;
744
+ const id = name.slice(prefix.length);
745
+ return /^\d+$/.test(id) ? Number(id) : null;
746
+ };
699
747
  const chunks = /* @__PURE__ */ new Map();
700
748
  for (const chunkScript of root.querySelectorAll(`script[type="${CHUNK_SCRIPT_TYPE}"]`)) {
749
+ const id = partId(chunkScript.getAttribute("name"));
750
+ if (id === null) continue;
701
751
  const chunkJson = chunkScript.textContent ?? "";
702
- chunks.set(Number(chunkScript.getAttribute("name")), {
752
+ chunks.set(id, {
703
753
  tree: parsePayload(chunkJson).t,
704
754
  json: chunkJson,
705
755
  values: readTreeValueScripts(chunkScript)
@@ -715,52 +765,84 @@ async function createServerTreeElement(source, options) {
715
765
  components.set(key, exportOf(await options.load(src), src, key.slice(separator + 1)));
716
766
  }));
717
767
  const serverHtml = /* @__PURE__ */ new Map();
718
- for (const container of root.querySelectorAll(`${SERVER_HTML_TAG}[${SERVER_HTML_ATTRIBUTE}]`)) serverHtml.set(Number(container.getAttribute(SERVER_HTML_ATTRIBUTE)), container.innerHTML);
719
- const resolver = {
720
- component(reference, exportName) {
721
- const Component = components.get(`${reference}#${exportName}`);
722
- if (!Component) throw new Error(`client reference ${reference}#${exportName} was not loaded`);
723
- return Component;
724
- },
725
- chunk(id) {
726
- const chunk = chunks.get(id);
727
- if (!chunk) throw new Error(`server tree chunk ${id} was not found`);
728
- const value = React.createElement(React.Fragment, null, createTreeValue(chunk.tree, resolver), createTreeScript(CHUNK_SCRIPT_TYPE, String(id), chunk.json, chunk.values));
729
- return {
730
- status: "fulfilled",
731
- value,
732
- promise: Promise.resolve(value)
733
- };
734
- },
735
- serverHtml(id, key) {
736
- const html = serverHtml.get(id);
737
- if (html === void 0) throw new Error(`server HTML ${id} was not found`);
738
- return React.createElement(options.ServerHtml, {
739
- ...key === null ? {} : { key },
740
- as: SERVER_HTML_TAG,
741
- [SERVER_HTML_ATTRIBUTE]: String(id),
742
- style: { display: "contents" },
743
- suppressHydrationWarning: true,
744
- dangerouslySetInnerHTML: { __html: html }
745
- });
746
- },
747
- action(id, sealed) {
748
- return createServerReference(id, sealed);
749
- }
768
+ for (const container of root.querySelectorAll(`${SERVER_HTML_TAG}[${SERVER_HTML_ATTRIBUTE}]`)) {
769
+ const id = partId(container.getAttribute(SERVER_HTML_ATTRIBUTE));
770
+ if (id !== null) serverHtml.set(id, container.innerHTML);
771
+ }
772
+ const resolver = (outlets, outletScript) => {
773
+ const treeResolver = {
774
+ component(reference, exportName) {
775
+ const Component = components.get(`${reference}#${exportName}`);
776
+ if (!Component) throw new Error(`client reference ${reference}#${exportName} was not loaded`);
777
+ return Component;
778
+ },
779
+ chunk(id) {
780
+ const chunk = chunks.get(id);
781
+ if (!chunk) throw new Error(`server tree chunk ${id} was not found`);
782
+ const value = React.createElement(React.Fragment, null, createTreeValue(chunk.tree, treeResolver), createTreeScript(CHUNK_SCRIPT_TYPE, scopedName(scope, id), chunk.json, chunk.values));
783
+ return {
784
+ status: "fulfilled",
785
+ value,
786
+ promise: Promise.resolve(value)
787
+ };
788
+ },
789
+ serverHtml(id, key) {
790
+ const html = serverHtml.get(id);
791
+ if (html === void 0) throw new Error(`server HTML ${id} was not found`);
792
+ return React.createElement(options.ServerHtml, {
793
+ ...key === null ? {} : { key },
794
+ as: SERVER_HTML_TAG,
795
+ [SERVER_HTML_ATTRIBUTE]: scopedName(scope, id),
796
+ style: { display: "contents" },
797
+ suppressHydrationWarning: true,
798
+ dangerouslySetInnerHTML: { __html: html }
799
+ });
800
+ },
801
+ outlet(name) {
802
+ return outletContent(outlets, name, outletScript);
803
+ },
804
+ action(id, sealed) {
805
+ return createServerReference(id, sealed);
806
+ }
807
+ };
808
+ return treeResolver;
750
809
  };
751
- const build = () => createTreePage(payload.t, json, resolver, void 0, scripts);
810
+ return {
811
+ payload: payload.t,
812
+ json,
813
+ values: scripts,
814
+ components,
815
+ resolver,
816
+ hasOutlet: hasChildrenOutlet(payload.t)
817
+ };
818
+ }
819
+ /** Points a tree's loaded components at a hot-updated module; true when the tree renders it. */
820
+ function updateTreeComponents(components, src, module) {
821
+ let changed = false;
822
+ for (const key of components.keys()) {
823
+ const separator = key.lastIndexOf("#");
824
+ if (key.slice(0, separator) !== src) continue;
825
+ components.set(key, exportOf(module, src, key.slice(separator + 1)));
826
+ changed = true;
827
+ }
828
+ return changed;
829
+ }
830
+ /**
831
+ * Rebuilds a server-tree page in the browser from its server HTML (an element, or the HTML of a
832
+ * navigation), after loading its client modules. Returns null for a page without a tree.
833
+ */
834
+ async function createServerTreeElement(source, options) {
835
+ const prepared = await prepareTree(typeof source === "string" ? parseHtml(source) : source, void 0, options);
836
+ if (!prepared) return null;
837
+ const tree = prepared;
838
+ const resolver = tree.resolver();
839
+ const build = () => createTreePage(tree.payload, tree.json, resolver, void 0, tree.values);
752
840
  build();
753
841
  function ServerTreePage() {
754
842
  const [, setVersion] = React.useState(0);
755
843
  React.useEffect(() => {
756
844
  const listener = (src, module) => {
757
- let changed = false;
758
- for (const key of components.keys()) {
759
- const separator = key.lastIndexOf("#");
760
- if (key.slice(0, separator) !== src) continue;
761
- components.set(key, exportOf(module, src, key.slice(separator + 1)));
762
- changed = true;
763
- }
845
+ const changed = updateTreeComponents(tree.components, src, module);
764
846
  if (changed) setVersion((version) => version + 1);
765
847
  return changed;
766
848
  };
@@ -774,4 +856,35 @@ async function createServerTreeElement(source, options) {
774
856
  }
775
857
  return React.createElement(ServerTreePage, options.key === void 0 ? null : { key: options.key });
776
858
  }
777
- export { IslandsProvider as a, getContexts as c, setIslandServerHook as d, setRenderStateResolver as f, updateServerTrees as i, isolateIslands as l, createServerTreeElement as n, createIsland as o, withinClientRoot as p, renderServerTree as r, getClientReference as s, PAYLOAD_ONLY_ATTRIBUTE as t, runtimeTagged as u };
859
+ /**
860
+ * Rebuilds a layout the server rendered with `renderServerTree(..., { outlets, scope })`, from
861
+ * its server HTML or a navigation's HTML. The layout's own module never reaches the browser: its
862
+ * client components load from the tree, and `render(outlets)` places the page at its outlet.
863
+ * Rendering it with the same component types at the same place keeps its client state across
864
+ * navigations. Returns null when `source` has no tree for `scope`.
865
+ */
866
+ async function createServerTreeLayout(source, scope, options) {
867
+ const tree = await prepareTree(typeof source === "string" ? parseHtml(source) : source, scope, options);
868
+ if (!tree) return null;
869
+ const script = createTreeScript(TREE_SCRIPT_TYPE, scope, tree.json, tree.values);
870
+ const render = (outlets) => {
871
+ const resolver = tree.resolver(outlets, tree.hasOutlet ? script : void 0);
872
+ const layout = createTreeValue(tree.payload, resolver);
873
+ return tree.hasOutlet ? layout : React.createElement(React.Fragment, null, layout, script);
874
+ };
875
+ render({});
876
+ const listener = (src, module) => {
877
+ const changed = updateTreeComponents(tree.components, src, module);
878
+ if (changed) options.onUpdate?.();
879
+ return changed;
880
+ };
881
+ const listeners = treeListeners();
882
+ listeners.add(listener);
883
+ return {
884
+ render,
885
+ dispose: () => {
886
+ listeners.delete(listener);
887
+ }
888
+ };
889
+ }
890
+ export { renderServerTree as a, createIsland as c, isolateIslands as d, runtimeTagged as f, withinClientRoot as h, createServerTreeLayout as i, getClientReference as l, setRenderStateResolver as m, PAYLOAD_ONLY_ATTRIBUTE as n, updateServerTrees as o, setIslandServerHook as p, createServerTreeElement as r, IslandsProvider as s, Outlet as t, getContexts as u };
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as emit, c as createValueStore, i as defineClientHooks, l as getValueStore, n as decodeActionReference, o as hookEventName, r as setActionNavigator, s as setClientHooks, t as createServerReference, u as valueHeaders } from "./_chunks/actions-CA_K-In7.mjs";
1
+ import { a as emit, c as createValueStore, i as defineClientHooks, l as getValueStore, n as decodeActionReference, o as hookEventName, r as setActionNavigator, s as setClientHooks, t as createServerReference, u as valueHeaders } from "./_chunks/actions-2sTj0A2T.mjs";
2
2
  import { F as stripRuntimeFields, P as readPostedAction, x as RENDER_HEADER } from "./_chunks/wire-CXOodE06.mjs";
3
3
  import { i as REPLAYABLE_SELECTOR, n as PENDING_INTERACTIONS, r as PREHYDRATION_SCRIPT, t as INTERACTION_EVENT } from "./_chunks/prehydration-B1GLTLdk.mjs";
4
4
  // src/client/forms.ts
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { A as VALUE_SCRIPT_TYPE, C as SLOT_TAG, D as TREE_SCRIPT_TYPE, E as TAG_VALUE, F as stripRuntimeFields, M as formFieldName, N as getTag, O as VALUES_HEADER, P as readPostedAction, S as SLOT_ELEMENT, T as TAG_BOUND, _ as MAX_HELD_VALUES, a as BLOB_TAG, b as REFERENCE_TAG, c as CHUNK_SCRIPT_TYPE, d as FORM_REF_PREFIX, f as FORM_STATE_SCRIPT_TYPE, g as LAYOUTS_HEADER, h as ISLAND_ELEMENT, i as ACTION_TAG, j as attachmentField, k as VALUE_KEY_PATTERN, l as FORM_DATA_TAG, m as HYDRATED_ATTRIBUTE, n as ACTION_KEY_FIELD, o as BOUND_FIELD, p as HTML_ELEMENT, r as ACTION_STALE_HEADER, s as CALL_FIELD, t as ACTION_HEADER, u as FORM_FIELD_PREFIX, v as OBJECT_TAG, w as TAG, x as RENDER_HEADER, y as PROPS_SCRIPT_TYPE } from "./_chunks/wire-CXOodE06.mjs";
2
- import { a as encode, c as needsObjectTag, d as toBase64, i as decodeTagged, l as plainObject, n as NOT_A_VALUE, o as encodeLeaf, r as decode, s as fromBase64, t as EncodeError, u as tagged } from "./_chunks/codec-BTDkzzPP.mjs";
2
+ import { a as encode, c as needsObjectTag, d as tagged, f as toBase64, i as decodeTagged, l as plainObject, n as NOT_A_VALUE, o as encodeLeaf, r as decode, s as fromBase64, t as EncodeError } from "./_chunks/codec-DqfVYKsx.mjs";
3
3
  export { ACTION_HEADER, ACTION_KEY_FIELD, ACTION_STALE_HEADER, ACTION_TAG, BLOB_TAG, BOUND_FIELD, CALL_FIELD, CHUNK_SCRIPT_TYPE, EncodeError, FORM_DATA_TAG, FORM_FIELD_PREFIX, FORM_REF_PREFIX, FORM_STATE_SCRIPT_TYPE, HTML_ELEMENT, HYDRATED_ATTRIBUTE, ISLAND_ELEMENT, LAYOUTS_HEADER, MAX_HELD_VALUES, NOT_A_VALUE, OBJECT_TAG, PROPS_SCRIPT_TYPE, REFERENCE_TAG, RENDER_HEADER, SLOT_ELEMENT, SLOT_TAG, TAG, TAG_BOUND, TAG_VALUE, TREE_SCRIPT_TYPE, VALUES_HEADER, VALUE_KEY_PATTERN, VALUE_SCRIPT_TYPE, attachmentField, decode, decodeTagged, encode, encodeLeaf, formFieldName, fromBase64, getTag, needsObjectTag, plainObject, readPostedAction, stripRuntimeFields, tagged, toBase64 };
@@ -1,7 +1,7 @@
1
1
  import { a as scheduleHydration, i as isIslandStrategy, n as IslandStrategy, r as ScheduleOptions, t as ISLAND_STRATEGIES } from "../_chunks/schedule-Dw14dW0H.mjs";
2
2
  import { m as HYDRATED_ATTRIBUTE } from "../_chunks/wire-C3Rpbwl6.mjs";
3
3
  import { t as ValueStore } from "../_chunks/value-store-Dm-7xJ6W.mjs";
4
- import { f as isolateIslands, i as createServerTreeElement, m as withinClientRoot, n as ServerTreeElementOptions, o as updateServerTrees, t as PAYLOAD_ONLY_ATTRIBUTE } from "../_chunks/tree-fID6sz25.mjs";
4
+ import { a as ServerTreeLayoutOptions, c as createServerTreeLayout, g as isolateIslands, i as ServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, r as ServerTreeElementOptions, s as createServerTreeElement, u as updateServerTrees, v as withinClientRoot } from "../_chunks/tree-B4RMU2UX.mjs";
5
5
  import * as React from "react";
6
6
 
7
7
  // src/react/hydrate.d.ts
@@ -46,4 +46,4 @@ declare function createServerHtml(runtime: Pick<HydrationRuntime, "hydrate" | "d
46
46
  declare function refreshDocument(runtime: HydrationRuntime): Promise<void>;
47
47
  /** Puts another page's body in place of this one's, and hydrates its islands. */
48
48
  declare function swapDocument(runtime: HydrationRuntime, next: Document): Promise<void>;
49
- export { HYDRATED_ATTRIBUTE, type HydrationOptions, type HydrationRuntime, ISLAND_STRATEGIES, type IslandStrategy, PAYLOAD_ONLY_ATTRIBUTE, type ScheduleOptions, type ServerTreeElementOptions, createHydrationRuntime, createServerHtml, createServerTreeElement, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
49
+ export { HYDRATED_ATTRIBUTE, type HydrationOptions, type HydrationRuntime, ISLAND_STRATEGIES, type IslandStrategy, PAYLOAD_ONLY_ATTRIBUTE, type ScheduleOptions, type ServerTreeElementOptions, type ServerTreeLayout, type ServerTreeLayoutOptions, createHydrationRuntime, createServerHtml, createServerTreeElement, createServerTreeLayout, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
@@ -1,7 +1,7 @@
1
- import { a as emit, l as getValueStore, t as createServerReference, u as valueHeaders } from "../_chunks/actions-CA_K-In7.mjs";
2
- import { i as decodeTagged, n as NOT_A_VALUE, r as decode } from "../_chunks/codec-BTDkzzPP.mjs";
1
+ import { a as emit, l as getValueStore, t as createServerReference, u as valueHeaders } from "../_chunks/actions-2sTj0A2T.mjs";
2
+ import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-DqfVYKsx.mjs";
3
3
  import { N as getTag, S as SLOT_ELEMENT, f as FORM_STATE_SCRIPT_TYPE, h as ISLAND_ELEMENT, m as HYDRATED_ATTRIBUTE, y as PROPS_SCRIPT_TYPE } from "../_chunks/wire-CXOodE06.mjs";
4
- import { c as getContexts, i as updateServerTrees, l as isolateIslands, n as createServerTreeElement, p as withinClientRoot, t as PAYLOAD_ONLY_ATTRIBUTE } from "../_chunks/tree-BEV73f_8.mjs";
4
+ import { d as isolateIslands, h as withinClientRoot, i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement, u as getContexts } from "../_chunks/tree-BvYR6-PF.mjs";
5
5
  import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-BFoGKRrU.mjs";
6
6
  import * as React from "react";
7
7
  import { hydrateRoot } from "react-dom/client";
@@ -31,8 +31,26 @@ function readFormState(document) {
31
31
  return;
32
32
  }
33
33
  }
34
+ /**
35
+ * Revived values, by the object the value store resolved. Islands that share a value (one sent
36
+ * once for several islands) then share the revived object instead of walking it again. A value
37
+ * with a slot is not kept: a slot's HTML belongs to one island.
38
+ */
39
+ const revivedValues = /* @__PURE__ */ new WeakMap();
40
+ let slotsRevived = 0;
34
41
  /** Turns travelled props back into values: slots keep their server HTML, actions become callable. */
35
42
  function revive(value, container) {
43
+ if (value && typeof value === "object") {
44
+ const cached = revivedValues.get(value);
45
+ if (cached !== void 0) return cached;
46
+ const slotsBefore = slotsRevived;
47
+ const revived = reviveUncached(value, container);
48
+ if (slotsRevived === slotsBefore) revivedValues.set(value, revived);
49
+ return revived;
50
+ }
51
+ return value;
52
+ }
53
+ function reviveUncached(value, container) {
36
54
  if (Array.isArray(value)) {
37
55
  let changed = false;
38
56
  const revived = value.map((entry) => {
@@ -42,7 +60,6 @@ function revive(value, container) {
42
60
  });
43
61
  return changed ? revived : value;
44
62
  }
45
- if (!value || typeof value !== "object") return value;
46
63
  const object = value;
47
64
  const tag = getTag(object);
48
65
  if (tag === "a" && typeof object["v"] === "string") {
@@ -50,6 +67,7 @@ function revive(value, container) {
50
67
  return createServerReference(object["v"], typeof token === "string" ? token : void 0);
51
68
  }
52
69
  if (tag === "s" && typeof object["v"] === "string") {
70
+ slotsRevived++;
53
71
  const name = object["v"];
54
72
  let html = "";
55
73
  for (const slot of container.querySelectorAll(SLOT_ELEMENT)) if (slot.getAttribute("name") === name) {
@@ -73,16 +91,16 @@ function revive(value, container) {
73
91
  const revived = {};
74
92
  for (const [key, entry] of Object.entries(object)) {
75
93
  const next = revive(entry, container);
76
- Object.defineProperty(revived, key, {
77
- value: next,
78
- enumerable: true,
79
- writable: true,
80
- configurable: true
81
- });
94
+ setOwn(revived, key, next);
82
95
  if (next !== entry) changed = true;
83
96
  }
84
97
  return changed ? revived : value;
85
98
  }
99
+ /**
100
+ * React streams Suspense content that resolves late into a hidden `<div id="S:n">`, then moves it
101
+ * into place. Its islands hydrate once moved, not while staged.
102
+ */
103
+ const STREAM_STAGING = "div[hidden][id^=\"S:\"]";
86
104
  /** The hydration runtime for one page. */
87
105
  function createHydrationRuntime(options) {
88
106
  const roots = /* @__PURE__ */ new Map();
@@ -146,6 +164,7 @@ function createHydrationRuntime(options) {
146
164
  const text = script.textContent || "{}";
147
165
  let parsed = JSON.parse(text);
148
166
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("props must be an object");
167
+ if (!text.includes(`"\$"`)) return parsed;
149
168
  if (text.includes(`"\$":"r"`)) {
150
169
  values.ingest(container.ownerDocument);
151
170
  parsed = values.resolve(parsed);
@@ -160,9 +179,10 @@ function createHydrationRuntime(options) {
160
179
  formStateRead = true;
161
180
  formState = readFormState(scope instanceof Document ? scope : scope.ownerDocument ?? document);
162
181
  }
182
+ if (scope instanceof Document) watchStream(scope);
163
183
  const candidates = Array.from(scope.querySelectorAll(ISLAND_SELECTOR));
164
184
  if (scope instanceof Element && scope.matches(ISLAND_SELECTOR)) candidates.unshift(scope);
165
- const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !container.parentElement?.closest(ISLAND_SELECTOR));
185
+ const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !container.parentElement?.closest(ISLAND_SELECTOR) && !container.closest(STREAM_STAGING));
166
186
  await Promise.all(islands.map(async (container) => {
167
187
  const src = container.getAttribute("src");
168
188
  const id = container.getAttribute("props");
@@ -312,6 +332,39 @@ function createHydrationRuntime(options) {
312
332
  return updated;
313
333
  }
314
334
  /** Unmounts the islands inside `scope`, and stops scheduled ones. */
335
+ /**
336
+ * A page still streaming: islands in Suspense content that arrives later hydrate as React puts it
337
+ * in place, and islands React removes (a replaced fallback) are released. Ends once the document
338
+ * is parsed and React has revealed every staged boundary (it may batch reveals past parsing).
339
+ */
340
+ let watching = false;
341
+ function watchStream(document) {
342
+ if (watching || document.readyState !== "loading" || typeof MutationObserver !== "function") return;
343
+ watching = true;
344
+ const removed = /* @__PURE__ */ new Set();
345
+ let queued = false;
346
+ const run = () => {
347
+ queued = false;
348
+ for (const node of removed) if (!node.isConnected) dispose(node);
349
+ removed.clear();
350
+ if (document.readyState !== "loading" && !document.querySelector(STREAM_STAGING)) {
351
+ observer.disconnect();
352
+ watching = false;
353
+ }
354
+ hydrate(document).catch((error) => report("Hydrating streamed islands failed.", error));
355
+ };
356
+ const observer = new MutationObserver((records) => {
357
+ for (const record of records) for (const node of record.removedNodes) removed.add(node);
358
+ if (queued) return;
359
+ queued = true;
360
+ queueMicrotask(run);
361
+ });
362
+ observer.observe(document, {
363
+ childList: true,
364
+ subtree: true
365
+ });
366
+ document.addEventListener("DOMContentLoaded", run, { once: true });
367
+ }
315
368
  function dispose(scope) {
316
369
  for (const [container, controller] of pending) if (container === scope || scope.contains(container)) {
317
370
  controller.abort();
@@ -400,4 +453,4 @@ async function swapDocument(runtime, next) {
400
453
  document.body.replaceChildren(...Array.from(body.childNodes));
401
454
  await runtime.hydrate(document);
402
455
  }
403
- export { HYDRATED_ATTRIBUTE, ISLAND_STRATEGIES, PAYLOAD_ONLY_ATTRIBUTE, createHydrationRuntime, createServerHtml, createServerTreeElement, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
456
+ export { HYDRATED_ATTRIBUTE, ISLAND_STRATEGIES, PAYLOAD_ONLY_ATTRIBUTE, createHydrationRuntime, createServerHtml, createServerTreeElement, createServerTreeLayout, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
@@ -1,5 +1,5 @@
1
1
  import { r as ReactFormState } from "../_chunks/handle-C0N9ZyNV.mjs";
2
- import { a as renderServerTree, c as IslandRenderState, d as getClientReference, f as isolateIslands, l as IslandsProvider, m as withinClientRoot, p as setRenderStateResolver, r as ServerTreeRenderOptions, s as ClientReference, t as PAYLOAD_ONLY_ATTRIBUTE, u as createIsland } from "../_chunks/tree-fID6sz25.mjs";
2
+ import { _ as setRenderStateResolver, d as ClientReference, f as IslandRenderState, g as isolateIslands, h as getClientReference, l as renderServerTree, m as createIsland, n as PAYLOAD_ONLY_ATTRIBUTE, o as ServerTreeRenderOptions, p as IslandsProvider, t as Outlet, v as withinClientRoot } from "../_chunks/tree-B4RMU2UX.mjs";
3
3
  import * as React from "react";
4
4
  import { RenderToReadableStreamOptions } from "react-dom/server";
5
5
 
@@ -48,4 +48,4 @@ declare function renderToStream(element: React.ReactNode, {
48
48
  }>;
49
49
  /** Whether a response rendered for `request` leaves out values its browser holds. */
50
50
  declare function isPersonalized(request: Request): boolean;
51
- export { type ClientReference, type IslandRenderState, IslandsProvider, PAYLOAD_ONLY_ATTRIBUTE, RenderToStreamOptions, type ServerTreeRenderOptions, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
51
+ export { type ClientReference, type IslandRenderState, IslandsProvider, Outlet, PAYLOAD_ONLY_ATTRIBUTE, RenderToStreamOptions, type ServerTreeRenderOptions, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
@@ -1,9 +1,9 @@
1
- import { a as encode } from "../_chunks/codec-BTDkzzPP.mjs";
1
+ import { a as encode } from "../_chunks/codec-DqfVYKsx.mjs";
2
2
  import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-CXOodE06.mjs";
3
3
  import { r as PREHYDRATION_SCRIPT } from "../_chunks/prehydration-B1GLTLdk.mjs";
4
- import { g as escapeScriptJson, h as addressValues, i as encodeActionReference, v as parseHeldValues, y as selectValuesToSend } from "../_chunks/actions-C4dZ1-XD.mjs";
4
+ import { g as escapeScriptJson, h as addressValues, i as encodeActionReference, v as parseHeldValues, y as selectValuesToSend } from "../_chunks/actions-B8dJ8f7L.mjs";
5
5
  import { t as getBuildDefaults } from "../_chunks/build-COIcs7wm.mjs";
6
- import { a as IslandsProvider, c as getContexts, d as setIslandServerHook, f as setRenderStateResolver, l as isolateIslands, o as createIsland, p as withinClientRoot, r as renderServerTree, s as getClientReference, t as PAYLOAD_ONLY_ATTRIBUTE, u as runtimeTagged } from "../_chunks/tree-BEV73f_8.mjs";
6
+ import { a as renderServerTree, c as createIsland, d as isolateIslands, f as runtimeTagged, h as withinClientRoot, l as getClientReference, m as setRenderStateResolver, n as PAYLOAD_ONLY_ATTRIBUTE, p as setIslandServerHook, s as IslandsProvider, t as Outlet, u as getContexts } from "../_chunks/tree-BvYR6-PF.mjs";
7
7
  import * as React from "react";
8
8
  // src/react/server.ts
9
9
  /**
@@ -81,4 +81,4 @@ async function renderToStream(element, { request, formState, bootstrap = true, p
81
81
  function isPersonalized(request) {
82
82
  return parseHeldValues(request).size > 0;
83
83
  }
84
- export { IslandsProvider, PAYLOAD_ONLY_ATTRIBUTE, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
84
+ export { IslandsProvider, Outlet, PAYLOAD_ONLY_ATTRIBUTE, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
package/dist/react.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { a as renderServerTree, c as IslandRenderState, f as isolateIslands, l as IslandsProvider, m as withinClientRoot, r as ServerTreeRenderOptions, u as createIsland } from "./_chunks/tree-fID6sz25.mjs";
1
+ import { f as IslandRenderState, g as isolateIslands, l as renderServerTree, m as createIsland, o as ServerTreeRenderOptions, p as IslandsProvider, t as Outlet, v as withinClientRoot } from "./_chunks/tree-B4RMU2UX.mjs";
2
2
  import { n as IslandStrategy } from "./_chunks/schedule-Dw14dW0H.mjs";
3
- export { type IslandRenderState, type IslandStrategy, IslandsProvider, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, withinClientRoot };
3
+ export { type IslandRenderState, type IslandStrategy, IslandsProvider, Outlet, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, withinClientRoot };
package/dist/react.mjs CHANGED
@@ -1 +1 @@
1
- export { a as IslandsProvider, o as createIsland, l as isolateIslands, r as renderServerTree, p as withinClientRoot } from "./_chunks/tree-BEV73f_8.mjs";
1
+ export { s as IslandsProvider, t as Outlet, c as createIsland, d as isolateIslands, a as renderServerTree, h as withinClientRoot } from "./_chunks/tree-BvYR6-PF.mjs";
package/dist/server.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as hashValue, a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, g as escapeScriptJson, h as addressValues, i as encodeActionReference, l as SealError, m as VALUE_MIN_LENGTH, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys, v as parseHeldValues, y as selectValuesToSend } from "./_chunks/actions-C4dZ1-XD.mjs";
2
- import { a as encode, r as decode } from "./_chunks/codec-BTDkzzPP.mjs";
1
+ import { _ as hashValue, a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, g as escapeScriptJson, h as addressValues, i as encodeActionReference, l as SealError, m as VALUE_MIN_LENGTH, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys, v as parseHeldValues, y as selectValuesToSend } from "./_chunks/actions-B8dJ8f7L.mjs";
2
+ import { a as encode, r as decode } from "./_chunks/codec-DqfVYKsx.mjs";
3
3
  import { F as stripRuntimeFields, P as readPostedAction, j as attachmentField, n as ACTION_KEY_FIELD, o as BOUND_FIELD, r as ACTION_STALE_HEADER, s as CALL_FIELD, t as ACTION_HEADER } from "./_chunks/wire-CXOodE06.mjs";
4
4
  import { n as setBuildDefaults, t as getBuildDefaults } from "./_chunks/build-COIcs7wm.mjs";
5
5
  // src/server/hooks.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/islx",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A renderer-agnostic wire format for server-rendered islands: typed props, values sent once, server actions, and partial navigations.",
5
5
  "keywords": [
6
6
  "islands",