@rsc-kit/core 0.16.1 → 0.16.3

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/request.js CHANGED
@@ -28,8 +28,84 @@
28
28
  //
29
29
  // Scoped to the request, on the same store cache() uses. Two requests in
30
30
  // flight cannot see each other's headers.
31
- import { resolveScope } from './revalidate.js';
32
- const SCOPE = Symbol.for('@rsc-kit/core.request-scope');
31
+ import { resolveScope } from "./revalidate.js";
32
+ const SCOPE = Symbol.for("@rsc-kit/core.request-scope");
33
+ /**
34
+ * The accessors themselves and the plumbing under them: frames to walk past
35
+ * on the way to the component that called.
36
+ */
37
+ const NOT_A_CALLER = new Set([
38
+ "never",
39
+ "noteRequestRead",
40
+ "recordRead",
41
+ "caller",
42
+ "slot",
43
+ "cookies",
44
+ "headers",
45
+ "connection",
46
+ "searchParams",
47
+ "request",
48
+ "url",
49
+ "parseParams",
50
+ "parseSearchParams",
51
+ "parseBody",
52
+ "get",
53
+ "set",
54
+ "has",
55
+ ]);
56
+ /** The accessors a component calls by name. One calling another is plumbing, not a second read. */
57
+ const ACCESSORS = new Set([
58
+ "cookies",
59
+ "headers",
60
+ "connection",
61
+ "searchParams",
62
+ "request",
63
+ "url",
64
+ ]);
65
+ /**
66
+ * The nearest named function above the accessor on the stack, and whether
67
+ * another accessor sits between: `cookies()` reads through `headers()`, and
68
+ * that inner read is not a second thing the component did.
69
+ *
70
+ * Read from a thrown-away Error: the one thing a build cannot otherwise learn
71
+ * is which component reached for the request, and the stack knows. Function
72
+ * names survive a server bundle, which is not minified, so this is
73
+ * `RootLayout` and not a line number in a chunk. Internal names are walked
74
+ * past; an anonymous frame is skipped rather than reported as `<anonymous>`.
75
+ */
76
+ function caller(own) {
77
+ const stack = new Error().stack ?? "";
78
+ let accessors = 0;
79
+ for (const line of stack.split("\n")) {
80
+ const match = /^\s*at (?:async )?([^\s(]+)/.exec(line);
81
+ if (!match)
82
+ continue;
83
+ const name = match[1].split(".").pop();
84
+ if (ACCESSORS.has(name))
85
+ accessors++;
86
+ if (NOT_A_CALLER.has(name) ||
87
+ name === "<anonymous>" ||
88
+ name === "Object" ||
89
+ name === "Promise")
90
+ continue;
91
+ if (/^[a-z_$]/.test(name) && !/^use[A-Z]/.test(name))
92
+ continue;
93
+ return { name, nested: accessors > (ACCESSORS.has(own) ? 1 : 0) };
94
+ }
95
+ return { name: null, nested: accessors > (ACCESSORS.has(own) ? 1 : 0) };
96
+ }
97
+ function recordRead(store, by) {
98
+ if (!store.readBy.includes(by))
99
+ store.readBy.push(by);
100
+ // Older stores, written by a bundle built before this field existed.
101
+ store.readWhere ??= [];
102
+ const { name, nested } = caller(by.replace(/\(.*$/, ""));
103
+ if (nested)
104
+ return;
105
+ const entry = name ? `${by} in ${name}` : by;
106
+ if (!store.readWhere.includes(entry))
107
+ store.readWhere.push(entry);
108
+ }
33
109
  const globals = globalThis;
34
110
  let ready = null;
35
111
  function scope() {
@@ -38,8 +114,8 @@ function scope() {
38
114
  function slot() {
39
115
  const store = scope()?.getStore();
40
116
  if (!store) {
41
- throw new Error('No request in scope. headers() and cookies() are for a render — middleware, a layout, ' +
42
- 'a page — and there is nothing to read outside one.');
117
+ throw new Error("No request in scope. headers() and cookies() are for a render — middleware, a layout, " +
118
+ "a page — and there is nothing to read outside one.");
43
119
  }
44
120
  store.read = true;
45
121
  return store;
@@ -52,9 +128,9 @@ function slot() {
52
128
  */
53
129
  export async function headers() {
54
130
  const store = slot();
55
- return store.request ? store.headers : never('headers()');
131
+ return store.request ? store.headers : never("headers()");
56
132
  }
57
- const DRAFT = Symbol.for('@rsc-kit/core.response-draft');
133
+ const DRAFT = Symbol.for("@rsc-kit/core.response-draft");
58
134
  let draftReady = null;
59
135
  function draft() {
60
136
  return globals[DRAFT] ?? null;
@@ -64,12 +140,12 @@ function writable(what) {
64
140
  const open = draft()?.getStore();
65
141
  if (!open) {
66
142
  throw new Error(`${what} needs a response that has not been sent. Middleware and server actions have one; ` +
67
- 'a script outside a request does not.');
143
+ "a script outside a request does not.");
68
144
  }
69
145
  if (open.sealed) {
70
146
  throw new Error(`${what} was called after the response had been sent. Headers go out before the render ` +
71
- 'starts — that is what makes the first paint fast — so set them in middleware, which ' +
72
- 'runs before it.');
147
+ "starts — that is what makes the first paint fast — so set them in middleware, which " +
148
+ "runs before it.");
73
149
  }
74
150
  return open;
75
151
  }
@@ -91,12 +167,14 @@ function writable(what) {
91
167
  */
92
168
  function sealable(draft) {
93
169
  const guard = (method) => (...args) => {
94
- writable('responseHeaders()');
170
+ writable("responseHeaders()");
95
171
  return draft.headers[method](...args);
96
172
  };
97
173
  const proxy = new Proxy(draft.headers, {
98
174
  get(target, property, receiver) {
99
- if (property === 'set' || property === 'append' || property === 'delete') {
175
+ if (property === "set" ||
176
+ property === "append" ||
177
+ property === "delete") {
100
178
  return guard(property);
101
179
  }
102
180
  // A maplike forEach passes the object it was called on as the third
@@ -104,17 +182,17 @@ function sealable(draft) {
104
182
  // Headers — a live, unguarded reference handed out by the very method
105
183
  // meant to be read-only. Substituted for the proxy so there is no way
106
184
  // through.
107
- if (property === 'forEach') {
185
+ if (property === "forEach") {
108
186
  return (fn, thisArg) => target.forEach((value, key) => fn.call(thisArg, value, key, proxy));
109
187
  }
110
188
  const value = Reflect.get(target, property, receiver);
111
- return typeof value === 'function' ? value.bind(target) : value;
189
+ return typeof value === "function" ? value.bind(target) : value;
112
190
  },
113
191
  });
114
192
  return proxy;
115
193
  }
116
194
  export function responseHeaders() {
117
- return sealable(writable('responseHeaders()'));
195
+ return sealable(writable("responseHeaders()"));
118
196
  }
119
197
  /**
120
198
  * Run a request with a response that can still be changed, and take what was
@@ -154,26 +232,26 @@ const COOKIE_ATTRIBUTE = /[;,\r\n]/;
154
232
  export function serializeCookie(cookie) {
155
233
  if (!COOKIE_NAME.test(cookie.name)) {
156
234
  throw new Error(`Not a usable cookie name: ${JSON.stringify(cookie.name)}. ` +
157
- 'A name is a token — letters, digits and !#$%&\'*+-.^_`|~ — with no spaces, ' +
158
- 'semicolons or equals signs. Nothing escapes them, so one cannot be encoded around.');
235
+ "A name is a token — letters, digits and !#$%&'*+-.^_`|~ — with no spaces, " +
236
+ "semicolons or equals signs. Nothing escapes them, so one cannot be encoded around.");
159
237
  }
160
238
  if (cookie.options.sameSite !== undefined) {
161
239
  const value = String(cookie.options.sameSite).toLowerCase();
162
- if (value !== 'strict' && value !== 'lax' && value !== 'none') {
240
+ if (value !== "strict" && value !== "lax" && value !== "none") {
163
241
  throw new Error(`Not a SameSite value: ${JSON.stringify(cookie.options.sameSite)}. ` +
164
- 'It is written into the header as given, so anything else becomes further attributes.');
242
+ "It is written into the header as given, so anything else becomes further attributes.");
165
243
  }
166
244
  }
167
245
  for (const [attribute, value] of [
168
- ['path', cookie.options.path],
169
- ['domain', cookie.options.domain],
246
+ ["path", cookie.options.path],
247
+ ["domain", cookie.options.domain],
170
248
  // Trusted because it is typed as a Date — but a cast reaches this, and the
171
249
  // result lands in the header verbatim like the others.
172
- ['expires', cookie.options.expires?.toUTCString()],
250
+ ["expires", cookie.options.expires?.toUTCString()],
173
251
  ]) {
174
- if (typeof value === 'string' && COOKIE_ATTRIBUTE.test(value)) {
252
+ if (typeof value === "string" && COOKIE_ATTRIBUTE.test(value)) {
175
253
  throw new Error(`The cookie ${attribute} ${JSON.stringify(value)} contains a separator. ` +
176
- 'It would be read as further attributes rather than as part of this one.');
254
+ "It would be read as further attributes rather than as part of this one.");
177
255
  }
178
256
  }
179
257
  const parts = [`${cookie.name}=${encodeURIComponent(cookie.value)}`];
@@ -184,31 +262,31 @@ export function serializeCookie(cookie) {
184
262
  parts.push(`Expires=${o.expires.toUTCString()}`);
185
263
  // Defaulted, because a cookie without one is scoped to the path that set it
186
264
  // — a session written by POST /_rsc/action would not be sent for any page.
187
- parts.push(`Path=${o.path ?? '/'}`);
265
+ parts.push(`Path=${o.path ?? "/"}`);
188
266
  if (o.domain)
189
267
  parts.push(`Domain=${o.domain}`);
190
268
  if (o.sameSite)
191
269
  parts.push(`SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`);
192
270
  if (o.secure)
193
- parts.push('Secure');
271
+ parts.push("Secure");
194
272
  if (o.httpOnly)
195
- parts.push('HttpOnly');
273
+ parts.push("HttpOnly");
196
274
  if (o.partitioned)
197
- parts.push('Partitioned');
198
- return parts.join('; ');
275
+ parts.push("Partitioned");
276
+ return parts.join("; ");
199
277
  }
200
278
  export async function cookies() {
201
279
  // Named before the await, because `headers()` never settles during a build —
202
280
  // nothing after this line runs there, and the reason would be recorded as
203
281
  // headers() for a call nobody wrote.
204
282
  const store = slot();
205
- if (!store.request && !store.readBy.includes('cookies()'))
206
- store.readBy.push('cookies()');
207
- const parsed = parseCookies((await headers()).get('cookie') ?? '');
283
+ if (!store.request)
284
+ recordRead(store, "cookies()");
285
+ const parsed = parseCookies((await headers()).get("cookie") ?? "");
208
286
  const write = (name, value, options = {}) => {
209
287
  // Appended, never set: several cookies on one response are several
210
288
  // Set-Cookie headers, and replacing would leave only the last.
211
- writable('cookies().set()').headers.append('Set-Cookie', serializeCookie({ name, value, options }));
289
+ writable("cookies().set()").headers.append("Set-Cookie", serializeCookie({ name, value, options }));
212
290
  };
213
291
  return {
214
292
  get: (name) => parsed[name],
@@ -217,7 +295,7 @@ export async function cookies() {
217
295
  set: write,
218
296
  // Expired rather than removed: a browser drops a cookie when it is told
219
297
  // one has already passed, and there is no other way to say it.
220
- delete: (name, options = {}) => write(name, '', { ...options, maxAge: 0 }),
298
+ delete: (name, options = {}) => write(name, "", { ...options, maxAge: 0 }),
221
299
  };
222
300
  }
223
301
  /**
@@ -228,7 +306,7 @@ export async function cookies() {
228
306
  */
229
307
  export async function request() {
230
308
  const store = slot();
231
- return store.request ? store.original : never('request()');
309
+ return store.request ? store.original : never("request()");
232
310
  }
233
311
  /**
234
312
  * Mark everything below as belonging to the request, not to the build.
@@ -266,17 +344,17 @@ export async function connection() {
266
344
  // No request means a build. Suspend rather than continue, the same way
267
345
  // headers() and cookies() do.
268
346
  if (!store.request)
269
- return never('connection()');
347
+ return never("connection()");
270
348
  }
271
349
  /** The url this request was made to, whichever way the host supplied it. */
272
350
  export async function url() {
273
351
  const store = slot();
274
- return store.request ? store.url : never('url()');
352
+ return store.request ? store.url : never("url()");
275
353
  }
276
354
  function parseCookies(header) {
277
355
  const out = {};
278
- for (const part of header.split(';')) {
279
- const eq = part.indexOf('=');
356
+ for (const part of header.split(";")) {
357
+ const eq = part.indexOf("=");
280
358
  if (eq === -1)
281
359
  continue;
282
360
  const name = part.slice(0, eq).trim();
@@ -312,10 +390,15 @@ export async function withRequest(from, run) {
312
390
  const store = {
313
391
  request: from !== null,
314
392
  url: from ? (isRequest ? from.url : from.url) : null,
315
- headers: from ? (isRequest ? from.headers : new Headers(from.headers)) : new Headers(),
393
+ headers: from
394
+ ? isRequest
395
+ ? from.headers
396
+ : new Headers(from.headers)
397
+ : new Headers(),
316
398
  original: isRequest ? from : null,
317
399
  read: false,
318
400
  readBy: [],
401
+ readWhere: [],
319
402
  };
320
403
  return await scope().run(store, run);
321
404
  }
@@ -342,14 +425,12 @@ export function noteRequestRead(by) {
342
425
  const store = scope()?.getStore();
343
426
  if (!store)
344
427
  return;
345
- if (!store.readBy.includes(by))
346
- store.readBy.push(by);
428
+ recordRead(store, by);
347
429
  }
348
430
  function never(by) {
349
431
  const store = slot();
350
432
  store.read = true;
351
- if (!store.readBy.includes(by))
352
- store.readBy.push(by);
433
+ recordRead(store, by);
353
434
  return new Promise(() => { });
354
435
  }
355
436
  /**
@@ -369,6 +450,11 @@ export function requestWasRead() {
369
450
  export function requestReadBy() {
370
451
  return scope()?.getStore()?.readBy ?? [];
371
452
  }
453
+ /** The same reads, each with the component that made it: `cookies() in RootLayout`. */
454
+ export function requestReadWhere() {
455
+ const store = scope()?.getStore();
456
+ return store?.readWhere?.length ? store.readWhere : (store?.readBy ?? []);
457
+ }
372
458
  /**
373
459
  * The query string, as a page receives it.
374
460
  *
@@ -381,8 +467,8 @@ export async function searchParams() {
381
467
  // Named before the await, so the build reports the call someone wrote rather
382
468
  // than url(), which this happens to be built on.
383
469
  const store = slot();
384
- if (!store.request && !store.readBy.includes('searchParams()')) {
385
- store.readBy.push('searchParams()');
470
+ if (!store.request && !store.readBy.includes("searchParams()")) {
471
+ store.readBy.push("searchParams()");
386
472
  }
387
473
  const from = await url();
388
474
  return from ? new URL(from).searchParams : new URLSearchParams();
@@ -1 +1 @@
1
- {"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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","sourcesContent":["// Reading the request from inside a render.\n//\n// import { headers, cookies } from '@rsc-kit/core/request'\n//\n// export default async function middleware() {\n// const locale = cookies().get('locale') ?? negotiate(headers().get('accept-language'))\n// if (!locale) redirect('/en')\n// }\n//\n// Server components have no arguments beyond their props, and middleware has\n// none at all, so without this an app on a JavaScript host cannot see a header\n// — no locale negotiation, no subdomain, no cookie. The Laravel host escaped\n// that through rpc(), because PHP has the request; this is the same thing for\n// everyone else.\n//\n// Read-only on purpose. Setting a cookie means writing a response header, and\n// by the time a component renders the shell may already have been flushed — an\n// API that appears to set one and silently does not is worse than not having\n// it. Set them in the host, which owns the response.\n//\n// Asynchronous on purpose too, and not for ergonomics. A build has no request,\n// so a read there suspends and never settles — which is the same thing the\n// prerender probe does to a host call. Anything still waiting when the probe's\n// budget expires becomes a fallback in the frozen shell, so a read inside a\n// Suspense boundary leaves the shell frozen and makes only that boundary\n// dynamic. Reading synchronously could only mark the whole route dynamic,\n// because there would be nothing to suspend on.\n//\n// Scoped to the request, on the same store cache() uses. Two requests in\n// flight cannot see each other's headers.\n\nimport { resolveScope } from './revalidate.js'\n\n/** What a host can supply: a real Request, or the parts of one. */\nexport type RequestLike = Request | { url: string; headers: Record<string, string> } | null\n\ninterface Slot {\n /** False during a build: there is nothing to answer with. */\n request: boolean\n url: string | null\n /**\n * Held as Headers rather than as a Request.\n *\n * `new Request(url, { headers })` drops Cookie: a spec-compliant Headers\n * built with the \"request\" guard refuses the forbidden header names, and\n * Cookie is one of them. `new Headers()` has no guard and keeps it. Bun\n * happens to allow both, which is why rebuilding a Request looked fine until\n * it ran anywhere stricter — and what it loses is every cookie, silently.\n */\n headers: Headers\n original: Request | null\n /**\n * Whether anything asked. Only for the build's report — the classification\n * comes from the suspension, not from this.\n */\n read: boolean\n /**\n * What read it, in the order they did.\n *\n * Only the name of the accessor — `cookies()`, `headers()` — which is enough\n * for the build to say why a page could not be frozen. Without it the answer\n * is \"something in this page reached for the request\", and finding which is\n * the part that takes an afternoon.\n */\n readBy: string[]\n}\n\nconst SCOPE = Symbol.for('@rsc-kit/core.request-scope')\n\nconst globals = globalThis as Record<symbol | string, unknown>\n\nlet ready: Promise<void> | null = null\n\ninterface Scope {\n getStore(): Slot | undefined\n run<T>(store: Slot, fn: () => T): T\n}\n\nfunction scope(): Scope | null {\n return (globals[SCOPE] as Scope | undefined) ?? null\n}\n\nfunction slot(): Slot {\n const store = scope()?.getStore()\n\n if (!store) {\n throw new Error(\n 'No request in scope. headers() and cookies() are for a render — middleware, a layout, ' +\n 'a page — and there is nothing to read outside one.',\n )\n }\n\n store.read = true\n\n return store\n}\n\n/**\n * The request's headers.\n *\n * Empty during a build, where there is no request: the page is marked as\n * needing one instead, and rendered on demand rather than frozen.\n */\nexport async function headers(): Promise<Headers> {\n const store = slot()\n\n return store.request ? store.headers : never('headers()')\n}\n\n/** How a cookie should be written. The names browsers use. */\nexport interface CookieOptions {\n path?: string\n domain?: string\n maxAge?: number\n expires?: Date\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n partitioned?: boolean\n}\n\nexport interface Cookies {\n get(name: string): string | undefined\n has(name: string): boolean\n getAll(): Record<string, string>\n /**\n * Write one on the response.\n *\n * Only from a server action, which has a response of its own that has not\n * been sent. A render has already flushed its headers by the time a\n * component runs — that is what makes the first paint fast — so this throws\n * there rather than appearing to work.\n */\n set(name: string, value: string, options?: CookieOptions): void\n /** Write one that expires immediately. Same rule about where. */\n delete(name: string, options?: CookieOptions): void\n}\n\n/** Kept for the name it had when it could only read. */\nexport type ReadonlyCookies = Cookies\n\n/**\n * The response being built, while it can still be changed.\n *\n * Open from the moment a request is picked up until the host constructs its\n * Response, and sealed the instant it does. Middleware runs inside that window\n * — before any rendering starts — so a header set there is on the answer. A\n * component runs after it, during streaming, where the status line and headers\n * are already on the wire; setting one there is refused rather than dropped.\n */\ninterface Draft {\n headers: Headers\n sealed: boolean\n}\n\nconst DRAFT = Symbol.for('@rsc-kit/core.response-draft')\n\nlet draftReady: Promise<void> | null = null\n\ninterface DraftScope {\n getStore(): Draft | undefined\n run<T>(store: Draft, fn: () => T): T\n}\n\nfunction draft(): DraftScope | null {\n return (globals[DRAFT] as DraftScope | undefined) ?? null\n}\n\n/** The open draft, or an explanation of why there is not one. */\nfunction writable(what: string): Draft {\n const open = draft()?.getStore()\n\n if (!open) {\n throw new Error(\n `${what} needs a response that has not been sent. Middleware and server actions have one; ` +\n 'a script outside a request does not.',\n )\n }\n\n if (open.sealed) {\n throw new Error(\n `${what} was called after the response had been sent. Headers go out before the render ` +\n 'starts — that is what makes the first paint fast — so set them in middleware, which ' +\n 'runs before it.',\n )\n }\n\n return open\n}\n\n/**\n * Headers to put on the answer.\n *\n * Mutate it as you would any Headers. `append` is the one to reach for when a\n * header may legitimately appear twice; `set` replaces.\n */\n/**\n * The draft's headers, refusing writes once the response has gone.\n *\n * A proxy rather than the Headers itself: returning the live object means code\n * that captured it before the response was built can go on mutating a thing\n * nobody will read. Nothing in this package does that — the host copies the\n * headers onto the response the instant it seals — but \"safe because of the\n * order two other functions happen to run in\" is not a property, and an app\n * holding the object across an await would find writes silently doing nothing.\n */\nfunction sealable(draft: Draft): Headers {\n const guard = (method: 'set' | 'append' | 'delete') =>\n (...args: [string, string]) => {\n writable('responseHeaders()')\n\n return (draft.headers[method] as (...a: string[]) => void)(...args)\n }\n\n const proxy: Headers = new Proxy(draft.headers, {\n get(target, property, receiver) {\n if (property === 'set' || property === 'append' || property === 'delete') {\n return guard(property)\n }\n\n // A maplike forEach passes the object it was called on as the third\n // callback argument, and binding to the target makes that the raw\n // Headers — a live, unguarded reference handed out by the very method\n // meant to be read-only. Substituted for the proxy so there is no way\n // through.\n if (property === 'forEach') {\n return (fn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown) =>\n target.forEach((value, key) => fn.call(thisArg, value, key, proxy))\n }\n\n const value = Reflect.get(target, property, receiver)\n\n return typeof value === 'function' ? value.bind(target) : value\n },\n })\n\n return proxy\n}\n\nexport function responseHeaders(): Headers {\n return sealable(writable('responseHeaders()'))\n}\n\n/**\n * Run a request with a response that can still be changed, and take what was\n * put on it.\n *\n * `seal()` is called by the host the moment it builds the Response, so\n * anything set afterwards is refused instead of silently going nowhere.\n */\nexport async function withResponseDraft<T>(\n run: (draft: { taken: () => Headers; seal: () => void }) => Promise<T>,\n): Promise<T> {\n if (!globals[DRAFT]) {\n draftReady ??= resolveScope().then((resolved) => {\n globals[DRAFT] ??= resolved as unknown as DraftScope\n })\n\n await draftReady\n }\n\n const open: Draft = { headers: new Headers(), sealed: false }\n\n return await draft()!.run(open, () =>\n run({\n taken: () => open.headers,\n seal: () => {\n open.sealed = true\n },\n }),\n )\n}\n\n/**\n * A cookie name, as RFC 6265 defines one: a token, so no separators at all.\n *\n * Checked rather than escaped because there is no escaping — a name is not a\n * quoted string. An app that derives one from user input (`pref_${key}`) would\n * otherwise let that input close the pair and open another: a name of\n * `session=attacker; Path=/; HttpOnly; x` serializes to a header whose *first*\n * pair is a session cookie the caller chose, and the browser reads the first.\n */\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** No separator may appear in an attribute either, for the same reason. */\nconst COOKIE_ATTRIBUTE = /[;,\\r\\n]/\n\n/** `name=value; Path=/; HttpOnly` — the header a browser expects. */\nexport function serializeCookie(cookie: { name: string; value: string; options: CookieOptions }): string {\n if (!COOKIE_NAME.test(cookie.name)) {\n throw new Error(\n `Not a usable cookie name: ${JSON.stringify(cookie.name)}. ` +\n 'A name is a token — letters, digits and !#$%&\\'*+-.^_`|~ — with no spaces, ' +\n 'semicolons or equals signs. Nothing escapes them, so one cannot be encoded around.',\n )\n }\n\n if (cookie.options.sameSite !== undefined) {\n const value = String(cookie.options.sameSite).toLowerCase()\n\n if (value !== 'strict' && value !== 'lax' && value !== 'none') {\n throw new Error(\n `Not a SameSite value: ${JSON.stringify(cookie.options.sameSite)}. ` +\n 'It is written into the header as given, so anything else becomes further attributes.',\n )\n }\n }\n\n for (const [attribute, value] of [\n ['path', cookie.options.path],\n ['domain', cookie.options.domain],\n // Trusted because it is typed as a Date — but a cast reaches this, and the\n // result lands in the header verbatim like the others.\n ['expires', cookie.options.expires?.toUTCString()],\n ] as const) {\n if (typeof value === 'string' && COOKIE_ATTRIBUTE.test(value)) {\n throw new Error(\n `The cookie ${attribute} ${JSON.stringify(value)} contains a separator. ` +\n 'It would be read as further attributes rather than as part of this one.',\n )\n }\n }\n\n const parts = [`${cookie.name}=${encodeURIComponent(cookie.value)}`]\n const o = cookie.options\n\n if (o.maxAge !== undefined) parts.push(`Max-Age=${Math.floor(o.maxAge)}`)\n if (o.expires) parts.push(`Expires=${o.expires.toUTCString()}`)\n // Defaulted, because a cookie without one is scoped to the path that set it\n // — a session written by POST /_rsc/action would not be sent for any page.\n parts.push(`Path=${o.path ?? '/'}`)\n if (o.domain) parts.push(`Domain=${o.domain}`)\n if (o.sameSite) parts.push(`SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`)\n if (o.secure) parts.push('Secure')\n if (o.httpOnly) parts.push('HttpOnly')\n if (o.partitioned) parts.push('Partitioned')\n\n return parts.join('; ')\n}\n\nexport async function cookies(): Promise<Cookies> {\n // Named before the await, because `headers()` never settles during a build —\n // nothing after this line runs there, and the reason would be recorded as\n // headers() for a call nobody wrote.\n const store = slot()\n\n if (!store.request && !store.readBy.includes('cookies()')) store.readBy.push('cookies()')\n\n const parsed = parseCookies((await headers()).get('cookie') ?? '')\n\n const write = (name: string, value: string, options: CookieOptions = {}): void => {\n // Appended, never set: several cookies on one response are several\n // Set-Cookie headers, and replacing would leave only the last.\n writable('cookies().set()').headers.append(\n 'Set-Cookie',\n serializeCookie({ name, value, options }),\n )\n }\n\n return {\n get: (name) => parsed[name],\n has: (name) => name in parsed,\n getAll: () => ({ ...parsed }),\n set: write,\n // Expired rather than removed: a browser drops a cookie when it is told\n // one has already passed, and there is no other way to say it.\n delete: (name, options = {}) => write(name, '', { ...options, maxAge: 0 }),\n }\n}\n\n/**\n * The whole request, for a host-specific need the accessors do not cover.\n *\n * Null on a host that forwards only the parts — the worker behind Laravel has\n * a socket, not a request. Use headers() and cookies(), which work everywhere.\n */\nexport async function request(): Promise<Request | null> {\n const store = slot()\n\n return store.request ? store.original : never('request()')\n}\n\n/**\n * Mark everything below as belonging to the request, not to the build.\n *\n * export default async function Orders() {\n * await connection()\n *\n * const rows = await db.query('select * from orders')\n *\n * return <ul>{rows.map(...)}</ul>\n * }\n *\n * At build time this never resolves, so nothing after it runs: the query is not\n * made, the boundary above becomes a hole, and the rest of the page still\n * freezes. At request time it resolves immediately and the component runs\n * normally.\n *\n * **Once is enough.** It is a barrier, not a wrapper — everything after it in\n * this component belongs to the request, however many calls that is. Repeating\n * it before each query does nothing.\n *\n * It needs a boundary above it. With a <Suspense> or a loading.tsx there is a\n * fallback to store and the page becomes a shell; with neither, nothing can\n * paint and the build refuses the route rather than storing a blank.\n *\n * Reach for it when the build should not run something — a database the build\n * machine cannot see, or a value that must differ per visitor. A query the\n * build CAN run, whose answer is the same for everyone, wants none of this: it\n * should be frozen.\n *\n * The same name and behaviour as Next's `connection()`.\n */\nexport async function connection(): Promise<void> {\n const store = slot()\n\n // No request means a build. Suspend rather than continue, the same way\n // headers() and cookies() do.\n if (!store.request) return never('connection()')\n}\n\n/** The url this request was made to, whichever way the host supplied it. */\nexport async function url(): Promise<string | null> {\n const store = slot()\n\n return store.request ? store.url : never('url()')\n}\n\nfunction parseCookies(header: string): Record<string, string> {\n const out: Record<string, string> = {}\n\n for (const part of header.split(';')) {\n const eq = part.indexOf('=')\n\n if (eq === -1) continue\n\n const name = part.slice(0, eq).trim()\n\n if (!name) continue\n\n // Decoded because that is how they were written. A malformed escape is\n // left as it arrived rather than throwing the render away.\n const raw = part.slice(eq + 1).trim()\n\n try {\n out[name] = decodeURIComponent(raw)\n } catch {\n out[name] = raw\n }\n }\n\n return out\n}\n\n/**\n * Run one request with its headers readable.\n *\n * `null` is the build: a scope with nothing in it, so a page that reads is\n * caught rather than silently frozen holding whatever the machine that built\n * it happened to send.\n */\nexport async function withRequest<T>(from: RequestLike, run: () => Promise<T>): Promise<T> {\n if (!globals[SCOPE]) {\n ready ??= resolveScope().then((resolved) => {\n globals[SCOPE] ??= resolved as unknown as Scope\n })\n\n await ready\n }\n\n const isRequest = from instanceof Request\n const store: Slot = {\n request: from !== null,\n url: from ? (isRequest ? from.url : from.url) : null,\n headers: from ? (isRequest ? from.headers : new Headers(from.headers)) : new Headers(),\n original: isRequest ? from : null,\n read: false,\n readBy: [],\n }\n\n return await scope()!.run(store, run)\n}\n\n/**\n * A promise that never settles, so the caller suspends.\n *\n * What a read does during a build. React treats it exactly as it treats a host\n * call that never answers: the component suspends, its Suspense fallback goes\n * into the shell, and the probe's budget decides the rest.\n */\n/**\n * Record a read without suspending on it.\n *\n * For a reader that CAN answer during a build but whose answer would be wrong\n * to store — an api route's query string, which exists and is empty, and which\n * the build has no business freezing an answer to. `never()` is for a reader\n * with nothing to return; this is for one that has something and should still\n * be noticed.\n *\n * A no-op outside a render scope, so a handler called at request time pays a\n * property read and nothing else.\n */\nexport function noteRequestRead(by: string): void {\n const store = scope()?.getStore()\n\n if (!store) return\n\n if (!store.readBy.includes(by)) store.readBy.push(by)\n}\n\nfunction never(by: string): Promise<never> {\n const store = slot()\n\n store.read = true\n\n if (!store.readBy.includes(by)) store.readBy.push(by)\n\n return new Promise(() => {})\n}\n\n/**\n * Whether anything read the request during the scope that is open.\n *\n * For the prerenderer, which opens one with no request and asks afterwards.\n */\nexport function requestWasRead(): boolean {\n return scope()?.getStore()?.read ?? false\n}\n\n/**\n * What reached for the request during the scope that is open.\n *\n * For the build, which asks afterwards so it can say why a page is rendered per\n * visitor rather than only that it is.\n */\nexport function requestReadBy(): string[] {\n return scope()?.getStore()?.readBy ?? []\n}\n\n/**\n * The query string, as a page receives it.\n *\n * Derived from the request rather than passed down the render, so no host has\n * to forward it separately — and so it suspends during a build for the same\n * reason every other read does. A frozen page cannot know the query it will be\n * asked for.\n */\nexport async function searchParams(): Promise<URLSearchParams> {\n // Named before the await, so the build reports the call someone wrote rather\n // than url(), which this happens to be built on.\n const store = slot()\n\n if (!store.request && !store.readBy.includes('searchParams()')) {\n store.readBy.push('searchParams()')\n }\n\n const from = await url()\n\n return from ? new URL(from).searchParams : new URLSearchParams()\n}\n"]}
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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","sourcesContent":["// Reading the request from inside a render.\n//\n// import { headers, cookies } from '@rsc-kit/core/request'\n//\n// export default async function middleware() {\n// const locale = cookies().get('locale') ?? negotiate(headers().get('accept-language'))\n// if (!locale) redirect('/en')\n// }\n//\n// Server components have no arguments beyond their props, and middleware has\n// none at all, so without this an app on a JavaScript host cannot see a header\n// — no locale negotiation, no subdomain, no cookie. The Laravel host escaped\n// that through rpc(), because PHP has the request; this is the same thing for\n// everyone else.\n//\n// Read-only on purpose. Setting a cookie means writing a response header, and\n// by the time a component renders the shell may already have been flushed — an\n// API that appears to set one and silently does not is worse than not having\n// it. Set them in the host, which owns the response.\n//\n// Asynchronous on purpose too, and not for ergonomics. A build has no request,\n// so a read there suspends and never settles — which is the same thing the\n// prerender probe does to a host call. Anything still waiting when the probe's\n// budget expires becomes a fallback in the frozen shell, so a read inside a\n// Suspense boundary leaves the shell frozen and makes only that boundary\n// dynamic. Reading synchronously could only mark the whole route dynamic,\n// because there would be nothing to suspend on.\n//\n// Scoped to the request, on the same store cache() uses. Two requests in\n// flight cannot see each other's headers.\n\nimport { resolveScope } from \"./revalidate.js\";\n\n/** What a host can supply: a real Request, or the parts of one. */\nexport type RequestLike =\n Request | { url: string; headers: Record<string, string> } | null;\n\ninterface Slot {\n /** False during a build: there is nothing to answer with. */\n request: boolean;\n url: string | null;\n /**\n * Held as Headers rather than as a Request.\n *\n * `new Request(url, { headers })` drops Cookie: a spec-compliant Headers\n * built with the \"request\" guard refuses the forbidden header names, and\n * Cookie is one of them. `new Headers()` has no guard and keeps it. Bun\n * happens to allow both, which is why rebuilding a Request looked fine until\n * it ran anywhere stricter — and what it loses is every cookie, silently.\n */\n headers: Headers;\n original: Request | null;\n /**\n * Whether anything asked. Only for the build's report — the classification\n * comes from the suspension, not from this.\n */\n read: boolean;\n /**\n * What read it, in the order they did.\n *\n * Only the name of the accessor — `cookies()`, `headers()` — which is enough\n * for the build to say why a page could not be frozen. Without it the answer\n * is \"something in this page reached for the request\", and finding which is\n * the part that takes an afternoon.\n */\n readBy: string[];\n /**\n * The same reads with the component that made each one - `cookies() in\n * RootLayout` - taken from the stack at the moment of the read, which is\n * still the component's own call: an accessor runs synchronously up to\n * the point it suspends. Only the build reads this. \"Reads the request\" is\n * a category; this is the line to open.\n */\n readWhere: string[];\n}\n\nconst SCOPE = Symbol.for(\"@rsc-kit/core.request-scope\");\n\n/**\n * The accessors themselves and the plumbing under them: frames to walk past\n * on the way to the component that called.\n */\nconst NOT_A_CALLER = new Set([\n \"never\",\n \"noteRequestRead\",\n \"recordRead\",\n \"caller\",\n \"slot\",\n \"cookies\",\n \"headers\",\n \"connection\",\n \"searchParams\",\n \"request\",\n \"url\",\n \"parseParams\",\n \"parseSearchParams\",\n \"parseBody\",\n \"get\",\n \"set\",\n \"has\",\n]);\n\n/** The accessors a component calls by name. One calling another is plumbing, not a second read. */\nconst ACCESSORS = new Set([\n \"cookies\",\n \"headers\",\n \"connection\",\n \"searchParams\",\n \"request\",\n \"url\",\n]);\n\n/**\n * The nearest named function above the accessor on the stack, and whether\n * another accessor sits between: `cookies()` reads through `headers()`, and\n * that inner read is not a second thing the component did.\n *\n * Read from a thrown-away Error: the one thing a build cannot otherwise learn\n * is which component reached for the request, and the stack knows. Function\n * names survive a server bundle, which is not minified, so this is\n * `RootLayout` and not a line number in a chunk. Internal names are walked\n * past; an anonymous frame is skipped rather than reported as `<anonymous>`.\n */\nfunction caller(own: string): { name: string | null; nested: boolean } {\n const stack = new Error().stack ?? \"\";\n let accessors = 0;\n\n for (const line of stack.split(\"\\n\")) {\n const match = /^\\s*at (?:async )?([^\\s(]+)/.exec(line);\n\n if (!match) continue;\n\n const name = match[1]!.split(\".\").pop()!;\n\n if (ACCESSORS.has(name)) accessors++;\n if (\n NOT_A_CALLER.has(name) ||\n name === \"<anonymous>\" ||\n name === \"Object\" ||\n name === \"Promise\"\n )\n continue;\n if (/^[a-z_$]/.test(name) && !/^use[A-Z]/.test(name)) continue;\n\n return { name, nested: accessors > (ACCESSORS.has(own) ? 1 : 0) };\n }\n\n return { name: null, nested: accessors > (ACCESSORS.has(own) ? 1 : 0) };\n}\n\nfunction recordRead(store: Slot, by: string): void {\n if (!store.readBy.includes(by)) store.readBy.push(by);\n\n // Older stores, written by a bundle built before this field existed.\n store.readWhere ??= [];\n\n const { name, nested } = caller(by.replace(/\\(.*$/, \"\"));\n\n if (nested) return;\n\n const entry = name ? `${by} in ${name}` : by;\n\n if (!store.readWhere.includes(entry)) store.readWhere.push(entry);\n}\n\nconst globals = globalThis as Record<symbol | string, unknown>;\n\nlet ready: Promise<void> | null = null;\n\ninterface Scope {\n getStore(): Slot | undefined;\n run<T>(store: Slot, fn: () => T): T;\n}\n\nfunction scope(): Scope | null {\n return (globals[SCOPE] as Scope | undefined) ?? null;\n}\n\nfunction slot(): Slot {\n const store = scope()?.getStore();\n\n if (!store) {\n throw new Error(\n \"No request in scope. headers() and cookies() are for a render — middleware, a layout, \" +\n \"a page — and there is nothing to read outside one.\",\n );\n }\n\n store.read = true;\n\n return store;\n}\n\n/**\n * The request's headers.\n *\n * Empty during a build, where there is no request: the page is marked as\n * needing one instead, and rendered on demand rather than frozen.\n */\nexport async function headers(): Promise<Headers> {\n const store = slot();\n\n return store.request ? store.headers : never(\"headers()\");\n}\n\n/** How a cookie should be written. The names browsers use. */\nexport interface CookieOptions {\n path?: string;\n domain?: string;\n maxAge?: number;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"strict\" | \"lax\" | \"none\";\n partitioned?: boolean;\n}\n\nexport interface Cookies {\n get(name: string): string | undefined;\n has(name: string): boolean;\n getAll(): Record<string, string>;\n /**\n * Write one on the response.\n *\n * Only from a server action, which has a response of its own that has not\n * been sent. A render has already flushed its headers by the time a\n * component runs — that is what makes the first paint fast — so this throws\n * there rather than appearing to work.\n */\n set(name: string, value: string, options?: CookieOptions): void;\n /** Write one that expires immediately. Same rule about where. */\n delete(name: string, options?: CookieOptions): void;\n}\n\n/** Kept for the name it had when it could only read. */\nexport type ReadonlyCookies = Cookies;\n\n/**\n * The response being built, while it can still be changed.\n *\n * Open from the moment a request is picked up until the host constructs its\n * Response, and sealed the instant it does. Middleware runs inside that window\n * — before any rendering starts — so a header set there is on the answer. A\n * component runs after it, during streaming, where the status line and headers\n * are already on the wire; setting one there is refused rather than dropped.\n */\ninterface Draft {\n headers: Headers;\n sealed: boolean;\n}\n\nconst DRAFT = Symbol.for(\"@rsc-kit/core.response-draft\");\n\nlet draftReady: Promise<void> | null = null;\n\ninterface DraftScope {\n getStore(): Draft | undefined;\n run<T>(store: Draft, fn: () => T): T;\n}\n\nfunction draft(): DraftScope | null {\n return (globals[DRAFT] as DraftScope | undefined) ?? null;\n}\n\n/** The open draft, or an explanation of why there is not one. */\nfunction writable(what: string): Draft {\n const open = draft()?.getStore();\n\n if (!open) {\n throw new Error(\n `${what} needs a response that has not been sent. Middleware and server actions have one; ` +\n \"a script outside a request does not.\",\n );\n }\n\n if (open.sealed) {\n throw new Error(\n `${what} was called after the response had been sent. Headers go out before the render ` +\n \"starts — that is what makes the first paint fast — so set them in middleware, which \" +\n \"runs before it.\",\n );\n }\n\n return open;\n}\n\n/**\n * Headers to put on the answer.\n *\n * Mutate it as you would any Headers. `append` is the one to reach for when a\n * header may legitimately appear twice; `set` replaces.\n */\n/**\n * The draft's headers, refusing writes once the response has gone.\n *\n * A proxy rather than the Headers itself: returning the live object means code\n * that captured it before the response was built can go on mutating a thing\n * nobody will read. Nothing in this package does that — the host copies the\n * headers onto the response the instant it seals — but \"safe because of the\n * order two other functions happen to run in\" is not a property, and an app\n * holding the object across an await would find writes silently doing nothing.\n */\nfunction sealable(draft: Draft): Headers {\n const guard =\n (method: \"set\" | \"append\" | \"delete\") =>\n (...args: [string, string]) => {\n writable(\"responseHeaders()\");\n\n return (draft.headers[method] as (...a: string[]) => void)(...args);\n };\n\n const proxy: Headers = new Proxy(draft.headers, {\n get(target, property, receiver) {\n if (\n property === \"set\" ||\n property === \"append\" ||\n property === \"delete\"\n ) {\n return guard(property);\n }\n\n // A maplike forEach passes the object it was called on as the third\n // callback argument, and binding to the target makes that the raw\n // Headers — a live, unguarded reference handed out by the very method\n // meant to be read-only. Substituted for the proxy so there is no way\n // through.\n if (property === \"forEach\") {\n return (\n fn: (value: string, key: string, parent: Headers) => void,\n thisArg?: unknown,\n ) =>\n target.forEach((value, key) => fn.call(thisArg, value, key, proxy));\n }\n\n const value = Reflect.get(target, property, receiver);\n\n return typeof value === \"function\" ? value.bind(target) : value;\n },\n });\n\n return proxy;\n}\n\nexport function responseHeaders(): Headers {\n return sealable(writable(\"responseHeaders()\"));\n}\n\n/**\n * Run a request with a response that can still be changed, and take what was\n * put on it.\n *\n * `seal()` is called by the host the moment it builds the Response, so\n * anything set afterwards is refused instead of silently going nowhere.\n */\nexport async function withResponseDraft<T>(\n run: (draft: { taken: () => Headers; seal: () => void }) => Promise<T>,\n): Promise<T> {\n if (!globals[DRAFT]) {\n draftReady ??= resolveScope().then((resolved) => {\n globals[DRAFT] ??= resolved as unknown as DraftScope;\n });\n\n await draftReady;\n }\n\n const open: Draft = { headers: new Headers(), sealed: false };\n\n return await draft()!.run(open, () =>\n run({\n taken: () => open.headers,\n seal: () => {\n open.sealed = true;\n },\n }),\n );\n}\n\n/**\n * A cookie name, as RFC 6265 defines one: a token, so no separators at all.\n *\n * Checked rather than escaped because there is no escaping — a name is not a\n * quoted string. An app that derives one from user input (`pref_${key}`) would\n * otherwise let that input close the pair and open another: a name of\n * `session=attacker; Path=/; HttpOnly; x` serializes to a header whose *first*\n * pair is a session cookie the caller chose, and the browser reads the first.\n */\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** No separator may appear in an attribute either, for the same reason. */\nconst COOKIE_ATTRIBUTE = /[;,\\r\\n]/;\n\n/** `name=value; Path=/; HttpOnly` — the header a browser expects. */\nexport function serializeCookie(cookie: {\n name: string;\n value: string;\n options: CookieOptions;\n}): string {\n if (!COOKIE_NAME.test(cookie.name)) {\n throw new Error(\n `Not a usable cookie name: ${JSON.stringify(cookie.name)}. ` +\n \"A name is a token — letters, digits and !#$%&'*+-.^_`|~ — with no spaces, \" +\n \"semicolons or equals signs. Nothing escapes them, so one cannot be encoded around.\",\n );\n }\n\n if (cookie.options.sameSite !== undefined) {\n const value = String(cookie.options.sameSite).toLowerCase();\n\n if (value !== \"strict\" && value !== \"lax\" && value !== \"none\") {\n throw new Error(\n `Not a SameSite value: ${JSON.stringify(cookie.options.sameSite)}. ` +\n \"It is written into the header as given, so anything else becomes further attributes.\",\n );\n }\n }\n\n for (const [attribute, value] of [\n [\"path\", cookie.options.path],\n [\"domain\", cookie.options.domain],\n // Trusted because it is typed as a Date — but a cast reaches this, and the\n // result lands in the header verbatim like the others.\n [\"expires\", cookie.options.expires?.toUTCString()],\n ] as const) {\n if (typeof value === \"string\" && COOKIE_ATTRIBUTE.test(value)) {\n throw new Error(\n `The cookie ${attribute} ${JSON.stringify(value)} contains a separator. ` +\n \"It would be read as further attributes rather than as part of this one.\",\n );\n }\n }\n\n const parts = [`${cookie.name}=${encodeURIComponent(cookie.value)}`];\n const o = cookie.options;\n\n if (o.maxAge !== undefined) parts.push(`Max-Age=${Math.floor(o.maxAge)}`);\n if (o.expires) parts.push(`Expires=${o.expires.toUTCString()}`);\n // Defaulted, because a cookie without one is scoped to the path that set it\n // — a session written by POST /_rsc/action would not be sent for any page.\n parts.push(`Path=${o.path ?? \"/\"}`);\n if (o.domain) parts.push(`Domain=${o.domain}`);\n if (o.sameSite)\n parts.push(`SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`);\n if (o.secure) parts.push(\"Secure\");\n if (o.httpOnly) parts.push(\"HttpOnly\");\n if (o.partitioned) parts.push(\"Partitioned\");\n\n return parts.join(\"; \");\n}\n\nexport async function cookies(): Promise<Cookies> {\n // Named before the await, because `headers()` never settles during a build —\n // nothing after this line runs there, and the reason would be recorded as\n // headers() for a call nobody wrote.\n const store = slot();\n\n if (!store.request) recordRead(store, \"cookies()\");\n\n const parsed = parseCookies((await headers()).get(\"cookie\") ?? \"\");\n\n const write = (\n name: string,\n value: string,\n options: CookieOptions = {},\n ): void => {\n // Appended, never set: several cookies on one response are several\n // Set-Cookie headers, and replacing would leave only the last.\n writable(\"cookies().set()\").headers.append(\n \"Set-Cookie\",\n serializeCookie({ name, value, options }),\n );\n };\n\n return {\n get: (name) => parsed[name],\n has: (name) => name in parsed,\n getAll: () => ({ ...parsed }),\n set: write,\n // Expired rather than removed: a browser drops a cookie when it is told\n // one has already passed, and there is no other way to say it.\n delete: (name, options = {}) => write(name, \"\", { ...options, maxAge: 0 }),\n };\n}\n\n/**\n * The whole request, for a host-specific need the accessors do not cover.\n *\n * Null on a host that forwards only the parts — the worker behind Laravel has\n * a socket, not a request. Use headers() and cookies(), which work everywhere.\n */\nexport async function request(): Promise<Request | null> {\n const store = slot();\n\n return store.request ? store.original : never(\"request()\");\n}\n\n/**\n * Mark everything below as belonging to the request, not to the build.\n *\n * export default async function Orders() {\n * await connection()\n *\n * const rows = await db.query('select * from orders')\n *\n * return <ul>{rows.map(...)}</ul>\n * }\n *\n * At build time this never resolves, so nothing after it runs: the query is not\n * made, the boundary above becomes a hole, and the rest of the page still\n * freezes. At request time it resolves immediately and the component runs\n * normally.\n *\n * **Once is enough.** It is a barrier, not a wrapper — everything after it in\n * this component belongs to the request, however many calls that is. Repeating\n * it before each query does nothing.\n *\n * It needs a boundary above it. With a <Suspense> or a loading.tsx there is a\n * fallback to store and the page becomes a shell; with neither, nothing can\n * paint and the build refuses the route rather than storing a blank.\n *\n * Reach for it when the build should not run something — a database the build\n * machine cannot see, or a value that must differ per visitor. A query the\n * build CAN run, whose answer is the same for everyone, wants none of this: it\n * should be frozen.\n *\n * The same name and behaviour as Next's `connection()`.\n */\nexport async function connection(): Promise<void> {\n const store = slot();\n\n // No request means a build. Suspend rather than continue, the same way\n // headers() and cookies() do.\n if (!store.request) return never(\"connection()\");\n}\n\n/** The url this request was made to, whichever way the host supplied it. */\nexport async function url(): Promise<string | null> {\n const store = slot();\n\n return store.request ? store.url : never(\"url()\");\n}\n\nfunction parseCookies(header: string): Record<string, string> {\n const out: Record<string, string> = {};\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n\n if (eq === -1) continue;\n\n const name = part.slice(0, eq).trim();\n\n if (!name) continue;\n\n // Decoded because that is how they were written. A malformed escape is\n // left as it arrived rather than throwing the render away.\n const raw = part.slice(eq + 1).trim();\n\n try {\n out[name] = decodeURIComponent(raw);\n } catch {\n out[name] = raw;\n }\n }\n\n return out;\n}\n\n/**\n * Run one request with its headers readable.\n *\n * `null` is the build: a scope with nothing in it, so a page that reads is\n * caught rather than silently frozen holding whatever the machine that built\n * it happened to send.\n */\nexport async function withRequest<T>(\n from: RequestLike,\n run: () => Promise<T>,\n): Promise<T> {\n if (!globals[SCOPE]) {\n ready ??= resolveScope().then((resolved) => {\n globals[SCOPE] ??= resolved as unknown as Scope;\n });\n\n await ready;\n }\n\n const isRequest = from instanceof Request;\n const store: Slot = {\n request: from !== null,\n url: from ? (isRequest ? from.url : from.url) : null,\n headers: from\n ? isRequest\n ? from.headers\n : new Headers(from.headers)\n : new Headers(),\n original: isRequest ? from : null,\n read: false,\n readBy: [],\n readWhere: [],\n };\n\n return await scope()!.run(store, run);\n}\n\n/**\n * A promise that never settles, so the caller suspends.\n *\n * What a read does during a build. React treats it exactly as it treats a host\n * call that never answers: the component suspends, its Suspense fallback goes\n * into the shell, and the probe's budget decides the rest.\n */\n/**\n * Record a read without suspending on it.\n *\n * For a reader that CAN answer during a build but whose answer would be wrong\n * to store — an api route's query string, which exists and is empty, and which\n * the build has no business freezing an answer to. `never()` is for a reader\n * with nothing to return; this is for one that has something and should still\n * be noticed.\n *\n * A no-op outside a render scope, so a handler called at request time pays a\n * property read and nothing else.\n */\nexport function noteRequestRead(by: string): void {\n const store = scope()?.getStore();\n\n if (!store) return;\n\n recordRead(store, by);\n}\n\nfunction never(by: string): Promise<never> {\n const store = slot();\n\n store.read = true;\n recordRead(store, by);\n\n return new Promise(() => {});\n}\n\n/**\n * Whether anything read the request during the scope that is open.\n *\n * For the prerenderer, which opens one with no request and asks afterwards.\n */\nexport function requestWasRead(): boolean {\n return scope()?.getStore()?.read ?? false;\n}\n\n/**\n * What reached for the request during the scope that is open.\n *\n * For the build, which asks afterwards so it can say why a page is rendered per\n * visitor rather than only that it is.\n */\nexport function requestReadBy(): string[] {\n return scope()?.getStore()?.readBy ?? [];\n}\n\n/** The same reads, each with the component that made it: `cookies() in RootLayout`. */\nexport function requestReadWhere(): string[] {\n const store = scope()?.getStore();\n\n return store?.readWhere?.length ? store.readWhere : (store?.readBy ?? []);\n}\n\n/**\n * The query string, as a page receives it.\n *\n * Derived from the request rather than passed down the render, so no host has\n * to forward it separately — and so it suspends during a build for the same\n * reason every other read does. A frozen page cannot know the query it will be\n * asked for.\n */\nexport async function searchParams(): Promise<URLSearchParams> {\n // Named before the await, so the build reports the call someone wrote rather\n // than url(), which this happens to be built on.\n const store = slot();\n\n if (!store.request && !store.readBy.includes(\"searchParams()\")) {\n store.readBy.push(\"searchParams()\");\n }\n\n const from = await url();\n\n return from ? new URL(from).searchParams : new URLSearchParams();\n}\n"]}
package/dist/routing.d.ts CHANGED
@@ -20,13 +20,6 @@ export interface MatchedApiRoute {
20
20
  route: ManifestApiRoute;
21
21
  params: Record<string, string>;
22
22
  }
23
- /**
24
- * The api route a url answers, if one does.
25
- *
26
- * Exact segment binding only, scored the same way pages are: more static
27
- * segments wins, a catch-all is weakest. Reusing that is the point — an api
28
- * route takes `[id]` and `[...rest]` because the matcher was already there.
29
- */
30
23
  export declare function matchApiRoute(manifest: RouteManifest, pathname: string): MatchedApiRoute | null;
31
24
  /**
32
25
  * What a 405 should say this route does answer.
package/dist/routing.js CHANGED
@@ -9,26 +9,56 @@
9
9
  // Every host would otherwise write this, and would get the same two things
10
10
  // wrong: answering /docs/new with [slug], and treating the layout chain as a
11
11
  // set rather than a sequence.
12
- /**
13
- * The api route a url answers, if one does.
14
- *
15
- * Exact segment binding only, scored the same way pages are: more static
16
- * segments wins, a catch-all is weakest. Reusing that is the point — an api
17
- * route takes `[id]` and `[...rest]` because the matcher was already there.
18
- */
12
+ const indexes = new WeakMap();
13
+ const apiIndexes = new WeakMap();
14
+ function indexRoutes(routes) {
15
+ const scored = [];
16
+ const exact = new Map();
17
+ for (const route of routes) {
18
+ const score = route.segments.reduce((n, s) => n + (s.type === 'static' ? 2 : s.type === 'param' ? 1 : 0), 0);
19
+ scored.push({ route, score });
20
+ // An all-static route matches exactly one path, with the highest score any
21
+ // route can have for that path. First one in wins, as in the scan.
22
+ if (route.segments.every((s) => s.type === 'static')) {
23
+ const key = route.segments.map((s) => s.value).join('/');
24
+ if (!exact.has(key))
25
+ exact.set(key, route);
26
+ }
27
+ }
28
+ return { scored, exact };
29
+ }
30
+ function indexed(manifest) {
31
+ let found = indexes.get(manifest);
32
+ if (!found) {
33
+ found = indexRoutes(manifest.routes);
34
+ indexes.set(manifest, found);
35
+ }
36
+ return found;
37
+ }
38
+ function indexedApis(manifest) {
39
+ let found = apiIndexes.get(manifest);
40
+ if (!found) {
41
+ found = indexRoutes(manifest.apis ?? []);
42
+ apiIndexes.set(manifest, found);
43
+ }
44
+ return found;
45
+ }
19
46
  export function matchApiRoute(manifest, pathname) {
20
47
  const parts = pathname.split('/').filter(Boolean);
48
+ const { scored, exact } = indexedApis(manifest);
49
+ const direct = exact.get(parts.join('/'));
50
+ if (direct)
51
+ return { route: direct, params: {} };
21
52
  let best = null;
22
53
  let bestScore = -1;
23
- for (const route of manifest.apis ?? []) {
54
+ for (const { route, score } of scored) {
55
+ if (score <= bestScore)
56
+ continue;
24
57
  const bound = bindSegments(route.segments, parts);
25
58
  if (!bound)
26
59
  continue;
27
- const score = route.segments.reduce((n, s) => n + (s.type === 'static' ? 2 : s.type === 'param' ? 1 : 0), 0);
28
- if (score > bestScore) {
29
- best = { route, params: bound };
30
- bestScore = score;
31
- }
60
+ best = { route, params: bound };
61
+ bestScore = score;
32
62
  }
33
63
  return best;
34
64
  }
@@ -45,18 +75,22 @@ export function allowFor(route) {
45
75
  }
46
76
  export function matchRoute(manifest, pathname) {
47
77
  const parts = pathname.split('/').filter(Boolean);
78
+ const { scored, exact } = indexed(manifest);
79
+ const direct = exact.get(parts.join('/'));
80
+ if (direct)
81
+ return { route: direct, params: {} };
48
82
  let best = null;
49
83
  let bestScore = -1;
50
- for (const route of manifest.routes) {
84
+ // More static segments wins; a catch-all is the weakest possible match. A
85
+ // route that could not beat the best so far is not worth binding.
86
+ for (const { route, score } of scored) {
87
+ if (score <= bestScore)
88
+ continue;
51
89
  const bound = bindSegments(route.segments, parts);
52
90
  if (!bound)
53
91
  continue;
54
- // More static segments wins; a catch-all is the weakest possible match.
55
- const score = route.segments.reduce((n, s) => n + (s.type === 'static' ? 2 : s.type === 'param' ? 1 : 0), 0);
56
- if (score > bestScore) {
57
- best = { route, params: bound };
58
- bestScore = score;
59
- }
92
+ best = { route, params: bound };
93
+ bestScore = score;
60
94
  }
61
95
  return best;
62
96
  }