@uniflowed/router 0.0.0-alpha.4 → 0.0.0-alpha.41

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 (46) hide show
  1. package/action.js +301 -0
  2. package/client.js +295 -8
  3. package/handler.js +127 -106
  4. package/http-client.js +104 -0
  5. package/index.js +72 -4
  6. package/internal/action-endpoint.js +435 -0
  7. package/internal/action-wire.js +608 -0
  8. package/internal/base-path.js +175 -0
  9. package/internal/boundaries.js +481 -0
  10. package/internal/boundary-data.js +88 -0
  11. package/internal/compose.js +490 -0
  12. package/internal/devtools.js +131 -0
  13. package/internal/diagnostics.js +169 -0
  14. package/internal/error-view.js +193 -0
  15. package/internal/flight-browser.js +238 -0
  16. package/internal/flight-chunks.js +205 -0
  17. package/internal/flight-ssr.js +78 -0
  18. package/internal/flight.js +183 -0
  19. package/internal/head.js +219 -0
  20. package/internal/hydration.js +1085 -0
  21. package/internal/inspector.js +626 -0
  22. package/internal/native-links.js +67 -0
  23. package/internal/native-tree.js +89 -0
  24. package/internal/navigation-cache.js +181 -0
  25. package/internal/payload-rows.js +270 -0
  26. package/internal/payload.js +685 -0
  27. package/internal/prepare-document.js +49 -0
  28. package/internal/react-version.js +77 -0
  29. package/internal/request.js +43 -0
  30. package/internal/resolve.js +1613 -0
  31. package/internal/resolved-summary.js +199 -0
  32. package/internal/routing.js +478 -0
  33. package/internal/runtime.js +1613 -548
  34. package/internal/server-route.js +58 -0
  35. package/internal/shell.js +115 -0
  36. package/internal/stream.js +1099 -0
  37. package/middleware.js +350 -0
  38. package/native-navigation.js +217 -0
  39. package/native.js +416 -0
  40. package/package.json +48 -7
  41. package/routing.js +51 -0
  42. package/rsc-client.js +120 -0
  43. package/rsc-ssr.js +446 -0
  44. package/rsc.js +381 -0
  45. package/server-components.js +159 -0
  46. package/server.js +450 -75
