@farm.js/islx 0.0.5 → 0.0.7
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 +32 -0
- package/README.md +4 -4
- package/SPEC.md +7 -4
- package/dist/_chunks/{actions-8RQfrM-p.mjs → actions-C-IwUCKU.mjs} +27 -2
- package/dist/_chunks/{actions-BuS5jDi0.mjs → actions-CvQ0-zjg.mjs} +15 -14
- package/dist/_chunks/{tree-BUkGP4AR.mjs → tree-C47p6khN.mjs} +1 -1
- package/dist/_chunks/{vite-DG7hBn8A.mjs → vite-BTzYlEiY.mjs} +4 -1
- package/dist/client.d.mts +17 -1
- package/dist/client.mjs +2 -2
- package/dist/react/client.mjs +2 -2
- package/dist/react/server.mjs +8 -4
- package/dist/react.mjs +1 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.mjs +1 -1
- package/dist/tanstack-start.mjs +13 -1
- package/dist/vite.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.7
|
|
4
|
+
|
|
5
|
+
Cloudflare Workers start, and sealed captured values name their key with 4 bytes.
|
|
6
|
+
|
|
7
|
+
- `@farm.js/islx/react/server` no longer generates random values while it loads. It made the
|
|
8
|
+
promise-name prefix with `crypto.getRandomValues` at module scope, which Workers reject
|
|
9
|
+
("Disallowed operation called within global scope"), so no islx app started on Cloudflare
|
|
10
|
+
since promise props (0.0.4). The prefix is made at the first render now.
|
|
11
|
+
- An inline `"use server"` function's captured values are sealed with format version 2: the key id
|
|
12
|
+
is the first 4 bytes of the key's SHA-256 instead of the first byte. With one byte, a deploy that
|
|
13
|
+
changed keys without keeping the old one gave the new key the same id one time in 256, and
|
|
14
|
+
values from pages rendered before it failed as invalid (`400`) instead of stale (the page
|
|
15
|
+
reloads). An app that does not set `ISLX_ACTIONS_KEY` gets a new key on every build, so this hit
|
|
16
|
+
about one deploy in 256.
|
|
17
|
+
- Values sealed by 0.0.6 or earlier (format version 1) are stale: forms on pages open across the
|
|
18
|
+
upgrade reload once.
|
|
19
|
+
|
|
20
|
+
## 0.0.6
|
|
21
|
+
|
|
22
|
+
Server action interceptor, early clicks on TanStack Start, library islands under pnpm.
|
|
23
|
+
|
|
24
|
+
- `setActionInterceptor((call, next) => …)` from `@farm.js/islx/client` wraps every server
|
|
25
|
+
action call, references decoded from props included. It sees the call when it is made
|
|
26
|
+
(`{ id, name, args }`) and `next` sends it in queue order, so a framework can unwrap what its
|
|
27
|
+
server side adds to a result (cache invalidations, a query's cache entry, a declared failure).
|
|
28
|
+
- TanStack Start: a click on a button, or a form submit, in an island before it hydrates is held
|
|
29
|
+
and replayed, as in a plain islx app (the pre-hydration script goes in the root route's head).
|
|
30
|
+
- A library's `"use client"` components (islands the Vite plugin makes in a bundled server build)
|
|
31
|
+
import islx's island runtime from the app instead of from the library's folder. Under pnpm the
|
|
32
|
+
library's folder in the store cannot see `@farm.js/islx`, so the server build failed to resolve
|
|
33
|
+
`@farm.js/islx/react`; now it gets the same copy the app's pages use.
|
|
34
|
+
|
|
3
35
|
## 0.0.5
|
|
4
36
|
|
|
5
37
|
TanStack Start.
|
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.
|
|
16
|
+
> Status: 0.0.7. React, Vite and TanStack Start 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
|
|
|
@@ -186,7 +186,9 @@ export default defineConfig({ plugins: [tanstackStart(), react(), islxTanstackSt
|
|
|
186
186
|
Routes, loaders and layouts render on the server. The browser loads islx and the `"use client"`
|
|
187
187
|
components a page uses (the modules under `src/`), not the router, the route tree or loader
|
|
188
188
|
data: Start's client entry becomes islx's, `<Scripts />` still loads it, and the router is not
|
|
189
|
-
dehydrated.
|
|
189
|
+
dehydrated. A click on a button (or a form submit) in an island before it hydrates is held and
|
|
190
|
+
replayed. Links move between
|
|
191
|
+
routes without loading a document. A loader's promise passed
|
|
190
192
|
unawaited to a client component streams in. Islands call Start server functions
|
|
191
193
|
(`createServerFn`) and islx server actions (`"use server"`).
|
|
192
194
|
|
|
@@ -197,8 +199,6 @@ unawaited to a client component streams in. Islands call Start server functions
|
|
|
197
199
|
- Client components run without a router in the browser: they cannot use `useRouter`,
|
|
198
200
|
`useNavigate` or `<Link>` (render plain `<a>` links; the server renders `<Link>` in routes).
|
|
199
201
|
- A library's `"use client"` components render as server HTML.
|
|
200
|
-
- A click before the islands hydrate is not replayed yet (the `interaction` strategy hydrates on
|
|
201
|
-
it, but the click itself is dropped).
|
|
202
202
|
- Start sends a bot the whole page before any of it; people get it streamed.
|
|
203
203
|
- Start needs Node 22.12 or later.
|
|
204
204
|
|
package/SPEC.md
CHANGED
|
@@ -198,14 +198,17 @@ travel with the action, sealed, so the browser can neither read nor change them:
|
|
|
198
198
|
sealed = base64url( version ‖ key id ‖ IV ‖ AES-256-GCM(ciphertext ‖ tag) )
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
- `version` is `
|
|
201
|
+
- `version` is `2` (one byte); `key id` is the first 4 bytes of SHA-256 of the key, big-endian;
|
|
202
|
+
the IV is 12 random bytes;
|
|
202
203
|
- the additional data is `islx:action:<id>`, so values sealed for one action never open for
|
|
203
204
|
another;
|
|
204
205
|
- the plaintext is the captured values as a tagged JSON array.
|
|
205
206
|
|
|
206
|
-
A value sealed with a key the server does not have is **stale** (the
|
|
207
|
-
does not open is **invalid** (`400`). Servers may hold previous keys,
|
|
208
|
-
key rotation keep working. Every server instance must share the keys.
|
|
207
|
+
A value sealed with a key the server does not have, or in another `version`, is **stale** (the
|
|
208
|
+
page reloads); anything that does not open is **invalid** (`400`). Servers may hold previous keys,
|
|
209
|
+
so pages rendered before a key rotation keep working. Every server instance must share the keys.
|
|
210
|
+
Version 1 named the key with one byte, so one key rotation in 256 made values from open pages
|
|
211
|
+
invalid instead of stale; its values are stale now.
|
|
209
212
|
|
|
210
213
|
## Integration model
|
|
211
214
|
|
|
@@ -151,6 +151,10 @@ function state() {
|
|
|
151
151
|
function setActionNavigator(navigator) {
|
|
152
152
|
state().navigator = navigator;
|
|
153
153
|
}
|
|
154
|
+
/** Installs the framework's interceptor for server action calls (one; a later call replaces it). */
|
|
155
|
+
function setActionInterceptor(interceptor) {
|
|
156
|
+
state().interceptor = interceptor;
|
|
157
|
+
}
|
|
154
158
|
/** Encodes an action passed back to the server as an argument. */
|
|
155
159
|
function encodeFunction(value) {
|
|
156
160
|
const reference = state().info.get(value);
|
|
@@ -252,7 +256,28 @@ function createReference(reference) {
|
|
|
252
256
|
reference.redirect = void 0;
|
|
253
257
|
return call(reference, args);
|
|
254
258
|
};
|
|
255
|
-
const
|
|
259
|
+
const previous = client.queue;
|
|
260
|
+
const queued = () => previous.then(run);
|
|
261
|
+
const interceptor = client.interceptor;
|
|
262
|
+
let sent;
|
|
263
|
+
if (!interceptor) sent = queued();
|
|
264
|
+
else {
|
|
265
|
+
let stale = false;
|
|
266
|
+
const next = () => queued().then((value) => {
|
|
267
|
+
if (value === STALE) stale = true;
|
|
268
|
+
return value;
|
|
269
|
+
});
|
|
270
|
+
try {
|
|
271
|
+
sent = Promise.resolve(interceptor({
|
|
272
|
+
id,
|
|
273
|
+
name,
|
|
274
|
+
args: [...reference.bound, ...args]
|
|
275
|
+
}, next)).then((value) => stale ? STALE : value);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
sent = Promise.reject(error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const result = sent.then((value) => {
|
|
256
281
|
if (value !== STALE) {
|
|
257
282
|
const { redirect } = reference;
|
|
258
283
|
emit("actionEnd", {
|
|
@@ -303,4 +328,4 @@ function createServerReference(id, token, name) {
|
|
|
303
328
|
}
|
|
304
329
|
return reference;
|
|
305
330
|
}
|
|
306
|
-
export {
|
|
331
|
+
export { defineClientHooks as a, setClientHooks as c, valueHeaders as d, setActionNavigator as i, createValueStore as l, decodeActionReference as n, emit as o, setActionInterceptor as r, hookEventName as s, createServerReference as t, getValueStore as u };
|
|
@@ -24,10 +24,11 @@ import { a as encode, r as decode, t as EncodeError } from "./codec-C8to-RQW.mjs
|
|
|
24
24
|
* Sealing is asynchronous. React's server renderer waits for it: a form whose action's values
|
|
25
25
|
* are still sealing suspends, and renders once they are ready.
|
|
26
26
|
*
|
|
27
|
-
* sealed = base64url( version (1 byte) ‖ key id (
|
|
27
|
+
* sealed = base64url( version (1 byte) ‖ key id (4 bytes) ‖ IV (12 bytes) ‖ ciphertext+tag )
|
|
28
28
|
*/
|
|
29
|
-
const VERSION =
|
|
29
|
+
const VERSION = 2;
|
|
30
30
|
const IV_BYTES = 12;
|
|
31
|
+
const HEADER_BYTES = 5;
|
|
31
32
|
const KEY_BYTES = 32;
|
|
32
33
|
/** Thrown when a sealed value does not open: changed, made for another action, or stale. */
|
|
33
34
|
var SealError = class extends Error {
|
|
@@ -58,7 +59,7 @@ function fromBase64url(text) {
|
|
|
58
59
|
return null;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
|
-
/** A new random key, as base64url: what `
|
|
62
|
+
/** A new random key, as base64url: what `ISLX_ACTIONS_KEY` holds. */
|
|
62
63
|
function generateActionKey() {
|
|
63
64
|
return base64url(crypto.getRandomValues(new Uint8Array(KEY_BYTES)));
|
|
64
65
|
}
|
|
@@ -67,10 +68,10 @@ async function importKey(secret) {
|
|
|
67
68
|
if (!bytes || bytes.length !== KEY_BYTES) throw new TypeError("an actions key must be 32 random bytes as base64url; create one with generateActionKey() from @farm.js/islx/server");
|
|
68
69
|
return crypto.subtle.importKey("raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
69
70
|
}
|
|
70
|
-
/** The key's id:
|
|
71
|
+
/** The key's id: the first 4 bytes of its SHA-256, so a value names the key that sealed it. */
|
|
71
72
|
async function keyId(secret) {
|
|
72
73
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(secret.trim()));
|
|
73
|
-
return new
|
|
74
|
+
return new DataView(digest).getUint32(0);
|
|
74
75
|
}
|
|
75
76
|
/**
|
|
76
77
|
* The keys values are sealed and opened with. `current` seals; `previous` keys still open
|
|
@@ -128,11 +129,11 @@ async function sealBound(keys, actionId, values, options = {}) {
|
|
|
128
129
|
iv,
|
|
129
130
|
additionalData: additionalData(actionId)
|
|
130
131
|
}, keys.current, new TextEncoder().encode(json)));
|
|
131
|
-
const sealed = new Uint8Array(
|
|
132
|
+
const sealed = new Uint8Array(17 + ciphertext.length);
|
|
132
133
|
sealed[0] = VERSION;
|
|
133
|
-
sealed
|
|
134
|
-
sealed.set(iv,
|
|
135
|
-
sealed.set(ciphertext,
|
|
134
|
+
new DataView(sealed.buffer).setUint32(1, keys.currentId);
|
|
135
|
+
sealed.set(iv, HEADER_BYTES);
|
|
136
|
+
sealed.set(ciphertext, 17);
|
|
136
137
|
return base64url(sealed);
|
|
137
138
|
}
|
|
138
139
|
/**
|
|
@@ -141,17 +142,17 @@ async function sealBound(keys, actionId, values, options = {}) {
|
|
|
141
142
|
*/
|
|
142
143
|
async function unsealBound(keys, actionId, sealed, options = {}) {
|
|
143
144
|
const bytes = fromBase64url(sealed);
|
|
144
|
-
if (
|
|
145
|
-
if (bytes
|
|
146
|
-
const candidates = keys.byId.get(bytes
|
|
145
|
+
if (bytes && bytes.length > 0 && bytes[0] !== VERSION) throw new SealError("stale", "sealed action values use another format version");
|
|
146
|
+
if (!bytes || bytes.length < 33) throw new SealError("invalid", "sealed action values are malformed");
|
|
147
|
+
const candidates = keys.byId.get(new DataView(bytes.buffer).getUint32(1));
|
|
147
148
|
if (!candidates) throw new SealError("stale", "sealed action values use a key this server does not have");
|
|
148
149
|
let plaintext;
|
|
149
150
|
for (const key of candidates) try {
|
|
150
151
|
plaintext = await crypto.subtle.decrypt({
|
|
151
152
|
name: "AES-GCM",
|
|
152
|
-
iv: bytes.subarray(
|
|
153
|
+
iv: bytes.subarray(HEADER_BYTES, 17),
|
|
153
154
|
additionalData: additionalData(actionId)
|
|
154
|
-
}, key, bytes.subarray(
|
|
155
|
+
}, key, bytes.subarray(17));
|
|
155
156
|
break;
|
|
156
157
|
} catch {}
|
|
157
158
|
if (!plaintext) throw new SealError("invalid", "sealed action values do not open for this action");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-C8to-RQW.mjs";
|
|
2
2
|
import { M as VALUE_SCRIPT_TYPE, c as CHUNK_SCRIPT_TYPE, k as TREE_SCRIPT_TYPE, p as HTML_ELEMENT } from "./wire-QEQWBr5s.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { t as createServerReference, u as getValueStore } from "./actions-C-IwUCKU.mjs";
|
|
4
4
|
import { a as getIslandServerHook, o as isolateIslands, r as getClientReference } from "./island-DVOr-OUw.mjs";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
// src/react/tree.ts
|
|
@@ -177,6 +177,7 @@ function transformClientModule(options) {
|
|
|
177
177
|
const RUNTIME_ID = "virtual:islx/runtime";
|
|
178
178
|
/** @internal The browser entry, for a framework whose own entry imports it. */
|
|
179
179
|
const ENTRY_ID = "virtual:islx/client-entry";
|
|
180
|
+
const LIBRARY_RUNTIME_ID = "virtual:islx/react-for-libraries";
|
|
180
181
|
const CLIENT_HOOKS_ID = "virtual:islx/hooks-client";
|
|
181
182
|
const SERVER_HOOKS_ID = "virtual:islx/hooks-server";
|
|
182
183
|
const BROWSER_GLOBALS = /* @__PURE__ */ new Set([
|
|
@@ -366,6 +367,7 @@ function islx(options = {}, host = {}) {
|
|
|
366
367
|
preamble = resolved.plugins.some((plugin) => plugin.name === "vite:react-virtual-preamble");
|
|
367
368
|
},
|
|
368
369
|
resolveId(id) {
|
|
370
|
+
if (id === LIBRARY_RUNTIME_ID) return this.resolve("@farm.js/islx/react", path.join(config.root, "package.json"), { skipSelf: true });
|
|
369
371
|
if (id === RUNTIME_ID || id === "virtual:islx/client-entry" || id === CLIENT_HOOKS_ID || id === SERVER_HOOKS_ID) return `\0${id}`;
|
|
370
372
|
},
|
|
371
373
|
async load(id) {
|
|
@@ -425,7 +427,8 @@ function islx(options = {}, host = {}) {
|
|
|
425
427
|
return JSON.stringify(relative);
|
|
426
428
|
},
|
|
427
429
|
sideEffects: [RUNTIME_ID],
|
|
428
|
-
directive: clientDirective
|
|
430
|
+
directive: clientDirective,
|
|
431
|
+
...library ? { runtime: LIBRARY_RUNTIME_ID } : {}
|
|
429
432
|
});
|
|
430
433
|
if (island) {
|
|
431
434
|
if (!library) transformActions({
|
package/dist/client.d.mts
CHANGED
|
@@ -10,8 +10,24 @@ interface ActionNavigator {
|
|
|
10
10
|
/** A client navigation to `href`, as the action's redirect. */
|
|
11
11
|
navigate?(href: string): Promise<void> | void;
|
|
12
12
|
}
|
|
13
|
+
/** A call of a server action, as an interceptor sees it. */
|
|
14
|
+
interface ActionCall {
|
|
15
|
+
id: string;
|
|
16
|
+
/** The export (or inline function) name, when known. */
|
|
17
|
+
name?: string;
|
|
18
|
+
/** The arguments, after any bound with `.bind`. */
|
|
19
|
+
args: readonly unknown[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wraps every server action call: call `next` to make the request, and return what the caller
|
|
23
|
+
* gets. A framework uses it to unwrap what its own server side adds to a result (cache
|
|
24
|
+
* invalidations, a query's cache entry) or to turn a returned error back into one it throws.
|
|
25
|
+
*/
|
|
26
|
+
type ActionInterceptor = (call: ActionCall, next: () => Promise<unknown>) => Promise<unknown>;
|
|
13
27
|
/** Connects actions to the framework's router. */
|
|
14
28
|
declare function setActionNavigator(navigator: ActionNavigator): void;
|
|
29
|
+
/** Installs the framework's interceptor for server action calls (one; a later call replaces it). */
|
|
30
|
+
declare function setActionInterceptor(interceptor: ActionInterceptor | undefined): void;
|
|
15
31
|
/** Decodes an action in a result or props: `{"$":"a","v":id,"b"?:sealed}`. */
|
|
16
32
|
declare function decodeActionReference(object: Record<string, unknown>): AnyFunction | undefined;
|
|
17
33
|
/** The function that calls a server action; one per id and sealed values, so props keep identity. */
|
|
@@ -55,4 +71,4 @@ declare const INTERACTION_EVENT = "f:island-interaction";
|
|
|
55
71
|
declare const REPLAYABLE_SELECTOR = "button,[role=\"button\"],input[type=\"button\"],input[type=\"submit\"],input[type=\"reset\"]";
|
|
56
72
|
/** The inline script's source (no `<script>` tag): pass it to the page's head or bootstrap. */
|
|
57
73
|
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 };
|
|
74
|
+
export { type ActionCall, type ActionInterceptor, 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, setActionInterceptor, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as defineClientHooks, c as setClientHooks, d as valueHeaders, i as setActionNavigator, l as createValueStore, n as decodeActionReference, o as emit, r as setActionInterceptor, s as hookEventName, t as createServerReference, u as getValueStore } from "./_chunks/actions-C-IwUCKU.mjs";
|
|
2
2
|
import { C as RENDER_HEADER, I as readPostedAction, L as stripRuntimeFields } from "./_chunks/wire-QEQWBr5s.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
|
|
@@ -194,4 +194,4 @@ function navigate(href, options = {}) {
|
|
|
194
194
|
else location.assign(href);
|
|
195
195
|
return Promise.resolve();
|
|
196
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 };
|
|
197
|
+
export { INTERACTION_EVENT, NAVIGATION_RENDER, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionInterceptor, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
package/dist/react/client.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as valueHeaders, o as emit, t as createServerReference, u as getValueStore } from "../_chunks/actions-C-IwUCKU.mjs";
|
|
2
2
|
import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-C8to-RQW.mjs";
|
|
3
3
|
import { F as getTag, f as FORM_STATE_SCRIPT_TYPE, h as ISLAND_ELEMENT, m as HYDRATED_ATTRIBUTE, w as SLOT_ELEMENT, x as PROPS_SCRIPT_TYPE, y as PROMISE_SCRIPT_TYPE } from "../_chunks/wire-QEQWBr5s.mjs";
|
|
4
4
|
import { i as getContexts, o as isolateIslands, u as withinClientRoot } from "../_chunks/island-DVOr-OUw.mjs";
|
|
5
|
-
import { i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement } from "../_chunks/tree-
|
|
5
|
+
import { i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement } from "../_chunks/tree-C47p6khN.mjs";
|
|
6
6
|
import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-CjoybS3b.mjs";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { hydrateRoot } from "react-dom/client";
|
package/dist/react/server.mjs
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { a as encode } from "../_chunks/codec-C8to-RQW.mjs";
|
|
2
2
|
import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-QEQWBr5s.mjs";
|
|
3
3
|
import { r as PREHYDRATION_SCRIPT } from "../_chunks/prehydration-B1GLTLdk.mjs";
|
|
4
|
-
import { i as encodeActionReference } from "../_chunks/actions-
|
|
4
|
+
import { i as encodeActionReference } from "../_chunks/actions-CvQ0-zjg.mjs";
|
|
5
5
|
import { t as getBuildDefaults } from "../_chunks/build-COIcs7wm.mjs";
|
|
6
6
|
import { a as parseHeldValues, n as addressValues, o as selectValuesToSend, r as escapeScriptJson } from "../_chunks/values-37UIzxM0.mjs";
|
|
7
7
|
import { c as setIslandServerHook, i as getContexts, l as setRenderStateResolver, n as createIsland, o as isolateIslands, r as getClientReference, s as runtimeTagged, t as IslandsProvider, u as withinClientRoot } from "../_chunks/island-DVOr-OUw.mjs";
|
|
8
|
-
import { a as renderServerTree, n as PAYLOAD_ONLY_ATTRIBUTE, t as Outlet } from "../_chunks/tree-
|
|
8
|
+
import { a as renderServerTree, n as PAYLOAD_ONLY_ATTRIBUTE, t as Outlet } from "../_chunks/tree-C47p6khN.mjs";
|
|
9
9
|
import * as React from "react";
|
|
10
10
|
// src/react/server.ts
|
|
11
11
|
/**
|
|
12
12
|
* @farm.js/islx/react/server: rendering a React page with islands. Importing it gives islands the
|
|
13
13
|
* server code they need (values, action references); `renderToStream` renders a page.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
let promisePrefix;
|
|
16
16
|
let promiseCount = 0;
|
|
17
|
+
function nextPromiseName() {
|
|
18
|
+
promisePrefix ??= `${Array.from(crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(2)), (part) => part.toString(36)).join("")}.`;
|
|
19
|
+
return promisePrefix + (promiseCount++).toString(36);
|
|
20
|
+
}
|
|
17
21
|
function isThenable(value) {
|
|
18
22
|
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
19
23
|
}
|
|
@@ -23,7 +27,7 @@ setIslandServerHook({
|
|
|
23
27
|
const addressed = addressValues(props, { encodeSpecial: (value) => {
|
|
24
28
|
if (value && typeof value === "object" && runtimeTagged.has(value)) return value;
|
|
25
29
|
if (isThenable(value)) {
|
|
26
|
-
const name =
|
|
30
|
+
const name = nextPromiseName();
|
|
27
31
|
promises.push({
|
|
28
32
|
name,
|
|
29
33
|
promise: value
|
package/dist/react.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as createIsland, o as isolateIslands, t as IslandsProvider, u as withinClientRoot } from "./_chunks/island-DVOr-OUw.mjs";
|
|
2
|
-
import { a as renderServerTree, t as Outlet } from "./_chunks/tree-
|
|
2
|
+
import { a as renderServerTree, t as Outlet } from "./_chunks/tree-C47p6khN.mjs";
|
|
3
3
|
export { IslandsProvider, Outlet, createIsland, isolateIslands, renderServerTree, withinClientRoot };
|
package/dist/server.d.mts
CHANGED
|
@@ -71,15 +71,15 @@ declare class SealError extends Error {
|
|
|
71
71
|
interface ActionKeys {
|
|
72
72
|
/** The key new values are sealed with. */
|
|
73
73
|
current: CryptoKey;
|
|
74
|
-
/** Its id, written into each sealed value (
|
|
74
|
+
/** Its id, written into each sealed value (a 32-bit unsigned integer). */
|
|
75
75
|
currentId: number;
|
|
76
76
|
/**
|
|
77
|
-
* Every key that opens a value, by id: the current one and earlier ones still accepted.
|
|
78
|
-
*
|
|
77
|
+
* Every key that opens a value, by id: the current one and earlier ones still accepted. Two keys
|
|
78
|
+
* may still share an id; each is tried.
|
|
79
79
|
*/
|
|
80
80
|
byId: ReadonlyMap<number, readonly CryptoKey[]>;
|
|
81
81
|
}
|
|
82
|
-
/** A new random key, as base64url: what `
|
|
82
|
+
/** A new random key, as base64url: what `ISLX_ACTIONS_KEY` holds. */
|
|
83
83
|
declare function generateActionKey(): string;
|
|
84
84
|
/**
|
|
85
85
|
* The keys values are sealed and opened with. `current` seals; `previous` keys still open
|
package/dist/server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as parseHeldValues, i as hashValue, n as addressValues, o as selectValuesToSend, r as escapeScriptJson, t as VALUE_MIN_LENGTH } from "./_chunks/values-37UIzxM0.mjs";
|
|
2
|
-
import { a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, i as encodeActionReference, l as SealError, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys } from "./_chunks/actions-
|
|
2
|
+
import { a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, i as encodeActionReference, l as SealError, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys } from "./_chunks/actions-CvQ0-zjg.mjs";
|
|
3
3
|
import { a as encode, r as decode } from "./_chunks/codec-C8to-RQW.mjs";
|
|
4
4
|
import { I as readPostedAction, L as stripRuntimeFields, N 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-QEQWBr5s.mjs";
|
|
5
5
|
import { n as setBuildDefaults, t as getBuildDefaults } from "./_chunks/build-COIcs7wm.mjs";
|
package/dist/tanstack-start.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as islx, t as ENTRY_ID } from "./_chunks/vite-
|
|
1
|
+
import { n as islx, t as ENTRY_ID } from "./_chunks/vite-BTzYlEiY.mjs";
|
|
2
2
|
import { normalizePath } from "vite";
|
|
3
3
|
// src/tanstack-start/index.ts
|
|
4
4
|
/**
|
|
@@ -30,7 +30,9 @@ export default createServerEntry({
|
|
|
30
30
|
});
|
|
31
31
|
`;
|
|
32
32
|
const routerWrapper = (routerPath) => `import * as app from ${JSON.stringify(routerPath)};
|
|
33
|
+
import { PREHYDRATION_SCRIPT } from "@farm.js/islx/client";
|
|
33
34
|
export * from ${JSON.stringify(routerPath)};
|
|
35
|
+
const islxHead = new WeakSet();
|
|
34
36
|
export async function getRouter(...args) {
|
|
35
37
|
const router = await app.getRouter(...args);
|
|
36
38
|
const lifecycle = (router.serverSsrLifecycle ??= {});
|
|
@@ -38,6 +40,16 @@ export async function getRouter(...args) {
|
|
|
38
40
|
(lifecycle.onServerSsrAttach ??= []).push((ssr) => {
|
|
39
41
|
ssr.dehydrate = async () => ssr.disableHydration();
|
|
40
42
|
});
|
|
43
|
+
const root = router.routeTree;
|
|
44
|
+
if (root && !islxHead.has(root)) {
|
|
45
|
+
islxHead.add(root);
|
|
46
|
+
const head = root.options.head;
|
|
47
|
+
const add = (own) => ({ ...own, scripts: [{ children: PREHYDRATION_SCRIPT }, ...(own?.scripts ?? [])] });
|
|
48
|
+
root.options.head = (context) => {
|
|
49
|
+
const own = head?.(context);
|
|
50
|
+
return own && typeof own.then === "function" ? own.then(add) : add(own);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
41
53
|
return router;
|
|
42
54
|
}
|
|
43
55
|
`;
|
package/dist/vite.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as ENTRY_ID, n as default, n as islx, r as resolveDirectives, i as serializeHooks } from "./_chunks/vite-
|
|
1
|
+
export { t as ENTRY_ID, n as default, n as islx, r as resolveDirectives, i as serializeHooks } from "./_chunks/vite-BTzYlEiY.mjs";
|
package/package.json
CHANGED