@dunx/http 3.2.0 → 3.3.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.
@@ -27,6 +27,14 @@ export interface PubSubRelay {
27
27
  */
28
28
  close?(): unknown;
29
29
  }
30
+ /**
31
+ * Both relays take a URL, and a wrong scheme has to fail here rather than at
32
+ * connect time: an absence-tolerant relay swallows the connection error, so a
33
+ * typo would degrade silently to single-node fan-out.
34
+ */
35
+ export declare const assertRelayUrl: (url: string, protocols: readonly string[], example: string) => string;
36
+ /** The URL with any password removed, for logs and error messages. */
37
+ export declare const redactUrl: (url: string) => string;
30
38
  /** Which relay call failed, so one message can say what degraded. */
31
39
  export type RelayPhase = 'publish' | 'subscribe' | 'close';
32
40
  export interface RelayOptions {
@@ -42,4 +42,20 @@ export type SocketOptions = Readonly<Pick<WebSocketHandler<SocketData>, 'backpre
42
42
  * nothing replaced it.
43
43
  */
44
44
  readonly onError?: SocketErrorHandler;
45
+ /**
46
+ * What a binary frame arrives as: `'nodebuffer'` (Bun's default),
47
+ * `'arraybuffer'`, `'uint8array'` or `'blob'`. Bun 1.4.1 added `'blob'` and
48
+ * accepts all four on a server socket.
49
+ *
50
+ * Not one of the `Pick`ed keys above, because Bun does not take it as a handler
51
+ * option - it is a mutable property on each `ServerWebSocket`. Declaring it
52
+ * here and assigning it as each connection opens is what makes it a
53
+ * server-wide setting rather than a line in every gateway's `@OnOpen`. The type
54
+ * is read off the socket, so the accepted values cannot drift from the runtime.
55
+ *
56
+ * It changes what `message` and the ping/pong payloads are at run time. Bun
57
+ * types them as the default's, so a gateway that sets this narrows the value
58
+ * itself.
59
+ */
60
+ readonly binaryType?: Socket['binaryType'];
45
61
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/http",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways",
5
5
  "keywords": [
6
6
  "bun",
@@ -65,8 +65,8 @@
65
65
  "@opentelemetry/sdk-trace-node": "2.11.0"
66
66
  },
67
67
  "peerDependencies": {
68
- "@dunx/core": "^3.2.0",
69
- "@types/bun": ">=1.3.0"
68
+ "@dunx/core": "^3.3.0",
69
+ "@types/bun": ">=1.4.1"
70
70
  },
71
71
  "peerDependenciesMeta": {
72
72
  "@types/bun": {
@@ -74,6 +74,6 @@
74
74
  }
75
75
  },
76
76
  "engines": {
77
- "bun": ">=1.4.0"
77
+ "bun": ">=1.4.1"
78
78
  }
79
79
  }
