@backendkit-labs/http-client 0.1.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.
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ var axios2 = require('axios');
4
+ var result = require('@backendkit-labs/result');
5
+ var circuitBreaker = require('@backendkit-labs/circuit-breaker');
6
+ var pipeline = require('@backendkit-labs/pipeline');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var axios2__default = /*#__PURE__*/_interopDefault(axios2);
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14
+ var __decorateClass = (decorators, target, key, kind) => {
15
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
16
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
17
+ if (decorator = decorators[i])
18
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
19
+ if (kind && result) __defProp(target, key, result);
20
+ return result;
21
+ };
22
+ var CancelManager = class {
23
+ tokens = /* @__PURE__ */ new Map();
24
+ getOrCreate(key) {
25
+ const existing = this.tokens.get(key);
26
+ if (existing) return existing;
27
+ const source = axios2__default.default.CancelToken.source();
28
+ this.tokens.set(key, source);
29
+ return source;
30
+ }
31
+ cancel(key, reason) {
32
+ const source = this.tokens.get(key);
33
+ if (source) {
34
+ source.cancel(reason ?? `Request ${key} cancelled`);
35
+ this.tokens.delete(key);
36
+ }
37
+ }
38
+ cancelAll() {
39
+ for (const [key, source] of this.tokens) {
40
+ source.cancel(`Request ${key} cancelled`);
41
+ }
42
+ this.tokens.clear();
43
+ }
44
+ delete(key) {
45
+ this.tokens.delete(key);
46
+ }
47
+ has(key) {
48
+ return this.tokens.has(key);
49
+ }
50
+ get size() {
51
+ return this.tokens.size;
52
+ }
53
+ };
54
+ var DEFAULT_RETRY = {
55
+ attempts: 0,
56
+ delayMs: 100,
57
+ maxDelayMs: 5e3,
58
+ jitter: true,
59
+ shouldRetry: (e) => e.type === "network" || e.type === "timeout" || e.type === "http" && (e.status ?? 0) >= 500
60
+ };
61
+ var HttpClient = class {
62
+ axiosInstance;
63
+ cb;
64
+ pipeline;
65
+ retry;
66
+ cancelMgr = new CancelManager();
67
+ _metrics = {
68
+ requests: 0,
69
+ success: 0,
70
+ failed: 0,
71
+ cancelled: 0,
72
+ circuitOpen: 0,
73
+ retried: 0
74
+ };
75
+ constructor(config = {}) {
76
+ this.axiosInstance = axios2__default.default.create({
77
+ baseURL: config.baseURL,
78
+ timeout: config.timeout ?? 1e4,
79
+ headers: config.headers
80
+ });
81
+ if (config.circuitBreaker) {
82
+ const { name: cbName, ...cbRest } = config.circuitBreaker;
83
+ this.cb = new circuitBreaker.CircuitBreaker({
84
+ name: cbName ?? "http-client",
85
+ ...circuitBreaker.DEFAULT_CIRCUIT_BREAKER_CONFIG,
86
+ isFailure: circuitBreaker.isHttpServerError,
87
+ ...cbRest
88
+ });
89
+ }
90
+ const p = new pipeline.Pipeline();
91
+ for (const step of config.steps ?? []) p.pipe(step);
92
+ this.pipeline = p;
93
+ this.retry = config.retry ? { ...DEFAULT_RETRY, ...config.retry } : DEFAULT_RETRY;
94
+ }
95
+ // ── HTTP methods ──────────────────────────────────────────────────────────
96
+ get(url, config) {
97
+ return this.execute("GET", url, void 0, config);
98
+ }
99
+ post(url, data, config) {
100
+ return this.execute("POST", url, data, config);
101
+ }
102
+ put(url, data, config) {
103
+ return this.execute("PUT", url, data, config);
104
+ }
105
+ patch(url, data, config) {
106
+ return this.execute("PATCH", url, data, config);
107
+ }
108
+ delete(url, config) {
109
+ return this.execute("DELETE", url, void 0, config);
110
+ }
111
+ // ── Cancellation ──────────────────────────────────────────────────────────
112
+ cancelRequest(key) {
113
+ this.cancelMgr.cancel(key);
114
+ this._metrics.cancelled++;
115
+ }
116
+ cancelAll() {
117
+ const count = this.cancelMgr.size;
118
+ this.cancelMgr.cancelAll();
119
+ this._metrics.cancelled += count;
120
+ }
121
+ // ── Observability ─────────────────────────────────────────────────────────
122
+ getMetrics() {
123
+ return { ...this._metrics };
124
+ }
125
+ getCircuitBreakerState() {
126
+ return this.cb?.getState();
127
+ }
128
+ getCircuitBreakerMetrics() {
129
+ return this.cb?.getMetrics();
130
+ }
131
+ // ── Internal ──────────────────────────────────────────────────────────────
132
+ async execute(method, url, data, config = {}) {
133
+ this._metrics.requests++;
134
+ const initialCtx = {
135
+ url,
136
+ method,
137
+ data,
138
+ headers: config.headers ?? {},
139
+ params: config.params,
140
+ timeout: config.timeout,
141
+ cancelKey: config.cancelKey,
142
+ correlationId: config.correlationId
143
+ };
144
+ if (initialCtx.cancelKey) {
145
+ this.cancelMgr.getOrCreate(initialCtx.cancelKey);
146
+ }
147
+ const pipelineResult = await this.pipeline.run(initialCtx);
148
+ if (!pipelineResult.ok) {
149
+ this._metrics.failed++;
150
+ return result.fail(pipelineResult.error.cause);
151
+ }
152
+ const ctx = pipelineResult.value;
153
+ if (ctx.cancelKey && !this.cancelMgr.has(ctx.cancelKey)) {
154
+ this._metrics.failed++;
155
+ return result.fail({ type: "cancelled", message: `Request ${ctx.cancelKey} cancelled` });
156
+ }
157
+ const cancelToken = ctx.cancelKey ? this.cancelMgr.getOrCreate(ctx.cancelKey).token : void 0;
158
+ const rawCall = () => this.callAxios(ctx, cancelToken);
159
+ const cbCall = async () => {
160
+ if (!this.cb) return rawCall();
161
+ try {
162
+ return await this.cb.execute(rawCall);
163
+ } catch (e) {
164
+ if (e instanceof circuitBreaker.CircuitBreakerOpenError) {
165
+ this._metrics.circuitOpen++;
166
+ const err = { type: "circuit-open", message: e.message };
167
+ throw err;
168
+ }
169
+ throw e;
170
+ }
171
+ };
172
+ try {
173
+ const response = this.retry.attempts > 0 ? await this.withRetry(cbCall, this.retry) : await cbCall();
174
+ if (ctx.cancelKey) this.cancelMgr.delete(ctx.cancelKey);
175
+ this._metrics.success++;
176
+ return result.ok(response);
177
+ } catch (e) {
178
+ if (ctx.cancelKey) this.cancelMgr.delete(ctx.cancelKey);
179
+ const error = this.isHttpClientError(e) ? e : this.normalizeError(e);
180
+ if (error.type !== "circuit-open") this._metrics.failed++;
181
+ return result.fail(error);
182
+ }
183
+ }
184
+ async callAxios(ctx, cancelToken) {
185
+ const response = await this.axiosInstance.request({
186
+ url: ctx.url,
187
+ method: ctx.method,
188
+ data: ctx.data,
189
+ headers: ctx.headers,
190
+ params: ctx.params,
191
+ timeout: ctx.timeout,
192
+ cancelToken
193
+ });
194
+ return {
195
+ data: response.data,
196
+ status: response.status,
197
+ headers: response.headers
198
+ };
199
+ }
200
+ async withRetry(fn, config) {
201
+ let lastError;
202
+ for (let attempt = 0; attempt <= config.attempts; attempt++) {
203
+ try {
204
+ return await fn();
205
+ } catch (e) {
206
+ lastError = e;
207
+ if (attempt === config.attempts) break;
208
+ const error = this.isHttpClientError(e) ? e : this.normalizeError(e);
209
+ if (!config.shouldRetry(error, attempt)) break;
210
+ this._metrics.retried++;
211
+ const baseDelay = config.delayMs * Math.pow(2, attempt);
212
+ const jitter = config.jitter ? Math.random() * config.delayMs : 0;
213
+ const delay = Math.min(baseDelay + jitter, config.maxDelayMs);
214
+ await new Promise((resolve) => setTimeout(resolve, delay));
215
+ }
216
+ }
217
+ throw lastError;
218
+ }
219
+ normalizeError(error) {
220
+ if (axios2__default.default.isCancel(error)) {
221
+ return {
222
+ type: "cancelled",
223
+ message: error.message ?? "Request cancelled"
224
+ };
225
+ }
226
+ if (axios2__default.default.isAxiosError(error)) {
227
+ const code = error.code;
228
+ if (code === "ECONNABORTED" || code === "ETIMEDOUT") {
229
+ return { type: "timeout", message: error.message, cause: error };
230
+ }
231
+ if (!error.response) {
232
+ return { type: "network", message: error.message, cause: error };
233
+ }
234
+ return {
235
+ type: "http",
236
+ message: error.message,
237
+ status: error.response.status,
238
+ data: error.response.data,
239
+ cause: error
240
+ };
241
+ }
242
+ return { type: "network", message: "Unexpected error", cause: error };
243
+ }
244
+ isHttpClientError(e) {
245
+ return typeof e === "object" && e !== null && "type" in e && "message" in e && typeof e.type === "string";
246
+ }
247
+ };
248
+
249
+ exports.CancelManager = CancelManager;
250
+ exports.HttpClient = HttpClient;
251
+ exports.__decorateClass = __decorateClass;
252
+ //# sourceMappingURL=chunk-GZWQJKYR.cjs.map
253
+ //# sourceMappingURL=chunk-GZWQJKYR.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/cancel-manager.ts","../src/core/http-client.ts"],"names":["axios","CircuitBreaker","DEFAULT_CIRCUIT_BREAKER_CONFIG","isHttpServerError","Pipeline","fail","CircuitBreakerOpenError","ok"],"mappings":"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","file":"chunk-GZWQJKYR.cjs","sourcesContent":["import axios from 'axios';\nimport type { CancelTokenSource } from 'axios';\n\nexport class CancelManager {\n private readonly tokens = new Map<string, CancelTokenSource>();\n\n getOrCreate(key: string): CancelTokenSource {\n const existing = this.tokens.get(key);\n if (existing) return existing;\n const source = axios.CancelToken.source();\n this.tokens.set(key, source);\n return source;\n }\n\n cancel(key: string, reason?: string): void {\n const source = this.tokens.get(key);\n if (source) {\n source.cancel(reason ?? `Request ${key} cancelled`);\n this.tokens.delete(key);\n }\n }\n\n cancelAll(): void {\n for (const [key, source] of this.tokens) {\n source.cancel(`Request ${key} cancelled`);\n }\n this.tokens.clear();\n }\n\n delete(key: string): void {\n this.tokens.delete(key);\n }\n\n has(key: string): boolean {\n return this.tokens.has(key);\n }\n\n get size(): number {\n return this.tokens.size;\n }\n}\n","import axios from 'axios';\nimport type { AxiosInstance, CancelToken } from 'axios';\nimport { ok, fail } from '@backendkit-labs/result';\nimport type { Result } from '@backendkit-labs/result';\nimport { CircuitBreaker, CircuitBreakerOpenError, DEFAULT_CIRCUIT_BREAKER_CONFIG, isHttpServerError } from '@backendkit-labs/circuit-breaker';\nimport type { CircuitBreakerConfig } from '@backendkit-labs/circuit-breaker';\nimport { Pipeline } from '@backendkit-labs/pipeline';\nimport { CancelManager } from './cancel-manager.js';\nimport type {\n HttpClientConfig,\n HttpClientError,\n HttpCtx,\n HttpMetrics,\n HttpResponse,\n RequestConfig,\n RetryConfig,\n} from './types.js';\n\nconst DEFAULT_RETRY: Required<RetryConfig> = {\n attempts: 0,\n delayMs: 100,\n maxDelayMs: 5_000,\n jitter: true,\n shouldRetry: (e) =>\n e.type === 'network' || e.type === 'timeout' || (e.type === 'http' && (e.status ?? 0) >= 500),\n};\n\nexport class HttpClient {\n private readonly axiosInstance: AxiosInstance;\n private readonly cb: CircuitBreaker | undefined;\n private readonly pipeline: Pipeline<HttpCtx, HttpClientError>;\n private readonly retry: Required<RetryConfig>;\n private readonly cancelMgr = new CancelManager();\n private readonly _metrics: HttpMetrics = {\n requests: 0, success: 0, failed: 0, cancelled: 0, circuitOpen: 0, retried: 0,\n };\n\n constructor(config: HttpClientConfig = {}) {\n this.axiosInstance = axios.create({\n baseURL: config.baseURL,\n timeout: config.timeout ?? 10_000,\n headers: config.headers,\n });\n\n if (config.circuitBreaker) {\n const { name: cbName, ...cbRest } = config.circuitBreaker;\n this.cb = new CircuitBreaker({\n name: cbName ?? 'http-client',\n ...DEFAULT_CIRCUIT_BREAKER_CONFIG,\n isFailure: isHttpServerError,\n ...cbRest,\n } as CircuitBreakerConfig);\n }\n\n const p = new Pipeline<HttpCtx, HttpClientError>();\n for (const step of config.steps ?? []) p.pipe(step);\n this.pipeline = p;\n\n this.retry = config.retry\n ? { ...DEFAULT_RETRY, ...config.retry }\n : DEFAULT_RETRY;\n }\n\n // ── HTTP methods ──────────────────────────────────────────────────────────\n\n get<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>> {\n return this.execute<T>('GET', url, undefined, config);\n }\n\n post<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>> {\n return this.execute<T>('POST', url, data, config);\n }\n\n put<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>> {\n return this.execute<T>('PUT', url, data, config);\n }\n\n patch<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>> {\n return this.execute<T>('PATCH', url, data, config);\n }\n\n delete<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>> {\n return this.execute<T>('DELETE', url, undefined, config);\n }\n\n // ── Cancellation ──────────────────────────────────────────────────────────\n\n cancelRequest(key: string): void {\n this.cancelMgr.cancel(key);\n this._metrics.cancelled++;\n }\n\n cancelAll(): void {\n const count = this.cancelMgr.size;\n this.cancelMgr.cancelAll();\n this._metrics.cancelled += count;\n }\n\n // ── Observability ─────────────────────────────────────────────────────────\n\n getMetrics(): Readonly<HttpMetrics> {\n return { ...this._metrics };\n }\n\n getCircuitBreakerState() {\n return this.cb?.getState();\n }\n\n getCircuitBreakerMetrics() {\n return this.cb?.getMetrics();\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private async execute<T>(\n method: string,\n url: string,\n data: unknown,\n config: RequestConfig = {},\n ): Promise<Result<HttpResponse<T>, HttpClientError>> {\n this._metrics.requests++;\n\n const initialCtx: HttpCtx = {\n url,\n method,\n data,\n headers: config.headers ?? {},\n params: config.params,\n timeout: config.timeout,\n cancelKey: config.cancelKey,\n correlationId: config.correlationId,\n };\n\n // Pre-register cancel token synchronously so callers can cancel before the request fires\n if (initialCtx.cancelKey) {\n this.cancelMgr.getOrCreate(initialCtx.cancelKey);\n }\n\n // Pre-request middleware pipeline\n const pipelineResult = await this.pipeline.run(initialCtx);\n if (!pipelineResult.ok) {\n this._metrics.failed++;\n return fail(pipelineResult.error.cause);\n }\n\n const ctx = pipelineResult.value;\n\n // If the cancel key was cancelled during the pipeline, bail out immediately\n if (ctx.cancelKey && !this.cancelMgr.has(ctx.cancelKey)) {\n this._metrics.failed++;\n return fail({ type: 'cancelled', message: `Request ${ctx.cancelKey} cancelled` });\n }\n\n const cancelToken = ctx.cancelKey\n ? this.cancelMgr.getOrCreate(ctx.cancelKey).token\n : undefined;\n\n // Raw axios call — throws on any error\n const rawCall = () => this.callAxios<T>(ctx, cancelToken);\n\n // Circuit breaker wraps the raw call — throws CircuitBreakerOpenError when open\n const cbCall = async (): Promise<HttpResponse<T>> => {\n if (!this.cb) return rawCall();\n try {\n return await this.cb.execute(rawCall);\n } catch (e) {\n if (e instanceof CircuitBreakerOpenError) {\n this._metrics.circuitOpen++;\n const err: HttpClientError = { type: 'circuit-open', message: e.message };\n throw err;\n }\n throw e;\n }\n };\n\n try {\n const response = this.retry.attempts > 0\n ? await this.withRetry(cbCall, this.retry)\n : await cbCall();\n\n if (ctx.cancelKey) this.cancelMgr.delete(ctx.cancelKey);\n this._metrics.success++;\n return ok(response);\n } catch (e) {\n if (ctx.cancelKey) this.cancelMgr.delete(ctx.cancelKey);\n const error = this.isHttpClientError(e) ? e : this.normalizeError(e);\n if (error.type !== 'circuit-open') this._metrics.failed++;\n return fail(error);\n }\n }\n\n private async callAxios<T>(ctx: HttpCtx, cancelToken?: CancelToken): Promise<HttpResponse<T>> {\n const response = await this.axiosInstance.request<T>({\n url: ctx.url,\n method: ctx.method,\n data: ctx.data,\n headers: ctx.headers,\n params: ctx.params,\n timeout: ctx.timeout,\n cancelToken,\n });\n\n return {\n data: response.data,\n status: response.status,\n headers: response.headers as Record<string, string>,\n };\n }\n\n private async withRetry<T>(\n fn: () => Promise<T>,\n config: Required<RetryConfig>,\n ): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= config.attempts; attempt++) {\n try {\n return await fn();\n } catch (e) {\n lastError = e;\n\n if (attempt === config.attempts) break;\n\n const error = this.isHttpClientError(e) ? e : this.normalizeError(e);\n if (!config.shouldRetry(error, attempt)) break;\n\n this._metrics.retried++;\n\n const baseDelay = config.delayMs * Math.pow(2, attempt);\n const jitter = config.jitter ? Math.random() * config.delayMs : 0;\n const delay = Math.min(baseDelay + jitter, config.maxDelayMs);\n\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n throw lastError;\n }\n\n private normalizeError(error: unknown): HttpClientError {\n if (axios.isCancel(error)) {\n return {\n type: 'cancelled',\n message: (error as { message?: string }).message ?? 'Request cancelled',\n };\n }\n\n if (axios.isAxiosError(error)) {\n const code = error.code;\n if (code === 'ECONNABORTED' || code === 'ETIMEDOUT') {\n return { type: 'timeout', message: error.message, cause: error };\n }\n if (!error.response) {\n return { type: 'network', message: error.message, cause: error };\n }\n return {\n type: 'http',\n message: error.message,\n status: error.response.status,\n data: error.response.data,\n cause: error,\n };\n }\n\n return { type: 'network', message: 'Unexpected error', cause: error };\n }\n\n private isHttpClientError(e: unknown): e is HttpClientError {\n return (\n typeof e === 'object' &&\n e !== null &&\n 'type' in e &&\n 'message' in e &&\n typeof (e as HttpClientError).type === 'string'\n );\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ var chunkGZWQJKYR_cjs = require('./chunk-GZWQJKYR.cjs');
4
+
5
+ // src/core/types.ts
6
+ var HttpClientToken = class {
7
+ description;
8
+ symbol;
9
+ constructor(name) {
10
+ this.description = `HttpClient(${name})`;
11
+ this.symbol = /* @__PURE__ */ Symbol(`HttpClient(${name})`);
12
+ }
13
+ };
14
+ function defineHttpClient(name) {
15
+ return new HttpClientToken(name);
16
+ }
17
+
18
+ Object.defineProperty(exports, "CancelManager", {
19
+ enumerable: true,
20
+ get: function () { return chunkGZWQJKYR_cjs.CancelManager; }
21
+ });
22
+ Object.defineProperty(exports, "HttpClient", {
23
+ enumerable: true,
24
+ get: function () { return chunkGZWQJKYR_cjs.HttpClient; }
25
+ });
26
+ exports.HttpClientToken = HttpClientToken;
27
+ exports.defineHttpClient = defineHttpClient;
28
+ //# sourceMappingURL=index.cjs.map
29
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/types.ts"],"names":[],"mappings":";;;;;AAwEO,IAAM,kBAAN,MAAsB;AAAA,EAClB,WAAA;AAAA,EACA,MAAA;AAAA,EAIT,YAAY,IAAA,EAAc;AACxB,IAAA,IAAA,CAAK,WAAA,GAAc,cAAc,IAAI,CAAA,CAAA,CAAA;AACrC,IAAA,IAAA,CAAK,MAAA,mBAAc,MAAA,CAAO,CAAA,WAAA,EAAc,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EACjD;AACF;AAEO,SAAS,iBAAiB,IAAA,EAA+B;AAC9D,EAAA,OAAO,IAAI,gBAAgB,IAAI,CAAA;AACjC","file":"index.cjs","sourcesContent":["import type { PipelineStep } from '@backendkit-labs/pipeline';\nimport type { CircuitBreakerOptions, CircuitBreakerState, CircuitBreakerMetrics } from '@backendkit-labs/circuit-breaker';\n\nexport type { CircuitBreakerState, CircuitBreakerMetrics };\n\nexport type HttpErrorType = 'http' | 'network' | 'cancelled' | 'circuit-open' | 'timeout';\n\nexport interface HttpClientError {\n type: HttpErrorType;\n message: string;\n status?: number;\n data?: unknown;\n cause?: unknown;\n}\n\nexport interface HttpResponse<T = unknown> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\n/** Mutable context passed through the pre-request pipeline steps. */\nexport interface HttpCtx {\n url: string;\n method: string;\n data?: unknown;\n headers: Record<string, string>;\n params?: Record<string, unknown>;\n timeout?: number;\n cancelKey?: string;\n correlationId?: string;\n}\n\nexport interface RequestConfig {\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n timeout?: number;\n /** Key used to identify and cancel this request programmatically. */\n cancelKey?: string;\n correlationId?: string;\n}\n\nexport interface RetryConfig {\n /** Number of retry attempts after the first failure. */\n attempts: number;\n delayMs?: number;\n maxDelayMs?: number;\n jitter?: boolean;\n /** Return false to stop retrying on a specific error. */\n shouldRetry?: (error: HttpClientError, attempt: number) => boolean;\n}\n\nexport interface HttpClientConfig {\n baseURL?: string;\n timeout?: number;\n headers?: Record<string, string>;\n circuitBreaker?: CircuitBreakerOptions;\n retry?: RetryConfig;\n /** Pre-request pipeline steps. Receive and transform the HttpCtx before the call is made. */\n steps?: PipelineStep<HttpCtx, HttpClientError>[];\n}\n\nexport interface HttpMetrics {\n requests: number;\n success: number;\n failed: number;\n cancelled: number;\n circuitOpen: number;\n retried: number;\n}\n\n/** Typed injection token for named HTTP clients. */\nexport class HttpClientToken {\n readonly description: string;\n readonly symbol: symbol;\n\n declare readonly _phantom: never;\n\n constructor(name: string) {\n this.description = `HttpClient(${name})`;\n this.symbol = Symbol(`HttpClient(${name})`);\n }\n}\n\nexport function defineHttpClient(name: string): HttpClientToken {\n return new HttpClientToken(name);\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import * as _backendkit_labs_circuit_breaker from '@backendkit-labs/circuit-breaker';
2
+ export { CircuitBreakerMetrics, CircuitBreakerState } from '@backendkit-labs/circuit-breaker';
3
+ import { Result } from '@backendkit-labs/result';
4
+ import { H as HttpClientConfig, R as RequestConfig, a as HttpResponse, b as HttpClientError, c as HttpMetrics } from './types-x0zPV-KD.cjs';
5
+ export { d as HttpClientToken, e as HttpCtx, f as HttpErrorType, g as RetryConfig, h as defineHttpClient } from './types-x0zPV-KD.cjs';
6
+ import { CancelTokenSource } from 'axios';
7
+ import '@backendkit-labs/pipeline';
8
+
9
+ declare class HttpClient {
10
+ private readonly axiosInstance;
11
+ private readonly cb;
12
+ private readonly pipeline;
13
+ private readonly retry;
14
+ private readonly cancelMgr;
15
+ private readonly _metrics;
16
+ constructor(config?: HttpClientConfig);
17
+ get<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
18
+ post<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
19
+ put<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
20
+ patch<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
21
+ delete<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
22
+ cancelRequest(key: string): void;
23
+ cancelAll(): void;
24
+ getMetrics(): Readonly<HttpMetrics>;
25
+ getCircuitBreakerState(): _backendkit_labs_circuit_breaker.CircuitBreakerState | undefined;
26
+ getCircuitBreakerMetrics(): _backendkit_labs_circuit_breaker.CircuitBreakerMetrics | undefined;
27
+ private execute;
28
+ private callAxios;
29
+ private withRetry;
30
+ private normalizeError;
31
+ private isHttpClientError;
32
+ }
33
+
34
+ declare class CancelManager {
35
+ private readonly tokens;
36
+ getOrCreate(key: string): CancelTokenSource;
37
+ cancel(key: string, reason?: string): void;
38
+ cancelAll(): void;
39
+ delete(key: string): void;
40
+ has(key: string): boolean;
41
+ get size(): number;
42
+ }
43
+
44
+ export { CancelManager, HttpClient, HttpClientConfig, HttpClientError, HttpMetrics, HttpResponse, RequestConfig };
@@ -0,0 +1,44 @@
1
+ import * as _backendkit_labs_circuit_breaker from '@backendkit-labs/circuit-breaker';
2
+ export { CircuitBreakerMetrics, CircuitBreakerState } from '@backendkit-labs/circuit-breaker';
3
+ import { Result } from '@backendkit-labs/result';
4
+ import { H as HttpClientConfig, R as RequestConfig, a as HttpResponse, b as HttpClientError, c as HttpMetrics } from './types-x0zPV-KD.js';
5
+ export { d as HttpClientToken, e as HttpCtx, f as HttpErrorType, g as RetryConfig, h as defineHttpClient } from './types-x0zPV-KD.js';
6
+ import { CancelTokenSource } from 'axios';
7
+ import '@backendkit-labs/pipeline';
8
+
9
+ declare class HttpClient {
10
+ private readonly axiosInstance;
11
+ private readonly cb;
12
+ private readonly pipeline;
13
+ private readonly retry;
14
+ private readonly cancelMgr;
15
+ private readonly _metrics;
16
+ constructor(config?: HttpClientConfig);
17
+ get<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
18
+ post<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
19
+ put<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
20
+ patch<T>(url: string, data?: unknown, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
21
+ delete<T>(url: string, config?: RequestConfig): Promise<Result<HttpResponse<T>, HttpClientError>>;
22
+ cancelRequest(key: string): void;
23
+ cancelAll(): void;
24
+ getMetrics(): Readonly<HttpMetrics>;
25
+ getCircuitBreakerState(): _backendkit_labs_circuit_breaker.CircuitBreakerState | undefined;
26
+ getCircuitBreakerMetrics(): _backendkit_labs_circuit_breaker.CircuitBreakerMetrics | undefined;
27
+ private execute;
28
+ private callAxios;
29
+ private withRetry;
30
+ private normalizeError;
31
+ private isHttpClientError;
32
+ }
33
+
34
+ declare class CancelManager {
35
+ private readonly tokens;
36
+ getOrCreate(key: string): CancelTokenSource;
37
+ cancel(key: string, reason?: string): void;
38
+ cancelAll(): void;
39
+ delete(key: string): void;
40
+ has(key: string): boolean;
41
+ get size(): number;
42
+ }
43
+
44
+ export { CancelManager, HttpClient, HttpClientConfig, HttpClientError, HttpMetrics, HttpResponse, RequestConfig };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { CancelManager, HttpClient } from './chunk-5RHNUUOY.js';
2
+
3
+ // src/core/types.ts
4
+ var HttpClientToken = class {
5
+ description;
6
+ symbol;
7
+ constructor(name) {
8
+ this.description = `HttpClient(${name})`;
9
+ this.symbol = /* @__PURE__ */ Symbol(`HttpClient(${name})`);
10
+ }
11
+ };
12
+ function defineHttpClient(name) {
13
+ return new HttpClientToken(name);
14
+ }
15
+
16
+ export { HttpClientToken, defineHttpClient };
17
+ //# sourceMappingURL=index.js.map
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/types.ts"],"names":[],"mappings":";;;AAwEO,IAAM,kBAAN,MAAsB;AAAA,EAClB,WAAA;AAAA,EACA,MAAA;AAAA,EAIT,YAAY,IAAA,EAAc;AACxB,IAAA,IAAA,CAAK,WAAA,GAAc,cAAc,IAAI,CAAA,CAAA,CAAA;AACrC,IAAA,IAAA,CAAK,MAAA,mBAAc,MAAA,CAAO,CAAA,WAAA,EAAc,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EACjD;AACF;AAEO,SAAS,iBAAiB,IAAA,EAA+B;AAC9D,EAAA,OAAO,IAAI,gBAAgB,IAAI,CAAA;AACjC","file":"index.js","sourcesContent":["import type { PipelineStep } from '@backendkit-labs/pipeline';\nimport type { CircuitBreakerOptions, CircuitBreakerState, CircuitBreakerMetrics } from '@backendkit-labs/circuit-breaker';\n\nexport type { CircuitBreakerState, CircuitBreakerMetrics };\n\nexport type HttpErrorType = 'http' | 'network' | 'cancelled' | 'circuit-open' | 'timeout';\n\nexport interface HttpClientError {\n type: HttpErrorType;\n message: string;\n status?: number;\n data?: unknown;\n cause?: unknown;\n}\n\nexport interface HttpResponse<T = unknown> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\n/** Mutable context passed through the pre-request pipeline steps. */\nexport interface HttpCtx {\n url: string;\n method: string;\n data?: unknown;\n headers: Record<string, string>;\n params?: Record<string, unknown>;\n timeout?: number;\n cancelKey?: string;\n correlationId?: string;\n}\n\nexport interface RequestConfig {\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n timeout?: number;\n /** Key used to identify and cancel this request programmatically. */\n cancelKey?: string;\n correlationId?: string;\n}\n\nexport interface RetryConfig {\n /** Number of retry attempts after the first failure. */\n attempts: number;\n delayMs?: number;\n maxDelayMs?: number;\n jitter?: boolean;\n /** Return false to stop retrying on a specific error. */\n shouldRetry?: (error: HttpClientError, attempt: number) => boolean;\n}\n\nexport interface HttpClientConfig {\n baseURL?: string;\n timeout?: number;\n headers?: Record<string, string>;\n circuitBreaker?: CircuitBreakerOptions;\n retry?: RetryConfig;\n /** Pre-request pipeline steps. Receive and transform the HttpCtx before the call is made. */\n steps?: PipelineStep<HttpCtx, HttpClientError>[];\n}\n\nexport interface HttpMetrics {\n requests: number;\n success: number;\n failed: number;\n cancelled: number;\n circuitOpen: number;\n retried: number;\n}\n\n/** Typed injection token for named HTTP clients. */\nexport class HttpClientToken {\n readonly description: string;\n readonly symbol: symbol;\n\n declare readonly _phantom: never;\n\n constructor(name: string) {\n this.description = `HttpClient(${name})`;\n this.symbol = Symbol(`HttpClient(${name})`);\n }\n}\n\nexport function defineHttpClient(name: string): HttpClientToken {\n return new HttpClientToken(name);\n}\n"]}
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ var chunkGZWQJKYR_cjs = require('../chunk-GZWQJKYR.cjs');
4
+ var common = require('@nestjs/common');
5
+
6
+ var HTTP_CLIENT_MODULE_OPTIONS = "HTTP_CLIENT_MODULE_OPTIONS";
7
+ exports.HttpClientModule = class HttpClientModule {
8
+ static forRoot(options) {
9
+ const providers = options.clients.map(
10
+ ({ token, config }) => exports.HttpClientModule._clientProvider(token, config)
11
+ );
12
+ return {
13
+ module: exports.HttpClientModule,
14
+ providers,
15
+ exports: providers,
16
+ global: true
17
+ };
18
+ }
19
+ static forRootAsync(options) {
20
+ const asyncProvider = exports.HttpClientModule._asyncOptionsProvider(options);
21
+ const clientsProvider = {
22
+ provide: "HTTP_CLIENT_INSTANCES",
23
+ useFactory: (opts) => {
24
+ return opts.clients.map(({ token, config }) => ({
25
+ token,
26
+ instance: new chunkGZWQJKYR_cjs.HttpClient(config)
27
+ }));
28
+ },
29
+ inject: [HTTP_CLIENT_MODULE_OPTIONS]
30
+ };
31
+ const allProviders = [
32
+ asyncProvider,
33
+ clientsProvider
34
+ ];
35
+ return {
36
+ module: exports.HttpClientModule,
37
+ imports: options.imports ?? [],
38
+ providers: allProviders,
39
+ exports: allProviders,
40
+ global: true
41
+ };
42
+ }
43
+ static _clientProvider(token, config) {
44
+ return {
45
+ provide: token.symbol,
46
+ useFactory: () => new chunkGZWQJKYR_cjs.HttpClient(config)
47
+ };
48
+ }
49
+ static _asyncOptionsProvider(options) {
50
+ if (options.useFactory) {
51
+ return {
52
+ provide: HTTP_CLIENT_MODULE_OPTIONS,
53
+ useFactory: options.useFactory,
54
+ inject: options.inject ?? []
55
+ };
56
+ }
57
+ const cls = options.useExisting ?? options.useClass;
58
+ if (cls) {
59
+ return {
60
+ provide: HTTP_CLIENT_MODULE_OPTIONS,
61
+ useFactory: (factory) => factory.createHttpClientOptions(),
62
+ inject: [cls]
63
+ };
64
+ }
65
+ return {
66
+ provide: HTTP_CLIENT_MODULE_OPTIONS,
67
+ useValue: { clients: [] }
68
+ };
69
+ }
70
+ };
71
+ exports.HttpClientModule = chunkGZWQJKYR_cjs.__decorateClass([
72
+ common.Module({})
73
+ ], exports.HttpClientModule);
74
+ var InjectHttpClient = (token) => common.Inject(token.symbol);
75
+
76
+ exports.InjectHttpClient = InjectHttpClient;
77
+ //# sourceMappingURL=index.cjs.map
78
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/nestjs/http-client.module.ts","../../src/nestjs/http-client.decorator.ts"],"names":["HttpClientModule","HttpClient","__decorateClass","Module","Inject"],"mappings":";;;;;AAUA,IAAM,0BAAA,GAA6B,4BAAA;AAGtBA,2BAAN,sBAAA,CAAuB;AAAA,EAC5B,OAAO,QAAQ,OAAA,EAAiD;AAC9D,IAAA,MAAM,SAAA,GAAwB,QAAQ,OAAA,CAAQ,GAAA;AAAA,MAAI,CAAC,EAAE,KAAA,EAAO,MAAA,OAC1DA,wBAAA,CAAiB,eAAA,CAAgB,OAAO,MAAM;AAAA,KAChD;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAUA,wBAAA;AAAA,MACV,SAAA;AAAA,MACA,OAAA,EAAU,SAAA;AAAA,MACV,MAAA,EAAU;AAAA,KACZ;AAAA,EACF;AAAA,EAEA,OAAO,aAAa,OAAA,EAAsD;AACxE,IAAA,MAAM,aAAA,GAAgBA,wBAAA,CAAiB,qBAAA,CAAsB,OAAO,CAAA;AAEpE,IAAA,MAAM,eAAA,GAA4B;AAAA,MAChC,OAAA,EAAY,uBAAA;AAAA,MACZ,UAAA,EAAY,CAAC,IAAA,KAAkC;AAC7C,QAAA,OAAO,KAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAE,KAAA,EAAO,QAAO,MAAO;AAAA,UAC9C,KAAA;AAAA,UACA,QAAA,EAAU,IAAIC,4BAAA,CAAW,MAAM;AAAA,SACjC,CAAE,CAAA;AAAA,MACJ,CAAA;AAAA,MACA,MAAA,EAAQ,CAAC,0BAA0B;AAAA,KACrC;AAEA,IAAA,MAAM,YAAA,GAA2B;AAAA,MAC/B,aAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAUD,wBAAA;AAAA,MACV,OAAA,EAAU,OAAA,CAAQ,OAAA,IAAW,EAAC;AAAA,MAC9B,SAAA,EAAW,YAAA;AAAA,MACX,OAAA,EAAU,YAAA;AAAA,MACV,MAAA,EAAU;AAAA,KACZ;AAAA,EACF;AAAA,EAEA,OAAe,eAAA,CAAgB,KAAA,EAAwB,MAAA,EAAoC;AACzF,IAAA,OAAO;AAAA,MACL,SAAY,KAAA,CAAM,MAAA;AAAA,MAClB,UAAA,EAAY,MAAM,IAAIC,4BAAA,CAAW,MAAM;AAAA,KACzC;AAAA,EACF;AAAA,EAEA,OAAe,sBAAsB,OAAA,EAAiD;AACpF,IAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,MAAA,OAAO;AAAA,QACL,OAAA,EAAY,0BAAA;AAAA,QACZ,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,MAAA,EAAa,OAAA,CAAQ,MAAA,IAAU;AAAC,OAClC;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,WAAA,IAAe,OAAA,CAAQ,QAAA;AAC3C,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,OAAO;AAAA,QACL,OAAA,EAAY,0BAAA;AAAA,QACZ,UAAA,EAAY,CAAC,OAAA,KAAsC,OAAA,CAAQ,uBAAA,EAAwB;AAAA,QACnF,MAAA,EAAY,CAAC,GAAqC;AAAA,OACpD;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,OAAA,EAAU,0BAAA;AAAA,MACV,QAAA,EAAU,EAAE,OAAA,EAAS,EAAC;AAAE,KAC1B;AAAA,EACF;AACF;AAxEaD,wBAAA,GAANE,iCAAA,CAAA;AAAA,EADNC,aAAA,CAAO,EAAE;AAAA,CAAA,EACGH,wBAAA,CAAA;ACVN,IAAM,gBAAA,GAAmB,CAC9B,KAAA,KAC8BI,aAAA,CAAO,MAAM,MAAM","file":"index.cjs","sourcesContent":["import { Module, DynamicModule, Provider } from '@nestjs/common';\nimport type { Type, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';\nimport { HttpClient } from '../core/http-client.js';\nimport type { HttpClientConfig, HttpClientToken } from '../core/types.js';\nimport type {\n HttpClientModuleOptions,\n HttpClientModuleAsyncOptions,\n HttpClientOptionsFactory,\n} from './http-client.options.js';\n\nconst HTTP_CLIENT_MODULE_OPTIONS = 'HTTP_CLIENT_MODULE_OPTIONS';\n\n@Module({})\nexport class HttpClientModule {\n static forRoot(options: HttpClientModuleOptions): DynamicModule {\n const providers: Provider[] = options.clients.map(({ token, config }) =>\n HttpClientModule._clientProvider(token, config),\n );\n\n return {\n module: HttpClientModule,\n providers,\n exports: providers,\n global: true,\n };\n }\n\n static forRootAsync(options: HttpClientModuleAsyncOptions): DynamicModule {\n const asyncProvider = HttpClientModule._asyncOptionsProvider(options);\n\n const clientsProvider: Provider = {\n provide: 'HTTP_CLIENT_INSTANCES',\n useFactory: (opts: HttpClientModuleOptions) => {\n return opts.clients.map(({ token, config }) => ({\n token,\n instance: new HttpClient(config),\n }));\n },\n inject: [HTTP_CLIENT_MODULE_OPTIONS],\n };\n\n const allProviders: Provider[] = [\n asyncProvider,\n clientsProvider,\n ];\n\n return {\n module: HttpClientModule,\n imports: options.imports ?? [],\n providers: allProviders,\n exports: allProviders,\n global: true,\n };\n }\n\n private static _clientProvider(token: HttpClientToken, config: HttpClientConfig): Provider {\n return {\n provide: token.symbol,\n useFactory: () => new HttpClient(config),\n };\n }\n\n private static _asyncOptionsProvider(options: HttpClientModuleAsyncOptions): Provider {\n if (options.useFactory) {\n return {\n provide: HTTP_CLIENT_MODULE_OPTIONS,\n useFactory: options.useFactory as (...args: unknown[]) => HttpClientModuleOptions | Promise<HttpClientModuleOptions>,\n inject: (options.inject ?? []) as (InjectionToken | OptionalFactoryDependency)[],\n };\n }\n\n const cls = options.useExisting ?? options.useClass;\n if (cls) {\n return {\n provide: HTTP_CLIENT_MODULE_OPTIONS,\n useFactory: (factory: HttpClientOptionsFactory) => factory.createHttpClientOptions(),\n inject: [cls as Type<HttpClientOptionsFactory>],\n };\n }\n\n return {\n provide: HTTP_CLIENT_MODULE_OPTIONS,\n useValue: { clients: [] } satisfies HttpClientModuleOptions,\n };\n }\n}\n","import { Inject } from '@nestjs/common';\nimport type { HttpClientToken } from '../core/types.js';\n\nexport const InjectHttpClient = (\n token: HttpClientToken,\n): ReturnType<typeof Inject> => Inject(token.symbol);\n"]}
@@ -0,0 +1,32 @@
1
+ import { ModuleMetadata, Type, InjectionToken, OptionalFactoryDependency, DynamicModule, Inject } from '@nestjs/common';
2
+ import { d as HttpClientToken, H as HttpClientConfig } from '../types-x0zPV-KD.cjs';
3
+ import '@backendkit-labs/pipeline';
4
+ import '@backendkit-labs/circuit-breaker';
5
+
6
+ interface HttpClientDefinition {
7
+ token: HttpClientToken;
8
+ config: HttpClientConfig;
9
+ }
10
+ interface HttpClientModuleOptions {
11
+ clients: HttpClientDefinition[];
12
+ }
13
+ interface HttpClientOptionsFactory {
14
+ createHttpClientOptions(): Promise<HttpClientModuleOptions> | HttpClientModuleOptions;
15
+ }
16
+ interface HttpClientModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
17
+ useFactory?: (...args: unknown[]) => Promise<HttpClientModuleOptions> | HttpClientModuleOptions;
18
+ useClass?: Type<HttpClientOptionsFactory>;
19
+ useExisting?: Type<HttpClientOptionsFactory>;
20
+ inject?: (InjectionToken | OptionalFactoryDependency)[];
21
+ }
22
+
23
+ declare class HttpClientModule {
24
+ static forRoot(options: HttpClientModuleOptions): DynamicModule;
25
+ static forRootAsync(options: HttpClientModuleAsyncOptions): DynamicModule;
26
+ private static _clientProvider;
27
+ private static _asyncOptionsProvider;
28
+ }
29
+
30
+ declare const InjectHttpClient: (token: HttpClientToken) => ReturnType<typeof Inject>;
31
+
32
+ export { type HttpClientDefinition, HttpClientModule, type HttpClientModuleAsyncOptions, type HttpClientModuleOptions, type HttpClientOptionsFactory, InjectHttpClient };
@@ -0,0 +1,32 @@
1
+ import { ModuleMetadata, Type, InjectionToken, OptionalFactoryDependency, DynamicModule, Inject } from '@nestjs/common';
2
+ import { d as HttpClientToken, H as HttpClientConfig } from '../types-x0zPV-KD.js';
3
+ import '@backendkit-labs/pipeline';
4
+ import '@backendkit-labs/circuit-breaker';
5
+
6
+ interface HttpClientDefinition {
7
+ token: HttpClientToken;
8
+ config: HttpClientConfig;
9
+ }
10
+ interface HttpClientModuleOptions {
11
+ clients: HttpClientDefinition[];
12
+ }
13
+ interface HttpClientOptionsFactory {
14
+ createHttpClientOptions(): Promise<HttpClientModuleOptions> | HttpClientModuleOptions;
15
+ }
16
+ interface HttpClientModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
17
+ useFactory?: (...args: unknown[]) => Promise<HttpClientModuleOptions> | HttpClientModuleOptions;
18
+ useClass?: Type<HttpClientOptionsFactory>;
19
+ useExisting?: Type<HttpClientOptionsFactory>;
20
+ inject?: (InjectionToken | OptionalFactoryDependency)[];
21
+ }
22
+
23
+ declare class HttpClientModule {
24
+ static forRoot(options: HttpClientModuleOptions): DynamicModule;
25
+ static forRootAsync(options: HttpClientModuleAsyncOptions): DynamicModule;
26
+ private static _clientProvider;
27
+ private static _asyncOptionsProvider;
28
+ }
29
+
30
+ declare const InjectHttpClient: (token: HttpClientToken) => ReturnType<typeof Inject>;
31
+
32
+ export { type HttpClientDefinition, HttpClientModule, type HttpClientModuleAsyncOptions, type HttpClientModuleOptions, type HttpClientOptionsFactory, InjectHttpClient };