@dunx/http 3.1.1 → 3.1.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/{chunk-3xs8zrpv.js → chunk-53cs6qek.js} +18 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.js +134 -8
- package/dist/internal.js +1 -1
- package/dist/ws/postgres-relay.d.ts +38 -0
- package/dist/ws/redis-relay.d.ts +3 -3
- package/dist/ws/relay-module.d.ts +23 -6
- package/dist/ws/relay.d.ts +15 -0
- package/package.json +2 -2
|
@@ -534,6 +534,7 @@ var discoverGateways = (modules, resolve) => {
|
|
|
534
534
|
};
|
|
535
535
|
|
|
536
536
|
// src/ws/relay.ts
|
|
537
|
+
import { AppError as AppError4 } from "@dunx/core";
|
|
537
538
|
var DEFAULT_RELAY_CHANNEL = "dunx:ws";
|
|
538
539
|
var defaultRelayError = (error, phase) => {
|
|
539
540
|
console.warn(`[dunx/http] the websocket relay could not ${phase}. Fan-out is local to ` + "this process until it recovers:", error);
|
|
@@ -561,6 +562,14 @@ var decodeRelay = (message) => {
|
|
|
561
562
|
return { origin: o, topic: t, data: b ? Buffer.from(d, "base64") : d };
|
|
562
563
|
};
|
|
563
564
|
|
|
565
|
+
class WsRelay {
|
|
566
|
+
constructor() {
|
|
567
|
+
if (new.target === WsRelay) {
|
|
568
|
+
throw new AppError4("WsRelay is a contract, not an implementation. Bind one with " + "WsRelayModule.forRoot() for Redis or WsRelayModule.forPostgres() " + "for Postgres, or extend it with a relay of your own.");
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
564
573
|
// src/server/request-id.ts
|
|
565
574
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
566
575
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -651,7 +660,7 @@ var preflight = (options, methods) => {
|
|
|
651
660
|
var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
|
|
652
661
|
|
|
653
662
|
// src/server/routes.ts
|
|
654
|
-
import { AppError as
|
|
663
|
+
import { AppError as AppError5 } from "@dunx/core";
|
|
655
664
|
|
|
656
665
|
// src/server/raw-body.ts
|
|
657
666
|
var WANTED = Symbol.for("dunx.http.rawBody.wanted");
|
|
@@ -793,7 +802,7 @@ var assertNoCollisions = (discovered) => {
|
|
|
793
802
|
const owner = `${route.controller}.${route.handlerName}`;
|
|
794
803
|
const existing = owners.get(key);
|
|
795
804
|
if (existing !== undefined) {
|
|
796
|
-
throw new
|
|
805
|
+
throw new AppError5(`Route collision: ${key} is declared by ${existing} and by ${owner}. ` + "Bun would keep only one of them.");
|
|
797
806
|
}
|
|
798
807
|
owners.set(key, owner);
|
|
799
808
|
}
|
|
@@ -802,7 +811,7 @@ var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
|
|
|
802
811
|
const gateways = new Set(gatewayPaths);
|
|
803
812
|
for (const route of discovered) {
|
|
804
813
|
if (gateways.has(route.path)) {
|
|
805
|
-
throw new
|
|
814
|
+
throw new AppError5(`Gateway path collision: ${route.path} is served by a gateway and by ` + `${route.controller}.${route.handlerName}(). The upgrade is a route too, ` + "so one of them would be dropped.");
|
|
806
815
|
}
|
|
807
816
|
}
|
|
808
817
|
};
|
|
@@ -1018,7 +1027,7 @@ class CompressionOptions {
|
|
|
1018
1027
|
Object.defineProperty(CompressionOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: CompressionOptionsInit = {}", optional: true }] });
|
|
1019
1028
|
|
|
1020
1029
|
// src/ws/redis-relay.ts
|
|
1021
|
-
import { AppError as
|
|
1030
|
+
import { AppError as AppError6 } from "@dunx/core";
|
|
1022
1031
|
var PROTOCOLS = [
|
|
1023
1032
|
"redis:",
|
|
1024
1033
|
"rediss:",
|
|
@@ -1034,21 +1043,22 @@ var assertUrl = (url) => {
|
|
|
1034
1043
|
try {
|
|
1035
1044
|
parsed = new URL(url);
|
|
1036
1045
|
} catch {
|
|
1037
|
-
throw new
|
|
1046
|
+
throw new AppError6(`${JSON.stringify(url)} is not a valid URL for the websocket relay. ` + "Expected something like redis://localhost:6379.");
|
|
1038
1047
|
}
|
|
1039
1048
|
if (!PROTOCOLS.includes(parsed.protocol)) {
|
|
1040
|
-
throw new
|
|
1049
|
+
throw new AppError6(`Unsupported protocol ${JSON.stringify(parsed.protocol)} in ` + `${JSON.stringify(url)}. Expected one of ${PROTOCOLS.join(", ")}.`);
|
|
1041
1050
|
}
|
|
1042
1051
|
return url;
|
|
1043
1052
|
};
|
|
1044
1053
|
|
|
1045
|
-
class RedisRelay {
|
|
1054
|
+
class RedisRelay extends WsRelay {
|
|
1046
1055
|
#url;
|
|
1047
1056
|
#options;
|
|
1048
1057
|
#pub;
|
|
1049
1058
|
#sub;
|
|
1050
1059
|
#channel;
|
|
1051
1060
|
constructor(options = {}) {
|
|
1061
|
+
super();
|
|
1052
1062
|
this.#url = assertUrl(options.url ?? defaultRelayUrl());
|
|
1053
1063
|
this.#options = {
|
|
1054
1064
|
maxRetries: options.maxRetries ?? 0,
|
|
@@ -1303,4 +1313,4 @@ __runInitializers(_init, 1, HiddenHealthController);
|
|
|
1303
1313
|
__decoratorMetadata(_init, HiddenHealthController);
|
|
1304
1314
|
let _HiddenHealthController = HiddenHealthController;
|
|
1305
1315
|
|
|
1306
|
-
export { defaultStatusFor, Controller, Get, Post, Put, Patch, Delete, metaKey, meta, ROLES, PUBLIC, HIDDEN, UNMATCHED, Roles, Public, ApiHidden, UseGuards, guardsOf, metaOf, mergeMeta, HttpError, ValidationError, ErrorFilter, isErrorFilter, toErrorMapper, errorMapper, defaultErrorMapper, joinPath, discoverRoutes, encode, decode, HandlerKind, markHandler, markGateway, isGateway, composeSocket, observe, buildRuntime, buildGateways, buildWebSocket, normalizePath, discoverGateway, discoverGateways, DEFAULT_RELAY_CHANNEL, defaultRelayError, encodeRelay, decodeRelay, RawBody, REQUEST_ID_HEADER, RequestIds, buildContext, withCors, preflight, compose, assertNoCollisions, assertNoGatewayCollisions, withUpgradeRoutes, buildFallback, buildRoutes, StaticOptions, normalizePrefix, negotiate, CompressionEncoding, isCompressibleType, CompressionOptions, defaultRelayUrl, RedisRelay, HEALTH_REPORT_SCHEMA, HealthOptions, HealthRegistry, HealthController, HiddenHealthController };
|
|
1316
|
+
export { defaultStatusFor, Controller, Get, Post, Put, Patch, Delete, metaKey, meta, ROLES, PUBLIC, HIDDEN, UNMATCHED, Roles, Public, ApiHidden, UseGuards, guardsOf, metaOf, mergeMeta, HttpError, ValidationError, ErrorFilter, isErrorFilter, toErrorMapper, errorMapper, defaultErrorMapper, joinPath, discoverRoutes, encode, decode, HandlerKind, markHandler, markGateway, isGateway, composeSocket, observe, buildRuntime, buildGateways, buildWebSocket, normalizePath, discoverGateway, discoverGateways, DEFAULT_RELAY_CHANNEL, defaultRelayError, encodeRelay, decodeRelay, WsRelay, RawBody, REQUEST_ID_HEADER, RequestIds, buildContext, withCors, preflight, compose, assertNoCollisions, assertNoGatewayCollisions, withUpgradeRoutes, buildFallback, buildRoutes, StaticOptions, normalizePrefix, negotiate, CompressionEncoding, isCompressibleType, CompressionOptions, defaultRelayUrl, RedisRelay, HEALTH_REPORT_SCHEMA, HealthOptions, HealthRegistry, HealthController, HiddenHealthController };
|
package/dist/index.d.ts
CHANGED
|
@@ -30,9 +30,10 @@ export type { Envelope } from './ws/envelope.js';
|
|
|
30
30
|
export type { SocketContext, SocketDispatch, SocketFrame, SocketMiddleware, SocketNext, } from './ws/middleware.js';
|
|
31
31
|
export { SocketLoggingMiddleware, type SocketLoggingOptions, } from './ws/logging.js';
|
|
32
32
|
export { PubSub } from './ws/pubsub.js';
|
|
33
|
+
export { PostgresRelay, type PostgresRelayOptions, } from './ws/postgres-relay.js';
|
|
33
34
|
export { RedisRelay, type RedisRelayOptions } from './ws/redis-relay.js';
|
|
34
|
-
export { RelayConnectionOptions, WsRelayModule } from './ws/relay-module.js';
|
|
35
|
-
export { DEFAULT_RELAY_CHANNEL, type PubSubRelay, type RelayOptions, } from './ws/relay.js';
|
|
35
|
+
export { PostgresRelayConnectionOptions, RelayConnectionOptions, WsRelayModule, } from './ws/relay-module.js';
|
|
36
|
+
export { DEFAULT_RELAY_CHANNEL, WsRelay, type PubSubRelay, type RelayOptions, } from './ws/relay.js';
|
|
36
37
|
export type { Socket, SocketData, SocketErrorHandler, SocketOptions, } from './ws/socket.js';
|
|
37
38
|
export { HealthIndicator, PingProbe, QueryProbe, type ProbeResult, type ProbeState, } from './health/contracts.js';
|
|
38
39
|
export { HealthController } from './health/controller.js';
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
UNMATCHED,
|
|
37
37
|
UseGuards,
|
|
38
38
|
ValidationError,
|
|
39
|
+
WsRelay,
|
|
39
40
|
assertNoCollisions,
|
|
40
41
|
assertNoGatewayCollisions,
|
|
41
42
|
buildFallback,
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
observe,
|
|
61
62
|
toErrorMapper,
|
|
62
63
|
withUpgradeRoutes
|
|
63
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-53cs6qek.js";
|
|
64
65
|
import {
|
|
65
66
|
HttpStatusCode,
|
|
66
67
|
__decorateElement,
|
|
@@ -1396,6 +1397,71 @@ var OnMessage = (event) => (value) => {
|
|
|
1396
1397
|
markHandler(value, { kind: HandlerKind.MESSAGE, event });
|
|
1397
1398
|
return value;
|
|
1398
1399
|
};
|
|
1400
|
+
// src/ws/postgres-relay.ts
|
|
1401
|
+
import { AppError as AppError7 } from "@dunx/core";
|
|
1402
|
+
var PROTOCOLS = ["postgres:", "postgresql:"];
|
|
1403
|
+
var defaultPostgresRelayUrl = () => process.env["POSTGRES_URL"] ?? process.env["DATABASE_URL"] ?? "postgres://localhost:5432";
|
|
1404
|
+
var assertUrl = (url) => {
|
|
1405
|
+
let parsed;
|
|
1406
|
+
try {
|
|
1407
|
+
parsed = new URL(url);
|
|
1408
|
+
} catch {
|
|
1409
|
+
throw new AppError7(`${JSON.stringify(url)} is not a valid URL for the websocket relay. ` + "Expected something like postgres://localhost:5432/app.");
|
|
1410
|
+
}
|
|
1411
|
+
if (!PROTOCOLS.includes(parsed.protocol)) {
|
|
1412
|
+
throw new AppError7(`Unsupported protocol ${JSON.stringify(parsed.protocol)} in ` + `${JSON.stringify(url)}. Expected one of ${PROTOCOLS.join(", ")}.`);
|
|
1413
|
+
}
|
|
1414
|
+
return url;
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
class PostgresRelay extends WsRelay {
|
|
1418
|
+
#url;
|
|
1419
|
+
#max;
|
|
1420
|
+
#sql;
|
|
1421
|
+
#subscription;
|
|
1422
|
+
constructor(options = {}) {
|
|
1423
|
+
super();
|
|
1424
|
+
this.#url = assertUrl(options.url ?? defaultPostgresRelayUrl());
|
|
1425
|
+
this.#max = options.max ?? 1;
|
|
1426
|
+
}
|
|
1427
|
+
get url() {
|
|
1428
|
+
const parsed = new URL(this.#url);
|
|
1429
|
+
if (parsed.password)
|
|
1430
|
+
parsed.password = "***";
|
|
1431
|
+
return parsed.toString();
|
|
1432
|
+
}
|
|
1433
|
+
#client() {
|
|
1434
|
+
return this.#sql ??= new Bun.SQL({ url: this.#url, max: this.#max });
|
|
1435
|
+
}
|
|
1436
|
+
async publish(channel, message) {
|
|
1437
|
+
await this.#client().notify(channel, message);
|
|
1438
|
+
}
|
|
1439
|
+
async subscribe(channel, listener) {
|
|
1440
|
+
const sql = this.#client();
|
|
1441
|
+
try {
|
|
1442
|
+
this.#subscription = await sql.listen(channel, listener);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
if (this.#sql === sql) {
|
|
1445
|
+
this.#sql = undefined;
|
|
1446
|
+
await sql.close();
|
|
1447
|
+
}
|
|
1448
|
+
throw error;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
async close() {
|
|
1452
|
+
const sql = this.#sql;
|
|
1453
|
+
const subscription = this.#subscription;
|
|
1454
|
+
this.#sql = undefined;
|
|
1455
|
+
this.#subscription = undefined;
|
|
1456
|
+
if (subscription) {
|
|
1457
|
+
try {
|
|
1458
|
+
await subscription.unlisten();
|
|
1459
|
+
} catch {}
|
|
1460
|
+
}
|
|
1461
|
+
await sql?.close();
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
Object.defineProperty(PostgresRelay, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "options: PostgresRelayOptions = {}", optional: true }] });
|
|
1399
1465
|
// src/ws/relay-module.ts
|
|
1400
1466
|
import {
|
|
1401
1467
|
provide as provide5
|
|
@@ -1424,6 +1490,22 @@ class RelayConnectionOptions {
|
|
|
1424
1490
|
}
|
|
1425
1491
|
Object.defineProperty(RelayConnectionOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: RedisRelayOptions = {}", optional: true, typeOnly: "RedisRelayOptions" }] });
|
|
1426
1492
|
|
|
1493
|
+
class PostgresRelayConnectionOptions {
|
|
1494
|
+
url;
|
|
1495
|
+
max;
|
|
1496
|
+
constructor(init = {}) {
|
|
1497
|
+
this.url = init.url;
|
|
1498
|
+
this.max = init.max;
|
|
1499
|
+
}
|
|
1500
|
+
toInit() {
|
|
1501
|
+
return {
|
|
1502
|
+
...this.url !== undefined && { url: this.url },
|
|
1503
|
+
...this.max !== undefined && { max: this.max }
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
Object.defineProperty(PostgresRelayConnectionOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: PostgresRelayOptions = {}", optional: true, typeOnly: "PostgresRelayOptions" }] });
|
|
1508
|
+
|
|
1427
1509
|
class RelayLifecycle {
|
|
1428
1510
|
relay;
|
|
1429
1511
|
constructor(relay) {
|
|
@@ -1433,16 +1515,35 @@ class RelayLifecycle {
|
|
|
1433
1515
|
await this.relay.close();
|
|
1434
1516
|
}
|
|
1435
1517
|
}
|
|
1436
|
-
Object.defineProperty(RelayLifecycle, Symbol.for("dunx.deps"), { value: () => [
|
|
1437
|
-
var
|
|
1518
|
+
Object.defineProperty(RelayLifecycle, Symbol.for("dunx.deps"), { value: () => [WsRelay] });
|
|
1519
|
+
var redisBindings = (options) => [
|
|
1438
1520
|
provide5(RelayConnectionOptions, options),
|
|
1439
1521
|
provide5(RedisRelay, {
|
|
1440
1522
|
useFactory: (settings) => new RedisRelay(settings.toInit()),
|
|
1441
1523
|
inject: [RelayConnectionOptions]
|
|
1442
1524
|
}),
|
|
1525
|
+
provide5(WsRelay, {
|
|
1526
|
+
useFactory: (relay) => relay,
|
|
1527
|
+
inject: [RedisRelay]
|
|
1528
|
+
}),
|
|
1443
1529
|
provide5(RelayLifecycle, {
|
|
1444
1530
|
useFactory: (relay) => new RelayLifecycle(relay),
|
|
1445
|
-
inject: [
|
|
1531
|
+
inject: [WsRelay]
|
|
1532
|
+
})
|
|
1533
|
+
];
|
|
1534
|
+
var postgresBindings = (options) => [
|
|
1535
|
+
provide5(PostgresRelayConnectionOptions, options),
|
|
1536
|
+
provide5(PostgresRelay, {
|
|
1537
|
+
useFactory: (settings) => new PostgresRelay(settings.toInit()),
|
|
1538
|
+
inject: [PostgresRelayConnectionOptions]
|
|
1539
|
+
}),
|
|
1540
|
+
provide5(WsRelay, {
|
|
1541
|
+
useFactory: (relay) => relay,
|
|
1542
|
+
inject: [PostgresRelay]
|
|
1543
|
+
}),
|
|
1544
|
+
provide5(RelayLifecycle, {
|
|
1545
|
+
useFactory: (relay) => new RelayLifecycle(relay),
|
|
1546
|
+
inject: [WsRelay]
|
|
1446
1547
|
})
|
|
1447
1548
|
];
|
|
1448
1549
|
|
|
@@ -1450,8 +1551,8 @@ class WsRelayModule {
|
|
|
1450
1551
|
static forRoot(init = {}) {
|
|
1451
1552
|
return {
|
|
1452
1553
|
module: WsRelayModule,
|
|
1453
|
-
exports: [RedisRelay, RelayConnectionOptions],
|
|
1454
|
-
providers:
|
|
1554
|
+
exports: [WsRelay, RedisRelay, RelayConnectionOptions],
|
|
1555
|
+
providers: redisBindings({
|
|
1455
1556
|
useFactory: () => new RelayConnectionOptions(init),
|
|
1456
1557
|
inject: []
|
|
1457
1558
|
})
|
|
@@ -1462,8 +1563,30 @@ class WsRelayModule {
|
|
|
1462
1563
|
return {
|
|
1463
1564
|
module: WsRelayModule,
|
|
1464
1565
|
...config.imports === undefined ? {} : { imports: config.imports },
|
|
1465
|
-
exports: [RedisRelay, RelayConnectionOptions],
|
|
1466
|
-
providers:
|
|
1566
|
+
exports: [WsRelay, RedisRelay, RelayConnectionOptions],
|
|
1567
|
+
providers: redisBindings({
|
|
1568
|
+
useFactory,
|
|
1569
|
+
inject: config.inject ?? []
|
|
1570
|
+
})
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
static forPostgres(init = {}) {
|
|
1574
|
+
return {
|
|
1575
|
+
module: WsRelayModule,
|
|
1576
|
+
exports: [WsRelay, PostgresRelay, PostgresRelayConnectionOptions],
|
|
1577
|
+
providers: postgresBindings({
|
|
1578
|
+
useFactory: () => new PostgresRelayConnectionOptions(init),
|
|
1579
|
+
inject: []
|
|
1580
|
+
})
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
static forPostgresAsync(config) {
|
|
1584
|
+
const useFactory = async (...deps) => new PostgresRelayConnectionOptions(await config.useFactory(...deps));
|
|
1585
|
+
return {
|
|
1586
|
+
module: WsRelayModule,
|
|
1587
|
+
...config.imports === undefined ? {} : { imports: config.imports },
|
|
1588
|
+
exports: [WsRelay, PostgresRelay, PostgresRelayConnectionOptions],
|
|
1589
|
+
providers: postgresBindings({
|
|
1467
1590
|
useFactory,
|
|
1468
1591
|
inject: config.inject ?? []
|
|
1469
1592
|
})
|
|
@@ -1707,6 +1830,8 @@ export {
|
|
|
1707
1830
|
Patch,
|
|
1708
1831
|
PingProbe,
|
|
1709
1832
|
Post,
|
|
1833
|
+
PostgresRelay,
|
|
1834
|
+
PostgresRelayConnectionOptions,
|
|
1710
1835
|
PubSub,
|
|
1711
1836
|
Public,
|
|
1712
1837
|
Put,
|
|
@@ -1739,6 +1864,7 @@ export {
|
|
|
1739
1864
|
UNMATCHED,
|
|
1740
1865
|
UseGuards,
|
|
1741
1866
|
ValidationError,
|
|
1867
|
+
WsRelay,
|
|
1742
1868
|
WsRelayModule,
|
|
1743
1869
|
defaultErrorMapper,
|
|
1744
1870
|
errorMapper,
|
package/dist/internal.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WsRelay } from './relay.js';
|
|
2
|
+
/** The same fallback chain `Bun.SQL` uses when given no URL. */
|
|
3
|
+
export declare const defaultPostgresRelayUrl: () => string;
|
|
4
|
+
export interface PostgresRelayOptions {
|
|
5
|
+
/** @default `$POSTGRES_URL`, `$DATABASE_URL`, then `postgres://localhost:5432` */
|
|
6
|
+
readonly url?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Size of the query pool `notify` publishes through. **`subscribe` opens a
|
|
9
|
+
* dedicated connection on top of it**, so a relay that is listening holds up to
|
|
10
|
+
* `max + 1`. Measured: `max: 1` shows two rows in `pg_stat_activity`.
|
|
11
|
+
*
|
|
12
|
+
* @default 1
|
|
13
|
+
*/
|
|
14
|
+
readonly max?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A {@link WsRelay} on `Bun.SQL`'s `LISTEN`/`NOTIFY`, a Bun global, so it costs
|
|
18
|
+
* `@dunx/http` no dependency and an app already on Postgres needs no broker.
|
|
19
|
+
*
|
|
20
|
+
* One client, two connections while it listens: Bun dedicates one to the `LISTEN`
|
|
21
|
+
* and leaves the pool to answer `notify`. Budget `max + 1` per replica.
|
|
22
|
+
*
|
|
23
|
+
* **A frame over about 7.9 KB is refused**, because Postgres caps a `NOTIFY`
|
|
24
|
+
* payload at 7999 bytes and the relay envelope adds to the frame. `PubSub` reports
|
|
25
|
+
* one `logger.warn` and fan-out stays local for that message. Redis has no
|
|
26
|
+
* comparable ceiling; measured in docs/architecture/http.md.
|
|
27
|
+
*
|
|
28
|
+
* Reconnection is Bun's, so there is no retry budget here.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PostgresRelay extends WsRelay {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(options?: PostgresRelayOptions);
|
|
33
|
+
/** The URL with any password removed, for logs and error messages. */
|
|
34
|
+
get url(): string;
|
|
35
|
+
publish(channel: string, message: string): Promise<void>;
|
|
36
|
+
subscribe(channel: string, listener: (message: string) => void): Promise<void>;
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
package/dist/ws/redis-relay.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WsRelay } from './relay.js';
|
|
2
2
|
/** The same fallback chain `Bun.RedisClient` uses when given no URL. */
|
|
3
3
|
export declare const defaultRelayUrl: () => string;
|
|
4
4
|
export interface RedisRelayOptions {
|
|
@@ -18,14 +18,14 @@ export interface RedisRelayOptions {
|
|
|
18
18
|
readonly tls?: boolean | Bun.TLSOptions;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* A {@link
|
|
21
|
+
* A {@link WsRelay} on `Bun.RedisClient`, a Bun global, so it costs
|
|
22
22
|
* `@dunx/http` no dependency.
|
|
23
23
|
*
|
|
24
24
|
* Two connections: a client in subscriber mode rejects every data command, so the
|
|
25
25
|
* subscription cannot share the publishing socket. Both open lazily, and a failed
|
|
26
26
|
* one is discarded rather than reused.
|
|
27
27
|
*/
|
|
28
|
-
export declare class RedisRelay
|
|
28
|
+
export declare class RedisRelay extends WsRelay {
|
|
29
29
|
#private;
|
|
30
30
|
constructor(options?: RedisRelayOptions);
|
|
31
31
|
/** The URL with any password removed, for logs and error messages. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type AsyncModuleConfig, type Deps, type DynamicModule } from '@dunx/core';
|
|
2
|
+
import { type PostgresRelayOptions } from './postgres-relay.js';
|
|
2
3
|
import { type RedisRelayOptions } from './redis-relay.js';
|
|
3
4
|
/**
|
|
4
|
-
* The relay's connection settings, as a class so a factory can bind them.
|
|
5
|
+
* The Redis relay's connection settings, as a class so a factory can bind them.
|
|
5
6
|
*
|
|
6
7
|
* `RedisRelayOptions` stays the interface a caller writes; this is what the
|
|
7
8
|
* container holds, which is the same split `HttpClientOptions` and
|
|
@@ -16,15 +17,22 @@ export declare class RelayConnectionOptions {
|
|
|
16
17
|
/** Only the keys actually set, so each `RedisRelay` default still applies. */
|
|
17
18
|
toInit(): RedisRelayOptions;
|
|
18
19
|
}
|
|
20
|
+
/** The same, for the Postgres relay. */
|
|
21
|
+
export declare class PostgresRelayConnectionOptions {
|
|
22
|
+
readonly url: string | undefined;
|
|
23
|
+
readonly max: number | undefined;
|
|
24
|
+
constructor(init?: PostgresRelayOptions);
|
|
25
|
+
toInit(): PostgresRelayOptions;
|
|
26
|
+
}
|
|
19
27
|
/**
|
|
20
28
|
* Binds the websocket relay, so `relay` is a provider rather than an instance
|
|
21
29
|
* `main.ts` constructs and threads into `HttpFactory.create`.
|
|
22
30
|
*
|
|
23
|
-
*
|
|
31
|
+
* Name {@link WsRelay} at the injection site and the backend is a wiring choice:
|
|
24
32
|
*
|
|
25
33
|
* ```ts
|
|
26
34
|
* export class AppHttpOptions extends HttpOptionsProvider {
|
|
27
|
-
* constructor(private readonly bus:
|
|
35
|
+
* constructor(private readonly bus: WsRelay) {
|
|
28
36
|
* super();
|
|
29
37
|
* }
|
|
30
38
|
*
|
|
@@ -34,11 +42,12 @@ export declare class RelayConnectionOptions {
|
|
|
34
42
|
* }
|
|
35
43
|
* ```
|
|
36
44
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
45
|
+
* The backend is chosen by which method you call, not by a field in the options.
|
|
46
|
+
* A relay of your own needs no module: extend `WsRelay`, bind it, and return it
|
|
47
|
+
* from that same getter.
|
|
40
48
|
*/
|
|
41
49
|
export declare class WsRelayModule {
|
|
50
|
+
/** Redis or Valkey, over `Bun.RedisClient`. */
|
|
42
51
|
static forRoot(init?: RedisRelayOptions): DynamicModule;
|
|
43
52
|
/**
|
|
44
53
|
* The same bindings with the settings behind a factory, so the url can come off
|
|
@@ -46,4 +55,12 @@ export declare class WsRelayModule {
|
|
|
46
55
|
* alongside does not, since a dynamic module is its own scope.
|
|
47
56
|
*/
|
|
48
57
|
static forRootAsync<const D extends Deps>(config: AsyncModuleConfig<RedisRelayOptions, D>): DynamicModule;
|
|
58
|
+
/**
|
|
59
|
+
* Postgres, over `Bun.SQL`'s `LISTEN`/`NOTIFY`, for an app that already has a
|
|
60
|
+
* database and would rather not run a broker. A frame over about 7.9 KB is
|
|
61
|
+
* refused; see {@link PostgresRelay}.
|
|
62
|
+
*/
|
|
63
|
+
static forPostgres(init?: PostgresRelayOptions): DynamicModule;
|
|
64
|
+
/** `forPostgres` with the settings behind a factory. */
|
|
65
|
+
static forPostgresAsync<const D extends Deps>(config: AsyncModuleConfig<PostgresRelayOptions, D>): DynamicModule;
|
|
49
66
|
}
|
package/dist/ws/relay.d.ts
CHANGED
|
@@ -81,3 +81,18 @@ export interface RelayFrame {
|
|
|
81
81
|
export declare const encodeRelay: (origin: string, topic: string, data: string | Bun.BufferSource) => string;
|
|
82
82
|
/** `undefined` for anything that is not one of our frames, which is then ignored. */
|
|
83
83
|
export declare const decodeRelay: (message: string) => RelayFrame | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* The injectable form of {@link PubSubRelay}. An interface has no runtime value
|
|
86
|
+
* for the container to record, so a module binds this and a consumer that wants
|
|
87
|
+
* the relay names this rather than `RedisRelay` or `PostgresRelay` - which is
|
|
88
|
+
* what lets the backend change without the code that publishes changing with it.
|
|
89
|
+
*
|
|
90
|
+
* `close` is abstract here though it is optional on the interface: a relay the
|
|
91
|
+
* container built is the container's to close.
|
|
92
|
+
*/
|
|
93
|
+
export declare abstract class WsRelay implements PubSubRelay {
|
|
94
|
+
constructor();
|
|
95
|
+
abstract publish(channel: string, message: string): unknown;
|
|
96
|
+
abstract subscribe(channel: string, listener: (message: string) => void): unknown;
|
|
97
|
+
abstract close(): unknown;
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/http",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@dunx/core": "workspace:*"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@dunx/core": "^3.1.
|
|
65
|
+
"@dunx/core": "^3.1.2",
|
|
66
66
|
"@types/bun": ">=1.3.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependenciesMeta": {
|