@hyperttp/core 1.3.3 → 1.4.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.
Files changed (46) hide show
  1. package/Core/HyperCore.d.ts +142 -152
  2. package/Core/HyperCore.d.ts.map +1 -1
  3. package/Core/HyperCore.js +289 -286
  4. package/Core/HyperCore.js.map +1 -1
  5. package/README.md +279 -134
  6. package/package.json +14 -8
  7. package/transports/browser.d.ts +228 -0
  8. package/transports/browser.d.ts.map +1 -0
  9. package/transports/browser.js +451 -0
  10. package/transports/browser.js.map +1 -0
  11. package/transports/manager.d.ts +7 -67
  12. package/transports/manager.d.ts.map +1 -1
  13. package/transports/manager.js +138 -145
  14. package/transports/manager.js.map +1 -1
  15. package/transports/node.d.ts +0 -20
  16. package/transports/node.d.ts.map +1 -1
  17. package/transports/node.js +104 -48
  18. package/transports/node.js.map +1 -1
  19. package/utils/decompress.d.ts +16 -18
  20. package/utils/decompress.d.ts.map +1 -1
  21. package/utils/decompress.js +143 -63
  22. package/utils/decompress.js.map +1 -1
  23. package/utils/errors.d.ts +0 -10
  24. package/utils/errors.d.ts.map +1 -1
  25. package/utils/errors.js +1 -11
  26. package/utils/errors.js.map +1 -1
  27. package/utils/merge.d.ts +0 -3
  28. package/utils/merge.d.ts.map +1 -1
  29. package/utils/merge.js +0 -4
  30. package/utils/merge.js.map +1 -1
  31. package/utils/normalize.d.ts +40 -20
  32. package/utils/normalize.d.ts.map +1 -1
  33. package/utils/normalize.js +197 -63
  34. package/utils/normalize.js.map +1 -1
  35. package/utils/pipeline.d.ts +10 -30
  36. package/utils/pipeline.d.ts.map +1 -1
  37. package/utils/pipeline.js +44 -44
  38. package/utils/pipeline.js.map +1 -1
  39. package/utils/response.d.ts +35 -44
  40. package/utils/response.d.ts.map +1 -1
  41. package/utils/response.js +228 -69
  42. package/utils/response.js.map +1 -1
  43. package/utils/retryUtils.d.ts +2 -10
  44. package/utils/retryUtils.d.ts.map +1 -1
  45. package/utils/retryUtils.js +17 -21
  46. package/utils/retryUtils.js.map +1 -1
package/Core/HyperCore.js CHANGED
@@ -1,30 +1,45 @@
1
1
  import { defaultConfig } from "../defaultConfig.js";
2
- import { mapResponseFast, mapStreamFast, mergeHeadersFast, } from "../utils/response.js";
2
+ import { mapResponseFast, mapStreamFast, mergeHeadersFast } from "../utils/response.js";
3
3
  import { TransportManager } from "../transports/manager.js";
4
4
  import { createPipelines, executeErrorPipeline, executeRequestPipeline, executeResponsePipeline, executeResponseDataPipeline, insertHookSorted, } from "../utils/pipeline.js";
5
- import { normalizeBody, normalizeHeaders, normalizeUrl, } from "../utils/normalize.js";
6
- import { decompressBuffer, createDecompressStream, } from "../utils/decompress.js";
7
- const urlCache = new Map();
5
+ import { normalizeBody, normalizeHeaders, normalizeUrl } from "../utils/normalize.js";
8
6
  /**
9
- * @ru Основной класс HTTP-клиента с поддержкой плагинов, потоковой передачи, кэширования URL и гибкой настройки.
10
- * @en Core HTTP client class with plugin support, streaming, URL caching and flexible configuration.
11
- * @implements {IHyperCore}
7
+ * @ru Глобальный кэш URL для избежания повторного парсинга через `new URL()`.
8
+ * Использует `Object.create(null)` для быстрого доступа без прототипа.
9
+ * @en Global URL cache to avoid repeated parsing via `new URL()`.
10
+ * Uses `Object.create(null)` for fast prototype-less access.
11
+ */
12
+ const urlCache = Object.create(null);
13
+ let urlCacheCount = 0;
14
+ const MAX_CACHE_SIZE = 512;
15
+ /**
16
+ * @ru Пул переиспользуемых объектов InternalRequest для zero-allocation в горячем пути.
17
+ * @en Pool of reusable InternalRequest objects for zero-allocation in the hot path.
18
+ */
19
+ const requestPool = [];
20
+ const MAX_POOL_SIZE = 64;
21
+ /**
22
+ * @ru Ядро HTTP-клиента Hyperttp. Обеспечивает диспетчеризацию запросов через
23
+ * конвейер плагинов, пулинг объектов запросов и кэширование URL.
24
+ * @en Hyperttp HTTP client core. Provides request dispatching through the
25
+ * plugin pipeline, request object pooling, and URL caching.
12
26
  */
