@voltro/web 0.28.0 → 0.29.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
@@ -1,6 +1,8 @@
1
1
  import { ComponentType } from 'react';
2
2
  import { PipeableStream } from 'react-dom/server';
3
+ import { PreloadSeed } from '@voltro/client';
3
4
  import { ReactNode } from 'react';
5
+ import { seedPreloadedSubscription } from '@voltro/client';
4
6
  import { StoreSeed } from '@voltro/client';
5
7
  import { StoreSeedBag } from '@voltro/client';
6
8
 
@@ -45,15 +47,23 @@ export declare const assertDeferralSupported: (target: DeferralTarget) => void;
45
47
  * `enterWith` is the Node API for exactly this case: it installs the store for
46
48
  * the current async context and everything that flows from it, which for a
47
49
  * per-request handler is precisely the intended lifetime. Call it once at the
48
- * top of a render, then read `currentStoreSeeds()` where the payload is built.
50
+ * top of a render, then read `currentStoreSeeds()` / `currentPreloadSeeds()`
51
+ * where the payload is built.
49
52
  *
50
53
  * Do NOT call it from a long-lived context (a module top level, a server
51
54
  * bootstrap): there it would install one bag for the whole process, which is
52
55
  * the leak the scoping exists to prevent.
56
+ *
57
+ * Returns the STORE bag so existing callers keep their `{ seeds }` shape; the
58
+ * preload bag is installed into the same scope and read via
59
+ * `currentPreloadSeeds()`.
53
60
  */
54
61
  export declare const beginStoreSeeds: () => StoreSeedBag;
55
62
 
56
- /** The current request's seeds, or null outside a render. Tests + diagnostics. */
63
+ /** The current request's preloaded-subscription seeds, or null outside a render. */
64
+ export declare const currentPreloadSeeds: () => ReadonlyArray<PreloadSeed> | null;
65
+
66
+ /** The current request's store seeds, or null outside a render. Tests + diagnostics. */
57
67
  export declare const currentStoreSeeds: () => ReadonlyArray<StoreSeed> | null;
58
68
 
59
69
  /** Render modes / interactive modes a page may combine with `defer()`. */
@@ -153,6 +163,18 @@ export declare interface InlinedRouterState {
153
163
  readonly key?: string;
154
164
  readonly value: unknown;
155
165
  }>;
166
+ /**
167
+ * Preloaded-subscription first values the server seeded for this document
168
+ * (see `seedPreloadedSubscription` in `@voltro/client`). Rides the same
169
+ * payload as `storeSeeds` for the same reason: one thing to emit, parse, and
170
+ * remember on all three SSR paths. `mount` applies them before the first
171
+ * render so `usePreloadedSubscription` shows real data with no flash.
172
+ */
173
+ readonly preloadSeeds?: ReadonlyArray<{
174
+ readonly api: string;
175
+ readonly key: string;
176
+ readonly value: unknown;
177
+ }>;
156
178
  /** Deferred (streamed) loader fields: field name -> client-registry id, for
157
179
  * the page loader and per chain index. The VALUES are not here — they
158
180
  * arrive later, published by the settle `<script>` each `<Await>` boundary
@@ -316,6 +338,11 @@ declare interface PageMeta {
316
338
  * JSON-serialisable is accepted; `@context` + `@type` are
317
339
  * authored, not synthesised. */
318
340
  readonly jsonLd?: ReadonlyArray<Record<string, unknown>>;
341
+ /** Keep this page out of the index. Emits
342
+ * `<meta name="robots" content="noindex, nofollow">`, and the build's
343
+ * `sitemap.xml` generator EXCLUDES the route. Use for utility pages
344
+ * (checkout steps, previews, thank-you pages) that should not rank. */
345
+ readonly noIndex?: boolean;
319
346
  }
320
347
 
321
348
  export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
