{"version":3,"sources":["../node_modules/bessa_patterns.ts/src/ObserverSubject.ts","../node_modules/bessa_patterns.ts/src/DualObserverSubject.ts","../src/utils/DefaultEventNotifier.ts","../src/core/IbiraAPIFetcher.ts","../src/core/IbiraAPIFetchManager.ts","../src/utils/DefaultCache.ts","../src/utils/throttle.ts","../src/utils/debounce.ts","../src/config/version.ts"],"sourcesContent":["/**\n * ObserverSubject - Generic concrete implementation of the Observer/Subject pattern\n *\n * @fileoverview Provides a reusable subject that manages a list of observer callbacks\n * and notifies them with a typed snapshot whenever state changes.\n *\n * **Design Principles:**\n * - **Single Responsibility:** Observer management only\n * - **Generic:** Type parameter `T` defines the snapshot shape passed to observers\n * - **Concrete:** Can be instantiated directly or extended by specialised subclasses\n *\n * @module core/ObserverSubject\n * @since 0.9.1-alpha\n * @author Marcelo Pereira Barbosa\n *\n * @example\n * // Direct usage\n * import ObserverSubject from './core/ObserverSubject.js';\n *\n * const subject = new ObserverSubject<{ value: number }>();\n *\n * const unsubscribe = subject.subscribe((snapshot) => {\n *   console.log('Value changed:', snapshot.value);\n * });\n *\n * subject._notifyObservers({ value: 42 });\n * unsubscribe();\n *\n * @example\n * // Subclass usage\n * class MyState extends ObserverSubject<{ count: number }> {\n *   private _count = 0;\n *   increment() {\n *     this._count++;\n *     this._notifyObservers({ count: this._count });\n *   }\n * }\n */\n\n/**\n * ObserverSubject<T> - Manages a list of observer callbacks and notifies them with a typed snapshot.\n *\n * @class\n * @template T - The type of the snapshot object passed to observers on notification\n */\nclass ObserverSubject<T> {\n  private _observers: ((snapshot: T) => void)[];\n\n  /**\n   * Creates a new ObserverSubject instance with an empty observer list.\n   *\n   * @constructor\n   */\n  constructor() {\n    /**\n     * Registered observer callbacks\n     * @type {Function[]}\n     * @private\n     */\n    this._observers = [];\n  }\n\n  /**\n   * Subscribe to notifications\n   *\n   * @param {Function} callback - Called on each notification: (snapshot: T) => void\n   * @returns {Function} Unsubscribe function — call it to remove this observer\n   * @throws {TypeError} If callback is not a function\n   *\n   * @example\n   * const unsubscribe = subject.subscribe((snapshot) => {\n   *   console.log(snapshot);\n   * });\n   * // Later:\n   * unsubscribe();\n   */\n  subscribe(callback: (snapshot: T) => void): () => void {\n    if (typeof callback !== 'function') {\n      throw new TypeError('ObserverSubject: callback must be a function');\n    }\n    this._observers.push(callback);\n\n    return () => {\n      const index = this._observers.indexOf(callback);\n      if (index > -1) {\n        this._observers.splice(index, 1);\n      }\n    };\n  }\n\n  /**\n   * Unsubscribe an observer by reference\n   *\n   * @param {Function} callback - The callback to remove\n   * @returns {boolean} True if the callback was found and removed\n   *\n   * @example\n   * const handler = (s) => console.log(s);\n   * subject.subscribe(handler);\n   * subject.unsubscribe(handler); // true\n   */\n  unsubscribe(callback: (snapshot: T) => void): boolean {\n    const index = this._observers.indexOf(callback);\n    if (index > -1) {\n      this._observers.splice(index, 1);\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Get number of active observers\n   *\n   * @returns {number} Number of subscribed observers\n   */\n  getObserverCount(): number {\n    return this._observers.length;\n  }\n\n  /**\n   * Remove all observers\n   *\n   * @example\n   * subject.clearObservers();\n   * console.log(subject.getObserverCount()); // 0\n   */\n  clearObservers(): void {\n    this._observers = [];\n  }\n\n  /**\n   * Notify all observers with the given snapshot\n   *\n   * Errors thrown by individual observer callbacks are caught and logged so\n   * that a misbehaving observer cannot prevent the others from being called.\n   *\n   * @param {T} snapshot - Value forwarded to every observer callback\n   *\n   * @example\n   * subject._notifyObservers({ value: 42 });\n   */\n  protected _notifyObservers(snapshot: T): void {\n    this._observers.forEach((callback) => {\n      try {\n        callback(snapshot);\n      } catch (err: unknown) {\n        console.warn('ObserverSubject: Error notifying observer', err);\n      }\n    });\n  }\n}\n\nexport default ObserverSubject;\n","/**\n * DualObserverSubject - GoF Observer pattern implementation supporting both\n * object-based observers (with update methods) and function-based observers.\n *\n * @fileoverview Provides a reusable Subject that maintains two independent observer\n * collections: object observers (GoF pattern, notified via notifyObservers) and\n * function observers (modern callback pattern, notified via notifyFunctionObservers).\n *\n * **Design Principles:**\n * - **Dual Observer Collections:** Object and function observers are managed independently\n * - **Immutability:** subscribe/unsubscribe create new arrays (spread + filter); no in-place mutation\n * - **Error Isolation:** Errors in individual observers are caught so others still receive notifications\n * - **Null Safety:** Null/undefined subscriptions are silently ignored\n *\n * **Observer Types:**\n * - Object observers: `{ update(...args): void }` — subscribed via `subscribe()`, notified via `notifyObservers()`\n * - Function observers: `(...args) => void` — subscribed via `subscribeFunction()`, notified via `notifyFunctionObservers()`\n *\n * @module core/DualObserverSubject\n * @since 0.10.0-alpha\n * @author Marcelo Pereira Barbosa\n *\n * @example\n * // Object-based (GoF) pattern\n * const subject = new DualObserverSubject();\n *\n * const myObserver = {\n *   update(source, event, data) {\n *     console.log('Notified:', event, data);\n *   }\n * };\n *\n * subject.subscribe(myObserver);\n * subject.notifyObservers(this, 'positionChanged', { lat: -23.5, lon: -46.6 });\n * subject.unsubscribe(myObserver);\n *\n * @example\n * // Function-based pattern\n * const subject = new DualObserverSubject();\n *\n * const handler = (source, event, data) => {\n *   console.log('Function notified:', event);\n * };\n *\n * subject.subscribeFunction(handler);\n * subject.notifyFunctionObservers(this, 'positionChanged', data);\n * subject.unsubscribeFunction(handler);\n *\n * @example\n * // Mixed usage — both patterns are independent\n * const subject = new DualObserverSubject();\n * subject.subscribe({ update: (src, evt) => console.log(evt) });\n * subject.subscribeFunction((src, evt) => console.log(evt));\n *\n * subject.notifyObservers(this, 'event');    // notifies ONLY object observers\n * subject.notifyFunctionObservers(this, 'event'); // notifies ONLY function observers\n */\n\n/**\n * An observer object that may implement an `update` method.\n * @template T - Tuple of argument types for observer notifications (defaults to `unknown[]`)\n */\nexport type ObserverObject<T extends unknown[] = unknown[]> = { update?: (...args: T) => void };\n\n/**\n * A function-based observer callback.\n * @template T - Tuple of argument types for observer notifications (defaults to `unknown[]`)\n */\nexport type ObserverFunction<T extends unknown[] = unknown[]> = (...args: T) => void;\n\n/**\n * DualObserverSubject — Subject managing two independent observer collections.\n *\n * @class\n * @template T - Tuple of argument types forwarded to observer callbacks (defaults to `unknown[]`)\n *\n * @example\n * // Typed usage: all notifications are [source: object, event: string]\n * const bus = new DualObserverSubject<[source: object, event: string]>();\n * bus.subscribe({ update(src, evt) { console.log(evt); } });\n * bus.notifyObservers(this, 'click');\n *\n * @example\n * // Untyped usage (backwards-compatible default)\n * const subject = new DualObserverSubject();\n * subject.subscribe({ update: (...args) => console.log(args) });\n */\nclass DualObserverSubject<T extends unknown[] = unknown[]> {\n  private _observers: ObserverObject<T>[];\n  private _functionObservers: ObserverFunction<T>[];\n\n  /** Read-only view of object observers subscribed via {@link subscribe}. */\n  get observers(): ReadonlyArray<ObserverObject<T>> {\n    return this._observers;\n  }\n  /** Read-only view of function observers subscribed via {@link subscribeFunction}. */\n  get functionObservers(): ReadonlyArray<ObserverFunction<T>> {\n    return this._functionObservers;\n  }\n\n  /**\n   * Creates a new DualObserverSubject with empty observer collections.\n   */\n  constructor() {\n    this._observers = [];\n    this._functionObservers = [];\n  }\n\n  /**\n   * Subscribes an object observer to receive notifications via its `update()` method.\n   *\n   * **Immutable Pattern:** Creates a new array using spread operator instead of\n   * mutating the existing observers array.\n   *\n   * @param {ObserverObject | null | undefined} observer - Observer object (may have `update` method)\n   * @returns {void}\n   *\n   * @example\n   * const observer = { update: (source, event) => console.log(event) };\n   * subject.subscribe(observer);\n   */\n  subscribe(observer: ObserverObject<T> | null | undefined): void {\n    if (observer) {\n      this._observers = [...this._observers, observer];\n    }\n  }\n\n  /**\n   * Unsubscribes an object observer from notifications.\n   *\n   * **Immutable Pattern:** Uses filter to create a new array without the observer.\n   *\n   * @param {ObserverObject} observer - Observer object to remove\n   * @returns {void}\n   *\n   * @example\n   * subject.unsubscribe(myObserver);\n   */\n  unsubscribe(observer: ObserverObject<T>): void {\n    this._observers = this._observers.filter((o) => o !== observer);\n  }\n\n  /**\n   * Notifies all subscribed object observers.\n   * Calls `observer.update(...args)` on each observer that implements `update`.\n   * Errors thrown by individual observers are caught so others still receive notifications.\n   *\n   * @param {...unknown} args - Arguments forwarded to each observer's `update()` method\n   * @returns {void}\n   *\n   * @example\n   * subject.notifyObservers(this, 'positionChanged', position, null);\n   */\n  notifyObservers(...args: T): void {\n    this._observers.forEach((observer) => {\n      if (typeof observer.update === 'function') {\n        try {\n          observer.update(...args);\n        } catch (err: unknown) {\n          console.warn('DualObserverSubject: Error notifying observer', err);\n        }\n      }\n    });\n  }\n\n  /**\n   * Subscribes a function observer to receive notifications via `notifyFunctionObservers`.\n   *\n   * **Immutable Pattern:** Creates a new array using spread operator.\n   *\n   * @param {ObserverFunction | null | undefined} observerFunction - Callback function\n   * @returns {void}\n   *\n   * @example\n   * const handler = (source, event, data) => console.log(event);\n   * subject.subscribeFunction(handler);\n   */\n  subscribeFunction(observerFunction: ObserverFunction<T> | null | undefined): void {\n    if (observerFunction) {\n      this._functionObservers = [...this._functionObservers, observerFunction];\n    }\n  }\n\n  /**\n   * Unsubscribes a function observer from notifications.\n   *\n   * **Immutable Pattern:** Uses filter to create a new array without the function.\n   *\n   * @param {ObserverFunction} observerFunction - Function to remove\n   * @returns {void}\n   *\n   * @example\n   * subject.unsubscribeFunction(handler);\n   */\n  unsubscribeFunction(observerFunction: ObserverFunction<T>): void {\n    this._functionObservers = this._functionObservers.filter((fn) => fn !== observerFunction);\n  }\n\n  /**\n   * Notifies all subscribed function observers.\n   * Errors thrown by individual observers are caught so others still receive notifications.\n   *\n   * @param {...unknown} args - Arguments forwarded to each observer function\n   * @returns {void}\n   *\n   * @example\n   * subject.notifyFunctionObservers(this, 'positionChanged', data);\n   */\n  notifyFunctionObservers(...args: T): void {\n    this._functionObservers.forEach((fn) => {\n      if (typeof fn === 'function') {\n        try {\n          fn(...args);\n        } catch (err: unknown) {\n          console.warn('DualObserverSubject: Error notifying function observer', err);\n        }\n      }\n    });\n  }\n\n  /**\n   * Returns the count of subscribed object observers.\n   *\n   * @returns {number} Number of object observers subscribed via {@link subscribe}\n   */\n  getObserverCount(): number {\n    return this._observers.length;\n  }\n\n  /**\n   * Returns the count of subscribed function observers.\n   *\n   * @returns {number} Number of function observers subscribed via {@link subscribeFunction}\n   */\n  getFunctionObserverCount(): number {\n    return this._functionObservers.length;\n  }\n\n  /**\n   * Removes all observers (both object and function collections).\n   *\n   * @returns {void}\n   *\n   * @example\n   * subject.clearAllObservers();\n   * console.log(subject.getObserverCount());         // 0\n   * console.log(subject.getFunctionObserverCount()); // 0\n   */\n  clearAllObservers(): void {\n    this._observers = [];\n    this._functionObservers = [];\n  }\n}\n\nexport default DualObserverSubject;\n","/**\n * @fileoverview Default event notification system with observer pattern\n * @module utils/DefaultEventNotifier\n * @license MIT\n * @copyright 2025 Marcelo Pereira Barbosa\n */\n\nimport { DualObserverSubject } from 'bessa_patterns.ts';\n\n/**\n * @typedef {Object} Observer\n * @property {Function} update - Method called with (eventType, payload) when events occur\n */\nexport interface Observer {\n\tupdate: (...args: unknown[]) => void;\n}\n\n/**\n * DefaultEventNotifier - Observer pattern implementation for event notifications\n *\n * Built on top of {@link DualObserverSubject} from\n * [bessa_patterns.ts](https://github.com/mpbarbosa/bessa_patterns.ts)\n * (v0.12.15-alpha), which provides the immutable-array observer management,\n * null-safety, and per-observer error isolation.\n *\n * Wraps the `DualObserverSubject` GoF channel with the ibira.js-specific\n * `notify()` / `clear()` / `subscriberCount` surface so the rest of the\n * codebase stays unchanged.\n *\n * **Supported Events:**\n * - 'loading-start': Fired when a fetch operation begins\n * - 'success': Fired when data is successfully fetched\n * - 'error': Fired when an error occurs during fetching\n *\n * @class DefaultEventNotifier\n * @since 0.1.0-alpha\n * @author Marcelo Pereira Barbosa\n *\n * @example\n * const notifier = new DefaultEventNotifier();\n *\n * notifier.subscribe({\n *   update: (event, data) => {\n *     console.log('Event:', event, 'Data:', data);\n *   }\n * });\n *\n * notifier.notify('success', { result: 'data' });\n */\nexport class DefaultEventNotifier {\n\tprivate readonly _subject: DualObserverSubject;\n\n\t/**\n\t * Creates a new DefaultEventNotifier instance\n\t *\n\t * @example\n\t * const notifier = new DefaultEventNotifier();\n\t */\n\tconstructor() {\n\t\tthis._subject = new DualObserverSubject();\n\t}\n\n\t/**\n\t * Read-only view of the currently subscribed observers.\n\t *\n\t * @type {ReadonlyArray<Observer>}\n\t */\n\tget observers(): ReadonlyArray<Observer> {\n\t\treturn this._subject.observers as ReadonlyArray<Observer>;\n\t}\n\n\t/**\n\t * Subscribes an observer to receive notifications\n\t *\n\t * @param {Observer | null | undefined} observer - Observer object with an update method\n\t *\n\t * @example\n\t * notifier.subscribe({\n\t *   update: (event, data) => {\n\t *     if (event === 'loading-start') console.log('Loading...');\n\t *     if (event === 'success') console.log('Data:', data);\n\t *   }\n\t * });\n\t */\n\tsubscribe(observer: Observer | null | undefined): void {\n\t\tthis._subject.subscribe(observer);\n\t}\n\n\t/**\n\t * Unsubscribes an observer from notifications.\n\t * Removes all occurrences if the same observer was subscribed multiple times.\n\t *\n\t * @param {Observer} observer - Observer object to remove\n\t *\n\t * @example\n\t * const observer = { update: (e, d) => console.log(e, d) };\n\t * notifier.subscribe(observer);\n\t * // Later...\n\t * notifier.unsubscribe(observer);\n\t */\n\tunsubscribe(observer: Observer): void {\n\t\tthis._subject.unsubscribe(observer);\n\t}\n\n\t/**\n\t * Notifies all subscribed observers with the provided arguments.\n\t * Observer errors are isolated — a throwing observer does not prevent\n\t * subsequent observers from receiving the notification.\n\t *\n\t * @param {...unknown} args - Arguments to pass to each observer's update method\n\t *\n\t * @example\n\t * // Notify with event type and payload\n\t * notifier.notify('success', { data: [1, 2, 3] });\n\t *\n\t * @example\n\t * // Notify with error\n\t * notifier.notify('error', { error: new Error('Failed') });\n\t */\n\tnotify(...args: unknown[]): void {\n\t\tthis._subject.notifyObservers(...args);\n\t}\n\n\t/**\n\t * Clears all subscribed observers\n\t *\n\t * @example\n\t * notifier.clear();\n\t * console.log('Subscribers:', notifier.subscriberCount); // 0\n\t */\n\tclear(): void {\n\t\tthis._subject.clearAllObservers();\n\t}\n\n\t/**\n\t * Gets the current number of subscribed observers\n\t *\n\t * @returns {number} The number of observers\n\t *\n\t * @example\n\t * console.log(`Active observers: ${notifier.subscriberCount}`);\n\t */\n\tget subscriberCount(): number {\n\t\treturn this._subject.getObserverCount();\n\t}\n}\n","/**\n * @fileoverview Core fetcher class with caching and observer pattern support\n * @module core/IbiraAPIFetcher\n * @license MIT\n * @copyright 2025 Marcelo Pereira Barbosa\n */\n\nimport { DefaultEventNotifier } from '../utils/DefaultEventNotifier.js';\nimport type { CacheEntry } from '../utils/DefaultCache.js';\nimport type { Observer } from '../utils/DefaultEventNotifier.js';\n\nexport type { CacheEntry };\n\n/** Duck-typed interface for any object that can act as a cache store.\n *\n * @template T - The type of the cached data. Defaults to `unknown` for backward compatibility.\n *   Implement this interface with a specific type to build custom typed cache backends.\n *\n * @example\n * class LocalStorageCache implements CacheInterface<User> { ... }\n */\nexport interface CacheInterface<T = unknown> {\n\thas(key: string): boolean;\n\tget(key: string): CacheEntry<T> | undefined | null;\n\tset(key: string, value: CacheEntry<T>): void;\n\tdelete(key: string): boolean;\n\tclear(): void;\n\treadonly size: number;\n\tentries(): Iterable<[string, CacheEntry<T>]>;\n\tmaxSize: number;\n\texpiration: number;\n}\n\n/** Duck-typed interface for any event notifier implementation. */\nexport interface EventNotifierInterface {\n\tsubscribe(observer: Observer): void;\n\tunsubscribe(observer: Observer): void;\n\tnotify(...args: unknown[]): void;\n\tclear(): void;\n\treadonly subscriberCount: number;\n}\n\n/** Union of valid HTTP method strings accepted by FetcherOptions and RetryConfig. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';\n\n/**\n * @typedef {Object} FetcherOptions\n * @property {number} [timeout=10000] - Request timeout in milliseconds\n * @property {Object} [eventNotifier] - Event notifier instance for observer pattern\n * @property {number} [maxRetries=3] - Maximum number of retry attempts\n * @property {number} [retryDelay=1000] - Initial retry delay in milliseconds\n * @property {number} [retryMultiplier=2] - Exponential backoff multiplier\n * @property {number[]} [retryableStatusCodes=[408, 429, 500, 502, 503, 504]] - HTTP status codes that trigger retries\n * @property {number} [maxCacheSize=100] - Maximum number of cache entries\n * @property {number} [cacheExpiration=300000] - Cache expiration time in milliseconds (default: 5 minutes)\n * @property {Object} [cache] - Cache instance (must implement Map-like interface)\n * @property {Function} [validateStatus] - Function `(status: number) => boolean` that determines whether an HTTP status is successful. Defaults to `status >= 200 && status < 300`.\n * @property {'GET'|'POST'|'PUT'|'PATCH'|'DELETE'|'HEAD'} [method='GET'] - HTTP method for the request\n * @property {Object|string|FormData|Blob|null} [body=null] - Request body. Plain objects are JSON-serialized automatically; strings/FormData/Blob are passed as-is.\n * @property {Object} [headers={}] - Additional HTTP request headers. When body is a plain object, `Content-Type: application/json` is added automatically.\n * @property {Function} [onRequest] - Request interceptor: `(options: RequestInit) => RequestInit | Promise<RequestInit>`. Called before each fetch attempt; return modified options to add headers, change the body, etc.\n * @property {Function} [onResponse] - Response interceptor: `(response: Response) => Response | Promise<Response>`. Called after each fetch attempt before status validation; return a modified or cloned response.\n * @property {Function} [retryStrategy] - Custom retry predicate: `(attempt: number, error: Error) => boolean`. When provided, replaces the built-in retryable-status check; return `true` to retry, `false` to give up.\n */\nexport interface FetcherOptions {\n\ttimeout?: number;\n\teventNotifier?: EventNotifierInterface;\n\tmaxRetries?: number;\n\tretryDelay?: number;\n\tretryMultiplier?: number;\n\tretryableStatusCodes?: number[];\n\tmaxCacheSize?: number;\n\tcacheExpiration?: number;\n\tcache?: CacheInterface;\n\tvalidateStatus?: (status: number) => boolean;\n\tmethod?: HttpMethod;\n\tbody?: Record<string, unknown> | string | FormData | Blob | ArrayBuffer | null;\n\theaders?: Record<string, string>;\n\tonRequest?: (options: RequestInit) => RequestInit | Promise<RequestInit>;\n\tonResponse?: (response: Response) => Response | Promise<Response>;\n\tretryStrategy?: (attempt: number, error: Error) => boolean;\n}\n\n/**\n * @typedef {Object} CacheOperation\n * @property {'set'|'update'|'delete'} type - Type of cache operation\n * @property {string} key - Cache key affected\n * @property {CacheEntry} [value] - New value (for set/update operations)\n */\nexport interface CacheOperation {\n\ttype: 'set' | 'update' | 'delete';\n\tkey: string;\n\tvalue?: CacheEntry;\n}\n\n/**\n * @typedef {Object} FetchEvent\n * @property {'loading-start'|'success'|'error'} type - Event type\n * @property {*} payload - Event payload data\n */\nexport interface FetchEvent {\n\ttype: 'loading-start' | 'success' | 'error';\n\tpayload: unknown;\n}\n\nexport interface FetchMeta {\n\tcacheKey: string;\n\ttimestamp: number;\n\texpiredKeysRemoved: number;\n\tattempt?: number;\n\tnetworkRequest?: boolean;\n}\n\n/**\n * @typedef {Object} FetchResult\n * @property {boolean} success - Whether the operation succeeded\n * @property {*} [data] - The fetched data (present when success is true)\n * @property {Error} [error] - The error that occurred (present when success is false)\n * @property {boolean} fromCache - Whether data came from cache\n * @property {CacheOperation[]} cacheOperations - List of cache operations performed\n * @property {FetchEvent[]} events - List of events that occurred\n * @property {Map} newCacheState - The new cache state after operations\n * @property {FetchMeta} meta - Metadata about the operation\n */\nexport interface FetchResult {\n\tsuccess: boolean;\n\tdata?: unknown;\n\terror?: Error;\n\tfromCache: boolean;\n\tcacheOperations: readonly CacheOperation[];\n\tevents: readonly FetchEvent[];\n\tnewCacheState: Map<string, CacheEntry>;\n\tmeta: FetchMeta;\n}\n\n/**\n * IbiraAPIFetcher - Core class for fetching and caching API data\n * \n * Provides a flexible, functional approach to API data fetching with built-in caching,\n * retry logic, and observer pattern support. The class is designed with referential\n * transparency in mind, offering both pure functional and practical imperative APIs.\n * \n * **Key Features:**\n * - Multiple factory methods for different use cases (with/without cache, events)\n * - Intelligent caching with expiration and LRU eviction\n * - Exponential backoff retry logic with configurable status codes\n * - Observer pattern for reactive updates\n * - Pure functional core with practical wrapper methods\n * - Immutable instances (Object.freeze) for predictable behavior\n * \n * @class IbiraAPIFetcher\n * @since 0.1.0-alpha\n * @author Marcelo Pereira Barbosa\n * \n * @example\n * // Simple usage with default cache\n * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/users');\n * const users = await fetcher.fetchData();\n * \n * @example\n * // Pure functional approach\n * const fetcher = IbiraAPIFetcher.pure('https://api.example.com/data');\n * const result = await fetcher.fetchDataPure(new Map(), Date.now());\n * console.log(result.data, result.events, result.cacheOperations);\n * \n * @example\n * // With observer pattern\n * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/data');\n * fetcher.subscribe({\n *   update: (event, data) => console.log(event, data)\n * });\n * await fetcher.fetchData();\n */\nexport class IbiraAPIFetcher {\n\treadonly url: string;\n\treadonly timeout: number;\n\treadonly cache: CacheInterface;\n\treadonly eventNotifier: EventNotifierInterface;\n\treadonly maxRetries: number;\n\treadonly retryDelay: number;\n\treadonly retryMultiplier: number;\n\treadonly retryableStatusCodes: ReadonlyArray<number>;\n\treadonly validateStatus: (status: number) => boolean;\n\treadonly method: HttpMethod;\n\treadonly body: Record<string, unknown> | string | FormData | Blob | ArrayBuffer | null;\n\treadonly headers: Readonly<Record<string, string>>;\n\treadonly onRequest: ((options: RequestInit) => RequestInit | Promise<RequestInit>) | null;\n\treadonly onResponse: ((response: Response) => Response | Promise<Response>) | null;\n\treadonly retryStrategy: ((attempt: number, error: Error) => boolean) | null;\n\n\t/**\n\t * Creates an IbiraAPIFetcher with a purely functional cache approach\n\t * This method provides better referential transparency by explicitly managing cache externally\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL\n\t * @param {CacheInterface} cache - External cache instance (must implement Map-like interface)\n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance with external cache\n\t * \n\t * @example\n\t * // Purely functional approach with external cache\n\t * const sharedCache = new Map();\n\t * const fetcher = IbiraAPIFetcher.withExternalCache(\n\t *   'https://api.example.com/data',\n\t *   sharedCache,\n\t *   { timeout: 5000 }\n\t * );\n\t */\n\tstatic withExternalCache(url: string, cache: CacheInterface, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\treturn new IbiraAPIFetcher(url, cache, options);\n\t}\n\n\t/**\n\t * Creates a default cache with expiration and size limits\n\t * \n\t * @private\n\t * @static\n\t * @param {FetcherOptions} [options={}] - Cache configuration options\n\t * @returns {CacheInterface} Configured cache instance\n\t */\n\tprivate static _createDefaultCache(options: FetcherOptions = {}): CacheInterface {\n\t\tconst cache = new Map<string, CacheEntry>() as unknown as CacheInterface;\n\t\tcache.maxSize = options.maxCacheSize || 100;\n\t\tcache.expiration = options.cacheExpiration || 5 * 60 * 1000; // 5 minutes default\n\t\treturn cache;\n\t}\n\n\t/**\n\t * Creates an IbiraAPIFetcher with default cache settings\n\t * Convenient for most use cases with reasonable defaults\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL\n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance with default cache\n\t * \n\t * @example\n\t * // Using default cache settings (100 entries, 5 minute expiration)\n\t * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/data');\n\t * \n\t * @example\n\t * // Custom cache settings\n\t * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/data', {\n\t *   maxCacheSize: 50,\n\t *   cacheExpiration: 10 * 60 * 1000 // 10 minutes\n\t * });\n\t */\n\tstatic withDefaultCache(url: string, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst cache = this._createDefaultCache(options);\n\t\treturn new IbiraAPIFetcher(url, cache, options);\n\t}\n\n\t/**\n\t * Creates an IbiraAPIFetcher with no caching for maximum referential transparency\n\t * Every call will result in a fresh network request\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL  \n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance with no cache\n\t * \n\t * @example\n\t * // No cache - purely functional, always fresh data\n\t * const fetcher = IbiraAPIFetcher.withoutCache('https://api.example.com/data');\n\t */\n\tstatic withoutCache(url: string, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst noCache: CacheInterface = {\n\t\t\thas: () => false,\n\t\t\tget: () => null,\n\t\t\tset: () => {},\n\t\t\tdelete: () => false,\n\t\t\tclear: () => {},\n\t\t\tsize: 0,\n\t\t\tentries: () => [] as unknown as IterableIterator<[string, CacheEntry]>,\n\t\t\tmaxSize: 0,\n\t\t\texpiration: 0\n\t\t};\n\t\treturn new IbiraAPIFetcher(url, noCache, options);\n\t}\n\n\t/**\n\t * Creates an IbiraAPIFetcher with external event notification for maximum referential transparency\n\t * Events are handled through callback functions instead of mutable observer state\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL\n\t * @param {Function} eventCallback - Function to handle events (event, data) => void\n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance with callback-based events\n\t * \n\t * @example\n\t * // Purely functional event handling\n\t * const fetcher = IbiraAPIFetcher.withEventCallback(\n\t *   'https://api.example.com/data',\n\t *   (event, data) => {\n\t *     if (event === 'loading-start') setLoading(true);\n\t *     if (event === 'success') setData(data);\n\t *     if (event === 'error') setError(data.error);\n\t *   }\n\t * );\n\t */\n\tstatic withEventCallback(url: string, eventCallback: (...args: unknown[]) => void, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst callbackNotifier: EventNotifierInterface = {\n\t\t\tsubscribe: () => {}, // No-op for external callback\n\t\t\tunsubscribe: () => {}, // No-op for external callback\n\t\t\tnotify: eventCallback,\n\t\t\tclear: () => {},\n\t\t\tsubscriberCount: 1\n\t\t};\n\t\t// Create default cache if none provided\n\t\tconst cache = options.cache || this._createDefaultCache(options);\n\t\treturn new IbiraAPIFetcher(url, cache, { ...options, eventNotifier: callbackNotifier });\n\t}\n\n\t/**\n\t * Creates an IbiraAPIFetcher with no event notifications for maximum referential transparency\n\t * No side effects from event notifications - purely functional data fetching\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL\n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance with no event notifications\n\t * \n\t * @example\n\t * // Pure functional approach - no event side effects\n\t * const fetcher = IbiraAPIFetcher.withoutEvents('https://api.example.com/data');\n\t * const data = await fetcher.fetchData(); // No event notifications\n\t */\n\tstatic withoutEvents(url: string, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst noEvents: EventNotifierInterface = {\n\t\t\tsubscribe: () => {},\n\t\t\tunsubscribe: () => {},\n\t\t\tnotify: () => {}, // Silent - no event notifications\n\t\t\tclear: () => {},\n\t\t\tsubscriberCount: 0\n\t\t};\n\t\t// Create default cache if none provided\n\t\tconst cache = options.cache || this._createDefaultCache(options);\n\t\treturn new IbiraAPIFetcher(url, cache, { ...options, eventNotifier: noEvents });\n\t}\n\n\t/**\n\t * Creates a completely pure IbiraAPIFetcher for maximum referential transparency\n\t * Use this when you want to handle all side effects (caching, events) externally\n\t * \n\t * @static\n\t * @param {string} url - The API endpoint URL\n\t * @param {FetcherOptions} [options={}] - Additional configuration options\n\t * @returns {IbiraAPIFetcher} Configured fetcher instance for pure functional usage\n\t * \n\t * @example\n\t * // Maximum purity - handle all effects externally\n\t * const fetcher = IbiraAPIFetcher.pure('https://api.example.com/data');\n\t * let cacheState = new Map();\n\t * \n\t * const result = await fetcher.fetchDataPure(cacheState, Date.now());\n\t * if (result.success) {\n\t *   cacheState = result.newCacheState; // Update cache externally\n\t *   console.log('Data:', result.data);\n\t * }\n\t */\n\tstatic pure(url: string, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst noCache: CacheInterface = {\n\t\t\thas: () => false,\n\t\t\tget: () => null,\n\t\t\tset: () => {},\n\t\t\tdelete: () => false,\n\t\t\tclear: () => {},\n\t\t\tsize: 0,\n\t\t\tentries: () => [] as unknown as IterableIterator<[string, CacheEntry]>,\n\t\t\tmaxSize: options.maxCacheSize || 100,\n\t\t\texpiration: options.cacheExpiration || 5 * 60 * 1000\n\t\t};\n\t\t\n\t\tconst noEvents: EventNotifierInterface = {\n\t\t\tsubscribe: () => {},\n\t\t\tunsubscribe: () => {},\n\t\t\tnotify: () => {},\n\t\t\tclear: () => {},\n\t\t\tsubscriberCount: 0\n\t\t};\n\t\t\n\t\treturn new IbiraAPIFetcher(url, noCache, { ...options, eventNotifier: noEvents });\n\t}\n\n\t/**\n\t * Creates an IbiraAPIFetcher instance\n\t * \n\t * @param {string} url - The API endpoint URL\n\t * @param {CacheInterface} cache - Cache instance (must implement Map-like interface with has/get/set/delete/clear/entries methods)\n\t * @param {FetcherOptions} [options={}] - Configuration options\n\t * @param {number} [options.timeout=10000] - Request timeout in milliseconds\n\t * @param {EventNotifierInterface} [options.eventNotifier] - Event notifier instance for observer pattern\n\t * @param {number} [options.maxRetries=3] - Maximum number of retry attempts\n\t * @param {number} [options.retryDelay=1000] - Initial retry delay in milliseconds\n\t * @param {number} [options.retryMultiplier=2] - Exponential backoff multiplier\n\t * @param {number[]} [options.retryableStatusCodes=[408, 429, 500, 502, 503, 504]] - HTTP status codes that trigger retries\n\t * @param {'GET'|'POST'|'PUT'|'PATCH'|'DELETE'|'HEAD'} [options.method='GET'] - HTTP method\n\t * @param {Object|string|FormData|Blob|null} [options.body=null] - Request body (plain objects are JSON-serialized automatically)\n\t * @param {Object} [options.headers={}] - Additional request headers\n\t * @param {Function} [options.onRequest] - Request interceptor called before each fetch attempt; return modified RequestInit\n\t * @param {Function} [options.onResponse] - Response interceptor called after each fetch attempt; return modified Response\n\t * @param {Function} [options.retryStrategy] - Custom retry predicate; return true to retry, false to stop\n\t */\n    constructor(url: string, cache: CacheInterface, options: FetcherOptions = {}) {\n\t\tthis.url = url;\n\t\tthis.timeout = options.timeout || 10000;\n\t\t\n\t\t// ✅ EXCELLENT: Cache as required dependency for maximum referential transparency\n\t\tthis.cache = cache;\n\n\t\t// ✅ IMPROVED: Event notifier as dependency injection for better referential transparency\n\t\tthis.eventNotifier = options.eventNotifier || new DefaultEventNotifier();\n\t\t\n\t\t// ✅ SIMPLIFIED: Cache configuration managed by cache instance itself\n\t\tthis.maxRetries = options.maxRetries ?? 3; // Maximum number of retry attempts\n\t\tthis.retryDelay = options.retryDelay ?? 1000; // Initial retry delay in milliseconds\n\t\tthis.retryMultiplier = options.retryMultiplier ?? 2; // Exponential backoff multiplier\n\t\t// ✅ IMMUTABLE: Create frozen copy of retryable status codes array for deep immutability\n\t\tthis.retryableStatusCodes = Object.freeze([...(options.retryableStatusCodes || [408, 429, 500, 502, 503, 504])]); // HTTP status codes that should trigger retries\n\t\t// Custom HTTP success predicate — defaults to the standard 2xx range\n\t\tthis.validateStatus = options.validateStatus || ((status) => status >= 200 && status < 300);\n\t\t// HTTP method, body, and headers for non-GET requests\n\t\tthis.method = (options.method || 'GET').toUpperCase() as HttpMethod;\n\t\tthis.body = options.body !== undefined ? options.body : null;\n\t\tthis.headers = Object.freeze({ ...(options.headers || {}) });\n\t\t// Pipeline hooks — null when not provided\n\t\tthis.onRequest = options.onRequest ?? null;\n\t\tthis.onResponse = options.onResponse ?? null;\n\t\tthis.retryStrategy = options.retryStrategy ?? null;\n\t\t\n\t\t// ✅ IMMUTABLE: Deep freeze the entire instance for maximum referential transparency\n\t\t// This prevents any external code from modifying the fetcher's properties\n\t\t// and guarantees true immutability at the language level\n\t\tObject.freeze(this);\n\t}\n\n\t/**\n\t * Generates a unique cache key for this fetcher.\n\t * Includes the HTTP method so POST /url and GET /url cache independently.\n\t * \n\t * @returns {string} The cache key (`METHOD:url`)\n\t */\n\tgetCacheKey(): string {\n\t\treturn `${this.method}:${this.url}`;\n\t}\n\n\t/**\n\t * Creates a cache entry with timestamp for expiration tracking\n\t * Uses cache's own expiration configuration for better encapsulation\n\t * \n\t * @private\n\t * @param {any} data - The data to cache\n\t * @param {number} currentTime - Current timestamp in milliseconds\n\t * @param {Object} cache - Cache instance with expiration configuration\n\t * @returns {Object} Cache entry with data and timestamp\n\t */\n\tprivate _createCacheEntry(data: unknown, currentTime: number, cache: CacheInterface): CacheEntry {\n\t\tconst expiration = cache.expiration || 300000; // Default 5 minutes if cache doesn't specify\n\t\treturn {\n\t\t\tdata: data,\n\t\t\ttimestamp: currentTime,\n\t\t\texpiresAt: currentTime + expiration\n\t\t};\n\t}\n\n\t/**\n\t * Checks if a cache entry is still valid (not expired)\n\t * \n\t * @private\n\t * @param {Object} cacheEntry - The cache entry to check\n\t * @param {number} currentTime - Current timestamp in milliseconds\n\t * @returns {boolean} True if the entry is still valid\n\t */\n\tprivate _isCacheEntryValid(cacheEntry: CacheEntry | undefined | null, currentTime: number): boolean {\n\t\treturn cacheEntry !== null && cacheEntry !== undefined && currentTime < cacheEntry.expiresAt;\n\t}\n\n\t/**\n\t * Identifies expired cache entries that should be removed\n\t * This is a pure function that returns keys to delete without mutating state\n\t * \n\t * @private\n\t * @param {Map} cache - The cache map to check\n\t * @param {number} currentTime - Current timestamp in milliseconds\n\t * @returns {string[]} Array of cache keys that have expired\n\t */\n\tprivate _getExpiredCacheKeys(cache: { entries(): Iterable<[string, CacheEntry]> }, currentTime: number): string[] {\n\t\tconst expiredKeys = [];\n\n\t\t// Find all expired entries\n\t\tfor (const [key, entry] of cache.entries()) {\n\t\t\tif (currentTime >= entry.expiresAt) {\n\t\t\t\texpiredKeys.push(key);\n\t\t\t}\n\t\t}\n\n\t\treturn expiredKeys;\n\t}\n\n\t/**\n\t * Determines if an error is retryable based on error type and status code\n\t * \n\t * @private\n\t * @param {Error} error - The error to check\n\t * @returns {boolean} True if the error is retryable\n\t */\n\tprivate _isRetryableError(error: Error): boolean {\n\t\t// Network errors (no response received)\n\t\tif (error.name === 'TypeError' && error.message.includes('fetch')) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Timeout errors\n\t\tif (error.name === 'AbortError' || error.message.includes('timeout')) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// HTTP status code errors\n\t\tif (error.message.includes('HTTP error! status:')) {\n\t\t\tconst statusMatch = error.message.match(/status: (\\d+)/);\n\t\t\tif (statusMatch) {\n\t\t\t\tconst statusCode = parseInt(statusMatch[1]);\n\t\t\t\treturn this.retryableStatusCodes.includes(statusCode);\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Calculates the delay for the next retry attempt using exponential backoff\n\t * \n\t * @private\n\t * @param {number} attempt - The current attempt number (0-based)\n\t * @returns {number} Delay in milliseconds\n\t */\n\tprivate _calculateRetryDelay(attempt: number): number {\n\t\tconst delay = this.retryDelay * Math.pow(this.retryMultiplier, attempt);\n\t\t// Add jitter to prevent thundering herd (±25% random variation)\n\t\tconst jitter = delay * 0.25 * (Math.random() - 0.5);\n\t\treturn Math.max(100, delay + jitter); // Minimum 100ms delay\n\t}\n\n\t/**\n\t * Sleeps for the specified number of milliseconds\n\t * \n\t * @private\n\t * @param {number} ms - Milliseconds to sleep\n\t * @returns {Promise<void>} Promise that resolves after the delay\n\t */\n\tprivate _sleep(ms: number): Promise<void> {\n\t\treturn new Promise(resolve => setTimeout(resolve, ms));\n\t}\n\n\t/**\n\t * Performs a single network request with timeout handling\n\t * \n\t * @private\n\t * @param {AbortController} abortController - Controller for request cancellation\n\t * @param {AbortSignal} [externalSignal] - Optional external signal for consumer-level cancellation\n\t * @returns {Promise<any>} Promise that resolves to the fetched data\n\t */\n\tprivate async _performSingleRequest(abortController: AbortController, externalSignal: AbortSignal | null = null): Promise<unknown> {\n\t\t// Combine the internal timeout signal with an optional external cancellation signal\n\t\tconst signal = abortController.signal;\n\t\tif (externalSignal) {\n\t\t\t// If the external signal is already aborted, abort immediately\n\t\t\tif (externalSignal.aborted) {\n\t\t\t\tabortController.abort();\n\t\t\t} else {\n\t\t\t\texternalSignal.addEventListener('abort', () => abortController.abort(), { once: true });\n\t\t\t}\n\t\t}\n\n\t\tconst fetchOptions: RequestInit = {\n\t\t\tmethod: this.method,\n\t\t\tsignal,\n\t\t};\n\n\t\t// Merge caller-supplied headers first, then auto-add Content-Type for object bodies\n\t\tconst mergedHeaders: Record<string, string> = { ...this.headers };\n\n\t\tconst body = this.body;\n\t\tif (body !== null) {\n\t\t\tif (typeof body === 'object' && !(body instanceof FormData) && !(body instanceof Blob) && !(body instanceof ArrayBuffer)) {\n\t\t\t\tfetchOptions.body = JSON.stringify(body);\n\t\t\t\tif (!mergedHeaders['Content-Type'] && !mergedHeaders['content-type']) {\n\t\t\t\t\tmergedHeaders['Content-Type'] = 'application/json';\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfetchOptions.body = body as BodyInit;\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(mergedHeaders).length > 0) {\n\t\t\tfetchOptions.headers = mergedHeaders;\n\t\t}\n\n\t\t// Set up timeout\n\t\tconst timeoutId = setTimeout(() => {\n\t\t\tabortController.abort();\n\t\t}, this.timeout);\n\n\t\ttry {\n\t\t\t// Apply request interceptor before sending (e.g. add auth headers, sign requests)\n\t\t\tconst effectiveFetchOptions = this.onRequest\n\t\t\t\t? await this.onRequest({ ...fetchOptions })\n\t\t\t\t: fetchOptions;\n\n\t\t\t// Perform network request using modern Fetch API\n\t\t\tconst response = await fetch(this.url, effectiveFetchOptions);\n\n\t\t\t// Clear timeout on successful response\n\t\t\tclearTimeout(timeoutId);\n\n\t\t\t// Apply response interceptor before validation (e.g. inspect, transform, or clone)\n\t\t\tconst processedResponse = this.onResponse\n\t\t\t\t? await this.onResponse(response)\n\t\t\t\t: response;\n\n\t\t\t// Check if HTTP request was successful using the configured validateStatus predicate\n\t\t\tif (!this.validateStatus(processedResponse.status)) {\n\t\t\t\tthrow new Error(`HTTP error! status: ${processedResponse.status}`);\n\t\t\t}\n\n\t\t\t// Parse JSON response data\n\t\t\tconst data = await processedResponse.json();\n\t\t\treturn data;\n\n\t\t} catch (error) {\n\t\t\tclearTimeout(timeoutId);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Subscribes an observer to receive event notifications\n\t * \n\t * @param {Observer} observer - Observer object with an update method\n\t */\n\tsubscribe(observer: Observer): void {\n\t\t// ✅ IMPROVED: Delegate to external event notifier for better referential transparency\n\t\tthis.eventNotifier.subscribe(observer);\n\t}\n\n\t/**\n\t * Unsubscribes an observer from event notifications\n\t * \n\t * @param {Observer} observer - Observer object to remove\n\t */\n\tunsubscribe(observer: Observer): void {\n\t\t// ✅ IMPROVED: Delegate to external event notifier for better referential transparency\n\t\tthis.eventNotifier.unsubscribe(observer);\n\t}\n\n\t/**\n\t * Notifies all subscribed observers with event data\n\t * \n\t * @param {...*} args - Arguments to pass to observers (typically eventType and payload)\n\t */\n\tnotifyObservers(...args: unknown[]): void {\n\t\t// ✅ IMPROVED: Delegate to external event notifier for better referential transparency\n\t\tthis.eventNotifier.notify(...args);\n\t}\n\n\t/**\n\t * PURE FUNCTIONAL VERSION: Computes fetch result without side effects\n\t * \n\t * This is the pure, referentially transparent core of the fetching logic.\n\t * It performs all computations without mutating external state, returning\n\t * a complete description of what should happen (data, cache operations, events).\n\t * \n\t * **Pure Referential Transparency:**\n\t * - ✅ No external state mutations\n\t * - ✅ Deterministic given same inputs (when time is provided)\n\t * - ✅ No side effects (network calls return descriptions)\n\t * - ✅ Returns immutable result objects\n\t * - ✅ Same input always produces same output structure\n\t * \n\t * @param {Map} currentCacheState - Current cache state (not mutated)\n\t * @param {number} [currentTime] - Current timestamp for deterministic behavior\n\t * @param {Function} [networkProvider] - Pure network function for testing\n\t * @param {AbortSignal} [signal] - Optional external AbortSignal for consumer-level cancellation\n\t * @returns {Promise<FetchResult>} Pure result object describing what should happen\n\t * \n\t * @example\n\t * // Pure functional usage - no side effects\n\t * const fetcher = IbiraAPIFetcher.withoutCache('https://api.example.com/data');\n\t * const result = await fetcher.fetchDataPure(new Map(), Date.now());\n\t * \n\t * // Result structure:\n\t * // {\n\t * //   success: true,\n\t * //   data: {...},\n\t * //   fromCache: false,\n\t * //   cacheOperations: [{ type: 'set', key: 'url', value: {...} }],\n\t * //   events: [{ type: 'loading-start', payload: {...} }],\n\t * //   newCacheState: Map {...}\n\t * // }\n\t * \n\t * @example\n\t * // With external AbortSignal\n\t * const controller = new AbortController();\n\t * setTimeout(() => controller.abort(), 2000);\n\t * const result = await fetcher.fetchDataPure(new Map(), Date.now(), null, controller.signal);\n\t */\n\tasync fetchDataPure(currentCacheState: Map<string, CacheEntry> | CacheInterface, currentTime: number = Date.now(), networkProvider: (() => Promise<unknown>) | null = null, signal: AbortSignal | null = null): Promise<FetchResult> {\n\t\tconst cacheKey = this.getCacheKey();\n\t\t\n\t\t// Pure function to get expired keys without mutations\n\t\tconst expiredKeys = this._getExpiredCacheKeys(currentCacheState, currentTime);\n\t\t\n\t\t// Create new cache state without expired entries (pure)\n\t\tconst cleanedCache = new Map<string, CacheEntry>();\n\t\tfor (const [key, value] of currentCacheState.entries()) {\n\t\t\tif (!expiredKeys.includes(key)) {\n\t\t\t\tcleanedCache.set(key, value);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Check cache (pure read operation)\n\t\tif (cleanedCache.has(cacheKey)) {\n\t\t\tconst cacheEntry = cleanedCache.get(cacheKey)!;\n\t\t\tif (this._isCacheEntryValid(cacheEntry, currentTime)) {\n\t\t\t\t// Update timestamp for LRU (create new entry, don't mutate)\n\t\t\t\tconst updatedEntry: CacheEntry = { ...cacheEntry, timestamp: currentTime };\n\t\t\t\tconst newCacheState = new Map(cleanedCache);\n\t\t\t\tnewCacheState.set(cacheKey, updatedEntry);\n\t\t\t\t\n\t\t\t\treturn Object.freeze({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tdata: cacheEntry.data,\n\t\t\t\t\tfromCache: true,\n\t\t\t\t\tcacheOperations: Object.freeze([\n\t\t\t\t\t\tObject.freeze({ type: 'update' as const, key: cacheKey, value: updatedEntry })\n\t\t\t\t\t]),\n\t\t\t\t\tevents: Object.freeze([]) as readonly FetchEvent[],\n\t\t\t\t\tnewCacheState,\n\t\t\t\t\tmeta: Object.freeze({\n\t\t\t\t\t\tcacheKey,\n\t\t\t\t\t\ttimestamp: currentTime,\n\t\t\t\t\t\texpiredKeysRemoved: expiredKeys.length\n\t\t\t\t\t})\n\t\t\t\t}) as FetchResult;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Network operation (pure when networkProvider is provided)\n\t\tconst events: FetchEvent[] = [\n\t\t\t{ type: 'loading-start', payload: Object.freeze({ url: this.url, cacheKey }) }\n\t\t];\n\t\t\n\t\ttry {\n\t\t\t// Use injected network provider for purity, or real fetch for practical use\n\t\t\tconst networkFn = networkProvider || (() => this._performSingleRequest(new AbortController(), signal));\n\t\t\tconst data = await networkFn();\n\t\t\t\n\t\t\t// Create new cache entry (pure)\n\t\t\tconst cacheEntry = this._createCacheEntry(data, currentTime, this.cache);\n\t\t\t\n\t\t\t// Create new cache state with the new entry (pure)\n\t\t\tconst newCacheState = new Map(cleanedCache);\n\t\t\tnewCacheState.set(cacheKey, cacheEntry);\n\t\t\t\n\t\t\t// Apply size limits (pure - returns new state)\n\t\t\tconst finalCacheState = this._applyCacheSizeLimitsPure(newCacheState);\n\t\t\t\n\t\t\treturn Object.freeze({\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: data,\n\t\t\t\tfromCache: false,\n\t\t\t\tcacheOperations: Object.freeze([\n\t\t\t\t\tObject.freeze({ type: 'set' as const, key: cacheKey, value: cacheEntry }),\n\t\t\t\t\t...this._calculateCacheEvictions(newCacheState, finalCacheState).map(op => Object.freeze(op))\n\t\t\t\t]),\n\t\t\t\tevents: Object.freeze([\n\t\t\t\t\t...events,\n\t\t\t\t\t{ type: 'success' as const, payload: data }\n\t\t\t\t]),\n\t\t\t\tnewCacheState: finalCacheState,\n\t\t\t\tmeta: Object.freeze({\n\t\t\t\t\tcacheKey,\n\t\t\t\t\ttimestamp: currentTime,\n\t\t\t\t\texpiredKeysRemoved: expiredKeys.length,\n\t\t\t\t\tattempt: 1,\n\t\t\t\t\tnetworkRequest: true\n\t\t\t\t})\n\t\t\t}) as FetchResult;\n\t\t\t\n\t\t} catch (error) {\n\t\t\treturn Object.freeze({\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error as Error,\n\t\t\t\tfromCache: false,\n\t\t\t\tcacheOperations: Object.freeze([]) as readonly CacheOperation[],\n\t\t\t\tevents: Object.freeze([\n\t\t\t\t\t...events,\n\t\t\t\t\t{ type: 'error' as const, payload: Object.freeze({ error }) }\n\t\t\t\t]),\n\t\t\t\tnewCacheState: cleanedCache,\n\t\t\t\tmeta: Object.freeze({\n\t\t\t\t\tcacheKey,\n\t\t\t\t\ttimestamp: currentTime,\n\t\t\t\t\texpiredKeysRemoved: expiredKeys.length,\n\t\t\t\t\tattempt: 1,\n\t\t\t\t\tnetworkRequest: true\n\t\t\t\t})\n\t\t\t}) as FetchResult;\n\t\t}\n\t}\n\n\t/**\n\t * Pure function to apply cache size limits without mutations\n\t * \n\t * @private\n\t * @param {Map} cacheState - Current cache state\n\t * @returns {Map} New cache state with size limits applied\n\t */\n\tprivate _applyCacheSizeLimitsPure(cacheState: Map<string, CacheEntry>): Map<string, CacheEntry> {\n\t\tconst maxSize = this.cache.maxSize || 50;\n\t\t\n\t\tif (cacheState.size <= maxSize) {\n\t\t\treturn new Map(cacheState);\n\t\t}\n\t\t\n\t\t// Sort entries by timestamp (oldest first) without mutating original\n\t\tconst entries = Array.from(cacheState.entries());\n\t\tentries.sort((a, b) => a[1].timestamp - b[1].timestamp);\n\t\t\n\t\t// Keep only the most recent entries\n\t\tconst entriesToKeep = entries.slice(-(maxSize));\n\t\treturn new Map(entriesToKeep);\n\t}\n\n\t/**\n\t * Pure function to calculate what cache evictions occurred\n\t * \n\t * @private\n\t * @param {Map} beforeState - Cache state before size limits\n\t * @param {Map} afterState - Cache state after size limits\n\t * @returns {Array} Array of eviction operations\n\t */\n\tprivate _calculateCacheEvictions(beforeState: Map<string, CacheEntry>, afterState: Map<string, CacheEntry>): CacheOperation[] {\n\t\tconst evictions: CacheOperation[] = [];\n\t\tfor (const [key] of beforeState) {\n\t\t\tif (!afterState.has(key)) {\n\t\t\t\tevictions.push({ type: 'delete', key });\n\t\t\t}\n\t\t}\n\t\treturn evictions;\n\t}\n\n\t/**\n\t * PRACTICAL WRAPPER: Applies side effects from pure computation\n\t * \n\t * This method uses the pure core but applies the side effects (cache mutations,\n\t * event notifications) for practical usage. It maintains the current API while\n\t * enabling pure functional testing and reasoning.\n\t * \n\t * @async\n\t * @param {Object} [cacheOverride] - Optional cache instance to use instead of the default\n\t * @param {AbortSignal} [signal] - Optional AbortSignal for consumer-level request cancellation\n\t * @returns {Promise<unknown>} Resolves with the fetched data, or retrieved from cache\n\t * @throws {Error} Thrown after all retry attempts are exhausted. Possible causes:\n\t *   - Network errors (e.g. `TypeError: Failed to fetch`)\n\t *   - HTTP error responses not matching `validateStatus` (e.g. `HTTP error! status: 500`)\n\t *   - JSON parsing errors from `response.json()`\n\t *   - Errors thrown by `onRequest` or `onResponse` interceptors\n\t *   - `AbortError` when `signal` is aborted externally (not retried)\n\t * \n\t * @example\n\t * // Practical usage - handles side effects automatically\n\t * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/data');\n\t * const data = await fetcher.fetchData();\n\t * console.log(data); // Retrieved data\n\t * \n\t * @example\n\t * // Cancel an in-flight request\n\t * const controller = new AbortController();\n\t * setTimeout(() => controller.abort(), 1000);\n\t * const data = await fetcher.fetchData(null, controller.signal);\n\t * \n\t * @example\n\t * // Pure functional testing\n\t * const mockNetwork = () => Promise.resolve({ test: 'data' });\n\t * const result = await fetcher.fetchDataPure(new Map(), Date.now(), mockNetwork);\n\t * // Test result without side effects\n\t */\n\tasync fetchData(cacheOverride: CacheInterface | null = null, signal: AbortSignal | null = null): Promise<unknown> {\n\t\tconst activeCache = cacheOverride || this.cache;\n\n\t\tfor (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n\t\t\tconst result = await this.fetchDataPure(activeCache, Date.now(), null, signal);\n\n\t\t\tthis._applySideEffects(result, activeCache);\n\n\t\t\tif (result.success) {\n\t\t\t\treturn result.data;\n\t\t\t}\n\n\t\t\tconst isLastAttempt = attempt >= this.maxRetries;\n\t\t\t// Do not retry if the caller's signal was aborted — that is a deliberate cancellation\n\t\t\tconst isExternallyAborted = signal?.aborted ?? false;\n\t\t\tconst shouldRetry = this.retryStrategy\n\t\t\t\t? this.retryStrategy(attempt, result.error!)\n\t\t\t\t: this._isRetryableError(result.error!);\n\t\t\tif (isLastAttempt || !shouldRetry || isExternallyAborted) {\n\t\t\t\tthrow result.error;\n\t\t\t}\n\n\t\t\tawait this._sleep(this._calculateRetryDelay(attempt));\n\t\t}\n\n\t\t// Unreachable — TypeScript exhaustiveness guard\n\t\tthrow new Error('Unexpected retry loop exit');\n\t}\n\n\t/**\n\t * Applies side effects based on pure computation results\n\t * \n\t * @private\n\t * @param {FetchResult} result - Result from fetchDataPure\n\t * @param {Object} activeCache - Cache to apply operations to\n\t */\n\tprivate _applySideEffects(result: FetchResult, activeCache: CacheInterface): void {\n\t\t// Apply cache operations\n\t\tresult.cacheOperations.forEach(operation => {\n\t\t\tswitch (operation.type) {\n\t\t\t\tcase 'set':\n\t\t\t\tcase 'update':\n\t\t\t\t\tactiveCache.set(operation.key, operation.value!);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'delete':\n\t\t\t\t\tactiveCache.delete(operation.key);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t});\n\t\t\n\t\t// Apply event notifications\n\t\tresult.events.forEach(event => {\n\t\t\tthis.notifyObservers(event.type, event.payload);\n\t\t});\n\t}\n}\n","/**\n * @fileoverview Manager for coordinating multiple concurrent API fetch operations\n * @module core/IbiraAPIFetchManager\n * @license MIT\n * @copyright 2025 Marcelo Pereira Barbosa\n */\n\nimport { IbiraAPIFetcher } from './IbiraAPIFetcher.js';\nimport type { CacheEntry, FetcherOptions, HttpMethod } from './IbiraAPIFetcher.js';\nimport type { Observer } from '../utils/DefaultEventNotifier.js';\n\n/**\n * @typedef {Object} ManagerOptions\n * @property {number} [maxCacheSize=100] - Maximum number of cache entries\n * @property {number} [cacheExpiration=300000] - Cache expiration time in milliseconds (default: 5 minutes)\n * @property {number} [cleanupInterval=60000] - Interval for periodic cache cleanup in milliseconds (default: 1 minute)\n * @property {number} [maxRetries=3] - Default maximum retry attempts for fetchers\n * @property {number} [retryDelay=1000] - Default initial retry delay in milliseconds\n * @property {number} [retryMultiplier=2] - Default exponential backoff multiplier for retries\n * @property {number[]} [retryableStatusCodes=[408, 429, 500, 502, 503, 504]] - Default HTTP status codes that trigger retries\n */\nexport interface ManagerOptions {\n\tmaxCacheSize?: number;\n\tcacheExpiration?: number;\n\tcleanupInterval?: number;\n\tmaxRetries?: number;\n\tretryDelay?: number;\n\tretryMultiplier?: number;\n\tretryableStatusCodes?: number[];\n}\n\n/**\n * @typedef {Object} ManagerStats\n * @property {number} activeFetchers - Number of active fetcher instances\n * @property {number} pendingRequests - Number of pending requests\n * @property {number} cacheSize - Current number of cached entries\n * @property {number} maxCacheSize - Maximum cache size limit\n * @property {number} expiredEntries - Number of expired entries not yet cleaned\n * @property {number} cacheUtilization - Cache utilization percentage\n * @property {string} lastCleanup - ISO timestamp of last cleanup\n * @property {number} cacheExpiration - Cache expiration time in milliseconds\n */\nexport interface ManagerStats {\n\tactiveFetchers: number;\n\tpendingRequests: number;\n\tcacheSize: number;\n\tmaxCacheSize: number;\n\texpiredEntries: number;\n\tcacheUtilization: number;\n\tlastCleanup: string;\n\tcacheExpiration: number;\n}\n\n/**\n * @typedef {Object} RetryConfig\n * @property {number} [maxRetries] - Maximum number of retry attempts\n * @property {number} [retryDelay] - Initial retry delay in milliseconds\n * @property {number} [retryMultiplier] - Exponential backoff multiplier\n * @property {number[]} [retryableStatusCodes] - HTTP status codes that should trigger retries\n */\nexport interface RetryConfig {\n\tmaxRetries?: number;\n\tretryDelay?: number;\n\tretryMultiplier?: number;\n\tretryableStatusCodes?: number[];\n\tmethod?: HttpMethod;\n}\n\n/**\n * IbiraAPIFetchManager - Manages multiple concurrent API fetch operations\n * \n * This class coordinates multiple IbiraAPIFetcher instances, handling race conditions,\n * preventing duplicate requests, and managing shared caching across different endpoints.\n * It provides a centralized way to manage API interactions in your application.\n * \n * **Key Features:**\n * - Request deduplication to prevent concurrent identical requests\n * - Centralized cache management across all fetchers  \n * - Race condition protection for multiple simultaneous calls\n * - Automatic periodic cache cleanup\n * - Per-URL and global retry configuration\n * - Statistics and monitoring capabilities\n * - Lifecycle management for cleanup\n * \n * @class IbiraAPIFetchManager\n * @since 0.1.0-alpha\n * @author Marcelo Pereira Barbosa\n * \n * @example\n * // Create manager with custom settings\n * const manager = new IbiraAPIFetchManager({\n *   maxCacheSize: 200,\n *   cacheExpiration: 10 * 60 * 1000 // 10 minutes\n * });\n * \n * // Fetch single URL\n * const data = await manager.fetch('https://api.example.com/data');\n * \n * @example\n * // Fetch multiple URLs concurrently\n * const urls = [\n *   'https://api.example.com/users',\n *   'https://api.example.com/posts'\n * ];\n * const results = await manager.fetchMultiple(urls);\n * \n * @example\n * // Subscribe to events\n * manager.subscribe('https://api.example.com/data', {\n *   update: (event, payload) => console.log(event, payload)\n * });\n * \n * @example\n * // Clean up when done\n * manager.destroy();\n */\nexport class IbiraAPIFetchManager {\n\tfetchers: Map<string, IbiraAPIFetcher>;\n\tpendingRequests: Map<string, Promise<unknown>>;\n\tglobalCache: Map<string, CacheEntry> & { maxSize: number; expiration: number };\n\tmaxCacheSize: number;\n\tcacheExpiration: number;\n\tcleanupInterval: number;\n\tlastCleanup: number;\n\tdefaultMaxRetries: number;\n\tdefaultRetryDelay: number;\n\tdefaultRetryMultiplier: number;\n\tdefaultRetryableStatusCodes: number[];\n\tcleanupTimer: ReturnType<typeof setInterval> | null;\n\n\t/**\n\t * Creates a new IbiraAPIFetchManager instance\n\t * \n\t * @param {ManagerOptions} [options={}] - Configuration options\n\t * \n\t * @example\n\t * // Create manager with custom settings\n\t * const manager = new IbiraAPIFetchManager({\n\t *   maxCacheSize: 200,\n\t *   cacheExpiration: 10 * 60 * 1000, // 10 minutes\n\t *   maxRetries: 5\n\t * });\n\t */\n\tconstructor(options: ManagerOptions = {}) {\n\t\tthis.fetchers = new Map();\n\t\tthis.pendingRequests = new Map();\n\t\tthis.globalCache = new Map() as Map<string, CacheEntry> & { maxSize: number; expiration: number };\n\t\tthis.maxCacheSize = options.maxCacheSize || 100; // Prevent unbounded cache growth\n\t\tthis.cacheExpiration = options.cacheExpiration || 300000; // 5 minutes default cache expiration\n\t\tthis.cleanupInterval = options.cleanupInterval || 60000; // 1 minute cleanup interval\n\t\tthis.lastCleanup = Date.now();\n\t\t\n\t\t// Retry configuration for all fetchers\n\t\tthis.defaultMaxRetries = options.maxRetries || 3;\n\t\tthis.defaultRetryDelay = options.retryDelay || 1000;\n\t\tthis.defaultRetryMultiplier = options.retryMultiplier || 2;\n\t\tthis.defaultRetryableStatusCodes = options.retryableStatusCodes || [408, 429, 500, 502, 503, 504];\n\t\tthis.cleanupTimer = null;\n\t\t\n\t\t// Start periodic cleanup\n\t\tthis._startPeriodicCleanup();\n\t}\n\n\t/**\n\t * Creates or retrieves a fetcher instance for the given URL and method.\n\t * \n\t * @param {string} url - The API endpoint URL\n\t * @param {Object} [options={}] - Configuration options for the fetcher\n\t * @param {number} [options.timeout] - Request timeout in milliseconds\n\t * @param {number} [options.maxRetries] - Maximum number of retry attempts\n\t * @param {number} [options.retryDelay] - Initial retry delay in milliseconds\n\t * @param {number} [options.retryMultiplier] - Exponential backoff multiplier\n\t * @param {number[]} [options.retryableStatusCodes] - HTTP status codes that should trigger retries\n\t * @param {'GET'|'POST'|'PUT'|'PATCH'|'DELETE'|'HEAD'} [options.method='GET'] - HTTP method\n\t * @param {Object|string|FormData|Blob|null} [options.body=null] - Request body\n\t * @param {Object} [options.headers={}] - Additional request headers\n\t * @returns {IbiraAPIFetcher} The fetcher instance for this URL + method combination\n\t * \n\t * @example\n\t * const fetcher = manager.getFetcher('https://api.example.com/data', {\n\t *   timeout: 5000,\n\t *   maxRetries: 5\n\t * });\n\t */\n\tgetFetcher(url: string, options: FetcherOptions = {}): IbiraAPIFetcher {\n\t\tconst method = (options.method || 'GET').toUpperCase() as HttpMethod;\n\t\tconst fetcherKey = `${method}:${url}`;\n\t\tif (!this.fetchers.has(fetcherKey)) {\n\t\t\t// Configure global cache with manager settings\n\t\t\tthis.globalCache.maxSize = this.maxCacheSize;\n\t\t\tthis.globalCache.expiration = this.cacheExpiration;\n\t\t\t\n\t\t\t// Create fetcher with shared global cache\n\t\t\tconst fetcherOptions = {\n\t\t\t\ttimeout: options.timeout,\n\t\t\t\tmaxRetries: options.maxRetries !== undefined ? options.maxRetries : this.defaultMaxRetries,\n\t\t\t\tretryDelay: options.retryDelay !== undefined ? options.retryDelay : this.defaultRetryDelay,\n\t\t\t\tretryMultiplier: options.retryMultiplier !== undefined ? options.retryMultiplier : this.defaultRetryMultiplier,\n\t\t\t\tretryableStatusCodes: options.retryableStatusCodes || this.defaultRetryableStatusCodes,\n\t\t\t\tmethod,\n\t\t\t\tbody: options.body !== undefined ? options.body : null,\n\t\t\t\theaders: options.headers || {},\n\t\t\t};\n\t\t\t\n\t\t\tconst fetcher = new IbiraAPIFetcher(url, this.globalCache, fetcherOptions);\n\t\t\tthis.fetchers.set(fetcherKey, fetcher);\n\t\t}\n\t\t\n\t\treturn this.fetchers.get(fetcherKey)!;\n\t}\n\n\t/**\n\t * Starts periodic cleanup of expired cache entries\n\t * \n\t * @private\n\t */\n\tprivate _startPeriodicCleanup(): void {\n\t\tthis.cleanupTimer = setInterval(() => {\n\t\t\tthis._performPeriodicCleanup();\n\t\t}, this.cleanupInterval);\n\t}\n\n\t/**\n\t * Identifies expired cache entries that should be removed\n\t * This is a pure function that returns keys to delete without mutating state\n\t * \n\t * @private\n\t * @param {Map} cache - The cache map to check\n\t * @param {number} currentTime - Current timestamp in milliseconds\n\t * @returns {string[]} Array of cache keys that have expired\n\t */\n\tprivate _getExpiredCacheKeys(cache: Map<string, CacheEntry>, currentTime: number): string[] {\n\t\tconst expiredKeys = [];\n\n\t\t// Find all expired entries\n\t\tfor (const [key, entry] of cache.entries()) {\n\t\t\tif (currentTime >= entry.expiresAt) {\n\t\t\t\texpiredKeys.push(key);\n\t\t\t}\n\t\t}\n\n\t\treturn expiredKeys;\n\t}\n\n\t/**\n\t * Performs periodic cleanup of expired cache entries and enforces size limits\n\t * \n\t * @private\n\t */\n\tprivate _performPeriodicCleanup(): void {\n\t\tconst now = Date.now();\n\t\tconst expiredKeys = this._getExpiredCacheKeys(this.globalCache, now);\n\n\t\t// Remove expired entries\n\t\texpiredKeys.forEach(key => this.globalCache.delete(key));\n\n\t\t// Enforce cache size limits using LRU strategy\n\t\tthis._enforceCacheSizeLimit();\n\t\t\n\t\tthis.lastCleanup = now;\n\t}\n\n\t/**\n\t * Enforces cache size limits by removing oldest entries\n\t * Uses LRU (Least Recently Used) eviction strategy\n\t * \n\t * @private\n\t */\n\tprivate _enforceCacheSizeLimit(): void {\n\t\tif (this.globalCache.size <= this.maxCacheSize) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Convert cache entries to array for sorting\n\t\tconst entries = Array.from(this.globalCache.entries());\n\t\t\n\t\t// Sort by timestamp (oldest first)\n\t\tentries.sort((a, b) => a[1].timestamp - b[1].timestamp);\n\t\t\n\t\t// Calculate how many entries to remove\n\t\tconst entriesToRemove = this.globalCache.size - this.maxCacheSize;\n\t\t\n\t\t// Remove oldest entries\n\t\tfor (let i = 0; i < entriesToRemove; i++) {\n\t\t\tthis.globalCache.delete(entries[i][0]);\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a cache entry is still valid (not expired)\n\t * \n\t * @private\n\t * @param {Object} cacheEntry - The cache entry to check\n\t * @param {number} currentTime - Current timestamp in milliseconds\n\t * @returns {boolean} True if the entry is still valid\n\t */\n\tprivate _isCacheEntryValid(cacheEntry: CacheEntry | undefined, currentTime: number): boolean {\n\t\treturn cacheEntry !== null && cacheEntry !== undefined && currentTime < cacheEntry.expiresAt;\n\t}\n\n\t/**\n\t * Fetches data with race condition protection and request deduplication\n\t * \n\t * This method ensures that multiple concurrent calls to the same endpoint\n\t * are deduplicated, preventing unnecessary network requests and potential\n\t * race conditions.\n\t * \n\t * @async\n\t * @param {string} url - The API endpoint URL\n\t * @param {Object} [options={}] - Configuration options\n\t * @param {number} [options.timeout] - Request timeout in milliseconds\n\t * @param {number} [options.maxRetries] - Maximum number of retry attempts\n\t * @param {number} [options.retryDelay] - Initial retry delay in milliseconds\n\t * @param {number} [options.retryMultiplier] - Exponential backoff multiplier\n\t * @param {number[]} [options.retryableStatusCodes] - Array of HTTP status codes that should trigger retries\n\t * @param {'GET'|'POST'|'PUT'|'PATCH'|'DELETE'|'HEAD'} [options.method='GET'] - HTTP method\n\t * @param {Object|string|FormData|Blob|null} [options.body=null] - Request body (plain objects auto-serialized to JSON)\n\t * @param {Object} [options.headers={}] - Additional request headers\n\t * @returns {Promise<*>} Promise that resolves to the fetched data\n\t * @throws {Error} Network errors, HTTP errors, or JSON parsing errors\n\t * \n\t * @example\n\t * try {\n\t *   const data = await manager.fetch('https://api.example.com/users');\n\t *   console.log(data);\n\t * } catch (error) {\n\t *   console.error('Fetch failed:', error);\n\t * }\n\t */\n\tasync fetch(url: string, options: FetcherOptions = {}): Promise<unknown> {\n\t\tconst fetcher = this.getFetcher(url, options);\n\t\tconst cacheKey = fetcher.getCacheKey();\n\n\t\t// Check if there's already a pending request for this URL\n\t\tif (this.pendingRequests.has(cacheKey)) {\n\t\t\t// Wait for the existing request instead of creating a new one\n\t\t\treturn await this.pendingRequests.get(cacheKey);\n\t\t}\n\n\t\t// Create a new request promise and track it\n\t\tconst requestPromise = this._executeFetch(fetcher);\n\t\tthis.pendingRequests.set(cacheKey, requestPromise);\n\n\t\ttry {\n\t\t\tconst result = await requestPromise;\n\t\t\treturn result;\n\t\t} finally {\n\t\t\t// Clean up the pending request tracker\n\t\t\tthis.pendingRequests.delete(cacheKey);\n\t\t}\n\t}\n\n\t/**\n\t * Internal method to execute the actual fetch operation\n\t * \n\t * @private\n\t * @async\n\t * @param {IbiraAPIFetcher} fetcher - The fetcher instance to use\n\t * @returns {Promise<*>} Promise that resolves to the fetched data\n\t */\n\tprivate async _executeFetch(fetcher: IbiraAPIFetcher): Promise<unknown> {\n\t\t// ✅ fetchData() now returns data directly, no need to check fetcher.data\n\t\treturn await fetcher.fetchData();\n\t}\n\n\t/**\n\t * Fetches multiple URLs concurrently with proper coordination\n\t * \n\t * @async\n\t * @param {string[]} urls - Array of URLs to fetch\n\t * @param {Object} [options={}] - Configuration options applied to all fetchers\n\t * @returns {Promise<PromiseSettledResult[]>} Promise that resolves to array of results with status and value/reason\n\t * \n\t * @example\n\t * const urls = [\n\t *   'https://api.example.com/users',\n\t *   'https://api.example.com/posts'\n\t * ];\n\t * const results = await manager.fetchMultiple(urls);\n\t * results.forEach(result => {\n\t *   if (result.status === 'fulfilled') {\n\t *     console.log('Success:', result.value);\n\t *   } else {\n\t *     console.error('Error:', result.reason);\n\t *   }\n\t * });\n\t */\n\tasync fetchMultiple(urls: string[], options: FetcherOptions = {}): Promise<PromiseSettledResult<unknown>[]> {\n\t\tconst promises = urls.map(url => this.fetch(url, options));\n\t\treturn await Promise.allSettled(promises);\n\t}\n\n\t/**\n\t * Subscribe to updates from a specific fetcher\n\t * \n\t * @param {string} url - The URL of the fetcher to subscribe to\n\t * @param {Object} observer - Observer object with update method\n\t * @param {Function} observer.update - Method called with (eventType, payload) on events\n\t * \n\t * @example\n\t * manager.subscribe('https://api.example.com/data', {\n\t *   update: (eventType, payload) => {\n\t *     console.log('Event:', eventType, payload);\n\t *   }\n\t * });\n\t */\n\tsubscribe(url: string, observer: Observer): void {\n\t\tconst fetcher = this.getFetcher(url);\n\t\tfetcher.subscribe(observer);\n\t}\n\n\t/**\n\t * Unsubscribe from updates from a specific fetcher\n\t * \n\t * @param {string} url - The URL of the fetcher to unsubscribe from\n\t * @param {Object} observer - Observer object to remove\n\t */\n\tunsubscribe(url: string, observer: Observer): void {\n\t\tconst fetcherKey = `GET:${url}`;\n\t\tif (this.fetchers.has(fetcherKey)) {\n\t\t\tthis.fetchers.get(fetcherKey)!.unsubscribe(observer);\n\t\t}\n\t}\n\n\t/**\n\t * Check if there's a pending request for a specific URL\n\t * \n\t * @param {string} url - The URL to check pending status for\n\t * @returns {boolean} Whether there's a pending request for this URL\n\t * \n\t * @example\n\t * if (manager.isLoading('https://api.example.com/data')) {\n\t *   console.log('Request in progress...');\n\t * }\n\t */\n\tisLoading(url: string): boolean {\n\t\tconst fetcher = this.getFetcher(url);\n\t\tconst cacheKey = fetcher.getCacheKey();\n\t\treturn this.pendingRequests.has(cacheKey);\n\t}\n\n\t/**\n\t * Get cached data for a specific URL without triggering a fetch\n\t * \n\t * @param {string} url - The URL to get cached data for\n\t * @returns {*|null} Cached data or null if not found or expired\n\t * \n\t * @example\n\t * const cached = manager.getCachedData('https://api.example.com/data');\n\t * if (cached) {\n\t *   console.log('Using cached data:', cached);\n\t * } else {\n\t *   const fresh = await manager.fetch('https://api.example.com/data');\n\t * }\n\t */\n\tgetCachedData(url: string): unknown | null {\n\t\tconst fetcher = this.getFetcher(url);\n\t\tconst cacheKey = fetcher.getCacheKey();\n\t\tconst cacheEntry = this.globalCache.get(cacheKey);\n\t\t\n\t\tconst now = Date.now();\n\t\tif (cacheEntry && this._isCacheEntryValid(cacheEntry, now)) {\n\t\t\t// Update timestamp for LRU tracking (immutable — create new entry)\n\t\t\tthis.globalCache.set(cacheKey, { ...cacheEntry, timestamp: now });\n\t\t\treturn cacheEntry.data;\n\t\t}\n\t\t\n\t\t// Remove expired entry if it exists\n\t\tif (cacheEntry) {\n\t\t\tthis.globalCache.delete(cacheKey);\n\t\t}\n\t\t\n\t\treturn null;\n\t}\n\n\t/**\n\t * Clear cached data for a specific URL or all cached data\n\t * \n\t * @param {string} [url] - Optional URL to clear cache for. If not provided, clears all cache\n\t * \n\t * @example\n\t * // Clear specific URL cache\n\t * manager.clearCache('https://api.example.com/data');\n\t * \n\t * @example\n\t * // Clear all cache\n\t * manager.clearCache();\n\t */\n\tclearCache(url: string | null = null): void {\n\t\tif (url) {\n\t\t\tconst fetcher = this.getFetcher(url);\n\t\t\tconst cacheKey = fetcher.getCacheKey();\n\t\t\tthis.globalCache.delete(cacheKey);\n\t\t} else {\n\t\t\tthis.globalCache.clear();\n\t\t}\n\t}\n\n\t/**\n\t * Clean up resources and cancel pending requests\n\t * Call this when the manager is no longer needed\n\t * \n\t * @example\n\t * // Clean up when component unmounts\n\t * useEffect(() => {\n\t *   return () => {\n\t *     manager.destroy();\n\t *   };\n\t * }, []);\n\t */\n\tdestroy(): void {\n\t\t// Stop periodic cleanup timer\n\t\tif (this.cleanupTimer) {\n\t\t\tclearInterval(this.cleanupTimer);\n\t\t\tthis.cleanupTimer = null;\n\t\t}\n\t\t\n\t\t// Clear all pending requests\n\t\tthis.pendingRequests.clear();\n\t\t\n\t\t// Clear all fetchers\n\t\tthis.fetchers.clear();\n\t\t\n\t\t// Clear global cache\n\t\tthis.globalCache.clear();\n\t}\n\n\t/**\n\t * Get statistics about the current state of the manager\n\t * \n\t * @returns {ManagerStats} Statistics object with current state information\n\t * \n\t * @example\n\t * const stats = manager.getStats();\n\t * console.log(`Cache: ${stats.cacheSize}/${stats.maxCacheSize} (${stats.cacheUtilization}%)`);\n\t */\n\tgetStats(): ManagerStats {\n\t\tconst now = Date.now();\n\t\tconst expiredKeys = this._getExpiredCacheKeys(this.globalCache, now);\n\t\t\n\t\treturn {\n\t\t\tactiveFetchers: this.fetchers.size,\n\t\t\tpendingRequests: this.pendingRequests.size,\n\t\t\tcacheSize: this.globalCache.size,\n\t\t\tmaxCacheSize: this.maxCacheSize,\n\t\t\texpiredEntries: expiredKeys.length,\n\t\t\tcacheUtilization: Math.round((this.globalCache.size / this.maxCacheSize) * 100),\n\t\t\tlastCleanup: new Date(this.lastCleanup).toISOString(),\n\t\t\tcacheExpiration: this.cacheExpiration\n\t\t};\n\t}\n\n\t/**\n\t * Manually trigger cache cleanup\n\t * Useful for testing or when you want to force cleanup\n\t * \n\t * @example\n\t * // Force cleanup before important operation\n\t * manager.triggerCleanup();\n\t */\n\ttriggerCleanup(): void {\n\t\tthis._performPeriodicCleanup();\n\t}\n\n\t/**\n\t * Set cache expiration time for new entries\n\t * \n\t * @param {number} milliseconds - Cache expiration time in milliseconds\n\t * \n\t * @example\n\t * // Set cache expiration to 10 minutes\n\t * manager.setCacheExpiration(10 * 60 * 1000);\n\t */\n\tsetCacheExpiration(milliseconds: number): void {\n\t\tthis.cacheExpiration = milliseconds;\n\t}\n\n\t/**\n\t * Set maximum cache size\n\t * \n\t * @param {number} size - Maximum number of cache entries\n\t * \n\t * @example\n\t * // Increase cache size for better performance\n\t * manager.setMaxCacheSize(500);\n\t */\n\tsetMaxCacheSize(size: number): void {\n\t\tthis.maxCacheSize = size;\n\t\t// Immediately enforce the new limit\n\t\tthis._enforceCacheSizeLimit();\n\t}\n\n\t/**\n\t * Set default retry configuration for new fetchers\n\t * \n\t * @param {RetryConfig} [retryConfig={}] - Retry configuration object\n\t * \n\t * @example\n\t * manager.setRetryConfig({\n\t *   maxRetries: 5,\n\t *   retryDelay: 2000,\n\t *   retryMultiplier: 1.5\n\t * });\n\t */\n\tsetRetryConfig(retryConfig: RetryConfig = {}): void {\n\t\tif (retryConfig.maxRetries !== undefined) { this.defaultMaxRetries = retryConfig.maxRetries; }\n\t\tif (retryConfig.retryDelay !== undefined) { this.defaultRetryDelay = retryConfig.retryDelay; }\n\t\tif (retryConfig.retryMultiplier !== undefined) { this.defaultRetryMultiplier = retryConfig.retryMultiplier; }\n\t\tif (retryConfig.retryableStatusCodes) { this.defaultRetryableStatusCodes = retryConfig.retryableStatusCodes; }\n\t}\n\n\t/**\n\t * Get current retry configuration\n\t * \n\t * @returns {RetryConfig} Current retry configuration\n\t * \n\t * @example\n\t * const config = manager.getRetryConfig();\n\t * console.log(`Max retries: ${config.maxRetries}`);\n\t */\n\tgetRetryConfig(): RetryConfig {\n\t\treturn {\n\t\t\tmaxRetries: this.defaultMaxRetries,\n\t\t\tretryDelay: this.defaultRetryDelay,\n\t\t\tretryMultiplier: this.defaultRetryMultiplier,\n\t\t\tretryableStatusCodes: [...this.defaultRetryableStatusCodes]\n\t\t};\n\t}\n\n\t/**\n\t * Set retry configuration for a specific URL\n\t * Creates a new immutable fetcher instance with updated configuration\n\t * instead of modifying existing properties (which would fail due to Object.freeze)\n\t * \n\t * @param {string} url - The URL to configure retries for\n\t * @param {RetryConfig} [retryConfig={}] - Retry configuration object\n\t * \n\t * @example\n\t * // Set higher retries for critical endpoint\n\t * manager.setRetryConfigForUrl('https://api.example.com/critical', {\n\t *   maxRetries: 10,\n\t *   retryDelay: 500\n\t * });\n\t */\n\tsetRetryConfigForUrl(url: string, retryConfig: RetryConfig = {}): void {\n\t\t// ✅ IMMUTABLE: Create new fetcher instance instead of modifying existing one\n\t\t// This respects the frozen nature of IbiraAPIFetcher instances\n\t\tconst method = (retryConfig.method || 'GET').toUpperCase() as HttpMethod;\n\t\tconst fetcherKey = `${method}:${url}`;\n\t\tif (this.fetchers.has(fetcherKey)) {\n\t\t\tconst oldFetcher = this.fetchers.get(fetcherKey)!;\n\t\t\t\n\t\t\t// Create new options object with updated retry configuration\n\t\t\tconst newOptions = {\n\t\t\t\ttimeout: oldFetcher.timeout,\n\t\t\t\tmaxRetries: retryConfig.maxRetries !== undefined ? retryConfig.maxRetries : oldFetcher.maxRetries,\n\t\t\t\tretryDelay: retryConfig.retryDelay !== undefined ? retryConfig.retryDelay : oldFetcher.retryDelay,\n\t\t\t\tretryMultiplier: retryConfig.retryMultiplier !== undefined ? retryConfig.retryMultiplier : oldFetcher.retryMultiplier,\n\t\t\t\tretryableStatusCodes: retryConfig.retryableStatusCodes || [...oldFetcher.retryableStatusCodes],\n\t\t\t\teventNotifier: oldFetcher.eventNotifier,\n\t\t\t\tmethod: oldFetcher.method,\n\t\t\t\tbody: oldFetcher.body,\n\t\t\t\theaders: { ...oldFetcher.headers },\n\t\t\t};\n\t\t\t\n\t\t\t// Create new immutable fetcher instance and replace the old one\n\t\t\tconst newFetcher = new IbiraAPIFetcher(url, oldFetcher.cache, newOptions);\n\t\t\tthis.fetchers.set(fetcherKey, newFetcher);\n\t\t} else {\n\t\t\t// If fetcher doesn't exist yet, create it with the specified retry config\n\t\t\tthis.getFetcher(url, retryConfig);\n\t\t}\n\t}\n}\n","/**\n * @fileoverview Default cache implementation with expiration and size limits\n * @module utils/DefaultCache\n * @license MIT\n * @copyright 2025 Marcelo Pereira Barbosa\n */\n\n/**\n * A single entry stored in the cache.\n *\n * @template T - The type of the cached data. Defaults to `unknown` for backward compatibility.\n */\nexport interface CacheEntry<T = unknown> {\n\treadonly data: T;\n\treadonly timestamp: number;\n\treadonly expiresAt: number;\n}\n\n/**\n * @typedef {Object} CacheOptions\n * @property {number} [maxSize=50] - Maximum number of cache entries\n * @property {number} [expiration=300000] - Cache expiration time in milliseconds (default: 5 minutes)\n */\nexport interface CacheOptions {\n\tmaxSize?: number;\n\texpiration?: number;\n}\n\n/**\n * DefaultCache - Map-based cache implementation with expiration and LRU eviction\n * \n * Provides a simple but effective caching mechanism for IbiraAPIFetcher with:\n * - Automatic expiration of old entries\n * - LRU (Least Recently Used) eviction when size limit is reached\n * - Map-compatible interface for easy integration\n * \n * @class DefaultCache\n * @template T - The type of the cached data. Defaults to `unknown` for backward compatibility.\n *   Use a specific type (e.g. `DefaultCache<User>`) to get typed cache entries.\n * @implements {Map}\n * @since 0.1.0-alpha\n * @author Marcelo Pereira Barbosa\n * \n * @example\n * // Untyped (backward-compatible)\n * const cache = new DefaultCache({ maxSize: 100, expiration: 5 * 60 * 1000 });\n * \n * @example\n * // Typed for end-to-end type inference\n * interface User { id: number; name: string; }\n * const cache = new DefaultCache<User>();\n * cache.set('user:1', { data: { id: 1, name: 'Alice' }, timestamp: Date.now(), expiresAt: Date.now() + 300000 });\n * const entry = cache.get('user:1'); // entry.data is typed as User\n */\nexport class DefaultCache<T = unknown> {\n\tstorage: Map<string, CacheEntry<T>>;\n\tmaxSize: number;\n\texpiration: number;\n\n\t/**\n\t * Creates a new DefaultCache instance\n\t * \n\t * @param {CacheOptions} [options={}] - Cache configuration options\n\t * \n\t * @example\n\t * const cache = new DefaultCache({ maxSize: 200, expiration: 600000 });\n\t */\n\tconstructor(options: CacheOptions = {}) {\n\t\tthis.storage = new Map();\n\t\tthis.maxSize = options.maxSize || 50;\n\t\tthis.expiration = options.expiration || 300000; // 5 minutes\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache\n\t * \n\t * @param {string} key - The cache key to check\n\t * @returns {boolean} True if the key exists, false otherwise\n\t * \n\t * @example\n\t * if (cache.has('myKey')) {\n\t *   console.log('Key exists');\n\t * }\n\t */\n\thas(key: string): boolean {\n\t\treturn this.storage.has(key);\n\t}\n\n\t/**\n\t * Retrieves a value from the cache\n\t * \n\t * @param {string} key - The cache key to retrieve\n\t * @returns {CacheEntry | undefined} The cached value or undefined if not found\n\t * \n\t * @example\n\t * const data = cache.get('myKey');\n\t * if (data) console.log('Found:', data);\n\t */\n\tget(key: string): CacheEntry<T> | undefined {\n\t\treturn this.storage.get(key);\n\t}\n\n\t/**\n\t * Stores a value in the cache\n\t * Automatically enforces size limits using LRU eviction\n\t * \n\t * @param {string} key - The cache key\n\t * @param {CacheEntry} value - The value to cache\n\t * \n\t * @example\n\t * cache.set('user:123', { data: { name: 'John' }, timestamp: Date.now(), expiresAt: Date.now() + 300000 });\n\t */\n\tset(key: string, value: CacheEntry<T>): void {\n\t\tthis.storage.set(key, value);\n\t\tthis._enforceSizeLimit();\n\t}\n\n\t/**\n\t * Removes a value from the cache\n\t * \n\t * @param {string} key - The cache key to delete\n\t * @returns {boolean} True if the entry was deleted, false otherwise\n\t * \n\t * @example\n\t * const deleted = cache.delete('myKey');\n\t * console.log(deleted ? 'Deleted' : 'Not found');\n\t */\n\tdelete(key: string): boolean {\n\t\treturn this.storage.delete(key);\n\t}\n\n\t/**\n\t * Clears all entries from the cache\n\t * \n\t * @example\n\t * cache.clear();\n\t * console.log('Cache cleared, size:', cache.size);\n\t */\n\tclear(): void {\n\t\tthis.storage.clear();\n\t}\n\n\t/**\n\t * Gets the current number of entries in the cache\n\t * \n\t * @returns {number} The number of cached entries\n\t * \n\t * @example\n\t * console.log(`Cache has ${cache.size} entries`);\n\t */\n\tget size(): number {\n\t\treturn this.storage.size;\n\t}\n\n\t/**\n\t * Returns an iterator over all cache entries\n\t * \n\t * @returns {IterableIterator<[string, CacheEntry]>} Iterator over [key, value] pairs\n\t * \n\t * @example\n\t * for (const [key, value] of cache.entries()) {\n\t *   console.log(key, value);\n\t * }\n\t */\n\tentries(): IterableIterator<[string, CacheEntry<T>]> {\n\t\treturn this.storage.entries();\n\t}\n\n\t/**\n\t * Enforces the maximum cache size by removing oldest entries\n\t * Uses LRU (Least Recently Used) eviction strategy\n\t * \n\t * @private\n\t */\n\tprivate _enforceSizeLimit(): void {\n\t\tif (this.storage.size <= this.maxSize) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst entries: Array<[string, CacheEntry<T>]> = Array.from(this.storage.entries());\n\t\tentries.sort((a, b) => a[1].timestamp - b[1].timestamp);\n\t\t\n\t\tconst entriesToRemove = this.storage.size - this.maxSize;\n\t\tfor (let i = 0; i < entriesToRemove; i++) {\n\t\t\tthis.storage.delete(entries[i][0]);\n\t\t}\n\t}\n}\n","/**\n * @fileoverview Throttle higher-order function — limits how often a function can be called\n * @module utils/throttle\n * @license MIT\n * @copyright 2026 Marcelo Pereira Barbosa\n */\n\n/**\n * A throttled function with an additional `flush()` method that resets the cooldown\n * so the next call executes immediately.\n *\n * @template TArgs - Tuple of argument types of the wrapped function\n * @template TReturn - Return type of the wrapped function\n */\nexport interface ThrottledFunction<TArgs extends unknown[], TReturn> {\n\t/**\n\t * Calls the wrapped function if the cooldown has elapsed; returns `undefined` otherwise.\n\t */\n\t(...args: TArgs): TReturn | undefined;\n\t/**\n\t * Resets the internal timestamp so the next call executes immediately regardless of the\n\t * elapsed time since the last execution.\n\t */\n\tflush(): void;\n}\n\n/**\n * Creates a throttled version of `fn` that executes at most once per `wait` milliseconds\n * (leading-edge strategy: the first call in each window fires immediately).\n *\n * Calls made while the cooldown is active are silently dropped and return `undefined`.\n * Use the returned `flush()` method to reset the cooldown on demand.\n *\n * @template TArgs - Tuple of argument types of the wrapped function\n * @template TReturn - Return type of the wrapped function\n *\n * @param {(...args: TArgs) => TReturn} fn - The function to throttle\n * @param {number} wait - Minimum milliseconds between executions (must be ≥ 0)\n * @returns {ThrottledFunction<TArgs, TReturn>} Throttled wrapper with a `flush()` escape hatch\n *\n * @throws {TypeError} When `fn` is not a function or `wait` is not a non-negative number\n *\n * @example\n * // Rate-limit a fetch to at most one request per second\n * import { IbiraAPIFetcher } from 'ibira.js';\n * import { throttle } from 'ibira.js/utils';\n *\n * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/data');\n * const throttledFetch = throttle(fetcher.fetchData.bind(fetcher), 1000);\n *\n * // Only the first call per second reaches the network; others return undefined\n * const result = await throttledFetch(); // executes\n * const dropped = throttledFetch();     // undefined — within cooldown\n *\n * @example\n * // Force the next call through regardless of the cooldown\n * throttledFetch.flush();\n * const fresh = await throttledFetch(); // executes immediately\n *\n * @since 0.4.20-alpha\n */\nexport function throttle<TArgs extends unknown[], TReturn>(\n\tfn: (...args: TArgs) => TReturn,\n\twait: number,\n): ThrottledFunction<TArgs, TReturn> {\n\tif (typeof fn !== 'function') {\n\t\tthrow new TypeError('throttle: first argument must be a function');\n\t}\n\tif (typeof wait !== 'number' || wait < 0 || !isFinite(wait)) {\n\t\tthrow new TypeError('throttle: wait must be a non-negative finite number');\n\t}\n\n\tlet lastCall = 0;\n\n\tconst throttled = (...args: TArgs): TReturn | undefined => {\n\t\tconst now = Date.now();\n\t\tif (now - lastCall >= wait) {\n\t\t\tlastCall = now;\n\t\t\treturn fn(...args);\n\t\t}\n\t\treturn undefined;\n\t};\n\n\tthrottled.flush = (): void => {\n\t\tlastCall = 0;\n\t};\n\n\treturn throttled;\n}\n","/**\n * @fileoverview Debounce higher-order function — delays execution until activity stops\n * @module utils/debounce\n * @license MIT\n * @copyright 2026 Marcelo Pereira Barbosa\n */\n\n/**\n * A debounced function with additional control methods.\n *\n * @template TArgs - Tuple of argument types of the wrapped function\n * @template TReturn - Return type of the wrapped function\n */\nexport interface DebouncedFunction<TArgs extends unknown[], TReturn> {\n\t/**\n\t * Resets the wait timer. All callers in the current window share the same Promise,\n\t * which resolves (or rejects) once the trailing-edge invocation completes.\n\t */\n\t(...args: TArgs): Promise<TReturn>;\n\t/**\n\t * Cancels the pending invocation. All Promises already returned by this debounced\n\t * function will remain pending indefinitely — callers should check `cancel()` is\n\t * appropriate before calling it.\n\t */\n\tcancel(): void;\n\t/**\n\t * Forces the debounced function to execute immediately with the most recently supplied\n\t * arguments, resolving all pending Promises. No-op when there is no pending invocation.\n\t */\n\tflush(): void;\n}\n\n/**\n * Creates a debounced version of `fn` that delays invocation until `wait` milliseconds\n * have elapsed since the **last** call (trailing-edge strategy).\n *\n * All calls made within the same wait window share a single Promise: when the debounced\n * function eventually fires, every caller's Promise resolves (or rejects) with the same\n * result. This makes it safe to `await` the debounced function in multiple places.\n *\n * @template TArgs - Tuple of argument types of the wrapped function\n * @template TReturn - Return type of the wrapped function\n *\n * @param {(...args: TArgs) => TReturn | Promise<TReturn>} fn - The function to debounce\n * @param {number} wait - Milliseconds to wait after the last call before executing (must be ≥ 0)\n * @returns {DebouncedFunction<TArgs, TReturn>} Debounced wrapper with `cancel()` and `flush()` methods\n *\n * @throws {TypeError} When `fn` is not a function or `wait` is not a non-negative number\n *\n * @example\n * // Search-as-you-type: fetch only after the user stops typing for 300 ms\n * import { IbiraAPIFetcher } from 'ibira.js';\n * import { debounce } from 'ibira.js/utils';\n *\n * const fetcher = IbiraAPIFetcher.withDefaultCache('https://api.example.com/search');\n * const debouncedFetch = debounce(fetcher.fetchData.bind(fetcher), 300);\n *\n * searchInput.addEventListener('input', async () => {\n *   const results = await debouncedFetch(); // only the final keystroke triggers a request\n *   renderResults(results);\n * });\n *\n * @example\n * // Cancel a pending request before navigation\n * window.addEventListener('beforeunload', () => debouncedFetch.cancel());\n *\n * @example\n * // Force immediate execution regardless of the wait timer\n * submitButton.addEventListener('click', () => debouncedFetch.flush());\n *\n * @since 0.4.20-alpha\n */\nexport function debounce<TArgs extends unknown[], TReturn>(\n\tfn: (...args: TArgs) => TReturn | Promise<TReturn>,\n\twait: number,\n): DebouncedFunction<TArgs, TReturn> {\n\tif (typeof fn !== 'function') {\n\t\tthrow new TypeError('debounce: first argument must be a function');\n\t}\n\tif (typeof wait !== 'number' || wait < 0 || !isFinite(wait)) {\n\t\tthrow new TypeError('debounce: wait must be a non-negative finite number');\n\t}\n\n\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\tlet latestArgs: TArgs | null = null;\n\tlet pending: Array<{\n\t\tresolve: (value: TReturn) => void;\n\t\treject: (reason: unknown) => void;\n\t}> = [];\n\n\tconst execute = async (): Promise<void> => {\n\t\ttimer = null;\n\t\tconst args = latestArgs!;\n\t\tlatestArgs = null;\n\t\tconst callbacks = pending.splice(0);\n\t\ttry {\n\t\t\tconst result = await fn(...args);\n\t\t\tcallbacks.forEach(cb => cb.resolve(result as TReturn));\n\t\t} catch (error) {\n\t\t\tcallbacks.forEach(cb => cb.reject(error));\n\t\t}\n\t};\n\n\tconst debounced = (...args: TArgs): Promise<TReturn> => {\n\t\tlatestArgs = args;\n\t\tif (timer !== null) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\treturn new Promise<TReturn>((resolve, reject) => {\n\t\t\tpending.push({ resolve, reject });\n\t\t\ttimer = setTimeout(() => void execute(), wait);\n\t\t});\n\t};\n\n\tdebounced.cancel = (): void => {\n\t\tif (timer !== null) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = null;\n\t\t}\n\t\tlatestArgs = null;\n\t\tpending = [];\n\t};\n\n\tdebounced.flush = (): void => {\n\t\tif (timer !== null) {\n\t\t\tclearTimeout(timer);\n\t\t\tvoid execute();\n\t\t}\n\t};\n\n\treturn debounced;\n}\n","/**\n * @fileoverview Semantic versioning configuration for ibira.js\n * @module config/version\n * @license MIT\n * @copyright 2026 Marcelo Pereira Barbosa\n * @see {@link https://semver.org/|Semantic Versioning 2.0.0}\n */\n\n/**\n * Semantic version object following SemVer 2.0.0 specification\n *\n * The version format is MAJOR.MINOR.PATCH-PRERELEASE where:\n * - MAJOR version increments for incompatible API changes\n * - MINOR version increments for backward-compatible new features\n * - PATCH version increments for backward-compatible bug fixes\n * - PRERELEASE identifier indicates unstable development (alpha, beta, rc)\n *\n * @constant\n * @type {Object}\n * @property {number} major - Major version number (breaking changes)\n * @property {number} minor - Minor version number (new features, backward compatible)\n * @property {number} patch - Patch version number (bug fixes)\n * @property {string} prerelease - Prerelease identifier (alpha, beta, rc)\n * @property {Function} toString - Returns the full version string\n *\n * @example\n * import { VERSION } from 'ibira.js';\n * console.log(VERSION.toString()); // \"0.4.22-alpha\"\n * console.log(`v${VERSION.major}.${VERSION.minor}.${VERSION.patch}`);\n */\nexport const VERSION: {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease: string;\n\ttoString(): string;\n} = {\n\tmajor: 0,\n\tminor: 4,\n\tpatch: 22,\n\tprerelease: \"alpha\", // Indicates unstable development\n\ttoString(): string {\n\t\treturn this.prerelease\n\t\t\t? `${this.major}.${this.minor}.${this.patch}-${this.prerelease}`\n\t\t\t: `${this.major}.${this.minor}.${this.patch}`;\n\t},\n};\n"],"mappings":"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","names":["DualObserverSubject","observer","o","args","err","observerFunction","fn","i","i"]}