@dunx/http 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5z96f3gr.js +110 -0
- package/dist/{chunk-x80f562w.js.map → chunk-5z96f3gr.js.map} +2 -2
- package/dist/client.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +309 -104
- package/dist/index.js.map +10 -6
- package/dist/inspect.d.ts +58 -0
- package/dist/server/request-logging.d.ts +17 -0
- package/dist/static/files.d.ts +35 -0
- package/dist/static/module.d.ts +27 -0
- package/dist/static/options.d.ts +46 -0
- package/package.json +2 -2
- package/dist/chunk-x80f562w.js +0 -40
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Dependency, type ModuleRef } from '@dunx/core';
|
|
2
|
+
export interface RouteInputs {
|
|
3
|
+
readonly body?: string;
|
|
4
|
+
readonly query?: string;
|
|
5
|
+
readonly params?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RouteNode {
|
|
8
|
+
readonly method: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly controller: string;
|
|
11
|
+
readonly handler: string;
|
|
12
|
+
readonly module: string;
|
|
13
|
+
readonly public: boolean;
|
|
14
|
+
readonly roles: readonly string[] | null;
|
|
15
|
+
/** Class-level `@UseGuards` first, then method-level, which is resolution order. */
|
|
16
|
+
readonly guards: readonly string[];
|
|
17
|
+
/** `@ApiHidden()`, so a caller can tell "not documented" from "not there". */
|
|
18
|
+
readonly hidden: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Which inputs the route validates, and by which Standard Schema vendor. The
|
|
21
|
+
* schemas themselves are not here: turning one into JSON Schema is zod-specific
|
|
22
|
+
* work that `@dunx/openapi` already does, and `dunx_openapi` is where it lives.
|
|
23
|
+
* What this answers is "does this route parse a body at all", which is the
|
|
24
|
+
* question that does not need a schema compiler.
|
|
25
|
+
*/
|
|
26
|
+
readonly validates: RouteInputs;
|
|
27
|
+
/** The success status the decorator declared, or null for the default. */
|
|
28
|
+
readonly status: number | null;
|
|
29
|
+
/** Status codes the route documents a response schema for. */
|
|
30
|
+
readonly responses: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
export declare const routesOf: (root: ModuleRef) => readonly RouteNode[];
|
|
33
|
+
/**
|
|
34
|
+
* Gateways are declared in `@Module({ providers })` like any other injectable and
|
|
35
|
+
* found by their marker, so they are read the same way routes are: the class's
|
|
36
|
+
* prototype, never an instance.
|
|
37
|
+
*
|
|
38
|
+
* `discoverGateway` does all of it - the path, the marked methods, the event each
|
|
39
|
+
* message handler claims - and `Object.create(Gateway.prototype)` satisfies its
|
|
40
|
+
* one argument, exactly as it satisfies `discoverRoutes`. Its sibling
|
|
41
|
+
* `discoverGateways` is the one that is unusable here: it takes a `resolve`
|
|
42
|
+
* callback and constructs every gateway, which is the boot this package exists to
|
|
43
|
+
* avoid. Only the bound `invoke` is dropped, being a function.
|
|
44
|
+
*/
|
|
45
|
+
export interface GatewayHandler {
|
|
46
|
+
readonly kind: string;
|
|
47
|
+
/** The envelope event a message handler claims; null is the raw catch-all. */
|
|
48
|
+
readonly event: string | null;
|
|
49
|
+
readonly method: string;
|
|
50
|
+
}
|
|
51
|
+
export interface GatewayNode {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly module: string;
|
|
55
|
+
readonly dependencies: readonly Dependency[];
|
|
56
|
+
readonly handlers: readonly GatewayHandler[];
|
|
57
|
+
}
|
|
58
|
+
export declare const gatewaysOf: (root: ModuleRef) => readonly GatewayNode[];
|
|
@@ -27,6 +27,23 @@ export interface RequestLoggingOptions {
|
|
|
27
27
|
* is what makes it free. `correlateIgnored` buys the correlation back.
|
|
28
28
|
*/
|
|
29
29
|
readonly ignore?: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* Path **prefixes** to skip, for a whole mount rather than one path.
|
|
32
|
+
*
|
|
33
|
+
* `ignore` is an exact-match `Set` because that is one lookup on the hot path
|
|
34
|
+
* and a health check is one path. A mount is not: `@dunx/dashboard` at
|
|
35
|
+
* `/_dunx` polls four endpoints every five seconds and bull-board pulls a
|
|
36
|
+
* dozen assets, and listing them is both tedious and wrong the moment either
|
|
37
|
+
* grows an endpoint.
|
|
38
|
+
*
|
|
39
|
+
* Scanned only when non-empty, so an app that sets none pays nothing - the
|
|
40
|
+
* same guard `ignore` has. Keep the list short; it is a loop.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* requestLogging: { ignorePrefix: ['/_dunx'] }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
readonly ignorePrefix?: readonly string[];
|
|
30
47
|
/**
|
|
31
48
|
* Keep the request id and the async scope on an `ignore`d path. Default
|
|
32
49
|
* **`false`**.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BunRequest } from 'bun';
|
|
2
|
+
import type { Middleware, Next } from '../server/middleware.js';
|
|
3
|
+
import type { RouteContext } from '../server/context.js';
|
|
4
|
+
import { StaticOptions } from './options.js';
|
|
5
|
+
/**
|
|
6
|
+
* Static files, on `Bun.file`.
|
|
7
|
+
*
|
|
8
|
+
* Nest has `ServeStaticModule` over `serve-static`, which is Express middleware
|
|
9
|
+
* doing its own `stat`, its own range parsing, its own ETag and its own MIME table.
|
|
10
|
+
* None of that is needed here: `Bun.file(path)` handed to a `Response` already
|
|
11
|
+
* streams, already sets `content-type` from the extension, already answers a
|
|
12
|
+
* `Range` request, and does the whole thing with `sendfile(2)` rather than reading
|
|
13
|
+
* into JavaScript. So this file is a **path check and a cache policy**, and that is
|
|
14
|
+
* the entire justification for it existing.
|
|
15
|
+
*
|
|
16
|
+
* A middleware rather than routes, for the same reason the dashboard is one: the
|
|
17
|
+
* file set is whatever is on disk at request time, and turning it into a
|
|
18
|
+
* `Bun.serve` route table would mean walking a directory at boot and being wrong
|
|
19
|
+
* the moment anything changed.
|
|
20
|
+
*/
|
|
21
|
+
export declare class StaticFiles implements Middleware {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options: StaticOptions);
|
|
24
|
+
/**
|
|
25
|
+
* The file for a request path, or `undefined` if it escapes the root.
|
|
26
|
+
*
|
|
27
|
+
* **The traversal check is the point of this method.** `..` segments are removed
|
|
28
|
+
* by `normalize`, but that alone is not enough: a root of `/srv/app` and a
|
|
29
|
+
* request for `/srv/app-secrets` both start with the same string, so the guard
|
|
30
|
+
* has to compare against the root **with a separator**. Both halves have to hold
|
|
31
|
+
* or a caller reads the filesystem.
|
|
32
|
+
*/
|
|
33
|
+
resolvePath(pathname: string): string | undefined;
|
|
34
|
+
handle(req: BunRequest, _ctx: RouteContext, next: Next): Promise<Response>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Deps, type DynamicModule, type FactoryProvider } from '@dunx/core';
|
|
2
|
+
import { type StaticOptionsInit } from './options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Serves a directory, the way Nest's `ServeStaticModule` does - and like the
|
|
5
|
+
* dashboard, **it does not register itself**. The app does:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const app = await HttpFactory.create(AppModule);
|
|
9
|
+
* app.use(StaticFiles);
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Position in the chain is the decision being left to the app. Static assets
|
|
13
|
+
* usually want to be *outside* an auth guard and *inside* request logging, and no
|
|
14
|
+
* default can know which. Anything outside the mount falls through untouched, so
|
|
15
|
+
* the app's own routes and its 404 behave exactly as before.
|
|
16
|
+
*
|
|
17
|
+
* There is no `index.html` fallback and no SPA rewrite. Both are one route in the
|
|
18
|
+
* app - `@Get('/*')` returning `Bun.file(...)` - and building them in would mean
|
|
19
|
+
* this middleware deciding what a 404 means for paths it does not own.
|
|
20
|
+
*/
|
|
21
|
+
export declare class StaticModule {
|
|
22
|
+
static forRoot(init: StaticOptionsInit): DynamicModule;
|
|
23
|
+
/** `forRoot` with the root read off the container - a config value, usually. */
|
|
24
|
+
static forRootAsync<const D extends Deps>(config: FactoryProvider<StaticOptionsInit, D> & {
|
|
25
|
+
readonly imports?: DynamicModule['imports'];
|
|
26
|
+
}): DynamicModule;
|
|
27
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface StaticOptionsInit {
|
|
2
|
+
/**
|
|
3
|
+
* The directory served. Resolved once, at construction, and every request is
|
|
4
|
+
* checked against it - see `StaticFiles.resolvePath`.
|
|
5
|
+
*/
|
|
6
|
+
readonly root: string;
|
|
7
|
+
/**
|
|
8
|
+
* The URL prefix it is served under. `/` serves from the root of the app, which
|
|
9
|
+
* is the usual case for a `public/` directory.
|
|
10
|
+
*
|
|
11
|
+
* @default '/'
|
|
12
|
+
*/
|
|
13
|
+
readonly path?: string;
|
|
14
|
+
/**
|
|
15
|
+
* `max-age` in seconds for anything `immutable` does not claim.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately short. A long max-age on a name that can change is a promise the
|
|
18
|
+
* server cannot keep, and the fix - a content hash in the filename - is the
|
|
19
|
+
* thing `immutable` is for.
|
|
20
|
+
*
|
|
21
|
+
* @default 60
|
|
22
|
+
*/
|
|
23
|
+
readonly maxAge?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Which paths may be cached forever.
|
|
26
|
+
*
|
|
27
|
+
* Only honest for a **content-addressed** name, where a change produces a
|
|
28
|
+
* different URL: `(path) => /\.[0-9a-f]{8}\.(js|css)$/.test(path)`. The default
|
|
29
|
+
* claims nothing, because guessing wrong here is a stale asset nobody can flush.
|
|
30
|
+
*/
|
|
31
|
+
readonly immutable?: (pathname: string) => boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A class, not an interface, so it is a runtime value and can therefore be a
|
|
35
|
+
* constructor parameter type that `@dunx/transform` records - the same reason
|
|
36
|
+
* `QueueOptions` and `RedisOptions` are classes.
|
|
37
|
+
*/
|
|
38
|
+
export declare class StaticOptions {
|
|
39
|
+
readonly root: string;
|
|
40
|
+
readonly path: string;
|
|
41
|
+
readonly maxAge: number;
|
|
42
|
+
readonly immutable: (pathname: string) => boolean;
|
|
43
|
+
constructor(init: StaticOptionsInit);
|
|
44
|
+
}
|
|
45
|
+
/** A leading slash and no trailing one, so `${path}/x` is never `//x`. */
|
|
46
|
+
export declare const normalizePrefix: (path: string) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@dunx/core": "workspace:*"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@dunx/core": "^1.
|
|
61
|
+
"@dunx/core": "^1.3.0",
|
|
62
62
|
"@types/bun": ">=1.3.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
package/dist/chunk-x80f562w.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/server/status.ts
|
|
3
|
-
var HttpStatusCode = Object.freeze({
|
|
4
|
-
OK: 200,
|
|
5
|
-
CREATED: 201,
|
|
6
|
-
ACCEPTED: 202,
|
|
7
|
-
NO_CONTENT: 204,
|
|
8
|
-
MOVED_PERMANENTLY: 301,
|
|
9
|
-
FOUND: 302,
|
|
10
|
-
NOT_MODIFIED: 304,
|
|
11
|
-
TEMPORARY_REDIRECT: 307,
|
|
12
|
-
PERMANENT_REDIRECT: 308,
|
|
13
|
-
BAD_REQUEST: 400,
|
|
14
|
-
UNAUTHORIZED: 401,
|
|
15
|
-
PAYMENT_REQUIRED: 402,
|
|
16
|
-
FORBIDDEN: 403,
|
|
17
|
-
NOT_FOUND: 404,
|
|
18
|
-
METHOD_NOT_ALLOWED: 405,
|
|
19
|
-
NOT_ACCEPTABLE: 406,
|
|
20
|
-
REQUEST_TIMEOUT: 408,
|
|
21
|
-
CONFLICT: 409,
|
|
22
|
-
GONE: 410,
|
|
23
|
-
PRECONDITION_FAILED: 412,
|
|
24
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
25
|
-
URI_TOO_LONG: 414,
|
|
26
|
-
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
27
|
-
IM_A_TEAPOT: 418,
|
|
28
|
-
UNPROCESSABLE_ENTITY: 422,
|
|
29
|
-
TOO_MANY_REQUESTS: 429,
|
|
30
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
31
|
-
NOT_IMPLEMENTED: 501,
|
|
32
|
-
BAD_GATEWAY: 502,
|
|
33
|
-
SERVICE_UNAVAILABLE: 503,
|
|
34
|
-
GATEWAY_TIMEOUT: 504
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export { HttpStatusCode };
|
|
38
|
-
|
|
39
|
-
//# debugId=881F02139124CAE264756E2164756E21
|
|
40
|
-
//# sourceMappingURL=chunk-x80f562w.js.map
|