@rsc-kit/core 0.30.1 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.js +32 -7
- package/dist/action.js.map +1 -1
- package/dist/conformance.js +3 -0
- package/dist/conformance.js.map +1 -1
- package/dist/host.d.ts +1 -0
- package/dist/host.js +1 -1
- package/dist/host.js.map +1 -1
- package/dist/hostCalls.js +11 -14
- package/dist/hostCalls.js.map +1 -1
- package/dist/js/queryClient.js +4 -0
- package/dist/js/queryClient.js.map +1 -1
- package/dist/query.d.ts +15 -0
- package/dist/query.js +25 -0
- package/dist/query.js.map +1 -1
- package/dist/vite.js +11 -2
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/hostCalls.js
CHANGED
|
@@ -15,7 +15,7 @@ import { revalidate } from './revalidate.js';
|
|
|
15
15
|
import { ActionRefusal, ActionValidationError } from './action.js';
|
|
16
16
|
import { redirect } from './redirect.js';
|
|
17
17
|
import { ServerAuthenticationError, ServerAuthorizationError } from './js/errors.js';
|
|
18
|
-
import {
|
|
18
|
+
import { NotFoundSignal } from './notFound.js';
|
|
19
19
|
const DEFAULT_FORWARDED = ['cookie', 'authorization'];
|
|
20
20
|
/** How many calls one POST carries at most. */
|
|
21
21
|
const BATCH_LIMIT = 50;
|
|
@@ -326,24 +326,21 @@ export function httpHostCalls(options) {
|
|
|
326
326
|
// abort(404), a record this caller cannot see. Raised as the engine's
|
|
327
327
|
// own notFound(), so the page answers with not-found.tsx and a 404,
|
|
328
328
|
// exactly as a component calling notFound() would; as an error it
|
|
329
|
-
// reached the page as "something went wrong".
|
|
329
|
+
// reached the page as "something went wrong". The backend's message rides
|
|
330
|
+
// on it for an action, which has no not-found page and shows the message.
|
|
330
331
|
if (reply?.refusalStatus === 404)
|
|
331
|
-
|
|
332
|
-
// A
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
332
|
+
throw new NotFoundSignal(reply.error ?? 'Not found');
|
|
333
|
+
// A status is what makes a reply a refusal rather than a failure: the
|
|
334
|
+
// backend's refuse(), Go's Refuse, Laravel's abort(). Its message was
|
|
335
|
+
// written for the person asking - Laravel shows an HttpException's in its
|
|
336
|
+
// own production JSON - so it is raised as one, with or without data, and
|
|
337
|
+
// onError never replaces it. Only refusals carried data at first, and
|
|
338
|
+
// Refuse(503, "busy") reached the form as "Something went wrong.".
|
|
339
|
+
if (reply?.error !== undefined && (reply.refusalStatus !== undefined || reply.refusalData !== undefined)) {
|
|
336
340
|
throw new ActionRefusal(reply.error, reply.refusalData, reply.refusalStatus ?? 409);
|
|
337
341
|
}
|
|
338
342
|
if (reply?.error !== undefined) {
|
|
339
343
|
const failure = new Error(`Host call ${JSON.stringify(name)} failed: ${reply.error}`);
|
|
340
|
-
// Carried on the error rather than thrown as another class: the host
|
|
341
|
-
// chose a status and the only job here is not to lose it on the way to
|
|
342
|
-
// whoever writes the response.
|
|
343
|
-
if (reply.refusalStatus) {
|
|
344
|
-
;
|
|
345
|
-
failure.refusalStatus = reply.refusalStatus;
|
|
346
|
-
}
|
|
347
344
|
if (reply.debug)
|
|
348
345
|
withBackendTrace(failure, reply.debug);
|
|
349
346
|
throw failure;
|
package/dist/hostCalls.js.map
CHANGED
|
@@ -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 { notFound } 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\".\n if (reply?.refusalStatus === 404) notFound()\n\n // A refusal with data is the backend's refuse(): its message was written\n // for the person asking, so it is raised as one, not as a failure whose\n // message onError would replace.\n if (reply?.error !== 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 // Carried on the error rather than thrown as another class: the host\n // chose a status and the only job here is not to lose it on the way to\n // whoever writes the response.\n if (reply.refusalStatus) {\n ;(failure as Error & { refusalStatus?: number }).refusalStatus = reply.refusalStatus\n }\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 } 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"]}
|
package/dist/js/queryClient.js
CHANGED
|
@@ -135,6 +135,10 @@ async function failureFrom(res) {
|
|
|
135
135
|
const error = new Error(parsed.message || `Query failed: ${res.status}`);
|
|
136
136
|
if (parsed.errors)
|
|
137
137
|
error.errors = parsed.errors;
|
|
138
|
+
// The data a refusal declared with .refusal(schema), checked on the server.
|
|
139
|
+
if (parsed.refusal !== undefined)
|
|
140
|
+
error.refusal = parsed.refusal;
|
|
141
|
+
error.status = res.status;
|
|
138
142
|
return error;
|
|
139
143
|
}
|
|
140
144
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryClient.js","sourceRoot":"","sources":["../../src/js/queryClient.ts"],"names":[],"mappings":"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","sourcesContent":["// The browser half of `query()`: send a read as a GET.\n//\n// That is the whole of it. There is no cache here, no batching and no\n// deduplication, because TanStack Query and SWR already do those and do them\n// better — this owns the transport and they own everything above it.\n//\n// The awkward fact it is built around: a server function's id is not readable\n// from the client. React keeps it in a module-private WeakMap and exposes no\n// getter, so there is no `ref.$$id` to build a url from. What the client *can*\n// do is call the reference and be handed the id by React itself — the stub's\n// whole body is `callServer(id, args)`, and this package already owns\n// `callServer`.\n//\n// So a read opens a one-shot slot and calls the reference; the transport claims\n// the slot and sends a GET instead of posting. The slot is also what says this\n// is a read at all: calling `getListings(kind)` directly is still a POST, and\n// `fetchQuery(getListings, [kind])` is the same call as a GET. Nothing has to\n// know a list of query ids, on either side of the build.\n\n/** Where reads are answered. Must match HEADER.queryPath on the server. */\nconst QUERY_PATH = '/_rsc/query'\n\n/**\n * Sent on every read, and required by the endpoint.\n *\n * Not decoration. A GET with no unusual header is a *simple* request, so any\n * page anywhere can trigger one with `<img src=\"…/_rsc/query?…\">` and it goes\n * out with the visitor's cookies — CORS stops the attacker reading the answer,\n * but the read still runs. This header is not CORS-safelisted, so the browser\n * preflights it, and nothing here answers a preflight.\n *\n * That restores exactly the protection a POST had: a `POST` carrying\n * `X-RSC-Action` is non-simple for the same reason.\n */\nconst QUERY_HEADER = 'X-RSC-Query'\n\n/**\n * How long a url may get before the read goes as a POST instead.\n *\n * Conservative. Proxies and CDNs start refusing somewhere between 8k and 16k,\n * and the failure is a 414 from a machine that is not ours.\n */\nconst MAX_URL = 6_000\n\ntype Reader = (...args: unknown[]) => Promise<unknown>\n\n/**\n * The slot a read opens before calling the reference.\n *\n * One-shot and synchronous: React's stub calls `callServer` in its own body\n * with nothing awaited in between, so exactly one transport call can claim it.\n */\nlet slot: { claimed: boolean; promise: Promise<unknown> | null } | null = null\n\n/**\n * Claim the open slot, if a read opened one.\n *\n * Called from the app's `callServer` before it posts. Returns null when this is\n * an ordinary action, which is every call that did not come through\n * `fetchQuery`.\n */\nexport function claimRead(id: string, args: unknown[]): Promise<unknown> | null {\n if (!slot || slot.claimed) return null\n\n slot.claimed = true\n slot.promise = send(id, args)\n\n return slot.promise\n}\n\n/**\n * Read a query, over GET.\n *\n * Hand this to a cache library and let it decide everything else:\n *\n * queryFn: () => fetchQuery(getListings, [kind])\n * useSWR(['listings', kind], () => fetchQuery(getListings, [kind]))\n *\n * Every call goes to the server, which is what a fetcher needs — staleness,\n * revalidation, retries, polling and deduplication all belong to the library\n * holding the answer, not to the thing that fetches it.\n */\nexport function fetchQuery<Data>(reference: () => Promise<Data>, args?: []): Promise<Data>\n// The query's own parameters, so a wrong key fails the typecheck rather than\n// the read. NoInfer keeps the tuple from being inferred from the call site,\n// where a wrong key would simply widen it.\nexport function fetchQuery<Args extends unknown[], Data>(\n reference: (...args: Args) => Promise<Data>,\n args: NoInfer<Args>,\n): Promise<Data>\nexport function fetchQuery<Data>(\n reference: (...args: never[]) => Promise<Data>,\n args: unknown[] = [],\n): Promise<Data> {\n if (typeof window === 'undefined') {\n // React's SSR runtime refuses a server-function call during the initial\n // render, and reaching a query's id means calling its reference — so this\n // cannot work here. Raised with the fix in it rather than left to surface\n // as React's more general message about fetch waterfalls.\n //\n // A cache library runs its fetcher in an effect, so the server render never\n // reaches this; a server component that wants the data during render should\n // await the query directly, which needs none of this.\n throw new Error(\n 'A query was read during server rendering. Read it in a server component with await, or through a cache library, whose fetcher runs after hydration.',\n )\n }\n\n const opened = { claimed: false, promise: null as Promise<unknown> | null }\n\n slot = opened\n\n try {\n // React's stub reaches callServer synchronously, so the slot is claimed by\n // the time this returns. The stub's own promise is discarded: the one the\n // transport made is the one that settles with the answer.\n ;(reference as unknown as Reader)(...(args as unknown[]))\n } finally {\n // Cleared here and nowhere else, so a reference that throws on the way in\n // does not leave the slot open for the next read to claim by accident.\n // Nothing is returned from this block: a `return` in `finally` discards\n // whatever the `try` was throwing, which would turn a reference that threw\n // synchronously into the misleading bound-reference error below.\n slot = null\n }\n\n if (!opened.claimed || !opened.promise) {\n // The only way here is a reference whose call path is asynchronous, which\n // today means one that was `.bind()`-ed. Loud, because the quiet version is\n // a read that silently went out as a POST.\n throw new Error(\n 'A query was called through a bound reference. Pass the exported query itself, unbound.',\n )\n }\n\n return opened.promise as Promise<Data>\n}\n\nasync function send(id: string, args: unknown[]): Promise<unknown> {\n const encoded = await encode(args)\n\n // encodeReply answers with FormData the moment an argument holds a File, and\n // a url cannot carry one. Rather than refuse a call that would have worked as\n // an action, the read falls back to a POST: it stops being cacheable, which\n // is the only thing it loses.\n if (typeof encoded !== 'string') return await post(id, args, 'an argument contained a File')\n\n const url = `${QUERY_PATH}?id=${encodeURIComponent(id)}&args=${encodeURIComponent(encoded)}`\n\n if (url.length > MAX_URL) return await post(id, args, 'the arguments are too large for a url')\n\n const res = await fetch(url, {\n headers: {\n [QUERY_HEADER]: '1',\n Accept: 'text/x-component',\n // Where the read came from. Same reason an action sends it: a host that\n // guards by route needs to know which page is asking.\n 'X-RSC-Referer': window.location.pathname + window.location.search,\n },\n })\n\n if (!res.ok || !res.body) {\n throw await failureFrom(res)\n }\n\n return await deserialize(res.body)\n}\n\n/**\n * What a refused read rejects with.\n *\n * A cache library reports failure by rejection, so this has to be an Error —\n * and one carrying the message the server meant, rather than a status code. A\n * validation refusal keeps its fields on `errors`, where a form can read them.\n */\nasync function failureFrom(res: Response): Promise<Error> {\n const body = await res.text().catch(() => '')\n\n try {\n const parsed = JSON.parse(body) as { message?: string; errors?: Record<string, string[]> }\n const error = new Error(parsed.message || `Query failed: ${res.status}`)\n\n if (parsed.errors) (error as Error & { errors?: unknown }).errors = parsed.errors\n\n return error\n } catch {\n return new Error(body || `Query failed: ${res.status}`)\n }\n}\n\n/**\n * The same read, as an action.\n *\n * Reached only when the arguments cannot ride in a url. Warned about in\n * development rather than silently tolerated, because the read still works and\n * the thing that changed — it is no longer a GET, so nothing can cache it — is\n * otherwise invisible.\n */\nasync function post(id: string, args: unknown[], why: string): Promise<unknown> {\n if (import.meta.env?.DEV) {\n console.warn(`[rsc-kit] A query was sent as a POST because ${why}. It will not be cached.`)\n }\n\n return await asAction(id, args)\n}\n\n/**\n * The Flight codec and the action transport, installed by the app bootstrap.\n *\n * Injected rather than imported, and not for testing: this module is reached\n * from client components, so importing the browser runtime here would pull a\n * second copy of it into that graph — two client-reference registries, where\n * components resolve to undefined with nothing logged. The bootstrap already\n * holds the one true copy.\n */\nlet deserialize: (stream: ReadableStream) => Promise<unknown> = () => {\n throw new Error('No Flight decoder installed. createViteRscApp() sets one up.')\n}\n\nlet encode: (args: unknown[]) => Promise<string | FormData> = () => {\n throw new Error('No Flight encoder installed. createViteRscApp() sets one up.')\n}\n\nlet asAction: (id: string, args: unknown[]) => Promise<unknown> = () => {\n throw new Error('No action transport installed. createViteRscApp() sets one up.')\n}\n\nexport function setQueryCodec(codec: {\n deserialize: (stream: ReadableStream) => Promise<unknown>\n encode: (args: unknown[]) => Promise<string | FormData>\n asAction: (id: string, args: unknown[]) => Promise<unknown>\n}): void {\n deserialize = codec.deserialize\n encode = codec.encode\n asAction = codec.asAction\n}\n"]}
|
|
1
|
+
{"version":3,"file":"queryClient.js","sourceRoot":"","sources":["../../src/js/queryClient.ts"],"names":[],"mappings":"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","sourcesContent":["// The browser half of `query()`: send a read as a GET.\n//\n// That is the whole of it. There is no cache here, no batching and no\n// deduplication, because TanStack Query and SWR already do those and do them\n// better — this owns the transport and they own everything above it.\n//\n// The awkward fact it is built around: a server function's id is not readable\n// from the client. React keeps it in a module-private WeakMap and exposes no\n// getter, so there is no `ref.$$id` to build a url from. What the client *can*\n// do is call the reference and be handed the id by React itself — the stub's\n// whole body is `callServer(id, args)`, and this package already owns\n// `callServer`.\n//\n// So a read opens a one-shot slot and calls the reference; the transport claims\n// the slot and sends a GET instead of posting. The slot is also what says this\n// is a read at all: calling `getListings(kind)` directly is still a POST, and\n// `fetchQuery(getListings, [kind])` is the same call as a GET. Nothing has to\n// know a list of query ids, on either side of the build.\n\n/** Where reads are answered. Must match HEADER.queryPath on the server. */\nconst QUERY_PATH = '/_rsc/query'\n\n/**\n * Sent on every read, and required by the endpoint.\n *\n * Not decoration. A GET with no unusual header is a *simple* request, so any\n * page anywhere can trigger one with `<img src=\"…/_rsc/query?…\">` and it goes\n * out with the visitor's cookies — CORS stops the attacker reading the answer,\n * but the read still runs. This header is not CORS-safelisted, so the browser\n * preflights it, and nothing here answers a preflight.\n *\n * That restores exactly the protection a POST had: a `POST` carrying\n * `X-RSC-Action` is non-simple for the same reason.\n */\nconst QUERY_HEADER = 'X-RSC-Query'\n\n/**\n * How long a url may get before the read goes as a POST instead.\n *\n * Conservative. Proxies and CDNs start refusing somewhere between 8k and 16k,\n * and the failure is a 414 from a machine that is not ours.\n */\nconst MAX_URL = 6_000\n\ntype Reader = (...args: unknown[]) => Promise<unknown>\n\n/**\n * The slot a read opens before calling the reference.\n *\n * One-shot and synchronous: React's stub calls `callServer` in its own body\n * with nothing awaited in between, so exactly one transport call can claim it.\n */\nlet slot: { claimed: boolean; promise: Promise<unknown> | null } | null = null\n\n/**\n * Claim the open slot, if a read opened one.\n *\n * Called from the app's `callServer` before it posts. Returns null when this is\n * an ordinary action, which is every call that did not come through\n * `fetchQuery`.\n */\nexport function claimRead(id: string, args: unknown[]): Promise<unknown> | null {\n if (!slot || slot.claimed) return null\n\n slot.claimed = true\n slot.promise = send(id, args)\n\n return slot.promise\n}\n\n/**\n * Read a query, over GET.\n *\n * Hand this to a cache library and let it decide everything else:\n *\n * queryFn: () => fetchQuery(getListings, [kind])\n * useSWR(['listings', kind], () => fetchQuery(getListings, [kind]))\n *\n * Every call goes to the server, which is what a fetcher needs — staleness,\n * revalidation, retries, polling and deduplication all belong to the library\n * holding the answer, not to the thing that fetches it.\n */\nexport function fetchQuery<Data>(reference: () => Promise<Data>, args?: []): Promise<Data>\n// The query's own parameters, so a wrong key fails the typecheck rather than\n// the read. NoInfer keeps the tuple from being inferred from the call site,\n// where a wrong key would simply widen it.\nexport function fetchQuery<Args extends unknown[], Data>(\n reference: (...args: Args) => Promise<Data>,\n args: NoInfer<Args>,\n): Promise<Data>\nexport function fetchQuery<Data>(\n reference: (...args: never[]) => Promise<Data>,\n args: unknown[] = [],\n): Promise<Data> {\n if (typeof window === 'undefined') {\n // React's SSR runtime refuses a server-function call during the initial\n // render, and reaching a query's id means calling its reference — so this\n // cannot work here. Raised with the fix in it rather than left to surface\n // as React's more general message about fetch waterfalls.\n //\n // A cache library runs its fetcher in an effect, so the server render never\n // reaches this; a server component that wants the data during render should\n // await the query directly, which needs none of this.\n throw new Error(\n 'A query was read during server rendering. Read it in a server component with await, or through a cache library, whose fetcher runs after hydration.',\n )\n }\n\n const opened = { claimed: false, promise: null as Promise<unknown> | null }\n\n slot = opened\n\n try {\n // React's stub reaches callServer synchronously, so the slot is claimed by\n // the time this returns. The stub's own promise is discarded: the one the\n // transport made is the one that settles with the answer.\n ;(reference as unknown as Reader)(...(args as unknown[]))\n } finally {\n // Cleared here and nowhere else, so a reference that throws on the way in\n // does not leave the slot open for the next read to claim by accident.\n // Nothing is returned from this block: a `return` in `finally` discards\n // whatever the `try` was throwing, which would turn a reference that threw\n // synchronously into the misleading bound-reference error below.\n slot = null\n }\n\n if (!opened.claimed || !opened.promise) {\n // The only way here is a reference whose call path is asynchronous, which\n // today means one that was `.bind()`-ed. Loud, because the quiet version is\n // a read that silently went out as a POST.\n throw new Error(\n 'A query was called through a bound reference. Pass the exported query itself, unbound.',\n )\n }\n\n return opened.promise as Promise<Data>\n}\n\nasync function send(id: string, args: unknown[]): Promise<unknown> {\n const encoded = await encode(args)\n\n // encodeReply answers with FormData the moment an argument holds a File, and\n // a url cannot carry one. Rather than refuse a call that would have worked as\n // an action, the read falls back to a POST: it stops being cacheable, which\n // is the only thing it loses.\n if (typeof encoded !== 'string') return await post(id, args, 'an argument contained a File')\n\n const url = `${QUERY_PATH}?id=${encodeURIComponent(id)}&args=${encodeURIComponent(encoded)}`\n\n if (url.length > MAX_URL) return await post(id, args, 'the arguments are too large for a url')\n\n const res = await fetch(url, {\n headers: {\n [QUERY_HEADER]: '1',\n Accept: 'text/x-component',\n // Where the read came from. Same reason an action sends it: a host that\n // guards by route needs to know which page is asking.\n 'X-RSC-Referer': window.location.pathname + window.location.search,\n },\n })\n\n if (!res.ok || !res.body) {\n throw await failureFrom(res)\n }\n\n return await deserialize(res.body)\n}\n\n/**\n * What a refused read rejects with.\n *\n * A cache library reports failure by rejection, so this has to be an Error —\n * and one carrying the message the server meant, rather than a status code. A\n * validation refusal keeps its fields on `errors`, where a form can read them.\n */\nasync function failureFrom(res: Response): Promise<Error> {\n const body = await res.text().catch(() => '')\n\n try {\n const parsed = JSON.parse(body) as { message?: string; errors?: Record<string, string[]>; refusal?: unknown }\n const error = new Error(parsed.message || `Query failed: ${res.status}`) as Error & {\n errors?: unknown\n refusal?: unknown\n status?: number\n }\n\n if (parsed.errors) error.errors = parsed.errors\n // The data a refusal declared with .refusal(schema), checked on the server.\n if (parsed.refusal !== undefined) error.refusal = parsed.refusal\n error.status = res.status\n\n return error\n } catch {\n return new Error(body || `Query failed: ${res.status}`)\n }\n}\n\n/**\n * The same read, as an action.\n *\n * Reached only when the arguments cannot ride in a url. Warned about in\n * development rather than silently tolerated, because the read still works and\n * the thing that changed — it is no longer a GET, so nothing can cache it — is\n * otherwise invisible.\n */\nasync function post(id: string, args: unknown[], why: string): Promise<unknown> {\n if (import.meta.env?.DEV) {\n console.warn(`[rsc-kit] A query was sent as a POST because ${why}. It will not be cached.`)\n }\n\n return await asAction(id, args)\n}\n\n/**\n * The Flight codec and the action transport, installed by the app bootstrap.\n *\n * Injected rather than imported, and not for testing: this module is reached\n * from client components, so importing the browser runtime here would pull a\n * second copy of it into that graph — two client-reference registries, where\n * components resolve to undefined with nothing logged. The bootstrap already\n * holds the one true copy.\n */\nlet deserialize: (stream: ReadableStream) => Promise<unknown> = () => {\n throw new Error('No Flight decoder installed. createViteRscApp() sets one up.')\n}\n\nlet encode: (args: unknown[]) => Promise<string | FormData> = () => {\n throw new Error('No Flight encoder installed. createViteRscApp() sets one up.')\n}\n\nlet asAction: (id: string, args: unknown[]) => Promise<unknown> = () => {\n throw new Error('No action transport installed. createViteRscApp() sets one up.')\n}\n\nexport function setQueryCodec(codec: {\n deserialize: (stream: ReadableStream) => Promise<unknown>\n encode: (args: unknown[]) => Promise<string | FormData>\n asAction: (id: string, args: unknown[]) => Promise<unknown>\n}): void {\n deserialize = codec.deserialize\n encode = codec.encode\n asAction = codec.asAction\n}\n"]}
|
package/dist/query.d.ts
CHANGED
|
@@ -57,3 +57,18 @@ export declare function isQuery(fn: unknown): boolean;
|
|
|
57
57
|
export declare function queryOptions(fn: unknown): QueryOptions;
|
|
58
58
|
/** The `Cache-Control` a query's answer goes out with. */
|
|
59
59
|
export declare function queryCacheControl(fn: unknown): string;
|
|
60
|
+
/**
|
|
61
|
+
* The status and message a read that was turned away answers with, or null
|
|
62
|
+
* when it failed.
|
|
63
|
+
*
|
|
64
|
+
* Signed out, not allowed, nothing there, or a refusal the backend gave a
|
|
65
|
+
* status of its own - each message was written to be seen, and as a 500 it
|
|
66
|
+
* was "Query failed.", with a fetcher unable to tell a 429 that says wait
|
|
67
|
+
* from a broken server. Matched by name and mark, never by class: the query
|
|
68
|
+
* was bundled apart from the engine.
|
|
69
|
+
*/
|
|
70
|
+
export declare function queryRefusal(error: unknown): {
|
|
71
|
+
status: number;
|
|
72
|
+
message: string;
|
|
73
|
+
refusal?: unknown;
|
|
74
|
+
} | null;
|
package/dist/query.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// checks before invoking anything. Without that check, `/_rsc/query?id=…`
|
|
20
20
|
// would run any registered action over GET, which is the classic
|
|
21
21
|
// a-crawler-emptied-the-database bug.
|
|
22
|
+
import { isNotFoundSignal } from './notFound.js';
|
|
22
23
|
/**
|
|
23
24
|
* Marks a function as safe to invoke over GET.
|
|
24
25
|
*
|
|
@@ -113,4 +114,28 @@ export function queryCacheControl(fn) {
|
|
|
113
114
|
// direction.
|
|
114
115
|
return age > 0 ? `${cache}, max-age=${age}` : `${cache}, max-age=0, must-revalidate`;
|
|
115
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The status and message a read that was turned away answers with, or null
|
|
119
|
+
* when it failed.
|
|
120
|
+
*
|
|
121
|
+
* Signed out, not allowed, nothing there, or a refusal the backend gave a
|
|
122
|
+
* status of its own - each message was written to be seen, and as a 500 it
|
|
123
|
+
* was "Query failed.", with a fetcher unable to tell a 429 that says wait
|
|
124
|
+
* from a broken server. Matched by name and mark, never by class: the query
|
|
125
|
+
* was bundled apart from the engine.
|
|
126
|
+
*/
|
|
127
|
+
export function queryRefusal(error) {
|
|
128
|
+
if (!(error instanceof Error))
|
|
129
|
+
return null;
|
|
130
|
+
const { name, message, refusalStatus, refusal } = error;
|
|
131
|
+
if (name === 'ServerAuthenticationError')
|
|
132
|
+
return { status: 401, message };
|
|
133
|
+
if (name === 'ServerAuthorizationError')
|
|
134
|
+
return { status: 403, message };
|
|
135
|
+
if (isNotFoundSignal(error))
|
|
136
|
+
return { status: 404, message };
|
|
137
|
+
if (typeof refusalStatus !== 'number' || refusalStatus < 400 || refusalStatus > 599)
|
|
138
|
+
return null;
|
|
139
|
+
return refusal === undefined ? { status: refusalStatus, message } : { status: refusalStatus, message, refusal };
|
|
140
|
+
}
|
|
116
141
|
//# sourceMappingURL=query.js.map
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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","sourcesContent":["// Reads from the server, over GET, without an endpoint to write.\n//\n// // listings.ts\n// \"use server\"\n// import { query } from '@rsc-kit/core/query'\n//\n// export const getListings = query(async (filters: Filters) =>\n// listingService.getFiltered(filters))\n//\n// A server function is a POST, and a POST is cacheable by nothing: not an HTTP\n// cache, not a CDN, not the service worker this package generates. Routing\n// reads through one makes every dynamic read the single thing that cannot\n// survive without a network. That is the whole reason this exists alongside\n// actions rather than on top of them.\n//\n// The module still carries \"use server\", because that is what registers the\n// function and gives it an id — this does not invent a second registry. What\n// it adds is a mark saying the function is a READ, which the GET endpoint\n// checks before invoking anything. Without that check, `/_rsc/query?id=…`\n// would run any registered action over GET, which is the classic\n// a-crawler-emptied-the-database bug.\n\n/**\n * Marks a function as safe to invoke over GET.\n *\n * A property rather than a WeakSet or `instanceof`, for the reason this\n * project keeps meeting: an app's server functions are bundled separately from\n * the engine, so each side gets its own copy of this module. Anything holding\n * identity across that seam — a set, a class — is simply empty on the other\n * side, and every query would answer \"not a query\" with nothing logged.\n *\n * Symbol.for, so both copies agree on the key as well as the value.\n */\nconst QUERY_MARK = Symbol.for('@rsc-kit/core.query')\nconst QUERY_OPTIONS = Symbol.for('@rsc-kit/core.query-options')\n\n/** How the answer may be stored. Conservative by default — see `QueryOptions`. */\nexport interface QueryOptions {\n /**\n * What `Cache-Control` the answer carries.\n *\n * The default is `private, no-store`, and it is deliberately the safe one: a\n * query may read the session, and a cacheable answer to a personal read is\n * how one visitor is served another's data. Widen it per query, once you\n * have looked at what that query returns.\n *\n * `'private'` lets the visitor's own browser and service worker keep it and\n * nothing else. `'public'` lets a CDN keep it, and is only ever right for a\n * read whose answer does not depend on who is asking.\n */\n cache?: 'no-store' | 'private' | 'public'\n /** Seconds the answer stays fresh. Ignored when `cache` is `'no-store'`. */\n maxAge?: number\n}\n\n/** A function that may be read over GET. */\nexport type QueryFn<Args extends unknown[], Data> = (...args: Args) => Promise<Data>\n\n/**\n * Declare a read.\n *\n * The wrapper is what gets registered and what the id points at, so the mark\n * travels with the thing the endpoint actually loads. Wrapping rather than\n * annotating the original also keeps `query(fn)` honest when someone exports\n * the raw `fn` beside it: that export is an action, not a query, and the GET\n * endpoint will refuse it.\n */\nexport function query<Args extends unknown[], Data>(\n fn: (...args: Args) => Promise<Data> | Data,\n options: QueryOptions = {},\n): QueryFn<Args, Data> {\n const read = async (...args: Args): Promise<Data> => await fn(...args)\n\n return markQuery(read, options)\n}\n\n/**\n * Mark a function a read, without wrapping it again.\n *\n * For a builder that has already assembled the function it wants registered —\n * `createActionClient().query()` — where wrapping once more would only add a\n * frame to every stack trace.\n */\nexport function markQuery<F extends (...args: never[]) => unknown>(\n fn: F,\n options: QueryOptions = {},\n): F {\n // Non-enumerable, so the mark does not show up in anything that walks the\n // function's own keys — a bundler's export analysis, a test's snapshot.\n Object.defineProperty(fn, QUERY_MARK, { value: true })\n Object.defineProperty(fn, QUERY_OPTIONS, { value: options })\n\n return fn\n}\n\n/**\n * A refusal a query can send back with its fields intact.\n *\n * Thrown rather than returned, because a query is handed to a cache library as\n * a fetcher and every one of them reports failure by rejection. The endpoint\n * turns this into a 422 so the message and the fields survive the boundary —\n * React strips a thrown error's message in production, which is the reason an\n * ACTION returns its failures instead.\n */\nexport class QueryValidationError extends Error {\n public readonly errors: Record<string, string[]>\n\n constructor(errors: Record<string, string[]>) {\n super('Validation failed')\n this.name = 'QueryValidationError'\n this.errors = errors\n ;(this as unknown as Record<symbol, boolean>)[VALIDATION_MARK] = true\n }\n}\n\n/** Whether this is a query refusal, whichever copy of the class built it. */\nexport function isQueryValidationError(error: unknown): error is QueryValidationError {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as Record<symbol, unknown>)[VALIDATION_MARK] === true\n )\n}\n\n/**\n * Marks a refusal so it survives a bundle seam.\n *\n * Symbol.for for the same reason the query mark is: an app's functions are\n * bundled apart from the engine, so each side gets its own copy of this class\n * and `instanceof` is simply false between them.\n */\nconst VALIDATION_MARK = Symbol.for('@rsc-kit/core.query-validation')\n\n/** Whether this function was declared with `query()`, whichever copy declared it. */\nexport function isQuery(fn: unknown): boolean {\n return typeof fn === 'function' && (fn as unknown as Record<symbol, unknown>)[QUERY_MARK] === true\n}\n\n/** What the query asked for, or the safe default when it asked for nothing. */\nexport function queryOptions(fn: unknown): QueryOptions {\n if (typeof fn !== 'function') return {}\n\n return ((fn as unknown as Record<symbol, unknown>)[QUERY_OPTIONS] as QueryOptions) ?? {}\n}\n\n/** The `Cache-Control` a query's answer goes out with. */\nexport function queryCacheControl(fn: unknown): string {\n const { cache = 'no-store', maxAge } = queryOptions(fn)\n\n if (cache === 'no-store') return 'private, no-store'\n\n const age = typeof maxAge === 'number' && maxAge > 0 ? Math.floor(maxAge) : 0\n\n // must-revalidate at zero rather than omitting max-age: with no directive at\n // all, whether an intermediary keeps it comes down to that intermediary's\n // heuristics, and this package does not leave that to chance in either\n // direction.\n return age > 0 ? `${cache}, max-age=${age}` : `${cache}, max-age=0, must-revalidate`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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","sourcesContent":["// Reads from the server, over GET, without an endpoint to write.\n//\n// // listings.ts\n// \"use server\"\n// import { query } from '@rsc-kit/core/query'\n//\n// export const getListings = query(async (filters: Filters) =>\n// listingService.getFiltered(filters))\n//\n// A server function is a POST, and a POST is cacheable by nothing: not an HTTP\n// cache, not a CDN, not the service worker this package generates. Routing\n// reads through one makes every dynamic read the single thing that cannot\n// survive without a network. That is the whole reason this exists alongside\n// actions rather than on top of them.\n//\n// The module still carries \"use server\", because that is what registers the\n// function and gives it an id — this does not invent a second registry. What\n// it adds is a mark saying the function is a READ, which the GET endpoint\n// checks before invoking anything. Without that check, `/_rsc/query?id=…`\n// would run any registered action over GET, which is the classic\n// a-crawler-emptied-the-database bug.\n\nimport { isNotFoundSignal } from './notFound.js'\n\n/**\n * Marks a function as safe to invoke over GET.\n *\n * A property rather than a WeakSet or `instanceof`, for the reason this\n * project keeps meeting: an app's server functions are bundled separately from\n * the engine, so each side gets its own copy of this module. Anything holding\n * identity across that seam — a set, a class — is simply empty on the other\n * side, and every query would answer \"not a query\" with nothing logged.\n *\n * Symbol.for, so both copies agree on the key as well as the value.\n */\nconst QUERY_MARK = Symbol.for('@rsc-kit/core.query')\nconst QUERY_OPTIONS = Symbol.for('@rsc-kit/core.query-options')\n\n/** How the answer may be stored. Conservative by default — see `QueryOptions`. */\nexport interface QueryOptions {\n /**\n * What `Cache-Control` the answer carries.\n *\n * The default is `private, no-store`, and it is deliberately the safe one: a\n * query may read the session, and a cacheable answer to a personal read is\n * how one visitor is served another's data. Widen it per query, once you\n * have looked at what that query returns.\n *\n * `'private'` lets the visitor's own browser and service worker keep it and\n * nothing else. `'public'` lets a CDN keep it, and is only ever right for a\n * read whose answer does not depend on who is asking.\n */\n cache?: 'no-store' | 'private' | 'public'\n /** Seconds the answer stays fresh. Ignored when `cache` is `'no-store'`. */\n maxAge?: number\n}\n\n/** A function that may be read over GET. */\nexport type QueryFn<Args extends unknown[], Data> = (...args: Args) => Promise<Data>\n\n/**\n * Declare a read.\n *\n * The wrapper is what gets registered and what the id points at, so the mark\n * travels with the thing the endpoint actually loads. Wrapping rather than\n * annotating the original also keeps `query(fn)` honest when someone exports\n * the raw `fn` beside it: that export is an action, not a query, and the GET\n * endpoint will refuse it.\n */\nexport function query<Args extends unknown[], Data>(\n fn: (...args: Args) => Promise<Data> | Data,\n options: QueryOptions = {},\n): QueryFn<Args, Data> {\n const read = async (...args: Args): Promise<Data> => await fn(...args)\n\n return markQuery(read, options)\n}\n\n/**\n * Mark a function a read, without wrapping it again.\n *\n * For a builder that has already assembled the function it wants registered —\n * `createActionClient().query()` — where wrapping once more would only add a\n * frame to every stack trace.\n */\nexport function markQuery<F extends (...args: never[]) => unknown>(\n fn: F,\n options: QueryOptions = {},\n): F {\n // Non-enumerable, so the mark does not show up in anything that walks the\n // function's own keys — a bundler's export analysis, a test's snapshot.\n Object.defineProperty(fn, QUERY_MARK, { value: true })\n Object.defineProperty(fn, QUERY_OPTIONS, { value: options })\n\n return fn\n}\n\n/**\n * A refusal a query can send back with its fields intact.\n *\n * Thrown rather than returned, because a query is handed to a cache library as\n * a fetcher and every one of them reports failure by rejection. The endpoint\n * turns this into a 422 so the message and the fields survive the boundary —\n * React strips a thrown error's message in production, which is the reason an\n * ACTION returns its failures instead.\n */\nexport class QueryValidationError extends Error {\n public readonly errors: Record<string, string[]>\n\n constructor(errors: Record<string, string[]>) {\n super('Validation failed')\n this.name = 'QueryValidationError'\n this.errors = errors\n ;(this as unknown as Record<symbol, boolean>)[VALIDATION_MARK] = true\n }\n}\n\n/** Whether this is a query refusal, whichever copy of the class built it. */\nexport function isQueryValidationError(error: unknown): error is QueryValidationError {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as Record<symbol, unknown>)[VALIDATION_MARK] === true\n )\n}\n\n/**\n * Marks a refusal so it survives a bundle seam.\n *\n * Symbol.for for the same reason the query mark is: an app's functions are\n * bundled apart from the engine, so each side gets its own copy of this class\n * and `instanceof` is simply false between them.\n */\nconst VALIDATION_MARK = Symbol.for('@rsc-kit/core.query-validation')\n\n/** Whether this function was declared with `query()`, whichever copy declared it. */\nexport function isQuery(fn: unknown): boolean {\n return typeof fn === 'function' && (fn as unknown as Record<symbol, unknown>)[QUERY_MARK] === true\n}\n\n/** What the query asked for, or the safe default when it asked for nothing. */\nexport function queryOptions(fn: unknown): QueryOptions {\n if (typeof fn !== 'function') return {}\n\n return ((fn as unknown as Record<symbol, unknown>)[QUERY_OPTIONS] as QueryOptions) ?? {}\n}\n\n/** The `Cache-Control` a query's answer goes out with. */\nexport function queryCacheControl(fn: unknown): string {\n const { cache = 'no-store', maxAge } = queryOptions(fn)\n\n if (cache === 'no-store') return 'private, no-store'\n\n const age = typeof maxAge === 'number' && maxAge > 0 ? Math.floor(maxAge) : 0\n\n // must-revalidate at zero rather than omitting max-age: with no directive at\n // all, whether an intermediary keeps it comes down to that intermediary's\n // heuristics, and this package does not leave that to chance in either\n // direction.\n return age > 0 ? `${cache}, max-age=${age}` : `${cache}, max-age=0, must-revalidate`\n}\n\n/**\n * The status and message a read that was turned away answers with, or null\n * when it failed.\n *\n * Signed out, not allowed, nothing there, or a refusal the backend gave a\n * status of its own - each message was written to be seen, and as a 500 it\n * was \"Query failed.\", with a fetcher unable to tell a 429 that says wait\n * from a broken server. Matched by name and mark, never by class: the query\n * was bundled apart from the engine.\n */\nexport function queryRefusal(error: unknown): { status: number; message: string; refusal?: unknown } | null {\n if (!(error instanceof Error)) return null\n\n const { name, message, refusalStatus, refusal } = error as Error & { refusalStatus?: unknown; refusal?: unknown }\n\n if (name === 'ServerAuthenticationError') return { status: 401, message }\n if (name === 'ServerAuthorizationError') return { status: 403, message }\n if (isNotFoundSignal(error)) return { status: 404, message }\n if (typeof refusalStatus !== 'number' || refusalStatus < 400 || refusalStatus > 599) return null\n\n return refusal === undefined ? { status: refusalStatus, message } : { status: refusalStatus, message, refusal }\n}\n"]}
|
package/dist/vite.js
CHANGED
|
@@ -3035,7 +3035,7 @@ import { backendVersions, installBackendVersionSource } from ${JSON.stringify(jo
|
|
|
3035
3035
|
import { RefreshOn } from ${JSON.stringify(join(packageDir, "js/refreshOn"))}
|
|
3036
3036
|
import { prerenderedBeside } from ${JSON.stringify(join(packageDir, "files"))}
|
|
3037
3037
|
import { renderToReadableStream, decodeReply, decodeAction, decodeFormState, loadServerAction } from '@vitejs/plugin-rsc/rsc'
|
|
3038
|
-
import { isQuery, queryCacheControl, isQueryValidationError } from ${JSON.stringify(join(packageDir, "query"))}
|
|
3038
|
+
import { isQuery, queryCacheControl, isQueryValidationError, queryRefusal } from ${JSON.stringify(join(packageDir, "query"))}
|
|
3039
3039
|
import { isActionValidationError, isClientBuilt } from ${JSON.stringify(join(packageDir, "action"))}
|
|
3040
3040
|
import { forgetCached } from ${JSON.stringify(join(packageDir, "cache"))}
|
|
3041
3041
|
import { noteFallback as noteCaughtRead } from ${JSON.stringify(join(packageDir, "request"))}
|
|
@@ -4859,7 +4859,7 @@ export async function handleQuery(
|
|
|
4859
4859
|
report?: (error: unknown) => string,
|
|
4860
4860
|
): Promise<
|
|
4861
4861
|
| { stream: ReadableStream; cacheControl: string }
|
|
4862
|
-
| { status: number; message: string; errors?: Record<string, string[]
|
|
4862
|
+
| { status: number; message: string; errors?: Record<string, string[]>; refusal?: unknown }
|
|
4863
4863
|
| null
|
|
4864
4864
|
> {
|
|
4865
4865
|
await instrumented()
|
|
@@ -4893,6 +4893,15 @@ export async function handleQuery(
|
|
|
4893
4893
|
return { status: 422, errors: error.errors, message: error.message }
|
|
4894
4894
|
}
|
|
4895
4895
|
|
|
4896
|
+
// Turned away rather than failed, and said with the status a page would
|
|
4897
|
+
// have answered: signed out, not allowed, nothing there, or a refusal the
|
|
4898
|
+
// backend gave a status of its own. Each message was written to be seen;
|
|
4899
|
+
// as a 500 it was "Query failed." and a fetcher could not tell a 429 to
|
|
4900
|
+
// wait from a broken server.
|
|
4901
|
+
const turnedAway = queryRefusal(error)
|
|
4902
|
+
|
|
4903
|
+
if (turnedAway) return turnedAway
|
|
4904
|
+
|
|
4896
4905
|
return { status: 500, message: report ? report(error) : 'Query failed.' }
|
|
4897
4906
|
}
|
|
4898
4907
|
|