@rsc-kit/core 0.30.38 → 0.30.42

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 (86) hide show
  1. package/dist/cache.d.ts +79 -2
  2. package/dist/cache.js +151 -5
  3. package/dist/cache.js.map +1 -1
  4. package/dist/changed.js +2 -1
  5. package/dist/changed.js.map +1 -1
  6. package/dist/conformance.d.ts +1 -0
  7. package/dist/conformance.js +27 -0
  8. package/dist/conformance.js.map +1 -1
  9. package/dist/flash.d.ts +18 -0
  10. package/dist/flash.js +29 -0
  11. package/dist/flash.js.map +1 -0
  12. package/dist/flashCookie.d.ts +17 -0
  13. package/dist/flashCookie.js +36 -0
  14. package/dist/flashCookie.js.map +1 -0
  15. package/dist/host.d.ts +28 -17
  16. package/dist/host.js +109 -31
  17. package/dist/host.js.map +1 -1
  18. package/dist/hostCalls.js +27 -11
  19. package/dist/hostCalls.js.map +1 -1
  20. package/dist/js/ActivityRouter.js +3 -3
  21. package/dist/js/ActivityRouter.js.map +1 -1
  22. package/dist/js/Changes.js +1 -1
  23. package/dist/js/Changes.js.map +1 -1
  24. package/dist/js/DefaultRouteError.d.ts +1 -1
  25. package/dist/js/DefaultRouteError.js.map +1 -1
  26. package/dist/js/Form.d.ts +2 -2
  27. package/dist/js/Form.js +5 -5
  28. package/dist/js/Form.js.map +1 -1
  29. package/dist/js/Link.js +2 -2
  30. package/dist/js/Link.js.map +1 -1
  31. package/dist/js/RedirectBoundary.js +2 -2
  32. package/dist/js/RedirectBoundary.js.map +1 -1
  33. package/dist/js/SegmentBoundary.js +5 -5
  34. package/dist/js/SegmentBoundary.js.map +1 -1
  35. package/dist/js/SlotBoundary.js +3 -3
  36. package/dist/js/SlotBoundary.js.map +1 -1
  37. package/dist/js/changesStore.js +1 -1
  38. package/dist/js/changesStore.js.map +1 -1
  39. package/dist/js/createViteRscApp.js +16 -17
  40. package/dist/js/createViteRscApp.js.map +1 -1
  41. package/dist/js/errors.d.ts +3 -1
  42. package/dist/js/errors.js +5 -2
  43. package/dist/js/errors.js.map +1 -1
  44. package/dist/js/navigate.d.ts +17 -2
  45. package/dist/js/navigate.js +50 -19
  46. package/dist/js/navigate.js.map +1 -1
  47. package/dist/js/nuqs.js +2 -2
  48. package/dist/js/nuqs.js.map +1 -1
  49. package/dist/js/pagePayload.js +2 -2
  50. package/dist/js/pagePayload.js.map +1 -1
  51. package/dist/js/payloadKey.d.ts +32 -0
  52. package/dist/js/payloadKey.js +61 -0
  53. package/dist/js/payloadKey.js.map +1 -0
  54. package/dist/js/refreshOn.js +3 -3
  55. package/dist/js/refreshOn.js.map +1 -1
  56. package/dist/js/section.d.ts +4 -4
  57. package/dist/js/section.js +2 -2
  58. package/dist/js/section.js.map +1 -1
  59. package/dist/js/updateStore.js +6 -2
  60. package/dist/js/updateStore.js.map +1 -1
  61. package/dist/js/useAppUpdate.js +1 -1
  62. package/dist/js/useAppUpdate.js.map +1 -1
  63. package/dist/js/useFlash.d.ts +16 -0
  64. package/dist/js/useFlash.js +56 -0
  65. package/dist/js/useFlash.js.map +1 -0
  66. package/dist/js/useOnline.js +1 -1
  67. package/dist/js/useOnline.js.map +1 -1
  68. package/dist/js/usePathname.js +1 -1
  69. package/dist/js/usePathname.js.map +1 -1
  70. package/dist/manifest.d.ts +7 -0
  71. package/dist/manifest.js.map +1 -1
  72. package/dist/pageCache.d.ts +47 -0
  73. package/dist/pageCache.js +177 -0
  74. package/dist/pageCache.js.map +1 -0
  75. package/dist/prerender.js +4 -0
  76. package/dist/prerender.js.map +1 -1
  77. package/dist/request.d.ts +36 -0
  78. package/dist/request.js +80 -0
  79. package/dist/request.js.map +1 -1
  80. package/dist/testing.d.ts +9 -0
  81. package/dist/testing.js +14 -2
  82. package/dist/testing.js.map +1 -1
  83. package/dist/vite.d.ts +27 -0
  84. package/dist/vite.js +120 -14
  85. package/dist/vite.js.map +1 -1
  86. package/package.json +11 -3
package/dist/hostCalls.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // function, the deferred-release ordering in streaming.ts still applies, and a
11
11
  // reply may still carry `revalidate` — a Go host that answers this endpoint
12
12
  // gets the same semantics the socket has, without the framing.
13
- import { headers as incomingHeaders, passSetCookies } from './request.js';
13
+ import { headers as incomingHeaders, passSetCookies, renderingAsNobody } from './request.js';
14
14
  import { revalidate } from './revalidate.js';
15
15
  import { ActionRefusal, ActionValidationError } from './action.js';
16
16
  import { redirect } from './redirect.js';
@@ -59,6 +59,11 @@ export function httpHostCalls(options) {
59
59
  'content-type': 'application/json',
60
60
  'x-rsc-host-secret': secret,
61
61
  };
62
+ // A page a shared cache may keep renders as nobody: no cookie, no
63
+ // authorization, so what the backend answers is the same for everyone.
64
+ // Asked before reading the request, so the read is not counted as one.
65
+ if (renderingAsNobody())
66
+ return headers;
62
67
  // Outside a render — a prerender pass, or a host calling one directly —
63
68
  // there is no request to forward from, and that is not an error. The call
64
69
  // simply carries no session, which is exactly what a build-time render
