@dunx/http 3.6.0 → 3.8.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/README.md +9 -0
- package/dist/chunk-08k9vq31.js +94 -0
- package/dist/{chunk-p9hdmkm6.js → chunk-1jt27yka.js} +8 -83
- package/dist/chunk-3eecdh6d.js +160 -0
- package/dist/chunk-cx4btdwe.js +56 -0
- package/dist/client/service.d.ts +13 -10
- package/dist/client/sse.d.ts +22 -8
- package/dist/client.d.ts +2 -0
- package/dist/client.js +72 -20
- package/dist/connect/middleware.d.ts +26 -0
- package/dist/connect/module.d.ts +29 -0
- package/dist/connect/options.d.ts +63 -0
- package/dist/connect/registry.d.ts +47 -0
- package/dist/connect.d.ts +12 -0
- package/dist/connect.js +213 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +312 -189
- package/dist/internal.d.ts +8 -10
- package/dist/internal.js +7 -3
- package/dist/route/claims.d.ts +11 -0
- package/dist/route/metadata.d.ts +11 -0
- package/dist/route/prefix.d.ts +6 -0
- package/dist/server/application.d.ts +1 -1
- package/dist/server/binding.d.ts +4 -2
- package/dist/server/claimed-routes.d.ts +15 -0
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/metrics.d.ts +2 -0
- package/dist/server/middleware.d.ts +19 -2
- package/dist/server/options-provider.d.ts +3 -0
- package/dist/server/options.d.ts +8 -0
- package/dist/server/routes.d.ts +7 -2
- package/dist/server/trace-context.d.ts +9 -11
- package/dist/sse/decorators.d.ts +28 -0
- package/dist/sse/event.d.ts +20 -0
- package/dist/sse/stream.d.ts +38 -0
- package/dist/static/files.d.ts +7 -0
- package/dist/static/options.d.ts +2 -2
- package/dist/throttle/guard.d.ts +3 -1
- package/package.json +19 -2
- package/dist/chunk-gmtwad7f.js +0 -71
package/dist/internal.d.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What the framework calls on itself, and the only place it is exported from
|
|
2
|
+
* What the framework calls on itself, and the only place it is exported from: the
|
|
3
|
+
* discovery readers and route metadata `@dunx/dashboard`, `@dunx/mcp`,
|
|
4
|
+
* `@dunx/openapi` and `@dunx/testing` import. The barrel stays the surface an app
|
|
5
|
+
* writes against.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
* package
|
|
6
|
-
*
|
|
7
|
-
* `@dunx/dashboard`, `@dunx/mcp`, `@dunx/openapi` and `@dunx/testing` import.
|
|
8
|
-
*
|
|
9
|
-
* It held 62 symbols and 50 had no importer, all of them reachable from inside
|
|
10
|
-
* this package by relative import. Add one back when a sibling needs it.
|
|
11
|
-
*
|
|
12
|
-
* No stability promise attaches to this subpath.
|
|
7
|
+
* It held 62 symbols and 50 had no importer, all reachable from inside this
|
|
8
|
+
* package by relative import. Add one back when a sibling needs it. No stability
|
|
9
|
+
* promise attaches here.
|
|
13
10
|
*/
|
|
14
11
|
export { discoverRoutes, joinPath, type DiscoveredRoute, } from './route/discover.js';
|
|
15
12
|
export { RoutePrefix } from './route/prefix.js';
|
|
16
13
|
export { defaultStatusFor } from './route/marker.js';
|
|
17
14
|
export { gatewaysOf, routesOf, type GatewayHandler, type GatewayNode, type RouteInputs, type RouteNode, } from './inspect.js';
|
|
18
15
|
export { buildContext } from './server/context.js';
|
|
16
|
+
export { IMMUTABLE_CACHE_CONTROL } from './static/files.js';
|
|
19
17
|
export { embedJson } from './server/html.js';
|
|
20
18
|
export { isGateway } from './ws/marker.js';
|
package/dist/internal.js
CHANGED
|
@@ -7,11 +7,14 @@ import {
|
|
|
7
7
|
HIDDEN2,
|
|
8
8
|
joinPath2,
|
|
9
9
|
discoverRoutes2,
|
|
10
|
-
RoutePrefix2
|
|
10
|
+
RoutePrefix2
|
|
11
|
+
} from "./chunk-1jt27yka.js";
|
|
12
|
+
import {
|
|
11
13
|
isGateway2,
|
|
12
14
|
discoverGateway,
|
|
13
|
-
buildContext2
|
|
14
|
-
|
|
15
|
+
buildContext2,
|
|
16
|
+
IMMUTABLE_CACHE_CONTROL2
|
|
17
|
+
} from "./chunk-3eecdh6d.js";
|
|
15
18
|
// src/inspect.ts
|
|
16
19
|
import {
|
|
17
20
|
classOf,
|
|
@@ -72,6 +75,7 @@ var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.opti
|
|
|
72
75
|
// src/server/html.ts
|
|
73
76
|
var embedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
74
77
|
export {
|
|
78
|
+
IMMUTABLE_CACHE_CONTROL2 as IMMUTABLE_CACHE_CONTROL,
|
|
75
79
|
RoutePrefix2 as RoutePrefix,
|
|
76
80
|
buildContext2 as buildContext,
|
|
77
81
|
defaultStatusFor2 as defaultStatusFor,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What has claimed each path, so a second claim is a boot error naming both
|
|
3
|
+
* rather than a silent overwrite. Routes and RPCs share it because the failure
|
|
4
|
+
* is the same one: a table keyed by path keeps whichever registered last.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PathClaims {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(noun: string, remedy: string);
|
|
9
|
+
/** Records `owner` against `key`, or throws naming the one already there. */
|
|
10
|
+
claim(key: string, owner: string): void;
|
|
11
|
+
}
|
package/dist/route/metadata.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Server } from 'bun';
|
|
1
2
|
import type { Ctor } from '@dunx/core';
|
|
2
3
|
import type { Middleware } from '../server/middleware.js';
|
|
3
4
|
/** What a route's decorators resolved to, keyed by `MetaKey.id`. */
|
|
@@ -28,6 +29,16 @@ export declare const HIDDEN: MetaKey<boolean>;
|
|
|
28
29
|
* genuinely public route from one that matched nothing.
|
|
29
30
|
*/
|
|
30
31
|
export declare const UNMATCHED: MetaKey<boolean>;
|
|
32
|
+
/** This route idles by design, so `buildRoutes` clears Bun's idle deadline for
|
|
33
|
+
* it with the server it is handed. `@Sse` sets it; a raw stream can too. */
|
|
34
|
+
export declare const STREAMS: MetaKey<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* The server that received the request, on the unmatched path only, where the
|
|
37
|
+
* context is built per request. A matched route's is built once at boot and
|
|
38
|
+
* carries none: those clear their deadline through {@link STREAMS}. Not exported
|
|
39
|
+
* from the barrel - `ConnectMiddleware` is the only reader.
|
|
40
|
+
*/
|
|
41
|
+
export declare const REQUEST_SERVER: MetaKey<Server<unknown>>;
|
|
31
42
|
export declare const Roles: (...roles: readonly string[]) => <F extends object>(target: F) => F;
|
|
32
43
|
export declare const Public: () => <F extends object>(target: F) => F;
|
|
33
44
|
/**
|
package/dist/route/prefix.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A leading slash and no trailing one, so `${path}/x` is never `//x`. `/` is
|
|
3
|
+
* what an empty prefix normalises to; a caller that wants `''` there maps it
|
|
4
|
+
* itself, which is the only difference between the two users.
|
|
5
|
+
*/
|
|
6
|
+
export declare const normalizePrefix: (path: string) => string;
|
|
1
7
|
/**
|
|
2
8
|
* The global prefix, as `listen()` resolved it.
|
|
3
9
|
*
|
|
@@ -3,7 +3,7 @@ import { ShutdownAware, type App, type Ctor, type InjectionToken, type ModuleRef
|
|
|
3
3
|
import type { DiscoveredRoute } from '../route/discover.js';
|
|
4
4
|
import type { WebSocketRuntime } from '../ws/adapter.js';
|
|
5
5
|
import type { CorsOptions } from './cors.js';
|
|
6
|
-
import type
|
|
6
|
+
import { type Middleware } from './middleware.js';
|
|
7
7
|
import { type AppSettings } from './settings.js';
|
|
8
8
|
import type { HttpOptions } from './options.js';
|
|
9
9
|
/**
|
package/dist/server/binding.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Server } from 'bun';
|
|
2
2
|
import type { SocketData } from '../ws/socket.js';
|
|
3
3
|
import type { WebSocketRuntime } from '../ws/adapter.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ServedHandler } from './middleware.js';
|
|
5
5
|
import { type BunRoutes } from './routes.js';
|
|
6
6
|
/** What `listen()` computes and hands the binding, once the table is final. */
|
|
7
7
|
export interface BindingPlan {
|
|
8
8
|
readonly port: number;
|
|
9
9
|
readonly routes: BunRoutes;
|
|
10
|
-
readonly fetch:
|
|
10
|
+
readonly fetch: ServedHandler;
|
|
11
11
|
readonly websocket: WebSocketRuntime | undefined;
|
|
12
12
|
}
|
|
13
13
|
/** The protocol settings, fixed at construction. `undefined` leaves Bun's own
|
|
@@ -20,6 +20,8 @@ export interface BindingProtocols {
|
|
|
20
20
|
* merged into the main table and a second `Bun.serve` takes them.
|
|
21
21
|
*/
|
|
22
22
|
readonly gatewayPort?: number | undefined;
|
|
23
|
+
/** Seconds a request may idle before Bun severs it. `undefined` leaves Bun's 10. */
|
|
24
|
+
readonly idleTimeout?: number | undefined;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* The two per-server counters a metrics reader wants, summed across however
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The paths a path-claiming middleware serves off the unmatched fallback.
|
|
3
|
+
*
|
|
4
|
+
* `ThrottleGuard` skips unmatched paths so a burst of 404s cannot spend a real
|
|
5
|
+
* caller's budget. A claimed path is not a 404: something answers it, and it
|
|
6
|
+
* costs whatever that costs, so it is rate limited like any route.
|
|
7
|
+
*
|
|
8
|
+
* Attached by `listen()`, for the reason `RoutePrefix` is: the set is not known
|
|
9
|
+
* until every middleware has been resolved.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ClaimedRoutes {
|
|
12
|
+
#private;
|
|
13
|
+
attach(paths: Iterable<string>): void;
|
|
14
|
+
has(path: string): boolean;
|
|
15
|
+
}
|
package/dist/server/cors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RouteHandler } from './middleware.js';
|
|
1
|
+
import type { RouteHandler, ServedHandler } from './middleware.js';
|
|
2
2
|
export type CorsOrigin = string | readonly string[] | ((origin: string) => boolean);
|
|
3
3
|
export interface CorsOptions {
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ export interface CorsOptions {
|
|
|
17
17
|
readonly maxAge?: number;
|
|
18
18
|
}
|
|
19
19
|
/** Adds the response-side CORS headers. One extra closure per route, at boot. */
|
|
20
|
-
export declare const withCors: (options: CorsOptions, handler:
|
|
20
|
+
export declare const withCors: (options: CorsOptions, handler: ServedHandler) => ServedHandler;
|
|
21
21
|
/**
|
|
22
22
|
* `Bun.serve({ routes })` answers a method miss with 404, so a preflight cannot be
|
|
23
23
|
* inferred - every CORS-enabled path gets its own `OPTIONS` handler, built at boot
|
package/dist/server/metrics.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export declare class RequestMetrics {
|
|
|
54
54
|
* reflecting a deploy three days ago; who calls this is the app's decision.
|
|
55
55
|
*/
|
|
56
56
|
reset(): void;
|
|
57
|
+
/** Internal: the paths a middleware answers off the fallback. */
|
|
58
|
+
claim(paths: Iterable<string>): void;
|
|
57
59
|
/**
|
|
58
60
|
* Internal: `listen()` hands the bound server's counters to the resolved
|
|
59
61
|
* singleton. Structural, so an app serving from two ports can hand over the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BunRequest } from 'bun';
|
|
1
|
+
import type { BunRequest, Server } from 'bun';
|
|
2
2
|
import type { RouteContext } from './context.js';
|
|
3
3
|
export type Next = () => Promise<Response>;
|
|
4
4
|
/**
|
|
@@ -15,6 +15,23 @@ export type RouteHandler = (req: BunRequest) => Promise<Response>;
|
|
|
15
15
|
* Bun accepts a plain `Response`, which is what lets a route with nothing to
|
|
16
16
|
* await skip promises altogether - see `buildRoutes`.
|
|
17
17
|
*/
|
|
18
|
-
export type ServedHandler = (req: BunRequest
|
|
18
|
+
export type ServedHandler = (req: BunRequest,
|
|
19
|
+
/** The server that received the request. Bun always passes it; optional so a
|
|
20
|
+
* test can call a handler with the request alone. See `STREAMS`. */
|
|
21
|
+
server?: Server<unknown>) => Response | Promise<Response>;
|
|
19
22
|
/** Folded into one closure per route at boot - no per-request array iteration. */
|
|
20
23
|
export declare const compose: (middleware: readonly Middleware[], ctx: RouteContext, handler: RouteHandler) => RouteHandler;
|
|
24
|
+
/**
|
|
25
|
+
* Middleware that answers a fixed set of paths itself, on the unmatched path.
|
|
26
|
+
* `buildRoutes` cross-checks them against the route table: Bun matches a route
|
|
27
|
+
* first, so a controller on one of these would shadow it with nothing said.
|
|
28
|
+
*/
|
|
29
|
+
export interface ClaimsPaths {
|
|
30
|
+
claimedPaths(): readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* The methods those paths answer. `preflight` is mounted over the route table,
|
|
33
|
+
* which a claimed path is not in, so an `OPTIONS` would reach the 404.
|
|
34
|
+
*/
|
|
35
|
+
claimedMethods(): readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export declare const hasClaimedPaths: (value: object) => value is ClaimsPaths;
|
|
@@ -104,6 +104,9 @@ export declare abstract class HttpOptionsProvider {
|
|
|
104
104
|
* usable with one. See {@link HttpOptions.gatewayPort}.
|
|
105
105
|
*/
|
|
106
106
|
get gatewayPort(): number | undefined;
|
|
107
|
+
/** Seconds a request may idle before Bun severs it. See
|
|
108
|
+
* {@link HttpOptions.idleTimeout}. */
|
|
109
|
+
get idleTimeout(): number | undefined;
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
109
112
|
* The base itself, bound when no module bound a subclass. Concrete because the
|
package/dist/server/options.d.ts
CHANGED
|
@@ -149,4 +149,12 @@ export interface HttpOptions extends AppOptions {
|
|
|
149
149
|
* gateway is declared. See docs/guide/20-deployment.md.
|
|
150
150
|
*/
|
|
151
151
|
readonly gatewayPort?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Seconds a request may go without traffic before `Bun.serve` severs it. `0`
|
|
154
|
+
* removes the limit for every request, which is what a slowloris counts on;
|
|
155
|
+
* a route that idles declares `meta(STREAMS, true)` instead, as `@Sse` does.
|
|
156
|
+
*
|
|
157
|
+
* @default 10, which is Bun's
|
|
158
|
+
*/
|
|
159
|
+
readonly idleTimeout?: number;
|
|
152
160
|
}
|
package/dist/server/routes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type HttpMethod } from '../route/marker.js';
|
|
|
4
4
|
import type { UpgradeHandler } from '../ws/adapter.js';
|
|
5
5
|
import { type CorsOptions } from './cors.js';
|
|
6
6
|
import { type ErrorMapper } from './errors.js';
|
|
7
|
-
import { type Middleware, type
|
|
7
|
+
import { type Middleware, type ServedHandler } from './middleware.js';
|
|
8
8
|
/** How a `@UseGuards` class becomes an instance. `listen()` passes `app.get`. */
|
|
9
9
|
/**
|
|
10
10
|
* How a guard or a module's middleware becomes an instance.
|
|
@@ -58,7 +58,12 @@ export declare const withUpgradeRoutes: (routes: BunRoutes, gateways: ReadonlyMa
|
|
|
58
58
|
* `ctx.get(UNMATCHED)` is the cheaper half: set here and by no real route, so a
|
|
59
59
|
* middleware can tell a miss from a handler's own 404 before calling `next()`.
|
|
60
60
|
*/
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* A route and a path-claiming middleware cannot share a path: Bun matches the
|
|
63
|
+
* route, the fallback never runs, and the claim is silently dead.
|
|
64
|
+
*/
|
|
65
|
+
export declare const assertNoShadowedClaims: (discovered: readonly DiscoveredRoute[], middleware: readonly Middleware[]) => void;
|
|
66
|
+
export declare const buildFallback: (middleware?: readonly Middleware[], onError?: ErrorMapper, cors?: CorsOptions, notFound?: 'guarded' | 'public') => ServedHandler;
|
|
62
67
|
export declare const buildRoutes: (discovered: readonly DiscoveredRoute[], middleware?: readonly Middleware[], onError?: ErrorMapper, cors?: CorsOptions, resolve?: GuardResolver) => BunRoutes;
|
|
63
68
|
/**
|
|
64
69
|
* A second key per route ending in `/`, holding the handlers the first one has.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { TRACEPARENT_HEADER, TRACESTATE_HEADER, type TraceIds } from '@dunx/core';
|
|
2
|
+
export { TRACEPARENT_HEADER, TRACESTATE_HEADER };
|
|
3
3
|
/**
|
|
4
4
|
* The span that answered, sent back so a caller can record which of the callee's
|
|
5
5
|
* spans its own span points at. Same four fields as `traceparent`, and the
|
|
@@ -12,15 +12,14 @@ export declare const TRACESTATE_HEADER = "tracestate";
|
|
|
12
12
|
* and adoption is thin, so treat a caller reading it as a bonus.
|
|
13
13
|
*/
|
|
14
14
|
export declare const TRACERESPONSE_HEADER = "traceresponse";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/**
|
|
16
|
+
* This server's view of a trace: the {@link TraceIds} on the wire plus the
|
|
17
|
+
* caller's span and any vendor `tracestate`, neither of which a `traceparent`
|
|
18
|
+
* carries on its own.
|
|
19
|
+
*/
|
|
20
|
+
export interface Trace extends TraceIds {
|
|
20
21
|
/** The caller's span, when one arrived in `traceparent`. */
|
|
21
22
|
readonly parentSpanId?: string;
|
|
22
|
-
/** Two hex digits. Bit 0 is `sampled`. */
|
|
23
|
-
readonly flags: string;
|
|
24
23
|
/** `tracestate` verbatim, when one arrived. Vendor data this server does not read. */
|
|
25
24
|
readonly state?: string;
|
|
26
25
|
}
|
|
@@ -41,7 +40,6 @@ export interface Trace {
|
|
|
41
40
|
* request carries no correlation id at all.
|
|
42
41
|
*/
|
|
43
42
|
export declare class TraceContext {
|
|
44
|
-
#private;
|
|
45
43
|
/**
|
|
46
44
|
* The inbound `traceparent`, or a fresh trace. A malformed header is discarded
|
|
47
45
|
* rather than repaired, as the standard requires. Version `ff` is invalid; a
|
|
@@ -63,7 +61,7 @@ export declare class TraceContext {
|
|
|
63
61
|
* The `traceparent` to send upstream. This server's span becomes the callee's
|
|
64
62
|
* parent, so the two link without inventing a span nothing logged.
|
|
65
63
|
*/
|
|
66
|
-
static header(trace:
|
|
64
|
+
static header(trace: TraceIds): string;
|
|
67
65
|
/**
|
|
68
66
|
* The response, carrying `traceresponse` if this request adopted a trace.
|
|
69
67
|
*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type RoutePath } from '../route/marker.js';
|
|
2
|
+
import type { Input, RouteSchemas } from '../route/schema.js';
|
|
3
|
+
import type { SseEvent } from './event.js';
|
|
4
|
+
import { SseStream } from './stream.js';
|
|
5
|
+
/** The request half of {@link RouteSchemas}: no body, and always 200. */
|
|
6
|
+
export type SseSchemas = Pick<RouteSchemas, 'params' | 'query'>;
|
|
7
|
+
/** What a `@Sse` handler may answer with. */
|
|
8
|
+
export type SseResult = SseStream | AsyncIterable<SseEvent>;
|
|
9
|
+
/** {@link Input} plus the resume header. */
|
|
10
|
+
export type SseInput<O extends SseSchemas> = Input<O> & {
|
|
11
|
+
/** The `id` this client last saw, from `Last-Event-ID`. Absent on a first connection. */
|
|
12
|
+
readonly lastEventId: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A `GET` route answering `text/event-stream`, from a handler returning an
|
|
16
|
+
* `AsyncIterable<SseEvent>` or an {@link SseStream}.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* @Sse('/progress')
|
|
20
|
+
* async *progress(input: SseInput<RouteSchemas>): AsyncGenerator<SseEvent> {
|
|
21
|
+
* yield { data: { step: 10 }, id: '10' };
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The parameter is annotated for the reason `@Get`'s is: a standard decorator
|
|
26
|
+
* can check a parameter's type but not supply one.
|
|
27
|
+
*/
|
|
28
|
+
export declare const Sse: <const O extends SseSchemas>(path?: RoutePath, options?: O) => <H extends (input: SseInput<O>) => SseResult | Promise<SseResult>>(value: H, _context: ClassMethodDecoratorContext) => H;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One server-sent event. Every field is optional: `retry` alone changes the
|
|
3
|
+
* reconnection delay, and `event` or `id` alone carries an empty `data:` so it
|
|
4
|
+
* dispatches, which a frame with no data field at all does not.
|
|
5
|
+
*/
|
|
6
|
+
export interface SseEvent {
|
|
7
|
+
/** A string as it is, anything else through `JSON.stringify`. Each line of it
|
|
8
|
+
* becomes its own `data:` line. */
|
|
9
|
+
readonly data?: unknown;
|
|
10
|
+
/** The `event:` name the client listens for. Absent dispatches `message`. */
|
|
11
|
+
readonly event?: string;
|
|
12
|
+
/** Sent back as `Last-Event-ID` on the client's next connection. */
|
|
13
|
+
readonly id?: string;
|
|
14
|
+
/** Milliseconds the client waits before reconnecting, truncated to an integer. */
|
|
15
|
+
readonly retry?: number;
|
|
16
|
+
}
|
|
17
|
+
/** `event`, framed, ending with the blank line that dispatches it. */
|
|
18
|
+
export declare const frameEvent: (event: SseEvent) => string;
|
|
19
|
+
/** A comment line: bytes on the wire that dispatch nothing, which is a heartbeat. */
|
|
20
|
+
export declare const frameComment: (text: string) => string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type SseEvent } from './event.js';
|
|
2
|
+
export interface SseStreamOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Milliseconds between the comment lines that stop a proxy seeing no bytes
|
|
5
|
+
* from reaping the connection. `0` sends none. @default 15000
|
|
6
|
+
*/
|
|
7
|
+
readonly heartbeatMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A server-sent-events response, held open by a `ReadableStream` in a `Response`.
|
|
11
|
+
* `@Sse` builds one for a handler returning an `AsyncIterable` and takes one a
|
|
12
|
+
* handler built itself; outside a route it is a `Response` a `@Get` can return.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SseStream {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(options?: SseStreamOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Pumps `events` into a new stream, closing it when the iterable ends. A throw
|
|
19
|
+
* once the headers are out cannot become a status, so the body ends without its
|
|
20
|
+
* terminal chunk and an `EventSource` reconnects.
|
|
21
|
+
*/
|
|
22
|
+
static from(events: AsyncIterable<SseEvent>, options?: SseStreamOptions): SseStream;
|
|
23
|
+
/** Aborts when the client goes away, for a handler that waits between events
|
|
24
|
+
* to race: `for await` only sees a disconnect between yields. */
|
|
25
|
+
get signal(): AbortSignal;
|
|
26
|
+
/** The `id` of the last event sent, or `undefined` if none carried one. */
|
|
27
|
+
get lastEventId(): string | undefined;
|
|
28
|
+
get closed(): boolean;
|
|
29
|
+
/** Frames and enqueues one event. A send after the stream closed is dropped. */
|
|
30
|
+
send(event: SseEvent): void;
|
|
31
|
+
/** A comment line, which is what the heartbeat sends. */
|
|
32
|
+
comment(text?: string): void;
|
|
33
|
+
close(): void;
|
|
34
|
+
/**
|
|
35
|
+
* `headers` merge under the three this sets. No status: an event stream is a
|
|
36
|
+
* 200 or it is not one. */
|
|
37
|
+
toResponse(headers?: Readonly<Record<string, string>>): Response;
|
|
38
|
+
}
|
package/dist/static/files.d.ts
CHANGED
|
@@ -2,6 +2,13 @@ import type { BunRequest } from 'bun';
|
|
|
2
2
|
import type { Middleware, Next } from '../server/middleware.js';
|
|
3
3
|
import type { RouteContext } from '../server/context.js';
|
|
4
4
|
import { StaticOptions } from './options.js';
|
|
5
|
+
/**
|
|
6
|
+
* One year, and `immutable`, which is only honest for a name that changes with
|
|
7
|
+
* the bytes: a content-addressed file, or a URL carrying the installed version.
|
|
8
|
+
* `@dunx/openapi` serves a renderer's assets under the second rule and takes this
|
|
9
|
+
* through `@dunx/http/internal`, so the two cannot drift.
|
|
10
|
+
*/
|
|
11
|
+
export declare const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
5
12
|
/**
|
|
6
13
|
* Static files, on `Bun.file`. A `Bun.file` handed to a `Response` already
|
|
7
14
|
* streams, sets `content-type`, answers a `Range` request and uses `sendfile(2)`,
|
package/dist/static/options.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizePrefix } from '../route/prefix.js';
|
|
1
2
|
export interface StaticOptionsInit {
|
|
2
3
|
/**
|
|
3
4
|
* The directory served. Resolved once, at construction, and every request is
|
|
@@ -42,5 +43,4 @@ export declare class StaticOptions {
|
|
|
42
43
|
readonly immutable: (pathname: string) => boolean;
|
|
43
44
|
constructor(init: StaticOptionsInit);
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
export declare const normalizePrefix: (path: string) => string;
|
|
46
|
+
export { normalizePrefix };
|
package/dist/throttle/guard.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Logger } from '@dunx/core';
|
|
2
2
|
import type { BunRequest } from 'bun';
|
|
3
|
+
import { ClaimedRoutes } from '../server/claimed-routes.js';
|
|
3
4
|
import { ClientAddress } from '../server/client-address.js';
|
|
4
5
|
import type { RouteContext } from '../server/context.js';
|
|
5
6
|
import type { Middleware, Next } from '../server/middleware.js';
|
|
@@ -23,6 +24,7 @@ export declare class ThrottleGuard implements Middleware {
|
|
|
23
24
|
private readonly store;
|
|
24
25
|
private readonly address;
|
|
25
26
|
private readonly logger;
|
|
26
|
-
|
|
27
|
+
private readonly claimed;
|
|
28
|
+
constructor(options: ThrottleOptions, store: ThrottleStore, address: ClientAddress, logger: Logger, claimed: ClaimedRoutes);
|
|
27
29
|
handle(req: BunRequest, ctx: RouteContext, next: Next): Promise<Response>;
|
|
28
30
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/http",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
|
+
"connect-rpc",
|
|
7
8
|
"controller",
|
|
8
9
|
"dunx",
|
|
10
|
+
"grpc-web",
|
|
9
11
|
"http",
|
|
10
12
|
"pubsub",
|
|
11
13
|
"realtime",
|
|
@@ -41,6 +43,10 @@
|
|
|
41
43
|
"types": "./dist/client.d.ts",
|
|
42
44
|
"import": "./dist/client.js"
|
|
43
45
|
},
|
|
46
|
+
"./connect": {
|
|
47
|
+
"types": "./dist/connect.d.ts",
|
|
48
|
+
"import": "./dist/connect.js"
|
|
49
|
+
},
|
|
44
50
|
"./internal": {
|
|
45
51
|
"types": "./dist/internal.d.ts",
|
|
46
52
|
"import": "./dist/internal.js"
|
|
@@ -59,16 +65,27 @@
|
|
|
59
65
|
"@arkv/shared": "0.8.0"
|
|
60
66
|
},
|
|
61
67
|
"devDependencies": {
|
|
68
|
+
"@bufbuild/protobuf": "2.15.0",
|
|
69
|
+
"@connectrpc/connect": "2.2.0",
|
|
70
|
+
"@connectrpc/connect-web": "2.2.0",
|
|
62
71
|
"@dunx/core": "workspace:*",
|
|
63
72
|
"@opentelemetry/api": "1.9.1",
|
|
64
73
|
"@opentelemetry/core": "2.11.0",
|
|
65
74
|
"@opentelemetry/sdk-trace-node": "2.11.0"
|
|
66
75
|
},
|
|
67
76
|
"peerDependencies": {
|
|
68
|
-
"@
|
|
77
|
+
"@bufbuild/protobuf": "^2.15.0",
|
|
78
|
+
"@connectrpc/connect": "^2.2.0",
|
|
79
|
+
"@dunx/core": "^3.8.0",
|
|
69
80
|
"@types/bun": ">=1.4.1"
|
|
70
81
|
},
|
|
71
82
|
"peerDependenciesMeta": {
|
|
83
|
+
"@bufbuild/protobuf": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"@connectrpc/connect": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
72
89
|
"@types/bun": {
|
|
73
90
|
"optional": true
|
|
74
91
|
}
|
package/dist/chunk-gmtwad7f.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/server/trace-context.ts
|
|
3
|
-
var TRACEPARENT_HEADER2 = "traceparent";
|
|
4
|
-
var TRACESTATE_HEADER2 = "tracestate";
|
|
5
|
-
var TRACERESPONSE_HEADER2 = "traceresponse";
|
|
6
|
-
var HEX_32 = /^[0-9a-f]{32}$/;
|
|
7
|
-
var HEX_16 = /^[0-9a-f]{16}$/;
|
|
8
|
-
var HEX_2 = /^[0-9a-f]{2}$/;
|
|
9
|
-
var ZERO_TRACE = "0".repeat(32);
|
|
10
|
-
var ZERO_SPAN = "0".repeat(16);
|
|
11
|
-
var SAMPLED = 1;
|
|
12
|
-
var DEFAULT_FLAGS = "01";
|
|
13
|
-
var TRACE = Symbol.for("dunx.http.trace");
|
|
14
|
-
var EXPOSE = Symbol.for("dunx.http.trace.expose");
|
|
15
|
-
var mint = (bytes) => crypto.getRandomValues(new Uint8Array(bytes)).toHex();
|
|
16
|
-
|
|
17
|
-
class TraceContext2 {
|
|
18
|
-
static adopt(req, expose = true) {
|
|
19
|
-
const inbound = TraceContext2.#parse(req.headers.get(TRACEPARENT_HEADER2));
|
|
20
|
-
const state = req.headers.get(TRACESTATE_HEADER2);
|
|
21
|
-
const trace = inbound === undefined ? { traceId: mint(16), spanId: mint(8), flags: DEFAULT_FLAGS } : {
|
|
22
|
-
traceId: inbound.traceId,
|
|
23
|
-
spanId: mint(8),
|
|
24
|
-
parentSpanId: inbound.spanId,
|
|
25
|
-
flags: inbound.flags,
|
|
26
|
-
...state === null ? {} : { state }
|
|
27
|
-
};
|
|
28
|
-
req[TRACE] = trace;
|
|
29
|
-
if (expose)
|
|
30
|
-
req[EXPOSE] = true;
|
|
31
|
-
return trace;
|
|
32
|
-
}
|
|
33
|
-
static of(req) {
|
|
34
|
-
return req[TRACE];
|
|
35
|
-
}
|
|
36
|
-
static header(trace) {
|
|
37
|
-
return `00-${trace.traceId}-${trace.spanId}-${trace.flags}`;
|
|
38
|
-
}
|
|
39
|
-
static stamp(response, req) {
|
|
40
|
-
const traced = req;
|
|
41
|
-
const trace = traced[TRACE];
|
|
42
|
-
if (trace !== undefined && traced[EXPOSE] === true) {
|
|
43
|
-
response.headers.set(TRACERESPONSE_HEADER2, TraceContext2.header(trace));
|
|
44
|
-
}
|
|
45
|
-
return response;
|
|
46
|
-
}
|
|
47
|
-
static sampled(trace) {
|
|
48
|
-
return (Number.parseInt(trace.flags, 16) & SAMPLED) === SAMPLED;
|
|
49
|
-
}
|
|
50
|
-
static #parse(header) {
|
|
51
|
-
if (header === null)
|
|
52
|
-
return;
|
|
53
|
-
const parts = header.split("-");
|
|
54
|
-
if (parts.length < 4)
|
|
55
|
-
return;
|
|
56
|
-
const [version, traceId, spanId, flags] = parts;
|
|
57
|
-
if (!HEX_2.test(version) || version === "ff")
|
|
58
|
-
return;
|
|
59
|
-
if (version === "00" && parts.length !== 4)
|
|
60
|
-
return;
|
|
61
|
-
if (!HEX_32.test(traceId) || traceId === ZERO_TRACE)
|
|
62
|
-
return;
|
|
63
|
-
if (!HEX_16.test(spanId) || spanId === ZERO_SPAN)
|
|
64
|
-
return;
|
|
65
|
-
if (!HEX_2.test(flags))
|
|
66
|
-
return;
|
|
67
|
-
return { traceId, spanId, flags };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export { TRACEPARENT_HEADER2, TRACESTATE_HEADER2, TRACERESPONSE_HEADER2, TraceContext2 };
|