@hyperttp/core 1.5.4 → 1.5.6

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 (84) hide show
  1. package/README.md +177 -305
  2. package/{Core → dist/Core}/HyperCore.d.ts.map +1 -1
  3. package/dist/Core/HyperCore.js +2 -0
  4. package/dist/Core/HyperCore.js.map +7 -0
  5. package/dist/Core/RequestBuilder.d.ts +8 -0
  6. package/dist/Core/RequestBuilder.d.ts.map +1 -0
  7. package/dist/Core/RequestBuilder.js +2 -0
  8. package/dist/Core/RequestBuilder.js.map +7 -0
  9. package/dist/LICENSE +21 -0
  10. package/dist/README.md +291 -0
  11. package/dist/defaultConfig.js.map +7 -0
  12. package/dist/package.json +50 -0
  13. package/dist/transports/browser.js.map +7 -0
  14. package/{transports → dist/transports}/manager.d.ts.map +1 -1
  15. package/dist/transports/manager.js +7 -0
  16. package/dist/transports/manager.js.map +7 -0
  17. package/dist/transports/node.js.map +7 -0
  18. package/{utils → dist/utils}/errors.d.ts +10 -0
  19. package/{utils → dist/utils}/errors.d.ts.map +1 -1
  20. package/dist/utils/errors.js +2 -0
  21. package/dist/utils/errors.js.map +7 -0
  22. package/{utils → dist/utils}/merge.d.ts.map +1 -1
  23. package/dist/utils/merge.js +2 -0
  24. package/dist/utils/merge.js.map +7 -0
  25. package/dist/utils/normalize.d.ts.map +1 -0
  26. package/dist/utils/normalize.js +2 -0
  27. package/dist/utils/normalize.js.map +7 -0
  28. package/dist/utils/pipeline.js.map +7 -0
  29. package/{utils → dist/utils}/response.d.ts +0 -5
  30. package/{utils → dist/utils}/response.d.ts.map +1 -1
  31. package/dist/utils/response.js +2 -0
  32. package/dist/utils/response.js.map +7 -0
  33. package/{utils → dist/utils}/retryUtils.d.ts.map +1 -1
  34. package/dist/utils/retryUtils.js +2 -0
  35. package/dist/utils/retryUtils.js.map +7 -0
  36. package/package.json +38 -8
  37. package/Core/HyperCore.js +0 -2
  38. package/Core/HyperCore.js.map +0 -7
  39. package/Core/RequestBuilder.d.ts +0 -32
  40. package/Core/RequestBuilder.d.ts.map +0 -1
  41. package/Core/RequestBuilder.js +0 -2
  42. package/Core/RequestBuilder.js.map +0 -7
  43. package/defaultConfig.js.map +0 -7
  44. package/transports/browser.js.map +0 -7
  45. package/transports/manager.js +0 -7
  46. package/transports/manager.js.map +0 -7
  47. package/transports/node.js.map +0 -7
  48. package/utils/errors.js +0 -2
  49. package/utils/errors.js.map +0 -7
  50. package/utils/merge.js +0 -2
  51. package/utils/merge.js.map +0 -7
  52. package/utils/normalize.d.ts.map +0 -1
  53. package/utils/normalize.js +0 -2
  54. package/utils/normalize.js.map +0 -7
  55. package/utils/pipeline.js.map +0 -7
  56. package/utils/response.js +0 -2
  57. package/utils/response.js.map +0 -7
  58. package/utils/retryUtils.js +0 -2
  59. package/utils/retryUtils.js.map +0 -7
  60. /package/{Core → dist/Core}/HyperCore.d.ts +0 -0
  61. /package/{Core → dist/Core}/index.d.ts +0 -0
  62. /package/{Core → dist/Core}/index.d.ts.map +0 -0
  63. /package/{Core → dist/Core}/index.js +0 -0
  64. /package/{Core → dist/Core}/index.js.map +0 -0
  65. /package/{defaultConfig.d.ts → dist/defaultConfig.d.ts} +0 -0
  66. /package/{defaultConfig.d.ts.map → dist/defaultConfig.d.ts.map} +0 -0
  67. /package/{defaultConfig.js → dist/defaultConfig.js} +0 -0
  68. /package/{index.d.ts → dist/index.d.ts} +0 -0
  69. /package/{index.d.ts.map → dist/index.d.ts.map} +0 -0
  70. /package/{index.js → dist/index.js} +0 -0
  71. /package/{index.js.map → dist/index.js.map} +0 -0
  72. /package/{transports → dist/transports}/browser.d.ts +0 -0
  73. /package/{transports → dist/transports}/browser.d.ts.map +0 -0
  74. /package/{transports → dist/transports}/browser.js +0 -0
  75. /package/{transports → dist/transports}/manager.d.ts +0 -0
  76. /package/{transports → dist/transports}/node.d.ts +0 -0
  77. /package/{transports → dist/transports}/node.d.ts.map +0 -0
  78. /package/{transports → dist/transports}/node.js +0 -0
  79. /package/{utils → dist/utils}/merge.d.ts +0 -0
  80. /package/{utils → dist/utils}/normalize.d.ts +0 -0
  81. /package/{utils → dist/utils}/pipeline.d.ts +0 -0
  82. /package/{utils → dist/utils}/pipeline.d.ts.map +0 -0
  83. /package/{utils → dist/utils}/pipeline.js +0 -0
  84. /package/{utils → dist/utils}/retryUtils.d.ts +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Core/HyperCore.ts"],
