@dunx/http 3.8.1 → 3.8.2
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/index.d.ts +2 -0
- package/dist/index.js +93 -18
- package/dist/ws/middleware.d.ts +7 -4
- package/dist/ws/observer.d.ts +46 -0
- package/dist/ws/relay-module.d.ts +5 -4
- package/dist/ws/relay-publisher.d.ts +50 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -32,10 +32,12 @@ export { Gateway, OnClose, OnDrain, OnMessage, OnOpen, OnPing, OnPong, OnUpgrade
|
|
|
32
32
|
export type { Envelope } from './ws/envelope.js';
|
|
33
33
|
export type { SocketContext, SocketDispatch, SocketFrame, SocketMiddleware, SocketNext, } from './ws/middleware.js';
|
|
34
34
|
export { SocketLoggingMiddleware, type SocketLoggingOptions, } from './ws/logging.js';
|
|
35
|
+
export { SocketObserver, type SocketOutcome } from './ws/observer.js';
|
|
35
36
|
export { PubSub } from './ws/pubsub.js';
|
|
36
37
|
export { PostgresRelay, type PostgresRelayOptions, } from './ws/postgres-relay.js';
|
|
37
38
|
export { RedisRelay, type RedisRelayOptions } from './ws/redis-relay.js';
|
|
38
39
|
export { PostgresRelayConnectionOptions, RelayConnectionOptions, WsRelayModule, } from './ws/relay-module.js';
|
|
40
|
+
export { RelayPublisher, type RelayPublisherInit, } from './ws/relay-publisher.js';
|
|
39
41
|
export { DEFAULT_RELAY_CHANNEL, WsRelay, type PubSubRelay, type RelayOptions, } from './ws/relay.js';
|
|
40
42
|
export type { Socket, SocketData, SocketErrorHandler, SocketOptions, } from './ws/socket.js';
|
|
41
43
|
export { HealthIndicator, PingProbe, QueryProbe, type ProbeResult, type ProbeState, } from './health/contracts.js';
|
package/dist/index.js
CHANGED
|
@@ -299,19 +299,19 @@ var observe = (next, done) => {
|
|
|
299
299
|
try {
|
|
300
300
|
result = next();
|
|
301
301
|
} catch (error) {
|
|
302
|
-
done(error, undefined);
|
|
302
|
+
done(error, undefined, false);
|
|
303
303
|
throw error;
|
|
304
304
|
}
|
|
305
305
|
if (result instanceof Promise) {
|
|
306
306
|
return result.then((value) => {
|
|
307
|
-
done(undefined, value);
|
|
307
|
+
done(undefined, value, true);
|
|
308
308
|
return value;
|
|
309
309
|
}, (error) => {
|
|
310
|
-
done(error, undefined);
|
|
310
|
+
done(error, undefined, false);
|
|
311
311
|
throw error;
|
|
312
312
|
});
|
|
313
313
|
}
|
|
314
|
-
done(undefined, result);
|
|
314
|
+
done(undefined, result, true);
|
|
315
315
|
return result;
|
|
316
316
|
};
|
|
317
317
|
|
|
@@ -2671,6 +2671,19 @@ var OnMessage = (event) => (value) => {
|
|
|
2671
2671
|
markHandler(value, { kind: HandlerKind.MESSAGE, event });
|
|
2672
2672
|
return value;
|
|
2673
2673
|
};
|
|
2674
|
+
// src/ws/observer.ts
|
|
2675
|
+
class SocketObserver {
|
|
2676
|
+
reportsErrors = false;
|
|
2677
|
+
handle(frame, ctx, next) {
|
|
2678
|
+
return observe(next, (error, value, ok) => {
|
|
2679
|
+
try {
|
|
2680
|
+
this.settled(ok ? { ok, value } : { ok, error }, frame, ctx);
|
|
2681
|
+
} catch (failure) {
|
|
2682
|
+
console.error(`[dunx/http] ${this.constructor.name}.settled threw, which cannot ` + "change the outcome it was watching:", failure);
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2674
2687
|
// src/ws/postgres-relay.ts
|
|
2675
2688
|
var PROTOCOLS = ["postgres:", "postgresql:"];
|
|
2676
2689
|
var defaultPostgresRelayUrl = () => process.env["POSTGRES_URL"] ?? process.env["DATABASE_URL"] ?? "postgres://localhost:5432";
|
|
@@ -2800,6 +2813,50 @@ Object.defineProperty(RedisRelay, Symbol.for("dunx.deps"), { value: () => [{ unr
|
|
|
2800
2813
|
import {
|
|
2801
2814
|
provide as provide5
|
|
2802
2815
|
} from "@dunx/core";
|
|
2816
|
+
|
|
2817
|
+
// src/ws/relay-publisher.ts
|
|
2818
|
+
class RelayPublisher {
|
|
2819
|
+
relay;
|
|
2820
|
+
#origin = `worker:${Bun.randomUUIDv7()}`;
|
|
2821
|
+
#channel;
|
|
2822
|
+
#onError;
|
|
2823
|
+
constructor(relay, init = {}) {
|
|
2824
|
+
this.relay = relay;
|
|
2825
|
+
this.#channel = init.channel ?? DEFAULT_RELAY_CHANNEL;
|
|
2826
|
+
this.#onError = init.onError ?? defaultRelayError;
|
|
2827
|
+
}
|
|
2828
|
+
get origin() {
|
|
2829
|
+
return this.#origin;
|
|
2830
|
+
}
|
|
2831
|
+
get channel() {
|
|
2832
|
+
return this.#channel;
|
|
2833
|
+
}
|
|
2834
|
+
publishEvent(topic, event, data) {
|
|
2835
|
+
this.publish(topic, encode(event, data));
|
|
2836
|
+
}
|
|
2837
|
+
publish(topic, data) {
|
|
2838
|
+
try {
|
|
2839
|
+
const result = this.relay.publish(this.#channel, encodeRelay(this.#origin, topic, data));
|
|
2840
|
+
if (result instanceof Promise) {
|
|
2841
|
+
result.catch((error) => {
|
|
2842
|
+
this.#report(error);
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
this.#report(error);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
#report(error) {
|
|
2850
|
+
try {
|
|
2851
|
+
this.#onError(error, "publish");
|
|
2852
|
+
} catch (failure) {
|
|
2853
|
+
console.error("[dunx/http] a RelayPublisher onError handler threw:", failure);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
Object.defineProperty(RelayPublisher, Symbol.for("dunx.deps"), { value: () => [WsRelay, { unresolved: "init: RelayPublisherInit = {}", optional: true }] });
|
|
2858
|
+
|
|
2859
|
+
// src/ws/relay-module.ts
|
|
2803
2860
|
class RelayConnectionOptions {
|
|
2804
2861
|
url;
|
|
2805
2862
|
maxRetries;
|
|
@@ -2850,7 +2907,12 @@ class RelayLifecycle {
|
|
|
2850
2907
|
}
|
|
2851
2908
|
}
|
|
2852
2909
|
Object.defineProperty(RelayLifecycle, Symbol.for("dunx.deps"), { value: () => [WsRelay] });
|
|
2853
|
-
var
|
|
2910
|
+
var publisherBinding = (init) => provide5(RelayPublisher, {
|
|
2911
|
+
useFactory: (relay) => new RelayPublisher(relay, init),
|
|
2912
|
+
inject: [WsRelay]
|
|
2913
|
+
});
|
|
2914
|
+
var redisBindings = (options, publisher) => [
|
|
2915
|
+
publisherBinding(publisher),
|
|
2854
2916
|
provide5(RelayConnectionOptions, options),
|
|
2855
2917
|
provide5(RedisRelay, {
|
|
2856
2918
|
useFactory: (settings) => new RedisRelay(settings.toInit()),
|
|
@@ -2865,7 +2927,8 @@ var redisBindings = (options) => [
|
|
|
2865
2927
|
inject: [WsRelay]
|
|
2866
2928
|
})
|
|
2867
2929
|
];
|
|
2868
|
-
var postgresBindings = (options) => [
|
|
2930
|
+
var postgresBindings = (options, publisher) => [
|
|
2931
|
+
publisherBinding(publisher),
|
|
2869
2932
|
provide5(PostgresRelayConnectionOptions, options),
|
|
2870
2933
|
provide5(PostgresRelay, {
|
|
2871
2934
|
useFactory: (settings) => new PostgresRelay(settings.toInit()),
|
|
@@ -2882,48 +2945,58 @@ var postgresBindings = (options) => [
|
|
|
2882
2945
|
];
|
|
2883
2946
|
|
|
2884
2947
|
class WsRelayModule {
|
|
2885
|
-
static forRoot(init = {}) {
|
|
2948
|
+
static forRoot(init = {}, publisher = {}) {
|
|
2886
2949
|
return {
|
|
2887
2950
|
module: WsRelayModule,
|
|
2888
|
-
exports: [WsRelay, RedisRelay, RelayConnectionOptions],
|
|
2951
|
+
exports: [WsRelay, RedisRelay, RelayConnectionOptions, RelayPublisher],
|
|
2889
2952
|
providers: redisBindings({
|
|
2890
2953
|
useFactory: () => new RelayConnectionOptions(init),
|
|
2891
2954
|
inject: []
|
|
2892
|
-
})
|
|
2955
|
+
}, publisher)
|
|
2893
2956
|
};
|
|
2894
2957
|
}
|
|
2895
|
-
static forRootAsync(config) {
|
|
2958
|
+
static forRootAsync(config, publisher = {}) {
|
|
2896
2959
|
const useFactory = async (...deps) => new RelayConnectionOptions(await config.useFactory(...deps));
|
|
2897
2960
|
return {
|
|
2898
2961
|
module: WsRelayModule,
|
|
2899
2962
|
...config.imports === undefined ? {} : { imports: config.imports },
|
|
2900
|
-
exports: [WsRelay, RedisRelay, RelayConnectionOptions],
|
|
2963
|
+
exports: [WsRelay, RedisRelay, RelayConnectionOptions, RelayPublisher],
|
|
2901
2964
|
providers: redisBindings({
|
|
2902
2965
|
useFactory,
|
|
2903
2966
|
inject: config.inject ?? []
|
|
2904
|
-
})
|
|
2967
|
+
}, publisher)
|
|
2905
2968
|
};
|
|
2906
2969
|
}
|
|
2907
|
-
static forPostgres(init = {}) {
|
|
2970
|
+
static forPostgres(init = {}, publisher = {}) {
|
|
2908
2971
|
return {
|
|
2909
2972
|
module: WsRelayModule,
|
|
2910
|
-
exports: [
|
|
2973
|
+
exports: [
|
|
2974
|
+
WsRelay,
|
|
2975
|
+
PostgresRelay,
|
|
2976
|
+
PostgresRelayConnectionOptions,
|
|
2977
|
+
RelayPublisher
|
|
2978
|
+
],
|
|
2911
2979
|
providers: postgresBindings({
|
|
2912
2980
|
useFactory: () => new PostgresRelayConnectionOptions(init),
|
|
2913
2981
|
inject: []
|
|
2914
|
-
})
|
|
2982
|
+
}, publisher)
|
|
2915
2983
|
};
|
|
2916
2984
|
}
|
|
2917
|
-
static forPostgresAsync(config) {
|
|
2985
|
+
static forPostgresAsync(config, publisher = {}) {
|
|
2918
2986
|
const useFactory = async (...deps) => new PostgresRelayConnectionOptions(await config.useFactory(...deps));
|
|
2919
2987
|
return {
|
|
2920
2988
|
module: WsRelayModule,
|
|
2921
2989
|
...config.imports === undefined ? {} : { imports: config.imports },
|
|
2922
|
-
exports: [
|
|
2990
|
+
exports: [
|
|
2991
|
+
WsRelay,
|
|
2992
|
+
PostgresRelay,
|
|
2993
|
+
PostgresRelayConnectionOptions,
|
|
2994
|
+
RelayPublisher
|
|
2995
|
+
],
|
|
2923
2996
|
providers: postgresBindings({
|
|
2924
2997
|
useFactory,
|
|
2925
2998
|
inject: config.inject ?? []
|
|
2926
|
-
})
|
|
2999
|
+
}, publisher)
|
|
2927
3000
|
};
|
|
2928
3001
|
}
|
|
2929
3002
|
}
|
|
@@ -3375,6 +3448,7 @@ export {
|
|
|
3375
3448
|
RedisRelay,
|
|
3376
3449
|
RedisThrottleStore,
|
|
3377
3450
|
RelayConnectionOptions,
|
|
3451
|
+
RelayPublisher,
|
|
3378
3452
|
RequestLoggingMiddleware,
|
|
3379
3453
|
RequestMetrics,
|
|
3380
3454
|
Roles2 as Roles,
|
|
@@ -3382,6 +3456,7 @@ export {
|
|
|
3382
3456
|
STREAMS2 as STREAMS,
|
|
3383
3457
|
SkipThrottle,
|
|
3384
3458
|
SocketLoggingMiddleware,
|
|
3459
|
+
SocketObserver,
|
|
3385
3460
|
Sse,
|
|
3386
3461
|
SseStream,
|
|
3387
3462
|
StaticFiles2 as StaticFiles,
|
package/dist/ws/middleware.d.ts
CHANGED
|
@@ -79,8 +79,11 @@ export declare const composeSocket: (middleware: readonly SocketMiddleware[], ct
|
|
|
79
79
|
* Calls `next()` and reports how it went, on whichever channel it went out on,
|
|
80
80
|
* leaving the result untouched.
|
|
81
81
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
82
|
+
* A synchronous throw and a rejection both reach `done` and are then rethrown, so
|
|
83
|
+
* a middleware that only observes cannot accidentally swallow a failure.
|
|
84
|
+
*
|
|
85
|
+
* `ok` says which happened, and it is not `error === undefined`: a handler may
|
|
86
|
+
* `throw undefined` or reject with it, and that reads as a success that returned
|
|
87
|
+
* nothing to anything comparing the value.
|
|
85
88
|
*/
|
|
86
|
-
export declare const observe: (next: SocketNext, done: (error: unknown, value: unknown) => void) => unknown;
|
|
89
|
+
export declare const observe: (next: SocketNext, done: (error: unknown, value: unknown, ok: boolean) => void) => unknown;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type SocketContext, type SocketFrame, type SocketMiddleware, type SocketNext } from './middleware.js';
|
|
2
|
+
/**
|
|
3
|
+
* How a handler went. A union rather than an `error` that is `undefined` on
|
|
4
|
+
* success: a handler may `throw undefined`, and `error !== undefined` reads that
|
|
5
|
+
* as a success.
|
|
6
|
+
*/
|
|
7
|
+
export type SocketOutcome = {
|
|
8
|
+
readonly ok: true;
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
} | {
|
|
11
|
+
readonly ok: false;
|
|
12
|
+
readonly error: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A {@link SocketMiddleware} that watches a frame and changes nothing about it.
|
|
16
|
+
*
|
|
17
|
+
* A gateway handler may return a value or a promise, so a middleware wanting the
|
|
18
|
+
* outcome has to handle a throw and a rejection and rethrow both; getting it
|
|
19
|
+
* wrong silently swallows a failure. Extend this and implement {@link settled}
|
|
20
|
+
* instead - the result reaches the caller untouched, `settled` throwing
|
|
21
|
+
* included.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* export class Reporter extends SocketObserver {
|
|
25
|
+
* override readonly reportsErrors = true;
|
|
26
|
+
*
|
|
27
|
+
* protected override settled(outcome: SocketOutcome, _f: SocketFrame, ctx: SocketContext): void {
|
|
28
|
+
* if (outcome.ok) return;
|
|
29
|
+
* this.logger.error('socket handler failed', { event: ctx.event, err: outcome.error });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* One that answers or refuses a frame is not this: implement `SocketMiddleware`.
|
|
35
|
+
*/
|
|
36
|
+
export declare abstract class SocketObserver implements SocketMiddleware {
|
|
37
|
+
/**
|
|
38
|
+
* Override to `true` when {@link settled} reports a failure somewhere. See
|
|
39
|
+
* {@link SocketMiddleware.reportsErrors} - a middleware that ignores a throw
|
|
40
|
+
* and claims otherwise turns error reporting off for the whole server.
|
|
41
|
+
*/
|
|
42
|
+
readonly reportsErrors: boolean;
|
|
43
|
+
handle(frame: SocketFrame, ctx: SocketContext, next: SocketNext): unknown;
|
|
44
|
+
/** How the handler went, once per frame, on whichever channel it used. */
|
|
45
|
+
protected abstract settled(outcome: SocketOutcome, frame: SocketFrame, ctx: SocketContext): void;
|
|
46
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AsyncModuleConfig, type Deps, type DynamicModule } from '@dunx/core';
|
|
2
2
|
import { type PostgresRelayOptions } from './postgres-relay.js';
|
|
3
3
|
import { type RedisRelayOptions } from './redis-relay.js';
|
|
4
|
+
import { type RelayPublisherInit } from './relay-publisher.js';
|
|
4
5
|
/**
|
|
5
6
|
* The Redis relay's connection settings, as a class so a factory can bind them.
|
|
6
7
|
*
|
|
@@ -48,19 +49,19 @@ export declare class PostgresRelayConnectionOptions {
|
|
|
48
49
|
*/
|
|
49
50
|
export declare class WsRelayModule {
|
|
50
51
|
/** Redis or Valkey, over `Bun.RedisClient`. */
|
|
51
|
-
static forRoot(init?: RedisRelayOptions): DynamicModule;
|
|
52
|
+
static forRoot(init?: RedisRelayOptions, publisher?: RelayPublisherInit): DynamicModule;
|
|
52
53
|
/**
|
|
53
54
|
* The same bindings with the settings behind a factory, so the url can come off
|
|
54
55
|
* `ConfigService`. `imports` reaches that factory; importing the module
|
|
55
56
|
* alongside does not, since a dynamic module is its own scope.
|
|
56
57
|
*/
|
|
57
|
-
static forRootAsync<const D extends Deps>(config: AsyncModuleConfig<RedisRelayOptions, D
|
|
58
|
+
static forRootAsync<const D extends Deps>(config: AsyncModuleConfig<RedisRelayOptions, D>, publisher?: RelayPublisherInit): DynamicModule;
|
|
58
59
|
/**
|
|
59
60
|
* Postgres, over `Bun.SQL`'s `LISTEN`/`NOTIFY`, for an app that already has a
|
|
60
61
|
* database and would rather not run a broker. A frame over about 7.9 KB is
|
|
61
62
|
* refused; see {@link PostgresRelay}.
|
|
62
63
|
*/
|
|
63
|
-
static forPostgres(init?: PostgresRelayOptions): DynamicModule;
|
|
64
|
+
static forPostgres(init?: PostgresRelayOptions, publisher?: RelayPublisherInit): DynamicModule;
|
|
64
65
|
/** `forPostgres` with the settings behind a factory. */
|
|
65
|
-
static forPostgresAsync<const D extends Deps>(config: AsyncModuleConfig<PostgresRelayOptions, D
|
|
66
|
+
static forPostgresAsync<const D extends Deps>(config: AsyncModuleConfig<PostgresRelayOptions, D>, publisher?: RelayPublisherInit): DynamicModule;
|
|
66
67
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { WsRelay, type RelayOptions } from './relay.js';
|
|
2
|
+
/** What a publisher needs beyond the relay itself. */
|
|
3
|
+
export interface RelayPublisherInit {
|
|
4
|
+
/**
|
|
5
|
+
* The broker channel, which **must** be the one the servers listen on
|
|
6
|
+
* (`HttpOptionsProvider.relayChannel`). A mismatch is silent - the broker
|
|
7
|
+
* accepts the publish and delivers it to nobody - so read both from one
|
|
8
|
+
* constant rather than writing the name twice.
|
|
9
|
+
*/
|
|
10
|
+
readonly channel?: string;
|
|
11
|
+
/** Reports a publish the broker refused. Defaults to a `console.warn`. */
|
|
12
|
+
readonly onError?: RelayOptions['onError'];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Publishes a socket frame from a process that has no server.
|
|
16
|
+
*
|
|
17
|
+
* `PubSub` needs a live `Bun.serve` for the local half of its fan-out, which a
|
|
18
|
+
* queue worker or a forked job child has none of. This is the other half alone:
|
|
19
|
+
* every frame goes to the broker and comes back out of the servers listening on
|
|
20
|
+
* the channel, with no local delivery and no subscribe.
|
|
21
|
+
*
|
|
22
|
+
* `WsRelayModule` binds it, so it is a constructor parameter:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* export class RoundAnnouncer {
|
|
26
|
+
* constructor(private readonly frames: RelayPublisher) {}
|
|
27
|
+
*
|
|
28
|
+
* announce(round: string): void {
|
|
29
|
+
* this.frames.publishEvent('lobby', 'round.started', { round });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class RelayPublisher {
|
|
35
|
+
#private;
|
|
36
|
+
private readonly relay;
|
|
37
|
+
constructor(relay: WsRelay, init?: RelayPublisherInit);
|
|
38
|
+
/** This process's id on the relay channel. Stable for its lifetime. */
|
|
39
|
+
get origin(): string;
|
|
40
|
+
/** The channel frames go out on. */
|
|
41
|
+
get channel(): string;
|
|
42
|
+
/** The same envelope `@OnMessage(event)` reads, published to a topic. */
|
|
43
|
+
publishEvent(topic: string, event: string, data?: unknown): void;
|
|
44
|
+
/**
|
|
45
|
+
* **Never throws.** A job handler that failed here would be retried and would
|
|
46
|
+
* repeat its side effects to deliver a frame nobody awaited. Failures go to
|
|
47
|
+
* `onError`.
|
|
48
|
+
*/
|
|
49
|
+
publish(topic: string, data: string | Bun.BufferSource): void;
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/http",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
4
4
|
"description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@bufbuild/protobuf": "^2.15.0",
|
|
78
78
|
"@connectrpc/connect": "^2.2.0",
|
|
79
|
-
"@dunx/core": "^3.8.
|
|
79
|
+
"@dunx/core": "^3.8.2",
|
|
80
80
|
"@types/bun": ">=1.4.1"
|
|
81
81
|
},
|
|
82
82
|
"peerDependenciesMeta": {
|