@@ -512,7 +539,8 @@ export declare const renderPageToHtml: (options: RenderPageOptions) => RenderPag
512
539
  export declare const renderPageToStream: (options: RenderPageStreamOptions) => RenderPageStreamResult;
513
540
 
514
541
  /**
515
- * The hydration payload, with this request's client-store seeds folded in.
542
+ * The hydration payload, with this request's client-store seeds AND
543
+ * preloaded-subscription seeds folded in.
516
544
  *
517
545
  * The fold happens HERE and nowhere else on purpose. There are three
518
546
  * independent SSR emitters, and every one of them already calls this function —
@@ -521,8 +549,8 @@ export declare const renderPageToStream: (options: RenderPageStreamOptions) => R
521
549
  * passing `storeSeeds` itself) is three chances to forget and one silent
522
550
  * difference between environments.
523
551
  *
524
- * An explicit `storeSeeds` on the input still wins, so the build-time prerender
525
- * can pass a set it computed some other way.
552
+ * An explicit `storeSeeds` / `preloadSeeds` on the input still wins, so the
553
+ * build-time prerender can pass a set it computed some other way.
526
554
  */
527
555
  export declare const renderRouterStateScript: (input: RouterStateInput) => string;
528
556
 
@@ -551,6 +579,13 @@ export declare interface RouterStateInput {
551
579
  readonly key?: string;
552
580
  readonly value: unknown;
553
581
  }>;