4
+ "sourcesContent": ["import type {\n IHyperCore,\n HyperTransport,\n HttpClientOptions,\n PluginContext,\n HyperPlugin,\n InternalRequest,\n HttpResponse,\n HyperttpError,\n RequestInterface,\n StreamResponse,\n RequestBodyData,\n Method,\n ResponseType,\n TransportResponse,\n} from \"@hyperttp/types\";\nimport { defaultConfig } from \"../defaultConfig.js\";\nimport { mapResponseFast, mapStreamFast, recycleResponse } from \"../utils/response.js\";\nimport { TransportManager } from \"../transports/manager.js\";\nimport {\n createPipelines,\n executeErrorPipeline,\n executeRequestPipeline,\n executeResponsePipeline,\n executeResponseDataPipeline,\n insertHookSorted,\n} from \"../utils/pipeline.js\";\nimport { normalizeHeaders, normalizeBodyForTransport } from \"../utils/normalize.js\";\nimport { calcDelay, shouldRetry, drainBody } from \"../utils/retryUtils.js\";\nimport { TimeoutError } from \"../utils/errors.js\";\nimport { RequestBuilder } from \"./RequestBuilder.js\";\n\ntype TransportArgs = Parameters<HyperTransport[\"execute\"]>[0];\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\nconst MAX_POOL_SIZE = 64;\n\n/**\n * @ru Высокопроизводительный семафор на базе кольцевого буфера.\n * Гарантирует отсутствие аллокаций массивов в steady-state режиме.\n * @en High-performance semaphore backed by a ring buffer.\n * Guarantees zero array allocations in steady state.\n */\nclass Semaphore {\n private current = 0;\n private readonly max: number;\n\n private queue: Array<(() => void) | undefined>;\n private capacity: number;\n private head = 0;\n private tail = 0;\n private size = 0;\n\n constructor(max: number, initialCapacity = 1024) {\n this.max = max;\n this.capacity = initialCapacity;\n const arr: Array<(() => void) | undefined> = [];\n arr.length = initialCapacity;\n this.queue = arr;\n }\n\n /**\n * @ru Пытается захватить слот без ожидания.\n * @en Attempts to acquire a slot without waiting.\n * @returns true if the slot was acquired.\n */\n tryAcquire(): boolean {\n if (this.current < this.max) {\n this.current++;\n return true;\n }\n return false;\n }\n\n /**\n * @ru Захватывает слот, ожидая при необходимости.\n * @en Acquires a slot, waiting if necessary.\n * @returns Promise that resolves when the slot is acquired.\n */\n acquire(): Promise<void> {\n if (this.current < this.max) {\n this.current++;\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve) => {\n if (this.size === this.capacity) {\n this.grow();\n }\n\n this.queue[this.tail] = resolve;\n this.tail = (this.tail + 1) % this.capacity;\n this.size++;\n });\n }\n\n /**\n * @ru Освобождает слот и запускает ожидающий из очереди.\n * @en Releases a slot and wakes the next waiter in the queue.\n */\n release(): void {\n if (this.size > 0) {\n const next = this.queue[this.head]!;\n this.queue[this.head] = undefined;\n this.head = (this.head + 1) % this.capacity;\n this.size--;\n next();\n } else {\n this.current--;\n }\n }\n\n /**\n * @ru Удваивает емкость буфера при переполнении.\n * @en Doubles buffer capacity on overflow.\n */\n private grow(): void {\n const oldCapacity = this.capacity;\n const newCapacity = oldCapacity * 2;\n\n const newQueue: Array<(() => void) | undefined> = [];\n newQueue.length = newCapacity;\n\n for (let i = 0; i < this.size; i++) {\n newQueue[i] = this.queue[(this.head + i) % oldCapacity];\n }\n\n this.queue = newQueue;\n this.capacity = newCapacity;\n this.head = 0;\n this.tail = this.size;\n }\n}\n\n/**\n * @ru Основной класс HTTP-клиента Hyperttp. Управляет транспортом, плагинами, повторными попытками и пулом запросов.\n * @en Core Hyperttp HTTP client class. Manages transport, plugins, retries, and request pooling.\n */\nexport class HyperCore implements IHyperCore {\n public config: HttpClientOptions;\n private readonly transportManager: TransportManager;\n private readonly transportReady: Promise<HyperTransport>;\n private readonly defaultHeaders: Record<string, string | string[]>;\n private readonly pluginCtx: PluginContext;\n private readonly pipelines = createPipelines();\n private readonly requestBuilder = new RequestBuilder();\n private readonly requestPool: InternalRequest[] = [];\n\n private semaphore: Semaphore | null = null;\n private hasRequestPlugins = false;\n private hasResponseDataPlugins = false;\n private hasResponsePlugins = false;\n private hasErrorPlugins = false;\n\n private readonly _cachedMaxRetries: number;\n private readonly _cachedRetryOpts: {\n maxRetries?: number;\n [k: string]: unknown;\n };\n\n constructor(config: HttpClientOptions = defaultConfig, transport?: HyperTransport) {\n this.config = {\n ...defaultConfig,\n ...config,\n network: {\n ...defaultConfig.network,\n ...config.network,\n stealth:\n config.network?.stealth || defaultConfig.network?.stealth\n ? Object.assign({}, defaultConfig.network?.stealth, config.network?.stealth)\n : undefined,\n },\n };\n this.transportManager = new TransportManager(this.config, transport);\n this.transportReady = this.transportManager.getSync()\n ? Promise.resolve(this.transportManager.getSync()!)\n : this.transportManager.ensure();\n\n this.defaultHeaders = normalizeHeaders({\n Accept: \"application/json, text/plain, */*\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"User-Agent\": this.config.network?.userAgent ?? \"Hyperttp/2.0\",\n ...this.config.network?.headers,\n });\n\n const maxConcurrent = this.config.network?.maxConcurrent;\n this.semaphore =\n maxConcurrent != null && maxConcurrent > 0 ? new Semaphore(maxConcurrent) : null;\n\n this._cachedRetryOpts = this.config.retry ?? {};\n this._cachedMaxRetries = this._cachedRetryOpts.maxRetries ?? 0;\n\n this.pluginCtx = { config: this.config, core: this };\n }\n\n /**\n * @ru Возвращает имя текущего транспорта.\n * @en Returns the name of the current transport.\n * @returns Transport constructor name.\n */\n public async getTransportName(): Promise<string> {\n const t = this.transportManager.transport ?? (await this.transportManager.get());\n return t.constructor.name;\n }\n\n /**\n * @ru Основной метод отправки запроса через транспорт с поддержкой плагинов и повторных попыток.\n * @en Core request dispatch method through transport with plugin and retry support.\n * @param req - The internal request to dispatch.\n * @returns Promise resolving to the HTTP response.\n */\n public async dispatch<T = unknown>(req: InternalRequest): Promise<HttpResponse<T>> {\n const retryOpts = this._cachedRetryOpts;\n const maxRetries = this._cachedMaxRetries;\n\n for (let attempt = 0; ; attempt++) {\n try {\n if (this.hasRequestPlugins) {\n const syncResult = executeRequestPipeline(this.pipelines.request, req, this.pluginCtx);\n const shortCircuit = syncResult instanceof Promise ? await syncResult : syncResult;\n\n if (shortCircuit != null) {\n this.recycleRequest(req);\n return shortCircuit as HttpResponse<T>;\n }\n }\n\n if (req.body != null) {\n req.body = normalizeBodyForTransport(req.body, req.headers);\n }\n\n const transport =\n this.transportManager.transport ??\n this.transportManager.getSync() ??\n (await this.transportReady);\n\n let acquired = false;\n if (this.semaphore) {\n acquired = this.semaphore.tryAcquire();\n if (!acquired) await this.semaphore.acquire();\n acquired = true;\n }\n\n if (req.signal?.aborted) {\n throw req.signal.reason ?? new DOMException(\"Aborted\", \"AbortError\");\n }\n\n let rawResponse: TransportResponse;\n const meta = req.meta as {\n responseType?: ResponseType;\n timings?: { networkMs?: number };\n };\n const shouldTrackTimings = !!meta.timings;\n const networkStart = shouldTrackTimings ? performance.now() : 0;\n try {\n rawResponse = await transport.execute(req as TransportArgs);\n } finally {\n if (acquired) this.semaphore?.release();\n }\n if (shouldTrackTimings) {\n meta.timings!.networkMs = performance.now() - networkStart;\n }\n\n if (attempt < maxRetries && shouldRetry(rawResponse.status, retryOpts)) {\n await drainBody(rawResponse.body);\n await sleep(calcDelay(attempt, retryOpts));\n continue;\n }\n\n if (this.hasResponseDataPlugins) {\n const syncResult = executeResponseDataPipeline(\n this.pipelines.responseData,\n rawResponse,\n this.pluginCtx,\n );\n rawResponse = syncResult instanceof Promise ? await syncResult : syncResult;\n }\n\n const response =\n meta.responseType === \"stream\"\n ? mapStreamFast(rawResponse)\n : mapResponseFast(rawResponse);\n\n if (this.hasResponsePlugins) {\n const syncResult = executeResponsePipeline(\n this.pipelines.responseMutators,\n this.pipelines.responseSideEffects,\n response as HttpResponse,\n req,\n this.pluginCtx,\n this.config.logger,\n );\n if (syncResult instanceof Promise) {\n await syncResult;\n }\n }\n\n this.recycleRequest(req);\n return response as HttpResponse<T>;\n } catch (error) {\n if (\n error instanceof Error &&\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n ) {\n if (req.signal && (req.signal as any).isTimeout) {\n const timeout = this.config.network?.timeout ?? 0;\n return this.handleDispatchError(new TimeoutError(req.url, timeout), req);\n }\n return this.handleDispatchError(error, req);\n }\n\n if (attempt < maxRetries && !req.signal?.aborted) {\n await sleep(calcDelay(attempt, retryOpts));\n continue;\n }\n\n return this.handleDispatchError(error as Error, req);\n }\n }\n }\n\n /**\n * @ru Регистрирует плагин с сортировкой по приоритету. Пропускает отключённые плагины.\n * @en Registers a plugin sorted by priority. Skips disabled plugins.\n * @param plugin - The plugin instance to register.\n * @returns This instance for chaining.\n */\n public use(plugin: HyperPlugin): this {\n const isEnabled = plugin.enabled ? plugin.enabled(this.config) : true;\n if (!isEnabled) return this;\n\n plugin.setup?.(this.pluginCtx);\n const priority = (plugin as { priority?: number }).priority ?? 0;\n\n const hook = { name: plugin.name, priority, run: null as any };\n\n if (plugin.onRequest) {\n insertHookSorted(this.pipelines.request, {\n name: hook.name,\n priority: hook.priority,\n run: plugin.onRequest,\n });\n this.hasRequestPlugins = true;\n }\n if (plugin.onResponse) {\n const target =\n plugin.mode === \"background\"\n ? this.pipelines.responseSideEffects\n : this.pipelines.responseMutators;\n insertHookSorted(target, {\n name: hook.name,\n priority: hook.priority,\n run: plugin.onResponse,\n });\n this.hasResponsePlugins = true;\n }\n if (plugin.onResponseData) {\n insertHookSorted(this.pipelines.responseData, {\n name: hook.name,\n priority: hook.priority,\n run: plugin.onResponseData,\n });\n this.hasResponseDataPlugins = true;\n }\n if (plugin.onError) {\n insertHookSorted(this.pipelines.error, {\n name: hook.name,\n priority: hook.priority,\n run: plugin.onError,\n });\n this.hasErrorPlugins = true;\n }\n return this;\n }\n\n /**\n * @ru Выполняет GET-запрос. Использует быстрый путь для простых строковых URL без плагинов/повторов.\n * @en Performs a GET request. Uses a fast path for bare string URLs without plugins/retries.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public get<T = unknown>(req: RequestInterface | string, signal?: AbortSignal) {\n if (\n typeof req === \"string\" &&\n req !== \"\" &&\n !signal &&\n !this.hasRequestPlugins &&\n !this.hasResponsePlugins &&\n !this.hasResponseDataPlugins &&\n !this.hasErrorPlugins &&\n this._cachedMaxRetries === 0\n ) {\n return this.fastGet<T>(req);\n }\n return this.dispatch<T>(this.acquireReq(\"GET\", req, undefined, signal));\n }\n\n private async fastGet<T>(url: string): Promise<HttpResponse<T>> {\n const resolvedUrl = this.requestBuilder.resolveUrl(url, this.config.baseURL);\n\n let acquired = false;\n if (this.semaphore) {\n acquired = this.semaphore.tryAcquire();\n if (!acquired) {\n await this.semaphore.acquire();\n acquired = true;\n }\n }\n\n const transport =\n this.transportManager.transport ??\n this.transportManager.getSync() ??\n (await this.transportReady);\n\n try {\n if (typeof (transport as any).fastRequest === \"function\") {\n return (await (transport as any).fastRequest(\n resolvedUrl,\n \"GET\",\n this.defaultHeaders,\n )) as HttpResponse<T>;\n }\n\n const raw = await transport.execute({\n method: \"GET\",\n url: resolvedUrl,\n headers: this.defaultHeaders as Record<string, string>,\n body: undefined,\n signal: undefined,\n stealth: this.config.network?.stealth,\n } as TransportArgs);\n\n return mapResponseFast(raw) as HttpResponse<T>;\n } finally {\n if (acquired) this.semaphore?.release();\n }\n }\n\n /**\n * @ru Выполняет POST-запрос.\n * @en Performs a POST request.\n * @param req - URL string or RequestInterface.\n * @param body - Optional request body.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public post<T = unknown>(\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n ) {\n return this.dispatch<T>(this.acquireReq(\"POST\", req, body, signal));\n }\n\n /**\n * @ru Выполняет PUT-запрос.\n * @en Performs a PUT request.\n * @param req - URL string or RequestInterface.\n * @param body - Optional request body.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public put<T = unknown>(\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n ) {\n return this.dispatch<T>(this.acquireReq(\"PUT\", req, body, signal));\n }\n\n /**\n * @ru Выполняет PATCH-запрос.\n * @en Performs a PATCH request.\n * @param req - URL string or RequestInterface.\n * @param body - Optional request body.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public patch<T = unknown>(\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n ) {\n return this.dispatch<T>(this.acquireReq(\"PATCH\", req, body, signal));\n }\n\n /**\n * @ru Выполняет DELETE-запрос.\n * @en Performs a DELETE request.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public delete<T = unknown>(req: RequestInterface | string, signal?: AbortSignal) {\n return this.dispatch<T>(this.acquireReq(\"DELETE\", req, undefined, signal));\n }\n\n /**\n * @ru Выполняет OPTIONS-запрос.\n * @en Performs an OPTIONS request.\n * @param req - URL string or RequestInterface.\n * @param body - Optional request body.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response.\n */\n public options<T = unknown>(\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n ) {\n return this.dispatch<T>(this.acquireReq(\"OPTIONS\", req, body, signal));\n }\n\n /**\n * @ru Выполняет HEAD-запрос.\n * @en Performs a HEAD request.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the HTTP response with null body.\n */\n public head(req: RequestInterface | string, signal?: AbortSignal) {\n return this.dispatch<null>(this.acquireReq(\"HEAD\", req, undefined, signal));\n }\n\n /**\n * @ru Выполняет GET-запрос и возвращает стриминг-ответ.\n * @en Performs a GET request and returns a streaming response.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to a StreamResponse.\n */\n public stream(\n req: RequestInterface | string,\n signal?: AbortSignal,\n ): Promise<StreamResponse<unknown>> {\n return this.dispatch(this.acquireReq(\"GET\", req, undefined, signal)) as Promise<\n StreamResponse<unknown>\n >;\n }\n\n /**\n * @ru Выполняет POST-запрос и возвращает стриминг-ответ.\n * @en Performs a POST request and returns a streaming response.\n * @param req - URL string or RequestInterface.\n * @param body - Optional request body.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to a StreamResponse.\n */\n public postStream<T = unknown>(\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n ): Promise<StreamResponse<T>> {\n return this.dispatch(this.acquireReq(\"POST\", req, body, signal)) as Promise<StreamResponse<T>>;\n }\n\n /**\n * @ru Создаёт новый экземпляр HyperCore с расширенной/переопределённой конфигурацией.\n * @en Creates a new HyperCore instance with extended/overridden configuration.\n * @param options - Partial configuration overrides.\n * @returns A new HyperCore instance.\n */\n public extend(options: Partial<HttpClientOptions>): HyperCore {\n const nextConfig = { ...this.config };\n for (const key in options) {\n if (Object.prototype.hasOwnProperty.call(options, key)) {\n if (key === \"network\" && options.network) {\n nextConfig.network = {\n ...this.config.network,\n ...options.network,\n stealth:\n options.network.stealth || this.config.network?.stealth\n ? {\n ...this.config.network?.stealth,\n ...options.network.stealth,\n }\n : undefined,\n };\n } else {\n (nextConfig as any)[key] = (options as any)[key];\n }\n }\n }\n return new HyperCore(nextConfig, this.transportManager.transport ?? undefined);\n }\n\n /**\n * @ru Алиас для extend().\n * @en Alias for extend().\n * @param options - Partial configuration overrides.\n * @returns A new HyperCore instance.\n */\n public create(options: Partial<HttpClientOptions>): HyperCore {\n return this.extend(options);\n }\n\n /**\n * @ru Завершает работу клиента и освобождает ресурсы транспорта.\n * @en Shuts down the client and releases transport resources.\n * @param graceful - If true, waits for active requests to complete.\n * @returns Promise that resolves when shutdown is complete.\n */\n public destroy(graceful = true): Promise<void> {\n return this.transportManager.destroy(graceful);\n }\n\n /**\n * @ru Выполняет GET-запрос и возвращает тело ответа, разобранное как JSON.\n * @en Performs a GET request and returns the response body parsed as JSON.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the parsed JSON value.\n */\n public json<T = unknown>(req: RequestInterface | string, signal?: AbortSignal): Promise<T> {\n return this.shortcut(req, signal).then((res) => {\n const out = res.json ? res.json<T>() : Promise.reject(new Error(\"json() not supported\"));\n recycleResponse(res);\n return out;\n });\n }\n\n /**\n * @ru Выполняет GET-запрос и возвращает тело ответа как строку.\n * @en Performs a GET request and returns the response body as text.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise resolving to the response text.\n */\n public text(req: RequestInterface | string, signal?: AbortSignal): Promise<string> {\n return this.shortcut(req, signal).then((res) => {\n const out = res.text ? res.text() : Promise.reject(new Error(\"text() not supported\"));\n recycleResponse(res);\n return out;\n });\n }\n\n /**\n * @ru Выполняет GET-запрос и сбрасывает тело ответа (без сохранения).\n * @en Performs a GET request and discards the response body.\n * @param req - URL string or RequestInterface.\n * @param signal - Optional abort signal.\n * @returns Promise that resolves when the body is discarded.\n */\n public dump(req: RequestInterface | string, signal?: AbortSignal): Promise<void> {\n return this.shortcut(req, signal).then((res) => {\n const out = res.dump ? res.dump() : Promise.resolve();\n recycleResponse(res);\n return out;\n });\n }\n\n private async handleDispatchError<T>(\n error: Error,\n req: InternalRequest,\n ): Promise<HttpResponse<T>> {\n try {\n if (this.hasErrorPlugins) {\n const recovered = await executeErrorPipeline(\n this.pipelines.error,\n error as HyperttpError,\n req,\n this.pluginCtx,\n );\n if (recovered != null) {\n if (this.hasResponsePlugins) {\n const syncResult = executeResponsePipeline(\n this.pipelines.responseMutators,\n this.pipelines.responseSideEffects,\n recovered as HttpResponse,\n req,\n this.pluginCtx,\n this.config.logger,\n );\n if (syncResult instanceof Promise) {\n await syncResult;\n }\n }\n return recovered as HttpResponse<T>;\n }\n }\n throw error;\n } finally {\n this.recycleRequest(req);\n }\n }\n\n private acquireReq(\n method: Method,\n req: RequestInterface | string,\n body?: RequestBodyData,\n signal?: AbortSignal,\n responseType?: \"stream\",\n ): InternalRequest {\n const pooled = this.requestPool.pop();\n return this.requestBuilder.build(\n method,\n req,\n body,\n signal,\n responseType,\n this.defaultHeaders,\n this.config,\n pooled,\n );\n }\n\n private recycleRequest(req: InternalRequest): void {\n if (this.requestPool.length < MAX_POOL_SIZE) {\n req.method = \"GET\";\n req.url = \"\";\n req.headers = this.defaultHeaders;\n req.body = undefined;\n req.signal = undefined;\n req.stealth = undefined;\n\n const m = req.meta as any;\n if (m) {\n m.responseType = undefined;\n\n if (typeof m.cleanupSignal === \"function\") {\n m.cleanupSignal();\n m.cleanupSignal = undefined;\n }\n\n if (m.timings) {\n m.timings.networkMs = undefined;\n }\n }\n this.requestPool.push(req);\n }\n }\n\n private shortcut(\n req: RequestInterface | string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<unknown>> {\n const method = typeof req === \"string\" ? \"GET\" : (req.method ?? \"GET\");\n return this.dispatch(this.acquireReq(method, req, undefined, signal));\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["defaultConfig", "mapResponseFast", "mapStreamFast", "recycleResponse", "TransportManager", "createPipelines", "executeErrorPipeline", "executeRequestPipeline", "executeResponsePipeline", "executeResponseDataPipeline", "insertHookSorted", "normalizeHeaders", "normalizeBodyForTransport", "calcDelay", "shouldRetry", "drainBody", "TimeoutError", "RequestBuilder", "sleep", "ms", "resolve", "MAX_POOL_SIZE", "Semaphore", "max", "initialCapacity", "arr", "next", "oldCapacity", "newCapacity", "newQueue", "config", "transport", "maxConcurrent", "req", "retryOpts", "maxRetries", "attempt", "syncResult", "shortCircuit", "acquired", "rawResponse", "meta", "shouldTrackTimings", "networkStart", "response", "error", "timeout", "plugin", "priority", "hook", "target", "signal", "url", "resolvedUrl", "raw", "body", "options", "nextConfig", "key", "graceful", "res", "out", "recovered", "method", "responseType", "pooled", "m"]
7
+ }
@@ -0,0 +1,8 @@
1
+ import type { HttpClientOptions, InternalRequest, RequestInterface, RequestBodyData, Method } from "@hyperttp/types";
2
+ export declare class RequestBuilder {
3
+ private readonly urlCache;
4
+ build(method: Method, req: RequestInterface | string, body: RequestBodyData | undefined, signal: AbortSignal | undefined, responseType: "stream" | undefined, defaultHeaders: Record<string, string | string[]>, config: HttpClientOptions, pooled?: InternalRequest): InternalRequest;
5
+ resolveUrl(url: string, baseURL?: string): string;
6
+ private appendQueryString;
7
+ }
8
+ //# sourceMappingURL=RequestBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestBuilder.d.ts","sourceRoot":"","sources":["../../src/Core/RequestBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,MAAM,EAEP,MAAM,iBAAiB,CAAC;AAwEzB,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2D;IAEpF,KAAK,CACH,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,gBAAgB,GAAG,MAAM,EAC9B,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,YAAY,EAAE,QAAQ,GAAG,SAAS,EAClC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EACjD,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,eAAe,GACvB,eAAe,CAiGjB;IAEM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAevD;IAED,OAAO,CAAC,iBAAiB;CAmB1B"}
@@ -0,0 +1,2 @@
1
+ "use strict";import{CacheManager as b}from"hcacher";import{normalizeBody as m,normalizeUrl as R}from"../utils/normalize.js";import{mergeHeadersFast as y}from"../utils/response.js";function C(h,e,t){const n=new AbortController;let a=!1;const o=()=>{a||(a=!0,h&&h.removeEventListener("abort",s),clearTimeout(l))},l=setTimeout(()=>{n.signal.isTimeout=!0,n.abort(new DOMException("Timeout","TimeoutError")),o()},e),s=()=>{n.abort(h?.reason),o()};return h&&(h.aborted?s():h.addEventListener("abort",s)),t.cleanupSignal=o,n.signal}function U(h,e,t){return e==null||e<=0?h:C(h,e,t)}export class RequestBuilder{urlCache=new b({maxSize:512,ttl:6e4});build(e,t,n,a,o,l,s,w){const r=w??{method:"GET",url:"",headers:l,body:void 0,signal:void 0,meta:{responseType:void 0,timings:{networkMs:void 0}},stealth:void 0},u=r.meta;if(typeof t=="string")return r.method=e,r.url=this.resolveUrl(t,s.baseURL),n!==void 0?r.headers=Object.assign({},l):r.headers=l,r.body=n!==void 0?m(e,n):void 0,r.signal=U(a,s.network?.timeout,u),u.responseType=o,r.stealth=s.network?.stealth,r;const d=R(t);if(!d)throw new Error(`[HyperCore] URL is undefined for ${e}`);let f;if(t.query){const i=d+"_base";let c=this.urlCache.get(i);c||(c=s.baseURL?new URL(d,s.baseURL).href:new URL(d).href,this.urlCache.set(i,c)),f=this.appendQueryString(c,t.query)}else{let i=this.urlCache.get(d);i||(i=s.baseURL?new URL(d,s.baseURL).href:new URL(d).href,this.urlCache.set(d,i)),f=i}r.method=e,r.url=f;const p=t.body??n;if(t.headers||p!==void 0){const i=Object.assign({},l);r.headers=t.headers?y(i,t.headers):i}else r.headers=l;if(r.body=m(e,p),r.signal=U(t.signal??a,s.network?.timeout,u),u.responseType=o??t.meta?.responseType,t.stealth)if(s.network?.stealth){const i=Object.assign({},s.network.stealth,t.stealth);r.stealth=i}else r.stealth=t.stealth;else r.stealth=s.network?.stealth;return r}resolveUrl(e,t){if(!e)throw new Error("[HyperCore] URL is undefined");let n=this.urlCache.get(e);return n||((e.startsWith("http://")||e.startsWith("https://"))&&!e.includes("?")?n=e:n=t?new URL(e,t).href:new URL(e).href,this.urlCache.set(e,n),n)}appendQueryString(e,t){let n="";for(const a in t){if(!Object.prototype.hasOwnProperty.call(t,a))continue;const o=t[a];if(o!=null)if(Array.isArray(o))for(let l=0;l<o.length;l++)n&&(n+="&"),n+=encodeURIComponent(a)+"="+encodeURIComponent(String(o[l]));else n&&(n+="&"),n+=encodeURIComponent(a)+"="+encodeURIComponent(String(o))}return n?e+(e.includes("?")?"&":"?")+n:e}}
2
+ //# sourceMappingURL=RequestBuilder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Core/RequestBuilder.ts"],
4
+ "sourcesContent": ["import type {\n HttpClientOptions,\n InternalRequest,\n RequestInterface,\n RequestBodyData,\n Method,\n ResponseType,\n} from \"@hyperttp/types\";\nimport { CacheManager } from \"hcacher\";\nimport { normalizeBody, normalizeUrl } from \"../utils/normalize.js\";\nimport { mergeHeadersFast } from \"../utils/response.js\";\n\n/**\n * @ru Создаёт AbortSignal с тайм-аутом и привязкой к пользовательскому сигналу отмены.\n * @en Creates an AbortSignal with timeout and user abort signal binding.\n * @param userSignal - Optional external abort signal.\n * @param timeoutMs - Timeout in milliseconds.\n * @param meta - Metadata object receiving cleanup function.\n * @returns AbortSignal bound to the timeout and user signal.\n */\nfunction createTimeoutSignal(\n userSignal: AbortSignal | undefined,\n timeoutMs: number,\n meta: any,\n): AbortSignal {\n const controller = new AbortController();\n\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n\n if (userSignal) {\n userSignal.removeEventListener(\"abort\", onUserAbort);\n }\n clearTimeout(timeoutId);\n };\n\n const timeoutId = setTimeout(() => {\n (controller.signal as any).isTimeout = true;\n controller.abort(new DOMException(\"Timeout\", \"TimeoutError\"));\n cleanup();\n }, timeoutMs);\n\n const onUserAbort = () => {\n controller.abort(userSignal?.reason);\n cleanup();\n };\n\n if (userSignal) {\n if (userSignal.aborted) {\n onUserAbort();\n } else {\n userSignal.addEventListener(\"abort\", onUserAbort);\n }\n }\n\n meta.cleanupSignal = cleanup;\n\n return controller.signal;\n}\n\n/**\n * @ru Применяет тайм-аут к сигналу отмены.\n * @en Applies a timeout to the abort signal.\n * @param signal - Optional external abort signal.\n * @param timeout - Timeout in milliseconds (skip if null/<=0).\n * @param meta - Metadata object receiving cleanup function.\n * @returns New abort signal with timeout, or the original signal.\n */\nfunction applyTimeout(\n signal: AbortSignal | undefined,\n timeout: number | undefined,\n meta: any,\n): AbortSignal | undefined {\n if (timeout == null || timeout <= 0) return signal;\n return createTimeoutSignal(signal, timeout, meta);\n}\n\nexport class RequestBuilder {\n private readonly urlCache = new CacheManager<string>({ maxSize: 512, ttl: 60_000 });\n\n build(\n method: Method,\n req: RequestInterface | string,\n body: RequestBodyData | undefined,\n signal: AbortSignal | undefined,\n responseType: \"stream\" | undefined,\n defaultHeaders: Record<string, string | string[]>,\n config: HttpClientOptions,\n pooled?: InternalRequest,\n ): InternalRequest {\n const internalReq = pooled ?? {\n method: \"GET\" as Method,\n url: \"\",\n headers: defaultHeaders,\n body: undefined,\n signal: undefined,\n meta: {\n responseType: undefined as ResponseType | undefined,\n timings: {\n networkMs: undefined as number | undefined,\n },\n },\n stealth: undefined,\n };\n\n const metaObj = internalReq.meta as {\n responseType: ResponseType | undefined;\n timings: {\n networkMs: number | undefined;\n };\n cleanupSignal?: () => void;\n };\n\n if (typeof req === \"string\") {\n internalReq.method = method;\n internalReq.url = this.resolveUrl(req, config.baseURL);\n\n if (body !== undefined) {\n internalReq.headers = Object.assign({}, defaultHeaders);\n } else {\n internalReq.headers = defaultHeaders;\n }\n\n internalReq.body = body !== undefined ? normalizeBody(method, body) : undefined;\n internalReq.signal = applyTimeout(signal, config.network?.timeout, metaObj);\n metaObj.responseType = responseType;\n internalReq.stealth = config.network?.stealth;\n return internalReq;\n }\n\n const rawUrl = normalizeUrl(req);\n if (!rawUrl) throw new Error(`[HyperCore] URL is undefined for ${method}`);\n\n let finalUrl: string;\n\n if (req.query) {\n const cacheKey = rawUrl + \"_base\";\n let baseUrl = this.urlCache.get(cacheKey);\n\n if (!baseUrl) {\n baseUrl = config.baseURL ? new URL(rawUrl, config.baseURL).href : new URL(rawUrl).href;\n this.urlCache.set(cacheKey, baseUrl);\n }\n\n finalUrl = this.appendQueryString(baseUrl, req.query);\n } else {\n let cachedUrl = this.urlCache.get(rawUrl);\n if (!cachedUrl) {\n cachedUrl = config.baseURL ? new URL(rawUrl, config.baseURL).href : new URL(rawUrl).href;\n this.urlCache.set(rawUrl, cachedUrl);\n }\n finalUrl = cachedUrl;\n }\n\n internalReq.method = method;\n internalReq.url = finalUrl;\n\n const finalBody = req.body ?? body;\n\n if (req.headers || finalBody !== undefined) {\n const targetHeaders = Object.assign({}, defaultHeaders);\n internalReq.headers = req.headers\n ? mergeHeadersFast(targetHeaders, req.headers)\n : targetHeaders;\n } else {\n internalReq.headers = defaultHeaders;\n }\n\n internalReq.body = normalizeBody(method, finalBody);\n internalReq.signal = applyTimeout(req.signal ?? signal, config.network?.timeout, metaObj);\n\n metaObj.responseType =\n responseType ?? (req.meta as { responseType?: ResponseType })?.responseType;\n\n if (req.stealth) {\n if (config.network?.stealth) {\n const nextStealth = Object.assign({}, config.network.stealth, req.stealth);\n internalReq.stealth = nextStealth;\n } else {\n internalReq.stealth = req.stealth;\n }\n } else {\n internalReq.stealth = config.network?.stealth;\n }\n\n return internalReq;\n }\n\n public resolveUrl(url: string, baseURL?: string): string {\n if (!url) throw new Error(\"[HyperCore] URL is undefined\");\n\n let finalUrl = this.urlCache.get(url);\n if (finalUrl) return finalUrl;\n\n const isAbsolute = url.startsWith(\"http://\") || url.startsWith(\"https://\");\n if (isAbsolute && !url.includes(\"?\")) {\n finalUrl = url;\n } else {\n finalUrl = baseURL ? new URL(url, baseURL).href : new URL(url).href;\n }\n\n this.urlCache.set(url, finalUrl);\n return finalUrl;\n }\n\n private appendQueryString(baseUrl: string, query: Record<string, unknown>): string {\n let qs = \"\";\n for (const k in query) {\n if (!Object.prototype.hasOwnProperty.call(query, k)) continue;\n const v = query[k];\n if (v == null) continue;\n if (Array.isArray(v)) {\n for (let j = 0; j < v.length; j++) {\n if (qs) qs += \"&\";\n qs += encodeURIComponent(k) + \"=\" + encodeURIComponent(String(v[j]));\n }\n } else {\n if (qs) qs += \"&\";\n qs += encodeURIComponent(k) + \"=\" + encodeURIComponent(String(v));\n }\n }\n if (!qs) return baseUrl;\n return baseUrl + (baseUrl.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n}\n"],
5
+ "mappings": "aAQA,OAAS,gBAAAA,MAAoB,UAC7B,OAAS,iBAAAC,EAAe,gBAAAC,MAAoB,wBAC5C,OAAS,oBAAAC,MAAwB,uBAUjC,SAASC,EACPC,EACAC,EACAC,EAAS,CAET,MAAMC,EAAa,IAAI,gBAEvB,IAAIC,EAAU,GACd,MAAMC,EAAU,IAAK,CACfD,IACJA,EAAU,GAENJ,GACFA,EAAW,oBAAoB,QAASM,CAAW,EAErD,aAAaC,CAAS,EACxB,EAEMA,EAAY,WAAW,IAAK,CAC/BJ,EAAW,OAAe,UAAY,GACvCA,EAAW,MAAM,IAAI,aAAa,UAAW,cAAc,CAAC,EAC5DE,EAAO,CACT,EAAGJ,CAAS,EAENK,EAAc,IAAK,CACvBH,EAAW,MAAMH,GAAY,MAAM,EACnCK,EAAO,CACT,EAEA,OAAIL,IACEA,EAAW,QACbM,EAAW,EAEXN,EAAW,iBAAiB,QAASM,CAAW,GAIpDJ,EAAK,cAAgBG,EAEdF,EAAW,MACpB,CAUA,SAASK,EACPC,EACAC,EACAR,EAAS,CAET,OAAIQ,GAAW,MAAQA,GAAW,EAAUD,EACrCV,EAAoBU,EAAQC,EAASR,CAAI,CAClD,CAEM,aAAO,cAAc,CACR,SAAW,IAAIP,EAAqB,CAAE,QAAS,IAAK,IAAK,GAAM,CAAE,EAElF,MACEgB,EACAC,EACAC,EACAJ,EACAK,EACAC,EACAC,EACAC,EAAwB,CAExB,MAAMC,EAAcD,GAAU,CAC5B,OAAQ,MACR,IAAK,GACL,QAASF,EACT,KAAM,OACN,OAAQ,OACR,KAAM,CACJ,aAAc,OACd,QAAS,CACP,UAAW,SAGf,QAAS,QAGLI,EAAUD,EAAY,KAQ5B,GAAI,OAAON,GAAQ,SACjB,OAAAM,EAAY,OAASP,EACrBO,EAAY,IAAM,KAAK,WAAWN,EAAKI,EAAO,OAAO,EAEjDH,IAAS,OACXK,EAAY,QAAU,OAAO,OAAO,CAAA,EAAIH,CAAc,EAEtDG,EAAY,QAAUH,EAGxBG,EAAY,KAAOL,IAAS,OAAYjB,EAAce,EAAQE,CAAI,EAAI,OACtEK,EAAY,OAASV,EAAaC,EAAQO,EAAO,SAAS,QAASG,CAAO,EAC1EA,EAAQ,aAAeL,EACvBI,EAAY,QAAUF,EAAO,SAAS,QAC/BE,EAGT,MAAME,EAASvB,EAAae,CAAG,EAC/B,GAAI,CAACQ,EAAQ,MAAM,IAAI,MAAM,oCAAoCT,CAAM,EAAE,EAEzE,IAAIU,EAEJ,GAAIT,EAAI,MAAO,CACb,MAAMU,EAAWF,EAAS,QAC1B,IAAIG,EAAU,KAAK,SAAS,IAAID,CAAQ,EAEnCC,IACHA,EAAUP,EAAO,QAAU,IAAI,IAAII,EAAQJ,EAAO,OAAO,EAAE,KAAO,IAAI,IAAII,CAAM,EAAE,KAClF,KAAK,SAAS,IAAIE,EAAUC,CAAO,GAGrCF,EAAW,KAAK,kBAAkBE,EAASX,EAAI,KAAK,CACtD,KAAO,CACL,IAAIY,EAAY,KAAK,SAAS,IAAIJ,CAAM,EACnCI,IACHA,EAAYR,EAAO,QAAU,IAAI,IAAII,EAAQJ,EAAO,OAAO,EAAE,KAAO,IAAI,IAAII,CAAM,EAAE,KACpF,KAAK,SAAS,IAAIA,EAAQI,CAAS,GAErCH,EAAWG,CACb,CAEAN,EAAY,OAASP,EACrBO,EAAY,IAAMG,EAElB,MAAMI,EAAYb,EAAI,MAAQC,EAE9B,GAAID,EAAI,SAAWa,IAAc,OAAW,CAC1C,MAAMC,EAAgB,OAAO,OAAO,CAAA,EAAIX,CAAc,EACtDG,EAAY,QAAUN,EAAI,QACtBd,EAAiB4B,EAAed,EAAI,OAAO,EAC3Cc,CACN,MACER,EAAY,QAAUH,EASxB,GANAG,EAAY,KAAOtB,EAAce,EAAQc,CAAS,EAClDP,EAAY,OAASV,EAAaI,EAAI,QAAUH,EAAQO,EAAO,SAAS,QAASG,CAAO,EAExFA,EAAQ,aACNL,GAAiBF,EAAI,MAA0C,aAE7DA,EAAI,QACN,GAAII,EAAO,SAAS,QAAS,CAC3B,MAAMW,EAAc,OAAO,OAAO,CAAA,EAAIX,EAAO,QAAQ,QAASJ,EAAI,OAAO,EACzEM,EAAY,QAAUS,CACxB,MACET,EAAY,QAAUN,EAAI,aAG5BM,EAAY,QAAUF,EAAO,SAAS,QAGxC,OAAOE,CACT,CAEO,WAAWU,EAAaC,EAAgB,CAC7C,GAAI,CAACD,EAAK,MAAM,IAAI,MAAM,8BAA8B,EAExD,IAAIP,EAAW,KAAK,SAAS,IAAIO,CAAG,EACpC,OAAIP,KAEeO,EAAI,WAAW,SAAS,GAAKA,EAAI,WAAW,UAAU,IACvD,CAACA,EAAI,SAAS,GAAG,EACjCP,EAAWO,EAEXP,EAAWQ,EAAU,IAAI,IAAID,EAAKC,CAAO,EAAE,KAAO,IAAI,IAAID,CAAG,EAAE,KAGjE,KAAK,SAAS,IAAIA,EAAKP,CAAQ,EACxBA,EACT,CAEQ,kBAAkBE,EAAiBO,EAA8B,CACvE,IAAIC,EAAK,GACT,UAAWC,KAAKF,EAAO,CACrB,GAAI,CAAC,OAAO,UAAU,eAAe,KAAKA,EAAOE,CAAC,EAAG,SACrD,MAAMC,EAAIH,EAAME,CAAC,EACjB,GAAIC,GAAK,KACT,GAAI,MAAM,QAAQA,CAAC,EACjB,QAASC,EAAI,EAAGA,EAAID,EAAE,OAAQC,IACxBH,IAAIA,GAAM,KACdA,GAAM,mBAAmBC,CAAC,EAAI,IAAM,mBAAmB,OAAOC,EAAEC,CAAC,CAAC,CAAC,OAGjEH,IAAIA,GAAM,KACdA,GAAM,mBAAmBC,CAAC,EAAI,IAAM,mBAAmB,OAAOC,CAAC,CAAC,CAEpE,CACA,OAAKF,EACER,GAAWA,EAAQ,SAAS,GAAG,EAAI,IAAM,KAAOQ,EADvCR,CAElB",
6
+ "names": ["CacheManager", "normalizeBody", "normalizeUrl", "mergeHeadersFast", "createTimeoutSignal", "userSignal", "timeoutMs", "meta", "controller", "cleaned", "cleanup", "onUserAbort", "timeoutId", "applyTimeout", "signal", "timeout", "method", "req", "body", "responseType", "defaultHeaders", "config", "pooled", "internalReq", "metaObj", "rawUrl", "finalUrl", "cacheKey", "baseUrl", "cachedUrl", "finalBody", "targetHeaders", "nextStealth", "url", "baseURL", "query", "qs", "k", "v", "j"]
7
+ }
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IT IF OR
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # @hyperttp/core ⚡
2
+
3
+ > High-performance HTTP engine for Node.js and Bun.
4
+
5
+ **English** | [Русский](https://github.com/IT-IF-OR/hyperttp-core/tree/main/lang/ru)
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@hyperttp/core)](https://www.npmjs.com/package/@hyperttp/core)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@hyperttp/core)](https://www.npmjs.com/package/@hyperttp/core)
9
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@hyperttp/core)](https://bundlephobia.com/package/@hyperttp/core)
10
+ [![license](https://img.shields.io/npm/l/@hyperttp/core)](./LICENSE)
11
+ [![typescript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
12
+
13
+ ---
14
+
15
+ ## What is @hyperttp/core?
16
+
17
+ **@hyperttp/core** is a low-level, high-performance HTTP engine designed for building fast,
18
+ extensible HTTP clients and SDKs.
19
+
20
+ It provides:
21
+
22
+ - ⚡ Optimized request execution pipeline
23
+ - 🔀 Runtime-aware transport abstraction
24
+ - 🔌 Plugin lifecycle hooks
25
+ - 🛡️ Safe request/response handling
26
+ - 📦 Zero runtime dependencies
27
+
28
+ `@hyperttp/core` is the foundation that powers **hyperttp**,
29
+ but it can also be used directly to build custom HTTP clients, SDKs, API wrappers and internal tooling.
30
+
31
+ > 💡 Looking for a batteries-included client?
32
+ >
33
+ > Use **[`hyperttp`](https://www.npmjs.com/package/hyperttp)** — it ships with retries, caching,
34
+ > parsing, rate limiting and other plugins already configured.
35
+
36
+ ---
37
+
38
+ ## Why @hyperttp/core?
39
+
40
+ Unlike the native `fetch()`, HyperCore is designed as an extensible HTTP engine rather than just a request API.
41
+
42
+ | Feature | fetch | @hyperttp/core |
43
+ | ---------------------- | :---: | :------------: |
44
+ | Transport abstraction | ❌ | ✅ |
45
+ | Plugin pipeline | ❌ | ✅ |
46
+ | Request/Response hooks | ❌ | ✅ |
47
+ | Custom transports | ❌ | ✅ |
48
+ | Built for SDKs | ⚠️ | ✅ |
49
+ | Safe response cleanup | ❌ | ✅ |
50
+ | Runtime auto-detection | ❌ | ✅ |
51
+
52
+ ---
53
+
54
+ ## Features
55
+
56
+ - ⚡ Optimized hot paths with minimal allocations
57
+ - 🚀 Fast manual query string serialization
58
+ - 🔀 Automatic runtime transport selection
59
+ - 🔌 Extensible plugin system
60
+ - 🧠 Request/response lifecycle hooks
61
+ - 🛡️ Protection against Prototype Pollution
62
+ - 🛡️ CRLF header validation
63
+ - 🛡️ Safe `ArrayBuffer` handling
64
+ - 📦 Zero runtime dependencies
65
+ - 🌊 Native stream support
66
+ - 🧹 Automatic resource cleanup
67
+ - 📈 Designed for high concurrency
68
+
69
+ ---
70
+
71
+ ## Architecture
72
+
73
+ ```text
74
+ hyperttp
75
+ │
76
+ ▼
77
+ @hyperttp/core
78
+ │
79
+ ┌───────────────┴───────────────┐
80
+ │ │
81
+ ▼ ▼
82
+ Transport Layer Plugin Pipeline
83
+ │ │
84
+ ├── BunTransport │
85
+ ├── UndiciTransport ├── Retry
86
+ ├── NodeTransport ├── Cache
87
+ ├── BrowserTransport ├── Parser
88
+ └── Custom Transport └── Пользовательские плагины
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Installation
94
+
95
+ ```bash
96
+ npm install @hyperttp/core
97
+ ```
98
+
99
+ Recommended transports:
100
+
101
+ ```bash
102
+ # Bun
103
+ npm install @hyperttp/transport-bun
104
+
105
+ # Node.js
106
+ npm install @hyperttp/transport-undici
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Quick Start
112
+
113
+ ```ts
114
+ import { HyperCore } from "@hyperttp/core";
115
+
116
+ const http = new HyperCore({
117
+ network: {
118
+ baseURL: "https://api.example.com",
119
+ headers: {
120
+ "X-App-Version": "1.0.0",
121
+ },
122
+ },
123
+ });
124
+
125
+ const response = await http.get("/users", {
126
+ query: {
127
+ page: 1,
128
+ limit: 20,
129
+ },
130
+ });
131
+
132
+ const users = await response.json();
133
+
134
+ console.log(users);
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Error Handling
140
+
141
+ ```ts
142
+ import { HyperCore, HttpClientError, TimeoutError } from "@hyperttp/core";
143
+
144
+ try {
145
+ const res = await http.get("/users");
146
+
147
+ console.log(await res.json());
148
+ } catch (error) {
149
+ if (TimeoutError.isTimeoutError(error)) {
150
+ console.error("Request timed out.");
151
+ }
152
+
153
+ if (HttpClientError.isHttpClientError(error)) {
154
+ console.error(error.statusCode, error.message);
155
+ }
156
+
157
+ throw error;
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Streaming
164
+
165
+ Read a streaming response:
166
+
167
+ ```ts
168
+ const response = await http.stream("https://stream.example.com/audio");
169
+
170
+ const reader = response.body.getReader();
171
+
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+
175
+ if (done) break;
176
+
177
+ console.log(value.length);
178
+ }
179
+ ```
180
+
181
+ Discard a response body without buffering:
182
+
183
+ ```ts
184
+ await http.dump("https://api.example.com/ping");
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Plugins
190
+
191
+ HyperCore exposes request lifecycle hooks that allow intercepting requests, responses and errors.
192
+
193
+ ```ts
194
+ http.use({
195
+ name: "logger",
196
+ priority: 10,
197
+
198
+ onRequest(req) {
199
+ req.meta.start = performance.now();
200
+ },
201
+
202
+ onResponse(res, req) {
203
+ const elapsed = performance.now() - (req.meta.start as number);
204
+
205
+ console.log(`${req.method} ${req.url} -> ${res.status} (${elapsed.toFixed(2)} ms)`);
206
+ },
207
+
208
+ onError(error, req) {
209
+ console.error(`${req.url}: ${error.message}`);
210
+ },
211
+ });
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Transports
217
+
218
+ HyperCore automatically detects the current runtime, or you can provide your own transport.
219
+
220
+ | Transport | Runtime |
221
+ | ---------------- | ------------- |
222
+ | BunTransport | Bun |
223
+ | UndiciTransport | Node.js |
224
+ | NodeTransport | Bun / Node.js |
225
+ | BrowserTransport | Browser |
226
+ | Custom Transport | Any |
227
+
228
+ Example:
229
+
230
+ ```ts
231
+ import { HyperCore } from "@hyperttp/core";
232
+ import { UndiciTransport } from "@hyperttp/transport-undici";
233
+
234
+ const http = new HyperCore({
235
+ customTransport: new UndiciTransport(),
236
+ });
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Performance
242
+
243
+ HyperCore is optimized for sustained high concurrency.
244
+
245
+ Recent stress testing:
246
+
247
+ - **200,000 requests**
248
+ - **1000 concurrent connections**
249
+ - **120 seconds**
250
+ - **0 request errors**
251
+
252
+ Optimizations include:
253
+
254
+ - minimal allocations
255
+ - object reuse
256
+ - optimized semaphore
257
+ - manual query serialization
258
+ - efficient header normalization
259
+ - zero-dependency core
260
+
261
+ ---
262
+
263
+ ## Ecosystem
264
+
265
+ | Package | Description |
266
+ | ---------------------------- | ------------------------ |
267
+ | `hyperttp` | Ready-to-use HTTP client |
268
+ | `@hyperttp/core` | HTTP engine |
269
+ | `@hyperttp/parser` | Response parsing |
270
+ | `@hyperttp/cache` | Cache utilities |
271
+ | `@hyperttp/transport-undici` | Undici transport |
272
+ | `@hyperttp/transport-bun` | Bun transport |
273
+
274
+ ---
275
+
276
+ ## Development
277
+
278
+ ```bash
279
+ bun install
280
+
281
+ bun run lint
282
+ bun run typecheck
283
+ bun run test
284
+ bun run build
285
+ ```
286
+
287
+ ---
288
+
289
+ ## License
290
+
291
+ MIT
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/defaultConfig.ts"],
4
+ "sourcesContent": ["import type { HttpClientOptions } from \"@hyperttp/types\";\n\n/**\n * @ru Конфигурация по умолчанию для HTTP-клиента Hyperttp.\n * @en Default configuration for the Hyperttp HTTP client.\n */\nexport const defaultConfig: HttpClientOptions = {\n network: {\n timeout: 30000,\n maxConcurrent: 128,\n pipelining: 0,\n keepAliveTimeout: 5000,\n followRedirects: true,\n maxRedirects: 5,\n rejectUnauthorized: true,\n },\n retry: {\n maxRetries: 0,\n baseDelay: 100,\n maxDelay: 5000,\n jitter: true,\n },\n verbose: false,\n};\n"],
5
+ "mappings": "aAMO,aAAM,cAAmC,CAC9C,QAAS,CACP,QAAS,IACT,cAAe,IACf,WAAY,EACZ,iBAAkB,IAClB,gBAAiB,GACjB,aAAc,EACd,mBAAoB,IAEtB,MAAO,CACL,WAAY,EACZ,UAAW,IACX,SAAU,IACV,OAAQ,IAEV,QAAS",
6
+ "names": []
7
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@hyperttp/core",
3
+ "version": "1.5.6",
4
+ "description": "High-performance, extensible HTTP client for Node.js, Bun, Deno and Browser",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./index.js",
8
+ "types": "./index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "import": "./index.js"
13
+ }
14
+ },
15
+ "keywords": [
16
+ "http",
17
+ "client",
18
+ "high-performance",
19
+ "undici",
20
+ "fetch",
21
+ "bun",
22
+ "deno",
23
+ "isomorphic",
24
+ "hyperttp"
25
+ ],
26
+ "author": "dirold2",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/IT-IF-OR/hyperttp-core.git"
31
+ },
32
+ "peerDependencies": {
33
+ "@hyperttp/types": "^0.2.5",
34
+ "@hyperttp/transport-bun": "^0.2.4",
35
+ "@hyperttp/transport-undici": "^0.2.5",
36
+ "hcacher": "^0.1.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@hyperttp/types": {
40
+ "optional": false
41
+ },
42
+ "@hyperttp/transport-bun": {
43
+ "optional": true
44
+ },
45
+ "@hyperttp/transport-undici": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "private": false
50
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/transports/browser.ts"],
4
+ "sourcesContent": ["import type {\n HttpClientOptions,\n HyperTransport,\n TransportRequest,\n TransportResponse,\n TransportResponsePayload,\n} from \"@hyperttp/types\";\n\n/**\n * @ru Конфигурация транспорта для браузера.\n * @en Browser transport configuration.\n */\nexport interface BrowserTransportConfig extends HttpClientOptions {\n /**\n * @ru Базовый URL для относительных путей. По умолчанию — window.location.origin.\n * @en Base URL for relative paths. Defaults to window.location.origin.\n */\n baseUrl?: string;\n}\n\n/**\n * @ru Интерфейс для безопасного доступа к мета-окружению сборщиков (Vite, Webpack и др.).\n * @en Interface for safe access to bundler meta-environment (Vite, Webpack, etc.).\n */\ninterface ImportMetaEnv {\n env?: {\n PROD?: boolean;\n [key: string]: unknown;\n };\n}\n\n/**\n * @ru Реализация транспорта для браузера с использованием глобального fetch API.\n * Оптимизирован для минимального размера и максимальной совместимости.\n * @en Browser transport implementation using the global fetch API.\n * Optimized for minimal size and maximum compatibility.\n */\nexport class BrowserTransport implements HyperTransport {\n private readonly isProduction: boolean;\n private readonly cleanBaseUrl: string;\n\n /**\n * @ru Создаёт экземпляр BrowserTransport.\n * @en Creates a BrowserTransport instance.\n * @param config - Transport configuration.\n */\n constructor(public config: BrowserTransportConfig) {\n const isLocalhost =\n typeof window !== \"undefined\" &&\n [\"localhost\", \"127.0.0.1\", \"::1\"].includes(window.location.hostname);\n\n const hasProcess = typeof process !== \"undefined\" && process.env;\n const hasMeta = typeof import.meta !== \"undefined\";\n\n this.isProduction =\n !isLocalhost &&\n ((hasProcess && process.env.NODE_ENV === \"production\") ||\n (hasMeta && (import.meta as unknown as ImportMetaEnv).env?.PROD === true));\n\n const base = config.baseUrl ?? (typeof window !== \"undefined\" ? window.location.origin : \"\");\n this.cleanBaseUrl = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n }\n\n /**\n * @ru Выполняет HTTP-запрос через глобальный fetch.\n * @en Executes an HTTP request via the global fetch.\n * @param req - The normalized transport request.\n * @returns Promise resolving to the transport response.\n */\n public async execute(req: TransportRequest): Promise<TransportResponse> {\n const urlStr = req.url;\n\n const isAbsolute =\n urlStr.startsWith(\"http://\") || urlStr.startsWith(\"https://\") || urlStr.startsWith(\"//\");\n\n const fullUrl = isAbsolute\n ? urlStr\n : urlStr.charCodeAt(0) === 47\n ? this.cleanBaseUrl + urlStr\n : this.cleanBaseUrl + \"/\" + urlStr;\n\n if (this.isProduction && fullUrl.includes(\"//localhost\")) {\n throw new Error(\"Localhost URL detected in production environment\");\n }\n\n const finalHeaders: Record<string, string> = Object.create(null);\n if (req.headers) {\n if (req.headers instanceof Headers) {\n req.headers.forEach((value, key) => {\n finalHeaders[key.toLowerCase()] = value;\n });\n } else if (Array.isArray(req.headers)) {\n for (let i = 0; i < req.headers.length; i++) {\n const pair = req.headers[i] as unknown as [string, string] | undefined;\n if (pair && typeof pair[0] === \"string\") {\n finalHeaders[pair[0].toLowerCase()] = String(pair[1]);\n }\n }\n } else {\n const src = req.headers as Record<string, unknown>;\n for (const key in src) {\n const value = src[key];\n if (value != null) {\n finalHeaders[key.toLowerCase()] = Array.isArray(value)\n ? value.join(\", \")\n : String(value);\n }\n }\n }\n }\n\n const res = await globalThis.fetch(fullUrl, {\n method: req.method,\n headers: finalHeaders as HeadersInit,\n body: req.body as BodyInit | null,\n signal: req.signal,\n });\n\n const resHeaders: Record<string, string> = Object.create(null);\n res.headers.forEach((value, key) => {\n resHeaders[key] = value;\n });\n\n return {\n status: res.status,\n url: res.url,\n body: res.body as unknown as TransportResponsePayload,\n headers: resHeaders,\n _raw: res,\n } as TransportResponse;\n }\n}\n"],
5
+ "mappings": "aAqCM,aAAO,gBAAgB,CASR,OARF,aACA,aAOjB,YAAmBA,EAA8B,aAA9BA,EACjB,MAAMC,EACJ,OAAO,OAAW,KAClB,CAAC,YAAa,YAAa,KAAK,EAAE,SAAS,OAAO,SAAS,QAAQ,EAE/DC,EAAa,OAAO,QAAY,KAAe,QAAQ,IACvDC,EAAU,OAAO,YAAgB,IAEvC,KAAK,aACH,CAACF,IACCC,GAAc,IACbC,GAAY,YAAyC,KAAK,OAAS,IAExE,MAAMC,EAAOJ,EAAO,UAAY,OAAO,OAAW,IAAc,OAAO,SAAS,OAAS,IACzF,KAAK,aAAeI,EAAK,SAAS,GAAG,EAAIA,EAAK,MAAM,EAAG,EAAE,EAAIA,CAC/D,CAQO,MAAM,QAAQC,EAAqB,CACxC,MAAMC,EAASD,EAAI,IAKbE,EAFJD,EAAO,WAAW,SAAS,GAAKA,EAAO,WAAW,UAAU,GAAKA,EAAO,WAAW,IAAI,EAGrFA,EACAA,EAAO,WAAW,CAAC,IAAM,GACvB,KAAK,aAAeA,EACpB,KAAK,aAAe,IAAMA,EAEhC,GAAI,KAAK,cAAgBC,EAAQ,SAAS,aAAa,EACrD,MAAM,IAAI,MAAM,kDAAkD,EAGpE,MAAMC,EAAuC,OAAO,OAAO,IAAI,EAC/D,GAAIH,EAAI,QACN,GAAIA,EAAI,mBAAmB,QACzBA,EAAI,QAAQ,QAAQ,CAACI,EAAOC,IAAO,CACjCF,EAAaE,EAAI,YAAW,CAAE,EAAID,CACpC,CAAC,UACQ,MAAM,QAAQJ,EAAI,OAAO,EAClC,QAASM,EAAI,EAAGA,EAAIN,EAAI,QAAQ,OAAQM,IAAK,CAC3C,MAAMC,EAAOP,EAAI,QAAQM,CAAC,EACtBC,GAAQ,OAAOA,EAAK,CAAC,GAAM,WAC7BJ,EAAaI,EAAK,CAAC,EAAE,YAAW,CAAE,EAAI,OAAOA,EAAK,CAAC,CAAC,EAExD,KACK,CACL,MAAMC,EAAMR,EAAI,QAChB,UAAWK,KAAOG,EAAK,CACrB,MAAMJ,EAAQI,EAAIH,CAAG,EACjBD,GAAS,OACXD,EAAaE,EAAI,YAAW,CAAE,EAAI,MAAM,QAAQD,CAAK,EACjDA,EAAM,KAAK,IAAI,EACf,OAAOA,CAAK,EAEpB,CACF,CAGF,MAAMK,EAAM,MAAM,WAAW,MAAMP,EAAS,CAC1C,OAAQF,EAAI,OACZ,QAASG,EACT,KAAMH,EAAI,KACV,OAAQA,EAAI,OACb,EAEKU,EAAqC,OAAO,OAAO,IAAI,EAC7D,OAAAD,EAAI,QAAQ,QAAQ,CAACL,EAAOC,IAAO,CACjCK,EAAWL,CAAG,EAAID,CACpB,CAAC,EAEM,CACL,OAAQK,EAAI,OACZ,IAAKA,EAAI,IACT,KAAMA,EAAI,KACV,QAASC,EACT,KAAMD,EAEV",
6
+ "names": ["config", "isLocalhost", "hasProcess", "hasMeta", "base", "req", "urlStr", "fullUrl", "finalHeaders", "value", "key", "i", "pair", "src", "res", "resHeaders"]
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/transports/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAyB1D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,OAK1B,CAAC;AAiLL;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BzF;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACI,SAAS,EAAE,cAAc,GAAG,IAAI,CAAQ;IAE/C;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAwC;IAEvD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAoB;IAElC;;;;;OAKG;IACH,YAAY,MAAM,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,cAAc,EAU7D;IAED;;;;OAIG;IACI,OAAO,IAAI,cAAc,GAAG,IAAI,CAEtC;IAED;;;;;;OAMG;IACI,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAUvC;IAED;;;;OAIG;IACI,GAAG,IAAI,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAErD;IAED;;;;OAIG;IACI,SAAS,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAWhD;IAED;;;;;;;OAOG;IACU,OAAO,CAAC,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBnD;CACF"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/transports/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAyB1D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,OAK1B,CAAC;AA4JL;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BzF;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACI,SAAS,EAAE,cAAc,GAAG,IAAI,CAAQ;IAE/C;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAwC;IAEvD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAoB;IAElC;;;;;OAKG;IACH,YAAY,MAAM,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,cAAc,EAU7D;IAED;;;;OAIG;IACI,OAAO,IAAI,cAAc,GAAG,IAAI,CAEtC;IAED;;;;;;OAMG;IACI,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAUvC;IAED;;;;OAIG;IACI,GAAG,IAAI,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAErD;IAED;;;;OAIG;IACI,SAAS,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAOhD;IAED;;;;;;;OAOG;IACU,OAAO,CAAC,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBnD;CACF"}
@@ -0,0 +1,7 @@
1
+ "use strict";export const CURRENT_RUNTIME=typeof Bun<"u"?"bun":typeof Deno<"u"?"deno":typeof process<"u"&&process.versions?.node?"node":"browser";const i=[{name:"Bun",runtime:["bun"],pkg:"@hyperttp/transport-bun",export:"BunTransport",priority:100},{name:"Deno",runtime:["deno"],pkg:"@hyperttp/transport-deno",export:"DenoTransport",priority:95},{name:"Undici",runtime:["node"],pkg:"@hyperttp/transport-undici",export:"UndiciTransport",priority:90},{name:"Browser",runtime:["browser"],pkg:"./browser.js",export:"BrowserTransport",priority:80},{name:"Node",runtime:["node","bun","deno"],pkg:"./node.js",export:"NodeTransport",priority:10}];i.forEach(Object.freeze),Object.freeze(i);const f=new Set(i.map(t=>t.pkg)),u=i.filter(t=>t.runtime.includes(CURRENT_RUNTIME)).sort((t,r)=>r.priority-t.priority);let s=null;function c(t){if(!t||typeof t!="object")return!1;const r=t,e=r.code;if(e==="ERR_MODULE_NOT_FOUND"||e==="MODULE_NOT_FOUND")return!0;const n=t instanceof Error?t.message:String(r.message??"");return n.includes("Cannot find module")||n.includes("Failed to resolve")||n.includes("Failed to load")}function d(t,r,e){const n=typeof process<"u"&&!1,o=typeof process<"u"&&process.env?.DEBUG==="1",l=t.includes(CURRENT_RUNTIME);if(o||n&&l){const p=`Failed to load preferred transport "${t}"`,a="Falling back to alternative transport...";e?.logger?(e.logger("warn",`[Hyperttp Debug] ${p}`,r),e.logger("warn",`[Hyperttp Debug] ${a}`)):(console.warn(`
2
+ \u26A0\uFE0F [Hyperttp Debug] ${p}:`),console.error(r),console.warn(`${a}
3
+ `))}}async function y(t,r,e){if(!f.has(t))throw new Error(`[Hyperttp Security] Blocked untrusted transport import attempt: ${t}`);try{const n=await import(t),o=n[r]??n.default;return typeof o=="function"?o:null}catch(n){if(c(n))return d(t,n,e),null;throw n}}export async function resolveTransport(t){if(t.customTransport)return t.customTransport;if(s)return new s(t);const r=[];for(let e=0;e<u.length;e++){const n=u[e];try{const o=await y(n.pkg,n.export,t);if(!o){r.push(`${n.pkg} (missing export or not installed)`);continue}return s=o,new o(t)}catch(o){if(c(o)){r.push(`${n.pkg} (not installed)`);continue}throw new Error(`[Hyperttp] Transport crash in ${n.pkg}: ${o?.message}`,{cause:o})}}throw new Error(`No compatible transport for runtime="${CURRENT_RUNTIME}".
4
+ Failures:
5
+ - ${r.join(`
6
+ - `)}`)}export class TransportManager{transport=null;promise=null;config;constructor(r,e){this.config=r,e?this.transport=e:r.customTransport?this.transport=r.customTransport:s&&(this.transport=new s(r))}getSync(){return this.transport}ensure(){return this.transport!==null?this.promise||(this.promise=Promise.resolve(this.transport)):this.promise!==null?this.promise:this.promise=resolveTransport(this.config).then(r=>(this.transport=r,r))}get(){return this.transport??this.ensure()}setConfig(r){this.config=r;const e=this.transport;typeof e?.setConfig=="function"&&e.setConfig(r)}async destroy(r=!0){const e=this.transport;if(e)try{if(r&&typeof e.close=="function"){await e.close();return}typeof e.destroy=="function"&&await e.destroy()}finally{this.transport=null,this.promise=null}}}
7
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/transports/manager.ts"],
4
+ "sourcesContent": ["import type { HttpClientOptions, HyperTransport } from \"@hyperttp/types\";\n\n/**\n * @ru Поддерживаемые среды выполнения для автоматического выбора транспорта.\n * @en Supported runtime environments for automatic transport selection.\n */\nexport type Runtime = \"bun\" | \"node\" | \"deno\" | \"browser\";\n\n/**\n * @ru Конструктор транспорта, принимающий конфигурацию клиента.\n * @en Transport constructor accepting client configuration.\n */\ntype TransportCtor = new (config: HttpClientOptions) => HyperTransport;\n\n/**\n * @ru Описание транспорта: имя, поддерживаемые среды, пакет, экспорт и приоритет.\n * @en Transport descriptor: name, supported runtimes, package, export, and priority.\n */\ntype TransportDef = {\n /** @ru Имя транспорта для логирования. @en Transport name for logging. */\n readonly name: string;\n /** @ru Список сред, в которых транспорт может работать. @en List of runtimes where the transport can operate. */\n readonly runtime: readonly Runtime[];\n /** @ru Имя пакета npm или относительный путь для встроенных транспортов. @en npm package name or relative path for built-in transports. */\n readonly pkg: string;\n /** @ru Имя экспортируемого класса транспорта. @en Exported transport class name. */\n readonly export: string;\n /** @ru Приоритет выбора (выше значение = раньше попытка загрузки). @en Selection priority (higher value = earlier load attempt). */\n readonly priority: number;\n};\n\n/**\n * @ru Определяет текущую среду выполнения при загрузке модуля.\n * Порядок проверки: Bun → Deno → Node.js → Browser.\n * @en Detects the current runtime at module load time.\n * Detection order: Bun → Deno → Node.js → Browser.\n */\nexport const CURRENT_RUNTIME: Runtime = (() => {\n if (typeof Bun !== \"undefined\") return \"bun\";\n if (typeof Deno !== \"undefined\") return \"deno\";\n if (typeof process !== \"undefined\" && process.versions?.node) return \"node\";\n return \"browser\";\n})();\n\n/**\n * @ru Внутренний реестр всех доступных транспортов с приоритетами.\n * Заморожен для предотвращения модификаций в runtime.\n * @en Internal registry of all available transports with priorities.\n * Frozen to prevent runtime modifications.\n */\nconst INTERNAL_TRANSPORTS: readonly TransportDef[] = [\n {\n name: \"Bun\",\n runtime: [\"bun\"],\n pkg: \"@hyperttp/transport-bun\",\n export: \"BunTransport\",\n priority: 100,\n },\n {\n name: \"Deno\",\n runtime: [\"deno\"],\n pkg: \"@hyperttp/transport-deno\",\n export: \"DenoTransport\",\n priority: 95,\n },\n {\n name: \"Undici\",\n runtime: [\"node\"],\n pkg: \"@hyperttp/transport-undici\",\n export: \"UndiciTransport\",\n priority: 90,\n },\n {\n name: \"Browser\",\n runtime: [\"browser\"],\n pkg: \"./browser.js\",\n export: \"BrowserTransport\",\n priority: 80,\n },\n {\n name: \"Node\",\n runtime: [\"node\", \"bun\", \"deno\"],\n pkg: \"./node.js\",\n export: \"NodeTransport\",\n priority: 10,\n },\n];\n\nINTERNAL_TRANSPORTS.forEach(Object.freeze);\nObject.freeze(INTERNAL_TRANSPORTS);\n\n/**\n * @ru Белый список разрешённых пакетов для защиты от несанкционированного импорта.\n * @en Whitelist of allowed packages to protect against unauthorized imports.\n */\nconst ALLOWED_PACKAGES = new Set<string>(INTERNAL_TRANSPORTS.map((t) => t.pkg));\n\n/**\n * @ru Отфильтрованный и отсортированный список транспортов для текущей среды.\n * Вычисляется один раз при загрузке модуля.\n * @en Filtered and sorted list of transports for the current runtime.\n * Computed once at module load time.\n */\nconst CANDIDATES = INTERNAL_TRANSPORTS.filter((t) => t.runtime.includes(CURRENT_RUNTIME)).sort(\n (a, b) => b.priority - a.priority,\n);\n\n/**\n * @ru Кэш разрешённого конструктора транспорта для избежания повторного разрешения.\n * @en Cached resolved transport constructor to avoid repeated resolution.\n */\nlet RESOLVED_RUNTIME_CTOR: TransportCtor | null = null;\n\n/**\n * @ru Проверяет, является ли ошибкой отсутствия модуля.\n * Поддерживает коды ERR_MODULE_NOT_FOUND и MODULE_NOT_FOUND,\n * а также сообщения об ошибках (для совместимости с Bun и Deno).\n * @en Checks if the error is a module-not-found error.\n * Supports ERR_MODULE_NOT_FOUND and MODULE_NOT_FOUND codes,\n * as well as error messages (for Bun and Deno compatibility).\n * @param err - The error to check.\n * @returns True if the error indicates a missing module.\n */\nfunction isModuleNotFoundError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n\n const e = err as Record<string, unknown>;\n const code = e.code as string | undefined;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") return true;\n\n const msg = err instanceof Error ? err.message : String(e.message ?? \"\");\n return (\n msg.includes(\"Cannot find module\") ||\n msg.includes(\"Failed to resolve\") ||\n msg.includes(\"Failed to load\")\n );\n}\n\n/**\n * @ru Логирует информацию о неудачной попытке загрузки транспорта.\n * Активируется в режиме DEBUG=1 или при ошибке нативного транспорта в dev-окружении.\n * @en Logs information about failed transport load attempts.\n * Activated in DEBUG=1 mode or when native transport fails in dev environment.\n * @param pkg - Package name that failed to load.\n * @param err - The error that occurred.\n * @param config - Optional client configuration for custom logger.\n */\nfunction logDebugFallback(pkg: string, err: unknown, config?: HttpClientOptions): void {\n const isDev = typeof process !== \"undefined\" && process.env?.NODE_ENV === \"development\";\n const isDebug = typeof process !== \"undefined\" && process.env?.DEBUG === \"1\";\n const isNativeTransport = pkg.includes(CURRENT_RUNTIME);\n\n if (isDebug || (isDev && isNativeTransport)) {\n const primaryMessage = `Failed to load preferred transport \"${pkg}\"`;\n const fallbackMessage = `Falling back to alternative transport...`;\n\n if (config?.logger) {\n config.logger(\"warn\", `[Hyperttp Debug] ${primaryMessage}`, err);\n config.logger(\"warn\", `[Hyperttp Debug] ${fallbackMessage}`);\n } else {\n console.warn(`\\n⚠️ [Hyperttp Debug] ${primaryMessage}:`);\n console.error(err);\n console.warn(`${fallbackMessage}\\n`);\n }\n }\n}\n\n/**\n * @ru Загружает конструктор транспорта из пакета или относительного пути.\n * @en Loads a transport constructor from a package or relative path.\n * @param pkg - Package name or relative path.\n * @param exportName - Name of the exported transport class.\n * @param config - Optional client configuration for debug logging.\n * @returns Transport constructor or null if not found.\n * @throws Error if the package is not in the whitelist.\n */\nasync function loadCtor(\n pkg: string,\n exportName: string,\n config?: HttpClientOptions,\n): Promise<TransportCtor | null> {\n if (!ALLOWED_PACKAGES.has(pkg)) {\n throw new Error(`[Hyperttp Security] Blocked untrusted transport import attempt: ${pkg}`);\n }\n\n try {\n const mod = (await import(pkg)) as Record<string, unknown>;\n const candidate = mod[exportName] ?? mod.default;\n return typeof candidate === \"function\" ? (candidate as TransportCtor) : null;\n } catch (err) {\n if (isModuleNotFoundError(err)) {\n logDebugFallback(pkg, err, config);\n return null;\n }\n throw err;\n }\n}\n\n/**\n * @ru Разрешает и создаёт оптимальный транспорт для текущей среды.\n * Перебирает кандидатов по приоритету, кэширует первый успешный результат.\n * @en Resolves and creates the optimal transport for the current runtime.\n * Iterates candidates by priority, caches the first successful result.\n * @param config - Client configuration options.\n * @returns Promise resolving to the instantiated transport.\n * @throws Error if no compatible transport is found.\n */\nexport async function resolveTransport(config: HttpClientOptions): Promise<HyperTransport> {\n if (config.customTransport) return config.customTransport;\n if (RESOLVED_RUNTIME_CTOR) return new RESOLVED_RUNTIME_CTOR(config);\n\n const failures: string[] = [];\n\n for (let i = 0; i < CANDIDATES.length; i++) {\n const t = CANDIDATES[i]!;\n try {\n const ctor = await loadCtor(t.pkg, t.export, config);\n if (!ctor) {\n failures.push(`${t.pkg} (missing export or not installed)`);\n continue;\n }\n\n RESOLVED_RUNTIME_CTOR = ctor;\n return new ctor(config);\n } catch (err) {\n if (isModuleNotFoundError(err)) {\n failures.push(`${t.pkg} (not installed)`);\n continue;\n }\n throw new Error(`[Hyperttp] Transport crash in ${t.pkg}: ${(err as Error)?.message}`, {\n cause: err,\n });\n }\n }\n\n throw new Error(\n `No compatible transport for runtime=\"${CURRENT_RUNTIME}\".\\nFailures:\\n- ${failures.join(\"\\n- \")}`,\n );\n}\n\n/**\n * @ru Менеджер транспорта с ленивой инициализацией и кэшированием.\n * Поддерживает синхронный доступ, отложенную инициализацию и корректное завершение.\n * @en Transport manager with lazy initialization and caching.\n * Supports synchronous access, deferred initialization, and graceful shutdown.\n */\nexport class TransportManager {\n /**\n * @ru Текущий экземпляр транспорта (null до инициализации).\n * @en Current transport instance (null before initialization).\n */\n public transport: HyperTransport | null = null;\n\n /**\n * @ru Промис отложенной инициализации транспорта.\n * @en Deferred transport initialization promise.\n */\n private promise: Promise<HyperTransport> | null = null;\n\n /**\n * @ru Конфигурация клиента для создания транспорта.\n * @en Client configuration for transport creation.\n */\n private config: HttpClientOptions;\n\n /**\n * @ru Создаёт менеджер транспорта с опциональным пользовательским транспортом.\n * @en Creates a transport manager with an optional custom transport.\n * @param config - Client configuration options.\n * @param custom - Optional custom transport instance.\n */\n constructor(config: HttpClientOptions, custom?: HyperTransport) {\n this.config = config;\n\n if (custom) {\n this.transport = custom;\n } else if (config.customTransport) {\n this.transport = config.customTransport;\n } else if (RESOLVED_RUNTIME_CTOR) {\n this.transport = new RESOLVED_RUNTIME_CTOR(config);\n }\n }\n\n /**\n * @ru Синхронно возвращает текущий транспорт или null, если не инициализирован.\n * @en Synchronously returns the current transport or null if not initialized.\n * @returns The transport instance or null.\n */\n public getSync(): HyperTransport | null {\n return this.transport;\n }\n\n /**\n * @ru Гарантирует наличие транспорта, инициализируя его при необходимости.\n * Повторные вызовы возвращают один и тот же промис.\n * @en Ensures a transport exists, initializing it if necessary.\n * Repeated calls return the same promise.\n * @returns Promise resolving to the transport instance.\n */\n public ensure(): Promise<HyperTransport> {\n if (this.transport !== null) {\n return this.promise || (this.promise = Promise.resolve(this.transport));\n }\n if (this.promise !== null) return this.promise;\n\n return (this.promise = resolveTransport(this.config).then((t) => {\n this.transport = t;\n return t;\n }));\n }\n\n /**\n * @ru Возвращает транспорт синхронно или промис, если требуется инициализация.\n * @en Returns the transport synchronously or a promise if initialization is needed.\n * @returns The transport instance or a promise resolving to it.\n */\n public get(): HyperTransport | Promise<HyperTransport> {\n return this.transport ?? this.ensure();\n }\n\n /**\n * @ru Обновляет конфигурацию транспорта, если он поддерживает setConfig.\n * @en Updates the transport configuration if it supports setConfig.\n * @param config - New client configuration options.\n */\n public setConfig(config: HttpClientOptions): void {\n this.config = config;\n\n const t = this.transport as { setConfig?: (cfg: HttpClientOptions) => void } | null;\n if (typeof t?.setConfig === \"function\") {\n t.setConfig(config);\n }\n }\n\n /**\n * @ru Завершает работу транспорта и освобождает ресурсы.\n * В graceful-режиме вызывает close(), иначе — destroy().\n * @en Shuts down the transport and releases resources.\n * In graceful mode calls close(), otherwise calls destroy().\n * @param graceful - If true, waits for active requests to complete.\n * @returns Promise that resolves when shutdown is complete.\n */\n public async destroy(graceful = true): Promise<void> {\n const t = this.transport as {\n close?: () => Promise<void> | void;\n destroy?: () => Promise<void> | void;\n } | null;\n\n if (!t) return;\n\n try {\n if (graceful && typeof t.close === \"function\") {\n await t.close();\n return;\n }\n if (typeof t.destroy === \"function\") {\n await t.destroy();\n }\n } finally {\n this.transport = null;\n this.promise = null;\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["INTERNAL_TRANSPORTS", "ALLOWED_PACKAGES", "CANDIDATES", "a", "b", "RESOLVED_RUNTIME_CTOR", "isModuleNotFoundError", "err", "e", "code", "msg", "logDebugFallback", "pkg", "config", "isDev", "isDebug", "isNativeTransport", "primaryMessage", "fallbackMessage", "loadCtor", "exportName", "mod", "candidate", "failures", "i", "t", "ctor", "custom", "graceful"]
7
+ }