@voltro/web 0.51.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ssr.d.ts CHANGED
@@ -7,6 +7,7 @@ import { ProcedureTypeMap } from '@voltro/client';
7
7
  import { ReactNode } from 'react';
8
8
  import { seedPreloadedSubscription } from '@voltro/client';
9
9
  import { seedPreloadedSubscriptionFailure } from '@voltro/client';
10
+ import { ServerRequestContextValue } from '@voltro/client';
10
11
  import { StoreSeed } from '@voltro/client';
11
12
  import { StoreSeedBag } from '@voltro/client';
12
13
 
@@ -93,12 +94,25 @@ export declare interface DeferralTarget {
93
94
  * Absent for every full-page render, where `renderMode` alone decides.
94
95
  */
95
96
  readonly layoutShell?: 'stream' | 'artefact' | undefined;
97
+ /**
98
+ * Partial prerendering: the page EXPORTED `ppr = true`. Makes `defer()` on
99
+ * `renderMode: 'isr'` legal — the cached SHELL renders the eager fields plus
100
+ * each `<Await>` fallback (credential-free, fail-closed), and the deferred
101
+ * HOLES run per request with the full request context, appended to the same
102
+ * response as registry settle scripts. The client reveals them through
103
+ * hydration — no React resume, no cached reveal scripts.
104
+ */
105
+ readonly ppr?: boolean;
96
106
  }
97
107
 
98
108
  /** Brand key on the object `defer()` returns. A single explicit container
99
109
  * check — NOT a scan of arbitrary loader results for markers by shape. */
100
110
  declare const DEFERRED_RESULT_TAG: "__voltroDeferredLoaderResult";
101
111
 
112
+ /** Stable registry id for one deferred field. `scope` is `'page'` or the
113
+ * layout's chain index — the same index the router-state payload keys by. */
114
+ export declare const deferredId: (scope: "page" | number, key: string) => string;
115
+
102
116
  /** What a loader returns when it defers part of its data. Opaque to user
103
117
  * code: build it with {@link defer}, read it with `useLoaderData()`. */
104
118
  declare interface DeferredLoaderResult<TEager extends object = object, TDeferred extends Record<string, Promise<unknown>> = Record<string, Promise<unknown>>> {
@@ -111,6 +125,9 @@ declare interface DeferredLoaderResult<TEager extends object = object, TDeferred
111
125
  readonly deferred: TDeferred;
112
126
  }
113
127
 
128
+ /** The body of the `<script>` `<Await>` emits when its promise resolves. */
129
+ export declare const deferredSettleScript: (id: string, value: unknown) => string;
130
+
114
131
  /** Build the wire payload from what the server render was given. */
115
132
  export declare const encodeRouterState: (input: RouterStateInput) => InlinedRouterState;
116
133
 
@@ -372,6 +389,19 @@ declare interface PageDescriptor<TLoaderData = unknown> {
372
389
  * set this to `'islands'` so only marked interactive zones
373
390
  * hydrate; the rest stays pure static HTML. */
374
391
  readonly interactive?: InteractiveMode | undefined;
392
+ /**
393
+ * Intercepting route (modal-with-URL): when a SOFT navigation reaches this
394
+ * page FROM one of the `from` route patterns, the page renders as an
395
+ * OVERLAY above the still-mounted origin page instead of replacing it —
396
+ * URL changes, Back closes the overlay, a hard load of the same URL
397
+ * renders the page standalone. `from` is one or more route PATTERNS
398
+ * (`'/photos'`, `'/albums/[id]'`); a soft navigation from any other route
399
+ * renders standalone. Full-hydration pages only (`interactive: 'full'`) —
400
+ * islands/none pages have no client router to intercept with.
401
+ */
402
+ readonly intercept?: {
403
+ readonly from: string | ReadonlyArray<string>;
404
+ } | undefined;
375
405
  }
376
406
 
