@dunx/http 0.1.0 → 0.1.1
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 +46 -46
- package/dist/index.js +2 -2
- package/dist/index.js.map +22 -22
- package/dist/route/metadata.d.ts +1 -1
- package/dist/route/schema.d.ts +3 -3
- package/dist/server/application.d.ts +4 -4
- package/dist/server/context.d.ts +2 -2
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/input.d.ts +3 -3
- package/dist/server/middleware.d.ts +3 -3
- package/dist/server/request-logging.d.ts +6 -6
- package/dist/server/routes.d.ts +3 -3
- package/dist/server/settings.d.ts +1 -1
- package/dist/server/status.d.ts +1 -1
- package/dist/ws/discover.d.ts +1 -1
- package/dist/ws/envelope.d.ts +2 -2
- package/dist/ws/pubsub.d.ts +4 -4
- package/dist/ws/redis-relay.d.ts +2 -2
- package/dist/ws/relay.d.ts +6 -6
- package/dist/ws/runtime.d.ts +1 -1
- package/dist/ws/socket.d.ts +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
`Bun.serve` adapter for [dunx](https://github.com/petarzarkov/dunx). Class-based
|
|
4
4
|
controllers **and WebSocket gateways**, standard decorators, and no JavaScript
|
|
5
|
-
router
|
|
5
|
+
router - Bun's native `routes` does path params and per-method dispatch in Zig.
|
|
6
6
|
|
|
7
7
|
`Bun.serve` takes `routes` and `websocket` in one call, so both live here: one
|
|
8
|
-
`listen()`, one server, one port. Zero dependencies beyond `@dunx/core`
|
|
8
|
+
`listen()`, one server, one port. Zero dependencies beyond `@dunx/core` - no
|
|
9
9
|
`express`, no `ws`, no `socket.io`.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
@@ -69,14 +69,14 @@ create(input: Input<typeof createNote>): Note {
|
|
|
69
69
|
|
|
70
70
|
`Input<typeof opts>` has to be written out. A standard method decorator can
|
|
71
71
|
**check** a handler's parameter type but cannot contextually type an unannotated
|
|
72
|
-
one, so the annotation is required
|
|
72
|
+
one, so the annotation is required - and it is a type-level function over the
|
|
73
73
|
options object, so each type is still declared exactly once. A wrong annotation is
|
|
74
74
|
a compile error naming the mismatched property; an unannotated parameter is
|
|
75
75
|
`TS7006`.
|
|
76
76
|
|
|
77
77
|
| Field | Source | Declared by |
|
|
78
78
|
| -------------- | ------------------------------------- | ----------- |
|
|
79
|
-
| `input.req` | the `BunRequest`
|
|
79
|
+
| `input.req` | the `BunRequest` - always present | always |
|
|
80
80
|
| `input.body` | parsed by `content-type`, then validated | `body` |
|
|
81
81
|
| `input.query` | `new URL(req.url).searchParams` | `query` |
|
|
82
82
|
| `input.params` | `req.params` | `params` |
|
|
@@ -85,7 +85,7 @@ With no options at all, annotate `Input<RouteSchemas>` for the request, or take
|
|
|
85
85
|
parameter. Path params without a `params` schema stay on `input.req.params`.
|
|
86
86
|
|
|
87
87
|
Validation is the **Standard Schema** spec (`~standard.validate`, sync or async),
|
|
88
|
-
restated in this package's own types
|
|
88
|
+
restated in this package's own types - so Zod 4, Valibot and ArkType all work and
|
|
89
89
|
`@dunx/http` still has zero dependencies. Anything with a `~standard` property
|
|
90
90
|
qualifies, including a hand-written object; see `examples/full`.
|
|
91
91
|
|
|
@@ -98,7 +98,7 @@ Parsed only when `body` is declared, by media type:
|
|
|
98
98
|
| `application/json`, `*+json`, none | `req.json()` |
|
|
99
99
|
| `application/x-www-form-urlencoded` | fields; a repeated key becomes an array |
|
|
100
100
|
| `multipart/form-data` | fields and `File`s, same repeat rule |
|
|
101
|
-
| `text/*` | `req.text()`
|
|
101
|
+
| `text/*` | `req.text()` - a string |
|
|
102
102
|
| anything else | **415**, nothing read |
|
|
103
103
|
|
|
104
104
|
A body the caller mangled is a **400** (`Malformed application/json body`), never a
|
|
@@ -109,16 +109,16 @@ the schema error that is about to be more useful.
|
|
|
109
109
|
|
|
110
110
|
| Handler returns | Response |
|
|
111
111
|
| ----------------- | -------------------------------------------- |
|
|
112
|
-
| a `Response` | passed through untouched
|
|
112
|
+
| a `Response` | passed through untouched - the escape hatch |
|
|
113
113
|
| `undefined`/`null`| `204`, no body |
|
|
114
114
|
| anything else | `Response.json(value)` at the status below |
|
|
115
115
|
|
|
116
|
-
Status precedence: `options.status`, else **201 for POST**, else **200**
|
|
116
|
+
Status precedence: `options.status`, else **201 for POST**, else **200** - Nest's
|
|
117
117
|
rule. A thrown `HttpError` still goes through the error mapper.
|
|
118
118
|
|
|
119
119
|
### Validation failures
|
|
120
120
|
|
|
121
|
-
A rejected schema is a `ValidationError`
|
|
121
|
+
A rejected schema is a `ValidationError` - a `400` whose body carries every issue,
|
|
122
122
|
with the path flattened to dots (both `['a', 0]` and `[{ key: 'a' }, { key: 0 }]`
|
|
123
123
|
render as `a.0`):
|
|
124
124
|
|
|
@@ -132,7 +132,7 @@ render as `a.0`):
|
|
|
132
132
|
|
|
133
133
|
### Which validator to use
|
|
134
134
|
|
|
135
|
-
Any of them. This is measured rather than asserted
|
|
135
|
+
Any of them. This is measured rather than asserted - `bun run validation` in
|
|
136
136
|
`tools/bench` runs the same dunx app and the same schema shape with only the library
|
|
137
137
|
behind `~standard` changed, and reports what each one costs per request:
|
|
138
138
|
|
|
@@ -146,13 +146,13 @@ behind `~standard` changed, and reports what each one costs per request:
|
|
|
146
146
|
|
|
147
147
|
**`await req.json()` on the same request costs 3.10 µs**, which is more than all of
|
|
148
148
|
them put together. So validation is not where a slow endpoint's time goes, and
|
|
149
|
-
swapping zod for a compiled validator buys about 7% of a small request
|
|
149
|
+
swapping zod for a compiled validator buys about 7% of a small request - worth having
|
|
150
150
|
if a profile points at it, not worth restructuring for. zod is what `@dunx/openapi`
|
|
151
151
|
reads schemas from (via `z.toJSONSchema`), and it is the default for that reason
|
|
152
152
|
rather than a performance one. Three fields, though: a deeply nested schema would very
|
|
153
153
|
likely separate these engines much further.
|
|
154
154
|
|
|
155
|
-
Two of the five ship no `~standard` property. Bridging one is small enough to inline
|
|
155
|
+
Two of the five ship no `~standard` property. Bridging one is small enough to inline -
|
|
156
156
|
this is the whole of it:
|
|
157
157
|
|
|
158
158
|
```ts
|
|
@@ -181,7 +181,7 @@ Full numbers, methodology and the ajv version:
|
|
|
181
181
|
## Route metadata and scoped middleware
|
|
182
182
|
|
|
183
183
|
A decorator annotates a route; a guard reads the annotation back. Metadata on its
|
|
184
|
-
own enforces nothing
|
|
184
|
+
own enforces nothing - which is why `@Roles` needs a guard that looks at it, and
|
|
185
185
|
why one global guard plus `@Public()` is the combination worth learning.
|
|
186
186
|
|
|
187
187
|
```ts
|
|
@@ -251,7 +251,7 @@ const app = await HttpFactory.create(AppModule, { middleware: [AuthGuard] });
|
|
|
251
251
|
### `RouteContext`
|
|
252
252
|
|
|
253
253
|
The second argument to `handle`. Built **once per route at boot** and closed over
|
|
254
|
-
by the chain, so `get` is a `Map` lookup over an already-merged record
|
|
254
|
+
by the chain, so `get` is a `Map` lookup over an already-merged record - not a
|
|
255
255
|
prototype walk, and nothing is resolved per request.
|
|
256
256
|
|
|
257
257
|
| Member | Is |
|
|
@@ -263,7 +263,7 @@ prototype walk, and nothing is resolved per request.
|
|
|
263
263
|
| `get(key)` | The metadata value, or `undefined` |
|
|
264
264
|
|
|
265
265
|
`get` resolves the **handler's** metadata first and the **controller class's**
|
|
266
|
-
second
|
|
266
|
+
second - the same override direction as Nest's `Reflector.getAllAndOverride`.
|
|
267
267
|
|
|
268
268
|
### Your own keys
|
|
269
269
|
|
|
@@ -326,7 +326,7 @@ logs at `warn`, a 5xx at `error`.
|
|
|
326
326
|
It needs no configuration: `Logger` and `RequestContext` are `@dunx/core`
|
|
327
327
|
contracts with default bindings, so this works in an app that imported no logging
|
|
328
328
|
module. Import `@dunx/infra/logger` and the same entries go through
|
|
329
|
-
`@arkv/logger`
|
|
329
|
+
`@arkv/logger` - sanitized, masked, optionally to a rotating file - with nothing
|
|
330
330
|
here changing.
|
|
331
331
|
|
|
332
332
|
Everything the **handler** logs in between carries the same `requestId`, `method`,
|
|
@@ -337,7 +337,7 @@ one is minted and returned on the response.
|
|
|
337
337
|
### Bodies are off by default, and what that costs
|
|
338
338
|
|
|
339
339
|
`requestBody` and `responseBody` default to **`false`**. Turning either on means a
|
|
340
|
-
`clone().text()`
|
|
340
|
+
`clone().text()` - a second copy of every payload, buffered and parsed, on the hot
|
|
341
341
|
path. Measured in `tools/bench`, both on cost roughly two thirds of the throughput
|
|
342
342
|
on the `validate` scenario. The response body is also the field most likely to
|
|
343
343
|
carry a secret, so this is the right default twice over.
|
|
@@ -345,7 +345,7 @@ carry a secret, so this is the right default twice over.
|
|
|
345
345
|
Turn them on in development, where seeing the payload is the point:
|
|
346
346
|
|
|
347
347
|
```ts
|
|
348
|
-
// Off entirely
|
|
348
|
+
// Off entirely - what the benchmark's primary `dunx` subject uses, since no other
|
|
349
349
|
// framework in that suite logs anything.
|
|
350
350
|
HttpFactory.create(AppModule, { requestLogging: false });
|
|
351
351
|
|
|
@@ -362,15 +362,15 @@ HttpFactory.create(AppModule, {
|
|
|
362
362
|
|
|
363
363
|
**Even at its cheapest, a log line is not free.** `tools/bench` carries `dunx` and
|
|
364
364
|
`dunx-logging` as separate subjects for exactly this reason: with logging off dunx
|
|
365
|
-
runs at 81
|
|
365
|
+
runs at 81-100% of raw `Bun.serve` depending on the scenario, and with it on, 40-45%.
|
|
366
366
|
The remainder is `JSON.stringify` plus a `write` per request inside an
|
|
367
367
|
`AsyncLocalStorage` scope. If you need the last of the throughput, turn it off and
|
|
368
|
-
sample at the edge instead
|
|
368
|
+
sample at the edge instead - but know what you gave up.
|
|
369
369
|
|
|
370
370
|
### Unmatched paths are logged too
|
|
371
371
|
|
|
372
372
|
`Bun.serve({ routes })` answers a miss itself, so nothing in the middleware chain
|
|
373
|
-
would ever see a 404
|
|
373
|
+
would ever see a 404 - invisible to logging, metrics and tracing. `listen()`
|
|
374
374
|
installs one `fetch` fallback that runs the global middleware and returns
|
|
375
375
|
`{"error":"NOT_FOUND","status":404}`.
|
|
376
376
|
|
|
@@ -381,7 +381,7 @@ fallback runs only after it has decided nothing matched.
|
|
|
381
381
|
|
|
382
382
|
A route with **no middleware and no CORS** is dispatched by a handler in which
|
|
383
383
|
nothing is `async`. It returns a `Response` rather than a `Promise<Response>`
|
|
384
|
-
wherever it has nothing to wait for
|
|
384
|
+
wherever it has nothing to wait for - Bun accepts either. The general path awaits the
|
|
385
385
|
input reader, the handler and the response coercion, and for most shapes those awaits
|
|
386
386
|
are on values that were never thenable, each costing an async frame and a microtask
|
|
387
387
|
tick for nothing.
|
|
@@ -389,16 +389,16 @@ tick for nothing.
|
|
|
389
389
|
| Route shape | What it costs |
|
|
390
390
|
| ---------------------------------------- | ----------------------------------------- |
|
|
391
391
|
| no schemas | no promise at all |
|
|
392
|
-
| `query` and/or `params`, sync validator | no promise at all
|
|
392
|
+
| `query` and/or `params`, sync validator | no promise at all - read and validated inline |
|
|
393
393
|
| `body` declared | one promise link, for `req.json()` |
|
|
394
394
|
|
|
395
395
|
Measured in `tools/bench`: `plaintext` 89.5% -> 97.2% of raw `Bun.serve` when this
|
|
396
396
|
covered only schema-less routes, and `validate` 84.0% -> 92.3% once it was extended
|
|
397
397
|
to routes that read input. A handler that *does* return a promise, or a validator
|
|
398
|
-
that does, is adopted rather than wrapped
|
|
398
|
+
that does, is adopted rather than wrapped - nothing about this is conditional on
|
|
399
399
|
writing sync code.
|
|
400
400
|
|
|
401
|
-
Adding middleware
|
|
401
|
+
Adding middleware - including `requestLogging` - opts a route back into the async
|
|
402
402
|
path, because middleware is `async` by contract.
|
|
403
403
|
|
|
404
404
|
## App-level configuration
|
|
@@ -417,13 +417,13 @@ await app.listen(3000);
|
|
|
417
417
|
|
|
418
418
|
Calling any of them **after** `listen()` throws. The route table and the middleware
|
|
419
419
|
chain are folded into one closure per route when the server binds, so a late call
|
|
420
|
-
could only ever be a silent no-op
|
|
420
|
+
could only ever be a silent no-op - the failure mode worth trading for an error.
|
|
421
421
|
|
|
422
422
|
| Hook | Effect |
|
|
423
423
|
| ------------------------ | ----------------------------------------------------------------------------- |
|
|
424
424
|
| `setGlobalPrefix(p)` | Prefixes every discovered route. Slashes normalised; last call wins |
|
|
425
425
|
| `use(...middleware)` | Appends container-resolved `Ctor<Middleware>`, so it can inject |
|
|
426
|
-
| `set(key, value)` | Typed settings
|
|
426
|
+
| `set(key, value)` | Typed settings - a key must exist on `AppSettings`, so a typo is a type error |
|
|
427
427
|
| `setting(key)` | Reads one back |
|
|
428
428
|
| `enableCors(options?)` | Response headers plus an `OPTIONS` preflight per path. Last call wins |
|
|
429
429
|
| `clientIp(req)` | The `inject(ClientAddress)` singleton, honouring `'trust proxy'` |
|
|
@@ -433,11 +433,11 @@ could only ever be a silent no-op — the failure mode worth trading for an erro
|
|
|
433
433
|
|
|
434
434
|
- **Middleware order**: `HttpOptions.middleware` first (outermost), then each
|
|
435
435
|
`use()` call in the order it was made, then a controller's `@UseGuards`, then a
|
|
436
|
-
method's
|
|
436
|
+
method's - innermost. Outermost sees the request first and the response last.
|
|
437
437
|
- **Port**: the `listen(port)` argument, else `HttpOptions.port`, else `3000`.
|
|
438
438
|
- **Error mapper**: `HttpOptions.onError`; there is no imperative equivalent.
|
|
439
439
|
- **Overrides**: `HttpOptions.overrides` is core's `AppOptions.overrides`, passed
|
|
440
|
-
straight through
|
|
440
|
+
straight through - bindings replaced in place, which is what `@dunx/testing`'s
|
|
441
441
|
`createTestServer` uses.
|
|
442
442
|
- **Repeated calls**: `setGlobalPrefix`, `set` and `enableCors` all replace, so the
|
|
443
443
|
last call wins. `use()` appends.
|
|
@@ -448,7 +448,7 @@ could only ever be a silent no-op — the failure mode worth trading for an erro
|
|
|
448
448
|
### CORS and preflight
|
|
449
449
|
|
|
450
450
|
`Bun.serve({ routes })` answers a method miss with `404`, so a preflight can never
|
|
451
|
-
be inferred
|
|
451
|
+
be inferred - `enableCors()` mounts an explicit `OPTIONS` handler on every path,
|
|
452
452
|
built at boot from the methods that path actually declares. `origin` takes a
|
|
453
453
|
string, a list, or a predicate; anything not allowed gets **no** CORS headers at
|
|
454
454
|
all, which is what makes the browser block it. `'*'` is the default, and because a
|
|
@@ -459,7 +459,7 @@ a mapped `500` still carries them.
|
|
|
459
459
|
|
|
460
460
|
### Client IP
|
|
461
461
|
|
|
462
|
-
`ClientAddress` needs no registration
|
|
462
|
+
`ClientAddress` needs no registration - every class is injectable, and `listen()`
|
|
463
463
|
hands the resolved singleton the live server:
|
|
464
464
|
|
|
465
465
|
```ts
|
|
@@ -480,7 +480,7 @@ send whatever it likes.
|
|
|
480
480
|
|
|
481
481
|
## WebSocket gateways
|
|
482
482
|
|
|
483
|
-
A gateway is a normal injectable class declared in `@Module({ providers })`
|
|
483
|
+
A gateway is a normal injectable class declared in `@Module({ providers })` - there
|
|
484
484
|
is no second list and no module to configure. `HttpFactory` finds it by its
|
|
485
485
|
`@Gateway` marker, and `listen()` mounts it on the same server as the routes:
|
|
486
486
|
|
|
@@ -539,7 +539,7 @@ path that upgrades.
|
|
|
539
539
|
|
|
540
540
|
| Decorator | Signature | Notes |
|
|
541
541
|
| ------------------- | ------------------------------------- | -------------------------------------------------------------- |
|
|
542
|
-
| `@Gateway(path)` | class | Required
|
|
542
|
+
| `@Gateway(path)` | class | Required - it is what marks the provider as a gateway |
|
|
543
543
|
| `@OnUpgrade()` | `(req: BunRequest)` | Return a `Response` to refuse; anything else becomes `context` |
|
|
544
544
|
| `@OnOpen()` | `(socket)` | |
|
|
545
545
|
| `@OnMessage(event)` | `(data, socket)` | Routed by envelope event name |
|
|
@@ -549,7 +549,7 @@ path that upgrades.
|
|
|
549
549
|
| `@OnPing()` | `(data, socket)` | Bun still answers with a pong |
|
|
550
550
|
| `@OnPong()` | `(data, socket)` | |
|
|
551
551
|
|
|
552
|
-
Handlers may be `async`. A returned value is sent to the sender
|
|
552
|
+
Handlers may be `async`. A returned value is sent to the sender - under the same
|
|
553
553
|
event name for `@OnMessage(event)`, verbatim (or JSON) for the raw handler, and
|
|
554
554
|
never for a lifecycle handler. Return `undefined` to send nothing.
|
|
555
555
|
|
|
@@ -569,7 +569,7 @@ needed** for a socket to connect. Consequences, all measured:
|
|
|
569
569
|
and can be returned as the connection's `context`.
|
|
570
570
|
- A plain `GET` on a gateway path is **426**; any other method is Bun's native
|
|
571
571
|
**404**, because the upgrade is mounted as a `GET`. A path no gateway and no
|
|
572
|
-
controller serves is the same native 404
|
|
572
|
+
controller serves is the same native 404 - there is nothing to fall through to.
|
|
573
573
|
- A path claimed by both a gateway and a controller route is a **boot error**
|
|
574
574
|
naming both, since one of the two would otherwise be dropped from the table.
|
|
575
575
|
- `setGlobalPrefix()` moves routes, **not** gateways. A gateway path is the exact
|
|
@@ -588,7 +588,7 @@ These throw at boot rather than picking a winner:
|
|
|
588
588
|
- two handlers claiming one event or one lifecycle slot, named individually
|
|
589
589
|
- two gateways on one path, named individually
|
|
590
590
|
- a `@Gateway` class with no handlers at all
|
|
591
|
-
- a handler-declaring provider that is **not** a `@Gateway`
|
|
591
|
+
- a handler-declaring provider that is **not** a `@Gateway` - it could never
|
|
592
592
|
receive a frame, so it is an error instead of a silent no-op
|
|
593
593
|
|
|
594
594
|
### The envelope
|
|
@@ -603,7 +603,7 @@ smallest one that works:
|
|
|
603
603
|
It is **opt-in**: a frame is only parsed for a gateway that declares at least one
|
|
604
604
|
`@OnMessage(event)` handler. A gateway with only a raw `@OnMessage()` never sees
|
|
605
605
|
JSON it did not ask for. Binary frames, invalid JSON, a non-object, a missing
|
|
606
|
-
`event`, and an event no handler claims all fall through to the raw handler
|
|
606
|
+
`event`, and an event no handler claims all fall through to the raw handler - and
|
|
607
607
|
are ignored if there is none. Nothing is ever replied to the sender that a handler
|
|
608
608
|
did not return.
|
|
609
609
|
|
|
@@ -618,7 +618,7 @@ Topics live in Bun, not in a JavaScript map. A socket joins one with
|
|
|
618
618
|
native methods on the socket you already hold.
|
|
619
619
|
|
|
620
620
|
`PubSub` is the injectable side, for publishing without a socket. `HttpFactory`
|
|
621
|
-
binds it around your root module, so nothing has to be imported or registered
|
|
621
|
+
binds it around your root module, so nothing has to be imported or registered -
|
|
622
622
|
listing it in `providers` as well is the container's duplicate-binding error:
|
|
623
623
|
|
|
624
624
|
```ts
|
|
@@ -634,7 +634,7 @@ class Notifier {
|
|
|
634
634
|
```
|
|
635
635
|
|
|
636
636
|
`publish` returns the bytes sent, `0` if the message was dropped, `-1` under
|
|
637
|
-
backpressure
|
|
637
|
+
backpressure - Bun's own status. It goes through `server.publish`, which reaches
|
|
638
638
|
**every** subscriber including the socket whose handler triggered it (unlike
|
|
639
639
|
`socket.publish`, which honours `publishToSelf`). Publishing before the server is
|
|
640
640
|
listening throws saying so.
|
|
@@ -665,7 +665,7 @@ the default logs.
|
|
|
665
665
|
### Shutdown with a live socket
|
|
666
666
|
|
|
667
667
|
Measured: a graceful `server.stop()` waits for open connections, and a WebSocket
|
|
668
|
-
does not close on its own
|
|
668
|
+
does not close on its own - so it **never resolves** while a socket is open. An app
|
|
669
669
|
with at least one gateway therefore force-stops (`stop(true)`) in `shutdown()`, and
|
|
670
670
|
those clients see a `1006` close. An app with no gateways still stops gracefully.
|
|
671
671
|
Bun also delivers an empty close `reason` to `@OnClose` once a socket has exchanged
|
|
@@ -686,7 +686,7 @@ const app = await HttpFactory.create(AppModule, {
|
|
|
686
686
|
});
|
|
687
687
|
```
|
|
688
688
|
|
|
689
|
-
That is the whole opt-in. `RedisRelay` is `Bun.RedisClient`
|
|
689
|
+
That is the whole opt-in. `RedisRelay` is `Bun.RedisClient` - a Bun global - so this
|
|
690
690
|
adds **no dependency**, and with no `relay` configured nothing here runs at all.
|
|
691
691
|
|
|
692
692
|
Nothing else changes. `socket.subscribe(topic)` is still Bun's, and a topic no
|
|
@@ -726,11 +726,11 @@ await app.get(PubSub).relayThrough(app.get(RedisConnection), {
|
|
|
726
726
|
await app.listen(3000);
|
|
727
727
|
```
|
|
728
728
|
|
|
729
|
-
Only one relay per `PubSub`
|
|
729
|
+
Only one relay per `PubSub` - a second `relayThrough` throws, because two
|
|
730
730
|
subscriptions on one channel is the other way to deliver everything twice.
|
|
731
731
|
|
|
732
732
|
`socket.publish(topic, data)` is Bun's own method and stays local; anything that must
|
|
733
|
-
cross nodes goes through `PubSub`. `subscriberCount` is local too
|
|
733
|
+
cross nodes goes through `PubSub`. `subscriberCount` is local too - Bun cannot count
|
|
734
734
|
another node's sockets.
|
|
735
735
|
|
|
736
736
|
`maxRetries` on `RedisRelay` defaults to `0`, and that is deliberate: a
|
|
@@ -740,7 +740,7 @@ Bun's reconnection.
|
|
|
740
740
|
|
|
741
741
|
## Status codes
|
|
742
742
|
|
|
743
|
-
`HttpStatusCode` is a frozen object, not an `enum`
|
|
743
|
+
`HttpStatusCode` is a frozen object, not an `enum` - one name serving as both the
|
|
744
744
|
value and the type, so it reads like an enum and erases like a constant:
|
|
745
745
|
|
|
746
746
|
```ts
|
|
@@ -769,11 +769,11 @@ still works.
|
|
|
769
769
|
- Schemas, parsers and the status are resolved in `buildRoutes` at boot, into the
|
|
770
770
|
same closure the middleware chain folds into. Per request the framework parses
|
|
771
771
|
and validates what was declared, calls the method, wraps the return, and maps a
|
|
772
|
-
throw
|
|
772
|
+
throw - no metadata read, no lookup, no DI.
|
|
773
773
|
- Gateways use the same marker-plus-prototype-scan discovery as routes, and go into
|
|
774
774
|
the same route table. `withUpgradeRoutes` and `buildWebSocket` are exported for
|
|
775
775
|
anyone assembling `Bun.serve` themselves.
|
|
776
776
|
|
|
777
777
|
## License
|
|
778
778
|
|
|
779
|
-
|
|
779
|
+
Apache-2.0
|
package/dist/index.js
CHANGED
|
@@ -603,7 +603,7 @@ class PubSub {
|
|
|
603
603
|
}
|
|
604
604
|
async relayThrough(relay, options = {}) {
|
|
605
605
|
if (this.#relay) {
|
|
606
|
-
throw new AppError5("PubSub already relays. Two subscriptions on one channel would deliver " + "every relayed message twice
|
|
606
|
+
throw new AppError5("PubSub already relays. Two subscriptions on one channel would deliver " + "every relayed message twice - pass HttpOptions.relay or call " + "relayThrough(), not both.");
|
|
607
607
|
}
|
|
608
608
|
this.#relay = relay;
|
|
609
609
|
this.#channel = options.channel ?? DEFAULT_RELAY_CHANNEL;
|
|
@@ -1423,5 +1423,5 @@ export {
|
|
|
1423
1423
|
ClientAddress
|
|
1424
1424
|
};
|
|
1425
1425
|
|
|
1426
|
-
//# debugId=
|
|
1426
|
+
//# debugId=1EC09309FBB1CF1964756E2164756E21
|
|
1427
1427
|
//# sourceMappingURL=index.js.map
|