@@ -284,7 +289,9 @@ export function httpHostCalls(options) {
284
289
  function interpret(name, { status, reply, text, setCookies }) {
285
290
  // First, whatever the outcome: a login that then redirects still logged
286
291
  // the visitor in. In the caller's context, where the page's response is.
287
- if (setCookies?.length)
292
+ // Dropped as nobody: a cookie on a page kept for everyone is handed to
293
+ // everyone - and its Set-Cookie alone would make the page private.
294
+ if (setCookies?.length && !renderingAsNobody())
288
295
  passSetCookies(setCookies);
289
296
  // Refusing the input is not the call failing — it is the call answering.
290
297
  //
@@ -313,7 +320,13 @@ export function httpHostCalls(options) {
313
320
  // Raised as the engine's own redirect, so it travels the path every other
314
321
  // redirect travels — a real 3xx above a Suspense boundary, the digest
315
322
  // below one — rather than becoming an error the page has to interpret.
323
+ //
324
+ // What it revalidated is marked first: a backend that switched the team
325
+ // and redirected - Rsc::revalidate('all') then redirect() in PHP - goes
326
+ // to a page whose layout reads the team, and the redirect carries the
327
+ // revalidation to the client only if it was marked before the throw.
316
328
  if (reply?.redirect) {
329
+ markRevalidated(reply.revalidate);
317
330
  redirect(reply.redirect, reply.redirectStatus ?? 307);
318
331
  }
319
332
  if (reply?.unauthenticated) {
@@ -352,17 +365,20 @@ export function httpHostCalls(options) {
352
365
  if (reply === null) {
353
366
  throw new Error(`Host call ${JSON.stringify(name)} returned a body that is not JSON: ${text.slice(0, 200)}`);
354
367
  }
355
- if (reply.revalidate?.length) {
356
- if (onRevalidate)
357
- onRevalidate(reply.revalidate);
358
- // Named by the host over the wire - Rsc::revalidate('orders') in PHP -
359
- // so nothing here can check it; the renderer refuses an unknown name.
360
- else
361
- for (const target of reply.revalidate)
362
- revalidate(target);
363
- }
368
+ markRevalidated(reply.revalidate);
364
369
  return reply.result ?? null;
365
370
  }
371
+ function markRevalidated(targets) {
372
+ if (!targets?.length)
373
+ return;
374
+ if (onRevalidate)
375
+ onRevalidate(targets);
376
+ // Named by the host over the wire - Rsc::revalidate('orders') in PHP -
377
+ // so nothing here can check it; the renderer refuses an unknown name.
378
+ else
379
+ for (const target of targets)
380
+ revalidate(target);
381
+ }
366
382
  return async function hostCall(name, ...args) {
367
383
  const headers = await forwardedHeaders();
368
384
  const settled = await enqueue(name, args, headers);
@@ -1 +1 @@
1
- {"version":3,"file":"hostCalls.js","sourceRoot":"","sources":["../src/hostCalls.ts"],"names":[],"mappings":"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","sourcesContent":["// Host calls over HTTP, so a host in any language can answer them.\n//\n// A server component writing `await rpc('Orders.recent')` needs the host\n// mid-render. The Laravel path answers on a second unix socket with its own\n// framing (PROTOCOL.md, \"The callback channel\"), which every non-JS host has\n// to reimplement before it can render a single page that fetches anything.\n// This is the same conversation over an ordinary POST.\n//\n// The transport is the only thing that changes. The engine still sees one\n// function, the deferred-release ordering in streaming.ts still applies, and a\n// reply may still carry `revalidate` — a Go host that answers this endpoint\n// gets the same semantics the socket has, without the framing.\n\nimport { headers as incomingHeaders, passSetCookies } from './request.js'\nimport { revalidate } from './revalidate.js'\nimport type { RevalidateTarget } from './routes.js'\nimport { ActionRefusal, ActionValidationError } from './action.js'\nimport { redirect } from './redirect.js'\nimport { ServerAuthenticationError, ServerAuthorizationError } from './js/errors.js'\nimport { NotFoundSignal } from './notFound.js'\n\nexport interface HttpHostCallsOptions {\n /**\n * Where calls are POSTed. A loopback address or a unix socket url — see the\n * note on `secret` for why this must not be a public one.\n */\n endpoint: string\n /**\n * Sent as `X-RSC-Host-Secret`, and required rather than optional.\n *\n * This endpoint runs functions by name on behalf of a render. PROTOCOL.md\n * Part 3b draws the line at client-supplied input deciding what RUNS, and an\n * unauthenticated callback endpoint is on the wrong side of it: anyone who\n * can reach it can invoke any registered host function directly, with\n * whatever arguments they like and none of the app's routing in front. A\n * default of \"off\" would be the kind that ships.\n */\n secret: string\n /**\n * Request headers copied from the render's own request onto the call.\n *\n * This is what makes a host call run as the person browsing: the backend's\n * session middleware reads its cookie and finds the same user. Nothing else\n * is forwarded, because everything else is either meaningless to the backend\n * or actively wrong — `content-length` and `content-type` describe this POST,\n * not the page request.\n */\n forwardHeaders?: string[]\n /** Defaults to 30s. A render blocked on a host that never answers is a hung request. */\n timeoutMs?: number\n /** Injectable for tests and for a runtime whose fetch is not global. */\n fetch?: typeof fetch\n /**\n * What to do with whatever a reply reported as invalidated.\n *\n * Defaults to marking it on the engine, which is the only thing that makes\n * `Revalidate(ctx, \"orders\")` on the host side mean anything: an action's\n * answer carries the re-rendered region only if the target reached the\n * revalidation scope before the action returned. Without it a host reports\n * what it dirtied, nothing listens, and the browser is told nothing — the\n * page simply shows stale data with no error anywhere.\n */\n onRevalidate?: (targets: string[]) => void\n}\n\nconst DEFAULT_FORWARDED = ['cookie', 'authorization']\n\nexport interface HostCallReply {\n result?: unknown\n error?: string\n revalidate?: string[]\n /**\n * Field name to messages, when the host refused the input.\n *\n * The same shape everything else here already uses: Laravel's own\n * `$e->errors()`, the socket protocol's `validation_errors`, and what\n * `issuesToErrors` turns a Standard Schema result into. Dot-joined for a\n * nested field, the empty string for a message about the form rather than\n * any one field.\n */\n validationErrors?: Record<string, string[]>\n /** The caller has no session. Becomes the engine's own authentication error. */\n unauthenticated?: boolean\n /** The caller has a session and still may not. */\n unauthorized?: boolean\n /**\n * Where the host says this request should go instead.\n *\n * Answered with a 200 and this field, never as a 3xx: fetch follows a\n * redirect transparently, so a real one would send the host call itself to\n * the destination and hand whatever came back to the render as the\n * function's result.\n */\n redirect?: string\n /**\n * Where an unexpected failure happened in the backend, sent only in\n * development (Laravel's app.debug, Go's CallbackHandler.Debug): the\n * thrown type, its message, and frames newest first.\n */\n debug?: { type?: string; message?: string; trace?: string[] }\n /** The status to redirect with. Defaults to 307, which preserves the method. */\n redirectStatus?: number\n /**\n * The status a refusal should be answered with.\n *\n * A middleware that aborted meant what it aborted with — throttle answers\n * 429, a signed-url check 403 — and collapsing those to 500 makes a\n * rate-limited visitor indistinguishable from a broken server.\n */\n refusalStatus?: number\n /**\n * What a deliberate refusal carries beside its message (`error`): the\n * records blocking a delete, say. Raised as the action client's own\n * refusal, so an action passes it to the page as `result.refusal`, checked\n * against the schema the action declared - see `refuse()`.\n */\n refusalData?: unknown\n}\n\n/**\n * A batch on the wire: several calls in one POST, answered in order.\n *\n * Calls issued in the same tick of a render - sibling components each\n * awaiting rpc() - travel together, so a page's parallel reads cost the host\n * one request rather than one each. A host that has never heard of the\n * envelope answers it as a malformed single call, and the calls are sent one\n * at a time from then on; nothing is lost but the saving.\n */\nexport interface HostCallBatch {\n calls: { function: string; args: unknown[] }[]\n}\n\nexport interface HostCallBatchReply {\n /** One per call, in order. Each carries the status that call would have had. */\n replies: (HostCallReply & { status?: number })[]\n}\n\n/**\n * A batch answered as it goes: one line per call, each as the host finishes\n * it, in whatever order that is.\n *\n * `application/x-ndjson`, each line a reply with the call's `index` in the\n * batch. A host that answers this way lets a fast call resolve while a slow\n * sibling is still running - which is what keeps a page's boundaries\n * streaming independently when their reads travelled together. A host that\n * answers the whole batch at once, as one JSON object of `replies`, is read\n * as before; the saving of the batch stays, the independence does not.\n */\nexport type HostCallBatchLine = HostCallReply & { index: number; status?: number }\n\n/** How many calls one POST carries at most. */\nconst BATCH_LIMIT = 50\n\ntype Settled = { status: number; reply: HostCallReply | null; text: string; setCookies?: string[] }\n\ninterface Pending {\n name: string\n args: unknown[]\n resolve: (settled: Settled) => void\n reject: (error: unknown) => void\n}\n\ninterface Bucket {\n headers: Record<string, string>\n calls: Pending[]\n}\n\n// After the current I/O, before the next timer: what a render issued\n// synchronously - and across the microtasks between one component's awaits -\n// is in the bucket by then. setImmediate where the runtime has it, a zero\n// timer where it does not (a Worker).\nconst nextTick: (fn: () => void) => void =\n typeof setImmediate === 'function' ? (fn) => setImmediate(fn) : (fn) => setTimeout(fn, 0)\n\n/**\n * The function to hand to `installHostFn`, or to `hostCalls` on the JS host.\n */\n/**\n * The backend's trace, on the error the render sees.\n *\n * As its cause - an Error named for the backend's type, whose stack is the\n * backend's frames - so Node, Bun and the dev overlay print it under the\n * renderer's own stack; and appended to that stack, for whatever prints only\n * the one. Before this a failed rpc() pointed at a line of JavaScript, and the\n * PHP or Go that failed was a separate search through another log.\n */\nfunction withBackendTrace(failure: Error, debug: NonNullable<HostCallReply['debug']>): void {\n const frames = (debug.trace ?? []).map((frame) => ' at ' + frame).join('\\n')\n const cause = new Error(debug.message ?? '')\n\n cause.name = 'Backend ' + (debug.type ?? 'Error')\n cause.stack = cause.name + ': ' + (debug.message ?? '') + (frames ? '\\n' + frames : '')\n\n failure.cause = cause\n failure.stack = (failure.stack ?? String(failure)) + '\\nCaused in the backend by ' + cause.stack\n}\n\nexport function httpHostCalls(\n options: HttpHostCallsOptions,\n): (name: string, ...args: unknown[]) => Promise<unknown> {\n const {\n endpoint,\n secret,\n forwardHeaders = DEFAULT_FORWARDED,\n timeoutMs = 30_000,\n fetch: fetchImpl,\n onRevalidate,\n } = options\n\n if (!secret) {\n throw new Error('httpHostCalls requires a secret — see the note on the option.')\n }\n\n const forwarded = forwardHeaders.map((name) => name.toLowerCase())\n\n // One bucket per set of forwarded headers: two visitors' renders in flight\n // at once must not share a request, because the host reads the cookie off\n // the request to know whose session a call runs under.\n const buckets = new Map<string, Bucket>()\n\n async function forwardedHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n 'x-rsc-host-secret': secret,\n }\n\n // Outside a render — a prerender pass, or a host calling one directly —\n // there is no request to forward from, and that is not an error. The call\n // simply carries no session, which is exactly what a build-time render\n // should be doing.\n //\n // Only that one condition is swallowed. A blanket catch here is the wrong\n // shape: anything else going wrong while reading the request would come\n // out as a call with no session, which is not a failure the caller sees —\n // it is the visitor silently becoming anonymous, and the page rendering as\n // though they were logged out.\n try {\n const from = await incomingHeaders()\n\n for (const key of forwarded) {\n const value = from.get(key)\n if (value !== null) headers[key] = value\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n\n if (!message.startsWith('No request in scope')) throw error\n }\n\n return headers\n }\n\n /** One POST. `label` names what is being sent, for the error messages. */\n async function post(\n body: unknown,\n headers: Record<string, string>,\n label: string,\n ): Promise<{ status: number; text: string; setCookies: string[] }> {\n const response = await send(body, headers, label)\n\n // Read the body before branching on status: a host that reports the error\n // in JSON with a 500 is saying something more useful than \"500\", and\n // throwing on the status alone discards it.\n return {\n status: response.status,\n text: await response.text(),\n // A login's session cookie: kept to put on the page's response.\n setCookies: response.headers.getSetCookie?.() ?? [],\n }\n }\n\n /** The POST itself, headers in hand, body still to read. */\n async function send(body: unknown, headers: Record<string, string>, label: string): Promise<Response> {\n const doFetch = fetchImpl ?? globalThis.fetch\n\n // AbortSignal.timeout is not on every runtime this engine targets, so the\n // controller is written out rather than assumed.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n\n let response: Response\n\n try {\n response = await doFetch(endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n } catch (error) {\n if (controller.signal.aborted) {\n throw new Error(`Host call ${label} timed out after ${timeoutMs}ms`)\n }\n\n throw new Error(\n `Host call ${label} could not reach the host at ${endpoint}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n } finally {\n clearTimeout(timer)\n }\n\n return response\n }\n\n function parse(text: string): HostCallReply | null {\n try {\n return text ? (JSON.parse(text) as HostCallReply) : null\n } catch {\n return null\n }\n }\n\n async function single(name: string, args: unknown[], headers: Record<string, string>): Promise<Settled> {\n const { status, text, setCookies } = await post({ function: name, args }, headers, JSON.stringify(name))\n\n return { status, reply: parse(text), text, setCookies }\n }\n\n /** Read a streamed batch, one reply per line, resolving each call as its line lands. */\n async function resolveAsLinesArrive(body: ReadableStream<Uint8Array>, calls: Pending[]): Promise<void> {\n const answered = new Set<number>()\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffered = ''\n\n const take = (line: string) => {\n const trimmed = line.trim()\n\n if (!trimmed) return\n\n let parsed: Partial<HostCallBatchLine> | null\n\n try {\n parsed = JSON.parse(trimmed) as Partial<HostCallBatchLine>\n } catch {\n return\n }\n\n const index = parsed?.index\n\n if (typeof index !== 'number' || !calls[index] || answered.has(index)) return\n\n const { index: _, status: own, ...reply } = parsed as HostCallBatchLine\n\n answered.add(index)\n calls[index]!.resolve({ status: own ?? 200, reply, text: JSON.stringify(reply) })\n }\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n\n if (done) break\n\n buffered += decoder.decode(value, { stream: true })\n\n let at: number\n\n while ((at = buffered.indexOf('\\n')) !== -1) {\n take(buffered.slice(0, at))\n buffered = buffered.slice(at + 1)\n }\n }\n\n buffered += decoder.decode()\n take(buffered)\n } catch (error) {\n for (const [i, call] of calls.entries()) {\n if (!answered.has(i)) call.reject(error)\n }\n\n return\n }\n\n // The host closed the batch with a call unanswered: a fault on its side,\n // reported to the call rather than left pending forever.\n for (const [i, call] of calls.entries()) {\n if (!answered.has(i)) {\n call.reject(new Error(`Host call ${JSON.stringify(call.name)} was not answered in its batch`))\n }\n }\n }\n\n /**\n * Send a bucket. One call goes as itself, so a host that speaks only the\n * single shape - or a request that happened to be alone - sees nothing\n * new on the wire.\n */\n async function flush(bucket: Bucket): Promise<void> {\n const { headers, calls } = bucket\n\n if (calls.length === 1) {\n const [only] = calls\n\n try {\n only.resolve(await single(only.name, only.args, headers))\n } catch (error) {\n only.reject(error)\n }\n\n return\n }\n\n const label = `batch of ${calls.length} (${calls.map((c) => c.name).join(', ')})`\n let response: Response\n\n try {\n response = await send(\n { calls: calls.map((c) => ({ function: c.name, args: c.args })) } satisfies HostCallBatch,\n headers,\n label,\n )\n } catch (error) {\n // The host could not be reached, or did not answer in time. Not\n // re-sent one by one: the calls may have run, and an action run twice\n // is worse than one reported as failed.\n for (const call of calls) call.reject(error)\n\n return\n }\n\n // Answered as it goes: each call resolves the moment its line arrives,\n // so a component waiting on a fast read paints while a slow sibling's\n // is still running. The batch saved the round trips; this keeps the\n // streaming the batch would otherwise have cost.\n if (response.status < 400 && (response.headers.get('content-type') ?? '').includes('x-ndjson') && response.body) {\n await resolveAsLinesArrive(response.body, calls)\n\n return\n }\n\n const status = response.status\n const text = await response.text()\n const parsed = parse(text) as Partial<HostCallBatchReply> | null\n const replies = parsed?.replies\n\n if (status < 400 && Array.isArray(replies) && replies.length === calls.length) {\n calls.forEach((call, i) => {\n const { status: own, ...reply } = replies[i]!\n\n call.resolve({ status: own ?? 200, reply, text: JSON.stringify(reply) })\n })\n\n return\n }\n\n // Not a batch answer. Taking batches is part of the protocol - the\n // conformance suite checks it - so this is a backend that does not, or\n // one that failed before reading the calls: wrong secret, down, too many.\n // Every call fails with what the backend said, and none is sent again:\n // they may have run, and an action run twice is worse than one reported.\n const said = parsed && typeof (parsed as HostCallReply).error === 'string' ? (parsed as HostCallReply).error : text.slice(0, 200)\n const failure = new Error(\n `Host call ${label} was not answered as a batch (${status}${said ? `: ${said}` : ''}). ` +\n 'A backend answers { \"calls\": [...] } line by line or as { \"replies\": [...] }: ' +\n 'https://docs.rsc-kit.dev/hosts/your-own-backend#batches',\n )\n\n for (const call of calls) call.reject(failure)\n }\n\n function enqueue(name: string, args: unknown[], headers: Record<string, string>): Promise<Settled> {\n return new Promise<Settled>((resolve, reject) => {\n const key = JSON.stringify(headers)\n let bucket = buckets.get(key)\n\n if (!bucket) {\n bucket = { headers, calls: [] }\n buckets.set(key, bucket)\n\n const mine = bucket\n\n nextTick(() => {\n if (buckets.get(key) === mine) buckets.delete(key)\n\n void flush(mine)\n })\n }\n\n bucket.calls.push({ name, args, resolve, reject })\n\n if (bucket.calls.length >= BATCH_LIMIT) {\n buckets.delete(key)\n\n void flush(bucket)\n }\n })\n }\n\n /**\n * What a reply means, in the caller's own async context.\n *\n * Deliberately not done where the response arrives: a batch is answered in\n * a timer, outside every caller's request scope, and `redirect()` and\n * `revalidate()` both write to that scope. Interpreting here, after the\n * caller's await resumed, puts each reply back inside the render it belongs\n * to.\n */\n function interpret(name: string, { status, reply, text, setCookies }: Settled): unknown {\n // First, whatever the outcome: a login that then redirects still logged\n // the visitor in. In the caller's context, where the page's response is.\n if (setCookies?.length) passSetCookies(setCookies)\n\n // Refusing the input is not the call failing — it is the call answering.\n //\n // Thrown rather than returned, so a handler stops where it is instead of\n // carrying on with data the host rejected. createActionClient catches this\n // on the way out and returns { validationErrors }, which is the shape\n // useForm reads: React serialises a REJECTED server action opaquely, so a\n // validation error that stays thrown reaches the browser as \"an error\n // occurred\" with every field it named gone.\n //\n // Checked before `error`, so a host that sends both is read as a refusal\n // rather than as a failure with no fields.\n //\n // One error for both places a call is made. In an action, the mark is\n // what returns the fields to the form. In a page's render there is no\n // form: refusalStatus makes the page answer 422 rather than 500, and the\n // message says which call refused which fields, where \"Validation failed\"\n // read like an action had run.\n if (reply?.validationErrors) {\n const refused = new ActionValidationError(reply.validationErrors)\n const fields = Object.keys(reply.validationErrors).map((field) => field || '(the input as a whole)')\n\n refused.message = `Host call ${JSON.stringify(name)} refused its input: ${fields.join(', ')}`\n ;(refused as ActionValidationError & { refusalStatus?: number }).refusalStatus = 422\n\n throw refused\n }\n\n // Raised as the engine's own redirect, so it travels the path every other\n // redirect travels — a real 3xx above a Suspense boundary, the digest\n // below one — rather than becoming an error the page has to interpret.\n if (reply?.redirect) {\n redirect(reply.redirect as never, reply.redirectStatus ?? 307)\n }\n\n if (reply?.unauthenticated) {\n throw new ServerAuthenticationError(reply.error ?? 'Unauthenticated.')\n }\n\n if (reply?.unauthorized) {\n throw new ServerAuthorizationError(reply.error ?? 'This action is unauthorized.')\n }\n\n // The backend says there is no such thing - Go's Refuse(404), Laravel's\n // abort(404), a record this caller cannot see. Raised as the engine's\n // own notFound(), so the page answers with not-found.tsx and a 404,\n // exactly as a component calling notFound() would; as an error it\n // reached the page as \"something went wrong\". The backend's message rides\n // on it for an action, which has no not-found page and shows the message.\n if (reply?.refusalStatus === 404) throw new NotFoundSignal(reply.error ?? 'Not found')\n\n // A status is what makes a reply a refusal rather than a failure: the\n // backend's refuse(), Go's Refuse, Laravel's abort(). Its message was\n // written for the person asking - Laravel shows an HttpException's in its\n // own production JSON - so it is raised as one, with or without data, and\n // onError never replaces it. Only refusals carried data at first, and\n // Refuse(503, \"busy\") reached the form as \"Something went wrong.\".\n if (reply?.error !== undefined && (reply.refusalStatus !== undefined || reply.refusalData !== undefined)) {\n throw new ActionRefusal(reply.error, reply.refusalData, reply.refusalStatus ?? 409)\n }\n\n if (reply?.error !== undefined) {\n const failure = new Error(`Host call ${JSON.stringify(name)} failed: ${reply.error}`)\n\n if (reply.debug) withBackendTrace(failure, reply.debug)\n\n throw failure\n }\n\n if (status >= 400) {\n throw new Error(\n `Host call ${JSON.stringify(name)} failed: ${status}`.trim() +\n (text ? ` — ${text.slice(0, 200)}` : ''),\n )\n }\n\n if (reply === null) {\n throw new Error(\n `Host call ${JSON.stringify(name)} returned a body that is not JSON: ${text.slice(0, 200)}`,\n )\n }\n\n if (reply.revalidate?.length) {\n if (onRevalidate) onRevalidate(reply.revalidate)\n // Named by the host over the wire - Rsc::revalidate('orders') in PHP -\n // so nothing here can check it; the renderer refuses an unknown name.\n else for (const target of reply.revalidate) revalidate(target as RevalidateTarget)\n }\n\n return reply.result ?? null\n }\n\n return async function hostCall(name: string, ...args: unknown[]): Promise<unknown> {\n const headers = await forwardedHeaders()\n const settled = await enqueue(name, args, headers)\n\n return interpret(name, settled)\n }\n}\n"]}
1
+ {"version":3,"file":"hostCalls.js","sourceRoot":"","sources":["../src/hostCalls.ts"],"names":[],"mappings":"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","sourcesContent":["// Host calls over HTTP, so a host in any language can answer them.\n//\n// A server component writing `await rpc('Orders.recent')` needs the host\n// mid-render. The Laravel path answers on a second unix socket with its own\n// framing (PROTOCOL.md, \"The callback channel\"), which every non-JS host has\n// to reimplement before it can render a single page that fetches anything.\n// This is the same conversation over an ordinary POST.\n//\n// The transport is the only thing that changes. The engine still sees one\n// function, the deferred-release ordering in streaming.ts still applies, and a\n// reply may still carry `revalidate` — a Go host that answers this endpoint\n// gets the same semantics the socket has, without the framing.\n\nimport { headers as incomingHeaders, passSetCookies, renderingAsNobody } from './request.js'\nimport { revalidate } from './revalidate.js'\nimport type { RevalidateTarget } from './routes.js'\nimport { ActionRefusal, ActionValidationError } from './action.js'\nimport { redirect } from './redirect.js'\nimport { ServerAuthenticationError, ServerAuthorizationError } from './js/errors.js'\nimport { NotFoundSignal } from './notFound.js'\n\nexport interface HttpHostCallsOptions {\n /**\n * Where calls are POSTed. A loopback address or a unix socket url — see the\n * note on `secret` for why this must not be a public one.\n */\n endpoint: string\n /**\n * Sent as `X-RSC-Host-Secret`, and required rather than optional.\n *\n * This endpoint runs functions by name on behalf of a render. PROTOCOL.md\n * Part 3b draws the line at client-supplied input deciding what RUNS, and an\n * unauthenticated callback endpoint is on the wrong side of it: anyone who\n * can reach it can invoke any registered host function directly, with\n * whatever arguments they like and none of the app's routing in front. A\n * default of \"off\" would be the kind that ships.\n */\n secret: string\n /**\n * Request headers copied from the render's own request onto the call.\n *\n * This is what makes a host call run as the person browsing: the backend's\n * session middleware reads its cookie and finds the same user. Nothing else\n * is forwarded, because everything else is either meaningless to the backend\n * or actively wrong — `content-length` and `content-type` describe this POST,\n * not the page request.\n */\n forwardHeaders?: string[]\n /** Defaults to 30s. A render blocked on a host that never answers is a hung request. */\n timeoutMs?: number\n /** Injectable for tests and for a runtime whose fetch is not global. */\n fetch?: typeof fetch\n /**\n * What to do with whatever a reply reported as invalidated.\n *\n * Defaults to marking it on the engine, which is the only thing that makes\n * `Revalidate(ctx, \"orders\")` on the host side mean anything: an action's\n * answer carries the re-rendered region only if the target reached the\n * revalidation scope before the action returned. Without it a host reports\n * what it dirtied, nothing listens, and the browser is told nothing — the\n * page simply shows stale data with no error anywhere.\n */\n onRevalidate?: (targets: string[]) => void\n}\n\nconst DEFAULT_FORWARDED = ['cookie', 'authorization']\n\nexport interface HostCallReply {\n result?: unknown\n error?: string\n revalidate?: string[]\n /**\n * Field name to messages, when the host refused the input.\n *\n * The same shape everything else here already uses: Laravel's own\n * `$e->errors()`, the socket protocol's `validation_errors`, and what\n * `issuesToErrors` turns a Standard Schema result into. Dot-joined for a\n * nested field, the empty string for a message about the form rather than\n * any one field.\n */\n validationErrors?: Record<string, string[]>\n /** The caller has no session. Becomes the engine's own authentication error. */\n unauthenticated?: boolean\n /** The caller has a session and still may not. */\n unauthorized?: boolean\n /**\n * Where the host says this request should go instead.\n *\n * Answered with a 200 and this field, never as a 3xx: fetch follows a\n * redirect transparently, so a real one would send the host call itself to\n * the destination and hand whatever came back to the render as the\n * function's result.\n */\n redirect?: string\n /**\n * Where an unexpected failure happened in the backend, sent only in\n * development (Laravel's app.debug, Go's CallbackHandler.Debug): the\n * thrown type, its message, and frames newest first.\n */\n debug?: { type?: string; message?: string; trace?: string[] }\n /** The status to redirect with. Defaults to 307, which preserves the method. */\n redirectStatus?: number\n /**\n * The status a refusal should be answered with.\n *\n * A middleware that aborted meant what it aborted with — throttle answers\n * 429, a signed-url check 403 — and collapsing those to 500 makes a\n * rate-limited visitor indistinguishable from a broken server.\n */\n refusalStatus?: number\n /**\n * What a deliberate refusal carries beside its message (`error`): the\n * records blocking a delete, say. Raised as the action client's own\n * refusal, so an action passes it to the page as `result.refusal`, checked\n * against the schema the action declared - see `refuse()`.\n */\n refusalData?: unknown\n}\n\n/**\n * A batch on the wire: several calls in one POST, answered in order.\n *\n * Calls issued in the same tick of a render - sibling components each\n * awaiting rpc() - travel together, so a page's parallel reads cost the host\n * one request rather than one each. A host that has never heard of the\n * envelope answers it as a malformed single call, and the calls are sent one\n * at a time from then on; nothing is lost but the saving.\n */\nexport interface HostCallBatch {\n calls: { function: string; args: unknown[] }[]\n}\n\nexport interface HostCallBatchReply {\n /** One per call, in order. Each carries the status that call would have had. */\n replies: (HostCallReply & { status?: number })[]\n}\n\n/**\n * A batch answered as it goes: one line per call, each as the host finishes\n * it, in whatever order that is.\n *\n * `application/x-ndjson`, each line a reply with the call's `index` in the\n * batch. A host that answers this way lets a fast call resolve while a slow\n * sibling is still running - which is what keeps a page's boundaries\n * streaming independently when their reads travelled together. A host that\n * answers the whole batch at once, as one JSON object of `replies`, is read\n * as before; the saving of the batch stays, the independence does not.\n */\nexport type HostCallBatchLine = HostCallReply & { index: number; status?: number }\n\n/** How many calls one POST carries at most. */\nconst BATCH_LIMIT = 50\n\ntype Settled = { status: number; reply: HostCallReply | null; text: string; setCookies?: string[] }\n\ninterface Pending {\n name: string\n args: unknown[]\n resolve: (settled: Settled) => void\n reject: (error: unknown) => void\n}\n\ninterface Bucket {\n headers: Record<string, string>\n calls: Pending[]\n}\n\n// After the current I/O, before the next timer: what a render issued\n// synchronously - and across the microtasks between one component's awaits -\n// is in the bucket by then. setImmediate where the runtime has it, a zero\n// timer where it does not (a Worker).\nconst nextTick: (fn: () => void) => void =\n typeof setImmediate === 'function' ? (fn) => setImmediate(fn) : (fn) => setTimeout(fn, 0)\n\n/**\n * The function to hand to `installHostFn`, or to `hostCalls` on the JS host.\n */\n/**\n * The backend's trace, on the error the render sees.\n *\n * As its cause - an Error named for the backend's type, whose stack is the\n * backend's frames - so Node, Bun and the dev overlay print it under the\n * renderer's own stack; and appended to that stack, for whatever prints only\n * the one. Before this a failed rpc() pointed at a line of JavaScript, and the\n * PHP or Go that failed was a separate search through another log.\n */\nfunction withBackendTrace(failure: Error, debug: NonNullable<HostCallReply['debug']>): void {\n const frames = (debug.trace ?? []).map((frame) => ' at ' + frame).join('\\n')\n const cause = new Error(debug.message ?? '')\n\n cause.name = 'Backend ' + (debug.type ?? 'Error')\n cause.stack = cause.name + ': ' + (debug.message ?? '') + (frames ? '\\n' + frames : '')\n\n failure.cause = cause\n failure.stack = (failure.stack ?? String(failure)) + '\\nCaused in the backend by ' + cause.stack\n}\n\nexport function httpHostCalls(\n options: HttpHostCallsOptions,\n): (name: string, ...args: unknown[]) => Promise<unknown> {\n const {\n endpoint,\n secret,\n forwardHeaders = DEFAULT_FORWARDED,\n timeoutMs = 30_000,\n fetch: fetchImpl,\n onRevalidate,\n } = options\n\n if (!secret) {\n throw new Error('httpHostCalls requires a secret — see the note on the option.')\n }\n\n const forwarded = forwardHeaders.map((name) => name.toLowerCase())\n\n // One bucket per set of forwarded headers: two visitors' renders in flight\n // at once must not share a request, because the host reads the cookie off\n // the request to know whose session a call runs under.\n const buckets = new Map<string, Bucket>()\n\n async function forwardedHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n 'x-rsc-host-secret': secret,\n }\n\n // A page a shared cache may keep renders as nobody: no cookie, no\n // authorization, so what the backend answers is the same for everyone.\n // Asked before reading the request, so the read is not counted as one.\n if (renderingAsNobody()) return headers\n\n // Outside a render — a prerender pass, or a host calling one directly —\n // there is no request to forward from, and that is not an error. The call\n // simply carries no session, which is exactly what a build-time render\n // should be doing.\n //\n // Only that one condition is swallowed. A blanket catch here is the wrong\n // shape: anything else going wrong while reading the request would come\n // out as a call with no session, which is not a failure the caller sees —\n // it is the visitor silently becoming anonymous, and the page rendering as\n // though they were logged out.\n try {\n const from = await incomingHeaders()\n\n for (const key of forwarded) {\n const value = from.get(key)\n if (value !== null) headers[key] = value\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n\n if (!message.startsWith('No request in scope')) throw error\n }\n\n return headers\n }\n\n /** One POST. `label` names what is being sent, for the error messages. */\n async function post(\n body: unknown,\n headers: Record<string, string>,\n label: string,\n ): Promise<{ status: number; text: string; setCookies: string[] }> {\n const response = await send(body, headers, label)\n\n // Read the body before branching on status: a host that reports the error\n // in JSON with a 500 is saying something more useful than \"500\", and\n // throwing on the status alone discards it.\n return {\n status: response.status,\n text: await response.text(),\n // A login's session cookie: kept to put on the page's response.\n setCookies: response.headers.getSetCookie?.() ?? [],\n }\n }\n\n /** The POST itself, headers in hand, body still to read. */\n async function send(body: unknown, headers: Record<string, string>, label: string): Promise<Response> {\n const doFetch = fetchImpl ?? globalThis.fetch\n\n // AbortSignal.timeout is not on every runtime this engine targets, so the\n // controller is written out rather than assumed.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n\n let response: Response\n\n try {\n response = await doFetch(endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n } catch (error) {\n if (controller.signal.aborted) {\n throw new Error(`Host call ${label} timed out after ${timeoutMs}ms`)\n }\n\n throw new Error(\n `Host call ${label} could not reach the host at ${endpoint}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n } finally {\n clearTimeout(timer)\n }\n\n return response\n }\n\n function parse(text: string): HostCallReply | null {\n try {\n return text ? (JSON.parse(text) as HostCallReply) : null\n } catch {\n return null\n }\n }\n\n async function single(name: string, args: unknown[], headers: Record<string, string>): Promise<Settled> {\n const { status, text, setCookies } = await post({ function: name, args }, headers, JSON.stringify(name))\n\n return { status, reply: parse(text), text, setCookies }\n }\n\n /** Read a streamed batch, one reply per line, resolving each call as its line lands. */\n async function resolveAsLinesArrive(body: ReadableStream<Uint8Array>, calls: Pending[]): Promise<void> {\n const answered = new Set<number>()\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffered = ''\n\n const take = (line: string) => {\n const trimmed = line.trim()\n\n if (!trimmed) return\n\n let parsed: Partial<HostCallBatchLine> | null\n\n try {\n parsed = JSON.parse(trimmed) as Partial<HostCallBatchLine>\n } catch {\n return\n }\n\n const index = parsed?.index\n\n if (typeof index !== 'number' || !calls[index] || answered.has(index)) return\n\n const { index: _, status: own, ...reply } = parsed as HostCallBatchLine\n\n answered.add(index)\n calls[index]!.resolve({ status: own ?? 200, reply, text: JSON.stringify(reply) })\n }\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n\n if (done) break\n\n buffered += decoder.decode(value, { stream: true })\n\n let at: number\n\n while ((at = buffered.indexOf('\\n')) !== -1) {\n take(buffered.slice(0, at))\n buffered = buffered.slice(at + 1)\n }\n }\n\n buffered += decoder.decode()\n take(buffered)\n } catch (error) {\n for (const [i, call] of calls.entries()) {\n if (!answered.has(i)) call.reject(error)\n }\n\n return\n }\n\n // The host closed the batch with a call unanswered: a fault on its side,\n // reported to the call rather than left pending forever.\n for (const [i, call] of calls.entries()) {\n if (!answered.has(i)) {\n call.reject(new Error(`Host call ${JSON.stringify(call.name)} was not answered in its batch`))\n }\n }\n }\n\n /**\n * Send a bucket. One call goes as itself, so a host that speaks only the\n * single shape - or a request that happened to be alone - sees nothing\n * new on the wire.\n */\n async function flush(bucket: Bucket): Promise<void> {\n const { headers, calls } = bucket\n\n if (calls.length === 1) {\n const [only] = calls\n\n try {\n only.resolve(await single(only.name, only.args, headers))\n } catch (error) {\n only.reject(error)\n }\n\n return\n }\n\n const label = `batch of ${calls.length} (${calls.map((c) => c.name).join(', ')})`\n let response: Response\n\n try {\n response = await send(\n { calls: calls.map((c) => ({ function: c.name, args: c.args })) } satisfies HostCallBatch,\n headers,\n label,\n )\n } catch (error) {\n // The host could not be reached, or did not answer in time. Not\n // re-sent one by one: the calls may have run, and an action run twice\n // is worse than one reported as failed.\n for (const call of calls) call.reject(error)\n\n return\n }\n\n // Answered as it goes: each call resolves the moment its line arrives,\n // so a component waiting on a fast read paints while a slow sibling's\n // is still running. The batch saved the round trips; this keeps the\n // streaming the batch would otherwise have cost.\n if (response.status < 400 && (response.headers.get('content-type') ?? '').includes('x-ndjson') && response.body) {\n await resolveAsLinesArrive(response.body, calls)\n\n return\n }\n\n const status = response.status\n const text = await response.text()\n const parsed = parse(text) as Partial<HostCallBatchReply> | null\n const replies = parsed?.replies\n\n if (status < 400 && Array.isArray(replies) && replies.length === calls.length) {\n calls.forEach((call, i) => {\n const { status: own, ...reply } = replies[i]!\n\n call.resolve({ status: own ?? 200, reply, text: JSON.stringify(reply) })\n })\n\n return\n }\n\n // Not a batch answer. Taking batches is part of the protocol - the\n // conformance suite checks it - so this is a backend that does not, or\n // one that failed before reading the calls: wrong secret, down, too many.\n // Every call fails with what the backend said, and none is sent again:\n // they may have run, and an action run twice is worse than one reported.\n const said = parsed && typeof (parsed as HostCallReply).error === 'string' ? (parsed as HostCallReply).error : text.slice(0, 200)\n const failure = new Error(\n `Host call ${label} was not answered as a batch (${status}${said ? `: ${said}` : ''}). ` +\n 'A backend answers { \"calls\": [...] } line by line or as { \"replies\": [...] }: ' +\n 'https://docs.rsc-kit.dev/hosts/your-own-backend#batches',\n )\n\n for (const call of calls) call.reject(failure)\n }\n\n function enqueue(name: string, args: unknown[], headers: Record<string, string>): Promise<Settled> {\n return new Promise<Settled>((resolve, reject) => {\n const key = JSON.stringify(headers)\n let bucket = buckets.get(key)\n\n if (!bucket) {\n bucket = { headers, calls: [] }\n buckets.set(key, bucket)\n\n const mine = bucket\n\n nextTick(() => {\n if (buckets.get(key) === mine) buckets.delete(key)\n\n void flush(mine)\n })\n }\n\n bucket.calls.push({ name, args, resolve, reject })\n\n if (bucket.calls.length >= BATCH_LIMIT) {\n buckets.delete(key)\n\n void flush(bucket)\n }\n })\n }\n\n /**\n * What a reply means, in the caller's own async context.\n *\n * Deliberately not done where the response arrives: a batch is answered in\n * a timer, outside every caller's request scope, and `redirect()` and\n * `revalidate()` both write to that scope. Interpreting here, after the\n * caller's await resumed, puts each reply back inside the render it belongs\n * to.\n */\n function interpret(name: string, { status, reply, text, setCookies }: Settled): unknown {\n // First, whatever the outcome: a login that then redirects still logged\n // the visitor in. In the caller's context, where the page's response is.\n // Dropped as nobody: a cookie on a page kept for everyone is handed to\n // everyone - and its Set-Cookie alone would make the page private.\n if (setCookies?.length && !renderingAsNobody()) passSetCookies(setCookies)\n\n // Refusing the input is not the call failing — it is the call answering.\n //\n // Thrown rather than returned, so a handler stops where it is instead of\n // carrying on with data the host rejected. createActionClient catches this\n // on the way out and returns { validationErrors }, which is the shape\n // useForm reads: React serialises a REJECTED server action opaquely, so a\n // validation error that stays thrown reaches the browser as \"an error\n // occurred\" with every field it named gone.\n //\n // Checked before `error`, so a host that sends both is read as a refusal\n // rather than as a failure with no fields.\n //\n // One error for both places a call is made. In an action, the mark is\n // what returns the fields to the form. In a page's render there is no\n // form: refusalStatus makes the page answer 422 rather than 500, and the\n // message says which call refused which fields, where \"Validation failed\"\n // read like an action had run.\n if (reply?.validationErrors) {\n const refused = new ActionValidationError(reply.validationErrors)\n const fields = Object.keys(reply.validationErrors).map((field) => field || '(the input as a whole)')\n\n refused.message = `Host call ${JSON.stringify(name)} refused its input: ${fields.join(', ')}`\n ;(refused as ActionValidationError & { refusalStatus?: number }).refusalStatus = 422\n\n throw refused\n }\n\n // Raised as the engine's own redirect, so it travels the path every other\n // redirect travels — a real 3xx above a Suspense boundary, the digest\n // below one — rather than becoming an error the page has to interpret.\n //\n // What it revalidated is marked first: a backend that switched the team\n // and redirected - Rsc::revalidate('all') then redirect() in PHP - goes\n // to a page whose layout reads the team, and the redirect carries the\n // revalidation to the client only if it was marked before the throw.\n if (reply?.redirect) {\n markRevalidated(reply.revalidate)\n redirect(reply.redirect as never, reply.redirectStatus ?? 307)\n }\n\n if (reply?.unauthenticated) {\n throw new ServerAuthenticationError(reply.error ?? 'Unauthenticated.')\n }\n\n if (reply?.unauthorized) {\n throw new ServerAuthorizationError(reply.error ?? 'This action is unauthorized.')\n }\n\n // The backend says there is no such thing - Go's Refuse(404), Laravel's\n // abort(404), a record this caller cannot see. Raised as the engine's\n // own notFound(), so the page answers with not-found.tsx and a 404,\n // exactly as a component calling notFound() would; as an error it\n // reached the page as \"something went wrong\". The backend's message rides\n // on it for an action, which has no not-found page and shows the message.\n if (reply?.refusalStatus === 404) throw new NotFoundSignal(reply.error ?? 'Not found')\n\n // A status is what makes a reply a refusal rather than a failure: the\n // backend's refuse(), Go's Refuse, Laravel's abort(). Its message was\n // written for the person asking - Laravel shows an HttpException's in its\n // own production JSON - so it is raised as one, with or without data, and\n // onError never replaces it. Only refusals carried data at first, and\n // Refuse(503, \"busy\") reached the form as \"Something went wrong.\".\n if (reply?.error !== undefined && (reply.refusalStatus !== undefined || reply.refusalData !== undefined)) {\n throw new ActionRefusal(reply.error, reply.refusalData, reply.refusalStatus ?? 409)\n }\n\n if (reply?.error !== undefined) {\n const failure = new Error(`Host call ${JSON.stringify(name)} failed: ${reply.error}`)\n\n if (reply.debug) withBackendTrace(failure, reply.debug)\n\n throw failure\n }\n\n if (status >= 400) {\n throw new Error(\n `Host call ${JSON.stringify(name)} failed: ${status}`.trim() +\n (text ? ` — ${text.slice(0, 200)}` : ''),\n )\n }\n\n if (reply === null) {\n throw new Error(\n `Host call ${JSON.stringify(name)} returned a body that is not JSON: ${text.slice(0, 200)}`,\n )\n }\n\n markRevalidated(reply.revalidate)\n\n return reply.result ?? null\n }\n\n function markRevalidated(targets: string[] | undefined): void {\n if (!targets?.length) return\n if (onRevalidate) onRevalidate(targets)\n // Named by the host over the wire - Rsc::revalidate('orders') in PHP -\n // so nothing here can check it; the renderer refuses an unknown name.\n else for (const target of targets) revalidate(target as RevalidateTarget)\n }\n\n return async function hostCall(name: string, ...args: unknown[]): Promise<unknown> {\n const headers = await forwardedHeaders()\n const settled = await enqueue(name, args, headers)\n\n return interpret(name, settled)\n }\n}\n"]}
@@ -14,9 +14,9 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
14
14
  * visited entry is dropped past the limit.
15
15
  */
16
16
  import { Activity, useCallback, useEffect, useRef, useState, startTransition } from 'react';
17
- import { setNavigateHandler, setReplaceRootHandler, setRestoreHandler } from './navigate';
18
- import { navigationCommitted } from './perf';
19
- import { clearSegments, setSegment } from './segmentStore';
17
+ import { setNavigateHandler, setReplaceRootHandler, setRestoreHandler } from './navigate.js';
18
+ import { navigationCommitted } from './perf.js';
19
+ import { clearSegments, setSegment } from './segmentStore.js';
20
20
  /** Pages kept alive behind the visible one. Four covers ordinary back-and-forth. */
21
21
  export const DEFAULT_RETENTION = 4;
22
22
  export function useActivityRouter(initial, retention = DEFAULT_RETENTION) {
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityRouter.js","sourceRoot":"","sources":["../../src/js/ActivityRouter.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AAE3F,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AA4B1D,oFAAoF;AACpF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAElC,MAAM,UAAU,iBAAiB,CAC/B,OAAoB,EACpB,SAAS,GAAW,iBAAiB;IAErC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAEvD,8EAA8E;IAC9E,8EAA8E;IAC9E,4BAA4B;IAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAEhD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,IAAmB,EAAE,EAAE;QACjD,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,UAAU,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,qEAAqE;IACrE,2DAA2D;IAC3D,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,GAAW,EAAE,EAAE;QACd,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACd,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAA;QAEtD,OAAO,QAAQ,CAAC,OAAO,CAAA;IACzB,CAAC,EACD,CAAC,SAAS,CAAC,CACZ,CAAA;IAED,MAAM,IAAI,GAAG,WAAW,CACtB,CAAC,GAAW,EAAE,IAAe,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;QACvB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QAElG,MAAM,CAAC,CAAC,GAAG,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAClC,YAAY,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC,EACD,CAAC,MAAM,EAAE,KAAK,CAAC,CAChB,CAAA;IAED,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,IAAe,EAAE,EAAE;QAClB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAA;QAE1E,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,uEAAuE;QACvE,mEAAmE;QACnE,gEAAgE;QAChE,iEAAiE;QACjE,mEAAmE;QACnE,+CAA+C;QAC/C,eAAe,CAAC,GAAG,EAAE;YACnB,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClG,CAAC,CAAC,CAAA;IACJ,CAAC,EACD,CAAC,SAAS,EAAE,MAAM,CAAC,CACpB,CAAA;IAED,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,GAAW,EAAE,EAAE;QACd,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;YAAE,OAAO,KAAK,CAAA;QAExE,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,YAAY,CAAC,GAAG,CAAC,CAAA;QAEjB,OAAO,IAAI,CAAA;IACb,CAAC,EACD,CAAC,KAAK,CAAC,CACR,CAAA;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAA;QAE1E,MAAM,CAAC,IAAI,CAAC,CAAA;QACZ,QAAQ,CAAC,OAAO,GAAG,CAAC,SAAS,CAAC,CAAA;IAChC,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAA;IAEvB,MAAM,SAAS,GAAG,MAAM,CAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IACjF,SAAS,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAErD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,UAAU,EAAE,WAAW,EAAkD;IACtG,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAEhG,4EAA4E;IAC5E,SAAS,CAAC,GAAG,EAAE;QACb,mBAAmB,EAAE,CAAA;IACvB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;IAExB,SAAS,CAAC,GAAG,EAAE;QACb,kBAAkB,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;YAC7C,yEAAyE;YACzE,0DAA0D;YAC1D,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,UAAU,CAAC,YAAY,EAAE,GAAG,EAAE,IAAiB,CAAC,CAAA;gBAEhD,OAAM;YACR,CAAC;YAED,aAAa,EAAE,CAAA;YACf,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAiB,CAAC,CAAA;QACrC,CAAC,CAAC,CAAA;QAEF,iBAAiB,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/C,qBAAqB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAiB,CAAC,CAAC,CAAA;IACpE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,OAAO,CACL,4BACG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACtB,KAAC,QAAQ,IAAiB,IAAI,EAAE,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,YAC3E,KAAK,CAAC,IAAI,IADE,KAAK,CAAC,GAAG,CAEb,CACZ,CAAC,GACD,CACJ,CAAA;AACH,CAAC","sourcesContent":["/**\n * Keeps recently visited pages mounted but hidden, so going back restores them\n * rather than rebuilding them.\n *\n * Rendering each navigation with root.render() unmounts the previous tree, and\n * unmounting destroys client state: a half-typed form, an open disclosure, a\n * scrolled list. React's <Activity mode=\"hidden\"> hides a tree without\n * unmounting it — effects are torn down, but state survives — which is what\n * makes returning feel like the browser's own back button rather than a reload.\n *\n * Retention is bounded. Hidden trees keep their DOM, so an unbounded history\n * would grow the document for the length of the session; the least recently\n * visited entry is dropped past the limit.\n */\n\nimport { Activity, useCallback, useEffect, useRef, useState, startTransition } from 'react'\nimport type { ReactNode } from 'react'\nimport { setNavigateHandler, setReplaceRootHandler, setRestoreHandler } from './navigate'\nimport { navigationCommitted } from './perf'\nimport { clearSegments, setSegment } from './segmentStore'\n\nexport interface RouterEntry {\n /** Identifies the page: its URL, or the intercept variant of one. */\n key: string\n tree: ReactNode\n}\n\nexport interface ActivityRouterHandle {\n /** Show `tree` for `key`, retaining whatever was visible before. */\n show(key: string, tree: ReactNode): void\n /**\n * Give the visible page a new tree, in place: the same entry, the same\n * Activity, re-rendered. For a re-render of the whole document after an\n * action - revalidate(\"all\") - which is the page the visitor is on, not a\n * page they went to. show() under the current url made a NEW entry when\n * the visible one was keyed by the url the document loaded with, and\n * every partial navigation since had left that key alone: the whole app\n * remounted under a second Activity, the old one hidden behind it, and\n * the form's state - its \"added to cart\" - went with it.\n */\n replace(tree: ReactNode): void\n /** Reveal a retained page without refetching. False when not retained. */\n restore(key: string): boolean\n /** Drop every retained page but the visible one. */\n clear(): void\n}\n\n/** Pages kept alive behind the visible one. Four covers ordinary back-and-forth. */\nexport const DEFAULT_RETENTION = 4\n\nexport function useActivityRouter(\n initial: RouterEntry,\n retention: number = DEFAULT_RETENTION,\n): { entries: RouterEntry[]; activeKey: string; handle: ActivityRouterHandle } {\n const [entries, setEntries] = useState<RouterEntry[]>([initial])\n const [activeKey, setActiveKey] = useState(initial.key)\n\n // The router calls into this from outside React, where the values captured by\n // the last render may already be stale. Refs mirror state so those calls read\n // what is actually mounted.\n const entriesRef = useRef(entries)\n const orderRef = useRef<string[]>([initial.key])\n\n const commit = useCallback((next: RouterEntry[]) => {\n entriesRef.current = next\n setEntries(next)\n }, [])\n\n // Recency, most recent last. Retention is by visit order rather than\n // insertion, so bouncing between two pages evicts neither.\n const touch = useCallback(\n (key: string) => {\n const next = orderRef.current.filter((k) => k !== key)\n next.push(key)\n orderRef.current = next.slice(-Math.max(1, retention))\n\n return orderRef.current\n },\n [retention],\n )\n\n const show = useCallback(\n (key: string, tree: ReactNode) => {\n const kept = touch(key)\n const others = entriesRef.current.filter((entry) => entry.key !== key && kept.includes(entry.key))\n\n commit([...others, { key, tree }])\n setActiveKey(key)\n },\n [commit, touch],\n )\n\n const replace = useCallback(\n (tree: ReactNode) => {\n const active = entriesRef.current.find((entry) => entry.key === activeKey)\n\n if (!active) return\n\n // A transition, so a boundary in the new tree still waiting on its row\n // keeps what is on screen rather than showing its fallback - which\n // would unmount the form under it and take its state along. The\n // document's payload arrives with some rows still pending, and a\n // synchronous render showed the product's skeleton for a frame and\n // remounted the \"add to cart\" form beneath it.\n startTransition(() => {\n commit(entriesRef.current.map((entry) => (entry === active ? { key: entry.key, tree } : entry)))\n })\n },\n [activeKey, commit],\n )\n\n const restore = useCallback(\n (key: string) => {\n if (!entriesRef.current.some((entry) => entry.key === key)) return false\n\n touch(key)\n setActiveKey(key)\n\n return true\n },\n [touch],\n )\n\n const clear = useCallback(() => {\n const kept = entriesRef.current.filter((entry) => entry.key === activeKey)\n\n commit(kept)\n orderRef.current = [activeKey]\n }, [activeKey, commit])\n\n const handleRef = useRef<ActivityRouterHandle>({ show, replace, restore, clear })\n handleRef.current = { show, replace, restore, clear }\n\n return { entries, activeKey, handle: handleRef.current }\n}\n\n/**\n * Root of the hydrated app.\n *\n * Registers itself with the navigation engine so a navigation becomes a state\n * update here rather than a fresh root.render(), which is what allows the\n * previous page to stay mounted.\n */\nexport function ActivityRoot({ initialKey, initialTree }: { initialKey: string; initialTree: ReactNode }) {\n const { entries, activeKey, handle } = useActivityRouter({ key: initialKey, tree: initialTree })\n\n // A whole document shown, or a held page revealed: the navigation's commit.\n useEffect(() => {\n navigationCommitted()\n }, [activeKey, entries])\n\n useEffect(() => {\n setNavigateHandler((tree, key, segmentDepth) => {\n // A partial payload replaces one segment inside the page that is already\n // showing; only a whole document is a new page to retain.\n if (segmentDepth > 0) {\n setSegment(segmentDepth, key, tree as ReactNode)\n\n return\n }\n\n clearSegments()\n handle.show(key, tree as ReactNode)\n })\n\n setRestoreHandler((key) => handle.restore(key))\n setReplaceRootHandler((tree) => handle.replace(tree as ReactNode))\n }, [handle])\n\n return (\n <>\n {entries.map((entry) => (\n <Activity key={entry.key} mode={entry.key === activeKey ? 'visible' : 'hidden'}>\n {entry.tree}\n </Activity>\n ))}\n </>\n )\n}\n"]}
1
+ {"version":3,"file":"ActivityRouter.js","sourceRoot":"","sources":["../../src/js/ActivityRouter.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AAE3F,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AA4B7D,oFAAoF;AACpF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAElC,MAAM,UAAU,iBAAiB,CAC/B,OAAoB,EACpB,SAAS,GAAW,iBAAiB;IAErC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAEvD,8EAA8E;IAC9E,8EAA8E;IAC9E,4BAA4B;IAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAEhD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,IAAmB,EAAE,EAAE;QACjD,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,UAAU,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,qEAAqE;IACrE,2DAA2D;IAC3D,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,GAAW,EAAE,EAAE;QACd,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACd,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAA;QAEtD,OAAO,QAAQ,CAAC,OAAO,CAAA;IACzB,CAAC,EACD,CAAC,SAAS,CAAC,CACZ,CAAA;IAED,MAAM,IAAI,GAAG,WAAW,CACtB,CAAC,GAAW,EAAE,IAAe,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;QACvB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QAElG,MAAM,CAAC,CAAC,GAAG,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAClC,YAAY,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC,EACD,CAAC,MAAM,EAAE,KAAK,CAAC,CAChB,CAAA;IAED,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,IAAe,EAAE,EAAE;QAClB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAA;QAE1E,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,uEAAuE;QACvE,mEAAmE;QACnE,gEAAgE;QAChE,iEAAiE;QACjE,mEAAmE;QACnE,+CAA+C;QAC/C,eAAe,CAAC,GAAG,EAAE;YACnB,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClG,CAAC,CAAC,CAAA;IACJ,CAAC,EACD,CAAC,SAAS,EAAE,MAAM,CAAC,CACpB,CAAA;IAED,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,GAAW,EAAE,EAAE;QACd,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;YAAE,OAAO,KAAK,CAAA;QAExE,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,YAAY,CAAC,GAAG,CAAC,CAAA;QAEjB,OAAO,IAAI,CAAA;IACb,CAAC,EACD,CAAC,KAAK,CAAC,CACR,CAAA;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAA;QAE1E,MAAM,CAAC,IAAI,CAAC,CAAA;QACZ,QAAQ,CAAC,OAAO,GAAG,CAAC,SAAS,CAAC,CAAA;IAChC,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAA;IAEvB,MAAM,SAAS,GAAG,MAAM,CAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IACjF,SAAS,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAErD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,UAAU,EAAE,WAAW,EAAkD;IACtG,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAEhG,4EAA4E;IAC5E,SAAS,CAAC,GAAG,EAAE;QACb,mBAAmB,EAAE,CAAA;IACvB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;IAExB,SAAS,CAAC,GAAG,EAAE;QACb,kBAAkB,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;YAC7C,yEAAyE;YACzE,0DAA0D;YAC1D,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,UAAU,CAAC,YAAY,EAAE,GAAG,EAAE,IAAiB,CAAC,CAAA;gBAEhD,OAAM;YACR,CAAC;YAED,aAAa,EAAE,CAAA;YACf,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAiB,CAAC,CAAA;QACrC,CAAC,CAAC,CAAA;QAEF,iBAAiB,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/C,qBAAqB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAiB,CAAC,CAAC,CAAA;IACpE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,OAAO,CACL,4BACG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACtB,KAAC,QAAQ,IAAiB,IAAI,EAAE,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,YAC3E,KAAK,CAAC,IAAI,IADE,KAAK,CAAC,GAAG,CAEb,CACZ,CAAC,GACD,CACJ,CAAA;AACH,CAAC","sourcesContent":["/**\n * Keeps recently visited pages mounted but hidden, so going back restores them\n * rather than rebuilding them.\n *\n * Rendering each navigation with root.render() unmounts the previous tree, and\n * unmounting destroys client state: a half-typed form, an open disclosure, a\n * scrolled list. React's <Activity mode=\"hidden\"> hides a tree without\n * unmounting it — effects are torn down, but state survives — which is what\n * makes returning feel like the browser's own back button rather than a reload.\n *\n * Retention is bounded. Hidden trees keep their DOM, so an unbounded history\n * would grow the document for the length of the session; the least recently\n * visited entry is dropped past the limit.\n */\n\nimport { Activity, useCallback, useEffect, useRef, useState, startTransition } from 'react'\nimport type { ReactNode } from 'react'\nimport { setNavigateHandler, setReplaceRootHandler, setRestoreHandler } from './navigate.js'\nimport { navigationCommitted } from './perf.js'\nimport { clearSegments, setSegment } from './segmentStore.js'\n\nexport interface RouterEntry {\n /** Identifies the page: its URL, or the intercept variant of one. */\n key: string\n tree: ReactNode\n}\n\nexport interface ActivityRouterHandle {\n /** Show `tree` for `key`, retaining whatever was visible before. */\n show(key: string, tree: ReactNode): void\n /**\n * Give the visible page a new tree, in place: the same entry, the same\n * Activity, re-rendered. For a re-render of the whole document after an\n * action - revalidate(\"all\") - which is the page the visitor is on, not a\n * page they went to. show() under the current url made a NEW entry when\n * the visible one was keyed by the url the document loaded with, and\n * every partial navigation since had left that key alone: the whole app\n * remounted under a second Activity, the old one hidden behind it, and\n * the form's state - its \"added to cart\" - went with it.\n */\n replace(tree: ReactNode): void\n /** Reveal a retained page without refetching. False when not retained. */\n restore(key: string): boolean\n /** Drop every retained page but the visible one. */\n clear(): void\n}\n\n/** Pages kept alive behind the visible one. Four covers ordinary back-and-forth. */\nexport const DEFAULT_RETENTION = 4\n\nexport function useActivityRouter(\n initial: RouterEntry,\n retention: number = DEFAULT_RETENTION,\n): { entries: RouterEntry[]; activeKey: string; handle: ActivityRouterHandle } {\n const [entries, setEntries] = useState<RouterEntry[]>([initial])\n const [activeKey, setActiveKey] = useState(initial.key)\n\n // The router calls into this from outside React, where the values captured by\n // the last render may already be stale. Refs mirror state so those calls read\n // what is actually mounted.\n const entriesRef = useRef(entries)\n const orderRef = useRef<string[]>([initial.key])\n\n const commit = useCallback((next: RouterEntry[]) => {\n entriesRef.current = next\n setEntries(next)\n }, [])\n\n // Recency, most recent last. Retention is by visit order rather than\n // insertion, so bouncing between two pages evicts neither.\n const touch = useCallback(\n (key: string) => {\n const next = orderRef.current.filter((k) => k !== key)\n next.push(key)\n orderRef.current = next.slice(-Math.max(1, retention))\n\n return orderRef.current\n },\n [retention],\n )\n\n const show = useCallback(\n (key: string, tree: ReactNode) => {\n const kept = touch(key)\n const others = entriesRef.current.filter((entry) => entry.key !== key && kept.includes(entry.key))\n\n commit([...others, { key, tree }])\n setActiveKey(key)\n },\n [commit, touch],\n )\n\n const replace = useCallback(\n (tree: ReactNode) => {\n const active = entriesRef.current.find((entry) => entry.key === activeKey)\n\n if (!active) return\n\n // A transition, so a boundary in the new tree still waiting on its row\n // keeps what is on screen rather than showing its fallback - which\n // would unmount the form under it and take its state along. The\n // document's payload arrives with some rows still pending, and a\n // synchronous render showed the product's skeleton for a frame and\n // remounted the \"add to cart\" form beneath it.\n startTransition(() => {\n commit(entriesRef.current.map((entry) => (entry === active ? { key: entry.key, tree } : entry)))\n })\n },\n [activeKey, commit],\n )\n\n const restore = useCallback(\n (key: string) => {\n if (!entriesRef.current.some((entry) => entry.key === key)) return false\n\n touch(key)\n setActiveKey(key)\n\n return true\n },\n [touch],\n )\n\n const clear = useCallback(() => {\n const kept = entriesRef.current.filter((entry) => entry.key === activeKey)\n\n commit(kept)\n orderRef.current = [activeKey]\n }, [activeKey, commit])\n\n const handleRef = useRef<ActivityRouterHandle>({ show, replace, restore, clear })\n handleRef.current = { show, replace, restore, clear }\n\n return { entries, activeKey, handle: handleRef.current }\n}\n\n/**\n * Root of the hydrated app.\n *\n * Registers itself with the navigation engine so a navigation becomes a state\n * update here rather than a fresh root.render(), which is what allows the\n * previous page to stay mounted.\n */\nexport function ActivityRoot({ initialKey, initialTree }: { initialKey: string; initialTree: ReactNode }) {\n const { entries, activeKey, handle } = useActivityRouter({ key: initialKey, tree: initialTree })\n\n // A whole document shown, or a held page revealed: the navigation's commit.\n useEffect(() => {\n navigationCommitted()\n }, [activeKey, entries])\n\n useEffect(() => {\n setNavigateHandler((tree, key, segmentDepth) => {\n // A partial payload replaces one segment inside the page that is already\n // showing; only a whole document is a new page to retain.\n if (segmentDepth > 0) {\n setSegment(segmentDepth, key, tree as ReactNode)\n\n return\n }\n\n clearSegments()\n handle.show(key, tree as ReactNode)\n })\n\n setRestoreHandler((key) => handle.restore(key))\n setReplaceRootHandler((tree) => handle.replace(tree as ReactNode))\n }, [handle])\n\n return (\n <>\n {entries.map((entry) => (\n <Activity key={entry.key} mode={entry.key === activeKey ? 'visible' : 'hidden'}>\n {entry.tree}\n </Activity>\n ))}\n </>\n )\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useEffect } from "react";
3
- import { register } from "./changesStore";
3
+ import { register } from "./changesStore.js";
4
4
  /**
5
5
  * What a rendered region is live on, handed to the tab's watcher.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Changes.js","sourceRoot":"","sources":["../../src/js/Changes.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,EACN,KAAK,GAIN;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAElC,SAAS,CACP,GAAG,EAAE,CACH,QAAQ,CAAC;QACP,MAAM;QACN,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqC;KAC3D,CAAC,EACJ,CAAC,MAAM,EAAE,GAAG,CAAC,CACd,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { register } from \"./changesStore\";\n\n/**\n * What a rendered region is live on, handed to the tab's watcher.\n *\n * Renders nothing. Rendered by `RefreshOn` beside the region it describes,\n * so it arrives in the same payload - with the page, and again with the\n * region each time it is refreshed, carrying the versions that render saw.\n */\nexport function Changes({\n target,\n names,\n}: {\n target: string;\n names: Record<string, [number, string]>;\n}) {\n const key = JSON.stringify(names);\n\n useEffect(\n () =>\n register({\n target,\n names: JSON.parse(key) as Record<string, [number, string]>,\n }),\n [target, key],\n );\n\n return null;\n}\n"]}
1
+ {"version":3,"file":"Changes.js","sourceRoot":"","sources":["../../src/js/Changes.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,EACN,KAAK,GAIN;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAElC,SAAS,CACP,GAAG,EAAE,CACH,QAAQ,CAAC;QACP,MAAM;QACN,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqC;KAC3D,CAAC,EACJ,CAAC,MAAM,EAAE,GAAG,CAAC,CACd,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { register } from \"./changesStore.js\";\n\n/**\n * What a rendered region is live on, handed to the tab's watcher.\n *\n * Renders nothing. Rendered by `RefreshOn` beside the region it describes,\n * so it arrives in the same payload - with the page, and again with the\n * region each time it is refreshed, carrying the versions that render saw.\n */\nexport function Changes({\n target,\n names,\n}: {\n target: string;\n names: Record<string, [number, string]>;\n}) {\n const key = JSON.stringify(names);\n\n useEffect(\n () =>\n register({\n target,\n names: JSON.parse(key) as Record<string, [number, string]>,\n }),\n [target, key],\n );\n\n return null;\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  import { type ReactNode } from "react";
2
- import type { RouteErrorProps } from "./RouteErrorBoundary";
2
+ import type { RouteErrorProps } from "./RouteErrorBoundary.js";
3
3
  export declare function DefaultRouteError({ error, reset, }: RouteErrorProps): ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultRouteError.js","sourceRoot":"","sources":["../../src/js/DefaultRouteError.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n// What a page that throws shows when no error.tsx covers it.\n//\n// Without this, nothing did: the Flight row failed, the browser rethrew on\n// hydration with no boundary to catch it, and React unmounted the document -\n// a black page with the cause nowhere near it. Next shows its overlay in\n// development and a plain \"something went wrong\" in production; this is\n// that, as a page, and the layouts above it stay on screen.\n//\n// In development the message and stack, because the developer is the one\n// looking. In production React has already replaced the message with a\n// digest, and the digest is what this shows: the line to search the server\n// log for.\n\nimport { createElement, type ReactNode } from \"react\";\nimport type { RouteErrorProps } from \"./RouteErrorBoundary\";\n\n// `import.meta.env.DEV` as Vite spells it, so the build replaces the whole\n// expression with a literal. A `typeof import.meta` guard in front of it\n// survived the build as a bare `import.meta` in the ssr chunk - the one\n// thing Bun cannot lower when it compiles a binary's bytecode, so\n// `--bytecode` failed on this file - and guarded nothing: where import.meta\n// does not exist, a typeof of it does not parse either.\nconst DEV = Boolean(import.meta.env.DEV);\n\nconst mono = \"ui-monospace,SFMono-Regular,Menlo,monospace\";\nconst box = {\n maxWidth: \"60rem\",\n margin: \"3rem auto\",\n padding: \"1.5rem\",\n font: \"14px/1.5 \" + mono,\n color: \"#fecaca\",\n background: \"#1f0b0b\",\n border: \"1px solid #7f1d1d\",\n borderRadius: 8,\n};\nconst heading = {\n margin: \"0 0 .75rem\",\n fontSize: 16,\n fontWeight: 600,\n color: \"#fff\",\n};\nconst pre = {\n whiteSpace: \"pre-wrap\" as const,\n wordBreak: \"break-word\" as const,\n margin: \".75rem 0 0\",\n color: \"#fecaca\",\n};\nconst hint = { margin: \".75rem 0 0\", color: \"#fca5a5\" };\nconst button = {\n marginTop: \"1rem\",\n padding: \".4rem .8rem\",\n border: \"1px solid #fca5a5\",\n borderRadius: 6,\n background: \"transparent\",\n color: \"#fff\",\n cursor: \"pointer\",\n font: \"inherit\",\n};\n\nexport function DefaultRouteError({\n error,\n reset,\n}: RouteErrorProps): ReactNode {\n const name = error.name && error.name !== \"Error\" ? error.name + \": \" : \"\";\n\n return createElement(\n \"main\",\n { role: \"alert\", style: box, \"data-rsc-kit-error\": \"\" },\n createElement(\n \"h1\",\n { style: heading },\n DEV ? \"This page threw while rendering\" : \"Something went wrong\",\n ),\n DEV\n ? createElement(\n \"pre\",\n { style: pre },\n error.stack || name + error.message,\n )\n : createElement(\n \"p\",\n { style: { margin: 0 } },\n // A digest is a server error's: React replaced the message with it,\n // and the server log has the rest. Without one nothing reached the\n // server - a client reference the page could not load, say - and\n // the console is where it was reported.\n error.digest\n ? \"The error was logged on the server as \" + error.digest + \".\"\n : \"The error was logged in the browser console.\",\n ),\n DEV\n ? createElement(\n \"p\",\n { style: hint },\n \"Add an error.tsx beside the page to show something of your own here.\",\n )\n : null,\n createElement(\n \"button\",\n { type: \"button\", style: button, onClick: reset },\n \"Try again\",\n ),\n );\n}\n"]}
1
+ {"version":3,"file":"DefaultRouteError.js","sourceRoot":"","sources":["../../src/js/DefaultRouteError.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n// What a page that throws shows when no error.tsx covers it.\n//\n// Without this, nothing did: the Flight row failed, the browser rethrew on\n// hydration with no boundary to catch it, and React unmounted the document -\n// a black page with the cause nowhere near it. Next shows its overlay in\n// development and a plain \"something went wrong\" in production; this is\n// that, as a page, and the layouts above it stay on screen.\n//\n// In development the message and stack, because the developer is the one\n// looking. In production React has already replaced the message with a\n// digest, and the digest is what this shows: the line to search the server\n// log for.\n\nimport { createElement, type ReactNode } from \"react\";\nimport type { RouteErrorProps } from \"./RouteErrorBoundary.js\";\n\n// `import.meta.env.DEV` as Vite spells it, so the build replaces the whole\n// expression with a literal. A `typeof import.meta` guard in front of it\n// survived the build as a bare `import.meta` in the ssr chunk - the one\n// thing Bun cannot lower when it compiles a binary's bytecode, so\n// `--bytecode` failed on this file - and guarded nothing: where import.meta\n// does not exist, a typeof of it does not parse either.\nconst DEV = Boolean(import.meta.env.DEV);\n\nconst mono = \"ui-monospace,SFMono-Regular,Menlo,monospace\";\nconst box = {\n maxWidth: \"60rem\",\n margin: \"3rem auto\",\n padding: \"1.5rem\",\n font: \"14px/1.5 \" + mono,\n color: \"#fecaca\",\n background: \"#1f0b0b\",\n border: \"1px solid #7f1d1d\",\n borderRadius: 8,\n};\nconst heading = {\n margin: \"0 0 .75rem\",\n fontSize: 16,\n fontWeight: 600,\n color: \"#fff\",\n};\nconst pre = {\n whiteSpace: \"pre-wrap\" as const,\n wordBreak: \"break-word\" as const,\n margin: \".75rem 0 0\",\n color: \"#fecaca\",\n};\nconst hint = { margin: \".75rem 0 0\", color: \"#fca5a5\" };\nconst button = {\n marginTop: \"1rem\",\n padding: \".4rem .8rem\",\n border: \"1px solid #fca5a5\",\n borderRadius: 6,\n background: \"transparent\",\n color: \"#fff\",\n cursor: \"pointer\",\n font: \"inherit\",\n};\n\nexport function DefaultRouteError({\n error,\n reset,\n}: RouteErrorProps): ReactNode {\n const name = error.name && error.name !== \"Error\" ? error.name + \": \" : \"\";\n\n return createElement(\n \"main\",\n { role: \"alert\", style: box, \"data-rsc-kit-error\": \"\" },\n createElement(\n \"h1\",\n { style: heading },\n DEV ? \"This page threw while rendering\" : \"Something went wrong\",\n ),\n DEV\n ? createElement(\n \"pre\",\n { style: pre },\n error.stack || name + error.message,\n )\n : createElement(\n \"p\",\n { style: { margin: 0 } },\n // A digest is a server error's: React replaced the message with it,\n // and the server log has the rest. Without one nothing reached the\n // server - a client reference the page could not load, say - and\n // the console is where it was reported.\n error.digest\n ? \"The error was logged on the server as \" + error.digest + \".\"\n : \"The error was logged in the browser console.\",\n ),\n DEV\n ? createElement(\n \"p\",\n { style: hint },\n \"Add an error.tsx beside the page to show something of your own here.\",\n )\n : null,\n createElement(\n \"button\",\n { type: \"button\", style: button, onClick: reset },\n \"Try again\",\n ),\n );\n}\n"]}
package/dist/js/Form.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Route } from "../routes.js";
2
2
  import { type FormHTMLAttributes, type ReactNode, type Ref } from "react";
3
- import type { FormStore } from "./formStore";
4
- import type { StandardSchemaV1 } from "./standardSchema";
3
+ import type { FormStore } from "./formStore.js";
4
+ import type { StandardSchemaV1 } from "./standardSchema.js";
5
5
  type PrefetchStrategy = "hover" | "mount" | "none";
6
6
  /**
7
7
  * What `field(name)` hands a control, spread straight onto it.
package/dist/js/Form.js CHANGED
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useActionState, useState, useSyncExternalStore, useTransition, } from "react";
4
- import { submitForm } from "../formSubmit";
5
- import { ActionRefusedError, ServerValidationError, ServerDumpError, ServerRedirectError, redirectedTo } from "./errors";
6
- import { buildFormData, decodeFormData } from "./formEncoding";
7
- import { createFormStore } from "./formStore";
8
- import { validateWith } from "./standardSchema";
4
+ import { submitForm } from "../formSubmit.js";
5
+ import { ActionRefusedError, ServerValidationError, ServerDumpError, ServerRedirectError, redirectedTo } from "./errors.js";
6
+ import { buildFormData, decodeFormData } from "./formEncoding.js";
7
+ import { createFormStore } from "./formStore.js";
8
+ import { validateWith } from "./standardSchema.js";
9
9
  /**
10
10
  * How long a blur that leaves the form waits before checking the field. A
11
11
  * dialog's exit animation is shorter than this; a form still on screen after