377
407
  declare interface PageMeta {
@@ -571,6 +601,11 @@ export declare interface RenderPageStreamOptions extends RenderPageOptions {
571
601
  /** Bootstrap `<script>` module URLs injected by React at the end of the
572
602
  * shell — the client bundle that hydrates the streamed HTML. */
573
603
  readonly bootstrapModules?: ReadonlyArray<string>;
604
+ /** Per-request CSP nonce — React stamps it onto every script IT emits
605
+ * (bootstrap modules, Suspense settle scripts). The shell's own tags are
606
+ * stamped by the CLI's `stampScriptNonce`; the POLICY header is the
607
+ * middleware's (plan 08 §7). */
608
+ readonly nonce?: string;
574
609
  }
575
610
 
576
611
  export declare interface RenderPageStreamResult {
@@ -726,16 +761,6 @@ export { seedPreloadedSubscriptionFailure }
726
761
  */
727
762
  export declare const serialiseStateForInlining: (state: unknown) => string;
728
763
 
729
- declare interface ServerRequestContextValue {
730
- readonly cookies: Readonly<Record<string, string>>;
731
- readonly headers: Readonly<Record<string, string>>;
732
- /** Raw request URL as it came off the wire (path + query). Useful
733
- * for SSR pages that need to read `?q=…` style search params
734
- * without touching anything client-only. Empty string for build-
735
- * time SSG renders where there is no incoming request. */
736
- readonly url: string;
737
- }
738
-
739
764
  /**
740
765
  * Run one request's render with a fresh seed bag; return what it collected
741
766
  * alongside the render's own result.
package/dist/ssr.js CHANGED
@@ -1,31 +1,31 @@
1
- import { s as e } from "./defaultFallbacks-B4Ak-R6R.js";
2
- import { _ as t, a as n, g as r, m as i, n as a, p as o, s, t as c } from "./routerState-DAT472IC.js";
3
- import { E as l, c as u, d, l as f, r as p, t as m, w as h } from "./serverContext-38JTbYPa.js";
4
- import { createElement as g } from "react";
5
- import { makePreloadSeedBag as _, makeStoreSeedBag as v, seedPreloadedSubscription as y, seedPreloadedSubscriptionFailure as b, setPreloadSeedResolver as x, setStoreSeedResolver as S } from "@voltro/client";
6
- import { renderToPipeableStream as C, renderToString as w } from "react-dom/server";
7
- import { AsyncLocalStorage as T } from "node:async_hooks";
1
+ import { s as e } from "./defaultFallbacks-SQ45vAhz.js";
2
+ import { _ as t, a as n, f as r, h as i, m as a, n as o, s, t as c, u as l, v as u } from "./routerState-Cr6fTbka.js";
3
+ import { C as d, a as f, c as p, o as m, t as h, x as g } from "./serverContext-BwzZ6Q7d.js";
4
+ import { createElement as _ } from "react";
5
+ import { ServerRequestContext as v, makePreloadSeedBag as y, makeStoreSeedBag as b, seedPreloadedSubscription as x, seedPreloadedSubscriptionFailure as S, setPreloadSeedResolver as C, setStoreSeedResolver as w } from "@voltro/client";
6
+ import { renderToPipeableStream as T, renderToString as E } from "react-dom/server";
7
+ import { AsyncLocalStorage as D } from "node:async_hooks";
8
8
  //#region src/storeSeedScope.ts
9
- var E = new T();
10
- S(() => E.getStore()?.store ?? null), x(() => E.getStore()?.preload ?? null);
11
- var D = async (e) => {
9
+ var O = new D();
10
+ w(() => O.getStore()?.store ?? null), C(() => O.getStore()?.preload ?? null);
11
+ var k = async (e) => {
12
12
  let t = {
13
- store: v(),
14
- preload: _()
13
+ store: b(),
14
+ preload: y()
15
15
  };
16
16
  return {
17
- result: await E.run(t, e),
17
+ result: await O.run(t, e),
18
18
  storeSeeds: t.store.seeds,
19
19
  preloadSeeds: t.preload.seeds
20
20
  };
21
- }, O = () => {
21
+ }, A = () => {
22
22
  let e = {
23
- store: v(),
24
- preload: _()
23
+ store: b(),
24
+ preload: y()
25
25
  };
26
- return E.enterWith(e), e.store;
27
- }, k = () => E.getStore()?.store.seeds ?? null, A = () => E.getStore()?.preload.seeds ?? null, j = (e) => {
28
- let t = k(), r = A(), i = e.storeSeeds !== void 0 || t === null || t.length === 0 ? e : {
26
+ return O.enterWith(e), e.store;
27
+ }, j = () => O.getStore()?.store.seeds ?? null, M = () => O.getStore()?.preload.seeds ?? null, N = (e) => {
28
+ let t = j(), r = M(), i = e.storeSeeds !== void 0 || t === null || t.length === 0 ? e : {
29
29
  ...e,
30
30
  storeSeeds: t
31
31
  };
@@ -33,19 +33,19 @@ var D = async (e) => {
33
33
  ...i,
34
34
  preloadSeeds: r
35
35
  });
36
- }, M = (e, t, n, r, i) => {
37
- let a = g(u.Provider, { value: i ? n : f }, g(e));
36
+ }, P = (e, t, n, r, i) => {
37
+ let a = _(f.Provider, { value: i ? n : m }, _(e));
38
38
  if (!t) return a;
39
39
  for (let e = t.length - 1; e >= 0; e--) {
40
40
  let n = t[e];
41
41
  if (n.Layout) {
42
42
  let t = n.Layout;
43
- a = g(u.Provider, { value: n.loader === void 0 ? f : r(e) }, g(t, { children: a }));
43
+ a = _(f.Provider, { value: n.loader === void 0 ? m : r(e) }, _(t, { children: a }));
44
44
  }
45
45
  }
46
46
  return a;
47
- }, N = (t) => {
48
- let { descriptor: n, params: r, pathname: i, search: a, loaderData: o, segmentLoaderData: s, requestContext: c, outerWrap: u, locale: f, pageSlot: p } = t, _ = l(n.meta, r, o, f), v = {
47
+ }, F = (t) => {
48
+ let { descriptor: n, params: r, pathname: i, search: a, loaderData: o, segmentLoaderData: s, requestContext: c, outerWrap: l, locale: u, pageSlot: f } = t, m = d(n.meta, r, o, u), h = {
49
49
  pathname: i,
50
50
  search: a ?? "",
51
51
  params: r,
@@ -56,47 +56,48 @@ var D = async (e) => {
56
56
  prefetch: () => {},
57
57
  registerBlocker: () => () => {},
58
58
  blocked: null
59
- }, y = p ? d : n.Component;
59
+ }, y = f ? p : n.Component;
60
60
  if (!y) throw Error(`SSR received a descriptor with no Component for "${i}". Lazy page routes are a client-only optimization and must not be used on the server.`);
61
- let b = h(v, M(y, n.chain, p ? void 0 : o, (e) => s?.[e], !p && n.loader !== void 0));
62
- return b = g(e, {
61
+ let b = g(h, P(y, n.chain, f ? void 0 : o, (e) => s?.[e], !f && n.loader !== void 0));
62
+ return b = _(e, {
63
63
  chrome: null,
64
64
  children: b
65
- }), c && (b = g(m.Provider, { value: c }, b)), u && (b = u(b)), {
65
+ }), c && (b = _(v.Provider, { value: c }, b)), l && (b = l(b)), {
66
66
  tree: b,
67
- meta: _
67
+ meta: m
68
68
  };
69
- }, P = (e) => {
70
- let { tree: t, meta: n } = N(e);
69
+ }, I = (e) => {
70
+ let { tree: t, meta: n } = F(e);
71
71
  return {
72
- html: w(t),
72
+ html: E(t),
73
73
  meta: n
74
74
  };
75
- }, F = (e) => {
76
- let { tree: t, meta: n } = N(e);
75
+ }, L = (e) => {
76
+ let { tree: t, meta: n } = F(e);
77
77
  return {
78
- stream: C(t, {
78
+ stream: T(t, {
79
79
  ...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
80
+ ...e.nonce === void 0 ? {} : { nonce: e.nonce },
80
81
  ...e.onShellReady ? { onShellReady: e.onShellReady } : {},
81
82
  ...e.onShellError ? { onShellError: e.onShellError } : {},
82
83
  ...e.onAllReady ? { onAllReady: e.onAllReady } : {}
83
84
  }),
84
85
  meta: n
85
86
  };
86
- }, I = (e) => {
87
+ }, R = (e) => {
87
88
  if (!e) return "";
88
89
  let t = [];
89
- if (typeof e.title == "string" && t.push(`<title>${L(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${R(e.description)}" />`), e.noIndex && t.push("<meta name=\"robots\" content=\"noindex, nofollow\" />"), e.canonical && t.push(`<link rel="canonical" href="${R(e.canonical)}" />`), e.tags) for (let n of e.tags) {
90
- let e = n.name ? `name="${R(n.name)}"` : n.property ? `property="${R(n.property)}"` : "";
91
- e && t.push(`<meta ${e} content="${R(n.content)}" />`);
90
+ if (typeof e.title == "string" && t.push(`<title>${z(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${B(e.description)}" />`), e.noIndex && t.push("<meta name=\"robots\" content=\"noindex, nofollow\" />"), e.canonical && t.push(`<link rel="canonical" href="${B(e.canonical)}" />`), e.tags) for (let n of e.tags) {
91
+ let e = n.name ? `name="${B(n.name)}"` : n.property ? `property="${B(n.property)}"` : "";
92
+ e && t.push(`<meta ${e} content="${B(n.content)}" />`);
92
93
  }
93
94
  if (e.links) for (let n of e.links) {
94
95
  let e = [
95
- `rel="${R(n.rel)}"`,
96
- `href="${R(n.href)}"`,
97
- n.hreflang ? `hreflang="${R(n.hreflang)}"` : "",
98
- n.type ? `type="${R(n.type)}"` : "",
99
- n.title ? `title="${R(n.title)}"` : ""
96
+ `rel="${B(n.rel)}"`,
97
+ `href="${B(n.href)}"`,
98
+ n.hreflang ? `hreflang="${B(n.hreflang)}"` : "",
99
+ n.type ? `type="${B(n.type)}"` : "",
100
+ n.title ? `title="${B(n.title)}"` : ""
100
101
  ].filter(Boolean).join(" ");
101
102
  t.push(`<link ${e} />`);
102
103
  }
@@ -105,6 +106,6 @@ var D = async (e) => {
105
106
  t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
106
107
  }
107
108
  return t.join("\n ");
108
- }, L = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), R = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
109
+ }, z = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), B = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
109
110
  //#endregion
110
- export { c as ROUTER_STATE_SCRIPT_ID, s as assertDeferralSupported, O as beginStoreSeeds, A as currentPreloadSeeds, k as currentStoreSeeds, a as encodeRouterState, o as isDeferredLoaderResult, p as parseCookieHeader, i as prepareDeferredLoaderData, r as renderDeferredRegistryScript, I as renderMetaToHtml, P as renderPageToHtml, F as renderPageToStream, j as renderRouterStateScript, y as seedPreloadedSubscription, b as seedPreloadedSubscriptionFailure, t as serialiseStateForInlining, D as withStoreSeeds };
111
+ export { c as ROUTER_STATE_SCRIPT_ID, s as assertDeferralSupported, A as beginStoreSeeds, M as currentPreloadSeeds, j as currentStoreSeeds, l as deferredId, r as deferredSettleScript, o as encodeRouterState, a as isDeferredLoaderResult, h as parseCookieHeader, i as prepareDeferredLoaderData, t as renderDeferredRegistryScript, R as renderMetaToHtml, I as renderPageToHtml, L as renderPageToStream, N as renderRouterStateScript, x as seedPreloadedSubscription, S as seedPreloadedSubscriptionFailure, u as serialiseStateForInlining, k as withStoreSeeds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/web",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "The Voltro web framework — file-based routing, render modes (SSR / SSG / islands), the page-export contract, data hooks, and the browser mount.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -48,7 +48,12 @@
48
48
  "import": "./dist/middleware.js",
49
49
  "default": "./dist/middleware.js"
50
50
  },
51
- "./package.json": "./package.json"
51
+ "./package.json": "./package.json",
52
+ "./islands": {
53
+ "types": "./dist/islands.d.ts",
54
+ "import": "./dist/islands.js",
55
+ "default": "./dist/islands.js"
56
+ }
52
57
  },
53
58
  "main": "./dist/index.js",
54
59
  "module": "./dist/index.js",
@@ -58,8 +63,8 @@
58
63
  "node": ">=24.0.0"
59
64
  },
60
65
  "dependencies": {
61
- "@voltro/client": "0.51.0",
62
- "@voltro/ui": "0.51.0"
66
+ "@voltro/client": "0.53.0",
67
+ "@voltro/ui": "0.53.0"
63
68
  },
64
69
  "peerDependencies": {
65
70
  "@effect/platform": "^0.97.0",
@@ -1,10 +0,0 @@
1
- import { createContext as e } from "react";
2
- //#region src/globalContext.ts
3
- var t = (t, n) => {
4
- let r = globalThis, i = Symbol.for(`@voltro/web/context:${t}`), a = r[i];
5
- if (a) return a;
6
- let o = e(n);
7
- return r[i] = o, o;
8
- };
9
- //#endregion
10
- export { t };
@@ -1,81 +0,0 @@
1
- import { t as e } from "./frameworkBoot-CV_o2tIk.js";
2
- import { o as t, r as n, t as r } from "./routerState-DAT472IC.js";
3
- import { StrictMode as i, createElement as a } from "react";
4
- import { createRoot as o, hydrateRoot as s } from "react-dom/client";
5
- import { applyPreloadSeeds as c, applyStoreSeeds as l } from "@voltro/client";
6
- import { jsx as u } from "react/jsx-runtime";
7
- //#region src/islands.tsx
8
- var d = /* @__PURE__ */ new Map(), f = (e, t) => {
9
- d.set(t.name, e);
10
- let n = t.hydrate ?? "visible", r = (r) => a("div", {
11
- "data-voltro-island": "",
12
- "data-island-name": t.name,
13
- "data-island-hydrate": n,
14
- "data-island-props": m(r)
15
- }, a(e, r));
16
- return r.displayName = `Island(${t.name})`, r;
17
- }, p = (e) => d.get(e), m = (e) => JSON.stringify(e ?? {}).replace(/</g, "\\u003c"), h = (e) => {
18
- if (!e) return {};
19
- try {
20
- return JSON.parse(e);
21
- } catch {
22
- return {};
23
- }
24
- }, g = () => {
25
- let e = document.querySelectorAll("[data-voltro-island]:not([data-voltro-hydrated])");
26
- for (let t of e) {
27
- let e = t.dataset.islandName ?? "", n = d.get(e);
28
- if (!n) {
29
- console.warn(`[voltro] island "${e}" referenced in DOM but not registered. Did the island file get imported in this bundle?`);
30
- continue;
31
- }
32
- let r = h(t.dataset.islandProps ?? null), i = t.dataset.islandHydrate ?? "visible";
33
- t.setAttribute("data-voltro-hydrated", "pending");
34
- let o = () => {
35
- t.getAttribute("data-voltro-hydrated") !== "done" && (t.setAttribute("data-voltro-hydrated", "done"), s(t, a(n, r)));
36
- };
37
- if (i === "load") o();
38
- else if (i === "idle") {
39
- let e = globalThis.requestIdleCallback;
40
- e ? e(o) : setTimeout(o, 0);
41
- } else if (i === "visible") {
42
- let e = new IntersectionObserver((t) => {
43
- t.some((e) => e.isIntersecting) && (e.disconnect(), o());
44
- }, { rootMargin: "64px" });
45
- e.observe(t);
46
- } else if (i === "interaction") {
47
- let e = () => {
48
- t.removeEventListener("pointerdown", e), t.removeEventListener("keydown", e), o();
49
- };
50
- t.addEventListener("pointerdown", e, { passive: !0 }), t.addEventListener("keydown", e);
51
- }
52
- }
53
- }, _ = () => !1, v = "__voltroIslandsBundleNoticeShown", y = (e) => {
54
- if (!e) return;
55
- let t = globalThis;
56
- t[v] !== !0 && (t[v] = !0, console.info("[voltro] islands mode: hydration is scoped to this page's islands, but the JS payload is NOT reduced — the full app bundle is still downloaded and evaluated. Islands buy CPU, not bytes, today. See the \"Render modes\" docs page."));
57
- }, b = (e) => `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/ws/${e}`, x = (a, d) => {
58
- let f = document.getElementById(d.rootId ?? "root");
59
- if (!f) throw Error(`@voltro/web: no #${d.rootId ?? "root"} element in the document`);
60
- let p = Object.entries(d.apis).map(([e, t]) => ({
61
- name: e,
62
- group: t.group,
63
- descriptors: t.descriptors ?? {},
64
- wsUrl: t.wsUrl ?? b(e),
65
- headers: t.headers
66
- })), m = document.getElementById(r), h = n(m?.textContent, window.location.pathname), v = h !== null && f.children.length > 0, x = document.querySelector("meta[name=\"voltro-interactive\"]")?.getAttribute("content") ?? "full";
67
- if (x === "none") return;
68
- if (x === "islands") {
69
- y(_()), g();
70
- return;
71
- }
72
- v && t(h), l(h?.storeSeeds), c(h?.preloadSeeds);
73
- let S = /* @__PURE__ */ u(i, { children: /* @__PURE__ */ u(e, {
74
- App: a,
75
- apis: p,
76
- ...d.Devtools ? { Devtools: d.Devtools } : {}
77
- }) });
78
- v ? s(f, S) : o(f).render(S);
79
- };
80
- //#endregion
81
- export { f as a, g as i, y as n, p as r, x as t };