@@ -1,177 +0,0 @@
1
- // @bun
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __name = (target, name) => {
6
- Object.defineProperty(target, "name", {
7
- value: name,
8
- enumerable: false,
9
- configurable: true
10
- });
11
- return target;
12
- };
13
- var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
- var __typeError = (msg) => {
15
- throw TypeError(msg);
16
- };
17
- var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
18
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
19
- var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
20
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
21
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
22
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
23
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
24
- var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
25
- var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
26
- var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
27
- var __decoratorContext = (kind, name, done, metadata, fns) => ({
28
- kind: __decoratorStrings[kind],
29
- name,
30
- metadata,
31
- addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
32
- });
33
- var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
34
- var __runInitializers = (array, flags, self, value) => {
35
- for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
36
- flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
37
- return value;
38
- };
39
- var __decorateElement = (array, flags, name, decorators, target, extra) => {
40
- var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
41
- var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
42
- var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
43
- var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
44
- get [name]() {
45
- return __privateGet(this, extra);
46
- },
47
- set [name](x) {
48
- __privateSet(this, extra, x);
49
- }
50
- }, name));
51
- k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
52
- for (var i = decorators.length - 1;i >= 0; i--) {
53
- ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
54
- if (k) {
55
- ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
56
- if (k ^ 3)
57
- access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
58
- if (k > 2)
59
- access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
60
- }
61
- it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
62
- done._ = 1;
63
- if (k ^ 4 || it === undefined)
64
- __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
65
- else if (typeof it !== "object" || it === null)
66
- __typeError("Object expected");
67
- else
68
- __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
69
- }
70
- return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
71
- };
72
-
73
- // src/server/status.ts
74
- var HttpStatusCode = Object.freeze({
75
- OK: 200,
76
- CREATED: 201,
77
- ACCEPTED: 202,
78
- NO_CONTENT: 204,
79
- MOVED_PERMANENTLY: 301,
80
- FOUND: 302,
81
- NOT_MODIFIED: 304,
82
- TEMPORARY_REDIRECT: 307,
83
- PERMANENT_REDIRECT: 308,
84
- BAD_REQUEST: 400,
85
- UNAUTHORIZED: 401,
86
- PAYMENT_REQUIRED: 402,
87
- FORBIDDEN: 403,
88
- NOT_FOUND: 404,
89
- METHOD_NOT_ALLOWED: 405,
90
- NOT_ACCEPTABLE: 406,
91
- REQUEST_TIMEOUT: 408,
92
- CONFLICT: 409,
93
- GONE: 410,
94
- PRECONDITION_FAILED: 412,
95
- PAYLOAD_TOO_LARGE: 413,
96
- URI_TOO_LONG: 414,
97
- UNSUPPORTED_MEDIA_TYPE: 415,
98
- IM_A_TEAPOT: 418,
99
- UNPROCESSABLE_ENTITY: 422,
100
- TOO_MANY_REQUESTS: 429,
101
- INTERNAL_SERVER_ERROR: 500,
102
- NOT_IMPLEMENTED: 501,
103
- BAD_GATEWAY: 502,
104
- SERVICE_UNAVAILABLE: 503,
105
- GATEWAY_TIMEOUT: 504
106
- });
107
-
108
- // src/server/trace-context.ts
109
- var TRACEPARENT_HEADER = "traceparent";
110
- var TRACESTATE_HEADER = "tracestate";
111
- var TRACERESPONSE_HEADER = "traceresponse";
112
- var HEX_32 = /^[0-9a-f]{32}$/;
113
- var HEX_16 = /^[0-9a-f]{16}$/;
114
- var HEX_2 = /^[0-9a-f]{2}$/;
115
- var ZERO_TRACE = "0".repeat(32);
116
- var ZERO_SPAN = "0".repeat(16);
117
- var SAMPLED = 1;
118
- var DEFAULT_FLAGS = "01";
119
- var TRACE = Symbol.for("dunx.http.trace");
120
- var EXPOSE = Symbol.for("dunx.http.trace.expose");
121
- var mint = (bytes) => crypto.getRandomValues(new Uint8Array(bytes)).toHex();
122
-
123
- class TraceContext {
124
- static adopt(req, expose = true) {
125
- const inbound = TraceContext.#parse(req.headers.get(TRACEPARENT_HEADER));
126
- const state = req.headers.get(TRACESTATE_HEADER);
127
- const trace = inbound === undefined ? { traceId: mint(16), spanId: mint(8), flags: DEFAULT_FLAGS } : {
128
- traceId: inbound.traceId,
129
- spanId: mint(8),
130
- parentSpanId: inbound.spanId,
131
- flags: inbound.flags,
132
- ...state === null ? {} : { state }
133
- };
134
- req[TRACE] = trace;
135
- if (expose)
136
- req[EXPOSE] = true;
137
- return trace;
138
- }
139
- static of(req) {
140
- return req[TRACE];
141
- }
142
- static header(trace) {
143
- return `00-${trace.traceId}-${trace.spanId}-${trace.flags}`;
144
- }
145
- static stamp(response, req) {
146
- const traced = req;
147
- const trace = traced[TRACE];
148
- if (trace !== undefined && traced[EXPOSE] === true) {
149
- response.headers.set(TRACERESPONSE_HEADER, TraceContext.header(trace));
150
- }
151
- return response;
152
- }
153
- static sampled(trace) {
154
- return (Number.parseInt(trace.flags, 16) & SAMPLED) === SAMPLED;
155
- }
156
- static #parse(header) {
157
- if (header === null)
158
- return;
159
- const parts = header.split("-");
160
- if (parts.length < 4)
161
- return;
162
- const [version, traceId, spanId, flags] = parts;
163
- if (!HEX_2.test(version) || version === "ff")
164
- return;
165
- if (version === "00" && parts.length !== 4)
166
- return;
167
- if (!HEX_32.test(traceId) || traceId === ZERO_TRACE)
168
- return;
169
- if (!HEX_16.test(spanId) || spanId === ZERO_SPAN)
170
- return;
171
- if (!HEX_2.test(flags))
172
- return;
173
- return { traceId, spanId, flags };
174
- }
175
- }
176
-
177
- export { __privateGet, __privateAdd, __decoratorStart, __decoratorMetadata, __runInitializers, __decorateElement, HttpStatusCode, TRACEPARENT_HEADER, TRACESTATE_HEADER, TRACERESPONSE_HEADER, TraceContext };
@@ -1,130 +0,0 @@
1
- // @bun
2
- import {
3
- HttpStatusCode
4
- } from "./chunk-9x3evk19.js";
5
-
6
- // src/client/errors.ts
7
- import { AppError } from "@dunx/core";
8
-
9
- class FetchError extends AppError {
10
- status;
11
- statusText;
12
- body;
13
- response;
14
- name = "FetchError";
15
- constructor(status, statusText, body, response) {
16
- super(`HTTP ${status} ${statusText} from ${response.method} ${response.url}`);
17
- this.status = status;
18
- this.statusText = statusText;
19
- this.body = body;
20
- this.response = response;
21
- }
22
- }
23
- Object.defineProperty(FetchError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "override readonly status: number" }, { unresolved: "readonly statusText: string" }, { unresolved: "readonly body: unknown" }, { unresolved: `readonly response: {
24
- readonly method: string;
25
- readonly url: string;
26
- readonly headers: Headers;
27
- }` }] });
28
-
29
- class FetchTransportError extends AppError {
30
- response;
31
- aborted;
32
- name = "FetchTransportError";
33
- constructor(response, aborted, options) {
34
- super(`${response.method} ${response.url} failed: ${aborted ? "aborted" : "transport error"}`, options);
35
- this.response = response;
36
- this.aborted = aborted;
37
- }
38
- }
39
- Object.defineProperty(FetchTransportError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly response: { readonly method: string; readonly url: string }" }, { unresolved: "readonly aborted: boolean" }, ErrorOptions] });
40
-
41
- // src/client/json.ts
42
- var safeStringify = (value) => {
43
- const seen = new WeakSet;
44
- return JSON.stringify(value, (_key, entry) => {
45
- if (typeof entry === "object" && entry !== null) {
46
- if (seen.has(entry))
47
- return "[Circular]";
48
- seen.add(entry);
49
- }
50
- return entry;
51
- });
52
- };
53
- var isPlainObject = (value) => {
54
- if (typeof value !== "object" || value === null)
55
- return false;
56
- const proto = Object.getPrototypeOf(value);
57
- return proto === Object.prototype || proto === null;
58
- };
59
- var isJsonBody = (payload) => {
60
- if (payload === null || payload === undefined)
61
- return false;
62
- if (typeof payload !== "object")
63
- return typeof payload !== "string";
64
- return !(payload instanceof FormData || payload instanceof URLSearchParams || payload instanceof Blob || payload instanceof ArrayBuffer || payload instanceof ReadableStream || ArrayBuffer.isView(payload));
65
- };
66
-
67
- // src/client/retry.ts
68
- var uniform = () => {
69
- const buffer = new Uint32Array(1);
70
- crypto.getRandomValues(buffer);
71
- return (buffer[0] ?? 0) / 2 ** 32;
72
- };
73
- var backoffDelay = (attempt, { baseMs, power = 2, jitterMs = 1000, maxMs = 30000 }) => Math.min(baseMs * power ** attempt + uniform() * jitterMs, maxMs);
74
- var retryAfterMs = (headers, now = Date.now()) => {
75
- const header = headers.get("retry-after");
76
- if (header === null)
77
- return;
78
- const seconds = Number(header);
79
- if (Number.isFinite(seconds))
80
- return Math.max(0, seconds * 1000);
81
- const at = Date.parse(header);
82
- return Number.isNaN(at) ? undefined : Math.max(0, at - now);
83
- };
84
- var isRetryableStatus = (status) => status >= HttpStatusCode.INTERNAL_SERVER_ERROR || status === HttpStatusCode.REQUEST_TIMEOUT || status === HttpStatusCode.TOO_MANY_REQUESTS;
85
- var decide = (error, attempt, options) => {
86
- const {
87
- retryDelayMs = 1000,
88
- backoff,
89
- shouldRetryOnStatus = isRetryableStatus,
90
- respectRetryAfter = true
91
- } = options;
92
- const computed = backoffDelay(attempt, { baseMs: retryDelayMs, ...backoff });
93
- if (error instanceof FetchTransportError) {
94
- return { retry: !error.aborted, delayMs: computed };
95
- }
96
- if (error instanceof FetchError) {
97
- if (!shouldRetryOnStatus(error.status))
98
- return { retry: false, delayMs: 0 };
99
- const asked = respectRetryAfter ? retryAfterMs(error.response.headers) : undefined;
100
- const maxMs = backoff?.maxMs ?? 30000;
101
- return {
102
- retry: true,
103
- delayMs: asked === undefined ? computed : Math.min(asked, maxMs)
104
- };
105
- }
106
- return { retry: true, delayMs: computed };
107
- };
108
- var executeWithRetry = async (operation, options = {}) => {
109
- const { maxRetries = 3, onAttempt, onError, onSuccess } = options;
110
- let lastError;
111
- for (let attempt = 0;attempt <= maxRetries; attempt += 1) {
112
- onAttempt?.(attempt + 1, attempt > 0);
113
- try {
114
- const result = await operation();
115
- onSuccess?.(result, attempt + 1);
116
- return result;
117
- } catch (error) {
118
- lastError = error;
119
- const { retry, delayMs } = decide(error, attempt, options);
120
- const willRetry = retry && attempt < maxRetries;
121
- onError?.(error, attempt + 1, willRetry);
122
- if (!willRetry)
123
- throw error;
124
- await Bun.sleep(delayMs);
125
- }
126
- }
127
- throw lastError;
128
- };
129
-
130
- export { FetchError, FetchTransportError, safeStringify, isPlainObject, isJsonBody, backoffDelay, retryAfterMs, isRetryableStatus, executeWithRetry };