@rshono/core 1.0.0-rc.17 → 1.0.0-rc.19
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/README.md +34 -4
- package/dist/builder/env-shadow-loader.cjs +58 -2
- package/dist/builder/rspack-config.d.ts.map +1 -1
- package/dist/builder/rspack-config.js +51 -7
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/builder/server-only-imports.d.ts +33 -0
- package/dist/builder/server-only-imports.d.ts.map +1 -0
- package/dist/builder/server-only-imports.js +51 -0
- package/dist/builder/server-only-imports.js.map +1 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +35 -3
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +49 -0
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/index.js +14 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts.map +1 -1
- package/dist/deploy/cloudflare/runtime.js +31 -5
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +6 -2
- package/dist/deploy/contract.d.ts.map +1 -1
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts.map +1 -1
- package/dist/deploy/filesystem.js +2 -1
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts.map +1 -1
- package/dist/deploy/node/runtime.js +3 -4
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +8 -0
- package/dist/deploy/presets.d.ts.map +1 -1
- package/dist/deploy/presets.js +1 -0
- package/dist/deploy/presets.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +93 -25
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +11 -4
- package/dist/runtime/boundaries.d.ts.map +1 -1
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/context.d.ts +60 -9
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +61 -8
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/entry.client.js +156 -21
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts +7 -0
- package/dist/runtime/entry.rsc.d.ts.map +1 -1
- package/dist/runtime/entry.rsc.js +202 -57
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/flight-inject.d.ts +1 -1
- package/dist/runtime/flight-inject.d.ts.map +1 -1
- package/dist/runtime/flight-inject.js +207 -40
- package/dist/runtime/flight-inject.js.map +1 -1
- package/dist/runtime/request.d.ts.map +1 -1
- package/dist/runtime/request.js +10 -0
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/validate-entries.d.ts +78 -0
- package/dist/runtime/validate-entries.d.ts.map +1 -0
- package/dist/runtime/validate-entries.js +301 -0
- package/dist/runtime/validate-entries.js.map +1 -0
- package/dist/server/prerendered.d.ts +30 -11
- package/dist/server/prerendered.d.ts.map +1 -1
- package/dist/server/prerendered.js +72 -14
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +20 -1
- package/dist/server/server-config.d.ts.map +1 -1
- package/dist/server/server-config.js +27 -1
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/ssg.d.ts.map +1 -1
- package/dist/server/ssg.js +170 -27
- package/dist/server/ssg.js.map +1 -1
- package/dist/server/static.d.ts.map +1 -1
- package/dist/server/static.js +7 -1
- package/dist/server/static.js.map +1 -1
- package/package.json +5 -4
|
@@ -81,6 +81,14 @@ export type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | und
|
|
|
81
81
|
* Never construct it yourself. One instance is reused for the whole request, so its lazy getters
|
|
82
82
|
* ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.
|
|
83
83
|
*
|
|
84
|
+
* The eight members that only throw — `redirect`, `notFound`, `json`, `text`, `html`, `body`, `status`,
|
|
85
|
+
* `header` — are **permanent, and exist to throw**. Every one of them is a silent no-op when reached through
|
|
86
|
+
* {@link RequestContext.hono} from a page, so a stub that names the thing that does work is the difference
|
|
87
|
+
* between a message and a page that renders while quietly ignoring half of what it was asked for. They carry
|
|
88
|
+
* `@deprecated` for the strike-through an editor draws with it, not because they are on the way out: nothing
|
|
89
|
+
* will un-deprecate or remove them, and dropping them would leave `ctx.redirect('/x')` as
|
|
90
|
+
* "property does not exist", which says what is wrong and not what to do.
|
|
91
|
+
*
|
|
84
92
|
* @typeParam E - The Hono {@link Env} describing this app's `Bindings` and `Variables`, so
|
|
85
93
|
* {@link RequestContext.var} and {@link RequestContext.env} stay typed.
|
|
86
94
|
*
|
|
@@ -178,8 +186,15 @@ export declare class RequestContext<E extends Env = Env> {
|
|
|
178
186
|
*/
|
|
179
187
|
get var(): Readonly<E['Variables']>;
|
|
180
188
|
/**
|
|
181
|
-
* Environment for the request: process env vars merged with
|
|
182
|
-
* Computed once and cached.
|
|
189
|
+
* Environment for the request: process env vars, merged on a bindings platform with the bindings, which
|
|
190
|
+
* win on conflict. Computed once and cached.
|
|
191
|
+
*
|
|
192
|
+
* Bindings are merged **only where the platform supplies them** — `deploy: 'cloudflare'`, today. Hono's
|
|
193
|
+
* `c.env` is whatever the host passed as the second argument to `app.fetch`, and off Workers that is the
|
|
194
|
+
* adapter's own private state: `{ incoming, outgoing }` on Node and Vercel, the entire invocation —
|
|
195
|
+
* headers, cookies, `authorization` — on Lambda. Merging it would make `ctx.env` uncloneable on one and
|
|
196
|
+
* a disclosure vector on the other, both behind names this type declares `string | undefined`. Reach for
|
|
197
|
+
* {@link RequestContext.hono}`.env` if you really do want the adapter's argument.
|
|
183
198
|
*
|
|
184
199
|
* @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`
|
|
185
200
|
*
|
|
@@ -345,15 +360,41 @@ export declare function notFound(): never;
|
|
|
345
360
|
* - `request` — anything else that reached the top-level handler, a thrown endpoint route included.
|
|
346
361
|
*/
|
|
347
362
|
export type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';
|
|
348
|
-
/**
|
|
349
|
-
|
|
363
|
+
/**
|
|
364
|
+
* What an {@link ServerErrorHandler} is told about an error, beyond the error itself.
|
|
365
|
+
*
|
|
366
|
+
* @typeParam E - The app's Hono {@link Env}, to type {@link ServerErrorContext.hono}'s `var` and `env`.
|
|
367
|
+
*/
|
|
368
|
+
export interface ServerErrorContext<E extends Env = Env> {
|
|
350
369
|
/** The stage that produced it — see {@link ServerErrorSource}. */
|
|
351
370
|
source: ServerErrorSource;
|
|
352
371
|
/** The request being served, for the URL, method and headers. */
|
|
353
372
|
request: Request;
|
|
373
|
+
/**
|
|
374
|
+
* The Hono {@link Context} for this request — `hono.var` for whatever middleware put there, such as a
|
|
375
|
+
* request id to correlate the report on, and `hono.env` for a platform that passes bindings.
|
|
376
|
+
*
|
|
377
|
+
* Handed over rather than left to {@link getRequestContext}, which a handler cannot reach: an error with
|
|
378
|
+
* `source: 'request'` is reported from the top-level handler, which runs outside the ambient context.
|
|
379
|
+
*/
|
|
380
|
+
hono: Context<E>;
|
|
381
|
+
/**
|
|
382
|
+
* Holds the invocation open until `promise` settles, where the platform has something to ask.
|
|
383
|
+
*
|
|
384
|
+
* Reporting is what this hook exists for, and on a serverless platform a report started here is cut off
|
|
385
|
+
* the moment the response ends unless something keeps the invocation alive. On Cloudflare Workers that is
|
|
386
|
+
* `executionCtx.waitUntil`, which this calls. On the `node` and `vercel` targets there is nothing to hold
|
|
387
|
+
* open — the process outlives the response — so it is a no-op and the report finishes on its own. On
|
|
388
|
+
* `aws-lambda` it is a no-op as well, because `hono/aws-lambda`'s streaming handler exposes no execution
|
|
389
|
+
* context to ask; a slow report there is best-effort, so prefer a tracker that batches over one that
|
|
390
|
+
* round-trips per error.
|
|
391
|
+
*
|
|
392
|
+
* A rejection is logged rather than propagated: reporting can never fail a request.
|
|
393
|
+
*/
|
|
394
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
354
395
|
}
|
|
355
396
|
/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */
|
|
356
|
-
export type ServerErrorHandler = (error: unknown, context: ServerErrorContext) => void;
|
|
397
|
+
export type ServerErrorHandler<E extends Env = Env> = (error: unknown, context: ServerErrorContext<E>) => void;
|
|
357
398
|
/**
|
|
358
399
|
* Registers a handler for every error the framework catches, so they can reach an error tracker
|
|
359
400
|
* (Sentry, Datadog, a log pipeline) instead of only `stderr`.
|
|
@@ -362,27 +403,37 @@ export type ServerErrorHandler = (error: unknown, context: ServerErrorContext) =
|
|
|
362
403
|
* Registering again replaces the previous handler. Errors still go to `stderr` either way, and a
|
|
363
404
|
* handler that throws is caught and logged — reporting can never fail a request.
|
|
364
405
|
*
|
|
406
|
+
* @typeParam E - The app's Hono {@link Env}, to type the `hono` context the handler is given.
|
|
407
|
+
*
|
|
365
408
|
* @example
|
|
366
409
|
* ```ts
|
|
367
410
|
* // src/server.ts
|
|
368
411
|
* import * as Sentry from '@sentry/node';
|
|
369
412
|
* import { onServerError } from '@rshono/core/server';
|
|
370
413
|
*
|
|
371
|
-
* onServerError((error, { source, request }) => {
|
|
372
|
-
*
|
|
414
|
+
* onServerError((error, { source, request, hono, waitUntil }) => {
|
|
415
|
+
* // `waitUntil` so a serverless invocation is not frozen before the report is sent.
|
|
416
|
+
* waitUntil(
|
|
417
|
+
* Sentry.captureException(error, {
|
|
418
|
+
* tags: { source, requestId: hono.var.requestId },
|
|
419
|
+
* extra: { url: request.url },
|
|
420
|
+
* }),
|
|
421
|
+
* );
|
|
373
422
|
* });
|
|
374
423
|
* ```
|
|
375
424
|
*
|
|
376
425
|
* @see {@link https://www.rshono.com/docs/hono#error-reporting | Docs — error reporting}
|
|
377
426
|
*/
|
|
378
|
-
export declare function onServerError(handler: ServerErrorHandler): void;
|
|
427
|
+
export declare function onServerError<E extends Env = Env>(handler: ServerErrorHandler<E>): void;
|
|
379
428
|
/**
|
|
380
429
|
* Logs an error and forwards it to the registered {@link ServerErrorHandler} — the single funnel every
|
|
381
430
|
* caught server-side error goes through.
|
|
382
431
|
*
|
|
383
432
|
* @internal
|
|
384
433
|
*/
|
|
385
|
-
export declare function reportServerError(error: unknown, info:
|
|
434
|
+
export declare function reportServerError(error: unknown, info: {
|
|
435
|
+
source: ServerErrorSource;
|
|
436
|
+
hono: Context;
|
|
386
437
|
message: string;
|
|
387
438
|
}): void;
|
|
388
439
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAWzD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhD;AAkCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM7D;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/context.js
CHANGED
|
@@ -81,6 +81,8 @@ function firstForwardedValue(header) {
|
|
|
81
81
|
// Read through `typeof`: DefinePlugin inlines this into the server bundle, but the module is also the
|
|
82
82
|
// public `@rshono/core/server` entry, which tooling can load without one. Absent means don't trust.
|
|
83
83
|
const trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;
|
|
84
|
+
// Read the same way, for the same reason. Absent means the platform passes no bindings — see the getter.
|
|
85
|
+
const envBindings = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.envBindings;
|
|
84
86
|
/**
|
|
85
87
|
* The browser-facing {@link URL} for a request, resolved from Hono's {@link Context} — a fresh
|
|
86
88
|
* instance per call.
|
|
@@ -132,6 +134,14 @@ export function publicUrl(c) {
|
|
|
132
134
|
* Never construct it yourself. One instance is reused for the whole request, so its lazy getters
|
|
133
135
|
* ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.
|
|
134
136
|
*
|
|
137
|
+
* The eight members that only throw — `redirect`, `notFound`, `json`, `text`, `html`, `body`, `status`,
|
|
138
|
+
* `header` — are **permanent, and exist to throw**. Every one of them is a silent no-op when reached through
|
|
139
|
+
* {@link RequestContext.hono} from a page, so a stub that names the thing that does work is the difference
|
|
140
|
+
* between a message and a page that renders while quietly ignoring half of what it was asked for. They carry
|
|
141
|
+
* `@deprecated` for the strike-through an editor draws with it, not because they are on the way out: nothing
|
|
142
|
+
* will un-deprecate or remove them, and dropping them would leave `ctx.redirect('/x')` as
|
|
143
|
+
* "property does not exist", which says what is wrong and not what to do.
|
|
144
|
+
*
|
|
135
145
|
* @typeParam E - The Hono {@link Env} describing this app's `Bindings` and `Variables`, so
|
|
136
146
|
* {@link RequestContext.var} and {@link RequestContext.env} stay typed.
|
|
137
147
|
*
|
|
@@ -248,8 +258,15 @@ export class RequestContext {
|
|
|
248
258
|
return this.#raw.var;
|
|
249
259
|
}
|
|
250
260
|
/**
|
|
251
|
-
* Environment for the request: process env vars merged with
|
|
252
|
-
* Computed once and cached.
|
|
261
|
+
* Environment for the request: process env vars, merged on a bindings platform with the bindings, which
|
|
262
|
+
* win on conflict. Computed once and cached.
|
|
263
|
+
*
|
|
264
|
+
* Bindings are merged **only where the platform supplies them** — `deploy: 'cloudflare'`, today. Hono's
|
|
265
|
+
* `c.env` is whatever the host passed as the second argument to `app.fetch`, and off Workers that is the
|
|
266
|
+
* adapter's own private state: `{ incoming, outgoing }` on Node and Vercel, the entire invocation —
|
|
267
|
+
* headers, cookies, `authorization` — on Lambda. Merging it would make `ctx.env` uncloneable on one and
|
|
268
|
+
* a disclosure vector on the other, both behind names this type declares `string | undefined`. Reach for
|
|
269
|
+
* {@link RequestContext.hono}`.env` if you really do want the adapter's argument.
|
|
253
270
|
*
|
|
254
271
|
* @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`
|
|
255
272
|
*
|
|
@@ -259,7 +276,7 @@ export class RequestContext {
|
|
|
259
276
|
get env() {
|
|
260
277
|
if (this.#env)
|
|
261
278
|
return this.#env;
|
|
262
|
-
const bindings = this.#raw.env;
|
|
279
|
+
const bindings = envBindings ? this.#raw.env : undefined;
|
|
263
280
|
return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()));
|
|
264
281
|
}
|
|
265
282
|
/**
|
|
@@ -343,8 +360,9 @@ export class RequestContext {
|
|
|
343
360
|
this.#raw.header(name, value, options);
|
|
344
361
|
}
|
|
345
362
|
// Hono's response builders, restated as errors naming what to use instead — through `ctx.hono` every
|
|
346
|
-
// one of them is a silent no-op from a page.
|
|
347
|
-
// unread `..._args` is so `ctx.redirect('/x')` reaches the
|
|
363
|
+
// one of them is a silent no-op from a page. Permanent, deliberately: see the class doc. `@deprecated`
|
|
364
|
+
// strikes them through in autocomplete; the unread `..._args` is so `ctx.redirect('/x')` reaches the
|
|
365
|
+
// thrown message rather than an arity error.
|
|
348
366
|
/** @deprecated Not available on a page's context — use `redirect()` from `@rshono/core/server`. */
|
|
349
367
|
redirect(..._args) {
|
|
350
368
|
return notOnContext('redirect(location, status?)', "Use `redirect()` from '@rshono/core/server', which throws a signal the framework turns into a real redirect.");
|
|
@@ -462,6 +480,26 @@ export function notFound() {
|
|
|
462
480
|
throw new NotFoundSignal();
|
|
463
481
|
}
|
|
464
482
|
let errorHandler;
|
|
483
|
+
/**
|
|
484
|
+
* The platform's "keep this invocation alive" hook, or a no-op where there is none.
|
|
485
|
+
*
|
|
486
|
+
* `c.executionCtx` *throws* rather than answering `undefined` where a platform has no execution context, so
|
|
487
|
+
* a handler that reached for it itself would have its report swallowed by the guard in
|
|
488
|
+
* {@link reportServerError} — on exactly the platforms where nothing needed holding open.
|
|
489
|
+
*/
|
|
490
|
+
function keepAlive(c, promise) {
|
|
491
|
+
// Caught here rather than left to the platform: under `--unhandled-rejections=strict` a rejected report
|
|
492
|
+
// would end the process, and a failed report must never be worse than no report.
|
|
493
|
+
const settled = Promise.resolve(promise).catch((error) => {
|
|
494
|
+
console.error('[rshono] a promise passed to the onServerError waitUntil rejected:', error);
|
|
495
|
+
});
|
|
496
|
+
try {
|
|
497
|
+
c.executionCtx.waitUntil(settled);
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// No execution context: nothing here cuts the work off, so there is nothing to hold open.
|
|
501
|
+
}
|
|
502
|
+
}
|
|
465
503
|
/**
|
|
466
504
|
* Errors already forwarded, so one fault is reported once however many stages it crosses.
|
|
467
505
|
*
|
|
@@ -478,14 +516,22 @@ const alreadyReported = new WeakSet();
|
|
|
478
516
|
* Registering again replaces the previous handler. Errors still go to `stderr` either way, and a
|
|
479
517
|
* handler that throws is caught and logged — reporting can never fail a request.
|
|
480
518
|
*
|
|
519
|
+
* @typeParam E - The app's Hono {@link Env}, to type the `hono` context the handler is given.
|
|
520
|
+
*
|
|
481
521
|
* @example
|
|
482
522
|
* ```ts
|
|
483
523
|
* // src/server.ts
|
|
484
524
|
* import * as Sentry from '@sentry/node';
|
|
485
525
|
* import { onServerError } from '@rshono/core/server';
|
|
486
526
|
*
|
|
487
|
-
* onServerError((error, { source, request }) => {
|
|
488
|
-
*
|
|
527
|
+
* onServerError((error, { source, request, hono, waitUntil }) => {
|
|
528
|
+
* // `waitUntil` so a serverless invocation is not frozen before the report is sent.
|
|
529
|
+
* waitUntil(
|
|
530
|
+
* Sentry.captureException(error, {
|
|
531
|
+
* tags: { source, requestId: hono.var.requestId },
|
|
532
|
+
* extra: { url: request.url },
|
|
533
|
+
* }),
|
|
534
|
+
* );
|
|
489
535
|
* });
|
|
490
536
|
* ```
|
|
491
537
|
*
|
|
@@ -512,7 +558,14 @@ export function reportServerError(error, info) {
|
|
|
512
558
|
if (!errorHandler)
|
|
513
559
|
return;
|
|
514
560
|
try {
|
|
515
|
-
errorHandler(error, {
|
|
561
|
+
errorHandler(error, {
|
|
562
|
+
source: info.source,
|
|
563
|
+
request: info.hono.req.raw,
|
|
564
|
+
// The handler was registered for the app's own `Env`, which `onServerError` erased to store it; this
|
|
565
|
+
// puts the context back in the shape it was registered with. Same trade as {@link getRequestContext}.
|
|
566
|
+
hono: info.hono,
|
|
567
|
+
waitUntil: (promise) => keepAlive(info.hono, promise),
|
|
568
|
+
});
|
|
516
569
|
}
|
|
517
570
|
catch (handlerError) {
|
|
518
571
|
console.error('[rshono] the onServerError handler threw:', handlerError);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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","sourcesContent":["// `__RSHONO_CONFIG__` is a global const, and an `import` cannot bring one into scope — a path reference is\n// the only way to reach it, which is the whole reason that file is separate. See its header.\n// eslint-disable-next-line @typescript-eslint/triple-slash-reference\n/// <reference path=\"../types/rshono-config.d.ts\" />\n/**\n * The request context: {@link getRequestContext} and the {@link RequestContext} it returns, the\n * {@link redirect} / {@link notFound} control-flow helpers, and the {@link onServerError} reporting\n * funnel — plus the `@internal` plumbing that binds a request to the async context.\n *\n * The public half is re-exported by `runtime/server.ts`, which is what `@rshono/core/server` resolves\n * to; an app imports that.\n */\n\nimport type { Context, Env } from 'hono';\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie';\nimport type { CookieOptions } from 'hono/utils/cookie';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { NotFoundSignal, RedirectSignal } from './control.js';\n\n/**\n * HTTP status codes accepted by {@link redirect}.\n *\n * - `301` Moved Permanently, `308` Permanent Redirect — cacheable, permanent.\n * - `302` Found, `307` Temporary Redirect — temporary.\n * - `303` See Other — the default; forces a `GET` on the target, which is what you almost always want\n * after a form action (post/redirect/get).\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#redirection_messages | MDN — redirection status codes}\n */\nexport type RedirectStatus = 301 | 302 | 303 | 307 | 308;\n\nconst contextStorage = new AsyncLocalStorage<Context>();\n\n/** One {@link RequestContext} per Hono {@link Context}, so repeated `getRequestContext()` calls share its lazy getters. */\nconst wrappers = new WeakMap<Context, RequestContext>();\n\n// Keyed on the Hono context rather than held as a field, so marking a request never forces the lazily\n// built `RequestContext` wrapper into existence.\nconst rendering = new WeakSet<Context>();\n\n/**\n * Marks the request as having entered its page render, which is what makes\n * {@link RequestContext.setHeader} and `ctx.cookies.set()` start throwing.\n *\n * @internal\n */\nexport function beginPageRender(c: Context): void {\n rendering.add(c);\n}\n\n/** The shared refusal for a response write that arrived too late — the message names where it belongs instead. */\nfunction tooLateToWrite(call: string): never {\n throw new Error(\n `[rshono] ${call} was called while rendering a page, which is too late to affect the response. ` +\n 'A page streams, so its response head is already committed by the time the component runs — the ' +\n 'write would land on a full page load and be silently dropped on a soft navigation. Do it from a ' +\n \"'use server' action instead; or, in middleware and { type: 'endpoint' } routes — which are handed \" +\n \"Hono's `c` directly and run outside the request context — with `c.header(…)` / `setCookie(c, …)`.\",\n );\n}\n\n/** The shared refusal for a Hono `Context` member a page has no way to use. See the stubs on {@link RequestContext}. */\nfunction notOnContext(call: string, instead: string): never {\n throw new Error(\n `[rshono] ctx.${call} does not exist. A page returns JSX and the framework builds the response from it, ` +\n `so Hono's response builders have nothing to return to. ${instead}`,\n );\n}\n\n// Snapshotted rather than spread per request: enumerating `process.env` crosses into the host\n// environment (~20µs). Lazily, because `loadEnvFiles()` runs after this module is imported — so a\n// mutation after the first `ctx.env` read is not picked up.\nlet envSnapshot: Record<string, string | undefined> | undefined;\n\nfunction processEnv(): Record<string, string | undefined> {\n return (envSnapshot ??= typeof process !== 'undefined' && process.env ? { ...process.env } : {});\n}\n\n// Set by `build.ts` before it imports the app bundle, which inlines its own copy of this module — so\n// `process.env` is what crosses that boundary rather than a module-level flag.\nconst prerendering = typeof process !== 'undefined' && !!process.env?.RSHONO_PRERENDER;\n\n/**\n * Runs `fn` with `c` bound as the ambient request context, so {@link getRequestContext} resolves to it\n * anywhere in the call tree.\n *\n * @internal\n */\nexport function runWithContext<T>(c: Context, fn: () => T): T {\n return contextStorage.run(c, fn);\n}\n\n/**\n * The matched route params, or an empty object when there is no active match.\n *\n * @internal\n */\nexport function readParams(c: Context): Record<string, string> {\n try {\n return c.req.param();\n } catch {\n return {};\n }\n}\n\n/** A proxy chain appends to these headers, so the client-facing value is the first entry. */\nfunction firstForwardedValue(header: string | undefined): string | undefined {\n const first = header?.split(',')[0]?.trim();\n return first || undefined;\n}\n\n// Read through `typeof`: DefinePlugin inlines this into the server bundle, but the module is also the\n// public `@rshono/core/server` entry, which tooling can load without one. Absent means don't trust.\nconst trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;\n\n/**\n * The browser-facing {@link URL} for a request, resolved from Hono's {@link Context} — a fresh\n * instance per call.\n *\n * `c.req.url` is the internal address the server was reached on, which is wrong behind a proxy;\n * `X-Forwarded-Host` / `-Proto` correct it, but only when `trustProxy` is enabled in\n * `rshono.config.ts` — they are client-supplied, so trusting them unconditionally would let anyone\n * dictate the origin of every absolute URL the app builds.\n *\n * This is the form for **middleware**, which is handed `c` and runs outside the request context — and\n * so the way to give Hono's own middleware the origin the browser actually used. In a server component\n * or action, prefer {@link RequestContext.url}, the same value cached per request.\n *\n * @param c - The Hono {@link Context} for the request.\n * @returns The browser-facing URL — proxy-corrected under `trustProxy`, `c.req.url` otherwise.\n *\n * @example\n * ```ts\n * // src/server.ts — a CSRF check that still works behind a proxy that rewrites Host\n * import { publicUrl } from '@rshono/core/server';\n * import { csrf } from 'hono/csrf';\n *\n * server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin }));\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\nexport function publicUrl(c: Context): URL {\n const url = new URL(c.req.url);\n if (!trustProxy) return url;\n\n const forwardedHost = firstForwardedValue(c.req.header('x-forwarded-host'));\n // Parsed, not assigned to `url.host`: that setter keeps the existing port when the new value has none.\n const forwarded = forwardedHost ? URL.parse(`http://${forwardedHost}`) : null;\n if (forwarded) {\n url.hostname = forwarded.hostname;\n url.port = forwarded.port;\n }\n\n // Only the two schemes a browser could have requested; anything else leaves the scheme alone.\n const forwardedProto = firstForwardedValue(c.req.header('x-forwarded-proto'));\n if (forwardedProto === 'http' || forwardedProto === 'https') url.protocol = forwardedProto;\n\n return url;\n}\n\n/**\n * The environment available to a request: Workers `Bindings` merged with process env vars. Values not\n * declared in `Bindings` are typed as `string | undefined`. See {@link RequestContext.env}.\n *\n * @see {@link https://hono.dev/docs/getting-started/cloudflare-workers#bindings | Hono — bindings}\n */\nexport type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | undefined>;\n\n/**\n * Read-mostly wrapper around Hono's {@link Context}, for server components and server actions.\n *\n * Obtain one with {@link getRequestContext}, or take it off a page's `ctx` prop — the same object.\n * Never construct it yourself. One instance is reused for the whole request, so its lazy getters\n * ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.\n *\n * @typeParam E - The Hono {@link Env} describing this app's `Bindings` and `Variables`, so\n * {@link RequestContext.var} and {@link RequestContext.env} stay typed.\n *\n * @example\n * ```tsx\n * import { getRequestContext } from '@rshono/core/server';\n *\n * export default async function Whoami() {\n * const ctx = getRequestContext();\n * const session = ctx.cookies.get('session');\n * return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}, reachable in full via {@link RequestContext.hono}\n */\nexport class RequestContext<E extends Env = Env> {\n #raw: Context<E>;\n #url?: URL;\n #env?: EnvVars<E>;\n #params?: Record<string, string>;\n\n /**\n * One instance is created per request and handed out by {@link getRequestContext} or the `ctx` page\n * prop. Application code never calls this.\n *\n * @internal\n */\n constructor(c: Context<E>) {\n this.#raw = c;\n }\n\n /**\n * The underlying Hono {@link Context} — the escape hatch for what this wrapper does not expose, such\n * as `executionCtx.waitUntil()` on Workers.\n *\n * Its response builders (`redirect`, `json`, `body`, `status`, …) still do nothing from inside a\n * page: reaching them through here bypasses the errors the stubs on this class throw, it does not\n * make them work.\n *\n * @example\n * ```ts\n * getRequestContext().hono.executionCtx.waitUntil(logAsync()); // Workers\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n // Every member here is a getter or method so that none is *own enumerable*: React's \"you cannot pass\n // this to a client component\" diagnostic walks `Object.keys` recursively with no cycle guard, and the\n // Hono context graph reaches the socket through `req.raw`. `cookies` is the one own property, and it\n // is a shallow object of four functions.\n get hono(): Context<E> {\n return this.#raw;\n }\n\n /**\n * The parsed request — method, headers, path params, query and the body readers. Hono's\n * {@link Context.req}, unwrapped, so `ctx.req.header('authorization')` rather than\n * `ctx.hono.req.header(…)`.\n *\n * Reads only; setting a *response* header is {@link RequestContext.setHeader}, deliberately spelled\n * differently.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.req.method; // 'GET'\n * ctx.req.header('authorization'); // string | undefined\n * ctx.req.query('tab'); // string | undefined\n * ```\n *\n * @see {@link https://hono.dev/docs/api/request | Hono — HonoRequest}\n */\n get req(): Context<E>['req'] {\n return this.#raw.req;\n }\n\n /**\n * Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. Empty when no route\n * matched.\n *\n * A page is handed the same record as its `params` prop, typed key-by-key from its route path, and\n * that is the better read where it exists. This is for everywhere else — a nested server component,\n * or a `'use server'` action.\n */\n get params(): Record<string, string> {\n return (this.#params ??= readParams(this.#raw as Context));\n }\n\n /**\n * The browser-facing request URL. Parsed once and cached, so every read within a request returns the\n * same instance — treat it as read-only.\n *\n * `X-Forwarded-Host` / `-Proto` are honoured only when `trustProxy` is enabled in\n * `rshono.config.ts`, since any client can send them.\n *\n * @example `const tab = getRequestContext().url.searchParams.get('tab');`\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n get url(): URL {\n return (this.#url ??= publicUrl(this.#raw as Context));\n }\n\n /**\n * Typed variables set by middleware via `c.set('user', …)`, read here as `ctx.var.user`. Type them by\n * parameterising this class's {@link Env}.\n *\n * @example\n * ```ts\n * type AppEnv = { Variables: { user: { id: string } } };\n * const { user } = getRequestContext<AppEnv>().var; // typed, set by your middleware\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context#var | Hono — c.var}\n * @see {@link https://www.rshono.com/docs/hono#typing-the-context | Docs — typing the context}\n */\n get var(): Readonly<E['Variables']> {\n return this.#raw.var;\n }\n\n /**\n * Environment for the request: process env vars merged with runtime bindings, which win on conflict.\n * Computed once and cached.\n *\n * @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`\n *\n * @see {@link https://hono.dev/docs/api/context#env | Hono — c.env}\n * @see {@link https://www.rshono.com/docs/configuration#environment-and-secrets | Docs — environment and secrets}\n */\n get env(): EnvVars<E> {\n if (this.#env) return this.#env;\n const bindings = this.#raw.env as Record<string, unknown> | undefined;\n return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()) as EnvVars<E>);\n }\n\n /**\n * Read and write request/response cookies.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.cookies.get('session'); // string | undefined\n * ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });\n * ctx.cookies.delete('session', { path: '/' });\n * ```\n *\n * @see {@link https://hono.dev/docs/helpers/cookie | Hono — cookie helper}, which this wraps\n */\n cookies = {\n /** Reads a single cookie by name, or `undefined` if absent. Safe anywhere, a page included. */\n get: (name: string): string | undefined => getCookie(this.#raw, name),\n /** Reads every cookie as a `{ name: value }` record. Safe anywhere, a page included. */\n all: (): Record<string, string> => getCookie(this.#raw),\n /**\n * Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`\n * and the rest.\n *\n * **Throws inside a page render** — a `Set-Cookie` is a special case of\n * {@link RequestContext.setHeader}. Set cookies from a `'use server'` action, or with Hono's\n * `setCookie(c, …)` in middleware and endpoint routes.\n *\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @see {@link https://hono.dev/docs/helpers/cookie#options | Hono — cookie options}\n */\n set: (name: string, value: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.set()');\n setCookie(this.#raw, name, value, options);\n },\n /**\n * Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it.\n * Throws inside a page render, exactly as `set` does.\n *\n * @throws If called while a page is rendering.\n */\n delete: (name: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.delete()');\n deleteCookie(this.#raw, name, options);\n },\n };\n\n #assertWritable(call: string): void {\n if (rendering.has(this.#raw as Context)) tooLateToWrite(call);\n }\n\n /**\n * Sets a header on the response — from a `'use server'` action, which is the one place a request\n * context exists *and* the response is still open.\n *\n * From inside a page it throws: a page streams, so its response head is already committed by then,\n * and the write would land on a full page load but vanish on a soft navigation.\n *\n * Middleware and `{ type: 'endpoint' }` routes are handed Hono's `c` directly and use `c.header(…)`.\n * That is also where a header belonging to the *page* goes — `Cache-Control`, `X-Robots-Tag` — since\n * middleware runs before the render.\n *\n * @param name - Header name, case-insensitive.\n * @param value - Header value.\n * @param options - `{ append: true }` to add another value rather than replace.\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @example\n * ```ts\n * 'use server';\n * export async function logout() {\n * const ctx = getRequestContext();\n * ctx.cookies.delete('session', { path: '/' });\n * ctx.setHeader('clear-site-data', '\"cache\", \"storage\"');\n * redirect('/');\n * }\n * ```\n */\n setHeader(name: string, value: string, options?: { append?: boolean }): void {\n this.#assertWritable('ctx.setHeader()');\n this.#raw.header(name, value, options);\n }\n\n // Hono's response builders, restated as errors naming what to use instead — through `ctx.hono` every\n // one of them is a silent no-op from a page. `@deprecated` strikes them through in autocomplete; the\n // unread `..._args` is so `ctx.redirect('/x')` reaches the thrown message rather than an arity error.\n\n /** @deprecated Not available on a page's context — use `redirect()` from `@rshono/core/server`. */\n redirect(..._args: unknown[]): never {\n return notOnContext(\n 'redirect(location, status?)',\n \"Use `redirect()` from '@rshono/core/server', which throws a signal the framework turns into a real redirect.\",\n );\n }\n\n /** @deprecated Not available on a page's context — use `notFound()` from `@rshono/core/server`. */\n notFound(..._args: unknown[]): never {\n return notOnContext('notFound()', \"Use `notFound()` from '@rshono/core/server', which aborts the render and shows the app's not-found page.\");\n }\n\n /** @deprecated A page renders JSX. For a JSON response, use an `{ type: 'endpoint' }` route. */\n json(..._args: unknown[]): never {\n return notOnContext('json(object)', \"For a JSON response use an { type: 'endpoint' } route; to read the request body use `ctx.req.json()`.\");\n }\n\n /** @deprecated A page renders JSX. For a text response, use an `{ type: 'endpoint' }` route. */\n text(..._args: unknown[]): never {\n return notOnContext('text(string)', \"For a text response use an { type: 'endpoint' } route; to read the request body use `ctx.req.text()`.\");\n }\n\n /** @deprecated A page renders JSX, which the framework turns into HTML for you. */\n html(..._args: unknown[]): never {\n return notOnContext('html(string)', \"A page's JSX is already its HTML; for a hand-built HTML response use an { type: 'endpoint' } route.\");\n }\n\n /** @deprecated Not available on a page's context. To read the request body, use `ctx.req`. */\n body(..._args: unknown[]): never {\n return notOnContext(\n 'body(data, …)',\n \"To read the *request* body use `ctx.req.json()` / `ctx.req.text()` / `ctx.req.formData()`; to build a response, use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated A page's status is set by the framework — use `notFound()`, or an endpoint route. */\n status(..._args: unknown[]): never {\n return notOnContext(\n 'status(code)',\n \"A page's status is the framework's: 200, 404 via `notFound()`, 500 when it throws. For any other code use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated Renamed — use `ctx.setHeader(name, value)`, which is valid from a `'use server'` action. */\n header(..._args: unknown[]): never {\n return notOnContext(\n 'header(name, value)',\n \"Use `ctx.setHeader(name, value)` from a 'use server' action, or `c.header(…)` in middleware — a page renders too late to set one.\",\n );\n }\n}\n\n/**\n * The {@link RequestContext} for the current request — URL, cookies, params, env and middleware\n * variables — read from a server component or a server action. Memoised per request, so repeated calls\n * return the same instance.\n *\n * A page is handed that same object as its `ctx` prop, so this import is for everywhere else: a nested\n * server component, or a `'use server'` action module.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and {@link RequestContext.env}.\n * @throws If called at module load, where there is no ambient context to resolve.\n * @throws If called while prerendering a `render: 'static'` route, which has no\n * per-request context at build time — mark the route `render: 'dynamic'` instead.\n *\n * @example\n * ```ts\n * 'use server';\n * import { getRequestContext, redirect } from '@rshono/core/server';\n *\n * export async function login(form: FormData) {\n * getRequestContext().cookies.set('session', String(form.get('email')), { httpOnly: true });\n * redirect('/dashboard');\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n */\nexport function getRequestContext<E extends Env = Env>(): RequestContext<E> {\n if (prerendering) {\n throw new Error(\n \"[rshono] getRequestContext() was called while prerendering a `render: 'static'` route. A static page \" +\n 'is rendered once at build time, so it has no per-request context to read (URL, cookies, ' +\n \"headers, env). Change this route to `render: 'dynamic'` so it renders per request, or remove \" +\n 'the getRequestContext() call.',\n );\n }\n const c = contextStorage.getStore();\n if (!c) {\n throw new Error(\n '[rshono] getRequestContext() was called outside a request. It only works inside a server component or a server action, not at module load.',\n );\n }\n let ctx = wrappers.get(c);\n if (!ctx) {\n ctx = new RequestContext(c);\n wrappers.set(c, ctx);\n }\n return ctx as unknown as RequestContext<E>;\n}\n\n/**\n * Redirects the request to `location`, by throwing a control signal the framework turns into an HTTP\n * redirect.\n *\n * Because it throws it never returns, so TypeScript narrows away everything after the call and there\n * is nothing to `return`. Don't wrap it in a `try/catch` that swallows the signal.\n *\n * @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.\n * @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other), which is what makes\n * the browser follow up with a `GET` after a form action.\n *\n * @example\n * ```ts\n * const session = getRequestContext().cookies.get('session');\n * if (!session) redirect('/login');\n * // session is defined below this line\n * ```\n */\nexport function redirect(location: string, status: RedirectStatus = 303): never {\n throw new RedirectSignal(location, status);\n}\n\n/**\n * Aborts the current render with a 404, rendering the app's `notFound` page.\n *\n * Like {@link redirect} it throws a control signal and never returns, so TypeScript narrows away\n * everything after the call. Don't catch-and-swallow it.\n *\n * @example\n * ```tsx\n * export default async function Page({ params }: PageProps<'/users/:id'>) {\n * const user = await db.user.find(params.id);\n * if (!user) notFound();\n * return <Profile user={user} />; // user is non-null here\n * }\n * ```\n */\nexport function notFound(): never {\n throw new NotFoundSignal();\n}\n\n/**\n * Which stage of a request produced an error handed to a {@link ServerErrorHandler}.\n *\n * - `action` — a `'use server'` function threw. React sends the client an opaque marker with no\n * message in production, so this is the only place the real error is visible.\n * - `render` — a server component threw while the flight payload was being produced.\n * - `ssr` — SSR failed before the HTML shell could be sent, so the `error` page was unreachable too.\n * - `request` — anything else that reached the top-level handler, a thrown endpoint route included.\n */\nexport type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';\n\n/** What an {@link ServerErrorHandler} is told about an error, beyond the error itself. */\nexport interface ServerErrorContext {\n /** The stage that produced it — see {@link ServerErrorSource}. */\n source: ServerErrorSource;\n /** The request being served, for the URL, method and headers. */\n request: Request;\n}\n\n/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */\nexport type ServerErrorHandler = (error: unknown, context: ServerErrorContext) => void;\n\nlet errorHandler: ServerErrorHandler | undefined;\n\n/**\n * Errors already forwarded, so one fault is reported once however many stages it crosses.\n *\n * A thrown server action is reported where it is known to be an action and then re-thrown, which lands it in\n * the top-level handler as well — and a funnel that counts the same error twice, under two different\n * `source`s, is worse than one that only ever names the outer stage.\n */\nconst alreadyReported = new WeakSet<object>();\n\n/**\n * Registers a handler for every error the framework catches, so they can reach an error tracker\n * (Sentry, Datadog, a log pipeline) instead of only `stderr`.\n *\n * Call it once, at the top level of `src/server.ts`, which is imported as the server starts.\n * Registering again replaces the previous handler. Errors still go to `stderr` either way, and a\n * handler that throws is caught and logged — reporting can never fail a request.\n *\n * @example\n * ```ts\n * // src/server.ts\n * import * as Sentry from '@sentry/node';\n * import { onServerError } from '@rshono/core/server';\n *\n * onServerError((error, { source, request }) => {\n * Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/hono#error-reporting | Docs — error reporting}\n */\nexport function onServerError(handler: ServerErrorHandler): void {\n errorHandler = handler;\n}\n\n/**\n * Logs an error and forwards it to the registered {@link ServerErrorHandler} — the single funnel every\n * caught server-side error goes through.\n *\n * @internal\n */\nexport function reportServerError(error: unknown, info: ServerErrorContext & { message: string }): void {\n // The first stage to recognise it wins, since that is the one that knows what it was. A primitive throw\n // cannot be tracked and is reported wherever it is caught.\n if (typeof error === 'object' && error !== null) {\n if (alreadyReported.has(error)) return;\n alreadyReported.add(error);\n }\n console.error(info.message, error);\n if (!errorHandler) return;\n try {\n errorHandler(error, { source: info.source, request: info.request });\n } catch (handlerError) {\n console.error('[rshono] the onServerError handler threw:', handlerError);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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","sourcesContent":["// `__RSHONO_CONFIG__` is a global const, and an `import` cannot bring one into scope — a path reference is\n// the only way to reach it, which is the whole reason that file is separate. See its header.\n// eslint-disable-next-line @typescript-eslint/triple-slash-reference\n/// <reference path=\"../types/rshono-config.d.ts\" />\n/**\n * The request context: {@link getRequestContext} and the {@link RequestContext} it returns, the\n * {@link redirect} / {@link notFound} control-flow helpers, and the {@link onServerError} reporting\n * funnel — plus the `@internal` plumbing that binds a request to the async context.\n *\n * The public half is re-exported by `runtime/server.ts`, which is what `@rshono/core/server` resolves\n * to; an app imports that.\n */\n\nimport type { Context, Env } from 'hono';\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie';\nimport type { CookieOptions } from 'hono/utils/cookie';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { NotFoundSignal, RedirectSignal } from './control.js';\n\n/**\n * HTTP status codes accepted by {@link redirect}.\n *\n * - `301` Moved Permanently, `308` Permanent Redirect — cacheable, permanent.\n * - `302` Found, `307` Temporary Redirect — temporary.\n * - `303` See Other — the default; forces a `GET` on the target, which is what you almost always want\n * after a form action (post/redirect/get).\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#redirection_messages | MDN — redirection status codes}\n */\nexport type RedirectStatus = 301 | 302 | 303 | 307 | 308;\n\nconst contextStorage = new AsyncLocalStorage<Context>();\n\n/** One {@link RequestContext} per Hono {@link Context}, so repeated `getRequestContext()` calls share its lazy getters. */\nconst wrappers = new WeakMap<Context, RequestContext>();\n\n// Keyed on the Hono context rather than held as a field, so marking a request never forces the lazily\n// built `RequestContext` wrapper into existence.\nconst rendering = new WeakSet<Context>();\n\n/**\n * Marks the request as having entered its page render, which is what makes\n * {@link RequestContext.setHeader} and `ctx.cookies.set()` start throwing.\n *\n * @internal\n */\nexport function beginPageRender(c: Context): void {\n rendering.add(c);\n}\n\n/** The shared refusal for a response write that arrived too late — the message names where it belongs instead. */\nfunction tooLateToWrite(call: string): never {\n throw new Error(\n `[rshono] ${call} was called while rendering a page, which is too late to affect the response. ` +\n 'A page streams, so its response head is already committed by the time the component runs — the ' +\n 'write would land on a full page load and be silently dropped on a soft navigation. Do it from a ' +\n \"'use server' action instead; or, in middleware and { type: 'endpoint' } routes — which are handed \" +\n \"Hono's `c` directly and run outside the request context — with `c.header(…)` / `setCookie(c, …)`.\",\n );\n}\n\n/** The shared refusal for a Hono `Context` member a page has no way to use. See the stubs on {@link RequestContext}. */\nfunction notOnContext(call: string, instead: string): never {\n throw new Error(\n `[rshono] ctx.${call} does not exist. A page returns JSX and the framework builds the response from it, ` +\n `so Hono's response builders have nothing to return to. ${instead}`,\n );\n}\n\n// Snapshotted rather than spread per request: enumerating `process.env` crosses into the host\n// environment (~20µs). Lazily, because `loadEnvFiles()` runs after this module is imported — so a\n// mutation after the first `ctx.env` read is not picked up.\nlet envSnapshot: Record<string, string | undefined> | undefined;\n\nfunction processEnv(): Record<string, string | undefined> {\n return (envSnapshot ??= typeof process !== 'undefined' && process.env ? { ...process.env } : {});\n}\n\n// Set by `build.ts` before it imports the app bundle, which inlines its own copy of this module — so\n// `process.env` is what crosses that boundary rather than a module-level flag.\nconst prerendering = typeof process !== 'undefined' && !!process.env?.RSHONO_PRERENDER;\n\n/**\n * Runs `fn` with `c` bound as the ambient request context, so {@link getRequestContext} resolves to it\n * anywhere in the call tree.\n *\n * @internal\n */\nexport function runWithContext<T>(c: Context, fn: () => T): T {\n return contextStorage.run(c, fn);\n}\n\n/**\n * The matched route params, or an empty object when there is no active match.\n *\n * @internal\n */\nexport function readParams(c: Context): Record<string, string> {\n try {\n return c.req.param();\n } catch {\n return {};\n }\n}\n\n/** A proxy chain appends to these headers, so the client-facing value is the first entry. */\nfunction firstForwardedValue(header: string | undefined): string | undefined {\n const first = header?.split(',')[0]?.trim();\n return first || undefined;\n}\n\n// Read through `typeof`: DefinePlugin inlines this into the server bundle, but the module is also the\n// public `@rshono/core/server` entry, which tooling can load without one. Absent means don't trust.\nconst trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;\n\n// Read the same way, for the same reason. Absent means the platform passes no bindings — see the getter.\nconst envBindings = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.envBindings;\n\n/**\n * The browser-facing {@link URL} for a request, resolved from Hono's {@link Context} — a fresh\n * instance per call.\n *\n * `c.req.url` is the internal address the server was reached on, which is wrong behind a proxy;\n * `X-Forwarded-Host` / `-Proto` correct it, but only when `trustProxy` is enabled in\n * `rshono.config.ts` — they are client-supplied, so trusting them unconditionally would let anyone\n * dictate the origin of every absolute URL the app builds.\n *\n * This is the form for **middleware**, which is handed `c` and runs outside the request context — and\n * so the way to give Hono's own middleware the origin the browser actually used. In a server component\n * or action, prefer {@link RequestContext.url}, the same value cached per request.\n *\n * @param c - The Hono {@link Context} for the request.\n * @returns The browser-facing URL — proxy-corrected under `trustProxy`, `c.req.url` otherwise.\n *\n * @example\n * ```ts\n * // src/server.ts — a CSRF check that still works behind a proxy that rewrites Host\n * import { publicUrl } from '@rshono/core/server';\n * import { csrf } from 'hono/csrf';\n *\n * server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin }));\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\nexport function publicUrl(c: Context): URL {\n const url = new URL(c.req.url);\n if (!trustProxy) return url;\n\n const forwardedHost = firstForwardedValue(c.req.header('x-forwarded-host'));\n // Parsed, not assigned to `url.host`: that setter keeps the existing port when the new value has none.\n const forwarded = forwardedHost ? URL.parse(`http://${forwardedHost}`) : null;\n if (forwarded) {\n url.hostname = forwarded.hostname;\n url.port = forwarded.port;\n }\n\n // Only the two schemes a browser could have requested; anything else leaves the scheme alone.\n const forwardedProto = firstForwardedValue(c.req.header('x-forwarded-proto'));\n if (forwardedProto === 'http' || forwardedProto === 'https') url.protocol = forwardedProto;\n\n return url;\n}\n\n/**\n * The environment available to a request: Workers `Bindings` merged with process env vars. Values not\n * declared in `Bindings` are typed as `string | undefined`. See {@link RequestContext.env}.\n *\n * @see {@link https://hono.dev/docs/getting-started/cloudflare-workers#bindings | Hono — bindings}\n */\nexport type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | undefined>;\n\n/**\n * Read-mostly wrapper around Hono's {@link Context}, for server components and server actions.\n *\n * Obtain one with {@link getRequestContext}, or take it off a page's `ctx` prop — the same object.\n * Never construct it yourself. One instance is reused for the whole request, so its lazy getters\n * ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.\n *\n * The eight members that only throw — `redirect`, `notFound`, `json`, `text`, `html`, `body`, `status`,\n * `header` — are **permanent, and exist to throw**. Every one of them is a silent no-op when reached through\n * {@link RequestContext.hono} from a page, so a stub that names the thing that does work is the difference\n * between a message and a page that renders while quietly ignoring half of what it was asked for. They carry\n * `@deprecated` for the strike-through an editor draws with it, not because they are on the way out: nothing\n * will un-deprecate or remove them, and dropping them would leave `ctx.redirect('/x')` as\n * \"property does not exist\", which says what is wrong and not what to do.\n *\n * @typeParam E - The Hono {@link Env} describing this app's `Bindings` and `Variables`, so\n * {@link RequestContext.var} and {@link RequestContext.env} stay typed.\n *\n * @example\n * ```tsx\n * import { getRequestContext } from '@rshono/core/server';\n *\n * export default async function Whoami() {\n * const ctx = getRequestContext();\n * const session = ctx.cookies.get('session');\n * return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}, reachable in full via {@link RequestContext.hono}\n */\nexport class RequestContext<E extends Env = Env> {\n #raw: Context<E>;\n #url?: URL;\n #env?: EnvVars<E>;\n #params?: Record<string, string>;\n\n /**\n * One instance is created per request and handed out by {@link getRequestContext} or the `ctx` page\n * prop. Application code never calls this.\n *\n * @internal\n */\n constructor(c: Context<E>) {\n this.#raw = c;\n }\n\n /**\n * The underlying Hono {@link Context} — the escape hatch for what this wrapper does not expose, such\n * as `executionCtx.waitUntil()` on Workers.\n *\n * Its response builders (`redirect`, `json`, `body`, `status`, …) still do nothing from inside a\n * page: reaching them through here bypasses the errors the stubs on this class throw, it does not\n * make them work.\n *\n * @example\n * ```ts\n * getRequestContext().hono.executionCtx.waitUntil(logAsync()); // Workers\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n // Every member here is a getter or method so that none is *own enumerable*: React's \"you cannot pass\n // this to a client component\" diagnostic walks `Object.keys` recursively with no cycle guard, and the\n // Hono context graph reaches the socket through `req.raw`. `cookies` is the one own property, and it\n // is a shallow object of four functions.\n get hono(): Context<E> {\n return this.#raw;\n }\n\n /**\n * The parsed request — method, headers, path params, query and the body readers. Hono's\n * {@link Context.req}, unwrapped, so `ctx.req.header('authorization')` rather than\n * `ctx.hono.req.header(…)`.\n *\n * Reads only; setting a *response* header is {@link RequestContext.setHeader}, deliberately spelled\n * differently.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.req.method; // 'GET'\n * ctx.req.header('authorization'); // string | undefined\n * ctx.req.query('tab'); // string | undefined\n * ```\n *\n * @see {@link https://hono.dev/docs/api/request | Hono — HonoRequest}\n */\n get req(): Context<E>['req'] {\n return this.#raw.req;\n }\n\n /**\n * Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. Empty when no route\n * matched.\n *\n * A page is handed the same record as its `params` prop, typed key-by-key from its route path, and\n * that is the better read where it exists. This is for everywhere else — a nested server component,\n * or a `'use server'` action.\n */\n get params(): Record<string, string> {\n return (this.#params ??= readParams(this.#raw as Context));\n }\n\n /**\n * The browser-facing request URL. Parsed once and cached, so every read within a request returns the\n * same instance — treat it as read-only.\n *\n * `X-Forwarded-Host` / `-Proto` are honoured only when `trustProxy` is enabled in\n * `rshono.config.ts`, since any client can send them.\n *\n * @example `const tab = getRequestContext().url.searchParams.get('tab');`\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n get url(): URL {\n return (this.#url ??= publicUrl(this.#raw as Context));\n }\n\n /**\n * Typed variables set by middleware via `c.set('user', …)`, read here as `ctx.var.user`. Type them by\n * parameterising this class's {@link Env}.\n *\n * @example\n * ```ts\n * type AppEnv = { Variables: { user: { id: string } } };\n * const { user } = getRequestContext<AppEnv>().var; // typed, set by your middleware\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context#var | Hono — c.var}\n * @see {@link https://www.rshono.com/docs/hono#typing-the-context | Docs — typing the context}\n */\n get var(): Readonly<E['Variables']> {\n return this.#raw.var;\n }\n\n /**\n * Environment for the request: process env vars, merged on a bindings platform with the bindings, which\n * win on conflict. Computed once and cached.\n *\n * Bindings are merged **only where the platform supplies them** — `deploy: 'cloudflare'`, today. Hono's\n * `c.env` is whatever the host passed as the second argument to `app.fetch`, and off Workers that is the\n * adapter's own private state: `{ incoming, outgoing }` on Node and Vercel, the entire invocation —\n * headers, cookies, `authorization` — on Lambda. Merging it would make `ctx.env` uncloneable on one and\n * a disclosure vector on the other, both behind names this type declares `string | undefined`. Reach for\n * {@link RequestContext.hono}`.env` if you really do want the adapter's argument.\n *\n * @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`\n *\n * @see {@link https://hono.dev/docs/api/context#env | Hono — c.env}\n * @see {@link https://www.rshono.com/docs/configuration#environment-and-secrets | Docs — environment and secrets}\n */\n get env(): EnvVars<E> {\n if (this.#env) return this.#env;\n const bindings = envBindings ? (this.#raw.env as Record<string, unknown> | undefined) : undefined;\n return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()) as EnvVars<E>);\n }\n\n /**\n * Read and write request/response cookies.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.cookies.get('session'); // string | undefined\n * ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });\n * ctx.cookies.delete('session', { path: '/' });\n * ```\n *\n * @see {@link https://hono.dev/docs/helpers/cookie | Hono — cookie helper}, which this wraps\n */\n cookies = {\n /** Reads a single cookie by name, or `undefined` if absent. Safe anywhere, a page included. */\n get: (name: string): string | undefined => getCookie(this.#raw, name),\n /** Reads every cookie as a `{ name: value }` record. Safe anywhere, a page included. */\n all: (): Record<string, string> => getCookie(this.#raw),\n /**\n * Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`\n * and the rest.\n *\n * **Throws inside a page render** — a `Set-Cookie` is a special case of\n * {@link RequestContext.setHeader}. Set cookies from a `'use server'` action, or with Hono's\n * `setCookie(c, …)` in middleware and endpoint routes.\n *\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @see {@link https://hono.dev/docs/helpers/cookie#options | Hono — cookie options}\n */\n set: (name: string, value: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.set()');\n setCookie(this.#raw, name, value, options);\n },\n /**\n * Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it.\n * Throws inside a page render, exactly as `set` does.\n *\n * @throws If called while a page is rendering.\n */\n delete: (name: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.delete()');\n deleteCookie(this.#raw, name, options);\n },\n };\n\n #assertWritable(call: string): void {\n if (rendering.has(this.#raw as Context)) tooLateToWrite(call);\n }\n\n /**\n * Sets a header on the response — from a `'use server'` action, which is the one place a request\n * context exists *and* the response is still open.\n *\n * From inside a page it throws: a page streams, so its response head is already committed by then,\n * and the write would land on a full page load but vanish on a soft navigation.\n *\n * Middleware and `{ type: 'endpoint' }` routes are handed Hono's `c` directly and use `c.header(…)`.\n * That is also where a header belonging to the *page* goes — `Cache-Control`, `X-Robots-Tag` — since\n * middleware runs before the render.\n *\n * @param name - Header name, case-insensitive.\n * @param value - Header value.\n * @param options - `{ append: true }` to add another value rather than replace.\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @example\n * ```ts\n * 'use server';\n * export async function logout() {\n * const ctx = getRequestContext();\n * ctx.cookies.delete('session', { path: '/' });\n * ctx.setHeader('clear-site-data', '\"cache\", \"storage\"');\n * redirect('/');\n * }\n * ```\n */\n setHeader(name: string, value: string, options?: { append?: boolean }): void {\n this.#assertWritable('ctx.setHeader()');\n this.#raw.header(name, value, options);\n }\n\n // Hono's response builders, restated as errors naming what to use instead — through `ctx.hono` every\n // one of them is a silent no-op from a page. Permanent, deliberately: see the class doc. `@deprecated`\n // strikes them through in autocomplete; the unread `..._args` is so `ctx.redirect('/x')` reaches the\n // thrown message rather than an arity error.\n\n /** @deprecated Not available on a page's context — use `redirect()` from `@rshono/core/server`. */\n redirect(..._args: unknown[]): never {\n return notOnContext(\n 'redirect(location, status?)',\n \"Use `redirect()` from '@rshono/core/server', which throws a signal the framework turns into a real redirect.\",\n );\n }\n\n /** @deprecated Not available on a page's context — use `notFound()` from `@rshono/core/server`. */\n notFound(..._args: unknown[]): never {\n return notOnContext('notFound()', \"Use `notFound()` from '@rshono/core/server', which aborts the render and shows the app's not-found page.\");\n }\n\n /** @deprecated A page renders JSX. For a JSON response, use an `{ type: 'endpoint' }` route. */\n json(..._args: unknown[]): never {\n return notOnContext('json(object)', \"For a JSON response use an { type: 'endpoint' } route; to read the request body use `ctx.req.json()`.\");\n }\n\n /** @deprecated A page renders JSX. For a text response, use an `{ type: 'endpoint' }` route. */\n text(..._args: unknown[]): never {\n return notOnContext('text(string)', \"For a text response use an { type: 'endpoint' } route; to read the request body use `ctx.req.text()`.\");\n }\n\n /** @deprecated A page renders JSX, which the framework turns into HTML for you. */\n html(..._args: unknown[]): never {\n return notOnContext('html(string)', \"A page's JSX is already its HTML; for a hand-built HTML response use an { type: 'endpoint' } route.\");\n }\n\n /** @deprecated Not available on a page's context. To read the request body, use `ctx.req`. */\n body(..._args: unknown[]): never {\n return notOnContext(\n 'body(data, …)',\n \"To read the *request* body use `ctx.req.json()` / `ctx.req.text()` / `ctx.req.formData()`; to build a response, use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated A page's status is set by the framework — use `notFound()`, or an endpoint route. */\n status(..._args: unknown[]): never {\n return notOnContext(\n 'status(code)',\n \"A page's status is the framework's: 200, 404 via `notFound()`, 500 when it throws. For any other code use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated Renamed — use `ctx.setHeader(name, value)`, which is valid from a `'use server'` action. */\n header(..._args: unknown[]): never {\n return notOnContext(\n 'header(name, value)',\n \"Use `ctx.setHeader(name, value)` from a 'use server' action, or `c.header(…)` in middleware — a page renders too late to set one.\",\n );\n }\n}\n\n/**\n * The {@link RequestContext} for the current request — URL, cookies, params, env and middleware\n * variables — read from a server component or a server action. Memoised per request, so repeated calls\n * return the same instance.\n *\n * A page is handed that same object as its `ctx` prop, so this import is for everywhere else: a nested\n * server component, or a `'use server'` action module.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and {@link RequestContext.env}.\n * @throws If called at module load, where there is no ambient context to resolve.\n * @throws If called while prerendering a `render: 'static'` route, which has no\n * per-request context at build time — mark the route `render: 'dynamic'` instead.\n *\n * @example\n * ```ts\n * 'use server';\n * import { getRequestContext, redirect } from '@rshono/core/server';\n *\n * export async function login(form: FormData) {\n * getRequestContext().cookies.set('session', String(form.get('email')), { httpOnly: true });\n * redirect('/dashboard');\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n */\nexport function getRequestContext<E extends Env = Env>(): RequestContext<E> {\n if (prerendering) {\n throw new Error(\n \"[rshono] getRequestContext() was called while prerendering a `render: 'static'` route. A static page \" +\n 'is rendered once at build time, so it has no per-request context to read (URL, cookies, ' +\n \"headers, env). Change this route to `render: 'dynamic'` so it renders per request, or remove \" +\n 'the getRequestContext() call.',\n );\n }\n const c = contextStorage.getStore();\n if (!c) {\n throw new Error(\n '[rshono] getRequestContext() was called outside a request. It only works inside a server component or a server action, not at module load.',\n );\n }\n let ctx = wrappers.get(c);\n if (!ctx) {\n ctx = new RequestContext(c);\n wrappers.set(c, ctx);\n }\n return ctx as unknown as RequestContext<E>;\n}\n\n/**\n * Redirects the request to `location`, by throwing a control signal the framework turns into an HTTP\n * redirect.\n *\n * Because it throws it never returns, so TypeScript narrows away everything after the call and there\n * is nothing to `return`. Don't wrap it in a `try/catch` that swallows the signal.\n *\n * @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.\n * @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other), which is what makes\n * the browser follow up with a `GET` after a form action.\n *\n * @example\n * ```ts\n * const session = getRequestContext().cookies.get('session');\n * if (!session) redirect('/login');\n * // session is defined below this line\n * ```\n */\nexport function redirect(location: string, status: RedirectStatus = 303): never {\n throw new RedirectSignal(location, status);\n}\n\n/**\n * Aborts the current render with a 404, rendering the app's `notFound` page.\n *\n * Like {@link redirect} it throws a control signal and never returns, so TypeScript narrows away\n * everything after the call. Don't catch-and-swallow it.\n *\n * @example\n * ```tsx\n * export default async function Page({ params }: PageProps<'/users/:id'>) {\n * const user = await db.user.find(params.id);\n * if (!user) notFound();\n * return <Profile user={user} />; // user is non-null here\n * }\n * ```\n */\nexport function notFound(): never {\n throw new NotFoundSignal();\n}\n\n/**\n * Which stage of a request produced an error handed to a {@link ServerErrorHandler}.\n *\n * - `action` — a `'use server'` function threw. React sends the client an opaque marker with no\n * message in production, so this is the only place the real error is visible.\n * - `render` — a server component threw while the flight payload was being produced.\n * - `ssr` — SSR failed before the HTML shell could be sent, so the `error` page was unreachable too.\n * - `request` — anything else that reached the top-level handler, a thrown endpoint route included.\n */\nexport type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';\n\n/**\n * What an {@link ServerErrorHandler} is told about an error, beyond the error itself.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link ServerErrorContext.hono}'s `var` and `env`.\n */\nexport interface ServerErrorContext<E extends Env = Env> {\n /** The stage that produced it — see {@link ServerErrorSource}. */\n source: ServerErrorSource;\n /** The request being served, for the URL, method and headers. */\n request: Request;\n /**\n * The Hono {@link Context} for this request — `hono.var` for whatever middleware put there, such as a\n * request id to correlate the report on, and `hono.env` for a platform that passes bindings.\n *\n * Handed over rather than left to {@link getRequestContext}, which a handler cannot reach: an error with\n * `source: 'request'` is reported from the top-level handler, which runs outside the ambient context.\n */\n hono: Context<E>;\n /**\n * Holds the invocation open until `promise` settles, where the platform has something to ask.\n *\n * Reporting is what this hook exists for, and on a serverless platform a report started here is cut off\n * the moment the response ends unless something keeps the invocation alive. On Cloudflare Workers that is\n * `executionCtx.waitUntil`, which this calls. On the `node` and `vercel` targets there is nothing to hold\n * open — the process outlives the response — so it is a no-op and the report finishes on its own. On\n * `aws-lambda` it is a no-op as well, because `hono/aws-lambda`'s streaming handler exposes no execution\n * context to ask; a slow report there is best-effort, so prefer a tracker that batches over one that\n * round-trips per error.\n *\n * A rejection is logged rather than propagated: reporting can never fail a request.\n */\n waitUntil: (promise: Promise<unknown>) => void;\n}\n\n/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */\nexport type ServerErrorHandler<E extends Env = Env> = (error: unknown, context: ServerErrorContext<E>) => void;\n\nlet errorHandler: ServerErrorHandler | undefined;\n\n/**\n * The platform's \"keep this invocation alive\" hook, or a no-op where there is none.\n *\n * `c.executionCtx` *throws* rather than answering `undefined` where a platform has no execution context, so\n * a handler that reached for it itself would have its report swallowed by the guard in\n * {@link reportServerError} — on exactly the platforms where nothing needed holding open.\n */\nfunction keepAlive(c: Context, promise: Promise<unknown>): void {\n // Caught here rather than left to the platform: under `--unhandled-rejections=strict` a rejected report\n // would end the process, and a failed report must never be worse than no report.\n const settled = Promise.resolve(promise).catch((error) => {\n console.error('[rshono] a promise passed to the onServerError waitUntil rejected:', error);\n });\n try {\n c.executionCtx.waitUntil(settled);\n } catch {\n // No execution context: nothing here cuts the work off, so there is nothing to hold open.\n }\n}\n\n/**\n * Errors already forwarded, so one fault is reported once however many stages it crosses.\n *\n * A thrown server action is reported where it is known to be an action and then re-thrown, which lands it in\n * the top-level handler as well — and a funnel that counts the same error twice, under two different\n * `source`s, is worse than one that only ever names the outer stage.\n */\nconst alreadyReported = new WeakSet<object>();\n\n/**\n * Registers a handler for every error the framework catches, so they can reach an error tracker\n * (Sentry, Datadog, a log pipeline) instead of only `stderr`.\n *\n * Call it once, at the top level of `src/server.ts`, which is imported as the server starts.\n * Registering again replaces the previous handler. Errors still go to `stderr` either way, and a\n * handler that throws is caught and logged — reporting can never fail a request.\n *\n * @typeParam E - The app's Hono {@link Env}, to type the `hono` context the handler is given.\n *\n * @example\n * ```ts\n * // src/server.ts\n * import * as Sentry from '@sentry/node';\n * import { onServerError } from '@rshono/core/server';\n *\n * onServerError((error, { source, request, hono, waitUntil }) => {\n * // `waitUntil` so a serverless invocation is not frozen before the report is sent.\n * waitUntil(\n * Sentry.captureException(error, {\n * tags: { source, requestId: hono.var.requestId },\n * extra: { url: request.url },\n * }),\n * );\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/hono#error-reporting | Docs — error reporting}\n */\nexport function onServerError<E extends Env = Env>(handler: ServerErrorHandler<E>): void {\n errorHandler = handler as unknown as ServerErrorHandler;\n}\n\n/**\n * Logs an error and forwards it to the registered {@link ServerErrorHandler} — the single funnel every\n * caught server-side error goes through.\n *\n * @internal\n */\nexport function reportServerError(error: unknown, info: { source: ServerErrorSource; hono: Context; message: string }): void {\n // The first stage to recognise it wins, since that is the one that knows what it was. A primitive throw\n // cannot be tracked and is reported wherever it is caught.\n if (typeof error === 'object' && error !== null) {\n if (alreadyReported.has(error)) return;\n alreadyReported.add(error);\n }\n console.error(info.message, error);\n if (!errorHandler) return;\n try {\n errorHandler(error, {\n source: info.source,\n request: info.hono.req.raw,\n // The handler was registered for the app's own `Env`, which `onServerError` erased to store it; this\n // puts the context back in the shape it was registered with. Same trade as {@link getRequestContext}.\n hono: info.hono as Context<Env>,\n waitUntil: (promise) => keepAlive(info.hono, promise),\n });\n } catch (handlerError) {\n console.error('[rshono] the onServerError handler threw:', handlerError);\n }\n}\n"]}
|