@@ -0,0 +1,49 @@
1
+ // @flow
2
+ //
3
+ // Internal to `@uniflowed/router`: a server's document, made ready for React to
4
+ // hydrate.
5
+ //
6
+ // Where a server wrote the head's metadata, an element React leaves behind, and
7
+ // the placeholder action React writes into a form whose action is a function
8
+ // (its server and its client spell that placeholder differently): none of these
9
+ // comes from a route, and each would be reported as a mismatch. So each is put
10
+ // right before `hydrateRoot` compares the markup with the tree. Both ways of
11
+ // hydrating do it — `hydrate` in `../client.js`, for a route rendered from its
12
+ // modules, and `hydrateFlight` in `../rsc-client.js`, for one React Server
13
+ // Components rendered — so the code lives here rather than in either entry.
14
+
15
+ export function prepareDocumentForHydration(document: Document): void {
16
+ const head = document.head;
17
+ const envelope = head.querySelector('meta[name="uf:render"]');
18
+ if (envelope != null && head.firstChild !== envelope) {
19
+ head.insertBefore(envelope, head.firstChild);
20
+ }
21
+ moveLayoutMetaAfterRouteHead(head, head.querySelector("meta[charset]"));
22
+ moveLayoutMetaAfterRouteHead(head, head.querySelector('meta[name="viewport"]'));
23
+ document.getElementById("_R_")?.remove();
24
+ normalizeReactFormActions(document);
25
+ }
26
+
27
+ function moveLayoutMetaAfterRouteHead(head: HTMLHeadElement, meta: Element | null): void {
28
+ if (meta == null) {
29
+ return;
30
+ }
31
+ const colorScheme = head.querySelector('meta[name="color-scheme"]');
32
+ if (colorScheme != null && colorScheme !== meta) {
33
+ head.insertBefore(meta, colorScheme);
34
+ return;
35
+ }
36
+ head.appendChild(meta);
37
+ }
38
+
39
+ const SERVER_FORM_PLACEHOLDER = "javascript:throw new Error('React form unexpectedly submitted.')";
40
+ const CLIENT_FORM_PLACEHOLDER =
41
+ "javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')";
42
+
43
+ function normalizeReactFormActions(document: Document): void {
44
+ for (const form of document.querySelectorAll("form")) {
45
+ if (form.getAttribute("action") === SERVER_FORM_PLACEHOLDER) {
46
+ form.setAttribute("action", CLIENT_FORM_PLACEHOLDER);
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,77 @@
1
+ // @flow
2
+ //
3
+ // Internal to `@uniflowed/router`: the React that React Server Components need.
4
+ //
5
+ // The router installs beside React 19.2.3, the React that Expo SDK 57 and React
6
+ // Native 0.87 ship. Matching a URL, the native route table and a route rendered
7
+ // from its modules need nothing newer (ubugeeei-prod/uf#992). React Server
8
+ // Components do. They render through `react-server-dom-parcel`, React's own
9
+ // Flight renderer and client, which is released with React and requires the
10
+ // React it was released with: 19.3. So that package is an optional peer, and
11
+ // every module that loads it asks here first.
12
+ //
13
+ // # Why a check at run time
14
+ //
15
+ // A peer range cannot say it. The router has one range for `react`, and that
16
+ // range has to admit 19.2.3 for a native app, while an optional peer that is
17
+ // absent is never compared with anything. The failure is also not where the
18
+ // mistake is: React's Flight client imports against React 19.2 and fails later,
19
+ // inside a render, in terms of React's internals. So each entry that loads
20
+ // Flight refuses before it does anything, and names the React it found and the
21
+ // one it needs.
22
+ //
23
+ // It is a call rather than a statement at module scope, because no shipped
24
+ // module runs anything when it is imported
25
+ // (`crates/uf_lib/tests/package_surface.rs`).
26
+
27
+ import * as React from "react";
28
+
29
+ /** The oldest React that React Server Components render on. */
30
+ export const SERVER_COMPONENTS_REACT: string = "19.3.0";
31
+
32
+ /**
33
+ * Why `entry` cannot run on React `installed`, or `null` when it can.
34
+ *
35
+ * The release numbers are compared and a prerelease tag is ignored, so a 19.3
36
+ * canary counts as 19.3: React names a canary after the release it leads to.
37
+ */
38
+ export function serverComponentsRefusal(entry: string, installed: string): string | null {
39
+ const found = releaseOf(installed);
40
+ const needed = releaseOf(SERVER_COMPONENTS_REACT);
41
+ if (found != null && needed != null && !isBefore(found, needed)) {
42
+ return null;
43
+ }
44
+ return (
45
+ `@uniflowed/router: ${entry} needs React ${SERVER_COMPONENTS_REACT} or newer for React ` +
46
+ `Server Components, and the React it loaded is ${installed}. Install react, react-dom and ` +
47
+ "react-server-dom-parcel at ^19.3.0, or set `app.rsc: false` in uf.config.js to render " +
48
+ "routes from their modules, which the router supports from React 19.2.3."
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Refuse, with [`serverComponentsRefusal`]'s sentence, unless the React this
54
+ * module loaded can render React Server Components.
55
+ */
56
+ export function requireServerComponentsReact(entry: string): void {
57
+ const refusal = serverComponentsRefusal(entry, React.version);
58
+ if (refusal != null) {
59
+ throw new Error(refusal);
60
+ }
61
+ }
62
+
63
+ /** `[major, minor, patch]` of a version, or `null` when it does not start with one. */
64
+ function releaseOf(version: string): [number, number, number] | null {
65
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
66
+ return match == null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
67
+ }
68
+
69
+ /** Whether release `a` comes before release `b`. */
70
+ function isBefore(a: [number, number, number], b: [number, number, number]): boolean {
71
+ for (let index = 0; index < 3; index += 1) {
72
+ if (a[index] !== b[index]) {
73
+ return a[index] < b[index];
74
+ }
75
+ }
76
+ return false;
77
+ }
@@ -0,0 +1,43 @@
1
+ // @flow
2
+ //
3
+ // Internal to `@uniflowed/router`: the request the server half runs inside.
4
+ //
5
+ // One function, and it exists because two modules need the same refusal.
6
+ // `createMiddlewareRunner` and `createDispatcher` both used to build a request
7
+ // context of their own — `contextFor`, then `runWithContext`, then
8
+ // `drainDeferred` — which gave one request up to two contexts and ran what
9
+ // `after()` deferred before the response was written, and in the middleware's
10
+ // case before there was a response at all. See ubugeeei-prod/uf#389.
11
+ //
12
+ // Both now run inside whatever request the host established, which means both
13
+ // depend on the host having established one. That dependency is checked rather
14
+ // than assumed, for the reason `createApplicationHandler` gives about
15
+ // `entry.runMiddleware` itself: a missing half of the contract should be a
16
+ // failure on the first request, not an application that answers strangely.
17
+ //
18
+ // Server-only, like the two modules that import it. Nothing the browser loads
19
+ // reaches this.
20
+
21
+ import { insideRequest } from "@uniflowed/server/host";
22
+
23
+ /**
24
+ * Refuse to run outside a request, naming what has to establish one.
25
+ *
26
+ * The message is addressed to whoever wired the host, because that is who can
27
+ * fix it. Left to fail on its own, a mis-wired host would surface as the first
28
+ * `cookies()` in somebody's guard throwing "called outside a request … a
29
+ * static prerender, a module's top level, or a client component" — three
30
+ * places to look, none of them this one — and an application that calls no
31
+ * server function at all would sail past that and lose every `after()` instead.
32
+ */
33
+ export function requireRequest(entry: string): void {
34
+ if (insideRequest()) {
35
+ return;
36
+ }
37
+ throw new Error(
38
+ `@uniflowed/router: ${entry}() was called outside a request. A host owns the request: ` +
39
+ "begin it with `beginRequest` from `@uniflowed/server/host` (a bundled application " +
40
+ "re-exports it from `virtual:uf/server`), run the whole request inside `run`, and call " +
41
+ "`settle` once the response has been sent. See ubugeeei-prod/uf#389.",
42
+ );
43
+ }