13
27
  export class HyperCore {
14
- /**
15
- * @ru Активная конфигурация клиента (объединена с настройками по умолчанию).
16
- * @en Current client configuration (merged with default settings).
17
- */
18
28
  config;
19
29
  transportManager;
30
+ transportReady;
20
31
  defaultHeaders;
21
32
  pluginCtx;
22
33
  pipelines = createPipelines();
34
+ hasRequestPlugins = false;
35
+ hasResponseDataPlugins = false;
36
+ hasResponsePlugins = false;
37
+ hasErrorPlugins = false;
23
38
  /**
24
- * @ru Создаёт экземпляр HyperCore.
25
- * @en Creates a HyperCore instance.
26
- * @param config - Client configuration (overrides defaults).
27
- * @param transport - Optional transport layer (auto‑selected if omitted).
39
+ * @ru Создаёт новый экземпляр ядра HTTP-клиента.
40
+ * @en Creates a new HTTP client core instance.
41
+ * @param config - Client configuration options.
42
+ * @param transport - Optional custom transport implementation.
28
43
  */
29
44
  constructor(config = defaultConfig, transport) {
30
45
  this.config = {
@@ -33,6 +48,9 @@ export class HyperCore {
33
48
  network: { ...defaultConfig.network, ...config.network },
34
49
  };
35
50
  this.transportManager = new TransportManager(this.config, transport);
51
+ this.transportReady = this.transportManager.getSync()
52
+ ? Promise.resolve(this.transportManager.getSync())
53
+ : this.transportManager.ensure();
36
54
  this.defaultHeaders = normalizeHeaders({
37
55
  Accept: "application/json, text/plain, */*",
38
56
  "Accept-Encoding": "gzip, deflate, br",
@@ -42,70 +60,60 @@ export class HyperCore {
42
60
  this.pluginCtx = { config: this.config, core: this };
43
61
  }
44
62
  /**
45
- * @ru Возвращает текущий транспортный экземпляр (или null, если он ещё не инициализирован).
46
- * @en Returns the current transport instance (or null if not yet initialized).
63
+ * @ru Возвращает имя класса текущего активного транспорта.
64
+ * @en Returns the class name of the currently active transport.
65
+ * @returns Promise resolving to the transport class name.
47
66
  */
48
- get transport() {
49
- return this.transportManager.instance;
50
- }
51
67
  async getTransportName() {
52
- const t = await this.ensureTransport();
68
+ const t = this.transportManager.transport ?? (await this.transportManager.get());
53
69
  return t.constructor.name;
54
70
  }
55
71
  /**
56
- * @ru Гарантирует наличие готового к работе транспорта (асинхронная инициализация при необходимости).
57
- * @en Ensures a ready‑to‑use transport (async initialization if needed).
58
- * @returns Promise resolving to a transport instance.
72
+ * @ru Отправляет внутренний запрос через полный конвейер обработки:
73
+ * плагины запроса → транспорт → плагины данных ответа → маппинг → плагины ответа.
74
+ * @en Dispatches an internal request through the full processing pipeline:
75
+ * request plugins → transport → response data plugins → mapping → response plugins.
76
+ * @template T - Expected response body type.
77
+ * @param req - The normalized internal request object.
78
+ * @returns Promise resolving to the HTTP response.
59
79
  */
60
- ensureTransport() {
61
- return this.transportManager.get();
62
- }
63
- /**
64
- * @ru Внутренний метод диспетчеризации запроса, обрабатывающий как обычные, так и стриминговые ответы.
65
- * @en Internal request dispatcher handling both regular and streaming responses.
66
- * @param req - Internal request object.
67
- * @returns Promise resolving to a response (HttpResponse or StreamResponse).
68
- */
69
- async dispatchInternal(req) {
80
+ async dispatch(req) {
70
81
  try {
71
- if (this.pipelines.request.length > 0) {
72
- const shortCircuit = await executeRequestPipeline(this.pipelines.request, req, this.pluginCtx);
73
- if (shortCircuit) {
74
- await this.runResponsePipeline(shortCircuit, req);
82
+ if (this.hasRequestPlugins) {
83
+ const shortCircuit = await Promise.resolve(executeRequestPipeline(this.pipelines.request, req, this.pluginCtx));
84
+ if (shortCircuit != null) {
85
+ this.recycleRequest(req);
75
86
  return shortCircuit;
76
87
  }
77
88
  }
78
- const transport = this.transport || (await this.ensureTransport());
89
+ const transport = this.transportManager.getSync() ?? (await this.transportReady);
90
+ const networkStart = performance.now();
79
91
  let rawResponse = await transport.execute(req);
80
- if (this.pipelines.responseData.length > 0) {
81
- rawResponse = await executeResponseDataPipeline(this.pipelines.responseData, rawResponse, this.pluginCtx);
92
+ const networkMs = performance.now() - networkStart;
93
+ const meta = (req.meta ??= {});
94
+ meta.timings = { ...meta.timings, networkMs };
95
+ if (this.hasResponseDataPlugins) {
96
+ rawResponse = await Promise.resolve(executeResponseDataPipeline(this.pipelines.responseData, rawResponse, this.pluginCtx));
82
97
  }
83
- this.applyDecompression(rawResponse);
84
- const isStream = req.meta?.responseType === "stream";
85
- const response = isStream
98
+ const response = req.meta?.responseType === "stream"
86
99
  ? mapStreamFast(rawResponse)
87
100
  : mapResponseFast(rawResponse);
88
- await this.runResponsePipeline(response, req);
101
+ if (this.hasResponsePlugins) {
102
+ await Promise.resolve(executeResponsePipeline(this.pipelines.responseMutators, this.pipelines.responseSideEffects, response, req, this.pluginCtx, this.config.logger));
103
+ }
104
+ this.recycleRequest(req);
89
105
  return response;
90
106
  }
91
107
  catch (error) {
108
+ this.recycleRequest(req);
92
109
  return this.handleDispatchError(error, req);
93
110
  }
94
111
  }
95
112
  /**
96
- * @ru Отправляет HTTP-запрос и возвращает обычный (нестриминговый) ответ.
97
- * @en Sends an HTTP request and returns a regular (non‑streaming) response.
98
- * @param req - Internal request object.
99
- * @returns Promise resolving to a typed HTTP response.
100
- */
101
- async dispatch(req) {
102
- return this.dispatchInternal(req);
103
- }
104
- /**
105
- * @ru Регистрирует плагин в клиенте. Плагин может добавлять хуки на различных этапах обработки запроса/ответа.
106
- * @en Registers a plugin with the client. The plugin can add hooks at various request/response processing stages.
107
- * @param plugin - Plugin instance to register.
108
- * @returns This instance for chaining.
113
+ * @ru Регистрирует плагин в экземпляре клиента. Плагины выполняются в порядке приоритета.
114
+ * @en Registers a plugin into the client instance. Plugins are executed in priority order.
115
+ * @param plugin - The plugin instance to register.
116
+ * @returns The current instance for chaining.
109
117
  */
110
118
  use(plugin) {
111
119
  const isEnabled = plugin.enabled ? plugin.enabled(this.config) : true;
@@ -113,350 +121,345 @@ export class HyperCore {
113
121
  return this;
114
122
  plugin.setup?.(this.pluginCtx);
115
123
  const priority = plugin.priority ?? 0;
124
+ const hook = { name: plugin.name, priority };
116
125
  if (plugin.onRequest) {
117
126
  insertHookSorted(this.pipelines.request, {
118
- name: plugin.name,
119
- priority,
127
+ ...hook,
120
128
  run: plugin.onRequest,
121
129
  });
130
+ this.hasRequestPlugins = true;
122
131
  }
123
132
  if (plugin.onResponse) {
124
133
  const target = plugin.mode === "background"
125
134
  ? this.pipelines.responseSideEffects
126
135
  : this.pipelines.responseMutators;
127
- insertHookSorted(target, {
128
- name: plugin.name,
129
- priority,
130
- run: plugin.onResponse,
131
- });
136
+ insertHookSorted(target, { ...hook, run: plugin.onResponse });
137
+ this.hasResponsePlugins = true;
132
138
  }
133
139
  if (plugin.onResponseData) {
134
140
  insertHookSorted(this.pipelines.responseData, {
135
- name: plugin.name,
136
- priority,
141
+ ...hook,
137
142
  run: plugin.onResponseData,
138
143
  });
144
+ this.hasResponseDataPlugins = true;
139
145
  }
140
146
  if (plugin.onError) {
141
- insertHookSorted(this.pipelines.error, {
142
- name: plugin.name,
143
- priority,
144
- run: plugin.onError,
145
- });
147
+ insertHookSorted(this.pipelines.error, { ...hook, run: plugin.onError });
148
+ this.hasErrorPlugins = true;
146
149
  }
147
150
  return this;
148
151
  }
149
- /**
150
- * @ru Выполняет GET-запрос с потоковым ответом.
151
- * @en Performs a GET request with a streaming response.
152
- * @param req - Request URL or configuration object.
153
- * @param signal - Optional AbortSignal to cancel the request.
154
- * @returns Promise resolving to a StreamResponse.
155
- */
156
- async stream(req, signal) {
157
- const internalReq = this.buildInternalRequest("GET", req, undefined, signal);
158
- internalReq.meta = { ...internalReq.meta, responseType: "stream" };
159
- return this.dispatchInternal(internalReq);
160
- }
161
- /**
162
- * @ru Выполняет POST-запрос с потоковым ответом.
163
- * @en Performs a POST request with a streaming response.
164
- * @param req - Request URL or configuration object.
165
- * @param body - Request body data.
166
- * @param signal - Optional AbortSignal to cancel the request.
167
- * @returns Promise resolving to a StreamResponse.
168
- */
169
- async postStream(req, body, signal) {
170
- const internalReq = this.buildInternalRequest("POST", req, body, signal);
171
- internalReq.meta = { ...internalReq.meta, responseType: "stream" };
172
- return this.dispatchInternal(internalReq);
173
- }
174
152
  /**
175
153
  * @ru Выполняет GET-запрос.
176
154
  * @en Performs a GET request.
177
- * @param req - Request URL or configuration object.
178
- * @param signal - Optional AbortSignal to cancel the request.
179
- * @returns Promise resolving to an HttpResponse.
155
+ * @template T - Expected response body type.
156
+ * @param req - Request URL or RequestInterface object.
157
+ * @param signal - Optional abort signal.
158
+ * @returns Promise resolving to the HTTP response.
180
159
  */
181
160
  get(req, signal) {
182
- return this.dispatch(this.buildInternalRequest("GET", req, undefined, signal));
161
+ return this.dispatch(this.acquireReq("GET", req, undefined, signal));
183
162
  }
184
163
  /**
185
- * @ru Выполняет POST-запрос.
186
- * @en Performs a POST request.
187
- * @param req - Request URL or configuration object.
164
+ * @ru Выполняет POST-запрос с телом.
165
+ * @en Performs a POST request with a body.
166
+ * @template T - Expected response body type.
167
+ * @param req - Request URL or RequestInterface object.
188
168
  * @param body - Request body data.
189
- * @param signal - Optional AbortSignal to cancel the request.
190
- * @returns Promise resolving to an HttpResponse.
169
+ * @param signal - Optional abort signal.
170
+ * @returns Promise resolving to the HTTP response.
191
171
  */
192
172
  post(req, body, signal) {
193
- return this.dispatch(this.buildInternalRequest("POST", req, body, signal));
173
+ return this.dispatch(this.acquireReq("POST", req, body, signal));
194
174
  }
195
175
  /**
196
- * @ru Выполняет PUT-запрос.
197
- * @en Performs a PUT request.
198
- * @param req - Request URL or configuration object.
176
+ * @ru Выполняет PUT-запрос с телом.
177
+ * @en Performs a PUT request with a body.
178
+ * @template T - Expected response body type.
179
+ * @param req - Request URL or RequestInterface object.
199
180
  * @param body - Request body data.
200
- * @param signal - Optional AbortSignal to cancel the request.
201
- * @returns Promise resolving to an HttpResponse.
181
+ * @param signal - Optional abort signal.
182
+ * @returns Promise resolving to the HTTP response.
202
183
  */
203
184
  put(req, body, signal) {
204
- return this.dispatch(this.buildInternalRequest("PUT", req, body, signal));
185
+ return this.dispatch(this.acquireReq("PUT", req, body, signal));
205
186
  }
206
187
  /**
207
- * @ru Выполняет PATCH-запрос.
208
- * @en Performs a PATCH request.
209
- * @param req - Request URL or configuration object.
188
+ * @ru Выполняет PATCH-запрос с телом.
189
+ * @en Performs a PATCH request with a body.
190
+ * @template T - Expected response body type.
191
+ * @param req - Request URL or RequestInterface object.
210
192
  * @param body - Request body data.
211
- * @param signal - Optional AbortSignal to cancel the request.
212
- * @returns Promise resolving to an HttpResponse.
193
+ * @param signal - Optional abort signal.
194
+ * @returns Promise resolving to the HTTP response.
213
195
  */
214
196
  patch(req, body, signal) {
215
- return this.dispatch(this.buildInternalRequest("PATCH", req, body, signal));
197
+ return this.dispatch(this.acquireReq("PATCH", req, body, signal));
216
198
  }
217
199
  /**
218
200
  * @ru Выполняет DELETE-запрос.
219
201
  * @en Performs a DELETE request.
220
- * @param req - Request URL or configuration object.
221
- * @param signal - Optional AbortSignal to cancel the request.
222
- * @returns Promise resolving to an HttpResponse.
202
+ * @template T - Expected response body type.
203
+ * @param req - Request URL or RequestInterface object.
204
+ * @param signal - Optional abort signal.
205
+ * @returns Promise resolving to the HTTP response.
223
206
  */
224
207
  delete(req, signal) {
225
- return this.dispatch(this.buildInternalRequest("DELETE", req, undefined, signal));
208
+ return this.dispatch(this.acquireReq("DELETE", req, undefined, signal));
226
209
  }
227
210
  /**
228
211
  * @ru Выполняет OPTIONS-запрос.
229
212
  * @en Performs an OPTIONS request.
230
- * @param req - Request URL or configuration object.
231
- * @param body - Request body data.
232
- * @param signal - Optional AbortSignal to cancel the request.
233
- * @returns Promise resolving to an HttpResponse.
213
+ * @template T - Expected response body type.
214
+ * @param req - Request URL or RequestInterface object.
215
+ * @param body - Optional request body data.
216
+ * @param signal - Optional abort signal.
217
+ * @returns Promise resolving to the HTTP response.
234
218
  */
235
219
  options(req, body, signal) {
236
- return this.dispatch(this.buildInternalRequest("OPTIONS", req, body, signal));
220
+ return this.dispatch(this.acquireReq("OPTIONS", req, body, signal));
237
221
  }
238
222
  /**
239
- * @ru Выполняет HEAD-запрос.
240
- * @en Performs a HEAD request.
241
- * @param req - Request URL or configuration object.
242
- * @param signal - Optional AbortSignal to cancel the request.
243
- * @returns Promise resolving to an HttpResponse with null body.
223
+ * @ru Выполняет HEAD-запрос (без тела ответа).
224
+ * @en Performs a HEAD request (no response body).
225
+ * @param req - Request URL or RequestInterface object.
226
+ * @param signal - Optional abort signal.
227
+ * @returns Promise resolving to the HTTP response with null body.
244
228
  */
245
229
  head(req, signal) {
246
- return this.dispatch(this.buildInternalRequest("HEAD", req, undefined, signal));
230
+ return this.dispatch(this.acquireReq("HEAD", req, undefined, signal));
247
231
  }
248
232
  /**
249
- * @ru Создаёт новый экземпляр HyperCore путём расширения текущей конфигурации.
250
- * @en Creates a new HyperCore instance by extending the current configuration.
251
- * @param options - Partial configuration overrides.
252
- * @returns New HyperCore instance.
233
+ * @ru Инициирует потоковый GET-запрос. Тело ответа возвращается как ReadableStream.
234
+ * @en Initiates a streaming GET request. Response body is returned as a ReadableStream.
235
+ * @param req - Request URL or RequestInterface object.
236
+ * @param signal - Optional abort signal.
237
+ * @returns Promise resolving to the stream response.
238
+ */
239
+ stream(req, signal) {
240
+ return this.dispatch(this.acquireReq("GET", req, undefined, signal, "stream"));
241
+ }
242
+ /**
243
+ * @ru Инициирует потоковый POST-запрос с телом. Тело ответа возвращается как ReadableStream.
244
+ * @en Initiates a streaming POST request with a body. Response body is returned as a ReadableStream.
245
+ * @template T - Expected response body type.
246
+ * @param req - Request URL or RequestInterface object.
247
+ * @param body - Request body data.
248
+ * @param signal - Optional abort signal.
249
+ * @returns Promise resolving to the stream response.
250
+ */
251
+ postStream(req, body, signal) {
252
+ return this.dispatch(this.acquireReq("POST", req, body, signal, "stream"));
253
+ }
254
+ /**
255
+ * @ru Создаёт новый экземпляр клиента, объединяя текущую конфигурацию с переданными опциями.
256
+ * @en Creates a new client instance by merging the current configuration with provided options.
257
+ * @param options - Partial configuration options to extend.
258
+ * @returns A new HyperCore instance.
253
259
  */
254
260
  extend(options) {
255
261
  return new HyperCore({
256
262
  ...this.config,
257
263
  ...options,
258
264
  network: { ...this.config.network, ...options.network },
259
- }, this.transport ?? undefined);
265
+ }, this.transportManager.transport ?? undefined);
260
266
  }
261
267
  /**
262
- * @ru Алиас для {@link extend}. Создаёт новый экземпляр с переопределёнными опциями.
263
- * @en Alias for {@link extend}. Creates a new instance with overridden options.
264
- * @param options - Partial configuration overrides.
265
- * @returns New HyperCore instance.
268
+ * @ru Создаёт полностью новый экземпляр клиента на основе переданных опций.
269
+ * @en Creates a completely new client instance based on provided options.
270
+ * @param options - Partial configuration options for the new instance.
271
+ * @returns A new HyperCore instance.
266
272
  */
267
273
  create(options) {
268
274
  return this.extend(options);
269
275
  }
270
276
  /**
271
- * @ru Завершает работу клиента: закрывает транспортные соединения.
272
- * @en Shuts down the client: closes transport connections.
273
- * @param graceful - If true, attempts graceful shutdown (waiting for pending requests).
277
+ * @ru Завершает работу клиента и освобождает ресурсы (соединения, пулы).
278
+ * @en Shuts down the client and releases resources (connections, pools).
279
+ * @param graceful - If true, waits for active requests to complete before closing.
274
280
  * @returns Promise that resolves when shutdown is complete.
275
281
  */
276
- async destroy(graceful = true) {
277
- await this.transportManager.destroy(graceful);
282
+ destroy(graceful = true) {
283
+ return this.transportManager.destroy(graceful);
278
284
  }
279
285
  /**
280
- * @ru Выполняет запрос и возвращает распарсенный JSON-ответ.
281
- * @en Performs a request and returns parsed JSON response.
282
- * @param req - Request URL or configuration object.
283
- * @param signal - Optional AbortSignal to cancel the request.
284
- * @returns Promise resolving to the parsed JSON value.
285
- * @throws If response body cannot be parsed as JSON.
286
+ * @ru Выполняет GET-запрос и возвращает распарсенное JSON-тело ответа.
287
+ * @en Performs a GET request and returns the parsed JSON response body.
288
+ * @template T - Expected type of the parsed JSON.
289
+ * @param req - Request URL or RequestInterface object.
290
+ * @param signal - Optional abort signal.
291
+ * @returns Promise resolving to the parsed JSON data.
286
292
  */
287
293
  async json(req, signal) {
288
- const res = await this.dispatchShortcut(req, signal);
289
- return res.json?.() ?? Promise.reject(new Error("json() not supported"));
294
+ return ((await this.shortcut(req, signal)).json?.() ??
295
+ Promise.reject(new Error("json() not supported")));
290
296
  }
291
297
  /**
292
- * @ru Выполняет запрос и возвращает ответ в виде текста.
293
- * @en Performs a request and returns response as text.
294
- * @param req - Request URL or configuration object.
295
- * @param signal - Optional AbortSignal to cancel the request.
296
- * @returns Promise resolving to the response body as string.
297
- * @throws If response body cannot be read as text.
298
+ * @ru Выполняет GET-запрос и возвращает тело ответа как текст.
299
+ * @en Performs a GET request and returns the response body as text.
300
+ * @param req - Request URL or RequestInterface object.
301
+ * @param signal - Optional abort signal.
302
+ * @returns Promise resolving to the response text.
298
303
  */
299
304
  async text(req, signal) {
300
- const res = await this.dispatchShortcut(req, signal);
301
- return res.text?.() ?? Promise.reject(new Error("text() not supported"));
305
+ return ((await this.shortcut(req, signal)).text?.() ??
306
+ Promise.reject(new Error("text() not supported")));
302
307
  }
303
308
  /**
304
- * @ru Выполняет запрос и полностью потребляет тело ответа (без обработки).
305
- * @en Performs a request and fully consumes the response body (no processing).
306
- * @param req - Request URL or configuration object.
307
- * @param signal - Optional AbortSignal to cancel the request.
308
- * @returns Promise that resolves when response body is drained.
309
+ * @ru Выполняет GET-запрос и немедленно отбрасывает тело ответа для освобождения ресурсов.
310
+ * @en Performs a GET request and immediately discards the response body to free resources.
311
+ * @param req - Request URL or RequestInterface object.
312
+ * @param signal - Optional abort signal.
313
+ * @returns Promise that resolves when the stream is drained.
309
314
  */
310
315
  async dump(req, signal) {
311
- const res = await this.dispatchShortcut(req, signal);
312
- await res.dump?.();
313
- }
314
- /**
315
- * @ru Выполняет пайплайн обработки ответа (мутаторы и сайд-эффекты).
316
- * @en Executes response processing pipeline (mutators and side effects).
317
- * @param response - Response object (HttpResponse or StreamResponse).
318
- * @param req - Original internal request.
319
- */
320
- async runResponsePipeline(response, req) {
321
- if (this.pipelines.responseMutators.length ||
322
- this.pipelines.responseSideEffects.length) {
323
- await executeResponsePipeline(this.pipelines.responseMutators, this.pipelines.responseSideEffects, response, req, this.pluginCtx, this.config.logger);
324
- }
316
+ await (await this.shortcut(req, signal)).dump?.();
325
317
  }
326
318
  /**
327
- * @ru Обрабатывает ошибки, возникшие при выполнении запроса, с учётом пайплайна ошибок.
328
- * @en Handles errors occurring during request execution, considering the error pipeline.
329
- * @param error - The caught error.
330
- * @param req - Internal request that caused the error.
331
- * @returns If recovered, returns a response; otherwise throws the error.
332
- * @throws The original error if no plugin recovers it.
319
+ * @ru Обрабатывает ошибку диспетчеризации через конвейер плагинов обработки ошибок.
320
+ * @en Handles dispatch errors through the error handling plugin pipeline.
321
+ * @template T - Expected response body type.
322
+ * @param error - The error that occurred.
323
+ * @param req - The original internal request.
324
+ * @returns Promise resolving to a recovered HTTP response, or throws if unrecoverable.
333
325
  */
334
326
  async handleDispatchError(error, req) {
335
- if (this.pipelines.error.length) {
327
+ if (this.hasErrorPlugins) {
336
328
  const recovered = await executeErrorPipeline(this.pipelines.error, error, req, this.pluginCtx);
337
- if (recovered) {
338
- await this.runResponsePipeline(recovered, req);
329
+ if (recovered != null) {
330
+ if (this.hasResponsePlugins) {
331
+ await Promise.resolve(executeResponsePipeline(this.pipelines.responseMutators, this.pipelines.responseSideEffects, recovered, req, this.pluginCtx, this.config.logger));
332
+ }
339
333
  return recovered;
340
334
  }
341
335
  }
342
336
  throw error;
343
337
  }
344
338
  /**
345
- * @ru Применяет декомпрессию к телу ответа в зависимости от заголовка Content-Encoding.
346
- * @en Applies decompression to the response body based on the Content-Encoding header.
347
- * @param rawResponse - Raw transport response object (mutated in place).
339
+ * @ru Создаёт или переиспользует объект InternalRequest из пула, заполняя его поля.
340
+ * @en Creates or reuses an InternalRequest object from the pool, populating its fields.
341
+ * @param method - HTTP method (GET, POST, etc.).
342
+ * @param req - Request URL or RequestInterface object.
343
+ * @param body - Optional request body data.
344
+ * @param signal - Optional abort signal.
345
+ * @param responseType - Optional response type hint ('stream').
346
+ * @returns The populated internal request object.
348
347
  */
349
- applyDecompression(rawResponse) {
350
- const encoding = rawResponse.headers?.["content-encoding"];
351
- if (!encoding || !rawResponse.body)
352
- return;
353
- const enc = Array.isArray(encoding) ? encoding[0] : encoding;
354
- if (rawResponse.body instanceof Uint8Array) {
355
- rawResponse.body = decompressBuffer(rawResponse.body, enc);
356
- }
357
- else {
358
- rawResponse.body = createDecompressStream(rawResponse.body, enc);
348
+ acquireReq(method, req, body, signal, responseType) {
349
+ const pooled = requestPool.pop();
350
+ const internalReq = pooled ?? {
351
+ method: "GET",
352
+ url: "",
353
+ headers: this.defaultHeaders,
354
+ body: undefined,
355
+ signal: undefined,
356
+ meta: undefined,
357
+ };
358
+ if (typeof req === "string") {
359
+ internalReq.method = method;
360
+ internalReq.url = this.resolveUrl(req);
361
+ internalReq.headers = this.defaultHeaders;
362
+ internalReq.body = body !== undefined ? normalizeBody(method, body) : undefined;
363
+ internalReq.signal = signal;
364
+ internalReq.meta = responseType ? { responseType } : undefined;
365
+ return internalReq;
359
366
  }
360
- }
361
- /**
362
- * @ru Строит внутренний объект запроса на основе пользовательских параметров.
363
- * @en Builds an internal request object from user parameters.
364
- * @param method - HTTP method.
365
- * @param req - Request URL or configuration object.
366
- * @param body - Request body (optional).
367
- * @param signal - AbortSignal (optional).
368
- * @returns Fully constructed InternalRequest.
369
- * @throws If URL is undefined or invalid.
370
- */
371
- buildInternalRequest(method, req, body, signal) {
372
367
  const rawUrl = normalizeUrl(req);
373
- if (!rawUrl) {
368
+ if (!rawUrl)
374
369
  throw new Error(`[HyperCore] URL is undefined for ${method}`);
375
- }
376
- if (typeof req === "string") {
377
- let cached = urlCache.get(rawUrl);
378
- if (!cached) {
379
- const urlObj = new URL(rawUrl);
380
- cached = {
381
- href: urlObj.href,
382
- origin: urlObj.origin,
383
- path: urlObj.pathname + urlObj.search,
384
- };
385
- if (urlCache.size > 512)
386
- urlCache.clear();
387
- urlCache.set(rawUrl, cached);
370
+ let finalUrl = urlCache[rawUrl];
371
+ if (!finalUrl) {
372
+ const urlObj = new URL(rawUrl);
373
+ if (req.query)
374
+ this.appendQueryParams(urlObj, req.query);
375
+ finalUrl = urlObj.href;
376
+ if (urlCacheCount < MAX_CACHE_SIZE) {
377
+ urlCache[rawUrl] = finalUrl;
378
+ urlCacheCount++;
388
379
  }
389
- return {
390
- method,
391
- url: cached.href,
392
- origin: cached.origin,
393
- path: cached.path,
394
- headers: { ...this.defaultHeaders },
395
- body,
396
- signal,
397
- meta: {},
398
- };
399
380
  }
400
- const urlObj = new URL(rawUrl);
401
- if (req.query) {
402
- this.appendQueryParams(urlObj, req.query);
381
+ internalReq.method = method;
382
+ internalReq.url = finalUrl;
383
+ internalReq.headers = req.headers
384
+ ? mergeHeadersFast(this.defaultHeaders, req.headers)
385
+ : this.defaultHeaders;
386
+ internalReq.body = normalizeBody(method, req.body ?? body);
387
+ internalReq.signal = req.signal ?? signal;
388
+ internalReq.meta = responseType
389
+ ? { responseType }
390
+ : req.meta
391
+ ? {
392
+ responseType: req.meta.responseType,
393
+ }
394
+ : undefined;
395
+ return internalReq;
396
+ }
397
+ /**
398
+ * @ru Возвращает объект запроса в пул для переиспользования, очищая ссылки для GC.
399
+ * @en Returns the request object to the pool for reuse, clearing references for GC.
400
+ * @param req - The internal request object to recycle.
401
+ */
402
+ recycleRequest(req) {
403
+ if (requestPool.length < MAX_POOL_SIZE) {
404
+ req.body = undefined;
405
+ req.signal = undefined;
406
+ req.meta = undefined;
407
+ requestPool.push(req);
403
408
  }
404
- const headers = (req.headers ?? {});
405
- return this.createInternalRequestObject(req, {
406
- method,
407
- url: urlObj.href,
408
- origin: urlObj.origin,
409
- path: urlObj.pathname + urlObj.search,
410
- headers: mergeHeadersFast(this.defaultHeaders, headers),
411
- body: normalizeBody(method, req.body ?? body),
412
- signal: req.signal ?? signal,
413
- meta: req.meta ?? {},
414
- });
415
409
  }
416
410
  /**
417
- * @ru Создаёт объект запроса, сохраняя прототип исходного объекта (если есть).
418
- * @en Creates a request object preserving the prototype of the source object (if any).
419
- * @param source - Original request object (may have a custom prototype).
420
- * @param overrides - Properties to override or add.
421
- * @returns New object with merged properties and inherited prototype.
411
+ * @ru Разрешает и кэширует URL, нормализуя его через URL API при необходимости.
412
+ * @en Resolves and caches the URL, normalizing it via the URL API when necessary.
413
+ * @param url - The raw URL string.
414
+ * @returns The resolved and cached URL string.
422
415
  */
423
- createInternalRequestObject(source, overrides) {
424
- const proto = Object.getPrototypeOf(source);
425
- const target = proto && proto !== Object.prototype ? Object.create(proto) : {};
426
- return Object.assign(target, source, overrides);
416
+ resolveUrl(url) {
417
+ if (!url)
418
+ throw new Error("[HyperCore] URL is undefined");
419
+ let finalUrl = urlCache[url];
420
+ if (finalUrl)
421
+ return finalUrl;
422
+ const hasQuery = url.includes("?") || url.endsWith("?");
423
+ const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
424
+ finalUrl = !hasQuery || !isAbsolute ? new URL(url).href : url;
425
+ if (urlCacheCount < MAX_CACHE_SIZE) {
426
+ urlCache[url] = finalUrl;
427
+ urlCacheCount++;
428
+ }
429
+ return finalUrl;
427
430
  }
428
431
  /**
429
- * @ru Добавляет параметры запроса к URL-объекту.
430
- * @en Appends query parameters to a URL object.
431
- * @param url - URL object to mutate.
432
- * @param query - Record of query parameters (supports arrays).
432
+ * @ru Добавляет query-параметры к объекту URL, поддерживая массивы значений.
433
+ * @en Appends query parameters to the URL object, supporting arrays of values.
434
+ * @param url - The URL object to modify.
435
+ * @param query - Record of query parameter key-value pairs.
433
436
  */
434
437
  appendQueryParams(url, query) {
435
- if (!query)
436
- return;
437
438
  for (const k in query) {
438
- const v = query[k];
439
- if (v == null)
440
- continue;
441
- if (Array.isArray(v)) {
442
- for (const i of v)
443
- url.searchParams.append(k, String(i));
444
- }
445
- else {
446
- url.searchParams.set(k, String(v));
439
+ if (Object.prototype.hasOwnProperty.call(query, k)) {
440
+ const v = query[k];
441
+ if (v == null)
442
+ continue;
443
+ if (Array.isArray(v)) {
444
+ for (let j = 0; j < v.length; j++)
445
+ url.searchParams.append(k, String(v[j]));
446
+ }
447
+ else {
448
+ url.searchParams.set(k, String(v));
449
+ }
447
450
  }
448
451
  }
449
452
  }
450
453
  /**
451
- * @ru Упрощённый метод для текстовых/JSON-запросов, определяющий метод из объекта запроса.
452
- * @en Shortcut method for text/JSON requests, determining method from the request object.
453
- * @param req - Request URL or configuration object.
454
- * @param signal - Optional AbortSignal.
455
- * @returns Promise resolving to HttpResponse.
454
+ * @ru Выполняет быстрый запрос с автоматическим определением HTTP-метода.
455
+ * @en Performs a shortcut request with automatic HTTP method detection.
456
+ * @param req - Request URL or RequestInterface object.
457
+ * @param signal - Optional abort signal.
458
+ * @returns Promise resolving to the HTTP response.
456
459
  */
457
- async dispatchShortcut(req, signal) {
460
+ shortcut(req, signal) {
458
461
  const method = typeof req === "string" ? "GET" : (req.method ?? "GET");
459
- return this.dispatch(this.buildInternalRequest(method, req, undefined, signal));
462
+ return this.dispatch(this.acquireReq(method, req, undefined, signal));
460
463
  }
461
464
  }
462
465
  //# sourceMappingURL=HyperCore.js.map