@dunx/http 3.6.0 → 3.7.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/client/service.d.ts +13 -10
- package/dist/client/sse.d.ts +21 -8
- package/dist/client.d.ts +2 -0
- package/dist/client.js +52 -17
- package/dist/connect/middleware.d.ts +25 -0
- package/dist/connect/module.d.ts +29 -0
- package/dist/connect/options.d.ts +64 -0
- package/dist/connect/registry.d.ts +47 -0
- package/dist/connect.d.ts +12 -0
- package/dist/connect.js +208 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +253 -184
- 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/middleware.d.ts +14 -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/sse/decorators.d.ts +28 -0
- package/dist/sse/event.d.ts +19 -0
- package/dist/sse/stream.d.ts +35 -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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DescService } from '@bufbuild/protobuf';
|
|
2
|
+
import type { ConnectRouterOptions, ServiceImpl } from '@connectrpc/connect';
|
|
3
|
+
import type { Ctor, ModuleRef } from '@dunx/core';
|
|
4
|
+
/**
|
|
5
|
+
* Everything `createConnectRouter` takes except the protocol switches and
|
|
6
|
+
* `shutdownSignal`, which the container owns. `interceptors`, `contextValues`,
|
|
7
|
+
* `requestGate`, `jsonOptions` and the rest pass through untouched.
|
|
8
|
+
*/
|
|
9
|
+
export type ConnectRouterSettings = Omit<ConnectRouterOptions, 'connect' | 'grpc' | 'grpcWeb' | 'shutdownSignal'>;
|
|
10
|
+
/** One protobuf service and the class implementing it. */
|
|
11
|
+
export interface ConnectServiceRegistration {
|
|
12
|
+
readonly service: DescService;
|
|
13
|
+
readonly useClass: Ctor<object>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pairs a generated `DescService` with the class serving it, checking at compile
|
|
17
|
+
* time that the class has a method per RPC. A generic function rather than an
|
|
18
|
+
* object literal, which would lose the link between the two arguments.
|
|
19
|
+
*/
|
|
20
|
+
export declare const connectService: <T extends DescService>(service: T, useClass: Ctor<ServiceImpl<T>>) => ConnectServiceRegistration;
|
|
21
|
+
export interface ConnectOptionsInit extends ConnectRouterSettings {
|
|
22
|
+
/** The services to serve, each paired with its implementation class. */
|
|
23
|
+
readonly services: readonly ConnectServiceRegistration[];
|
|
24
|
+
/**
|
|
25
|
+
* Modules whose exports the implementation classes may inject. This module is
|
|
26
|
+
* its own scope and the classes are constructed in it, so a provider they need
|
|
27
|
+
* has to be exported by a module named here - importing it alongside does not
|
|
28
|
+
* reach them.
|
|
29
|
+
*/
|
|
30
|
+
readonly imports?: readonly ModuleRef[];
|
|
31
|
+
/**
|
|
32
|
+
* Mounted in front of every RPC path, empty by default, which leaves them at
|
|
33
|
+
* `/{package}.{Service}/{Method}`. `setGlobalPrefix` does not move them: that
|
|
34
|
+
* prefixes discovered routes, and these are matched by a middleware.
|
|
35
|
+
*/
|
|
36
|
+
readonly prefix?: string;
|
|
37
|
+
/** Connect, which a `curl` POST of JSON also speaks. @default true */
|
|
38
|
+
readonly connect?: boolean;
|
|
39
|
+
/** gRPC-Web, which browsers and `connect-go` speak. @default true */
|
|
40
|
+
readonly grpcWeb?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Seconds a **streaming** RPC may idle before `Bun.serve` severs it. `0` lifts
|
|
43
|
+
* the deadline for the whole call, which is the default because a gap between
|
|
44
|
+
* messages is the protocol rather than a symptom.
|
|
45
|
+
*
|
|
46
|
+
* Worth setting on a public mount: `ThrottleGuard` returns early on every
|
|
47
|
+
* unmatched path and an RPC is unmatched, so nothing else here bounds how long
|
|
48
|
+
* or how many streams one caller holds open.
|
|
49
|
+
*
|
|
50
|
+
* @default 0
|
|
51
|
+
*/
|
|
52
|
+
readonly streamTimeout?: number;
|
|
53
|
+
}
|
|
54
|
+
/** A class rather than an interface, so it is a runtime value the transform can
|
|
55
|
+
* record as a constructor parameter type. */
|
|
56
|
+
export declare class ConnectOptions {
|
|
57
|
+
readonly services: readonly ConnectServiceRegistration[];
|
|
58
|
+
readonly prefix: string;
|
|
59
|
+
readonly connect: boolean;
|
|
60
|
+
readonly grpcWeb: boolean;
|
|
61
|
+
readonly streamTimeout: number;
|
|
62
|
+
readonly router: ConnectRouterSettings;
|
|
63
|
+
constructor(init: ConnectOptionsInit);
|
|
64
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ConnectOptions } from './options.js';
|
|
2
|
+
/** What `createFetchHandler` returns: the `Bun.serve` signature exactly. */
|
|
3
|
+
export type ConnectHandler = (req: Request) => Promise<Response>;
|
|
4
|
+
/** One mounted RPC. */
|
|
5
|
+
export interface ConnectMethodInfo {
|
|
6
|
+
/** The path it answers, including {@link ConnectOptions.prefix}. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** e.g. `greet.v1.GreetService`. */
|
|
9
|
+
readonly service: string;
|
|
10
|
+
/** e.g. `Say`. */
|
|
11
|
+
readonly method: string;
|
|
12
|
+
/** `unary`, `server_streaming`, `client_streaming` or `bidi_streaming`. */
|
|
13
|
+
readonly kind: string;
|
|
14
|
+
/** e.g. `['grpc-web', 'connect']`. */
|
|
15
|
+
readonly protocols: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** What the middleware needs per path, in one lookup. */
|
|
18
|
+
export interface ConnectRoute {
|
|
19
|
+
readonly handle: ConnectHandler;
|
|
20
|
+
/** Anything but `unary`, so the gap between messages is the protocol. */
|
|
21
|
+
readonly streaming: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One fetch handler per RPC, built at boot into a path map, so a request costs a
|
|
25
|
+
* `Map.get`. Connect's own router picks the protocol off the content type.
|
|
26
|
+
*
|
|
27
|
+
* `grpc` is off and cannot be turned on: it carries `grpc-status` in an HTTP
|
|
28
|
+
* trailer and `Bun.serve` sends none, so advertising it would answer with a
|
|
29
|
+
* status no client reads. Probed on Bun 1.4.2.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ConnectRegistry {
|
|
32
|
+
#private;
|
|
33
|
+
/** Every mounted path, in registration order. */
|
|
34
|
+
get paths(): readonly string[];
|
|
35
|
+
/** Seconds a streaming call may idle; `0` lifts the deadline. */
|
|
36
|
+
readonly streamTimeout: number;
|
|
37
|
+
constructor(options: ConnectOptions, implementations: readonly object[]);
|
|
38
|
+
/** In registration order. */
|
|
39
|
+
get methods(): readonly ConnectMethodInfo[];
|
|
40
|
+
routeFor(path: string): ConnectRoute | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Aborts the signal every in-flight handler holds, so a long-running
|
|
43
|
+
* implementation gets its cue. It does not close an open response stream -
|
|
44
|
+
* the socket closing does. Measured on `@connectrpc/connect` 2.2.0.
|
|
45
|
+
*/
|
|
46
|
+
onShutdown(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dunx/http/connect` - protobuf services over Connect and gRPC-Web, mounted as
|
|
3
|
+
* middleware. `@connectrpc/connect` and `@bufbuild/protobuf` are optional peers,
|
|
4
|
+
* and importing `@dunx/http` does not load any of this.
|
|
5
|
+
*
|
|
6
|
+
* Native gRPC is not here: `grpc-status` travels in an HTTP trailer and
|
|
7
|
+
* `Bun.serve` sends none. See `internal/notes/research/rpc.md`.
|
|
8
|
+
*/
|
|
9
|
+
export { ConnectMiddleware } from './connect/middleware.js';
|
|
10
|
+
export { ConnectModule, type ConnectSettings } from './connect/module.js';
|
|
11
|
+
export { connectService, ConnectOptions, type ConnectOptionsInit, type ConnectRouterSettings, type ConnectServiceRegistration, } from './connect/options.js';
|
|
12
|
+
export { ConnectRegistry, type ConnectHandler, type ConnectMethodInfo, type ConnectRoute, } from './connect/registry.js';
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
UNMATCHED2,
|
|
4
|
+
REQUEST_SERVER,
|
|
5
|
+
normalizePrefix
|
|
6
|
+
} from "./chunk-1jt27yka.js";
|
|
7
|
+
import {
|
|
8
|
+
HttpStatusCode2
|
|
9
|
+
} from "./chunk-bg0dr54z.js";
|
|
10
|
+
import {
|
|
11
|
+
__decoratorStart,
|
|
12
|
+
__decoratorMetadata,
|
|
13
|
+
__runInitializers,
|
|
14
|
+
__decorateElement,
|
|
15
|
+
PathClaims
|
|
16
|
+
} from "./chunk-08k9vq31.js";
|
|
17
|
+
|
|
18
|
+
// src/connect/registry.ts
|
|
19
|
+
import {
|
|
20
|
+
createConnectRouter
|
|
21
|
+
} from "@connectrpc/connect";
|
|
22
|
+
import { createFetchHandler } from "@connectrpc/connect/protocol";
|
|
23
|
+
|
|
24
|
+
// src/connect/options.ts
|
|
25
|
+
var connectService = (service, useClass) => ({ service, useClass });
|
|
26
|
+
|
|
27
|
+
class ConnectOptions {
|
|
28
|
+
services;
|
|
29
|
+
prefix;
|
|
30
|
+
connect;
|
|
31
|
+
grpcWeb;
|
|
32
|
+
streamTimeout;
|
|
33
|
+
router;
|
|
34
|
+
constructor(init) {
|
|
35
|
+
const {
|
|
36
|
+
services,
|
|
37
|
+
prefix,
|
|
38
|
+
connect,
|
|
39
|
+
grpcWeb,
|
|
40
|
+
streamTimeout,
|
|
41
|
+
imports: _imports,
|
|
42
|
+
...router
|
|
43
|
+
} = init;
|
|
44
|
+
this.services = services;
|
|
45
|
+
const mounted = normalizePrefix(prefix ?? "");
|
|
46
|
+
this.prefix = mounted === "/" ? "" : mounted;
|
|
47
|
+
this.connect = connect ?? true;
|
|
48
|
+
this.grpcWeb = grpcWeb ?? true;
|
|
49
|
+
this.streamTimeout = streamTimeout ?? 0;
|
|
50
|
+
if (!Number.isFinite(this.streamTimeout) || this.streamTimeout < 0) {
|
|
51
|
+
throw new Error(`ConnectModule streamTimeout must be a non-negative number of seconds, got ${String(streamTimeout)}.`);
|
|
52
|
+
}
|
|
53
|
+
this.router = router;
|
|
54
|
+
if (!this.connect && !this.grpcWeb) {
|
|
55
|
+
throw new Error("ConnectModule needs at least one protocol, and both connect and " + "grpcWeb are false. Native gRPC is not a third option here: it " + "carries grpc-status in an HTTP trailer and Bun.serve sends none.");
|
|
56
|
+
}
|
|
57
|
+
if (services.length === 0) {
|
|
58
|
+
throw new Error("ConnectModule.forRoot was given no services. Pass at least one " + "connectService(Desc, Impl), or drop the module.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
Object.defineProperty(ConnectOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: ConnectOptionsInit" }] });
|
|
63
|
+
|
|
64
|
+
// src/connect/registry.ts
|
|
65
|
+
class ConnectRegistry {
|
|
66
|
+
#routes = new Map;
|
|
67
|
+
get paths() {
|
|
68
|
+
return [...this.#routes.keys()];
|
|
69
|
+
}
|
|
70
|
+
streamTimeout;
|
|
71
|
+
#methods = [];
|
|
72
|
+
#stopping = new AbortController;
|
|
73
|
+
constructor(options, implementations) {
|
|
74
|
+
this.streamTimeout = options.streamTimeout;
|
|
75
|
+
const router = createConnectRouter({
|
|
76
|
+
...options.router,
|
|
77
|
+
connect: options.connect,
|
|
78
|
+
grpcWeb: options.grpcWeb,
|
|
79
|
+
grpc: false,
|
|
80
|
+
shutdownSignal: this.#stopping.signal
|
|
81
|
+
});
|
|
82
|
+
options.services.forEach((registration, index) => {
|
|
83
|
+
const implementation = implementations[index];
|
|
84
|
+
if (implementation === undefined) {
|
|
85
|
+
throw new Error(`No instance was resolved for ${registration.useClass.name}, which ` + `serves ${registration.service.typeName}.`);
|
|
86
|
+
}
|
|
87
|
+
router.service(registration.service, implementation);
|
|
88
|
+
});
|
|
89
|
+
const claims = new PathClaims("RPC", "Register each service once, or give one its own prefix.");
|
|
90
|
+
for (const handler of router.handlers) {
|
|
91
|
+
const path = `${options.prefix}${handler.requestPath}`;
|
|
92
|
+
const rpc = `${handler.service.typeName}.${handler.method.name}`;
|
|
93
|
+
claims.claim(path, rpc);
|
|
94
|
+
this.#routes.set(path, {
|
|
95
|
+
handle: createFetchHandler(handler),
|
|
96
|
+
streaming: handler.method.methodKind !== "unary"
|
|
97
|
+
});
|
|
98
|
+
this.#methods.push({
|
|
99
|
+
path,
|
|
100
|
+
service: handler.service.typeName,
|
|
101
|
+
method: handler.method.name,
|
|
102
|
+
kind: handler.method.methodKind,
|
|
103
|
+
protocols: handler.protocolNames
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
get methods() {
|
|
108
|
+
return this.#methods;
|
|
109
|
+
}
|
|
110
|
+
routeFor(path) {
|
|
111
|
+
return this.#routes.get(path);
|
|
112
|
+
}
|
|
113
|
+
onShutdown() {
|
|
114
|
+
this.#stopping.abort();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
Object.defineProperty(ConnectRegistry, Symbol.for("dunx.deps"), { value: () => [ConnectOptions, { unresolved: "implementations: readonly object[]" }] });
|
|
118
|
+
|
|
119
|
+
// src/connect/middleware.ts
|
|
120
|
+
var NATIVE_GRPC = /^\s*application\/grpc\s*(?:[;+]|$)/i;
|
|
121
|
+
var grpcUnsupported = () => Response.json({
|
|
122
|
+
code: "unimplemented",
|
|
123
|
+
message: "This endpoint serves Connect and gRPC-Web, not gRPC. gRPC carries " + "grpc-status in an HTTP trailer and Bun.serve sends no trailers, so a " + "gRPC client would read every call as a protocol error. Use a Connect " + "or gRPC-Web transport, or put a proxy in front that translates."
|
|
124
|
+
}, { status: HttpStatusCode2.UNSUPPORTED_MEDIA_TYPE });
|
|
125
|
+
|
|
126
|
+
class ConnectMiddleware {
|
|
127
|
+
#registry;
|
|
128
|
+
constructor(registry) {
|
|
129
|
+
this.#registry = registry;
|
|
130
|
+
}
|
|
131
|
+
claimedPaths() {
|
|
132
|
+
return this.#registry.paths;
|
|
133
|
+
}
|
|
134
|
+
handle(req, ctx, next) {
|
|
135
|
+
if (ctx.get(UNMATCHED2) !== true)
|
|
136
|
+
return next();
|
|
137
|
+
const route = this.#registry.routeFor(ctx.path);
|
|
138
|
+
if (route === undefined)
|
|
139
|
+
return next();
|
|
140
|
+
const contentType = req.headers.get("content-type");
|
|
141
|
+
if (contentType !== null && NATIVE_GRPC.test(contentType)) {
|
|
142
|
+
return Promise.resolve(grpcUnsupported());
|
|
143
|
+
}
|
|
144
|
+
if (route.streaming) {
|
|
145
|
+
ctx.get(REQUEST_SERVER)?.timeout(req, this.#registry.streamTimeout);
|
|
146
|
+
}
|
|
147
|
+
return route.handle(req);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
Object.defineProperty(ConnectMiddleware, Symbol.for("dunx.deps"), { value: () => [ConnectRegistry] });
|
|
151
|
+
// src/connect/module.ts
|
|
152
|
+
import {
|
|
153
|
+
Module,
|
|
154
|
+
provide
|
|
155
|
+
} from "@dunx/core";
|
|
156
|
+
var build = (services, options, imports) => {
|
|
157
|
+
const implementations = services.map((registration) => registration.useClass);
|
|
158
|
+
return {
|
|
159
|
+
module: ConnectModule,
|
|
160
|
+
imports,
|
|
161
|
+
exports: [ConnectOptions, ConnectRegistry, ConnectMiddleware],
|
|
162
|
+
providers: [
|
|
163
|
+
...implementations,
|
|
164
|
+
options,
|
|
165
|
+
provide(ConnectRegistry, {
|
|
166
|
+
useFactory: (...deps) => {
|
|
167
|
+
const [resolved, ...instances] = deps;
|
|
168
|
+
return new ConnectRegistry(resolved, instances);
|
|
169
|
+
},
|
|
170
|
+
inject: [ConnectOptions, ...implementations]
|
|
171
|
+
}),
|
|
172
|
+
provide(ConnectMiddleware, {
|
|
173
|
+
useFactory: (registry) => new ConnectMiddleware(registry),
|
|
174
|
+
inject: [ConnectRegistry]
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
var _dec = [
|
|
180
|
+
Module({})
|
|
181
|
+
];
|
|
182
|
+
var _init = __decoratorStart(undefined);
|
|
183
|
+
|
|
184
|
+
class ConnectModule {
|
|
185
|
+
static forRoot(init) {
|
|
186
|
+
return build(init.services, provide(ConnectOptions, { useValue: new ConnectOptions(init) }), init.imports ?? []);
|
|
187
|
+
}
|
|
188
|
+
static forRootAsync(services, config) {
|
|
189
|
+
return build(services, provide(ConnectOptions, {
|
|
190
|
+
useFactory: async (...deps) => new ConnectOptions({
|
|
191
|
+
...await config.useFactory(...deps),
|
|
192
|
+
services
|
|
193
|
+
}),
|
|
194
|
+
inject: config.inject ?? []
|
|
195
|
+
}), config.imports ?? []);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
ConnectModule = __decorateElement(_init, 0, "ConnectModule", _dec, ConnectModule);
|
|
199
|
+
__runInitializers(_init, 1, ConnectModule);
|
|
200
|
+
__decoratorMetadata(_init, ConnectModule);
|
|
201
|
+
let _ConnectModule = ConnectModule;
|
|
202
|
+
export {
|
|
203
|
+
ConnectMiddleware,
|
|
204
|
+
ConnectModule,
|
|
205
|
+
ConnectOptions,
|
|
206
|
+
ConnectRegistry,
|
|
207
|
+
connectService
|
|
208
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Controller, Delete, Get, Patch, Post, Put, } from './route/decorators.js';
|
|
2
2
|
export type { HttpMethod, RoutePath } from './route/marker.js';
|
|
3
|
-
export { ApiHidden, HIDDEN, meta, metaKey, metaOf, mergeMeta, Public, PUBLIC, Roles, ROLES, UNMATCHED, UseGuards, type MetaKey, type MetaRecord, } from './route/metadata.js';
|
|
3
|
+
export { ApiHidden, HIDDEN, meta, metaKey, metaOf, mergeMeta, Public, PUBLIC, Roles, ROLES, STREAMS, UNMATCHED, UseGuards, type MetaKey, type MetaRecord, } from './route/metadata.js';
|
|
4
4
|
export type { InferOutput, Input, JsonSchema, ResponseMap, Returns, RouteInput, RouteSchemas, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, } from './route/schema.js';
|
|
5
5
|
export { ClientAddress } from './server/client-address.js';
|
|
6
6
|
export type { RouteContext } from './server/context.js';
|
|
@@ -19,6 +19,9 @@ export { StaticOptions, type StaticOptionsInit } from './static/options.js';
|
|
|
19
19
|
export { Compression } from './compression/compression.js';
|
|
20
20
|
export { CompressionModule } from './compression/module.js';
|
|
21
21
|
export { CompressionEncoding, CompressionOptions, type CompressionOptionsInit, } from './compression/options.js';
|
|
22
|
+
export { Sse, type SseInput, type SseResult, type SseSchemas, } from './sse/decorators.js';
|
|
23
|
+
export type { SseEvent } from './sse/event.js';
|
|
24
|
+
export { SseStream, type SseStreamOptions } from './sse/stream.js';
|
|
22
25
|
export { SKIP_THROTTLE, SkipThrottle, THROTTLE, Throttle, type ThrottleLimit, } from './throttle/decorators.js';
|
|
23
26
|
export { ThrottleGuard } from './throttle/guard.js';
|
|
24
27
|
export { ThrottleModule } from './throttle/module.js';
|