@farm.js/islx 0.0.0-stage → 0.0.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -2
  4. package/SPEC.md +220 -0
  5. package/dist/_chunks/actions-BCtIGvrP.d.mts +58 -0
  6. package/dist/_chunks/actions-C4dZ1-XD.mjs +615 -0
  7. package/dist/_chunks/actions-CA_K-In7.mjs +310 -0
  8. package/dist/_chunks/actions-Eutk6UzT.mjs +326 -0
  9. package/dist/_chunks/build-COIcs7wm.mjs +9 -0
  10. package/dist/_chunks/codec-BTDkzzPP.mjs +271 -0
  11. package/dist/_chunks/codec-CK8kZp4u.d.mts +64 -0
  12. package/dist/_chunks/handle-C0N9ZyNV.d.mts +35 -0
  13. package/dist/_chunks/hooks-3VZV8f_d.d.mts +30 -0
  14. package/dist/_chunks/hooks-De0gNpOn.d.mts +64 -0
  15. package/dist/_chunks/prehydration-B1GLTLdk.mjs +36 -0
  16. package/dist/_chunks/schedule-BFoGKRrU.mjs +216 -0
  17. package/dist/_chunks/schedule-Dw14dW0H.d.mts +17 -0
  18. package/dist/_chunks/tree-BEV73f_8.mjs +777 -0
  19. package/dist/_chunks/tree-fID6sz25.d.mts +102 -0
  20. package/dist/_chunks/value-store-Dm-7xJ6W.d.mts +19 -0
  21. package/dist/_chunks/wire-C3Rpbwl6.d.mts +98 -0
  22. package/dist/_chunks/wire-CXOodE06.mjs +121 -0
  23. package/dist/client.d.mts +58 -0
  24. package/dist/client.mjs +197 -0
  25. package/dist/compiler.d.mts +2 -0
  26. package/dist/compiler.mjs +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +3 -0
  29. package/dist/react/client.d.mts +49 -0
  30. package/dist/react/client.mjs +403 -0
  31. package/dist/react/server.d.mts +51 -0
  32. package/dist/react/server.mjs +84 -0
  33. package/dist/react.d.mts +3 -0
  34. package/dist/react.mjs +1 -0
  35. package/dist/server.d.mts +200 -0
  36. package/dist/server.mjs +378 -0
  37. package/dist/vite.d.mts +57 -0
  38. package/dist/vite.mjs +497 -0
  39. package/package.json +121 -4
