@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.
- package/dist/chunk-8939brh2.js +174 -0
- package/dist/chunk-bg0dr54z.js +37 -0
- package/dist/chunk-gmtwad7f.js +71 -0
- package/dist/client.js +128 -12
- package/dist/index.js +1411 -155
- package/dist/inspect.d.ts +9 -0
- package/dist/internal.d.ts +8 -25
- package/dist/internal.js +27 -99
- package/dist/server/application.d.ts +7 -99
- package/dist/server/binding.d.ts +67 -0
- package/dist/server/factory.d.ts +4 -2
- package/dist/server/html.d.ts +10 -0
- package/dist/server/metrics.d.ts +8 -3
- package/dist/server/options-provider.d.ts +14 -1
- package/dist/server/options.d.ts +141 -0
- package/dist/ws/decorators.d.ts +1 -1
- package/dist/ws/postgres-relay.d.ts +0 -2
- package/dist/ws/relay.d.ts +8 -0
- package/dist/ws/socket.d.ts +16 -0
- package/package.json +4 -4
- package/dist/chunk-9x3evk19.js +0 -177
- package/dist/chunk-e8a9c6j2.js +0 -130
- package/dist/chunk-y85wcdhw.js +0 -1297
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
HttpStatusCode2
|
|
4
|
+
} from "./chunk-bg0dr54z.js";
|
|
5
|
+
|
|
6
|
+
// src/route/marker.ts
|
|
7
|
+
var ROUTE = Symbol.for("dunx.route");
|
|
8
|
+
var CONTROLLER = Symbol.for("dunx.controller");
|
|
9
|
+
var defaultStatusFor2 = (method) => method === "POST" ? HttpStatusCode2.CREATED : HttpStatusCode2.OK;
|
|
10
|
+
var resolvePath = (path) => typeof path === "function" ? path() : path;
|
|
11
|
+
var markRoute = (target, meta) => {
|
|
12
|
+
Object.defineProperty(target, ROUTE, { value: meta, configurable: true });
|
|
13
|
+
};
|
|
14
|
+
var routeMetaOf = (value) => typeof value === "function" ? value[ROUTE] : undefined;
|
|
15
|
+
var markController = (target, prefix) => {
|
|
16
|
+
Object.defineProperty(target, CONTROLLER, {
|
|
17
|
+
value: prefix,
|
|
18
|
+
configurable: true
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var prefixOf = (target) => target[CONTROLLER] ?? "";
|
|
22
|
+
|
|
23
|
+
// src/route/metadata.ts
|
|
24
|
+
var META = Symbol.for("dunx.meta");
|
|
25
|
+
var GUARDS = Symbol.for("dunx.guards");
|
|
26
|
+
var metaKey2 = (name) => ({
|
|
27
|
+
name,
|
|
28
|
+
id: Symbol(name)
|
|
29
|
+
});
|
|
30
|
+
var write = (target, key, value) => {
|
|
31
|
+
const record = new Map(target[META]);
|
|
32
|
+
record.set(key.id, value);
|
|
33
|
+
Object.defineProperty(target, META, { value: record, configurable: true });
|
|
34
|
+
};
|
|
35
|
+
var meta2 = (key, value) => (target) => {
|
|
36
|
+
write(target, key, value);
|
|
37
|
+
return target;
|
|
38
|
+
};
|
|
39
|
+
var ROLES2 = metaKey2("roles");
|
|
40
|
+
var PUBLIC2 = metaKey2("public");
|
|
41
|
+
var HIDDEN2 = metaKey2("hidden");
|
|
42
|
+
var UNMATCHED2 = metaKey2("unmatched");
|
|
43
|
+
var Roles2 = (...roles) => meta2(ROLES2, roles);
|
|
44
|
+
var Public2 = () => meta2(PUBLIC2, true);
|
|
45
|
+
var ApiHidden2 = () => meta2(HIDDEN2, true);
|
|
46
|
+
var UseGuards2 = (...guards) => (target) => {
|
|
47
|
+
const existing = target[GUARDS] ?? [];
|
|
48
|
+
const merged = Object.hasOwn(target, GUARDS) ? [...guards, ...existing] : [...existing, ...guards];
|
|
49
|
+
Object.defineProperty(target, GUARDS, {
|
|
50
|
+
value: merged,
|
|
51
|
+
configurable: true
|
|
52
|
+
});
|
|
53
|
+
return target;
|
|
54
|
+
};
|
|
55
|
+
var guardsOf = (target) => target[GUARDS] ?? [];
|
|
56
|
+
var metaOf2 = (target) => target[META];
|
|
57
|
+
var mergeMeta2 = (...targets) => {
|
|
58
|
+
const merged = new Map;
|
|
59
|
+
for (const target of targets) {
|
|
60
|
+
const record = target[META];
|
|
61
|
+
if (record)
|
|
62
|
+
for (const [id, value] of record)
|
|
63
|
+
merged.set(id, value);
|
|
64
|
+
}
|
|
65
|
+
return merged;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/route/discover.ts
|
|
69
|
+
import { markedMethods } from "@dunx/core";
|
|
70
|
+
var joinPath2 = (prefix, path) => {
|
|
71
|
+
const joined = `/${prefix}/${path}`.replace(/\/{2,}/g, "/");
|
|
72
|
+
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
73
|
+
};
|
|
74
|
+
var discoverRoutes2 = (instance) => {
|
|
75
|
+
const klass = instance.constructor;
|
|
76
|
+
const prefix = prefixOf(klass);
|
|
77
|
+
const classGuards = guardsOf(klass);
|
|
78
|
+
const members = instance;
|
|
79
|
+
return markedMethods(Object.getPrototypeOf(instance), routeMetaOf).map(({ name, meta, value: marked }) => ({
|
|
80
|
+
method: meta.method,
|
|
81
|
+
path: joinPath2(prefix, resolvePath(meta.path)),
|
|
82
|
+
controller: klass.name,
|
|
83
|
+
handlerName: name,
|
|
84
|
+
handler: members[name].bind(instance),
|
|
85
|
+
options: meta.options,
|
|
86
|
+
meta: mergeMeta2(klass, marked),
|
|
87
|
+
classMeta: metaOf2(klass),
|
|
88
|
+
guards: [...classGuards, ...guardsOf(marked)]
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/ws/marker.ts
|
|
93
|
+
var HANDLER = Symbol.for("dunx.ws.handler");
|
|
94
|
+
var GATEWAY = Symbol.for("dunx.ws.gateway");
|
|
95
|
+
var HandlerKind = Object.freeze({
|
|
96
|
+
UPGRADE: "upgrade",
|
|
97
|
+
OPEN: "open",
|
|
98
|
+
MESSAGE: "message",
|
|
99
|
+
CLOSE: "close",
|
|
100
|
+
DRAIN: "drain",
|
|
101
|
+
PING: "ping",
|
|
102
|
+
PONG: "pong"
|
|
103
|
+
});
|
|
104
|
+
var markHandler = (target, meta) => {
|
|
105
|
+
Object.defineProperty(target, HANDLER, { value: meta, configurable: true });
|
|
106
|
+
};
|
|
107
|
+
var handlerMetaOf = (value) => typeof value === "function" ? value[HANDLER] : undefined;
|
|
108
|
+
var markGateway = (target, path) => {
|
|
109
|
+
Object.defineProperty(target, GATEWAY, { value: path, configurable: true });
|
|
110
|
+
};
|
|
111
|
+
var gatewayPathOf = (target) => target[GATEWAY] ?? "/";
|
|
112
|
+
var isGateway2 = (target) => target[GATEWAY] !== undefined;
|
|
113
|
+
|
|
114
|
+
// src/server/context.ts
|
|
115
|
+
var EMPTY = new Map;
|
|
116
|
+
var buildContext2 = (route) => {
|
|
117
|
+
const record = route.meta ?? EMPTY;
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
controller: route.controller,
|
|
120
|
+
handler: route.handlerName,
|
|
121
|
+
method: route.method,
|
|
122
|
+
path: route.path,
|
|
123
|
+
parsesBody: route.options?.body !== undefined,
|
|
124
|
+
get: (key) => record.get(key.id)
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/ws/discover.ts
|
|
129
|
+
import {
|
|
130
|
+
AppError,
|
|
131
|
+
classOf,
|
|
132
|
+
markedMethods as markedMethods2
|
|
133
|
+
} from "@dunx/core";
|
|
134
|
+
var normalizePath = (path) => {
|
|
135
|
+
const joined = `/${path}`.replace(/\/{2,}/g, "/");
|
|
136
|
+
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
137
|
+
};
|
|
138
|
+
var eachHandler = (start) => markedMethods2(start, handlerMetaOf);
|
|
139
|
+
var discoverGateway = (instance) => {
|
|
140
|
+
const klass = instance.constructor;
|
|
141
|
+
const members = instance;
|
|
142
|
+
return {
|
|
143
|
+
name: klass.name,
|
|
144
|
+
path: normalizePath(gatewayPathOf(klass)),
|
|
145
|
+
handlers: eachHandler(Object.getPrototypeOf(instance)).map(({ name, meta }) => ({
|
|
146
|
+
kind: meta.kind,
|
|
147
|
+
event: meta.event,
|
|
148
|
+
method: name,
|
|
149
|
+
invoke: members[name].bind(instance)
|
|
150
|
+
}))
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.name;
|
|
154
|
+
var discoverGateways = (modules, resolve) => {
|
|
155
|
+
const discovered = [];
|
|
156
|
+
for (const module of modules) {
|
|
157
|
+
for (const entry of module.options.providers ?? []) {
|
|
158
|
+
const candidate = classOf(entry);
|
|
159
|
+
if (!candidate)
|
|
160
|
+
continue;
|
|
161
|
+
if (isGateway2(candidate.ctor)) {
|
|
162
|
+
discovered.push(discoverGateway(resolve(candidate.token)));
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const orphan = findHandlerMethod(candidate.ctor);
|
|
166
|
+
if (orphan !== undefined) {
|
|
167
|
+
throw new AppError(`${candidate.ctor.name}.${orphan}() is a websocket handler, but ` + `${candidate.ctor.name} is not a gateway. Decorate the class with ` + "@Gateway(path), or drop the handler decorator.");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return discovered;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export { defaultStatusFor2, markRoute, markController, metaKey2, meta2, ROLES2, PUBLIC2, HIDDEN2, UNMATCHED2, Roles2, Public2, ApiHidden2, UseGuards2, metaOf2, mergeMeta2, joinPath2, discoverRoutes2, HandlerKind, markHandler, markGateway, isGateway2, discoverGateway, discoverGateways, buildContext2 };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/server/status.ts
|
|
3
|
+
var HttpStatusCode2 = Object.freeze({
|
|
4
|
+
OK: 200,
|
|
5
|
+
CREATED: 201,
|
|
6
|
+
ACCEPTED: 202,
|
|
7
|
+
NO_CONTENT: 204,
|
|
8
|
+
MOVED_PERMANENTLY: 301,
|
|
9
|
+
FOUND: 302,
|
|
10
|
+
NOT_MODIFIED: 304,
|
|
11
|
+
TEMPORARY_REDIRECT: 307,
|
|
12
|
+
PERMANENT_REDIRECT: 308,
|
|
13
|
+
BAD_REQUEST: 400,
|
|
14
|
+
UNAUTHORIZED: 401,
|
|
15
|
+
PAYMENT_REQUIRED: 402,
|
|
16
|
+
FORBIDDEN: 403,
|
|
17
|
+
NOT_FOUND: 404,
|
|
18
|
+
METHOD_NOT_ALLOWED: 405,
|
|
19
|
+
NOT_ACCEPTABLE: 406,
|
|
20
|
+
REQUEST_TIMEOUT: 408,
|
|
21
|
+
CONFLICT: 409,
|
|
22
|
+
GONE: 410,
|
|
23
|
+
PRECONDITION_FAILED: 412,
|
|
24
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
25
|
+
URI_TOO_LONG: 414,
|
|
26
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
27
|
+
IM_A_TEAPOT: 418,
|
|
28
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
29
|
+
TOO_MANY_REQUESTS: 429,
|
|
30
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
31
|
+
NOT_IMPLEMENTED: 501,
|
|
32
|
+
BAD_GATEWAY: 502,
|
|
33
|
+
SERVICE_UNAVAILABLE: 503,
|
|
34
|
+
GATEWAY_TIMEOUT: 504
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export { HttpStatusCode2 };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/server/trace-context.ts
|
|
3
|
+
var TRACEPARENT_HEADER2 = "traceparent";
|
|
4
|
+
var TRACESTATE_HEADER2 = "tracestate";
|
|
5
|
+
var TRACERESPONSE_HEADER2 = "traceresponse";
|
|
6
|
+
var HEX_32 = /^[0-9a-f]{32}$/;
|
|
7
|
+
var HEX_16 = /^[0-9a-f]{16}$/;
|
|
8
|
+
var HEX_2 = /^[0-9a-f]{2}$/;
|
|
9
|
+
var ZERO_TRACE = "0".repeat(32);
|
|
10
|
+
var ZERO_SPAN = "0".repeat(16);
|
|
11
|
+
var SAMPLED = 1;
|
|
12
|
+
var DEFAULT_FLAGS = "01";
|
|
13
|
+
var TRACE = Symbol.for("dunx.http.trace");
|
|
14
|
+
var EXPOSE = Symbol.for("dunx.http.trace.expose");
|
|
15
|
+
var mint = (bytes) => crypto.getRandomValues(new Uint8Array(bytes)).toHex();
|
|
16
|
+
|
|
17
|
+
class TraceContext2 {
|
|
18
|
+
static adopt(req, expose = true) {
|
|
19
|
+
const inbound = TraceContext2.#parse(req.headers.get(TRACEPARENT_HEADER2));
|
|
20
|
+
const state = req.headers.get(TRACESTATE_HEADER2);
|
|
21
|
+
const trace = inbound === undefined ? { traceId: mint(16), spanId: mint(8), flags: DEFAULT_FLAGS } : {
|
|
22
|
+
traceId: inbound.traceId,
|
|
23
|
+
spanId: mint(8),
|
|
24
|
+
parentSpanId: inbound.spanId,
|
|
25
|
+
flags: inbound.flags,
|
|
26
|
+
...state === null ? {} : { state }
|
|
27
|
+
};
|
|
28
|
+
req[TRACE] = trace;
|
|
29
|
+
if (expose)
|
|
30
|
+
req[EXPOSE] = true;
|
|
31
|
+
return trace;
|
|
32
|
+
}
|
|
33
|
+
static of(req) {
|
|
34
|
+
return req[TRACE];
|
|
35
|
+
}
|
|
36
|
+
static header(trace) {
|
|
37
|
+
return `00-${trace.traceId}-${trace.spanId}-${trace.flags}`;
|
|
38
|
+
}
|
|
39
|
+
static stamp(response, req) {
|
|
40
|
+
const traced = req;
|
|
41
|
+
const trace = traced[TRACE];
|
|
42
|
+
if (trace !== undefined && traced[EXPOSE] === true) {
|
|
43
|
+
response.headers.set(TRACERESPONSE_HEADER2, TraceContext2.header(trace));
|
|
44
|
+
}
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
static sampled(trace) {
|
|
48
|
+
return (Number.parseInt(trace.flags, 16) & SAMPLED) === SAMPLED;
|
|
49
|
+
}
|
|
50
|
+
static #parse(header) {
|
|
51
|
+
if (header === null)
|
|
52
|
+
return;
|
|
53
|
+
const parts = header.split("-");
|
|
54
|
+
if (parts.length < 4)
|
|
55
|
+
return;
|
|
56
|
+
const [version, traceId, spanId, flags] = parts;
|
|
57
|
+
if (!HEX_2.test(version) || version === "ff")
|
|
58
|
+
return;
|
|
59
|
+
if (version === "00" && parts.length !== 4)
|
|
60
|
+
return;
|
|
61
|
+
if (!HEX_32.test(traceId) || traceId === ZERO_TRACE)
|
|
62
|
+
return;
|
|
63
|
+
if (!HEX_16.test(spanId) || spanId === ZERO_SPAN)
|
|
64
|
+
return;
|
|
65
|
+
if (!HEX_2.test(flags))
|
|
66
|
+
return;
|
|
67
|
+
return { traceId, spanId, flags };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { TRACEPARENT_HEADER2, TRACESTATE_HEADER2, TRACERESPONSE_HEADER2, TraceContext2 };
|
package/dist/client.js
CHANGED
|
@@ -1,16 +1,47 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
safeStringify
|
|
8
|
-
} from "./chunk-e8a9c6j2.js";
|
|
3
|
+
TRACEPARENT_HEADER2,
|
|
4
|
+
TRACESTATE_HEADER2,
|
|
5
|
+
TraceContext2
|
|
6
|
+
} from "./chunk-gmtwad7f.js";
|
|
9
7
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
HttpStatusCode2
|
|
9
|
+
} from "./chunk-bg0dr54z.js";
|
|
10
|
+
|
|
11
|
+
// src/client/errors.ts
|
|
12
|
+
import { AppError } from "@dunx/core";
|
|
13
|
+
|
|
14
|
+
class FetchError extends AppError {
|
|
15
|
+
status;
|
|
16
|
+
statusText;
|
|
17
|
+
body;
|
|
18
|
+
response;
|
|
19
|
+
name = "FetchError";
|
|
20
|
+
constructor(status, statusText, body, response) {
|
|
21
|
+
super(`HTTP ${status} ${statusText} from ${response.method} ${response.url}`);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.statusText = statusText;
|
|
24
|
+
this.body = body;
|
|
25
|
+
this.response = response;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(FetchError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "override readonly status: number" }, { unresolved: "readonly statusText: string" }, { unresolved: "readonly body: unknown" }, { unresolved: `readonly response: {
|
|
29
|
+
readonly method: string;
|
|
30
|
+
readonly url: string;
|
|
31
|
+
readonly headers: Headers;
|
|
32
|
+
}` }] });
|
|
33
|
+
|
|
34
|
+
class FetchTransportError extends AppError {
|
|
35
|
+
response;
|
|
36
|
+
aborted;
|
|
37
|
+
name = "FetchTransportError";
|
|
38
|
+
constructor(response, aborted, options) {
|
|
39
|
+
super(`${response.method} ${response.url} failed: ${aborted ? "aborted" : "transport error"}`, options);
|
|
40
|
+
this.response = response;
|
|
41
|
+
this.aborted = aborted;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
Object.defineProperty(FetchTransportError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly response: { readonly method: string; readonly url: string }" }, { unresolved: "readonly aborted: boolean" }, ErrorOptions] });
|
|
14
45
|
// src/client/options.ts
|
|
15
46
|
class HttpClientOptions {
|
|
16
47
|
baseUrl;
|
|
@@ -51,6 +82,91 @@ import {
|
|
|
51
82
|
// src/client/service.ts
|
|
52
83
|
import { Logger, RequestContext } from "@dunx/core";
|
|
53
84
|
import { UrlHelper } from "@arkv/shared";
|
|
85
|
+
|
|
86
|
+
// src/client/json.ts
|
|
87
|
+
var safeStringify = (value) => {
|
|
88
|
+
const seen = new WeakSet;
|
|
89
|
+
return JSON.stringify(value, (_key, entry) => {
|
|
90
|
+
if (typeof entry === "object" && entry !== null) {
|
|
91
|
+
if (seen.has(entry))
|
|
92
|
+
return "[Circular]";
|
|
93
|
+
seen.add(entry);
|
|
94
|
+
}
|
|
95
|
+
return entry;
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
var isJsonBody = (payload) => {
|
|
99
|
+
if (payload === null || payload === undefined)
|
|
100
|
+
return false;
|
|
101
|
+
if (typeof payload !== "object")
|
|
102
|
+
return typeof payload !== "string";
|
|
103
|
+
return !(payload instanceof FormData || payload instanceof URLSearchParams || payload instanceof Blob || payload instanceof ArrayBuffer || payload instanceof ReadableStream || ArrayBuffer.isView(payload));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/client/retry.ts
|
|
107
|
+
var uniform = () => {
|
|
108
|
+
const buffer = new Uint32Array(1);
|
|
109
|
+
crypto.getRandomValues(buffer);
|
|
110
|
+
return (buffer[0] ?? 0) / 2 ** 32;
|
|
111
|
+
};
|
|
112
|
+
var backoffDelay = (attempt, { baseMs, power = 2, jitterMs = 1000, maxMs = 30000 }) => Math.min(baseMs * power ** attempt + uniform() * jitterMs, maxMs);
|
|
113
|
+
var retryAfterMs = (headers, now = Date.now()) => {
|
|
114
|
+
const header = headers.get("retry-after");
|
|
115
|
+
if (header === null)
|
|
116
|
+
return;
|
|
117
|
+
const seconds = Number(header);
|
|
118
|
+
if (Number.isFinite(seconds))
|
|
119
|
+
return Math.max(0, seconds * 1000);
|
|
120
|
+
const at = Date.parse(header);
|
|
121
|
+
return Number.isNaN(at) ? undefined : Math.max(0, at - now);
|
|
122
|
+
};
|
|
123
|
+
var isRetryableStatus = (status) => status >= HttpStatusCode2.INTERNAL_SERVER_ERROR || status === HttpStatusCode2.REQUEST_TIMEOUT || status === HttpStatusCode2.TOO_MANY_REQUESTS;
|
|
124
|
+
var decide = (error, attempt, options) => {
|
|
125
|
+
const {
|
|
126
|
+
retryDelayMs = 1000,
|
|
127
|
+
backoff,
|
|
128
|
+
shouldRetryOnStatus = isRetryableStatus,
|
|
129
|
+
respectRetryAfter = true
|
|
130
|
+
} = options;
|
|
131
|
+
const computed = backoffDelay(attempt, { baseMs: retryDelayMs, ...backoff });
|
|
132
|
+
if (error instanceof FetchTransportError) {
|
|
133
|
+
return { retry: !error.aborted, delayMs: computed };
|
|
134
|
+
}
|
|
135
|
+
if (error instanceof FetchError) {
|
|
136
|
+
if (!shouldRetryOnStatus(error.status))
|
|
137
|
+
return { retry: false, delayMs: 0 };
|
|
138
|
+
const asked = respectRetryAfter ? retryAfterMs(error.response.headers) : undefined;
|
|
139
|
+
const maxMs = backoff?.maxMs ?? 30000;
|
|
140
|
+
return {
|
|
141
|
+
retry: true,
|
|
142
|
+
delayMs: asked === undefined ? computed : Math.min(asked, maxMs)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { retry: true, delayMs: computed };
|
|
146
|
+
};
|
|
147
|
+
var executeWithRetry = async (operation, options = {}) => {
|
|
148
|
+
const { maxRetries = 3, onAttempt, onError, onSuccess } = options;
|
|
149
|
+
let lastError;
|
|
150
|
+
for (let attempt = 0;attempt <= maxRetries; attempt += 1) {
|
|
151
|
+
onAttempt?.(attempt + 1, attempt > 0);
|
|
152
|
+
try {
|
|
153
|
+
const result = await operation();
|
|
154
|
+
onSuccess?.(result, attempt + 1);
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
lastError = error;
|
|
158
|
+
const { retry, delayMs } = decide(error, attempt, options);
|
|
159
|
+
const willRetry = retry && attempt < maxRetries;
|
|
160
|
+
onError?.(error, attempt + 1, willRetry);
|
|
161
|
+
if (!willRetry)
|
|
162
|
+
throw error;
|
|
163
|
+
await Bun.sleep(delayMs);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
throw lastError;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/client/service.ts
|
|
54
170
|
class HttpService extends UrlHelper {
|
|
55
171
|
options;
|
|
56
172
|
logger;
|
|
@@ -214,12 +330,12 @@ class HttpService extends UrlHelper {
|
|
|
214
330
|
...serialised === "" ? {} : { "content-type": "application/json" },
|
|
215
331
|
...this.options.headers,
|
|
216
332
|
...typeof trace?.traceId === "string" && typeof trace.spanId === "string" ? {
|
|
217
|
-
[
|
|
333
|
+
[TRACEPARENT_HEADER2]: TraceContext2.header({
|
|
218
334
|
traceId: trace.traceId,
|
|
219
335
|
spanId: trace.spanId,
|
|
220
336
|
flags: typeof trace.traceFlags === "string" ? trace.traceFlags : "01"
|
|
221
337
|
}),
|
|
222
|
-
...typeof trace.traceState === "string" ? { [
|
|
338
|
+
...typeof trace.traceState === "string" ? { [TRACESTATE_HEADER2]: trace.traceState } : {}
|
|
223
339
|
} : {},
|
|
224
340
|
...config.headerFactory?.({
|
|
225
341
|
timestamp: Math.floor(Date.now() / 1000),
|