582
+ /** Preloaded-subscription first values seeded during this render (see
583
+ * `InlinedRouterState`). */
584
+ readonly preloadSeeds?: ReadonlyArray<{
585
+ readonly api: string;
586
+ readonly key: string;
587
+ readonly value: unknown;
588
+ }>;
554
589
  /** Set by the SSR-layout-shell path (a `renderMode:'spa'` page under an SSR
555
590
  * layout chain): the server rendered the layouts + an empty page slot and
556
591
  * ran only the layout loaders. Tells the client to reproduce the empty slot
@@ -596,6 +631,8 @@ declare interface RouteSegment {
596
631
  readonly loader?: LoaderFn | undefined;
597
632
  }
598
633
 
634
+ export { seedPreloadedSubscription }
635
+
599
636
  /**
600
637
  * Serialise arbitrary data for inlining into an HTML `<script>` body.
601
638
  *
@@ -627,6 +664,7 @@ declare interface ServerRequestContextValue {
627
664
  export declare const withStoreSeeds: <T>(render: () => Promise<T>) => Promise<{
628
665
  readonly result: T;
629
666
  readonly storeSeeds: ReadonlyArray<StoreSeed>;
667
+ readonly preloadSeeds: ReadonlyArray<PreloadSeed>;
630
668
  }>;
631
669
 
632
670
  export { }
package/dist/ssr.js CHANGED
@@ -1,40 +1,50 @@
1
- import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-DDDBYFrI.js";
2
- import { c, m as l, r as u, t as d, u as f, w as p } from "./serverContext-BASpdhXG.js";
3
- import { createElement as m } from "react";
4
- import { makeStoreSeedBag as h, setStoreSeedResolver as g } from "@voltro/client";
5
- import { renderToPipeableStream as _, renderToString as v } from "react-dom/server";
6
- import { AsyncLocalStorage as y } from "node:async_hooks";
1
+ import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-DAT472IC.js";
2
+ import { T as c, c as l, d as u, h as d, l as f, r as p, t as m } from "./serverContext-B1Pivair.js";
3
+ import { createElement as h } from "react";
4
+ import { makePreloadSeedBag as g, makeStoreSeedBag as _, seedPreloadedSubscription as v, setPreloadSeedResolver as y, setStoreSeedResolver as b } from "@voltro/client";
5
+ import { renderToPipeableStream as x, renderToString as S } from "react-dom/server";
6
+ import { AsyncLocalStorage as C } from "node:async_hooks";
7
7
  //#region src/storeSeedScope.ts
8
- var b = new y();
9
- g(() => b.getStore() ?? null);
10
- var x = async (e) => {
11
- let t = h();
8
+ var w = new C();
9
+ b(() => w.getStore()?.store ?? null), y(() => w.getStore()?.preload ?? null);
10
+ var T = async (e) => {
11
+ let t = {
12
+ store: _(),
13
+ preload: g()
14
+ };
12
15
  return {
13
- result: await b.run(t, e),
14
- storeSeeds: t.seeds
16
+ result: await w.run(t, e),
17
+ storeSeeds: t.store.seeds,
18
+ preloadSeeds: t.preload.seeds
19
+ };
20
+ }, E = () => {
21
+ let e = {
22
+ store: _(),
23
+ preload: g()
15
24
  };
16
- }, S = () => {
17
- let e = h();
18
- return b.enterWith(e), e;
19
- }, C = () => b.getStore()?.seeds ?? null, w = (e) => {
20
- let n = C();
21
- return t(e.storeSeeds !== void 0 || n === null || n.length === 0 ? e : {
25
+ return w.enterWith(e), e.store;
26
+ }, D = () => w.getStore()?.store.seeds ?? null, O = () => w.getStore()?.preload.seeds ?? null, k = (e) => {
27
+ let n = D(), r = O(), i = e.storeSeeds !== void 0 || n === null || n.length === 0 ? e : {
22
28
  ...e,
23
29
  storeSeeds: n
30
+ };
31
+ return t(e.preloadSeeds !== void 0 || r === null || r.length === 0 ? i : {
32
+ ...i,
33
+ preloadSeeds: r
24
34
  });
25
- }, T = (e, t, n, r) => {
26
- let i = m(c.Provider, { value: n }, m(e));
27
- if (!t) return i;
35
+ }, A = (e, t, n, r, i) => {
36
+ let a = h(l.Provider, { value: i ? n : f }, h(e));
37
+ if (!t) return a;
28
38
  for (let e = t.length - 1; e >= 0; e--) {
29
39
  let n = t[e];
30
40
  if (n.Layout) {
31
41
  let t = n.Layout;
32
- i = m(c.Provider, { value: r(e) }, m(t, { children: i }));
42
+ a = h(l.Provider, { value: n.loader === void 0 ? f : r(e) }, h(t, { children: a }));
33
43
  }
34
44
  }
35
- return i;
36
- }, E = (e) => {
37
- let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: c, pageSlot: u } = e, h = p(t.meta, n, i, c), g = {
45
+ return a;
46
+ }, j = (e) => {
47
+ let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: l, pageSlot: f } = e, p = c(t.meta, n, i, l), g = {
38
48
  pathname: r,
39
49
  search: "",
40
50
  params: n,
@@ -45,23 +55,23 @@ var x = async (e) => {
45
55
  prefetch: () => {},
46
56
  registerBlocker: () => () => {},
47
57
  blocked: null
48
- }, _ = u ? f : t.Component;
58
+ }, _ = f ? u : t.Component;
49
59
  if (!_) throw Error(`SSR received a descriptor with no Component for "${r}". Lazy page routes are a client-only optimization and must not be used on the server.`);
50
- let v = m(l.Provider, { value: g }, T(_, t.chain, u ? void 0 : i, (e) => a?.[e]));
51
- return o && (v = m(d.Provider, { value: o }, v)), s && (v = s(v)), {
60
+ let v = h(d.Provider, { value: g }, A(_, t.chain, f ? void 0 : i, (e) => a?.[e], !f && t.loader !== void 0));
61
+ return o && (v = h(m.Provider, { value: o }, v)), s && (v = s(v)), {
52
62
  tree: v,
53
- meta: h
63
+ meta: p
54
64
  };
55
- }, D = (e) => {
56
- let { tree: t, meta: n } = E(e);
65
+ }, M = (e) => {
66
+ let { tree: t, meta: n } = j(e);
57
67
  return {
58
- html: v(t),
68
+ html: S(t),
59
69
  meta: n
60
70
  };
61
- }, O = (e) => {
62
- let { tree: t, meta: n } = E(e);
71
+ }, N = (e) => {
72
+ let { tree: t, meta: n } = j(e);
63
73
  return {
64
- stream: _(t, {
74
+ stream: x(t, {
65
75
  ...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
66
76
  ...e.onShellReady ? { onShellReady: e.onShellReady } : {},
67
77
  ...e.onShellError ? { onShellError: e.onShellError } : {},
@@ -69,20 +79,20 @@ var x = async (e) => {
69
79
  }),
70
80
  meta: n
71
81
  };
72
- }, k = (e) => {
82
+ }, P = (e) => {
73
83
  if (!e) return "";
74
84
  let t = [];
75
- if (typeof e.title == "string" && t.push(`<title>${A(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${j(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${j(e.canonical)}" />`), e.tags) for (let n of e.tags) {
76
- let e = n.name ? `name="${j(n.name)}"` : n.property ? `property="${j(n.property)}"` : "";
77
- e && t.push(`<meta ${e} content="${j(n.content)}" />`);
85
+ if (typeof e.title == "string" && t.push(`<title>${F(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${I(e.description)}" />`), e.noIndex && t.push("<meta name=\"robots\" content=\"noindex, nofollow\" />"), e.canonical && t.push(`<link rel="canonical" href="${I(e.canonical)}" />`), e.tags) for (let n of e.tags) {
86
+ let e = n.name ? `name="${I(n.name)}"` : n.property ? `property="${I(n.property)}"` : "";
87
+ e && t.push(`<meta ${e} content="${I(n.content)}" />`);
78
88
  }
79
89
  if (e.links) for (let n of e.links) {
80
90
  let e = [
81
- `rel="${j(n.rel)}"`,
82
- `href="${j(n.href)}"`,
83
- n.hreflang ? `hreflang="${j(n.hreflang)}"` : "",
84
- n.type ? `type="${j(n.type)}"` : "",
85
- n.title ? `title="${j(n.title)}"` : ""
91
+ `rel="${I(n.rel)}"`,
92
+ `href="${I(n.href)}"`,
93
+ n.hreflang ? `hreflang="${I(n.hreflang)}"` : "",
94
+ n.type ? `type="${I(n.type)}"` : "",
95
+ n.title ? `title="${I(n.title)}"` : ""
86
96
  ].filter(Boolean).join(" ");
87
97
  t.push(`<link ${e} />`);
88
98
  }
@@ -91,6 +101,6 @@ var x = async (e) => {
91
101
  t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
92
102
  }
93
103
  return t.join("\n ");
94
- }, A = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), j = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
104
+ }, F = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), I = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
95
105
  //#endregion
96
- export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, S as beginStoreSeeds, C as currentStoreSeeds, i as encodeRouterState, a as isDeferredLoaderResult, u as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, k as renderMetaToHtml, D as renderPageToHtml, O as renderPageToStream, w as renderRouterStateScript, e as serialiseStateForInlining, x as withStoreSeeds };
106
+ export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, E as beginStoreSeeds, O as currentPreloadSeeds, D as currentStoreSeeds, i as encodeRouterState, a as isDeferredLoaderResult, p as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, P as renderMetaToHtml, M as renderPageToHtml, N as renderPageToStream, k as renderRouterStateScript, v as seedPreloadedSubscription, e as serialiseStateForInlining, T as withStoreSeeds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/web",
3
- "version": "0.28.0",
3
+ "version": "0.29.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",
@@ -52,8 +52,8 @@
52
52
  "node": ">=24.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@voltro/client": "0.28.0",
56
- "@voltro/ui": "0.28.0"
55
+ "@voltro/client": "0.29.0",
56
+ "@voltro/ui": "0.29.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@effect/platform": "^0.97.0",