@@ -0,0 +1,102 @@
1
+ import { t as ValueStore } from "./value-store-Dm-7xJ6W.mjs";
2
+ import { n as IslandStrategy } from "./schedule-Dw14dW0H.mjs";
3
+ import * as React from "react";
4
+
5
+ // src/react/island.d.ts
6
+
7
+ interface ClientReference {
8
+ src: string;
9
+ exportName: string;
10
+ Component: React.ComponentType<any>;
11
+ }
12
+ /** The module export an island component renders, or undefined for any other type. */
13
+ declare function getClientReference(type: unknown): ClientReference | undefined;
14
+ /**
15
+ * Makes `element`'s client components part of the React root around it (a layout that hydrates
16
+ * as one root), instead of islands of their own.
17
+ */
18
+ declare function withinClientRoot(element: React.ReactNode): React.ReactElement;
19
+ /**
20
+ * Starts islands again below a `withinClientRoot`: the page a hydrating layout keeps as server
21
+ * HTML renders its client components as islands of their own.
22
+ */
23
+ declare function isolateIslands(element: React.ReactNode): React.ReactElement;
24
+ /**
25
+ * For a framework that knows the request being rendered without React context (a request store):
26
+ * returns the render's settings when no `IslandsProvider` or `renderToStream` gave them. Keep the
27
+ * same `sent` set for the whole response.
28
+ */
29
+ declare function setRenderStateResolver(resolve: (() => IslandRenderState | null) | undefined): void;
30
+ /**
31
+ * Gives islands a render's settings when the framework renders the page itself (not through
32
+ * `renderToStream`): which values the browser holds, and which this render already wrote.
33
+ */
34
+ declare function IslandsProvider({
35
+ held,
36
+ children
37
+ }: {
38
+ held?: ReadonlySet<string> | null;
39
+ children?: React.ReactNode;
40
+ }): React.ReactElement;
41
+ /** The render's settings, from `renderToStream`. */
42
+ interface IslandRenderState {
43
+ /** Value keys the browser holds; null sends every value (a cacheable response). */
44
+ held: ReadonlySet<string> | null;
45
+ /**
46
+ * Value keys this render already wrote. Every value script lands in the document before the
47
+ * runtime runs, so an island later in the render, or in a boundary that streams later, finds it.
48
+ */
49
+ sent: Set<string>;
50
+ }
51
+ /**
52
+ * Wraps a client component so the server renders it as an island. `src` is the module the browser
53
+ * imports, `exportName` the component's export there.
54
+ */
55
+ declare function createIsland<P extends object>(Component: React.ComponentType<P>, src: string, exportName: string, strategy?: IslandStrategy): React.ComponentType<P>;
56
+ /**
57
+ * Marks a navigation response that carries only the page's payload. The browser rebuilds such a
58
+ * page in its layout's React root and never shows its HTML, so the server leaves the HTML out.
59
+ */
60
+ declare const PAYLOAD_ONLY_ATTRIBUTE = "partial";
61
+ interface ServerTreeRenderOptions {
62
+ /**
63
+ * Renders a part the resolver cannot represent, as server HTML whose client components are
64
+ * islands. Default `isolateIslands`.
65
+ */
66
+ isolate?(element: React.ReactNode): React.ReactNode;
67
+ /** Value keys the browser holds (`parseHeldValues(request)`): the payload leaves them out. */
68
+ held?: ReadonlySet<string> | null;
69
+ /**
70
+ * A navigation the browser renders in its layout's React root: send the payload, the chunks it
71
+ * streams and the server HTML parts it cannot represent, without the page's rendered HTML.
72
+ */
73
+ payloadOnly?: boolean;
74
+ }
75
+ /**
76
+ * Resolves a server page and returns what renders in its place: the resolved tree and its
77
+ * payload. Errors from the page's own code (a framework's redirect or not-found) propagate
78
+ * unchanged. Suspense children resolve concurrently and stream as React renders them.
79
+ * Needs `@farm.js/islx/react/server` imported (large values and actions are encoded there).
80
+ */
81
+ declare function renderServerTree(element: React.ReactNode, options?: ServerTreeRenderOptions): Promise<React.ReactElement>;
82
+ /** Re-renders mounted server trees that render a hot-updated client module. */
83
+ declare function updateServerTrees(src: string, module: Record<string, unknown>): number;
84
+ interface ServerTreeElementOptions {
85
+ /** Imports a client module, as the hydration runtime's `load`. */
86
+ load(src: string): Promise<Record<string, unknown>>;
87
+ /**
88
+ * Renders an `<f-html>` part from its server HTML and hydrates its islands:
89
+ * `createServerHtml(runtime)`.
90
+ */
91
+ ServerHtml: React.ComponentType<any>;
92
+ /** Remounts the page's client components when it changes, as a navigation does. */
93
+ key?: string;
94
+ /** The value store. Default: the page's. */
95
+ values?: Pick<ValueStore, "ingest" | "resolve">;
96
+ }
97
+ /**
98
+ * Rebuilds a server-tree page in the browser from its server HTML (an element, or the HTML of a
99
+ * navigation), after loading its client modules. Returns null for a page without a tree.
100
+ */
101
+ 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 };
@@ -0,0 +1,19 @@
1
+ interface ValueStore {
2
+ /** Reads the value scripts under `root` that the store does not hold yet. */
3
+ ingest(root: ParentNode): void;
4
+ /**
5
+ * Replaces value references in parsed JSON with the values they name. Other tags stay as they
6
+ * are, for the codec to decode afterwards.
7
+ */
8
+ resolve(value: unknown): unknown;
9
+ /** The `F-Values` header value naming the keys the store holds, or undefined. */
10
+ heldHeader(): string | undefined;
11
+ /** Forgets every value, as after a full page load. */
12
+ clear(): void;
13
+ }
14
+ declare function createValueStore(): ValueStore;
15
+ /** The page's value store, shared by every copy of islx on the page. */
16
+ declare function getValueStore(): ValueStore;
17
+ /** Headers a navigation sends so the server can leave out values the browser holds. */
18
+ declare function valueHeaders(): Record<string, string>;
19
+ export { valueHeaders as i, createValueStore as n, getValueStore as r, ValueStore as t };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The names islx writes into HTML, JSON and headers, and reads back. Elements, script types and
3
+ * headers carry the `f` prefix; JSON uses one reserved key, `$`, whose value is a short tag. See
4
+ * SPEC.md for the whole format.
5
+ *
6
+ * <f-island src="/src/toc.tsx" export="Toc" props="_R_b_" strategy="load">…</f-island>
7
+ * <script type="f/props" for="_R_b_">{…props…}</script>
8
+ * <script type="f/value" name="04jiw29097x8ki">…a value sent once…</script>
9
+ * <f-slot name="_R_5_s0">…server output passed as children…</f-slot>
10
+ */
11
+ /** The element around a client component's server HTML. */
12
+ declare const ISLAND_ELEMENT = "f-island";
13
+ /** The script type of an island's props; its `for` attribute names the island. */
14
+ declare const PROPS_SCRIPT_TYPE = "f/props";
15
+ /** The script type of a value sent once; its `name` attribute is the value's key. */
16
+ declare const VALUE_SCRIPT_TYPE = "f/value";
17
+ /** Set on an island once it hydrated (and its held interaction was replayed). */
18
+ declare const HYDRATED_ATTRIBUTE = "hydrated";
19
+ /** The element that keeps server output passed to an island as children. */
20
+ declare const SLOT_ELEMENT = "f-slot";
21
+ /** The script type of a page's server tree, rebuilt in the browser without the page's HTML. */
22
+ declare const TREE_SCRIPT_TYPE = "f/tree";
23
+ /** The script type of a server tree chunk that streamed in after the tree. */
24
+ declare const CHUNK_SCRIPT_TYPE = "f/chunk";
25
+ /** The element around server HTML inside a server tree. */
26
+ declare const HTML_ELEMENT = "f-html";
27
+ /** The script type of React's form state after a `useActionState` form post, for hydration. */
28
+ declare const FORM_STATE_SCRIPT_TYPE = "f/form-state";
29
+ /** The reserved JSON key whose value is a tag. */
30
+ declare const TAG = "$";
31
+ /** The key of a tagged object's data. */
32
+ declare const TAG_VALUE = "v";
33
+ /** The key of an action's sealed captured values. */
34
+ declare const TAG_BOUND = "b";
35
+ /** `{"$":"r","v":key}`: a value sent once, by key. */
36
+ declare const REFERENCE_TAG = "r";
37
+ /** `{"$":"s","v":name}`: a slot. */
38
+ declare const SLOT_TAG = "s";
39
+ /** `{"$":"a","v":id}` or `{"$":"a","v":id,"b":sealed}`: a server action. */
40
+ declare const ACTION_TAG = "a";
41
+ /** `{"$":"o","v":{…}}`: app data that has its own `$` key. */
42
+ declare const OBJECT_TAG = "o";
43
+ /** `{"$":"F","v":n}`: a `FormData` argument, sent as fields of a multipart call. */
44
+ declare const FORM_DATA_TAG = "F";
45
+ /** `{"$":"K","v":n}`: a `Blob` or `File` argument, sent as a field of a multipart call. */
46
+ declare const BLOB_TAG = "K";
47
+ /** The multipart field of a call's JSON (`{"a":…,"b":…}`) when it carries form data or files. */
48
+ declare const CALL_FIELD = "$F_CALL";
49
+ /** The multipart field name of attachment `n`, or of field `key` of `FormData` attachment `n`. */
50
+ declare function attachmentField(index: number, key?: string): string;
51
+ /** Request header: the value keys the browser holds. */
52
+ declare const VALUES_HEADER = "F-Values";
53
+ /** Request header: the layouts on screen, so the response can leave them out. */
54
+ declare const LAYOUTS_HEADER = "F-Layouts";
55
+ /** Request header: `tree` asks for a page's server tree without its HTML. */
56
+ declare const RENDER_HEADER = "F-Render";
57
+ /** Request header: the server action a client calls. */
58
+ declare const ACTION_HEADER = "F-Action";
59
+ /** Response header: the action id is unknown here, as after a deploy. The page should reload. */
60
+ declare const ACTION_STALE_HEADER = "F-Action-Stale";
61
+ /**
62
+ * A form or button posting a server action without JavaScript. React names the field (a hidden
63
+ * input for a form, the button's own name for a button) and writes the data fields after it; the
64
+ * last name a submission carries is the action that runs, so a clicked button wins over its form.
65
+ *
66
+ * <input type="hidden" name="$F_REF_R_1_">
67
+ * <input type="hidden" name="$F_R_1_:A" value="…action id…">
68
+ * <input type="hidden" name="$F_R_1_:B" value="…sealed values…">
69
+ */
70
+ declare const FORM_REF_PREFIX = "$F_REF_";
71
+ /** A form field of the action posted under `prefix`: `A` its id, `B` its sealed values. */
72
+ declare function formFieldName(prefix: string, field: "A" | "B"): string;
73
+ /** Every form field the runtime owns starts with this, and is removed before an action runs. */
74
+ declare const FORM_FIELD_PREFIX = "$F_";
75
+ /**
76
+ * The action a form submission posts: the last `$F_REF_` field names it, so a clicked button
77
+ * wins over its form. Null for a form that posts no action.
78
+ */
79
+ declare function readPostedAction(form: FormData): {
80
+ id: string;
81
+ token?: string;
82
+ } | null;
83
+ /** Removes the runtime's fields, so the action sees only the form's own. */
84
+ declare function stripRuntimeFields(form: FormData): void;
85
+ /** React's field naming which `useActionState` hook a form posted from. */
86
+ declare const ACTION_KEY_FIELD = "$ACTION_KEY";
87
+ /** A `FormData` argument's field for its action's sealed values, in calls from JavaScript. */
88
+ declare const BOUND_FIELD = "$F_BOUND";
89
+ /** A value key: 14 base-36 characters. */
90
+ declare const VALUE_KEY_PATTERN: RegExp;
91
+ /** Value keys a request names at most: the most recently used ones. */
92
+ declare const MAX_HELD_VALUES = 128;
93
+ /**
94
+ * The tag of a tagged object (`{"$":tag}`, `{"$":tag,"v":data}`, or an action's
95
+ * `{"$":"a","v":id,"b":sealed}`), or undefined for anything else.
96
+ */
97
+ declare function getTag(value: unknown): string | undefined;
98
+ export { VALUE_SCRIPT_TYPE as A, SLOT_TAG as C, TREE_SCRIPT_TYPE as D, TAG_VALUE as E, stripRuntimeFields as F, formFieldName as M, getTag as N, VALUES_HEADER as O, readPostedAction as P, SLOT_ELEMENT as S, TAG_BOUND as T, MAX_HELD_VALUES as _, BLOB_TAG as a, REFERENCE_TAG as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, LAYOUTS_HEADER as g, ISLAND_ELEMENT as h, ACTION_TAG as i, attachmentField as j, VALUE_KEY_PATTERN as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, OBJECT_TAG as v, TAG as w, RENDER_HEADER as x, PROPS_SCRIPT_TYPE as y };
@@ -0,0 +1,121 @@
1
+ // src/protocol/wire.ts
2
+ /**
3
+ * The names islx writes into HTML, JSON and headers, and reads back. Elements, script types and
4
+ * headers carry the `f` prefix; JSON uses one reserved key, `$`, whose value is a short tag. See
5
+ * SPEC.md for the whole format.
6
+ *
7
+ * <f-island src="/src/toc.tsx" export="Toc" props="_R_b_" strategy="load">…</f-island>
8
+ * <script type="f/props" for="_R_b_">{…props…}<\/script>
9
+ * <script type="f/value" name="04jiw29097x8ki">…a value sent once…<\/script>
10
+ * <f-slot name="_R_5_s0">…server output passed as children…</f-slot>
11
+ */
12
+ /** The element around a client component's server HTML. */
13
+ const ISLAND_ELEMENT = "f-island";
14
+ /** The script type of an island's props; its `for` attribute names the island. */
15
+ const PROPS_SCRIPT_TYPE = "f/props";
16
+ /** The script type of a value sent once; its `name` attribute is the value's key. */
17
+ const VALUE_SCRIPT_TYPE = "f/value";
18
+ /** Set on an island once it hydrated (and its held interaction was replayed). */
19
+ const HYDRATED_ATTRIBUTE = "hydrated";
20
+ /** The element that keeps server output passed to an island as children. */
21
+ const SLOT_ELEMENT = "f-slot";
22
+ /** The script type of a page's server tree, rebuilt in the browser without the page's HTML. */
23
+ const TREE_SCRIPT_TYPE = "f/tree";
24
+ /** The script type of a server tree chunk that streamed in after the tree. */
25
+ const CHUNK_SCRIPT_TYPE = "f/chunk";
26
+ /** The element around server HTML inside a server tree. */
27
+ const HTML_ELEMENT = "f-html";
28
+ /** The script type of React's form state after a `useActionState` form post, for hydration. */
29
+ const FORM_STATE_SCRIPT_TYPE = "f/form-state";
30
+ /** The reserved JSON key whose value is a tag. */
31
+ const TAG = "$";
32
+ /** The key of a tagged object's data. */
33
+ const TAG_VALUE = "v";
34
+ /** The key of an action's sealed captured values. */
35
+ const TAG_BOUND = "b";
36
+ /** `{"$":"r","v":key}`: a value sent once, by key. */
37
+ const REFERENCE_TAG = "r";
38
+ /** `{"$":"s","v":name}`: a slot. */
39
+ const SLOT_TAG = "s";
40
+ /** `{"$":"a","v":id}` or `{"$":"a","v":id,"b":sealed}`: a server action. */
41
+ const ACTION_TAG = "a";
42
+ /** `{"$":"o","v":{…}}`: app data that has its own `$` key. */
43
+ const OBJECT_TAG = "o";
44
+ /** `{"$":"F","v":n}`: a `FormData` argument, sent as fields of a multipart call. */
45
+ const FORM_DATA_TAG = "F";
46
+ /** `{"$":"K","v":n}`: a `Blob` or `File` argument, sent as a field of a multipart call. */
47
+ const BLOB_TAG = "K";
48
+ /** The multipart field of a call's JSON (`{"a":…,"b":…}`) when it carries form data or files. */
49
+ const CALL_FIELD = "$F_CALL";
50
+ /** The multipart field name of attachment `n`, or of field `key` of `FormData` attachment `n`. */
51
+ function attachmentField(index, key) {
52
+ return key === void 0 ? `$F_${index}` : `$F_${index}_${key}`;
53
+ }
54
+ /** Request header: the value keys the browser holds. */
55
+ const VALUES_HEADER = "F-Values";
56
+ /** Request header: the layouts on screen, so the response can leave them out. */
57
+ const LAYOUTS_HEADER = "F-Layouts";
58
+ /** Request header: `tree` asks for a page's server tree without its HTML. */
59
+ const RENDER_HEADER = "F-Render";
60
+ /** Request header: the server action a client calls. */
61
+ const ACTION_HEADER = "F-Action";
62
+ /** Response header: the action id is unknown here, as after a deploy. The page should reload. */
63
+ const ACTION_STALE_HEADER = "F-Action-Stale";
64
+ /**
65
+ * A form or button posting a server action without JavaScript. React names the field (a hidden
66
+ * input for a form, the button's own name for a button) and writes the data fields after it; the
67
+ * last name a submission carries is the action that runs, so a clicked button wins over its form.
68
+ *
69
+ * <input type="hidden" name="$F_REF_R_1_">
70
+ * <input type="hidden" name="$F_R_1_:A" value="…action id…">
71
+ * <input type="hidden" name="$F_R_1_:B" value="…sealed values…">
72
+ */
73
+ const FORM_REF_PREFIX = "$F_REF_";
74
+ /** A form field of the action posted under `prefix`: `A` its id, `B` its sealed values. */
75
+ function formFieldName(prefix, field) {
76
+ return `$F_${prefix}:${field}`;
77
+ }
78
+ /** Every form field the runtime owns starts with this, and is removed before an action runs. */
79
+ const FORM_FIELD_PREFIX = "$F_";
80
+ /**
81
+ * The action a form submission posts: the last `$F_REF_` field names it, so a clicked button
82
+ * wins over its form. Null for a form that posts no action.
83
+ */
84
+ function readPostedAction(form) {
85
+ let prefix = null;
86
+ for (const key of form.keys()) if (key.startsWith("$F_REF_")) prefix = key.slice(7);
87
+ if (prefix === null) return null;
88
+ const id = form.get(formFieldName(prefix, "A"));
89
+ const token = form.get(formFieldName(prefix, "B"));
90
+ if (typeof id !== "string") return null;
91
+ return {
92
+ id,
93
+ token: typeof token === "string" ? token : void 0
94
+ };
95
+ }
96
+ /** Removes the runtime's fields, so the action sees only the form's own. */
97
+ function stripRuntimeFields(form) {
98
+ for (const key of new Set(form.keys())) if (key.startsWith("$F_") || key === "$ACTION_KEY") form.delete(key);
99
+ }
100
+ /** React's field naming which `useActionState` hook a form posted from. */
101
+ const ACTION_KEY_FIELD = "$ACTION_KEY";
102
+ /** A `FormData` argument's field for its action's sealed values, in calls from JavaScript. */
103
+ const BOUND_FIELD = "$F_BOUND";
104
+ /** A value key: 14 base-36 characters. */
105
+ const VALUE_KEY_PATTERN = /^[0-9a-z]{14}$/;
106
+ /** Value keys a request names at most: the most recently used ones. */
107
+ const MAX_HELD_VALUES = 128;
108
+ /**
109
+ * The tag of a tagged object (`{"$":tag}`, `{"$":tag,"v":data}`, or an action's
110
+ * `{"$":"a","v":id,"b":sealed}`), or undefined for anything else.
111
+ */
112
+ function getTag(value) {
113
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
114
+ const tag = value["$"];
115
+ if (typeof tag !== "string") return void 0;
116
+ const keys = Object.keys(value);
117
+ if (keys.length === 1) return tag;
118
+ if (keys.length === 2 && "v" in value) return tag;
119
+ if (keys.length === 3 && tag === "a" && "v" in value && "b" in value) return tag;
120
+ }
121
+ export { VALUE_SCRIPT_TYPE as A, SLOT_TAG as C, TREE_SCRIPT_TYPE as D, TAG_VALUE as E, stripRuntimeFields as F, formFieldName as M, getTag as N, VALUES_HEADER as O, readPostedAction as P, SLOT_ELEMENT as S, TAG_BOUND as T, MAX_HELD_VALUES as _, BLOB_TAG as a, REFERENCE_TAG as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, LAYOUTS_HEADER as g, ISLAND_ELEMENT as h, ACTION_TAG as i, attachmentField as j, VALUE_KEY_PATTERN as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, OBJECT_TAG as v, TAG as w, RENDER_HEADER as x, PROPS_SCRIPT_TYPE as y };
@@ -0,0 +1,58 @@
1
+ import { i as valueHeaders, n as createValueStore, r as getValueStore, t as ValueStore } from "./_chunks/value-store-Dm-7xJ6W.mjs";
2
+ import { a as hookEventName, i as defineClientHooks, n as ClientHookEvents, o as setClientHooks, r as ClientHooks, t as ClientHookEvent } from "./_chunks/hooks-De0gNpOn.mjs";
3
+
4
+ // src/client/actions.d.ts
5
+ type AnyFunction = (...args: any[]) => any;
6
+ /** How actions reach the framework's router. */
7
+ interface ActionNavigator {
8
+ /** Renders the current page again from the server, keeping client state and scroll. */
9
+ refresh?(): Promise<void> | void;
10
+ /** A client navigation to `href`, as the action's redirect. */
11
+ navigate?(href: string): Promise<void> | void;
12
+ }
13
+ /** Connects actions to the framework's router. */
14
+ declare function setActionNavigator(navigator: ActionNavigator): void;
15
+ /** Decodes an action in a result or props: `{"$":"a","v":id,"b"?:sealed}`. */
16
+ declare function decodeActionReference(object: Record<string, unknown>): AnyFunction | undefined;
17
+ /** The function that calls a server action; one per id and sealed values, so props keep identity. */
18
+ declare function createServerReference(id: string, token?: string, name?: string): AnyFunction;
19
+ /** Starts handling server-component forms on this page. Safe to call more than once. */
20
+ declare function enhanceForms(): void;
21
+ interface NavigationOptions {
22
+ /** Puts the fetched page in place of the current one. */
23
+ swap(next: Document, url: URL): void | Promise<void>;
24
+ /** Animates the change with `document.startViewTransition`, where the browser has it. */
25
+ viewTransition?: boolean;
26
+ }
27
+ interface NavigateOptions {
28
+ /** Replace the current history entry instead of adding one. */
29
+ replace?: boolean;
30
+ }
31
+ /** The `F-Render` value of a navigation: the server renders the whole page before sending it. */
32
+ declare const NAVIGATION_RENDER = "document";
33
+ /**
34
+ * Handles same-site link clicks and history from now on. Returns `navigate` for navigations the
35
+ * app starts itself. Safe to call more than once.
36
+ */
37
+ declare function startNavigation(options: NavigationOptions): {
38
+ navigate(href: string, options?: NavigateOptions): Promise<void>;
39
+ };
40
+ /** Navigates within the site, through `startNavigation` when it runs, or by loading the page. */
41
+ declare function navigate(href: string, options?: NavigateOptions): Promise<void>;
42
+ /**
43
+ * Holding interactions that happen before the islx script has run. An inline script in the page
44
+ * captures a click or a submit on an island that has not hydrated (it is prevented and queued),
45
+ * and announces a pointerdown or a focus (which go through). The runtime then hydrates those
46
+ * islands first and replays what was held.
47
+ *
48
+ * The script is inlined into every page, so it stays hand-minified and imports nothing.
49
+ */
50
+ /** Where the inline script queues held interactions: `{ target, kind }`. */
51
+ declare const PENDING_INTERACTIONS = "__islxPending";
52
+ /** The DOM event the script dispatches for each interaction it sees. */
53
+ declare const INTERACTION_EVENT = "f:island-interaction";
54
+ /** Elements whose click is held and replayed rather than merely observed. */
55
+ declare const REPLAYABLE_SELECTOR = "button,[role=\"button\"],input[type=\"button\"],input[type=\"submit\"],input[type=\"reset\"]";
56
+ /** The inline script's source (no `<script>` tag): pass it to the page's head or bootstrap. */
57
+ declare const PREHYDRATION_SCRIPT: string;
58
+ export { type ActionNavigator, type ClientHookEvent, type ClientHookEvents, type ClientHooks, INTERACTION_EVENT, NAVIGATION_RENDER, type NavigateOptions, type NavigationOptions, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, type ValueStore, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
@@ -0,0 +1,197 @@
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";
2
+ import { F as stripRuntimeFields, P as readPostedAction, x as RENDER_HEADER } from "./_chunks/wire-CXOodE06.mjs";
3
+ import { i as REPLAYABLE_SELECTOR, n as PENDING_INTERACTIONS, r as PREHYDRATION_SCRIPT, t as INTERACTION_EVENT } from "./_chunks/prehydration-B1GLTLdk.mjs";
4
+ // src/client/forms.ts
5
+ /**
6
+ * Forms written in server components submit without reloading the page once JavaScript runs.
7
+ * No island owns them, so nothing else would: a submit of a form that posts an action is sent as
8
+ * a call, and the page refreshes as after any action, keeping island state. Forms inside islands
9
+ * are React's, which handles them first.
10
+ */
11
+ let enhanced = false;
12
+ /** Starts handling server-component forms on this page. Safe to call more than once. */
13
+ function enhanceForms() {
14
+ if (enhanced) return;
15
+ enhanced = true;
16
+ document.addEventListener("submit", (event) => {
17
+ if (event.defaultPrevented || !(event.target instanceof HTMLFormElement)) return;
18
+ const form = event.target;
19
+ const submitter = event.submitter ?? null;
20
+ const data = new FormData(form, submitter);
21
+ const posted = readPostedAction(data);
22
+ if (!posted) return;
23
+ event.preventDefault();
24
+ stripRuntimeFields(data);
25
+ form.setAttribute("aria-busy", "true");
26
+ createServerReference(posted.id, posted.token)(data).then(() => form.reset()).catch((error) => console.error("[islx] The form's action failed:", error)).finally(() => form.removeAttribute("aria-busy"));
27
+ });
28
+ }
29
+ // src/client/navigation.ts
30
+ /**
31
+ * Moving between pages without loading a new document. A click on a same-site link fetches the
32
+ * next page (naming the values the browser holds, so the server leaves them out), and the
33
+ * renderer swaps it in: the runtime, its loaded code and its values stay.
34
+ *
35
+ * Renderer-neutral: the adapter passes `swap`. Anything unusual loads the page the ordinary way:
36
+ * a link to another site, `target`, `download`, `rel="external"`, a modifier key, a non-HTML or
37
+ * failed response, or a page built by another deploy.
38
+ */
39
+ /** The `F-Render` value of a navigation: the server renders the whole page before sending it. */
40
+ const NAVIGATION_RENDER = "document";
41
+ let started = null;
42
+ /** Set by the click handler for the navigation it starts, so hooks see `kind: "link"`. */
43
+ let clicking = false;
44
+ /** The URL of the page's browser entry: a page from another deploy names another one. */
45
+ function entryOf(document) {
46
+ return document.querySelector("script[type=\"module\"][src][async]")?.getAttribute("src") ?? null;
47
+ }
48
+ function isOurs(state) {
49
+ return !!state && typeof state === "object" && state.islx === true;
50
+ }
51
+ function rememberScroll() {
52
+ const state = {
53
+ islx: true,
54
+ scroll: [window.scrollX, window.scrollY]
55
+ };
56
+ history.replaceState({
57
+ ...history.state,
58
+ ...state
59
+ }, "");
60
+ }
61
+ let announcer = null;
62
+ /** Tells screen readers the page changed, as a document load would. */
63
+ function announce(title) {
64
+ if (!announcer || !announcer.isConnected) {
65
+ announcer = document.createElement("div");
66
+ announcer.setAttribute("aria-live", "assertive");
67
+ announcer.setAttribute("role", "status");
68
+ announcer.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0";
69
+ document.body.append(announcer);
70
+ }
71
+ announcer.textContent = title;
72
+ }
73
+ /** Adds stylesheets the next page uses that this one has not loaded. */
74
+ function mergeStyles(next) {
75
+ const loaded = new Set(Array.from(document.head.querySelectorAll("link[rel=\"stylesheet\"]"), (link) => link.href));
76
+ for (const link of next.head.querySelectorAll("link[rel=\"stylesheet\"]")) {
77
+ const href = new URL(link.getAttribute("href") ?? "", next.baseURI || location.href).href;
78
+ if (!loaded.has(href)) document.head.append(document.importNode(link, true));
79
+ }
80
+ }
81
+ /**
82
+ * Handles same-site link clicks and history from now on. Returns `navigate` for navigations the
83
+ * app starts itself. Safe to call more than once.
84
+ */
85
+ function startNavigation(options) {
86
+ if (started) return started;
87
+ let current = null;
88
+ /** The page on screen: where a cancelled back or forward returns to. */
89
+ let here = new URL(location.href);
90
+ history.scrollRestoration = "manual";
91
+ rememberScroll();
92
+ async function go(url, push, scroll) {
93
+ const kind = push === "none" ? "history" : push === "push" && scroll === null && clicking ? "link" : "code";
94
+ clicking = false;
95
+ if (!emit("navigate", {
96
+ url,
97
+ from: here,
98
+ kind
99
+ })) {
100
+ if (push === "none") history.pushState({
101
+ islx: true,
102
+ scroll: [window.scrollX, window.scrollY]
103
+ }, "", here);
104
+ return;
105
+ }
106
+ const startedAt = performance.now();
107
+ current?.abort();
108
+ const controller = new AbortController();
109
+ current = controller;
110
+ let next;
111
+ let finalUrl;
112
+ try {
113
+ const response = await fetch(url, {
114
+ headers: {
115
+ accept: "text/html",
116
+ [RENDER_HEADER]: NAVIGATION_RENDER,
117
+ ...valueHeaders()
118
+ },
119
+ credentials: "same-origin",
120
+ signal: controller.signal
121
+ });
122
+ if (!response.headers.get("content-type")?.includes("text/html")) throw new Error("not a page");
123
+ finalUrl = new URL(response.url || url.href);
124
+ if (finalUrl.origin !== location.origin) throw new Error("redirected to another site");
125
+ finalUrl.hash = url.hash;
126
+ next = new DOMParser().parseFromString(await response.text(), "text/html");
127
+ if (entryOf(next) !== entryOf(document)) throw new Error("built by another deploy");
128
+ } catch (error) {
129
+ if (controller.signal.aborted) return;
130
+ emit("navigateError", {
131
+ url,
132
+ reason: error instanceof Error ? error.message : String(error)
133
+ });
134
+ if (push === "none") location.reload();
135
+ else location.assign(url.href);
136
+ return;
137
+ }
138
+ if (controller.signal.aborted) return;
139
+ if (push === "push") history.pushState({
140
+ islx: true,
141
+ scroll: [0, 0]
142
+ }, "", finalUrl);
143
+ else if (push === "replace") history.replaceState({
144
+ islx: true,
145
+ scroll: [0, 0]
146
+ }, "", finalUrl);
147
+ mergeStyles(next);
148
+ document.title = next.title;
149
+ document.activeElement?.blur?.();
150
+ const swap = () => options.swap(next, finalUrl);
151
+ const transition = document.startViewTransition;
152
+ if (options.viewTransition && typeof transition === "function") await transition.call(document, swap).updateCallbackDone;
153
+ else await swap();
154
+ here = new URL(location.href);
155
+ if (scroll) window.scrollTo(scroll[0], scroll[1]);
156
+ else if (finalUrl.hash) document.getElementById(decodeURIComponent(finalUrl.hash.slice(1)))?.scrollIntoView();
157
+ else window.scrollTo(0, 0);
158
+ announce(next.title || finalUrl.pathname);
159
+ if (current === controller) current = null;
160
+ emit("navigated", {
161
+ url: finalUrl,
162
+ duration: performance.now() - startedAt
163
+ });
164
+ }
165
+ document.addEventListener("click", (event) => {
166
+ if (event.defaultPrevented || event.button !== 0) return;
167
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
168
+ const link = event.target?.closest?.("a[href]");
169
+ if (!(link instanceof HTMLAnchorElement)) return;
170
+ if (link.target && link.target !== "_self") return;
171
+ if (link.hasAttribute("download") || link.relList.contains("external")) return;
172
+ const url = new URL(link.href);
173
+ if (url.origin !== location.origin || !/^https?:$/.test(url.protocol)) return;
174
+ if (url.pathname === location.pathname && url.search === location.search && url.hash) return;
175
+ event.preventDefault();
176
+ rememberScroll();
177
+ clicking = true;
178
+ go(url, "push", null);
179
+ });
180
+ window.addEventListener("popstate", (event) => {
181
+ if (!isOurs(event.state)) return;
182
+ go(new URL(location.href), "none", event.state.scroll);
183
+ });
184
+ started = { navigate(href, navigateOptions = {}) {
185
+ rememberScroll();
186
+ return go(new URL(href, location.href), navigateOptions.replace ? "replace" : "push", null);
187
+ } };
188
+ return started;
189
+ }
190
+ /** Navigates within the site, through `startNavigation` when it runs, or by loading the page. */
191
+ function navigate(href, options = {}) {
192
+ if (started) return started.navigate(href, options);
193
+ if (options.replace) location.replace(href);
194
+ else location.assign(href);
195
+ return Promise.resolve();
196
+ }
197
+ export { INTERACTION_EVENT, NAVIGATION_RENDER, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
@@ -0,0 +1,2 @@
1
+ import { a as getActionId, i as TransformActionsResult, n as AstNode, o as hasModuleDirective, r as TransformActionsOptions, s as transformActions, t as ActionCompileError } from "./_chunks/actions-BCtIGvrP.mjs";
2
+ export { ActionCompileError, type AstNode, type TransformActionsOptions, type TransformActionsResult, getActionId, hasModuleDirective, transformActions };
@@ -0,0 +1 @@
1
+ export { t as ActionCompileError, n as getActionId, r as hasModuleDirective, i as transformActions } from "./_chunks/actions-Eutk6UzT.mjs";
@@ -0,0 +1,3 @@
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-C3Rpbwl6.mjs";
2
+ import { a as decode, c as encodeLeaf, d as plainObject, f as tagged, i as NOT_A_VALUE, l as fromBase64, n as EncodeError, o as decodeTagged, p as toBase64, r as EncodeOptions, s as encode, t as DecodeOptions, u as needsObjectTag } from "./_chunks/codec-CK8kZp4u.mjs";
3
+ export { ACTION_HEADER, ACTION_KEY_FIELD, ACTION_STALE_HEADER, ACTION_TAG, BLOB_TAG, BOUND_FIELD, CALL_FIELD, CHUNK_SCRIPT_TYPE, type DecodeOptions, EncodeError, type EncodeOptions, 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 };
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
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";
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 };