@orieljs/http 0.1.0-alpha.1 → 0.1.0-alpha.11

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/NOTICE CHANGED
@@ -7,3 +7,10 @@ This product includes software developed by the Oriel project
7
7
  Oriel is licensed under the Apache License, Version 2.0 (see LICENSE).
8
8
  The Oriel name and logo are not covered by that license; see TRADEMARKS.md
9
9
  (https://github.com/orieljs/framework/blob/main/TRADEMARKS.md).
10
+
11
+ @orieljs/support (and @orieljs/framework, which bundles it) includes transliteration data from
12
+ voku/portable-ascii (https://github.com/voku/portable-ascii), Copyright (C) 2019 Lars Moelleken, MIT License.
13
+
14
+ @orieljs/notifications (and @orieljs/framework, which bundles it) includes the HTML and CSS of
15
+ Laravel's default notification mail (laravel/framework, https://github.com/laravel/framework),
16
+ Copyright (c) Taylor Otwell, MIT License.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @orieljs/http
2
+
3
+ The HTTP layer of Oriel: the request, responses and redirects, the middleware kernel, the error handler and its error pages, and the server for Node and Bun. It is Oriel's counterpart of Laravel's HTTP layer. Http is part of `@orieljs/framework`, which every Oriel app already installs, and is re-exported from it. In an app, import it from `@orieljs/framework`; `@orieljs/http` is also published on its own.
4
+
5
+ ```bash
6
+ npm install @orieljs/framework
7
+ ```
8
+
9
+ ```ts title="app/Http/Controllers/SearchController.ts"
10
+ import { request, response } from '@orieljs/framework'
11
+ import { Post } from '../../Models/Post'
12
+ import { Controller } from './Controller'
13
+
14
+ export class SearchController extends Controller {
15
+ async index() {
16
+ const term = request().input('q', '')
17
+ const posts = await Post.whereLike('title', `%${term}%`).latest().get()
18
+ return response().json({ term, posts }, 200, { 'Cache-Control': 'no-store' })
19
+ }
20
+ }
21
+ ```
22
+
23
+ `request()` is the current `IncomingRequest`, with `input()`, `string()`, `boolean()`, `integer()`, `only()`, `file()` and `header()`; `await request().user()` is the signed-in user, or `undefined` for a guest. `response()`, `redirect()` and `back()` build responses, and `abort()`, `abortIf()` and `abortUnless()` stop a request with an HTTP error. `await serve(app)` runs the app on Node or Bun.
24
+
25
+ Documentation: https://orieljs.vercel.app/docs/requests
26
+
27
+ Status: early alpha. APIs will change.
28
+
29
+ ## License
30
+
31
+ Oriel is open-source software licensed under the Apache License 2.0 (Apache-2.0; see `LICENSE` and `NOTICE`). The Oriel name and logo are trademarks; see `TRADEMARKS.md`.
32
+
33
+ Website: https://orieljs.vercel.app
package/TRADEMARKS.md ADDED
@@ -0,0 +1,33 @@
1
+ # Oriel trademark policy
2
+
3
+ The Oriel code is open source under the Apache License 2.0. The **Oriel name and logo** are not: the license covers
4
+ the code only (section 6 of the license says so), and the name and logo stay with the project. This page explains
5
+ what you can and can't do with them. The goal is simple: people should always be able to tell what is the real Oriel
6
+ and what isn't.
7
+
8
+ ## You can, without asking
9
+
10
+ - Use, change, ship and sell the code, as the license allows.
11
+ - Say that your app or package is "built with Oriel", "for Oriel" or "compatible with Oriel".
12
+ - Name a package `oriel-<something>` or `<something>-for-oriel` when it works with Oriel, as long as it is clear that
13
+ it is not official (for example, don't publish it under the `@orieljs` npm scope).
14
+ - Use the name and logo to write about Oriel, teach it, or link to it, in articles, talks, videos and courses.
15
+ - Use the unmodified logo to point to the project, as it is (no recolouring, cropping or
16
+ distortion).
17
+
18
+ ## You can't, without written permission
19
+
20
+ - Call a fork, a modified copy or a competing product "Oriel", or anything likely to be confused with it.
21
+ - Use the name or logo as the name, logo or domain of your own product, company or service.
22
+ - Suggest that the Oriel project made, endorses or supports your work when it doesn't.
23
+ - Sell merchandise with the name or logo.
24
+
25
+ ## Forks
26
+
27
+ Forks are welcome. Please give yours a different name and logo, keep the `LICENSE` and `NOTICE` files, and say that
28
+ it is based on Oriel.
29
+
30
+ ## Questions
31
+
32
+ Open an issue at https://github.com/orieljs/framework or email meeeet.dev@gmail.com. If you're unsure whether a use
33
+ is fine, ask; the answer is usually yes.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,14 @@
1
- import { HttpRequest, CookieJar, Session, Container, RedirectResponse, HeadersInit, Redirector, UploadedFile, Rules, OrielError, ResponseFactory, BodyInit, UrlGenerator, HttpKernel, Application, MiddlewareClass, Middleware, Next, MiddlewareConfigurator, Router, ConfigRepository, ExceptionHandler, ExceptionsConfigurator, ConsoleKernel, Route } from '@orieljs/contracts';
1
+ import { HttpRequest, CookieJar, Session, Container, RedirectResponse, HeadersInit, MessageBag, Redirector, UploadedFile, Rules, InferValidatedType, OrielError, RouteName, RouteArguments, ResponseFactory, BodyInit, UrlGenerator, HttpKernel, Application, MiddlewareClass, Middleware, Next, MiddlewareConfigurator, Router, ConfigRepository, ExceptionHandler, ExceptionsConfigurator, ConsoleKernel, Route } from '@orieljs/contracts';
2
+ import { FileType, Stringable, ServiceProvider } from '@orieljs/support';
2
3
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import { ServiceProvider } from '@orieljs/support';
4
+ import { SpawnOptions, ChildProcess } from 'node:child_process';
4
5
 
6
+ /**
7
+ * Whether a JSON text nests objects and arrays deeper than `maxDepth`, found by scanning (no parsing, no recursion):
8
+ * brackets inside strings don't count. The kernel answers 400 for such bodies (http.max_json_depth) before the
9
+ * recursive input handling (sanitizing, TrimStrings, validation) could overflow the stack.
10
+ */
11
+ declare function exceedsJsonDepth(text: string, maxDepth: number): boolean;
5
12
  /** Reads a request body into memory, refusing more than `limit` bytes (413), like PHP's post_max_size. */
6
13
  declare function readBody(raw: Request, limit: number | null): Promise<Uint8Array>;
7
14
 
@@ -12,9 +19,7 @@ declare function currentRequest(): HttpRequest | undefined;
12
19
  declare function optionalSession(): Session | undefined;
13
20
  declare function optionalCookieJar(): CookieJar | undefined;
14
21
 
15
- type ErrorsInput = Record<string, string | string[]> | {
16
- toObject(): Record<string, string[]>;
17
- };
22
+ type ErrorsInput = Record<string, string | string[]> | MessageBag;
18
23
  /**
19
24
  * Laravel's RedirectResponse. with()/withErrors()/withInput() flash to the request's Session immediately, like
20
25
  * Laravel; without a bound Session (an API-only app) they are skipped silently.
@@ -46,7 +51,10 @@ declare class HttpRedirector implements Redirector {
46
51
  /** Pulls url.intended from the session (put there by guest()), else goes to `fallback`. */
47
52
  intended(fallback?: string, status?: number): HttpRedirectResponse;
48
53
  away(url: string, status?: number): HttpRedirectResponse;
49
- /** Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. */
54
+ /**
55
+ * Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. Only the path
56
+ * is stored, so intended() goes back on whichever host serves the app, and never to another site's Referer.
57
+ */
50
58
  guest(path: string, status?: number): HttpRedirectResponse;
51
59
  setIntendedUrl(url: string): void;
52
60
  refresh(status?: number): HttpRedirectResponse;
@@ -58,13 +66,26 @@ declare function clientAddress(request: Request): string | undefined;
58
66
  /** An uploaded file from a multipart body. store()/storeAs() go through the FilesystemManager. */
59
67
  declare class FormUploadedFile implements UploadedFile {
60
68
  private readonly file;
69
+ private readonly detected?;
61
70
  readonly clientName: string;
62
71
  readonly mimeType: string;
63
72
  readonly size: number;
73
+ /**
74
+ * Reads the leading bytes first, so extension(), hashName() and store() name the file by its content. The request
75
+ * builds its uploads this way.
76
+ */
77
+ static fromFile(file: Blob & {
78
+ readonly name?: string;
79
+ }): Promise<FormUploadedFile>;
64
80
  constructor(file: Blob & {
65
81
  readonly name?: string;
66
- });
67
- /** Guessed from the MIME type, falling back to the client's extension (Laravel's extension()). */
82
+ }, detected?: FileType.DetectedType | undefined);
83
+ /**
84
+ * Laravel's extension(): the type the bytes show (guessExtension()), when they were read and match a known format.
85
+ * Text and bytes no format matches keep the client's claim (its MIME type, else its file name) when it is a plain
86
+ * extension, unless it names a format the bytes would have shown or one a browser renders or runs: otherwise 'txt'
87
+ * for text, '' for the rest. Unread (built directly), the client's claim when it is a plain extension, else ''.
88
+ */
68
89
  extension(): string;
69
90
  clientExtension(): string;
70
91
  /** A random 40-character name with the guessed extension. */
@@ -81,6 +102,8 @@ interface IncomingRequestOptions {
81
102
  trustedProxies?: readonly string[];
82
103
  /** Largest request body in bytes before a 413 (config http.max_body_size); null is unlimited. Default 8 MB. */
83
104
  maxBodySize?: number | null;
105
+ /** Deepest nesting of objects and arrays in a JSON body before a 400 (config http.max_json_depth); null is unlimited. Default 512. */
106
+ maxJsonDepth?: number | null;
84
107
  }
85
108
  /** Exact match, IPv4 CIDR ('10.0.0.0/8') or '*'. */
86
109
  declare function ipMatches(ip: string, patterns: readonly string[]): boolean;
@@ -136,7 +159,10 @@ declare class IncomingRequest implements HttpRequest {
136
159
  hasAny(...keys: Array<string | string[]>): boolean;
137
160
  missing(...keys: Array<string | string[]>): boolean;
138
161
  filled(key: string): boolean;
139
- string(key: string, defaultValue?: string): string;
162
+ /** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
163
+ string(key: string, defaultValue?: string): Stringable;
164
+ /** Laravel's str(): string() under its short name. */
165
+ str(key: string, defaultValue?: string): Stringable;
140
166
  /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
141
167
  boolean(key: string, defaultValue?: boolean): boolean;
142
168
  /** PHP's intval: leading digits, else 0. */
@@ -155,10 +181,17 @@ declare class IncomingRequest implements HttpRequest {
155
181
  /** One route parameter (after model binding), or all of them. */
156
182
  route<T = unknown>(key?: string): T;
157
183
  session(): Session;
158
- user<T = unknown>(guard?: string): T | undefined;
184
+ /**
185
+ * The signed-in user, through the resolver Auth sets, else the current scope's guard (the default, or `guard`).
186
+ * The guard keeps the user it found, so it is looked up once per request. Undefined for a guest or without auth.
187
+ */
188
+ user<T = unknown>(guard?: string): Promise<T | undefined>;
159
189
  setUserResolver(resolver: (guard?: string) => unknown): void;
160
- /** Validates all() through the ValidationFactory; throws its Validation error on failure. */
161
- validate<T = Record<string, unknown>>(rules: Rules, messages?: Record<string, string>, attributes?: Record<string, string>): Promise<T>;
190
+ /**
191
+ * Validates all() through the ValidationFactory; throws its Validation error on failure. The data is typed from the
192
+ * rules: `validate({ title: 'required|string' })` is `{ title: string }`.
193
+ */
194
+ validate<const R extends Rules>(rules: R, messages?: Record<string, string>, attributes?: Record<string, string>): Promise<InferValidatedType<R>>;
162
195
  }
163
196
 
164
197
  /** Helpers for request input: PHP-style bracket keys, safe cloning (no prototype pollution) and blank checks. */
@@ -209,22 +242,32 @@ declare class HttpServerError extends Error implements OrielError {
209
242
  readonly name = "HttpServerError";
210
243
  }
211
244
 
212
- /** request() → the current HttpRequest; request('key', default) → its input. */
213
- declare function request(): HttpRequest;
245
+ /**
246
+ * request() → the current request (Laravel's Illuminate\Http\Request: the HttpRequest contract plus its extras, such
247
+ * as string()); request('key', default) → its input.
248
+ */
249
+ declare function request(): IncomingRequest;
214
250
  declare function request<T = unknown>(key: string, defaultValue?: T): T;
215
251
  /** response() → the ResponseFactory; response(content, status, headers) → a Response. */
216
252
  declare function response(): ResponseFactory;
217
253
  declare function response(content: BodyInit | null, status?: number, headers?: HeadersInit): Response;
254
+ /** The Redirector with its route() typed by the generated route names and their parameters. */
255
+ type TypedRedirector = Omit<Redirector, 'route'> & {
256
+ route<N extends RouteName>(name: N, ...args: RouteArguments<N, [status?: number]>): RedirectResponse;
257
+ };
218
258
  /** redirect() → the Redirector; redirect('/path', status) → a RedirectResponse. */
219
- declare function redirect(): Redirector;
259
+ declare function redirect(): TypedRedirector;
220
260
  declare function redirect(to: string, status?: number): RedirectResponse;
221
261
  /** Redirect to the previous URL (same argument order as Redirector.back()). */
222
262
  declare function back(fallback?: string, status?: number): RedirectResponse;
223
263
  /** url() → the UrlGenerator; url('/path', query, absolute) → a URL. */
224
264
  declare function url(): UrlGenerator;
225
265
  declare function url(path: string, query?: Record<string, unknown>, absolute?: boolean): string;
226
- /** The URL of a named route. */
227
- declare function route(name: string, parameters?: unknown, absolute?: boolean): string;
266
+ /**
267
+ * The URL of a named route: route('posts.show', post), route('posts.show', { post, page: 2 }). Once `oriel
268
+ * route:types` has run, the name and the route's parameters are checked.
269
+ */
270
+ declare function route<N extends RouteName>(name: N, ...args: RouteArguments<N>): string;
228
271
  /** abort(404), abort(403, 'Nope', headers), or abort(response) to send a ready response. */
229
272
  declare function abort(status: number | Response, message?: string, headers?: Record<string, string>): never;
230
273
  declare function abortIf(condition: unknown, status: number | Response, message?: string, headers?: Record<string, string>): void;
@@ -312,6 +355,7 @@ declare function applyMiddlewareCallbacks(app: Application, kernel: Kernel): voi
312
355
  type ReportCallback = (error: unknown) => void | boolean | Promise<void | boolean>;
313
356
  type RenderCallback = (error: unknown, request: unknown) => Response | undefined | Promise<Response | undefined>;
314
357
  type ErrorClass = abstract new (...args: never[]) => Error;
358
+ type JsonWhenCallback = (request: HttpRequest, error: unknown) => boolean;
315
359
  /** HTTP status for an error (spec §7). */
316
360
  declare function statusFor(error: unknown): number;
317
361
  /** "The title field is required. (and 2 more errors)" (Laravel's ValidationException::summarize). */
@@ -326,6 +370,10 @@ declare class ErrorHandler implements ExceptionHandler {
326
370
  reportUsing(callback: ReportCallback): void;
327
371
  renderUsing(callback: RenderCallback): void;
328
372
  ignore(...classes: ErrorClass[]): void;
373
+ /** Laravel's shouldRenderJsonWhen(): the callback decides JSON or HTML instead of the Accept header. */
374
+ shouldRenderJsonWhen(callback: JsonWhenCallback): void;
375
+ /** Laravel's shouldReturnJson(): the shouldRenderJsonWhen() callback when set, else request.expectsJson(). */
376
+ protected shouldReturnJson(request: HttpRequest, error: unknown): boolean;
329
377
  shouldReport(error: unknown): boolean;
330
378
  /** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
331
379
  report(error: unknown): Promise<void>;
@@ -339,11 +387,16 @@ declare class ExceptionsConfiguration implements ExceptionsConfigurator {
339
387
  report(callback: ReportCallback): this;
340
388
  render(callback: RenderCallback): this;
341
389
  dontReport(...classes: ErrorClass[]): this;
390
+ /** shouldRenderJsonWhen((request) => request.is('api/*')): JSON errors for those requests, whatever they accept. */
391
+ shouldRenderJsonWhen(callback: (request: HttpRequest, error: unknown) => boolean): this;
342
392
  }
343
393
  /** Runs the withExceptions() callbacks against a handler (the provider does this when the handler is built). */
344
394
  declare function applyExceptionCallbacks(app: Application, handler: ErrorHandler): void;
345
395
 
346
- /** Laravel's wording for the status (its error pages say "Server Error" and "Page Expired"). */
396
+ /**
397
+ * The text an error page shows for a status: Laravel's own wording where it has a view ("Server Error", "Page
398
+ * Expired"), else Symfony's `Response::$statusTexts`, which Laravel shows for the others.
399
+ */
347
400
  declare function statusText(status: number): string;
348
401
  declare function escapeHtml(value: string): string;
349
402
  /** Laravel's minimal error page: "404 | Not Found". */
@@ -359,6 +412,59 @@ declare function stackFrames(error: Error): StackFrame[];
359
412
  /** The APP_DEBUG page: class, message, request, a source excerpt of the first app frame, the stack and causes. */
360
413
  declare function debugPage(error: unknown, method: string, url: string): Promise<string>;
361
414
 
415
+ interface ExportOptions {
416
+ /** The folder the site is written to. */
417
+ output: string;
418
+ /** The URL the pages are requested at (the app's `app.url`); only its origin's links are followed. */
419
+ baseUrl: string;
420
+ /** Follow every same-site link from `/` (and from `paths`). */
421
+ crawl: boolean;
422
+ /** Paths exported besides `/` (pages nothing links to, a feed, …). */
423
+ paths: string[];
424
+ /** Paths left out; `*` matches anything (`/drafts/*`). */
425
+ exclude: string[];
426
+ /** `/docs/routing` → `docs/routing.html` (`'html'`) or `docs/routing/index.html` (`'directory'`). */
427
+ fileNames: 'html' | 'directory';
428
+ /** Copied into the output first (the built assets, images); false copies nothing. */
429
+ publicPath: string | false;
430
+ }
431
+ interface ExportResult {
432
+ /** The paths written, in the order they were found. */
433
+ pages: string[];
434
+ /** Redirects met while following links: listed, not written. */
435
+ redirects: Array<{
436
+ path: string;
437
+ location: string;
438
+ }>;
439
+ /** Whether the app's 404 page was written as `404.html`. */
440
+ notFoundPage: boolean;
441
+ }
442
+ /** A page answered with an error: the export stops, and the last export stays as it was. */
443
+ declare class ExportError extends Error {
444
+ readonly path: string;
445
+ readonly status: number;
446
+ readonly name = "ExportError";
447
+ constructor(path: string, status: number);
448
+ }
449
+ /**
450
+ * Exports an app's pages as files: requests `/` and `paths` inside the process (as `serve` handles a request),
451
+ * follows same-site links, and writes every page into `output`, after a copy of `publicPath`. The site is written
452
+ * into a temporary folder beside `output` and swapped in at the end, so a failed export leaves the last one intact.
453
+ */
454
+ declare function exportSite(app: Application, options: ExportOptions): Promise<ExportResult>;
455
+
456
+ /**
457
+ * The file a page is written to, relative to the output folder: `/` → `index.html`, `/docs/routing` →
458
+ * `docs/routing.html` (or `docs/routing/index.html`). A path with an extension (`/feed.xml`) keeps its name.
459
+ */
460
+ declare function exportFileName(path: string, style: 'html' | 'directory'): string;
461
+ /**
462
+ * The paths a page links to on the same site (the origin of `baseUrl`): resolved against the page's URL, decoded,
463
+ * without query, hash or a trailing slash, each once, in document order. Links to other origins, `mailto:`, `tel:`,
464
+ * `javascript:` and to an anchor on the same page are left out.
465
+ */
466
+ declare function sameSiteLinks(html: string, pageUrl: string, baseUrl: string): string[];
467
+
362
468
  type Runtime = 'node' | 'bun';
363
469
  interface ServeOptions {
364
470
  /** Default: PORT, then SERVER_PORT, then 8000 (Laravel's serve). 0 picks a free port. */
@@ -373,12 +479,16 @@ interface ServeOptions {
373
479
  runtime?: Runtime;
374
480
  /** Called once listening. Default: prints "Server running on [url]." */
375
481
  onListen?: (server: RunningServer) => void;
482
+ /** Serve existing files from this directory before the app (Laravel's server.php). Default: app.publicPath(); false turns it off. */
483
+ publicPath?: string | false;
376
484
  }
377
485
  interface ResolvedServeOptions {
378
486
  readonly port: number;
379
487
  readonly hostname: string;
380
488
  readonly signals: boolean;
381
489
  readonly shutdownTimeout: number;
490
+ /** Where public files are served from, or undefined when turned off. */
491
+ readonly publicPath?: string;
382
492
  }
383
493
  interface RunningServer {
384
494
  readonly runtime: Runtime;
@@ -415,12 +525,50 @@ declare function serveBun(app: Application, options: ResolvedServeOptions): Prom
415
525
  declare function toWebRequest(req: IncomingMessage, res: ServerResponse, fallbackHost: string): Request;
416
526
  /** Web Response → ServerResponse, streaming the body with backpressure. Several Set-Cookie headers stay separate. */
417
527
  declare function writeResponse(res: ServerResponse, response: Response): Promise<void>;
528
+ /**
529
+ * A node:http request listener for the app: a public file when one matches, else the app's answer, then the
530
+ * terminable middleware. `serve` uses it, and so does any host that hands Oriel Node's request and response (Vercel's
531
+ * functions). `fallbackHost` is the Host used when a request has none.
532
+ */
533
+ declare function nodeRequestHandler(app: Application, options: {
534
+ publicPath?: string;
535
+ fallbackHost: string;
536
+ }): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
418
537
  declare function serveNode(app: Application, options: ResolvedServeOptions): Promise<RunningServer>;
419
538
 
420
539
  declare function detectRuntime(): Runtime;
421
540
  /** Boots the app and serves it with node:http or Bun.serve (public/index.ts: `await serve(app)`). */
422
541
  declare function serve(app: Application, options?: ServeOptions): Promise<RunningServer>;
423
542
 
543
+ /**
544
+ * Laravel's server.php for `artisan serve`: a GET or HEAD for a file that exists under public/ is answered
545
+ * with that file, so the built Vite assets (public/build) and other public files work without a web server.
546
+ * Anything else (directories, missing files, other methods) returns undefined and goes to the application.
547
+ */
548
+ declare function servePublicFile(publicPath: string, request: Request): Promise<Response | undefined>;
549
+
550
+ interface FileWatcher {
551
+ close(): void;
552
+ }
553
+
554
+ /** node:child_process's spawn(), replaceable in tests. */
555
+ type SpawnServer = (command: string, args: string[], options: SpawnOptions) => ChildProcess;
556
+ interface WatchedServerOptions {
557
+ /** The server's command line: the runtime, its flags, the CLI script, then `serve --host=… --port=…`. */
558
+ command: readonly string[];
559
+ cwd: string;
560
+ env: NodeJS.ProcessEnv;
561
+ /** Starts watching: calls back with the changed files (relative to the base path) once they settle. */
562
+ watch(onChange: (files: string[]) => void): FileWatcher;
563
+ info(message: string): void;
564
+ error(message: string): void;
565
+ /** How long a server may take to stop before it is killed, in ms. Default 15000 (serve gives requests 10 s). */
566
+ stopTimeout?: number;
567
+ /** The signals that stop it. Default SIGINT, SIGTERM and SIGHUP. */
568
+ signals?: readonly NodeJS.Signals[];
569
+ spawn?: SpawnServer;
570
+ }
571
+
424
572
  interface RouteListRow {
425
573
  /** 'GET|HEAD' */
426
574
  readonly method: string;
@@ -460,8 +608,10 @@ interface HttpCommandOptions {
460
608
  serve?: (app: Application, options: ServeOptions) => Promise<RunningServer>;
461
609
  /** The terminal width for route:list. Default: process.stdout.columns, else 120. */
462
610
  terminalWidth?: () => number;
611
+ /** Replaces `serve --watch`'s restarting server (tests). Resolves the command's exit code. */
612
+ watch?: (options: WatchedServerOptions) => Promise<number>;
463
613
  }
464
- /** Registers `serve`, `route:list` and `install:api` as closure commands (PC1). */
614
+ /** Registers `serve`, `route:list`, `export` and `install:api` as closure commands (PC1). */
465
615
  declare function registerHttpCommands(kernel: ConsoleKernel, app: Application, options?: HttpCommandOptions): void;
466
616
 
467
617
  /**
@@ -477,4 +627,4 @@ declare class HttpServiceProvider extends ServiceProvider {
477
627
  boot(): void;
478
628
  }
479
629
 
480
- export { API_ROUTES_STUB, ConvertEmptyStringsToNull, type ErrorClass, ErrorHandler, ExceptionsConfiguration, FormUploadedFile, type HttpCommandOptions, HttpException, HttpRedirectResponse, HttpRedirector, HttpResponseException, HttpResponseFactory, HttpServerError, HttpServiceProvider, IncomingRequest, type IncomingRequestOptions, Kernel, MiddlewareConfiguration, type RenderCallback, type ReportCallback, type ResolvedServeOptions, type RouteListFilters, type RouteListRow, type RunningServer, type Runtime, type ServeOptions, type StackFrame, TrimStrings, abort, abortIf, abortUnless, addApiRouting, applyExceptionCallbacks, applyMiddlewareCallbacks, back, bunRuntime, clientAddress, contentDisposition, currentRequest, debugPage, detectRuntime, escapeHtml, extensionFromMime, filterRouteList, formatRouteList, fromEntries, ipMatches, isBlank, mergeRecursive, mimeFromExtension, minimalPage, normalizeLists, optionalCookieJar, optionalSession, parseBracketKey, readBody, redirect, registerHttpCommands, request, requestScope, response, route, routeListRows, sanitize, serve, serveBun, serveNode, setClientAddress, stackFrames, statusFor, statusText, summarizeValidation, toWebRequest, transformLeaves, url, writeResponse };
630
+ export { API_ROUTES_STUB, ConvertEmptyStringsToNull, type ErrorClass, ErrorHandler, ExceptionsConfiguration, ExportError, type ExportOptions, type ExportResult, FormUploadedFile, type HttpCommandOptions, HttpException, HttpRedirectResponse, HttpRedirector, HttpResponseException, HttpResponseFactory, HttpServerError, HttpServiceProvider, IncomingRequest, type IncomingRequestOptions, type JsonWhenCallback, Kernel, MiddlewareConfiguration, type RenderCallback, type ReportCallback, type ResolvedServeOptions, type RouteListFilters, type RouteListRow, type RunningServer, type Runtime, type ServeOptions, type StackFrame, TrimStrings, type TypedRedirector, abort, abortIf, abortUnless, addApiRouting, applyExceptionCallbacks, applyMiddlewareCallbacks, back, bunRuntime, clientAddress, contentDisposition, currentRequest, debugPage, detectRuntime, escapeHtml, exceedsJsonDepth, exportFileName, exportSite, extensionFromMime, filterRouteList, formatRouteList, fromEntries, ipMatches, isBlank, mergeRecursive, mimeFromExtension, minimalPage, nodeRequestHandler, normalizeLists, optionalCookieJar, optionalSession, parseBracketKey, readBody, redirect, registerHttpCommands, request, requestScope, response, route, routeListRows, sameSiteLinks, sanitize, serve, serveBun, serveNode, servePublicFile, setClientAddress, stackFrames, statusFor, statusText, summarizeValidation, toWebRequest, transformLeaves, url, writeResponse };