import { ReadStream } from 'node:fs'; import * as type_fest from 'type-fest'; import { Except, SetRequired, SetOptional, AbstractConstructor, Constructor } from 'type-fest'; interface FrameRetry { /** Maximum number of retry attempts. */ max: number; /** * Returns the retry interval in milliseconds based on the attempt count and elapsed time. * Use this when you want to vary the interval per attempt (e.g. exponential backoff). * * @param retry Current retry attempt number * @param totalDuration Total elapsed time (ms) since the first attempt * @param eachDuration Duration (ms) of the most recent attempt */ getInterval?: (retry: number, totalDuration: number, eachDuration: number) => number; /** * Fixed retry interval in milliseconds. * Ignored when `getInterval` is configured. */ interval?: number; /** * When true, honours the `Retry-After` response header as the retry delay. * Takes precedence over `interval` and `getInterval`. * Defaults to true. */ useRetryAfter?: boolean; } interface FrameInternal { query?: Record; header?: Record; body?: unknown; param?: Record; retry: FrameRetry & { try: number; }; startAt: Date; eachStartAt: Date; endAt: Date; } type Method = 'get' | 'GET' | 'delete' | 'DELETE' | 'head' | 'HEAD' | 'options' | 'OPTIONS' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH' | 'purge' | 'PURGE' | 'link' | 'LINK' | 'unlink' | 'UNLINK' | 'SEARCH' | 'search'; type ValidationResult = { valid: true; } | { valid: false; error: TError[]; }; type ValidationResultType = 'exception' | 'value'; declare class BaseValidator { #private; constructor({ type }: { type: ValidationResultType; }); get type(): ValidationResultType; /** override your data getter */ getData(reply: TOrigin): TData; validator(_data: TData): ValidationResult | Promise>; validate(reply: TOrigin): Promise>; } /** * Authorization data that can be used by security providers. * * When a function is provided, it is resolved asynchronously at request time via `_execute()`. * This enables lazy loading from environment variables, secret managers, etc. * * @example * ```typescript * // Static bearer token * const bearerAuth: AuthorizationData = 'my-bearer-token'; * * // Lazy resolution from dotenv * const lazyAuth: AuthorizationData = () => process.env.API_TOKEN ?? ''; * * // Async from secret manager * const asyncAuth: AuthorizationData = async () => vault.getSecret('api-token'); * * // Basic auth credentials * const basicAuth: AuthorizationData = { username: 'user', password: 'pass' }; * * // API key object * const apiKeyAuth: AuthorizationData = { key: 'x-api-key', value: 'my-api-key' }; * * // Access token (e.g. OAuth2 bearer) * const tokenAuth: AuthorizationData = { accessToken: 'token-xyz' }; * ``` */ type AuthorizationData = string | (() => string | Promise) | { username: string; password: string; } | { key: string; value: string; } | { accessToken: string; } | Record; interface JinBasicAuth { username: string; password: string; } /** * Security context that contains authentication information to be applied to HTTP requests * * @example * ```typescript * const context: SecurityContext = { * headers: { * 'Authorization': 'Bearer token123', * 'X-API-Key': 'api-key-value' * }, * auth: { * username: 'user', * password: 'pass' * }, * queries: { * 'api_key': 'query-param-key' * } * }; * ``` */ interface SecurityContext { /** HTTP headers to be added to the request */ headers?: Record; /** Basic authentication credentials */ auth?: JinBasicAuth; /** Query parameters to be added to the request */ queries?: Record; } /** * Security provider interface for handling different authentication schemes * * Providers implement specific authentication methods like Bearer tokens, API keys, OAuth2, etc. * They can be used individually or combined to support multiple authentication schemes per endpoint. * * @example * ```typescript * class CustomBearerProvider implements SecurityProvider { * readonly type = 'http'; * readonly name = 'custom-bearer'; * * createContext(authorization?: AuthorizationData, dynamicKey?: string): SecurityContext { * const token = dynamicKey ?? authorization; * return { * headers: { * Authorization: `Bearer ${token}` * } * }; * } * } * * // Usage in frame * @Get({ * host: 'https://api.example.com', * security: new CustomBearerProvider(), * authorization: 'my-token' * }) * class MyFrame extends JinFrame {} * ``` */ interface SecurityProvider { /** The type of security scheme (following OpenAPI 3.0 security scheme types) */ readonly type: 'api-key' | 'http' | 'oauth2' | 'open-id-connect'; /** Unique name for this security provider instance */ readonly name: string; /** * Create authentication context for the request * * @param authorization - The authorization data configured in the frame * @param dynamicKey - Dynamic key passed at runtime (takes precedence over authorization) * @returns Security context with headers, auth, and params to be applied to the request * * @example * ```typescript * // Called with frame authorization * const context1 = provider.createContext('bearer-token'); * * // Called with dynamic key (overrides frame authorization) * const context2 = provider.createContext('frame-token', 'runtime-token'); * ``` */ createContext: (authorization?: AuthorizationData, dynamicKey?: string) => SecurityContext; } type Milliseconds = number; interface FrameOption { /** * Base URL of the API endpoint. * * You may pass the full `protocol://host/path` in this field alone, but separating `host` * and `path` is recommended so that a parent class can define the host and child classes * only override the path. */ host?: string | (() => string); /** * Path prefix of API Request endpoint. * * For example, you can set the relative path defined in the OpenAPI Spec's servers field to pathPrefix. * When set, this path will be prepended to the pathname when generating the Request URL. * * @example * ```typescript * // OpenAPI servers configuration: * // servers: [{ "url": "/api/v3" }] * pathPrefix: '/api/v3' * path: '/users/{id}' * // Final URL: https://example.com/api/v3/users/123 * * // Multiple path prefixes for different services * pathPrefix: '/user-service/v1' // User API * pathPrefix: '/order-service/v2' // Order API * ``` */ pathPrefix?: string | (() => string); /** Path of the API endpoint. Supports path-parameter placeholders such as `:id`. */ path?: string | (() => string); /** HTTP method of the endpoint. */ method: Method; /** Content-Type of the request body. */ contentType: string; /** User-Agent string sent with each request. */ userAgent?: string; /** Custom request body that bypasses decorator-based body assembly. */ customBody?: unknown; /** Retry configuration. */ retry?: FrameRetry; /** Request timeout in milliseconds. */ timeout?: Milliseconds; /** * Security providers for authentication. * Can be a single provider or an array of providers for multiple authentication schemes. */ security?: SecurityProvider | SecurityProvider[]; /** * Authorization data passed to security providers. * Used by providers to generate authentication headers or query parameters. */ authorization?: AuthorizationData; /** * Validators for pass and fail responses. * - pass: runs when HTTP response is successful; can throw JinValidationError if type is 'exception' * - fail: runs when HTTP response fails; only sets valid flag, never throws JinValidationError */ validators?: { pass?: BaseValidator; fail?: BaseValidator; }; /** * Determines whether a response status code is considered successful. * When not provided, defaults to `response.ok` (i.e. 200–299). * * Setting this at the decorator level applies it as the default for all executions of the frame. * A `validateStatus` passed to `_execute()` takes precedence over this value. * * @example * ```typescript * // Treat 404 as a success (e.g. idempotent DELETE) * validateStatus: (ok, status) => ok || status === 404 * ``` */ validateStatus?: (ok: boolean, status: number) => boolean; /** * When enabled, identical concurrent requests are deduplicated — only one network call * is made and the result is shared with all callers. */ dedupe?: boolean; /** * When true, clones the raw Response before consuming the body. * Allows reading resp.raw after the stream is consumed. * Incurs memory overhead — use only when raw access is needed. */ cloneRaw?: boolean; /** * Custom response body deserializer. * * Receives the raw response text and returns the parsed value. * Useful for handling non-standard JSON (e.g. BigInt values). * If not provided, JSON.parse is used with a plain-string fallback. */ deserialize?: (text: string) => unknown; } declare function getFrameInternalData(option?: Partial>): FrameInternal; declare function getFrameOption(method: Method, option?: Partial>): FrameOption; interface JinFrameCreateConfig { validateStatus?: (ok: boolean, status: number) => boolean; } /** * Configuration for JinFrame. */ interface JinFrameRequestConfig { /** * User-Agent string sent with each request. * Override this to identify your client (e.g. "my-app/1.0.0"). */ userAgent?: string; url?: string; /** * Overrides the host defined in the frame decorator for this request only. * Supports URI template syntax (e.g. `https://{tenant}.api.example.com`). */ host?: string; /** * Overrides the pathPrefix defined in the frame decorator for this request only. */ pathPrefix?: string; /** * Overrides the path defined in the frame decorator for this request only. * Supports URI template syntax (e.g. `/users/{id}`). */ path?: string; customBody?: unknown; auth?: JinBasicAuth; timeout?: Milliseconds; /** * AbortSignal to cancel the request. * Combined with the timeout signal when both are provided. */ signal?: AbortSignal; /** * Dynamic authorization data that will be passed to security providers * This will override the authorization data configured in the frame */ dynamicAuth?: AuthorizationData; /** * When true, clones the raw Response before consuming the body. * Allows reading reap.raw after the stream is consumed. * Incurs memory overhead - use only when raw access is needed. */ cloneRaw?: boolean; /** * Custom response body deserializer. * * Receives the raw response text and returns the parsed value. * Useful for handling non-standard JSON (e.g. BigInt values). * If not provided, JSON.parse is used. */ deserialize?: (text: string) => unknown; } type ConstructorFunction = new (...args: unknown[]) => C; type AnyFn = (...a: any[]) => unknown; type FunctionKeys = { [K in keyof T]-?: Extract extends never ? never : K; }[keyof T]; type NonFunctionProps = Omit>; type FieldsOf = Readonly>; type PublicFieldsOf = { [K in keyof NonFunctionProps as K extends `_${string}` ? never : K]: NonFunctionProps[K]; }; /** * Builder interface for constructing JinFrame instances with compile-time field tracking. * * `TSet` accumulates the union of field keys explicitly provided via `set()` or `from()`. * `build()` is only callable when `TSet` covers every public field key, enforcing that * all fields are assigned before the instance is created. * * **Known DX limitation with `getDefaultValues()`** * * Fields supplied by `getDefaultValues()` must be declared optional (`field?: T`) so that * `build()` does not require them to be explicitly set through the builder. * The trade-off is that TypeScript then widens their access type to `T | undefined`, * requiring null checks even though the value is always present at runtime. * Fully resolving this would require the type system to infer which fields are covered by * `getDefaultValues()` — currently not achievable without modifying `AbstractJinFrame`. */ interface BuilderFor, TSet extends keyof PublicFieldsOf> = never> { set: >>(k: K, v: PublicFieldsOf>[K]) => BuilderFor; from: >>>(v: V) => BuilderFor>)>; auto: () => BuilderFor>>; get: () => Readonly>>>; build: [keyof PublicFieldsOf>] extends [TSet] ? () => InstanceType : never; } /** * Result interface for deduplicated requests * @template Pass - The response data type */ interface DedupeResult { /** The response from the HTTP request */ resp: Response; /** Whether this request was deduplicated (true) or was the original request (false) */ isDeduped: boolean; } interface JinRequestConfig { url: string; method: Method; headers: Record; body?: BodyInit; timeout?: Milliseconds; signal?: AbortSignal; } declare abstract class AbstractJinFrame { #private; static getEndpoint(): URL; protected static getDefaultValues(): Partial>>; static builder>(this: C, ...ctorArgs: ConstructorParameters): BuilderFor; static of>(this: C, args: PublicFieldsOf> | ((b: BuilderFor) => unknown), ...ctorArgs: ConstructorParameters): InstanceType; protected _retryFail(_req: JinRequestConfig, _res: Response): void | Promise; protected _retryException(_req: JinRequestConfig, _err: Error): void | Promise; protected get _startAt(): Date; protected get _option(): FrameOption; constructor(); _getData>(kind: K): Pick[K]; protected _setData>(kind: K, value: FrameInternal[K]): void; _getOption(kind: K): FrameOption[K]; _setFields(args: typeof this): void; _getBodyInit(bodies: unknown): BodyInit | undefined; _getCacheKey(): string | undefined; _getBaseUrlString(paths: Record, override?: { host?: string; pathPrefix?: string; path?: string; }): string; /** * JinRequestConfig create using by class member variable. * * @param option same with JinRequestConfig, bug exclude some filed ignored * @returns created JinRequestConfig */ _request(option?: JinFrameRequestConfig & JinFrameCreateConfig): JinRequestConfig; _retry(req: JinRequestConfig, isValidateStatus: (ok: boolean, status: number) => boolean): Promise; } /** * Debug information for HTTP requests */ interface DebugInfo { /** Timestamp information when the request started */ ts: { /** Unix timestamp with milliseconds as string */ unix: string; /** * ISO timestamp without hyphens, containing only T character and dot * @example "20210721T112233.444" */ iso: string; }; /** Request execution duration in milliseconds */ duration: number; /** Whether the request was deduplicated */ isDeduped: boolean; /** HTTP request configuration object */ req: JinRequestConfig; } interface JinRespBase { status: number; statusText: string; headers: Record; /** * Original fetch Response object. Stream may already be consumed, * but object is preserved for metadata access. */ raw: Response; } interface JinFailResp extends JinRespBase { ok: false; data: T; valid: boolean; $validated: ValidationResult; } interface JinPassResp extends JinRespBase { ok: true; data: T; valid: boolean; $validated: ValidationResult; } type JinResp = JinPassResp | JinFailResp; interface JinFrameFunction { _create: (args?: JinFrameRequestConfig & JinFrameCreateConfig) => () => Promise>; _execute: (args?: JinFrameRequestConfig & JinFrameCreateConfig) => Promise>; } declare class JinRespError extends Error { #private; __discriminator: string; constructor({ debug, frame, resp, message, cause, }: { debug: DebugInfo; frame: JinFrame; resp: JinFailResp; message: string; cause?: unknown; }); get debug(): DebugInfo; get frame(): JinFrame; get resp(): JinFailResp | undefined; get status(): number; get statusText(): string; toString(): string; } declare class JinValidationError extends Error { #private; __discriminator: string; constructor({ debug, frame, resp, message, validator, validated, }: { debug: DebugInfo; frame: JinFrame; resp: JinPassResp; message: string; validator: BaseValidator; validated: ValidationResult; }); get debug(): DebugInfo; get frame(): JinFrame; get resp(): JinPassResp | undefined; get status(): number; get statusText(): string; get validator(): BaseValidator; get validated(): ValidationResult; } type GetError, TPASS, TFAIL, TValidationError = unknown> = (err: JinCreateError | JinRespError | JinValidationError) => Error; /** * Definition HTTP Request * * @typeParam Pass response data type for valid status — returned as `JinPassResp` * @typeParam Fail response data type for invalid status — returned as `JinFailResp` */ declare class JinFrame extends AbstractJinFrame implements JinFrameFunction { /** * Execute before request. If you can change request object that is affected request. * * @param this this instance * @param req request object * */ protected _preHook(_req: JinRequestConfig): void | Promise; /** * Execute after request. * * @param this this instance * @param req request object * @param result [discriminated union](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions) pass or fail */ protected _postHook(_req: JinRequestConfig, _reply: JinResp, _debugInfo: DebugInfo): void | Promise; _requestWrap(option?: JinFrameRequestConfig & JinFrameCreateConfig): JinRequestConfig; /** * Generate a request config and return a function that invokes HTTP APIs * * @param option request configuration options * @returns Function that invokes HTTP APIs */ _create(this: this, option?: JinFrameRequestConfig & JinFrameCreateConfig & { getError?: GetError; }): () => Promise & { $debug: DebugInfo; $frame: TSelf; }>; /** * Generate a request config and invoke HTTP APIs. * * Function-based `SecurityKey` values (set via `@Security(provider, () => key)`) are resolved * here before building the request. Use this method when the authorization key is dynamic. * * @param option request configuration options * @returns JinResp with pass or fail discriminated union */ _execute(this: TSelf, option?: JinFrameRequestConfig & JinFrameCreateConfig & { getError?: GetError; }): Promise>; } declare class JinCreateError, TPASS, TFAIL = TPASS> extends Error { #private; __discriminator: string; constructor({ debug, frame, message }: { debug: Omit; frame: T; message: string; }); get debug(): Omit; get frame(): T; get status(): number; get statusText(): string; } declare const defaultJinFrameTimeout = 120000; declare class JinFile { #private; constructor(name: JinFile['name'], file: JinFile['file']); get file(): T; get name(): string; } /** * Manages request deduplication to prevent multiple identical HTTP requests * from being sent simultaneously. When multiple requests with the same cache key * are made, only the first request is actually sent, and all subsequent requests * receive the same response. * * The cache key is generated using the `getCacheKey()` method from JinFrame instances, * which creates a unique identifier based on the request parameters and configuration. */ declare class RequestDedupeManager { /** Store pending requests (key: cacheKey, value: Promise of buffered response) */ private static pendingRequests; /** * Deduplicates HTTP requests based on cache key. If a request with the same * cache key is already in progress, returns the result of that request. * Otherwise, executes the new request and stores it for potential deduplication. * * @template Pass - The expected response data type * @param cacheKey - Unique identifier for the request used for deduplication (generated by JinFrame._getCacheKey()) * @param requesterFn - Function that performs the actual HTTP request * @returns Promise resolving to DedupeResult containing the response and deduplication flag * * @example * ```ts * // Cache key is typically generated by JinFrame._getCacheKey() * const frame = GetUserFrame.of({ id: '123' }); * const cacheKey = frame._getCacheKey(); * * const result = await RequestDedupeManager.dedupe( * cacheKey, * () => fetch('/users/123') * ); * console.log(result.isDeduped); // false for original request, true for duplicates * ``` */ static dedupe(cacheKey: string, requesterFn: () => Promise): Promise; /** * Returns the number of currently pending requests. * Useful for debugging and monitoring request deduplication. * * @returns The count of pending requests */ static getPendingRequestsCount(): number; /** * Clears all pending requests from the cache. * This method is primarily intended for testing purposes. * * @warning Use with caution in production as this will affect all pending requests */ static clearAllPendingRequests(): void; /** * Checks if a request with the given cache key is currently pending. * * @param cacheKey - The cache key to check * @returns true if a request with this cache key is pending, false otherwise */ static hasPendingRequest(cacheKey: string): boolean; } type JinHttpClient = (req: Request) => Promise>; declare function flatStringMap(map: Record): Record; /** * Define formatter for querystring, param, headers, body */ interface Formatter { /** * order of formatter apply * * @default ['number', 'string', 'dateTime'] * */ order?: ('string' | 'number' | 'dateTime')[]; /** * When true, silently discards the value on formatter error. * When false, throws an exception on error. */ ignoreError?: boolean; /** function is number type convert to another number, string, Date */ number?: (value: number) => number | Date | string; /** function is string type convert to another string, Date */ string?: (value: string) => string | Date; /** function is JavaScript Date type convert to string */ dateTime?: (value: Date) => string; } type SingleBodyFormatter = { /** use `dot notation`(eg. data.more.birthday) to specify where the results will be stored */ findFrom?: string; } & Formatter; interface CommonCacheKeyExcludePathOption { /** * When paths are provided in this option, they will be excluded from cache key generation. * Unlike Query, Param, and Header, Body and ObjectBody use path specifications to * exclude specific values. * * Including values that always change (like UUIDs) in the cache reduces cache efficiency. * Enable this option to exclude such fields from caching. */ cacheKeyExcludePaths?: string[]; } interface CommonFieldOption { /** Do encodeURIComponent execution, this option only executed in query parameter */ encode?: boolean; /** The field key name */ key: string; } interface BodyFieldOption extends CommonFieldOption, CommonCacheKeyExcludePathOption { type: 'body'; /** * If you want to create depth or rename on field of body * set this option dot separated string. See below, * * @example * `data.test.ironman` convert to `{ data: { test: { ironman: "value here" } } }` */ replaceAt?: string; /** * formatter configuration, use convert date type or transform data shape * * `formatters` field only work when have valid input type. * * `formatters` fields operate in order of string formatter, dateTime formatter. So You can change a string to * JavaScript Date instance using by string formatter and a converted Date instance to string using by dateTime * formatter. * * @remarks * If you use the string formatter to change to JavaScript Date instance and then do not change to a string, * the formatters setting is: automatically convert to iso8601 string * * @example * ordered example. * * ``` * { * findFrom: 'data.more.birthday', * string: (value: string) => parse(value, "yyyy-MM-dd'T'HH:mm:ss", new Date()), * dateTime: (value: Date) => format(value, 'yyyy-MM-dd HH:mm:ss'), * } * ``` * */ formatters?: SingleBodyFormatter | SingleBodyFormatter[]; } declare function getBodyField(thisFrame: unknown, field: BodyFieldOption): unknown; interface ObjectBodyFieldOption extends CommonFieldOption, CommonCacheKeyExcludePathOption { type: 'object-body'; /** * merge order of object-body. Sorted in ascending order. Objects with fast numbers are overwritten by * object with slow number. * * @default Number.MAX_SAFE_INTEGER * */ order: number; /** * formatter configuration, use convert date type or transform data shape * * `formatters` field only work when have valid input type. * * `formatters` fields operate in order of string formatter, dateTime formatter. So You can change a string to * JavaScript Date instance using by string formatter and a converted Date instance to string using by dateTime * formatter. * * @remarks * If you use the string formatter to change to JavaScript Date instance and then do not change to a string, * the formatters setting is: automatically convert to iso8601 string * * @example * ordered example. * * ``` * { * findFrom: 'data.more.birthday', * string: (value: string) => parse(value, "yyyy-MM-dd'T'HH:mm:ss", new Date()), * dateTime: (value: Date) => format(value, 'yyyy-MM-dd HH:mm:ss'), * } * ``` * */ formatters?: SingleBodyFormatter | SingleBodyFormatter[]; } /** Jin-Frame support type of array */ type SupportArrayType = string[] | boolean[] | number[] | Date[]; /** Jin-Frame support primitive type */ type SupportPrimitiveType = string | boolean | number | Date; declare function getBodyMap>(thisFrame: T, fields: (BodyFieldOption | ObjectBodyFieldOption)[]): Record | SupportPrimitiveType | SupportArrayType | unknown[]; declare function getObjectBodyField(thisFrame: unknown, field: ObjectBodyFieldOption): unknown; interface CommonCacheKeyExcludeOption { /** * When this option is set to true, the field will be excluded from cache key generation. * Including values that always change (like UUIDs) in the cache reduces cache efficiency. * Enable this option to exclude such fields from caching. */ cacheKeyExclude: boolean; } interface QueryParamHeaderCommonFieldOption { /** * If you want to create depth or rename on field of body * set this option dot separated string. See below, * * @example * `data.test.ironman` convert to `{ "data.test.ironman": "value here" }` */ replaceAt?: string; /** * "comma" option only working querystring. If you want to process array parameter of querystring * using by comma separated string, set this option * * Comma separated array parameter on querystring */ comma: boolean; bit: { /** enable bitwised operator using by array */ enable: boolean; /** If this configuration set enable, bitwised operation result are zero after submit zero value */ withZero: boolean; }; /** * formatter configuration, use convert date type or transform data shape * * `formatters` field only work when have valid input type. * * `formatters` fields operate in order of string formatter, dateTime formatter. So You can change a string to * JavaScript Date instance using by string formatter and a converted Date instance to string using by dateTime * formatter. * * @remarks * If you use the string formatter to change to JavaScript Date instance and then do not change to a string, * the formatters setting is: automatically convert to iso8601 string * * header field don't need a findFrom. HTTP protocol header not treat complex type object and array. * * @see https://developer.mozilla.org/en-US/docs/Web/API/Headers * * @example * ordered example. * * ``` * { * string: (value: string) => parse(value, "yyyy-MM-dd'T'HH:mm:ss", new Date()), * dateTime: (value: Date) => format(value, 'yyyy-MM-dd HH:mm:ss'), * } * ``` * */ formatters?: Formatter | Formatter[]; } interface QueryFieldOption extends CommonFieldOption, CommonCacheKeyExcludeOption, QueryParamHeaderCommonFieldOption { type: 'query'; /** * Querystring Array key formatting * * - brackets * - a[]=x&a[]=y * - indices * - a[0]=x&a[1]=y * - one-indices * - a[1]=x&a[2]=y */ keyFormat?: 'brackets' | 'indices' | 'one-indices'; } declare function getQuerystringKey({ key, index, format, }: { key: string; index: number; format?: QueryFieldOption['keyFormat']; }): string; interface HeaderFieldOption extends CommonFieldOption, CommonCacheKeyExcludeOption, QueryParamHeaderCommonFieldOption { type: 'header'; } interface ParamFieldOption extends CommonFieldOption, CommonCacheKeyExcludeOption, QueryParamHeaderCommonFieldOption { type: 'param'; } declare function getQuerystringKeyFormat(option?: QueryFieldOption | ParamFieldOption | HeaderFieldOption): QueryFieldOption['keyFormat']; interface CookieFieldOption extends CommonFieldOption, CommonCacheKeyExcludeOption, QueryParamHeaderCommonFieldOption { type: 'cookie'; } declare function getQuerystringMap>(thisFrame: T, fields: (QueryFieldOption | ParamFieldOption | HeaderFieldOption | CookieFieldOption)[]): Record; declare function bitwised(values: number[]): number; interface IGetCachePathParams { key: string; type: QueryFieldOption['type'] | ParamFieldOption['type'] | HeaderFieldOption['type'] | BodyFieldOption['type'] | ObjectBodyFieldOption['type'] | CookieFieldOption['type']; replaceAt?: string; } declare function getCachePath(params: IGetCachePathParams): string; /** * getDuration only calculate milliseconds ~ days */ declare function getDuration(start: Date, end: Date): number; declare function getError, TValidationError = unknown>(err: JinCreateError | JinRespError | JinValidationError, handler?: (err: JinCreateError | JinRespError | JinValidationError) => Error): Error; declare function getHeaderObject(headers: Headers): Record; declare function getRetryAfter(retry: FrameRetry, rawRetryAfter: string | string[] | undefined): number | undefined; declare function getUrlValue(value?: string | (() => string | undefined)): string | undefined; declare function isValidateStatusDefault(ok: boolean, _status: number): boolean; declare function mergeFrameOption(prev: FrameOption, next: FrameOption): FrameOption; declare function mergeRetryOption(prev: FrameRetry, next: FrameRetry): FrameRetry; /** * Runs a function and unwraps the result if it's a Promise * @param fn - Function to execute (can be sync or async) * @param args - Arguments to pass to the function (type-safe based on fn parameters) * @returns The unwrapped result */ declare function runAndUnwrap(fn: (...args: TArgs) => TReturn | Promise, ...args: TArgs): Promise; declare function setFrameOption(target: FrameOption, key: K, value: unknown): void; /** * Asynchronously waits for the specified number of milliseconds before resolving. * This function creates a Promise that resolves after the given interval using setTimeout. * * @param _interval - The number of milliseconds to wait before resolving * - Negative values are treated as 1ms * - NaN values are treated as 1ms * @returns A Promise that resolves to void after the specified interval * * @example * ```ts * // Wait for 1 second * await sleep(1000); * console.log('1 second has passed'); * * // Wait for 500ms * await sleep(500); * console.log('500ms has passed'); * * // Negative values are treated as 1ms * await sleep(-100); // waits 1ms * * // NaN values are treated as 1ms * await sleep(NaN); // waits 1ms * ``` * * @example * ```ts * // Use in async functions for delays * async function delayedExecution() { * console.log('Starting...'); * await sleep(2000); * console.log('2 seconds later'); * } * * // Use with Promise.all for concurrent operations * const results = await Promise.all([ * sleep(100), * sleep(200), * sleep(150) * ]); * // All complete after 200ms (the longest) * ``` */ declare function sleep(_interval: number): Promise; /** * API Key security provider that implements authentication using API keys. * Supports API key placement in headers, query parameters, or cookies. */ declare class ApiKeyProvider implements SecurityProvider { /** Type identifier for this security provider */ readonly type: "api-key"; /** Name of this security provider instance */ readonly name: string; /** The name of the API key parameter */ private readonly keyName; /** Location where the API key should be placed */ private readonly location; /** * Creates a new API Key provider * @param name - Name of this security provider instance * @param keyName - The name of the API key parameter (e.g., 'X-API-Key', 'apikey') * @param location - Where to place the API key: 'header', 'query', or 'cookie' */ constructor(name: string, keyName: string, location?: 'header' | 'query' | 'cookie'); /** * Creates security context with API key authentication * @param authorization - Authorization data containing the API key * @param dynamicKey - Optional dynamic API key that overrides the authorization data * @returns Security context with the API key applied to the appropriate location */ createContext(authorization?: AuthorizationData, dynamicKey?: string): SecurityContext; /** * Extracts the API key from authorization data * @param authorization - Authorization data that can be a string or an object with a 'key' property * @returns The extracted API key or undefined if not found */ private static extractKey; } /** * Basic Authentication security provider that implements HTTP Basic Auth. * Supports both username/password pairs and pre-encoded Basic auth strings. */ declare class BasicAuthProvider implements SecurityProvider { /** Type identifier for this security provider */ readonly type: "http"; /** Name of this security provider instance */ readonly name: string; /** * Creates a new Basic Authentication provider * @param name - Name of this security provider instance */ constructor(name?: string); /** * Creates security context with Basic Authentication * @param authorization - Authorization data containing username/password or Basic auth string * @param dynamicKey - Optional dynamic Basic auth string that overrides the authorization data * @returns Security context with Basic auth applied to headers or auth property */ createContext(authorization?: AuthorizationData, dynamicKey?: string): SecurityContext; /** * Handles dynamic key for Basic Authentication * @param dynamicKey - The dynamic Basic auth string * @returns Security context with Authorization header */ private static handleDynamicKey; /** * Handles authorization data for Basic Authentication * @param authorization - Authorization data that can be a string or an object with username/password * @returns Security context with either Authorization header or auth credentials */ private static handleAuthorization; } /** * Bearer Token security provider for HTTP Bearer Token authentication. * Automatically adds "Bearer " prefix to tokens if not already present. * Call `setKey()` to update the token at runtime (e.g. after token refresh). */ declare class BearerTokenProvider implements SecurityProvider { readonly type: "http"; readonly name: string; private _internalKey?; constructor(name?: string); setKey(key: string): this; createContext(authorization?: AuthorizationData, dynamicKey?: string): SecurityContext; } /** * decorator to set class variable to HTTP API body parameter * @param option body parameter option */ declare function Body(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; /** * decorator to set class variable to HTTP Cookie header * @param option cookie field option */ declare function Cookie(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; /** * decorator to set class variable to HTTP API header parameter * @param option header parameter option */ declare function Header(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; /** * decorator to set class variable to HTTP API body parameter * @param option body parameter option */ declare function ObjectBody(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; /** * decorator to set class variable to HTTP API path parameter * @param option path parameter option */ declare function Param(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; /** * decorator to set class variable to HTTP API query parameter * @param option query parameter option */ declare function Query(_option?: Partial>): (target: object, propertyKey: string | symbol) => void; declare const Delete: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Get: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Head: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Link: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Options: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Patch: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Post: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Purge: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Put: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Search: (_option?: Partial>) => (target: type_fest.Constructor) => void; declare const Unlink: (_option?: Partial>) => (target: type_fest.Constructor) => void; type FieldRecords = Record & Record & Record & Record; /** * Execute before request. If you can change request object that is affected request. * * @param req request object * */ type PreHook = (req: JinRequestConfig) => void | Promise; /** * Execute after request. * * @param req request object * @param reply reply object */ type PostHook = (req: JinRequestConfig, reply: JinPassResp | JinFailResp, debugInfo: DebugInfo) => void | Promise; type RetryFailHook = (req: JinRequestConfig, res: JinResp) => void | Promise; type MethodEntry = Readonly<{ option: Readonly; }>; /** * A security key that can be a static string or a function that returns a string asynchronously. * Supports lazy resolution from environment variables, secret managers (e.g. HashiCorp Vault), etc. * * @example * ```ts * // Static string * const key: SecurityKey = 'my-bearer-token'; * * // From dotenv at request time * const key: SecurityKey = () => process.env.API_TOKEN ?? ''; * * // Async from secret manager * const key: SecurityKey = async () => vault.getSecret('api-token'); * ``` */ type SecurityKey = string | (() => string | Promise); declare function getDefaultBodyFieldOption(option?: Partial | Except, 'type'>): BodyFieldOption; declare function getDefaultCookieFieldOption(option?: Partial | Omit, 'type'>): CookieFieldOption; declare function getDefaultHeaderFieldOption(option?: Partial | Omit, 'type'>): HeaderFieldOption; declare function getDefaultObjectBodyFieldOption(option?: Partial | Except, 'type'>): ObjectBodyFieldOption; declare function getDefaultParamFieldOption(option?: Partial | Omit, 'type'>): ParamFieldOption; declare function getDefaultQueryFieldOption(option?: Partial | Omit, 'type'>): QueryFieldOption; /** * Applies multiple security providers to generate a unified security context. * * This function processes an array of security providers sequentially, merging their * outputs into a single security context. Headers and query parameters are merged additively, * while auth configuration uses the last provider's value (last wins strategy). * * @param providers - Array of security providers to apply * @param authorization - Static authorization data configured at frame level * @param dynamicAuth - Runtime authorization data that overrides static configuration * * @returns Unified security context containing: * - `headers`: Merged HTTP headers from all providers * - `queries`: Merged query parameters from all providers * - `auth`: HTTP Basic auth configuration from the last provider that provided one * * @example * ```typescript * const providers = [apiKeyProvider, bearerTokenProvider]; * const context = applySecurityProviders( * providers, * { apiKey: 'static-key' }, * { token: 'runtime-token' } * ); * * // Returns merged context: * // { * // headers: { 'X-API-Key': 'static-key', 'Authorization': 'Bearer runtime-token' }, * // queries: { ... }, * // auth: { ... } // from last provider * // } * ``` */ declare function applySecurityProviders(providers: SecurityProvider[], authorization?: AuthorizationData, dynamicAuth?: AuthorizationData): SecurityContext; /** * Extracts and processes authorization information from various sources in priority order. * * The function follows this priority hierarchy: * 1. Authorization header from the headers parameter * 2. HTTP Basic auth configuration (username/password) * 3. Security providers * * @param headers - HTTP headers containing potential Authorization header * @param frameOption - Frame configuration options containing security and authorization settings * @param auth - Optional HTTP Basic auth configuration (username/password) * @param dynamicAuth - Optional dynamic authorization data for runtime security provider configuration * * @returns Authorization result object containing: * - `authKey`: Authorization token/key string (from header or security provider) * - `auth`: HTTP Basic auth configuration object * - `securityHeaders`: Additional headers from security providers * - `securityQueries`: Query parameters from security providers * * @example * ```typescript * // Using Authorization header * const result = getAuthorization( * { Authorization: 'Bearer token123' }, * frameOption * ); * // Returns: { authKey: 'Bearer token123', auth: undefined } * * // Using Basic auth * const result = getAuthorization( * {}, * frameOption, * { username: 'user', password: 'pass' } * ); * // Returns: { authKey: undefined, auth: { username: 'user', password: 'pass' } } * * // Using security providers * const result = getAuthorization( * {}, * { security: [apiKeyProvider] }, * undefined, * { apiKey: 'key123' } * ); * // Returns: { authKey: undefined, auth: undefined, securityHeaders: {...}, securityQueries: {...} } * ``` */ declare function getAuthorization(headers: Record, frameOption: Pick, auth?: JinBasicAuth, dynamicAuth?: AuthorizationData): { authKey?: string; auth?: JinBasicAuth; securityHeaders?: Record; securityQueries?: Record; }; declare function encode(enable: boolean | undefined | null, value: string | number): string; declare function encodes(enable: boolean | undefined | null, values: string | number): string; declare function encodes(enable: boolean | undefined | null, values: string[] | number[]): string[]; declare function applyFormat(origin: unknown, formatter: Formatter): unknown; declare function bodyFormatEach(initialValue: unknown, formatters: SingleBodyFormatter | SingleBodyFormatter[]): unknown; declare function bodyFormatting(initialValue: unknown, formatter: SingleBodyFormatter): unknown; declare function bodyFormattings(initialValue: unknown, formatters: Formatter | Formatter[]): unknown; declare function classifyBodyFormatters(formatters?: SingleBodyFormatter[]): { valid: SetRequired[]; invalid: SetOptional[]; }; declare function findFromBody(initialValue: unknown, findFrom?: string): unknown; declare function formatEach(initialValue: unknown, formatters: Formatter | Formatter[]): SupportPrimitiveType | SupportPrimitiveType[]; declare function formatting(initialValue: unknown, formatter: Formatter): SupportPrimitiveType | undefined; declare function formattings(initialValue: unknown, formatters: Formatter | Formatter[]): SupportPrimitiveType; declare function getBodyFormatters(formatters?: SingleBodyFormatter | SingleBodyFormatter[]): SingleBodyFormatter[]; declare function setToBody(initialValue: unknown, formatted: unknown, findFrom?: string): unknown; declare function stringifyExceptString(value: unknown): string; declare function stringifyQuerystring(values: unknown, option?: { comma?: boolean; encode?: boolean; }): string | string[]; declare function safeParse(value: string): T | undefined; declare function safeStringify(value: T, replacer?: (this: T, key: string, value: T) => T, space?: string | number): string | undefined; /** * @param retry retry configuration from the internal data * @param totalDuration The total duration(ms) of all retries since the start of the API request * @param eachDuration duration(ms) of a single retry attempt * @param retryAfterSeconds Optional Retry-After header value in seconds (takes highest priority) */ declare function getRetryInterval(retry: NonNullable, totalDuration: number, eachDuration: number, retryAfterSeconds?: number): number; declare function getStatusFromError(error: unknown): { status: number; statusText: string; }; declare function getUrl(host?: string, pathPrefix?: string, path?: string): { url: URL; str: string; pathname: string; isOnlyPath: boolean; }; declare function removeBothSlash(value: string): string; declare function removeEndSlash(value: string): string; declare function removeStartSlash(value: string): string; declare function startWithSlash(value: string): string; declare function isValidArrayType(values: unknown): values is SupportArrayType; declare function isValidNumberArray(value: unknown): value is number[]; declare function isValidObject(value: unknown): value is Record; declare function isValidPrimitiveType(value: unknown): value is Exclude; declare function isValidPrimitiveWithDateType(value: unknown): value is SupportPrimitiveType; declare function typeAssert(strict: boolean, value: unknown): value is SupportArrayType | SupportPrimitiveType; type AbstractConstructorFunction = abstract new (...args: unknown[]) => C; /** * Utility Type for JinFrame. ConstructorType help to create constructor parameter. * This tips from [This article](https://stackoverflow.com/questions/55479658/how-to-create-a-type-excluding-instance-methods-from-a-class-in-typescript) and * [TypeScript: Create a condition-based subset types - DailyJS](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c) */ type FlagExcludedType = { [Key in keyof Base]: Base[Key] extends Type ? never : Key; }; type AllowedNames = FlagExcludedType[keyof Base]; type OmitType = Pick>; type ConstructorType = OmitType; /** * Utility Type for JinFrame. ConstructorType help to create constructor parameter. * This tips from [This article](https://stackoverflow.com/questions/55479658/how-to-create-a-type-excluding-instance-methods-from-a-class-in-typescript) and * [TypeScript: Create a condition-based subset types - DailyJS](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c) */ type OmitConstructorType> = Omit, M>; interface WithBuilder> { builder: (...args: ConstructorParameters) => BuilderFor; } interface WithDefaultValues> { getDefaultValues?: () => Partial>>; } interface IRequestFieldRecord { param: ParamFieldOption[]; query: QueryFieldOption[]; body: BodyFieldOption[]; objectBody: ObjectBodyFieldOption[]; header: HeaderFieldOption[]; cookie: CookieFieldOption[]; } declare function getFieldMetadata(type: object, keys: { key: string; value: unknown; }[]): IRequestFieldRecord; declare const REQUEST_FIELD_DECORATOR: unique symbol; declare function getAllRequestMetaInherited(ctor: AbstractConstructor | Constructor): { methods: readonly MethodEntry[]; retries: readonly FrameRetry[]; timeouts: readonly Milliseconds[]; validators: readonly { pass?: BaseValidator; fail?: BaseValidator; }[]; dedupes: readonly boolean[]; authorizations: readonly FrameOption['authorization'][]; securities: readonly FrameOption['security'][]; }; /** * Resolves and merges all method decorator metadata for a class constructor, * walking the inheritance chain. When a parent and child both apply a decorator, * the parent receives a higher index — child metadata takes precedence. * * @example * ```typescript * @Delete({ host: 'i-am-host' }) * class ParentFrame {} * * @Get({ host: 'i-am-host' }) * class ChildFrame extends ParentFrame {} * // ParentFrame → index 01, ChildFrame → index 00 * ``` * * @param ctor Constructor function */ declare function getRequestMeta(ctor: AbstractConstructor | Constructor): MethodEntry; declare function makeRequestDecorator(method: Method): (_option?: Partial>) => (target: Constructor) => void; /** Appends a method entry to the metadata stored on the given constructor target. */ declare function pushRequestMeta(target: Constructor, entry: MethodEntry): void; declare const REQUEST_AUTHORIZATION_DECORATOR: unique symbol; declare const REQUEST_DEDUPE_DECORATOR: unique symbol; declare const REQUEST_METHOD_DECORATOR: unique symbol; declare const REQUEST_RETRY_DECORATOR: unique symbol; declare const REQUEST_SECURITY_DECORATOR: unique symbol; declare const REQUEST_TIMEOUT_DECORATOR: unique symbol; declare const REQUEST_VALIDATOR_DECORATOR: unique symbol; declare function Dedupe(): (target: object) => void; declare function Retry(_option: FrameRetry): (target: object) => void; declare function Security(_option: FrameOption['security'], key?: SecurityKey): (target: object) => void; declare function Timeout(_option: Milliseconds): (target: object) => void; declare function Validator(_option: { pass?: BaseValidator; fail?: BaseValidator; }): (target: object) => void; type MultipleBodyFormatter = SingleBodyFormatter[]; export { AbstractJinFrame, ApiKeyProvider, BaseValidator, BasicAuthProvider, BearerTokenProvider, Body, Cookie, Dedupe, Delete, Get, Head, Header, JinCreateError, JinFile, JinFrame, JinRespError, JinValidationError, Link, ObjectBody, Options, Param, Patch, Post, Purge, Put, Query, REQUEST_AUTHORIZATION_DECORATOR, REQUEST_DEDUPE_DECORATOR, REQUEST_FIELD_DECORATOR, REQUEST_METHOD_DECORATOR, REQUEST_RETRY_DECORATOR, REQUEST_SECURITY_DECORATOR, REQUEST_TIMEOUT_DECORATOR, REQUEST_VALIDATOR_DECORATOR, RequestDedupeManager, Retry, Search, Security, Timeout, Unlink, Validator, applyFormat, applySecurityProviders, bitwised, bodyFormatEach, bodyFormatting, bodyFormattings, classifyBodyFormatters, defaultJinFrameTimeout, encode, encodes, findFromBody, flatStringMap, formatEach, formatting, formattings, getAllRequestMetaInherited, getAuthorization, getBodyField, getBodyFormatters, getBodyMap, getCachePath, getDefaultBodyFieldOption, getDefaultCookieFieldOption, getDefaultHeaderFieldOption, getDefaultObjectBodyFieldOption, getDefaultParamFieldOption, getDefaultQueryFieldOption, getDuration, getError, getFieldMetadata, getFrameInternalData, getFrameOption, getHeaderObject, getObjectBodyField, getQuerystringKey, getQuerystringKeyFormat, getQuerystringMap, getRequestMeta, getRetryAfter, getRetryInterval, getStatusFromError, getUrl, getUrlValue, isValidArrayType, isValidNumberArray, isValidObject, isValidPrimitiveType, isValidPrimitiveWithDateType, isValidateStatusDefault, makeRequestDecorator, mergeFrameOption, mergeRetryOption, pushRequestMeta, removeBothSlash, removeEndSlash, removeStartSlash, runAndUnwrap, safeParse, safeStringify, setFrameOption, setToBody, sleep, startWithSlash, stringifyExceptString, stringifyQuerystring, typeAssert }; export type { AbstractConstructorFunction, AuthorizationData, BodyFieldOption, BuilderFor, CommonCacheKeyExcludeOption, CommonCacheKeyExcludePathOption, CommonFieldOption, ConstructorFunction, ConstructorType, CookieFieldOption, DebugInfo, DedupeResult, FieldRecords, FieldsOf, Formatter, FrameInternal, FrameOption, FrameRetry, GetError, HeaderFieldOption, JinBasicAuth, JinFailResp, JinFrameCreateConfig, JinFrameFunction, JinFrameRequestConfig, JinHttpClient, JinPassResp, JinRequestConfig, JinResp, JinRespBase, Method, MethodEntry, Milliseconds, MultipleBodyFormatter, ObjectBodyFieldOption, OmitConstructorType, ParamFieldOption, PostHook, PreHook, PublicFieldsOf, QueryFieldOption, QueryParamHeaderCommonFieldOption, RetryFailHook, SecurityContext, SecurityKey, SecurityProvider, SingleBodyFormatter, SupportArrayType, SupportPrimitiveType, ValidationResult, ValidationResultType, WithBuilder, WithDefaultValues };