From 5d24b76eb4a672640ee19924bf85b16312be49ba Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 11:44:25 +0200 Subject: [PATCH 01/23] Move cache policy from per-entry to per-instance Policy and maxAge are now configured once on the Cacheables constructor and apply to every entry in that instance, replacing the per-call options arg on cacheable(). This eliminates cross-policy cache entries and the errors that came with them. To compose policies, create multiple instances. The constructor key was renamed cachePolicy -> policy (the class name already implies "cache") and the CacheableOptions export was removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 89 +++++++++++++------------------ src/index.ts | 101 +++++++++++++++++------------------- tests/fetchPolicies.test.ts | 41 ++++++--------- tests/index.test.ts | 19 ++++--- 4 files changed, 108 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index b32a8f6..c6e3e60 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ cache.cacheable(() => fetch('https://some-url.com/api'), 'key') * [Usage](#usage) * [API](#api) * [new Cacheables(options?): Cacheables](#new-cacheablesoptions-cacheables) - * [cache.cacheable(resource, key, options?): Promise<T>](#cachecacheableresource-key-options-promiset) + * [cache.cacheable(resource, key): Promise<T>](#cachecacheableresource-key-promiset) * [cache.delete(key: string): void](#cachedeletekey-string-void) * [cache.clear(): void](#cacheclear-void) * [cache.keys(): string[]](#cachekeys-string) @@ -42,7 +42,6 @@ cache.cacheable(() => fetch('https://some-url.com/api'), 'key') * [Network Only – Non Concurrent](#network-only--non-concurrent) * [Max Age](#max-age) * [Stale While Revalidate](#stale-while-revalidate) - * [Cache Policy Composition](#cache-policy-composition) * [In Progress](#in-progress) * [License](#license) @@ -67,7 +66,9 @@ const apiUrl = "https://goweather.herokuapp.com/weather/Karlsruhe" // Create a new cache instance const cache = new Cacheables({ logTiming: true, - log: true + log: true, + policy: 'max-age', + maxAge: 5000, }) const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) @@ -78,10 +79,7 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) // The method returns a fully typed Promise just like `fetch(apiUrl)` // would but with the benefit of caching the result. const getWeatherData = () => - cache.cacheable(() => fetch(apiUrl), 'weather', { - cachePolicy: 'max-age', - maxAge: 5000, - }) + cache.cacheable(() => fetch(apiUrl), 'weather') const start = async () => { // Fetch some fresh weather data and store it in our cache. @@ -120,26 +118,36 @@ start() ##### - `options?: CacheOptions` ```ts -interface CacheOptions { +type CacheOptions = { enabled?: boolean // Enable/disable the cache, can be set anytime, default: true. - log?: boolean // Log hits to the cache, default: false. + log?: boolean // Log hits to the cache, default: false. logTiming?: boolean // Log the timing, default: false. -} +} & ( + | { policy?: 'cache-only' } // default + | { policy: 'network-only' } + | { policy: 'network-only-non-concurrent' } + | { policy: 'max-age', maxAge: number } // maxAge required + | { policy: 'stale-while-revalidate', maxAge?: number } // maxAge optional +) ``` +The cache policy (and `maxAge` where applicable) is set on the instance and applies to every entry in that cache. To use multiple policies, create multiple `Cacheables` instances. + #### Example: ```ts import { Cacheables } from 'cacheables' const cache = new Cacheables({ - logTiming: true + logTiming: true, + policy: 'max-age', + maxAge: 5000, }) ``` -### `cache.cacheable(resource, key, options?): Promise` +### `cache.cacheable(resource, key): Promise` -- If a resource exists in the cache (determined by the presence of a value with key `key`) `cacheable` decides on returning a cache based on the provided cache policy. +- If a resource exists in the cache (determined by the presence of a value with key `key`) `cacheable` decides on returning a cache based on the instance's cache policy. - If there's no resource in the cache, the provided `resource` will be called and used to store a cache value with key `key` and the value is returned. #### Arguments @@ -153,29 +161,14 @@ A function that returns a `Promise`. A key to store the cache at. See [Cacheables.key()](#cacheableskeyargs-string--number-string) for a safe and easy way to generate unique keys. -##### - `options?: CacheableOptions` (optional) - -An object defining the cache policy and possibly other options in the future. -The default cache policy is `cache-only`. -See [Cache Policies](#cache-policies). - -```ts -type CacheableOptions = { - cachePolicy: 'cache-only' | 'network-only-non-concurrent' | 'network-only' | 'max-age' | 'stale-while-revalidate' // See cache policies for details - maxAge?: number // Required if cache policy is `max-age` and optional if cache policy is `stale-while-revalidate` -} -``` - #### Example ```ts +const cache = new Cacheables({ policy: 'max-age', maxAge: 10000 }) + const cachedApiResponse = await cache.cacheable( () => fetch('https://github.com/'), 'key', - { - cachePolicy: 'max-age', - maxAge: 10000 - } ) ``` @@ -247,7 +240,8 @@ If there is no cache yet, all calls will be resolved by the first network reques ##### Example ```ts -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'cache-only' }) +const cache = new Cacheables({ policy: 'cache-only' }) +cache.cacheable(() => fetch(url), 'a') ``` ### Network Only @@ -257,7 +251,8 @@ Simultaneous requests trigger simultaneous network requests. ##### Example ```ts -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'network-only' }) +const cache = new Cacheables({ policy: 'network-only' }) +cache.cacheable(() => fetch(url), 'a') ``` ### Network Only – Non Concurrent @@ -267,7 +262,8 @@ All requests should be handled by the network but no concurrent network requests ##### Example ```ts -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'network-only-non-concurrent' }) +const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) +cache.cacheable(() => fetch(url), 'a') ``` ### Max Age @@ -278,10 +274,8 @@ All requests should be checked against max-age. If max-age is expired, a network ##### Example ```ts // Trigger a network request if the cached value is older than 1 second. -cache.cacheable(() => fetch(url), 'a', { - cachePolicy: 'max-age', - maxAge: 1000 -}) +const cache = new Cacheables({ policy: 'max-age', maxAge: 1000 }) +cache.cacheable(() => fetch(url), 'a') ``` ### Stale While Revalidate @@ -291,28 +285,15 @@ The cache policy `stale-while-revalidate` will return a cached value immediately ##### Example without `maxAge` ```ts // If there is a cache, return it but 'silently' update the cache. -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'stale-while-revalidate'}) +const cache = new Cacheables({ policy: 'stale-while-revalidate' }) +cache.cacheable(() => fetch(url), 'a') ``` ##### Example with `maxAge` ```ts // If there is a cache, return it and 'silently' update the cache if it's older than 1 second. -cache.cacheable(() => fetch(url), 'a', { - cachePolicy: 'stale-while-revalidate', - maxAge: 1000 -}) -``` - -### Cache Policy Composition -A single cacheable can be requested with different cache policies at any time. - -#### Example -```ts -// If there is a cache, return it. -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'cache-only' }) - -// If there is a cache, return it but 'silently' update the cache. -cache.cacheable(() => fetch(url), 'a', { cachePolicy: 'stale-while-revalidate' }) +const cache = new Cacheables({ policy: 'stale-while-revalidate', maxAge: 1000 }) +cache.cacheable(() => fetch(url), 'a') ``` ## In Progress diff --git a/src/index.ts b/src/index.ts index 97861f7..45ff8cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ //region Types -export type CacheOptions = { +type CacheOptionsBase = { /** * Enables caching */ @@ -14,37 +14,47 @@ export type CacheOptions = { logTiming?: boolean } -type CacheOnlyCachePolicy = { - cachePolicy: 'cache-only' +type CacheOnlyPolicy = { + policy?: 'cache-only' } -type NetworkOnlyNonConcurrentCachePolicy = { - cachePolicy: 'network-only-non-concurrent' +type NetworkOnlyPolicy = { + policy: 'network-only' } -type NetworkOnlyCachePolicy = { - cachePolicy: 'network-only' +type NetworkOnlyNonConcurrentPolicy = { + policy: 'network-only-non-concurrent' } -type MaxAgeCachePolicy = { - cachePolicy: 'max-age' +type MaxAgePolicy = { + policy: 'max-age' maxAge: number } -type SWRCachePolicy = { - cachePolicy: 'stale-while-revalidate' +type SWRPolicy = { + policy: 'stale-while-revalidate' maxAge?: number } +type PolicyOptions = + | CacheOnlyPolicy + | NetworkOnlyPolicy + | NetworkOnlyNonConcurrentPolicy + | MaxAgePolicy + | SWRPolicy + /** - * Cacheable options. + * Cacheables options. Combines instance settings with the cache policy + * (and policy-specific options like `maxAge`). */ -export type CacheableOptions = - | CacheOnlyCachePolicy - | NetworkOnlyCachePolicy - | NetworkOnlyNonConcurrentCachePolicy - | MaxAgeCachePolicy - | SWRCachePolicy +export type CacheOptions = CacheOptionsBase & PolicyOptions + +type Policy = + | 'cache-only' + | 'network-only' + | 'network-only-non-concurrent' + | 'max-age' + | 'stale-while-revalidate' //endregion @@ -57,10 +67,18 @@ export class Cacheables { log: boolean logTiming: boolean + #policy: Policy + #maxAge: number | undefined + constructor(options?: CacheOptions) { this.enabled = options?.enabled ?? true this.log = options?.log ?? false this.logTiming = options?.logTiming ?? false + this.#policy = options?.policy ?? 'cache-only' + this.#maxAge = + options?.policy === 'max-age' || options?.policy === 'stale-while-revalidate' + ? options.maxAge + : undefined } #cacheables: Record> = {} @@ -107,7 +125,6 @@ export class Cacheables { * that you want to cache for a certain period of time. * @param resource A function returning a Promise * @param key A key to identify the cache - * @param options {CacheableOptions} options * @example * const apiResponse = await cache.cacheable( * () => api.query({ @@ -115,16 +132,11 @@ export class Cacheables { * variables: someVariables, * }), * Cache.key('type', someCacheKey, someOtherCacheKey), - * 60000 * ) * @returns promise Resolves to the value of the provided resource, either from * cache or from the remote resource itself. */ - async cacheable( - resource: () => Promise, - key: string, - options?: CacheableOptions, - ): Promise { + async cacheable(resource: () => Promise, key: string): Promise { const shouldCache = this.enabled if (!shouldCache) { if (this.log) Logger.logDisabled() @@ -136,7 +148,7 @@ export class Cacheables { const logId = Logger.getLogId(key) if (logTiming) Logger.logTime(logId) - const result = await this.#cacheable(resource, key, options) + const result = await this.#cacheable(resource, key) if (logTiming) Logger.logTimeEnd(logId) if (log) Logger.logStats(key, this.#cacheables[key]) @@ -144,11 +156,7 @@ export class Cacheables { return result } - #cacheable( - resource: () => Promise, - key: string, - options?: CacheableOptions, - ): Promise { + #cacheable(resource: () => Promise, key: string): Promise { let cacheable = this.#cacheables[key] as Cacheable | undefined if (!cacheable) { @@ -156,7 +164,7 @@ export class Cacheables { this.#cacheables[key] = cacheable } - return cacheable.touch(resource, options) + return cacheable.touch(resource, this.#policy, this.#maxAge) } } //endregion @@ -203,20 +211,13 @@ class Cacheable { return this.#fetch(resource) } - #handlePreInit( - resource: () => Promise, - options?: CacheableOptions, - ): Promise { - if (!options) return this.#fetchNonConcurrent(resource) - switch (options.cachePolicy) { - case 'cache-only': - return this.#fetchNonConcurrent(resource) + #handlePreInit(resource: () => Promise, policy: Policy): Promise { + switch (policy) { case 'network-only': return this.#fetch(resource) + case 'cache-only': case 'stale-while-revalidate': - return this.#fetchNonConcurrent(resource) case 'max-age': - return this.#fetchNonConcurrent(resource) case 'network-only-non-concurrent': return this.#fetchNonConcurrent(resource) } @@ -258,28 +259,24 @@ class Cacheable { * Get and set the value of the Cacheable. * Some tricky race are conditions going on here, * but this should behave as expected - * @param resource - * @param options {CacheableOptions} */ async touch( resource: () => Promise, - options?: CacheableOptions, + policy: Policy, + maxAge: number | undefined, ): Promise { if (!this.#initialized) { - return this.#handlePreInit(resource, options) - } - if (!options) { - return this.#handleCacheOnly() + return this.#handlePreInit(resource, policy) } - switch (options.cachePolicy) { + switch (policy) { case 'cache-only': return this.#handleCacheOnly() case 'network-only': return this.#handleNetworkOnly(resource) case 'stale-while-revalidate': - return this.#handleSwr(resource, options.maxAge) + return this.#handleSwr(resource, maxAge) case 'max-age': - return this.#handleMaxAge(resource, options.maxAge) + return this.#handleMaxAge(resource, maxAge as number) case 'network-only-non-concurrent': return this.#handleNetworkOnlyNonConcurrent(resource) } diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index 9f532c3..df480a6 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -15,12 +15,10 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Fetch Policies', () => { it('cache-only', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ policy: 'cache-only' }) const COCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v), 'key', { - cachePolicy: 'cache-only', - }) + cache.cacheable(() => mockedApiRequest(v), 'key') const a = await COCacheable(0) const b = await COCacheable(1) @@ -32,12 +30,10 @@ describe('Fetch Policies', () => { }) it('network-only-non-concurrent', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) const NONCCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key', { - cachePolicy: 'network-only-non-concurrent', - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'key') // Preheat cache await NONCCacheable(-1) @@ -55,12 +51,10 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 0, 0, 3]) }) it('network-only', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ policy: 'network-only' }) const NOCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key', { - cachePolicy: 'network-only', - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'key') // Preheat cache await NOCacheable(-1) @@ -74,13 +68,10 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 1, 2]) }) it('max-age', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ policy: 'max-age', maxAge: 100 }) const MACacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key', { - cachePolicy: 'max-age', - maxAge: 100, - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'key') const a = await MACacheable(0) const b = await MACacheable(1) @@ -93,12 +84,10 @@ describe('Fetch Policies', () => { expect([a, b, c, d]).toEqual([0, 0, 2, 2]) }) it('stale-while-revalidate', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ policy: 'stale-while-revalidate' }) const SWRCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key', { - cachePolicy: 'stale-while-revalidate', - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'key') // Preheat cache await SWRCacheable(-1) @@ -119,13 +108,13 @@ describe('Fetch Policies', () => { }) it('stale-while-revalidate with maxAge', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ + policy: 'stale-while-revalidate', + maxAge: 200, + }) const SWRCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key', { - cachePolicy: 'stale-while-revalidate', - maxAge: 200, - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'key') // Preheat cache, takes ~50ms await SWRCacheable(0) diff --git a/tests/index.test.ts b/tests/index.test.ts index d7b103d..06e905a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,4 +1,4 @@ -import { CacheableOptions, Cacheables } from '../src' +import { Cacheables } from '../src' const errorMessage = 'This is an error message.' @@ -122,13 +122,13 @@ describe('Cache operations', () => { * Assuming the time starts at 0 */ it('Handles race conditions correctly', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ + policy: 'max-age', + maxAge: 100, + }) const racingCache = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'a', { - cachePolicy: 'max-age', - maxAge: 100, - }) + cache.cacheable(() => mockedApiRequest(v, 50), 'a') // Create a cache that times out at 100 and resolves at 50 const a = await racingCache('a') @@ -152,13 +152,12 @@ describe('Cache operations', () => { const cache = new Cacheables({ log: true, + policy: 'max-age', + maxAge: 100, }) const hitCache = async () => { - await cache.cacheable(() => mockedApiRequest(0, 10), 'a', { - cachePolicy: 'max-age', - maxAge: 100, - }) + await cache.cacheable(() => mockedApiRequest(0, 10), 'a') } // This should be a miss and take ~10ms From 4d7316a8b5db4b601ba163487cd132fade4562f3 Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 11:55:34 +0200 Subject: [PATCH 02/23] Rename cacheable() to remember() Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 28 ++++++------- package.json | 4 +- src/index.ts | 11 +++--- tests/fetchPolicies.test.ts | 78 ++++++++++++++++++------------------- tests/index.test.ts | 22 +++++------ 5 files changed, 72 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index c6e3e60..4990139 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A simple in-memory cache with support of different cache policies and elegant sy fetch('https://some-url.com/api') // with caching -cache.cacheable(() => fetch('https://some-url.com/api'), 'key') +cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` * [Installation](#installation) @@ -30,7 +30,7 @@ cache.cacheable(() => fetch('https://some-url.com/api'), 'key') * [Usage](#usage) * [API](#api) * [new Cacheables(options?): Cacheables](#new-cacheablesoptions-cacheables) - * [cache.cacheable(resource, key): Promise<T>](#cachecacheableresource-key-promiset) + * [cache.remember(resource, key): Promise<T>](#cacherememberresource-key-promiset) * [cache.delete(key: string): void](#cachedeletekey-string-void) * [cache.clear(): void](#cacheclear-void) * [cache.keys(): string[]](#cachekeys-string) @@ -79,7 +79,7 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) // The method returns a fully typed Promise just like `fetch(apiUrl)` // would but with the benefit of caching the result. const getWeatherData = () => - cache.cacheable(() => fetch(apiUrl), 'weather') + cache.remember(() => fetch(apiUrl), 'weather') const start = async () => { // Fetch some fresh weather data and store it in our cache. @@ -103,9 +103,9 @@ const start = async () => { start() ``` -`cacheable` serves both as the getter and setter. This method will return a cached resource if available or use the provided argument `resource` to fill the cache and return a value. +`remember` serves both as the getter and setter. This method will return a cached resource if available or use the provided argument `resource` to fill the cache and return a value. -> Be aware that there is no exclusive cache getter (like `cache.get('key)`). This is by design as the Promise provided by the first argument to `cacheable` is used to infer the return type of the cached resource. +> Be aware that there is no exclusive cache getter (like `cache.get('key)`). This is by design as the Promise provided by the first argument to `remember` is used to infer the return type of the cached resource. ## API @@ -145,9 +145,9 @@ const cache = new Cacheables({ }) ``` -### `cache.cacheable(resource, key): Promise` +### `cache.remember(resource, key): Promise` -- If a resource exists in the cache (determined by the presence of a value with key `key`) `cacheable` decides on returning a cache based on the instance's cache policy. +- If a resource exists in the cache (determined by the presence of a value with key `key`) `remember` decides on returning a cache based on the instance's cache policy. - If there's no resource in the cache, the provided `resource` will be called and used to store a cache value with key `key` and the value is returned. #### Arguments @@ -166,7 +166,7 @@ See [Cacheables.key()](#cacheableskeyargs-string--number-string) for a safe and ```ts const cache = new Cacheables({ policy: 'max-age', maxAge: 10000 }) -const cachedApiResponse = await cache.cacheable( +const cachedApiResponse = await cache.remember( () => fetch('https://github.com/'), 'key', ) @@ -241,7 +241,7 @@ If there is no cache yet, all calls will be resolved by the first network reques ##### Example ```ts const cache = new Cacheables({ policy: 'cache-only' }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ### Network Only @@ -252,7 +252,7 @@ Simultaneous requests trigger simultaneous network requests. ##### Example ```ts const cache = new Cacheables({ policy: 'network-only' }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ### Network Only – Non Concurrent @@ -263,7 +263,7 @@ All requests should be handled by the network but no concurrent network requests ##### Example ```ts const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ### Max Age @@ -275,7 +275,7 @@ All requests should be checked against max-age. If max-age is expired, a network ```ts // Trigger a network request if the cached value is older than 1 second. const cache = new Cacheables({ policy: 'max-age', maxAge: 1000 }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ### Stale While Revalidate @@ -286,14 +286,14 @@ The cache policy `stale-while-revalidate` will return a cached value immediately ```ts // If there is a cache, return it but 'silently' update the cache. const cache = new Cacheables({ policy: 'stale-while-revalidate' }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ##### Example with `maxAge` ```ts // If there is a cache, return it and 'silently' update the cache if it's older than 1 second. const cache = new Cacheables({ policy: 'stale-while-revalidate', maxAge: 1000 }) -cache.cacheable(() => fetch(url), 'a') +cache.remember(() => fetch(url), 'a') ``` ## In Progress diff --git a/package.json b/package.json index cb74795..b3d4629 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cacheables", - "version": "2.0.0", + "version": "3.0.0", "description": "A simple in-memory cache written in Typescript with automatic cache invalidation and an elegant syntax.", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", @@ -36,7 +36,7 @@ "cache", "in-memory", "typescript", - "cacheable", + "remember", "cacheables" ], "author": "Grischa Erbe ", diff --git a/src/index.ts b/src/index.ts index 45ff8cb..7dce9f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,7 +76,8 @@ export class Cacheables { this.logTiming = options?.logTiming ?? false this.#policy = options?.policy ?? 'cache-only' this.#maxAge = - options?.policy === 'max-age' || options?.policy === 'stale-while-revalidate' + options?.policy === 'max-age' || + options?.policy === 'stale-while-revalidate' ? options.maxAge : undefined } @@ -126,7 +127,7 @@ export class Cacheables { * @param resource A function returning a Promise * @param key A key to identify the cache * @example - * const apiResponse = await cache.cacheable( + * const apiResponse = await cache.remember( * () => api.query({ * query: someQuery, * variables: someVariables, @@ -136,7 +137,7 @@ export class Cacheables { * @returns promise Resolves to the value of the provided resource, either from * cache or from the remote resource itself. */ - async cacheable(resource: () => Promise, key: string): Promise { + async remember(resource: () => Promise, key: string): Promise { const shouldCache = this.enabled if (!shouldCache) { if (this.log) Logger.logDisabled() @@ -148,7 +149,7 @@ export class Cacheables { const logId = Logger.getLogId(key) if (logTiming) Logger.logTime(logId) - const result = await this.#cacheable(resource, key) + const result = await this.#remember(resource, key) if (logTiming) Logger.logTimeEnd(logId) if (log) Logger.logStats(key, this.#cacheables[key]) @@ -156,7 +157,7 @@ export class Cacheables { return result } - #cacheable(resource: () => Promise, key: string): Promise { + #remember(resource: () => Promise, key: string): Promise { let cacheable = this.#cacheables[key] as Cacheable | undefined if (!cacheable) { diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index df480a6..15eaf75 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -17,12 +17,12 @@ describe('Fetch Policies', () => { it('cache-only', async () => { const cache = new Cacheables({ policy: 'cache-only' }) - const COCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v), 'key') + const co = (v: any) => + cache.remember(() => mockedApiRequest(v), 'key') - const a = await COCacheable(0) - const b = await COCacheable(1) - const c = await COCacheable(2) + const a = await co(0) + const b = await co(1) + const c = await co(2) expect(a).toEqual(0) expect(b).toEqual(0) @@ -32,19 +32,19 @@ describe('Fetch Policies', () => { it('network-only-non-concurrent', async () => { const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) - const NONCCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key') + const nonc = (v: any) => + cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache - await NONCCacheable(-1) + await nonc(-1) - const a = NONCCacheable(0) - const b = NONCCacheable(1) - const c = NONCCacheable(2) + const a = nonc(0) + const b = nonc(1) + const c = nonc(2) await wait(100) - const d = NONCCacheable(3) + const d = nonc(3) const values = await Promise.all([a, b, c, d]) @@ -53,15 +53,15 @@ describe('Fetch Policies', () => { it('network-only', async () => { const cache = new Cacheables({ policy: 'network-only' }) - const NOCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key') + const no = (v: any) => + cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache - await NOCacheable(-1) + await no(-1) - const a = NOCacheable(0) - const b = NOCacheable(1) - const c = NOCacheable(2) + const a = no(0) + const b = no(1) + const c = no(2) const values = await Promise.all([a, b, c]) @@ -70,39 +70,39 @@ describe('Fetch Policies', () => { it('max-age', async () => { const cache = new Cacheables({ policy: 'max-age', maxAge: 100 }) - const MACacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key') + const ma = (v: any) => + cache.remember(() => mockedApiRequest(v, 50), 'key') - const a = await MACacheable(0) - const b = await MACacheable(1) + const a = await ma(0) + const b = await ma(1) await wait(100) - const c = await MACacheable(2) - const d = await MACacheable(3) + const c = await ma(2) + const d = await ma(3) expect([a, b, c, d]).toEqual([0, 0, 2, 2]) }) it('stale-while-revalidate', async () => { const cache = new Cacheables({ policy: 'stale-while-revalidate' }) - const SWRCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key') + const swr = (v: any) => + cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache - await SWRCacheable(-1) + await swr(-1) await wait(100) - const a = await SWRCacheable(0) - const b = await SWRCacheable(1) - const c = await SWRCacheable(2) + const a = await swr(0) + const b = await swr(1) + const c = await swr(2) await wait(100) - const d = await SWRCacheable(3) - const e = await SWRCacheable(4) - const f = await SWRCacheable(5) + const d = await swr(3) + const e = await swr(4) + const f = await swr(5) expect([a, b, c, d, e, f]).toEqual([-1, -1, -1, 0, 0, 0]) }) @@ -113,26 +113,26 @@ describe('Fetch Policies', () => { maxAge: 200, }) - const SWRCacheable = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'key') + const swr = (v: any) => + cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache, takes ~50ms - await SWRCacheable(0) + await swr(0) await wait(100) // ~150ms on the clock, maxAge not reached - const a = await SWRCacheable(1) + const a = await swr(1) await wait(100) // ~250ms on the clock, maxAge reached, cache updates silently - const b = await SWRCacheable(2) + const b = await swr(2) await wait(100) // ~350ms on the clock, cache should be updated silently with value `2` - const c = await SWRCacheable(3) + const c = await swr(3) expect([a, b, c]).toEqual([0, 0, 2]) }) diff --git a/tests/index.test.ts b/tests/index.test.ts index 06e905a..3c4ec3f 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -24,7 +24,7 @@ describe('Cache operations', () => { it('Returns correct values', async () => { const cache = new Cacheables() const value = 10 - const cachedValue = await cache.cacheable( + const cachedValue = await cache.remember( () => mockedApiRequest(value), 'a', ) @@ -39,11 +39,11 @@ describe('Cache operations', () => { const valueA = 10 const valueB = 20 - const cachedValueA = await cache.cacheable( + const cachedValueA = await cache.remember( () => mockedApiRequest(valueA), 'a', ) - const cachedValueB = await cache.cacheable( + const cachedValueB = await cache.remember( () => mockedApiRequest(valueB), 'b', ) @@ -56,7 +56,7 @@ describe('Cache operations', () => { const cache = new Cacheables() const value = 10 - await cache.cacheable(() => mockedApiRequest(value), 'a') + await cache.remember(() => mockedApiRequest(value), 'a') expect(cache.isCached('a')).toEqual(true) cache.delete('a') @@ -67,7 +67,7 @@ describe('Cache operations', () => { const cache = new Cacheables() const value = 10 - await cache.cacheable(() => mockedApiRequest(value), 'a') + await cache.remember(() => mockedApiRequest(value), 'a') expect(cache.isCached('a')).toEqual(true) cache.clear() @@ -85,7 +85,7 @@ describe('Cache operations', () => { }) const value = 10 - const uncachedValue = await cache.cacheable( + const uncachedValue = await cache.remember( () => mockedApiRequest(value), 'a', ) @@ -101,7 +101,7 @@ describe('Cache operations', () => { enabled: false, }) - const cachedRequest = () => cache.cacheable(() => mockedApiRequest(1), 'a') + const cachedRequest = () => cache.remember(() => mockedApiRequest(1), 'a') await cachedRequest() expect(console.log).lastCalledWith('CACHE: Caching disabled') @@ -128,7 +128,7 @@ describe('Cache operations', () => { }) const racingCache = (v: any) => - cache.cacheable(() => mockedApiRequest(v, 50), 'a') + cache.remember(() => mockedApiRequest(v, 50), 'a') // Create a cache that times out at 100 and resolves at 50 const a = await racingCache('a') @@ -157,7 +157,7 @@ describe('Cache operations', () => { }) const hitCache = async () => { - await cache.cacheable(() => mockedApiRequest(0, 10), 'a') + await cache.remember(() => mockedApiRequest(0, 10), 'a') } // This should be a miss and take ~10ms @@ -184,7 +184,7 @@ describe('Cache operations', () => { it("Doesn't interfere with error handling", async () => { const cache = new Cacheables() const rejecting = () => { - return cache.cacheable(() => mockedApiRequest(0, 10, true), 'a') + return cache.remember(() => mockedApiRequest(0, 10, true), 'a') } await expect(rejecting).rejects.toEqual(errorMessage) }) @@ -193,7 +193,7 @@ describe('Cache operations', () => { const cache = new Cacheables() let errNo = 1 const rejecting = () => { - return cache.cacheable(() => Promise.reject(errNo++), 'a') + return cache.remember(() => Promise.reject(errNo++), 'a') } await expect(rejecting()).rejects.toEqual(1) await expect(rejecting()).rejects.toEqual(2) From bb4bd4cf0d5868d5d482dab5d7351f99946a2f35 Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 14:46:26 +0200 Subject: [PATCH 03/23] Introduce multilayer storage adapter pattern (v4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single in-memory store with a composable IStorageAdapter contract: reads cascade L1 → Ln, hits back-fill missing layers preserving storedAt, and writes synthesize meta on L1 then propagate to deeper layers. Adapters are required at construction; clear/delete/isCached become async; keys() is removed; an optional namespace prefix isolates instances that share an adapter; Cacheables gains a TMeta generic for typed sidecar metadata. Ships a built-in MemoryAdapter and refines read() to return { value } | undefined so adapters can store undefined values without colliding with the absence signal. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 337 ++++++++----------- package-lock.json | 4 +- src/Cacheables.ts | 228 +++++++++++++ src/Logger.ts | 25 ++ src/adapters/MemoryAdapter.ts | 29 ++ src/index.ts | 326 +----------------- src/types.ts | 110 ++++++ tests/adapters/memoryAdapter.test.ts | 58 ++++ tests/cascade.test.ts | 477 +++++++++++++++++++++++++++ tests/fetchPolicies.test.ts | 46 ++- tests/index.test.ts | 52 +-- tests/namespace.test.ts | 60 ++++ tests/tsconfig.json | 3 +- 13 files changed, 1201 insertions(+), 554 deletions(-) create mode 100644 src/Cacheables.ts create mode 100644 src/Logger.ts create mode 100644 src/adapters/MemoryAdapter.ts create mode 100644 src/types.ts create mode 100644 tests/adapters/memoryAdapter.test.ts create mode 100644 tests/cascade.test.ts create mode 100644 tests/namespace.test.ts diff --git a/README.md b/README.md index 4990139..baa792a 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,22 @@ ![Language](https://img.shields.io/github/languages/top/grischaerbe/cacheables) ![Build](https://img.shields.io/github/workflow/status/grischaerbe/cacheables/Node.js%20Package) -A simple in-memory cache with support of different cache policies and elegant syntax written in Typescript. +A small, typed cache with composable storage adapters and a handful of cache policies, written in TypeScript. -- Elegant syntax: **Wrap existing API calls** to save some of those precious API calls. -- **Fully typed results**. No type casting required. +- Elegant syntax: **wrap existing async calls** with `cache.remember(...)`. +- **Multilayer storage**: compose a fast in-memory L1 with any L2 you write (filesystem, Redis, S3, …). Reads cascade L1 → Ln; on any hit the engine fills missing layers. +- **Fully typed results**, including a generic `TMeta` parameter for sidecar metadata. - Supports different **cache policies**. -- Written in **Typescript**. -- **Integrated Logs**: Check on the timing of your API calls. -- Helper function to build cache keys. +- Helper to build cache keys. +- Optional **namespace** prefix so multiple instances can share an adapter without collisions. - Works in the browser and Node.js. - **No dependencies**. -- Extensively tested. -- **Small**: 1.43 kB minified and gzipped. ```ts -// without caching -fetch('https://some-url.com/api') +import { Cacheables, MemoryAdapter } from 'cacheables' + +const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) -// with caching cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` @@ -29,20 +27,22 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') * [Quickstart](#quickstart) * [Usage](#usage) * [API](#api) - * [new Cacheables(options?): Cacheables](#new-cacheablesoptions-cacheables) - * [cache.remember(resource, key): Promise<T>](#cacherememberresource-key-promiset) - * [cache.delete(key: string): void](#cachedeletekey-string-void) - * [cache.clear(): void](#cacheclear-void) - * [cache.keys(): string[]](#cachekeys-string) - * [cache.isCached(key: string): boolean](#cacheiscachedkey-string-boolean) - * [Cacheables.key(...args: (string | number)[]): string](#cacheableskeyargs-string--number-string) + * [new Cacheables(options)](#new-cacheablesoptions-cacheablestmeta) + * [cache.remember(resource, key)](#cacherememberresource-key-promiset) + * [cache.delete(key)](#cachedeletekey-promisevoid) + * [cache.clear()](#cacheclear-promisevoid) + * [cache.isCached(key)](#cacheiscachedkey-promiseboolean) + * [cache.meta(key)](#cachemetakey-promisetmeta--undefined) + * [Cacheables.key(...args)](#cacheableskeyargs-string) +* [Storage adapters](#storage-adapters) + * [`IStorageAdapter` contract](#istorageadapter-contract) + * [Built-in `MemoryAdapter`](#built-in-memoryadapter) + * [Writing your own adapter](#writing-your-own-adapter) + * [Cascade behavior](#cascade-behavior) + * [Typed metadata (`TMeta`)](#typed-metadata-tmeta) +* [Namespacing](#namespacing) * [Cache Policies](#cache-policies) - * [Cache Only](#cache-only) - * [Network Only](#network-only) - * [Network Only – Non Concurrent](#network-only--non-concurrent) - * [Max Age](#max-age) - * [Stale While Revalidate](#stale-while-revalidate) -* [In Progress](#in-progress) +* [Migrating from v3 → v4](#migrating-from-v3--v4) * [License](#license) ## Installation @@ -51,259 +51,206 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') npm install cacheables ``` -## Quickstart - -[https://codesandbox.io/s/quickstart-cacheables-5zh6h?file=/src/index.ts](https://codesandbox.io/s/quickstart-cacheables-5zh6h?file=/src/index.ts) - ## Usage ```ts -// Import Cacheables -import { Cacheables } from "cacheables" +import { Cacheables, MemoryAdapter } from 'cacheables' -const apiUrl = "https://goweather.herokuapp.com/weather/Karlsruhe" +const apiUrl = 'https://goweather.herokuapp.com/weather/Karlsruhe' -// Create a new cache instance const cache = new Cacheables({ - logTiming: true, - log: true, + adapters: [new MemoryAdapter()], policy: 'max-age', - maxAge: 5000, + maxAge: 5_000, }) -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - -// Wrap the existing API call `fetch(apiUrl)` and assign a cache -// key `weather` to it. This example uses the cache policy 'max-age' -// which invalidates the cache after a certain time. -// The method returns a fully typed Promise just like `fetch(apiUrl)` -// would but with the benefit of caching the result. -const getWeatherData = () => - cache.remember(() => fetch(apiUrl), 'weather') - -const start = async () => { - // Fetch some fresh weather data and store it in our cache. - const weatherData = await getWeatherData() - - /** 3 seconds later **/ - await wait(3000) - - // The cached weather data is returned as the - // maxAge of 5 seconds did not yet expire. - const cachedWeatherData = await getWeatherData() +const getWeather = () => cache.remember(() => fetch(apiUrl), 'weather') - /** Another 3 seconds later **/ - await wait(3000) - - // Now that the maxAge is expired, the resource - // will be fetched and stored in our cache. - const freshWeatherData = await getWeatherData() -} - -start() +await getWeather() // miss — fetched +await getWeather() // hit — cached ``` -`remember` serves both as the getter and setter. This method will return a cached resource if available or use the provided argument `resource` to fill the cache and return a value. - -> Be aware that there is no exclusive cache getter (like `cache.get('key)`). This is by design as the Promise provided by the first argument to `remember` is used to infer the return type of the cached resource. +`remember` is both getter and setter. The first time a key is requested it calls the resource and stores the result in every configured adapter; subsequent reads cascade through the adapters until one returns a hit. ## API -### `new Cacheables(options?): Cacheables` - -- Creates a new `Cacheables` instance. - -#### Arguments - -##### - `options?: CacheOptions` +### `new Cacheables(options): Cacheables` ```ts -type CacheOptions = { - enabled?: boolean // Enable/disable the cache, can be set anytime, default: true. - log?: boolean // Log hits to the cache, default: false. - logTiming?: boolean // Log the timing, default: false. +type CacheablesOptions = { + adapters: IStorageAdapter[] // REQUIRED, L1 first + namespace?: string // adapter keys become `${namespace}:${key}` + enabled?: boolean // default: true + log?: boolean // default: false + logTiming?: boolean // default: false } & ( | { policy?: 'cache-only' } // default | { policy: 'network-only' } | { policy: 'network-only-non-concurrent' } - | { policy: 'max-age', maxAge: number } // maxAge required - | { policy: 'stale-while-revalidate', maxAge?: number } // maxAge optional + | { policy: 'max-age', maxAge: number } + | { policy: 'stale-while-revalidate', maxAge?: number } ) ``` -The cache policy (and `maxAge` where applicable) is set on the instance and applies to every entry in that cache. To use multiple policies, create multiple `Cacheables` instances. - -#### Example: - -```ts -import { Cacheables } from 'cacheables' - -const cache = new Cacheables({ - logTiming: true, - policy: 'max-age', - maxAge: 5000, -}) -``` +`adapters` must be a non-empty array; the constructor throws `Error('At least one storage adapter is required')` otherwise. The first adapter is L1 (fastest, queried first); the rest form deeper layers. ### `cache.remember(resource, key): Promise` -- If a resource exists in the cache (determined by the presence of a value with key `key`) `remember` decides on returning a cache based on the instance's cache policy. -- If there's no resource in the cache, the provided `resource` will be called and used to store a cache value with key `key` and the value is returned. - -#### Arguments +Resolves to the cached value if present (subject to policy), otherwise calls `resource()` and stores the result in every adapter. -##### - `resource: () => Promise` +### `cache.delete(key): Promise` -A function that returns a `Promise`. +Deletes the entry from every adapter. -##### - `key: string` +### `cache.clear(): Promise` -A key to store the cache at. -See [Cacheables.key()](#cacheableskeyargs-string--number-string) for a safe and easy way to generate unique keys. +Clears every adapter and the in-flight registry. -#### Example - -```ts -const cache = new Cacheables({ policy: 'max-age', maxAge: 10000 }) - -const cachedApiResponse = await cache.remember( - () => fetch('https://github.com/'), - 'key', -) -``` +### `cache.isCached(key): Promise` -### `cache.delete(key: string): void` +`true` if any layer reports the key (existence-only — does not consider freshness). -#### Arguments +### `cache.meta(key): Promise` -##### - `key: string` +Returns the meta from the highest-priority layer that has the key — useful for inspecting sidecar fields like `etag`, `ttl`, etc. -Delete a cache for a certain key. +### `Cacheables.key(...args): string` -#### Example +Joins the parts with `:`. Identical to v3. ```ts -cache.delete('key') +Cacheables.key('user', 42) // 'user:42' ``` -### `cache.clear(): void` +## Storage adapters -Delete all cached resources. +### `IStorageAdapter` contract -### `cache.keys(): string[]` - -Returns all the cache keys +```ts +interface IBaseMeta { + storedAt: number +} -### `cache.isCached(key: string): boolean` +interface IStorageAdapter { + read(key: string): Promise<{ value: T } | undefined> + write(key: string, value: T, meta?: TMeta): Promise + meta(key: string): Promise + delete(key: string): Promise + clear(): Promise +} +``` -#### Arguments +Rules: -##### - `key: string` +- `meta` MUST be cheap. The engine probes it on every layer for every read. +- `read` returns `undefined` when the entry is absent and `{ value }` when it's present — the wrapper exists so adapters can store entries whose value is itself `undefined` without colliding with the absence signal. +- When `write` receives a `meta`, the adapter MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This guarantees `max-age` semantics stay coherent across layers. +- When `write` is called with no `meta`, the adapter MUST synthesize one with `storedAt: Date.now()`. +- `clear` MUST remove every entry the adapter manages. +- Any throw from any adapter rejects the surrounding `remember()` call. There is no per-adapter error suppression. -Returns whether a cacheable is present for a certain key. +### Built-in `MemoryAdapter` -#### Example +Ships with the package; covers the common in-memory use case. ```ts -const aIsCached = cache.isCached('a') -``` +import { Cacheables, MemoryAdapter } from 'cacheables' -### `Cacheables.key(...args: (string | number)[]): string` +const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) +``` -A static helper function to easily build safe and consistent cache keys. +### Writing your own adapter -#### Example +Implement `IStorageAdapter`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 layers are easy to add without ceremony. A typical L2 keeps a sidecar (file, table, key/value entry) so `meta()` is cheap. ```ts -const id = '5d3c5be6-2da4-11ec-8d3d-0242ac130003' -console.log(Cacheables.key('user', id)) -// 'user:5d3c5be6-2da4-11ec-8d3d-0242ac130003' +import type { IStorageAdapter, IBaseMeta } from 'cacheables' + +class FileSystemAdapter implements IStorageAdapter { + async read(key: string): Promise<{ value: T } | undefined> { /* … */ } + async write(key: string, value: T, meta?: IBaseMeta): Promise { /* … */ } + async meta(key: string): Promise { /* … */ } + async delete(key: string): Promise { /* … */ } + async clear(): Promise { /* … */ } +} ``` -## Cache Policies - -*Cacheables* comes with multiple cache policies. -Each policy has different behaviour when it comes to preheating the cache (i.e. the first time it is requested) and balancing network requests. - -| Cache Policy | Behaviour | -|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `cache-only` (default) | All requests should return a value from the cache. | -| `network-only` | All requests should be handled by the network.
Simultaneous requests trigger simultaneous network requests. | -| `network-only-non-concurrent` | All requests should be handled by the network but no concurrent network requests are allowed.
All requests made in the timeframe of a network request are resolved once that is finished. | -| `max-age` | All requests should be checked against max-age.
If max-age is expired, a network request is triggered.
All requests made in the timeframe of a network request are resolved once that is finished. | -| `stale-while-revalidate` | All requests immediately return a cached value.
If no network request is running and maxAge is provided and reached or maxAge is not provided, a network request is triggered, 'silently' updating the cache in the background.
After the network request finished, subsequent requests will receive the updated cached value. | +### Cascade behavior -### Cache Only (default) - -The default and simplest cache policy. If there is a cache, return it. -If there is no cache yet, all calls will be resolved by the first network request (i.e. non-concurrent). - -##### Example ```ts -const cache = new Cacheables({ policy: 'cache-only' }) -cache.remember(() => fetch(url), 'a') +const cache = new Cacheables({ + adapters: [new MemoryAdapter(), new FileSystemAdapter()], + policy: 'max-age', + maxAge: 60_000, +}) ``` -### Network Only +- **Read**: probe `meta()` on every layer in parallel; the first layer that satisfies the freshness predicate is the hit. Read its value, and back-fill every other layer that is still missing the key — using the hit layer's meta so `storedAt` is preserved everywhere. +- **Miss + resource()**: write to L1 with no meta (L1 synthesizes its own), read meta back from L1, then write to L2..Ln with that exact meta. All layers converge to the same `storedAt`. +- **Stale L1 + fresh L2 (under `max-age`)**: the freshness predicate filters per-layer, so the engine returns the fresh L2 value and back-fills L1. -The opposite of `cache-only`. -Simultaneous requests trigger simultaneous network requests. +### Typed metadata (`TMeta`) + +`Cacheables` is generic in `TMeta`. Extend it to carry sidecar fields: -##### Example ```ts -const cache = new Cacheables({ policy: 'network-only' }) -cache.remember(() => fetch(url), 'a') -``` +import { Cacheables, type IBaseMeta, type IStorageAdapter } from 'cacheables' -### Network Only – Non Concurrent +interface ETagMeta extends IBaseMeta { + etag: string +} -A version of `network-only` but only one network request is running at any point in time. -All requests should be handled by the network but no concurrent network requests are allowed. All requests made in the timeframe of a network request are resolved once that is finished. +class ETagAdapter implements IStorageAdapter { + // read / write / meta / delete / clear … +} -##### Example -```ts -const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) -cache.remember(() => fetch(url), 'a') +const cache = new Cacheables({ + adapters: [new ETagAdapter()], +}) + +const meta = await cache.meta('user:42') // typed as ETagMeta | undefined ``` -### Max Age +Every adapter passed to the constructor must satisfy `IStorageAdapter`, and the TypeScript compiler enforces it. The built-in `MemoryAdapter` only implements `IStorageAdapter`, so it can't be used in a `Cacheables` instance with a custom `TMeta` — write a custom adapter (or wrap `MemoryAdapter`) when you need extended metadata. + +## Namespacing -The cache policy `max-age` defines after what time a cached value is treated as invalid. -All requests should be checked against max-age. If max-age is expired, a network request is triggered. All requests made in the timeframe of a network request are resolved once that is finished. +Set `namespace` and every adapter call sees keys prefixed with `${namespace}:`: -##### Example ```ts -// Trigger a network request if the cached value is older than 1 second. -const cache = new Cacheables({ policy: 'max-age', maxAge: 1000 }) -cache.remember(() => fetch(url), 'a') +const adapter = new MemoryAdapter() +const tenantA = new Cacheables({ adapters: [adapter], namespace: 'tenant-a' }) +const tenantB = new Cacheables({ adapters: [adapter], namespace: 'tenant-b' }) ``` -### Stale While Revalidate +Two instances can share an adapter without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying adapter (it has no notion of which keys belong to which namespace), so reach for it only when you really mean *everything*. -The cache policy `stale-while-revalidate` will return a cached value immediately and – if there is no network request already running and `maxAge` is either provided and reached or not provided – trigger a network request to 'silently' update the cache in the background. +## Cache Policies -##### Example without `maxAge` -```ts -// If there is a cache, return it but 'silently' update the cache. -const cache = new Cacheables({ policy: 'stale-while-revalidate' }) -cache.remember(() => fetch(url), 'a') -``` +| Policy | Behaviour | +|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cache-only` *(default)* | Return any cached value; on miss, call `resource()`. Concurrent miss callers share one fetch. | +| `network-only` | Always call `resource()`; concurrent calls each get their own. | +| `network-only-non-concurrent` | Always call `resource()`, but concurrent calls share one in-flight request. | +| `max-age` *(maxAge required)* | Return cache if `Date.now() - storedAt <= maxAge`, otherwise fetch. | +| `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | -##### Example with `maxAge` ```ts -// If there is a cache, return it and 'silently' update the cache if it's older than 1 second. -const cache = new Cacheables({ policy: 'stale-while-revalidate', maxAge: 1000 }) -cache.remember(() => fetch(url), 'a') +new Cacheables({ adapters: [new MemoryAdapter()], policy: 'max-age', maxAge: 1_000 }) ``` -## In Progress +## Migrating from v3 → v4 -PRs welcome +Breaking changes: -- [ ] ~~Cache invalidation callback~~ -- [ ] Adapters to store cache not only in memory -- [X] Cache policies -- [X] Tests +- `adapters` is now a **required** constructor option. `new Cacheables()` no longer compiles. Pass at least one adapter, e.g. `new Cacheables({ adapters: [new MemoryAdapter()] })`. +- `delete(key)` returns `Promise` (was `void`). Add `await`. +- `clear()` returns `Promise` (was `void`). Add `await`. +- `isCached(key)` returns `Promise` (was `boolean`). Add `await`. +- `keys()` is **removed**. Enumerating heterogeneous async layers (some non-enumerable, like CDNs) doesn't have a single sensible semantic. +- `Cacheables` is now generic in `TMeta`. Plain `new Cacheables({ adapters })` defaults to `Cacheables` and is source-compatible at the type level. +- New constructor options: `adapters` (required) and `namespace` (optional). +- Any throw from any adapter rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom adapters. ## License diff --git a/package-lock.json b/package-lock.json index f998508..31b9d83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cacheables", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cacheables", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "devDependencies": { "@size-limit/preset-small-lib": "^11.1.4", diff --git a/src/Cacheables.ts b/src/Cacheables.ts new file mode 100644 index 0000000..7995097 --- /dev/null +++ b/src/Cacheables.ts @@ -0,0 +1,228 @@ +import { Logger } from './Logger' +import type { + CacheablesOptions, + IBaseMeta, + IStorageAdapter, + Policy, +} from './types' + +export class Cacheables { + enabled: boolean + log: boolean + logTiming: boolean + + #policy: Policy + #maxAge: number | undefined + #adapters: IStorageAdapter[] + #namespace: string | undefined + #inflight = new Map>() + #hits = new Map() + + constructor(options: CacheablesOptions) { + if (!options.adapters || options.adapters.length === 0) { + throw new Error('At least one storage adapter is required') + } + this.#adapters = options.adapters + this.#namespace = options.namespace + this.enabled = options.enabled ?? true + this.log = options.log ?? false + this.logTiming = options.logTiming ?? false + this.#policy = options.policy ?? 'cache-only' + this.#maxAge = + options.policy === 'max-age' || + options.policy === 'stale-while-revalidate' + ? options.maxAge + : undefined + } + + static key(...args: (string | number)[]): string { + return args.join(':') + } + + #fullKey(key: string): string { + return this.#namespace === undefined ? key : `${this.#namespace}:${key}` + } + + async delete(key: string): Promise { + const fullKey = this.#fullKey(key) + this.#hits.delete(fullKey) + await Promise.all(this.#adapters.map((a) => a.delete(fullKey))) + } + + async clear(): Promise { + this.#inflight.clear() + this.#hits.clear() + await Promise.all(this.#adapters.map((a) => a.clear())) + } + + async isCached(key: string): Promise { + const fullKey = this.#fullKey(key) + const probes = await this.#cascadeProbe(fullKey) + return probes.some((m) => m !== undefined) + } + + async meta(key: string): Promise { + const fullKey = this.#fullKey(key) + const probes = await this.#cascadeProbe(fullKey) + return probes.find((m) => m !== undefined) + } + + async remember(resource: () => Promise, key: string): Promise { + if (!this.enabled) { + if (this.log) Logger.logDisabled() + return resource() + } + + const { logTiming, log } = this + const logId = Logger.getLogId(key) + if (logTiming) Logger.logTime(logId) + + const fullKey = this.#fullKey(key) + const { value, hit } = await this.#runPolicy(resource, fullKey) + + if (hit) { + const next = (this.#hits.get(fullKey) ?? 0) + 1 + this.#hits.set(fullKey, next) + } else if (!this.#hits.has(fullKey)) { + this.#hits.set(fullKey, 0) + } + + if (logTiming) Logger.logTimeEnd(logId) + if (log) Logger.logStats(key, this.#hits.get(fullKey) ?? 0) + + return value + } + + async #runPolicy( + resource: () => Promise, + fullKey: string, + ): Promise<{ value: T; hit: boolean }> { + switch (this.#policy) { + case 'cache-only': { + return this.#dedup(fullKey, async () => { + const cached = await this.#cascadeRead(fullKey) + if (cached) return { value: cached.value, hit: true } + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + }) + } + case 'network-only': { + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + } + case 'network-only-non-concurrent': { + return this.#dedup(fullKey, async () => { + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + }) + } + case 'max-age': { + const maxAge = this.#maxAge as number + return this.#dedup(fullKey, async () => { + const cached = await this.#cascadeRead( + fullKey, + (m) => Date.now() - m.storedAt <= maxAge, + ) + if (cached) return { value: cached.value, hit: true } + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + }) + } + case 'stale-while-revalidate': { + const cached = await this.#cascadeRead(fullKey) + const maxAge = this.#maxAge + const isStale = + !cached || + maxAge === undefined || + Date.now() - cached.meta.storedAt > maxAge + + if (cached && !isStale) { + return { value: cached.value, hit: true } + } + + if (cached && isStale) { + this.#dedup(fullKey, async () => { + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + }).catch(() => { + /* swallow background revalidation errors */ + }) + return { value: cached.value, hit: true } + } + + return this.#dedup(fullKey, async () => { + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return { value, hit: false } + }) + } + } + } + + #dedup(fullKey: string, run: () => Promise): Promise { + const existing = this.#inflight.get(fullKey) as Promise | undefined + if (existing) return existing + const p = run().finally(() => { + if (this.#inflight.get(fullKey) === p) this.#inflight.delete(fullKey) + }) + this.#inflight.set(fullKey, p) + return p + } + + async #cascadeProbe(fullKey: string): Promise<(TMeta | undefined)[]> { + return Promise.all(this.#adapters.map((a) => a.meta(fullKey))) + } + + async #cascadeRead( + fullKey: string, + isFresh?: (meta: TMeta) => boolean, + ): Promise<{ value: T; meta: TMeta } | undefined> { + const probes = await this.#cascadeProbe(fullKey) + const hitIdx = probes.findIndex( + (m) => m !== undefined && (isFresh ? isFresh(m) : true), + ) + if (hitIdx === -1) return undefined + + const adapter = this.#adapters[hitIdx]! + const result = await adapter.read(fullKey) + if (result === undefined) return undefined + + const value = result.value + const hitMeta = probes[hitIdx] as TMeta + await this.#cascadeFill(fullKey, value, hitMeta, probes, hitIdx) + return { value, meta: hitMeta } + } + + async #cascadeFill( + fullKey: string, + value: T, + hitMeta: TMeta, + probes: (TMeta | undefined)[], + hitIdx: number, + ): Promise { + const writes: Promise[] = [] + for (let i = 0; i < this.#adapters.length; i++) { + if (i === hitIdx) continue + if (probes[i] !== undefined) continue + writes.push(this.#adapters[i]!.write(fullKey, value, hitMeta)) + } + if (writes.length > 0) await Promise.all(writes) + } + + async #cascadeWrite(fullKey: string, value: T): Promise { + const [l1, ...rest] = this.#adapters + if (l1 === undefined) return + await l1.write(fullKey, value) + if (rest.length === 0) return + const meta = await l1.meta(fullKey) + if (meta === undefined) { + throw new Error('L1 adapter did not persist meta after write') + } + await Promise.all(rest.map((a) => a.write(fullKey, value, meta))) + } +} diff --git a/src/Logger.ts b/src/Logger.ts new file mode 100644 index 0000000..e280dc6 --- /dev/null +++ b/src/Logger.ts @@ -0,0 +1,25 @@ +export class Logger { + static getLogId(key: string): string { + return key + '---' + Math.random().toString(36).substr(2, 9) + } + + static logTime(key: string): void { + // eslint-disable-next-line no-console + console.time(key) + } + + static logTimeEnd(key: string): void { + // eslint-disable-next-line no-console + console.timeEnd(key) + } + + static logDisabled(): void { + // eslint-disable-next-line no-console + console.log('CACHE: Caching disabled') + } + + static logStats(key: string, hits: number): void { + // eslint-disable-next-line no-console + console.log(`Cacheable "${key}": hits: ${hits}`) + } +} diff --git a/src/adapters/MemoryAdapter.ts b/src/adapters/MemoryAdapter.ts new file mode 100644 index 0000000..58f6b13 --- /dev/null +++ b/src/adapters/MemoryAdapter.ts @@ -0,0 +1,29 @@ +import type { IBaseMeta, IStorageAdapter } from '../types' + +export class MemoryAdapter implements IStorageAdapter { + #store = new Map() + + async read(key: string): Promise<{ value: T } | undefined> { + const entry = this.#store.get(key) + return entry === undefined ? undefined : { value: entry.value as T } + } + + async write(key: string, value: T, meta?: IBaseMeta): Promise { + this.#store.set(key, { + value, + meta: meta ?? ({ storedAt: Date.now() } as IBaseMeta), + }) + } + + async meta(key: string): Promise { + return this.#store.get(key)?.meta + } + + async delete(key: string): Promise { + this.#store.delete(key) + } + + async clear(): Promise { + this.#store.clear() + } +} diff --git a/src/index.ts b/src/index.ts index 7dce9f5..fe68eef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,318 +1,8 @@ -//region Types -type CacheOptionsBase = { - /** - * Enables caching - */ - enabled?: boolean - /** - * Enable/disable logging of cache hits - */ - log?: boolean - /** - * Enable/disable timings - */ - logTiming?: boolean -} - -type CacheOnlyPolicy = { - policy?: 'cache-only' -} - -type NetworkOnlyPolicy = { - policy: 'network-only' -} - -type NetworkOnlyNonConcurrentPolicy = { - policy: 'network-only-non-concurrent' -} - -type MaxAgePolicy = { - policy: 'max-age' - maxAge: number -} - -type SWRPolicy = { - policy: 'stale-while-revalidate' - maxAge?: number -} - -type PolicyOptions = - | CacheOnlyPolicy - | NetworkOnlyPolicy - | NetworkOnlyNonConcurrentPolicy - | MaxAgePolicy - | SWRPolicy - -/** - * Cacheables options. Combines instance settings with the cache policy - * (and policy-specific options like `maxAge`). - */ -export type CacheOptions = CacheOptionsBase & PolicyOptions - -type Policy = - | 'cache-only' - | 'network-only' - | 'network-only-non-concurrent' - | 'max-age' - | 'stale-while-revalidate' - -//endregion - -//region Cacheables -/** - * Provides a simple in-memory cache with automatic or manual invalidation. - */ -export class Cacheables { - enabled: boolean - log: boolean - logTiming: boolean - - #policy: Policy - #maxAge: number | undefined - - constructor(options?: CacheOptions) { - this.enabled = options?.enabled ?? true - this.log = options?.log ?? false - this.logTiming = options?.logTiming ?? false - this.#policy = options?.policy ?? 'cache-only' - this.#maxAge = - options?.policy === 'max-age' || - options?.policy === 'stale-while-revalidate' - ? options.maxAge - : undefined - } - - #cacheables: Record> = {} - - /** - * Builds a key with the provided strings or numbers. - * @param args - */ - static key(...args: (string | number)[]): string { - return args.join(':') - } - - /** - * Deletes a cacheable. - * @param key - */ - delete(key: string): void { - delete this.#cacheables[key] - } - - /** - * Clears the cache by deleting all cacheables. - */ - clear(): void { - this.#cacheables = {} - } - - /** - * Returns whether a cacheable is present and valid (i.e., did not time out). - */ - isCached(key: string): boolean { - return !!this.#cacheables[key] - } - - /** - * Returns all the cache keys - */ - keys(): string[] { - return Object.keys(this.#cacheables) - } - - /** - * A "cacheable" represents a resource, commonly fetched from a remote source - * that you want to cache for a certain period of time. - * @param resource A function returning a Promise - * @param key A key to identify the cache - * @example - * const apiResponse = await cache.remember( - * () => api.query({ - * query: someQuery, - * variables: someVariables, - * }), - * Cache.key('type', someCacheKey, someOtherCacheKey), - * ) - * @returns promise Resolves to the value of the provided resource, either from - * cache or from the remote resource itself. - */ - async remember(resource: () => Promise, key: string): Promise { - const shouldCache = this.enabled - if (!shouldCache) { - if (this.log) Logger.logDisabled() - return resource() - } - - // Persist log settings as this could be a race condition - const { logTiming, log } = this - const logId = Logger.getLogId(key) - if (logTiming) Logger.logTime(logId) - - const result = await this.#remember(resource, key) - - if (logTiming) Logger.logTimeEnd(logId) - if (log) Logger.logStats(key, this.#cacheables[key]) - - return result - } - - #remember(resource: () => Promise, key: string): Promise { - let cacheable = this.#cacheables[key] as Cacheable | undefined - - if (!cacheable) { - cacheable = new Cacheable() - this.#cacheables[key] = cacheable - } - - return cacheable.touch(resource, this.#policy, this.#maxAge) - } -} -//endregion - -//region Cacheable -/** - * Helper class, can only be instantiated by calling its static - * function `create`. - */ -class Cacheable { - hits = 0 - #lastFetch = 0 - #initialized = false - #promise: Promise | undefined - - get #isFetching() { - return !!this.#promise - } - - #value: T = undefined as unknown as T - - #logHit() { - this.hits += 1 - } - - async #fetch(resource: () => Promise): Promise { - this.#lastFetch = Date.now() - this.#promise = resource() - try { - this.#value = await this.#promise - if (!this.#initialized) this.#initialized = true - } finally { - this.#promise = undefined - } - return this.#value - } - - async #fetchNonConcurrent(resource: () => Promise): Promise { - if (this.#isFetching) { - await this.#promise - this.#logHit() - return this.#value - } - return this.#fetch(resource) - } - - #handlePreInit(resource: () => Promise, policy: Policy): Promise { - switch (policy) { - case 'network-only': - return this.#fetch(resource) - case 'cache-only': - case 'stale-while-revalidate': - case 'max-age': - case 'network-only-non-concurrent': - return this.#fetchNonConcurrent(resource) - } - } - - #handleCacheOnly(): T { - this.#logHit() - return this.#value - } - - #handleNetworkOnly(resource: () => Promise): Promise { - return this.#fetch(resource) - } - - #handleNetworkOnlyNonConcurrent(resource: () => Promise): Promise { - return this.#fetchNonConcurrent(resource) - } - - #handleMaxAge(resource: () => Promise, maxAge: number) { - if (Date.now() > this.#lastFetch + maxAge) { - return this.#fetchNonConcurrent(resource) - } - this.#logHit() - return this.#value - } - - #handleSwr(resource: () => Promise, maxAge?: number): T { - if ( - !this.#isFetching && - ((maxAge && Date.now() > this.#lastFetch + maxAge) || !maxAge) - ) { - this.#fetchNonConcurrent(resource) - } - this.#logHit() - return this.#value - } - - /** - * Get and set the value of the Cacheable. - * Some tricky race are conditions going on here, - * but this should behave as expected - */ - async touch( - resource: () => Promise, - policy: Policy, - maxAge: number | undefined, - ): Promise { - if (!this.#initialized) { - return this.#handlePreInit(resource, policy) - } - switch (policy) { - case 'cache-only': - return this.#handleCacheOnly() - case 'network-only': - return this.#handleNetworkOnly(resource) - case 'stale-while-revalidate': - return this.#handleSwr(resource, maxAge) - case 'max-age': - return this.#handleMaxAge(resource, maxAge as number) - case 'network-only-non-concurrent': - return this.#handleNetworkOnlyNonConcurrent(resource) - } - } -} -//endregion - -//region Logger -/** - * Logger class with static logging functions. - */ -class Logger { - static getLogId(key: string) { - return key + '---' + Math.random().toString(36).substr(2, 9) - } - - static logTime(key: string): void { - // eslint-disable-next-line no-console - console.time(key) - } - - static logTimeEnd(key: string): void { - // eslint-disable-next-line no-console - console.timeEnd(key) - } - - static logDisabled(): void { - // eslint-disable-next-line no-console - console.log('CACHE: Caching disabled') - } - - static logStats(key: string, cacheable: Cacheable | undefined): void { - if (!cacheable) return - const { hits } = cacheable - console.log(`Cacheable "${key}": hits: ${hits}`) - } -} -//endregion +export { Cacheables } from './Cacheables' +export { MemoryAdapter } from './adapters/MemoryAdapter' +export type { + CacheOptions, + CacheablesOptions, + IBaseMeta, + IStorageAdapter, +} from './types' diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..652bbad --- /dev/null +++ b/src/types.ts @@ -0,0 +1,110 @@ +/** + * Base shape for all adapter metadata. Adapters MAY extend this with + * additional sidecar fields (etag, ttl, version, etc.) provided their + * extension is captured by the `TMeta` generic of `Cacheables`. + */ +export interface IBaseMeta { + storedAt: number +} + +/** + * Storage adapter contract. Adapters back layers (L1, L2, ...) of a + * `Cacheables` instance. Reads cascade L1 → Ln; on any hit the engine + * fills missing layers with the hit value preserving `meta.storedAt`. + * + * Contracts: + * - `meta` MUST be cheap (engine probes it on every layer per read). + * - `read` returns `undefined` when the entry is absent and `{ value }` + * when present — the wrapper exists so adapters can store entries + * whose value is itself `undefined` without colliding with the + * absence signal. + * - When `write` receives a `meta`, the adapter MUST persist + * `meta.storedAt` verbatim. Other fields MAY be transformed. + * - When `write` receives no `meta`, the adapter MUST synthesize one + * with `storedAt: Date.now()` (and any other `TMeta` fields it knows + * how to populate). + * - `clear` MUST remove every entry the adapter manages. + * - All five methods MAY throw on infrastructure errors. The engine + * treats throws as fatal (strict mode). + */ +export interface IStorageAdapter { + read(key: string): Promise<{ value: T } | undefined> + write(key: string, value: T, meta?: TMeta): Promise + meta(key: string): Promise + delete(key: string): Promise + clear(): Promise +} + +type CacheOptionsBase = { + /** + * Enables caching. + */ + enabled?: boolean + /** + * Enable/disable logging of cache hits. + */ + log?: boolean + /** + * Enable/disable timings. + */ + logTiming?: boolean +} + +type CacheOnlyPolicy = { + policy?: 'cache-only' +} + +type NetworkOnlyPolicy = { + policy: 'network-only' +} + +type NetworkOnlyNonConcurrentPolicy = { + policy: 'network-only-non-concurrent' +} + +type MaxAgePolicy = { + policy: 'max-age' + maxAge: number +} + +type SWRPolicy = { + policy: 'stale-while-revalidate' + maxAge?: number +} + +export type PolicyOptions = + | CacheOnlyPolicy + | NetworkOnlyPolicy + | NetworkOnlyNonConcurrentPolicy + | MaxAgePolicy + | SWRPolicy + +/** + * Policy identifiers (the `policy` field's possible values). + */ +export type Policy = + | 'cache-only' + | 'network-only' + | 'network-only-non-concurrent' + | 'max-age' + | 'stale-while-revalidate' + +/** + * Combined cache options without adapter wiring. Kept exported for + * backwards-compatible consumer types that mirror the policy shape. + */ +export type CacheOptions = CacheOptionsBase & PolicyOptions + +/** + * Constructor options for `Cacheables`. + * + * `adapters` is required; the array is L1 first. `namespace`, when + * supplied, is prefixed onto every key passed to adapters as + * `${namespace}:${key}`. + */ +export type CacheablesOptions = + CacheOptionsBase & + PolicyOptions & { + adapters: IStorageAdapter[] + namespace?: string + } diff --git a/tests/adapters/memoryAdapter.test.ts b/tests/adapters/memoryAdapter.test.ts new file mode 100644 index 0000000..8ea2ba7 --- /dev/null +++ b/tests/adapters/memoryAdapter.test.ts @@ -0,0 +1,58 @@ +import { MemoryAdapter } from '../../src' + +describe('MemoryAdapter', () => { + it('write without meta synthesizes storedAt: Date.now()', async () => { + const a = new MemoryAdapter() + const before = Date.now() + await a.write('k', 'v') + const after = Date.now() + const meta = await a.meta('k') + expect(meta).toBeDefined() + expect(meta!.storedAt).toBeGreaterThanOrEqual(before) + expect(meta!.storedAt).toBeLessThanOrEqual(after) + }) + + it('write with meta persists storedAt verbatim', async () => { + const a = new MemoryAdapter() + await a.write('k', 'v', { storedAt: 12345 }) + const meta = await a.meta('k') + expect(meta?.storedAt).toBe(12345) + }) + + it('read returns undefined for missing keys', async () => { + const a = new MemoryAdapter() + expect(await a.read('missing')).toBeUndefined() + expect(await a.meta('missing')).toBeUndefined() + }) + + it('read returns stored value wrapped', async () => { + const a = new MemoryAdapter() + await a.write('k', { hello: 'world' }) + expect(await a.read('k')).toEqual({ value: { hello: 'world' } }) + }) + + it('read distinguishes a stored undefined from absence', async () => { + const a = new MemoryAdapter() + await a.write('k', undefined) + expect(await a.read('k')).toEqual({ value: undefined }) + expect(await a.read('missing')).toBeUndefined() + }) + + it('delete removes only the specified key', async () => { + const a = new MemoryAdapter() + await a.write('a', 1) + await a.write('b', 2) + await a.delete('a') + expect(await a.read('a')).toBeUndefined() + expect(await a.read('b')).toEqual({ value: 2 }) + }) + + it('clear removes everything', async () => { + const a = new MemoryAdapter() + await a.write('a', 1) + await a.write('b', 2) + await a.clear() + expect(await a.read('a')).toBeUndefined() + expect(await a.read('b')).toBeUndefined() + }) +}) diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts new file mode 100644 index 0000000..1f93bf5 --- /dev/null +++ b/tests/cascade.test.ts @@ -0,0 +1,477 @@ +import { Cacheables } from '../src' +import type { IBaseMeta, IStorageAdapter } from '../src' + +interface WriteCall { + key: string + value: unknown + meta: IBaseMeta | undefined +} + +class FakeAdapter implements IStorageAdapter { + store = new Map() + + readCalls = 0 + metaCalls = 0 + writeCalls: WriteCall[] = [] + deleteCalls: string[] = [] + clearCalls = 0 + + throwOn: Partial< + Record<'read' | 'meta' | 'write' | 'delete' | 'clear', boolean> + > = {} + + constructor(seed?: { key: string; value: unknown; meta: IBaseMeta }) { + if (seed) this.store.set(seed.key, { value: seed.value, meta: seed.meta }) + } + + async read(key: string): Promise<{ value: T } | undefined> { + this.readCalls += 1 + if (this.throwOn.read) throw new Error('read failed') + const entry = this.store.get(key) + return entry === undefined ? undefined : { value: entry.value as T } + } + + async write(key: string, value: T, meta?: IBaseMeta): Promise { + this.writeCalls.push({ key, value, meta }) + if (this.throwOn.write) throw new Error('write failed') + this.store.set(key, { + value, + meta: meta ?? { storedAt: Date.now() }, + }) + } + + async meta(key: string): Promise { + this.metaCalls += 1 + if (this.throwOn.meta) throw new Error('meta failed') + return this.store.get(key)?.meta + } + + async delete(key: string): Promise { + this.deleteCalls.push(key) + if (this.throwOn.delete) throw new Error('delete failed') + this.store.delete(key) + } + + async clear(): Promise { + this.clearCalls += 1 + if (this.throwOn.clear) throw new Error('clear failed') + this.store.clear() + } +} + +describe('cascade behavior', () => { + it('L1 hit: does not read L2, probes L2 once, no writes', async () => { + const seedMeta: IBaseMeta = { storedAt: Date.now() } + const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) + const l2 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1, l2] }) + + const result = await cache.remember(async () => 'fresh', 'k') + + expect(result).toEqual('v') + expect(l1.readCalls).toBe(1) + expect(l2.readCalls).toBe(0) + expect(l2.metaCalls).toBe(1) + // L2 had no entry → backfill writes once with L1's meta. + expect(l2.writeCalls.length).toBe(1) + expect(l2.writeCalls[0]!.meta?.storedAt).toBe(seedMeta.storedAt) + // L1 already had it → no L1 write. + expect(l1.writeCalls.length).toBe(0) + }) + + it('L1 hit + L2 already has it: no writes anywhere', async () => { + const seedMeta: IBaseMeta = { storedAt: 1000 } + const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) + const l2 = new FakeAdapter({ + key: 'k', + value: 'v', + meta: { storedAt: 999 }, + }) + const cache = new Cacheables({ adapters: [l1, l2] }) + + await cache.remember(async () => 'fresh', 'k') + + expect(l1.writeCalls.length).toBe(0) + expect(l2.writeCalls.length).toBe(0) + }) + + it('L1 miss + L2 hit: L1 backfilled with L2 meta (storedAt preserved)', async () => { + const l2Meta: IBaseMeta = { storedAt: 12345 } + const l1 = new FakeAdapter() + const l2 = new FakeAdapter({ key: 'k', value: 'v2', meta: l2Meta }) + const cache = new Cacheables({ adapters: [l1, l2] }) + + const result = await cache.remember(async () => 'fresh', 'k') + + expect(result).toEqual('v2') + expect(l1.writeCalls.length).toBe(1) + expect(l1.writeCalls[0]!.value).toBe('v2') + expect(l1.writeCalls[0]!.meta?.storedAt).toBe(l2Meta.storedAt) + // L2 already had it → no extra L2 write. + expect(l2.writeCalls.length).toBe(0) + expect(l2.readCalls).toBe(1) + }) + + it('Both miss: resource called once; L1 written without meta; L2 written with L1 meta', async () => { + const l1 = new FakeAdapter() + const l2 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1, l2] }) + + const before = Date.now() + let calls = 0 + const result = await cache.remember(async () => { + calls += 1 + return 'fresh' + }, 'k') + const after = Date.now() + + expect(result).toEqual('fresh') + expect(calls).toBe(1) + + expect(l1.writeCalls.length).toBe(1) + expect(l1.writeCalls[0]!.value).toBe('fresh') + expect(l1.writeCalls[0]!.meta).toBeUndefined() + + expect(l2.writeCalls.length).toBe(1) + expect(l2.writeCalls[0]!.value).toBe('fresh') + const l2Meta = l2.writeCalls[0]!.meta! + expect(l2Meta).toBeDefined() + expect(l2Meta.storedAt).toBeGreaterThanOrEqual(before) + expect(l2Meta.storedAt).toBeLessThanOrEqual(after) + + // L1 stored meta synthesized internally; the meta forwarded to L2 must + // match L1's stored meta (storedAt preservation). + const l1Meta = await l1.meta('k') + expect(l1Meta?.storedAt).toBe(l2Meta.storedAt) + }) + + it('3 layers, L3 hit: L1 and L2 both filled with L3 meta', async () => { + const l3Meta: IBaseMeta = { storedAt: 42 } + const l1 = new FakeAdapter() + const l2 = new FakeAdapter() + const l3 = new FakeAdapter({ key: 'k', value: 'deep', meta: l3Meta }) + const cache = new Cacheables({ adapters: [l1, l2, l3] }) + + const result = await cache.remember(async () => 'fresh', 'k') + + expect(result).toEqual('deep') + expect(l1.writeCalls.length).toBe(1) + expect(l2.writeCalls.length).toBe(1) + expect(l3.writeCalls.length).toBe(0) + expect(l1.writeCalls[0]!.meta?.storedAt).toBe(42) + expect(l2.writeCalls[0]!.meta?.storedAt).toBe(42) + }) + + it('max-age across layers: stale L1 with fresh L2 returns L2 value', async () => { + const now = Date.now() + const l1 = new FakeAdapter({ + key: 'k', + value: 'l1-stale', + meta: { storedAt: now - 500 }, + }) + const l2 = new FakeAdapter({ + key: 'k', + value: 'l2-fresh', + meta: { storedAt: now - 50 }, + }) + const cache = new Cacheables({ + adapters: [l1, l2], + policy: 'max-age', + maxAge: 100, + }) + + let calls = 0 + const result = await cache.remember(async () => { + calls += 1 + return 'network' + }, 'k') + + expect(result).toEqual('l2-fresh') + expect(calls).toBe(0) + }) + + it('max-age miss across all layers calls resource', async () => { + const now = Date.now() + const l1 = new FakeAdapter({ + key: 'k', + value: 'l1-stale', + meta: { storedAt: now - 500 }, + }) + const l2 = new FakeAdapter({ + key: 'k', + value: 'l2-stale', + meta: { storedAt: now - 500 }, + }) + const cache = new Cacheables({ + adapters: [l1, l2], + policy: 'max-age', + maxAge: 100, + }) + + let calls = 0 + const result = await cache.remember(async () => { + calls += 1 + return 'network' + }, 'k') + + expect(result).toEqual('network') + expect(calls).toBe(1) + }) + + it('delete propagates to all adapters', async () => { + const l1 = new FakeAdapter() + const l2 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1, l2] }) + + await cache.remember(async () => 'v', 'k') + await cache.delete('k') + + expect(l1.deleteCalls).toEqual(['k']) + expect(l2.deleteCalls).toEqual(['k']) + }) + + it('clear propagates to all adapters', async () => { + const l1 = new FakeAdapter() + const l2 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1, l2] }) + + await cache.remember(async () => 'v', 'k') + await cache.clear() + + expect(l1.clearCalls).toBe(1) + expect(l2.clearCalls).toBe(1) + }) + + it('isCached returns true if any layer has the key', async () => { + const l1 = new FakeAdapter() + const l2 = new FakeAdapter({ + key: 'k', + value: 'v', + meta: { storedAt: Date.now() }, + }) + const cache = new Cacheables({ adapters: [l1, l2] }) + + expect(await cache.isCached('k')).toBe(true) + expect(await cache.isCached('missing')).toBe(false) + }) + + it('meta returns highest-priority layer meta', async () => { + const l1 = new FakeAdapter() + const l2 = new FakeAdapter({ + key: 'k', + value: 'v', + meta: { storedAt: 999 }, + }) + const cache = new Cacheables({ adapters: [l1, l2] }) + + expect(await cache.meta('k')).toEqual({ storedAt: 999 }) + + await cache.remember(async () => 'fresh', 'k') + // Now L1 is filled with L2 meta. + expect((await cache.meta('k'))?.storedAt).toBe(999) + }) + + it('caches resources that resolve to undefined', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1] }) + + let calls = 0 + const resource = async () => { + calls += 1 + return undefined + } + + const a = await cache.remember(resource, 'k') + const b = await cache.remember(resource, 'k') + const c = await cache.remember(resource, 'k') + + expect(calls).toBe(1) + expect(a).toBeUndefined() + expect(b).toBeUndefined() + expect(c).toBeUndefined() + }) + + it('treats meta-says-yes / read-says-undefined as a miss', async () => { + // Simulates a delete that races between #cascadeProbe and adapter.read: + // meta still reports the entry; read claims absence. + const l1 = new FakeAdapter({ + key: 'k', + value: 'stale', + meta: { storedAt: Date.now() }, + }) + l1.read = async (): Promise<{ value: T } | undefined> => { + l1.readCalls += 1 + return undefined + } + + const cache = new Cacheables({ adapters: [l1] }) + + let calls = 0 + const result = await cache.remember(async () => { + calls += 1 + return 'fresh' + }, 'k') + + expect(result).toBe('fresh') + expect(calls).toBe(1) + }) +}) + +describe('cascade strict error propagation', () => { + it('rejects when meta() throws', async () => { + const l1 = new FakeAdapter() + l1.throwOn.meta = true + const cache = new Cacheables({ adapters: [l1] }) + + await expect(cache.remember(async () => 'v', 'k')).rejects.toThrow( + 'meta failed', + ) + }) + + it('rejects when read() throws on hit', async () => { + const l1 = new FakeAdapter({ + key: 'k', + value: 'v', + meta: { storedAt: Date.now() }, + }) + l1.throwOn.read = true + const cache = new Cacheables({ adapters: [l1] }) + + await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( + 'read failed', + ) + }) + + it('rejects when write() throws on miss', async () => { + const l1 = new FakeAdapter() + l1.throwOn.write = true + const cache = new Cacheables({ adapters: [l1] }) + + await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( + 'write failed', + ) + }) + + it('rejects when delete() throws', async () => { + const l1 = new FakeAdapter() + l1.throwOn.delete = true + const cache = new Cacheables({ adapters: [l1] }) + + await expect(cache.delete('k')).rejects.toThrow('delete failed') + }) + + it('rejects when clear() throws', async () => { + const l1 = new FakeAdapter() + l1.throwOn.clear = true + const cache = new Cacheables({ adapters: [l1] }) + + await expect(cache.clear()).rejects.toThrow('clear failed') + }) +}) + +describe('concurrent dedup', () => { + const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + + it('cache-only: deduplicates concurrent miss callers', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ adapters: [l1] }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + expect(results.every((r) => r === 'v')).toBe(true) + expect(calls).toBe(1) + }) + + it('network-only-non-concurrent: deduplicates concurrent callers', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ + adapters: [l1], + policy: 'network-only-non-concurrent', + }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + expect(results.every((r) => r === 'v')).toBe(true) + expect(calls).toBe(1) + }) + + it('max-age: deduplicates concurrent miss callers', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ + adapters: [l1], + policy: 'max-age', + maxAge: 1000, + }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + expect(results.every((r) => r === 'v')).toBe(true) + expect(calls).toBe(1) + }) + + it('SWR miss: deduplicates concurrent miss callers', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ + adapters: [l1], + policy: 'stale-while-revalidate', + }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + expect(results.every((r) => r === 'v')).toBe(true) + expect(calls).toBe(1) + }) + + it('network-only: does NOT deduplicate (control)', async () => { + const l1 = new FakeAdapter() + const cache = new Cacheables({ + adapters: [l1], + policy: 'network-only', + }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return calls + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + expect(results.length).toBe(100) + expect(calls).toBe(100) + }) +}) diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index 15eaf75..e5c599e 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -1,6 +1,6 @@ -import { Cacheables } from '../src' +import { Cacheables, MemoryAdapter } from '../src' -const mockedApiRequest = (value: T, duration = 0): Promise => +const mockedApiRequest = (value: T, duration = 0): Promise => new Promise((resolve) => { if (duration > 0) { setTimeout(() => { @@ -15,10 +15,12 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Fetch Policies', () => { it('cache-only', async () => { - const cache = new Cacheables({ policy: 'cache-only' }) + const cache = new Cacheables({ + adapters: [new MemoryAdapter()], + policy: 'cache-only', + }) - const co = (v: any) => - cache.remember(() => mockedApiRequest(v), 'key') + const co = (v: any) => cache.remember(() => mockedApiRequest(v), 'key') const a = await co(0) const b = await co(1) @@ -30,7 +32,10 @@ describe('Fetch Policies', () => { }) it('network-only-non-concurrent', async () => { - const cache = new Cacheables({ policy: 'network-only-non-concurrent' }) + const cache = new Cacheables({ + adapters: [new MemoryAdapter()], + policy: 'network-only-non-concurrent', + }) const nonc = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') @@ -51,10 +56,12 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 0, 0, 3]) }) it('network-only', async () => { - const cache = new Cacheables({ policy: 'network-only' }) + const cache = new Cacheables({ + adapters: [new MemoryAdapter()], + policy: 'network-only', + }) - const no = (v: any) => - cache.remember(() => mockedApiRequest(v, 50), 'key') + const no = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache await no(-1) @@ -68,10 +75,13 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 1, 2]) }) it('max-age', async () => { - const cache = new Cacheables({ policy: 'max-age', maxAge: 100 }) + const cache = new Cacheables({ + adapters: [new MemoryAdapter()], + policy: 'max-age', + maxAge: 100, + }) - const ma = (v: any) => - cache.remember(() => mockedApiRequest(v, 50), 'key') + const ma = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') const a = await ma(0) const b = await ma(1) @@ -84,10 +94,12 @@ describe('Fetch Policies', () => { expect([a, b, c, d]).toEqual([0, 0, 2, 2]) }) it('stale-while-revalidate', async () => { - const cache = new Cacheables({ policy: 'stale-while-revalidate' }) + const cache = new Cacheables({ + adapters: [new MemoryAdapter()], + policy: 'stale-while-revalidate', + }) - const swr = (v: any) => - cache.remember(() => mockedApiRequest(v, 50), 'key') + const swr = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache await swr(-1) @@ -109,12 +121,12 @@ describe('Fetch Policies', () => { it('stale-while-revalidate with maxAge', async () => { const cache = new Cacheables({ + adapters: [new MemoryAdapter()], policy: 'stale-while-revalidate', maxAge: 200, }) - const swr = (v: any) => - cache.remember(() => mockedApiRequest(v, 50), 'key') + const swr = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') // Preheat cache, takes ~50ms await swr(0) diff --git a/tests/index.test.ts b/tests/index.test.ts index 3c4ec3f..3ae62e5 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,8 +1,8 @@ -import { Cacheables } from '../src' +import { Cacheables, MemoryAdapter } from '../src' const errorMessage = 'This is an error message.' -const mockedApiRequest = ( +const mockedApiRequest = ( value: T, duration = 0, reject = false, @@ -22,19 +22,15 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) const value = 10 - const cachedValue = await cache.remember( - () => mockedApiRequest(value), - 'a', - ) - expect(cache.isCached('a')).toEqual(true) - expect(cache.keys()).toEqual(['a']) + const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') + expect(await cache.isCached('a')).toEqual(true) expect(cachedValue).toEqual(value) }) it('Stores multiple caches', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) const valueA = 10 const valueB = 20 @@ -48,30 +44,31 @@ describe('Cache operations', () => { 'b', ) - expect(cache.keys().sort()).toEqual(['a', 'b'].sort()) + expect(await cache.isCached('a')).toEqual(true) + expect(await cache.isCached('b')).toEqual(true) expect([cachedValueA, cachedValueB]).toEqual([valueA, valueB]) }) it('Deletes values', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(cache.isCached('a')).toEqual(true) - cache.delete('a') - expect(cache.isCached('a')).toEqual(false) + expect(await cache.isCached('a')).toEqual(true) + await cache.delete('a') + expect(await cache.isCached('a')).toEqual(false) }) it('Clears the cache', async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(cache.isCached('a')).toEqual(true) - cache.clear() - expect(cache.isCached('a')).toEqual(false) + expect(await cache.isCached('a')).toEqual(true) + await cache.clear() + expect(await cache.isCached('a')).toEqual(false) }) it('Creates proper keys', () => { @@ -81,6 +78,7 @@ describe('Cache operations', () => { it('Returns correctly if disabled', async () => { const cache = new Cacheables({ + adapters: [new MemoryAdapter()], enabled: false, }) @@ -97,6 +95,7 @@ describe('Cache operations', () => { console.log = jest.fn() const cache = new Cacheables({ + adapters: [new MemoryAdapter()], log: true, enabled: false, }) @@ -112,6 +111,15 @@ describe('Cache operations', () => { await cachedRequest() expect(console.log).lastCalledWith('Cacheable "a": hits: 1') + + await cachedRequest() + expect(console.log).lastCalledWith('Cacheable "a": hits: 2') + }) + + it('Throws when constructed without adapters', () => { + expect(() => new Cacheables({ adapters: [] })).toThrow( + 'At least one storage adapter is required', + ) }) /** @@ -123,6 +131,7 @@ describe('Cache operations', () => { */ it('Handles race conditions correctly', async () => { const cache = new Cacheables({ + adapters: [new MemoryAdapter()], policy: 'max-age', maxAge: 100, }) @@ -151,6 +160,7 @@ describe('Cache operations', () => { console.log = jest.fn() const cache = new Cacheables({ + adapters: [new MemoryAdapter()], log: true, policy: 'max-age', maxAge: 100, @@ -182,7 +192,7 @@ describe('Cache operations', () => { }) it("Doesn't interfere with error handling", async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) const rejecting = () => { return cache.remember(() => mockedApiRequest(0, 10, true), 'a') } @@ -190,7 +200,7 @@ describe('Cache operations', () => { }) it("Doesn't cache rejected value", async () => { - const cache = new Cacheables() + const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) let errNo = 1 const rejecting = () => { return cache.remember(() => Promise.reject(errNo++), 'a') diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts new file mode 100644 index 0000000..e3f9f50 --- /dev/null +++ b/tests/namespace.test.ts @@ -0,0 +1,60 @@ +import { Cacheables, MemoryAdapter } from '../src' + +describe('namespace', () => { + it('isolates entries between two instances sharing one adapter', async () => { + const adapter = new MemoryAdapter() + const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) + const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + + await a.remember(async () => 'A', 'k') + await b.remember(async () => 'B', 'k') + + expect(await a.remember(async () => 'fresh', 'k')).toBe('A') + expect(await b.remember(async () => 'fresh', 'k')).toBe('B') + }) + + it('writes adapter keys with namespace prefix', async () => { + const adapter = new MemoryAdapter() + const a = new Cacheables({ adapters: [adapter], namespace: 'tenant1' }) + + await a.remember(async () => 'v', 'user:42') + + expect(await adapter.read('tenant1:user:42')).toEqual({ value: 'v' }) + expect(await adapter.read('user:42')).toBeUndefined() + }) + + it('delete on one namespace does not affect another', async () => { + const adapter = new MemoryAdapter() + const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) + const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + + await a.remember(async () => 'A', 'k') + await b.remember(async () => 'B', 'k') + + await a.delete('k') + + expect(await a.isCached('k')).toBe(false) + expect(await b.isCached('k')).toBe(true) + }) + + it('clear from one namespace wipes shared adapter (documented caveat)', async () => { + const adapter = new MemoryAdapter() + const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) + const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + + await a.remember(async () => 'A', 'k') + await b.remember(async () => 'B', 'k') + + await a.clear() + + expect(await a.isCached('k')).toBe(false) + expect(await b.isCached('k')).toBe(false) + }) + + it('omitting namespace stores keys verbatim', async () => { + const adapter = new MemoryAdapter() + const cache = new Cacheables({ adapters: [adapter] }) + await cache.remember(async () => 'v', 'k') + expect(await adapter.read('k')).toEqual({ value: 'v' }) + }) +}) diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 7b5e17e..8777942 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "noEmit": true + "noEmit": true, + "rootDir": ".." }, "include": ["."] } \ No newline at end of file From 37259ec4b23a601d0546608dc1febd6e42f5687a Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 15:03:11 +0200 Subject: [PATCH 04/23] Make namespace a required Cacheables option Promotes `namespace` from optional to required on `CacheablesOptions` to prevent silent key collisions when adapters are shared across instances. Drops the undefined branch in `#fullKey` and removes the verbatim-key behaviour test along with related README/test updates. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 21 ++++++----- src/Cacheables.ts | 4 +-- src/types.ts | 8 ++--- tests/cascade.test.ts | 72 ++++++++++++++++++++----------------- tests/fetchPolicies.test.ts | 6 ++++ tests/index.test.ts | 18 ++++++---- tests/namespace.test.ts | 7 ---- 7 files changed, 74 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index baa792a..8c5bd19 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ A small, typed cache with composable storage adapters and a handful of cache pol - **Fully typed results**, including a generic `TMeta` parameter for sidecar metadata. - Supports different **cache policies**. - Helper to build cache keys. -- Optional **namespace** prefix so multiple instances can share an adapter without collisions. +- Required **namespace** prefix so multiple instances can share an adapter without collisions. - Works in the browser and Node.js. - **No dependencies**. ```ts import { Cacheables, MemoryAdapter } from 'cacheables' -const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) +const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'app' }) cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` @@ -60,6 +60,7 @@ const apiUrl = 'https://goweather.herokuapp.com/weather/Karlsruhe' const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'weather', policy: 'max-age', maxAge: 5_000, }) @@ -79,7 +80,7 @@ await getWeather() // hit — cached ```ts type CacheablesOptions = { adapters: IStorageAdapter[] // REQUIRED, L1 first - namespace?: string // adapter keys become `${namespace}:${key}` + namespace: string // REQUIRED — adapter keys become `${namespace}:${key}` enabled?: boolean // default: true log?: boolean // default: false logTiming?: boolean // default: false @@ -156,7 +157,7 @@ Ships with the package; covers the common in-memory use case. ```ts import { Cacheables, MemoryAdapter } from 'cacheables' -const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) +const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'app' }) ``` ### Writing your own adapter @@ -180,6 +181,7 @@ class FileSystemAdapter implements IStorageAdapter { ```ts const cache = new Cacheables({ adapters: [new MemoryAdapter(), new FileSystemAdapter()], + namespace: 'app', policy: 'max-age', maxAge: 60_000, }) @@ -206,6 +208,7 @@ class ETagAdapter implements IStorageAdapter { const cache = new Cacheables({ adapters: [new ETagAdapter()], + namespace: 'app', }) const meta = await cache.meta('user:42') // typed as ETagMeta | undefined @@ -215,7 +218,7 @@ Every adapter passed to the constructor must satisfy `IStorageAdapter` ## Namespacing -Set `namespace` and every adapter call sees keys prefixed with `${namespace}:`: +`namespace` is required: every adapter call sees keys prefixed with `${namespace}:`. This isolates instances that share the same adapter, so you must pick a namespace at construction time even when only one instance uses an adapter. ```ts const adapter = new MemoryAdapter() @@ -236,20 +239,20 @@ Two instances can share an adapter without colliding. `delete` and `isCached` re | `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | ```ts -new Cacheables({ adapters: [new MemoryAdapter()], policy: 'max-age', maxAge: 1_000 }) +new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'app', policy: 'max-age', maxAge: 1_000 }) ``` ## Migrating from v3 → v4 Breaking changes: -- `adapters` is now a **required** constructor option. `new Cacheables()` no longer compiles. Pass at least one adapter, e.g. `new Cacheables({ adapters: [new MemoryAdapter()] })`. +- `adapters` is now a **required** constructor option. `new Cacheables()` no longer compiles. Pass at least one adapter, e.g. `new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'app' })`. - `delete(key)` returns `Promise` (was `void`). Add `await`. - `clear()` returns `Promise` (was `void`). Add `await`. - `isCached(key)` returns `Promise` (was `boolean`). Add `await`. - `keys()` is **removed**. Enumerating heterogeneous async layers (some non-enumerable, like CDNs) doesn't have a single sensible semantic. -- `Cacheables` is now generic in `TMeta`. Plain `new Cacheables({ adapters })` defaults to `Cacheables` and is source-compatible at the type level. -- New constructor options: `adapters` (required) and `namespace` (optional). +- `Cacheables` is now generic in `TMeta`. Plain `new Cacheables({ adapters, namespace })` defaults to `Cacheables` and is source-compatible at the type level. +- New constructor options: `adapters` (required) and `namespace` (required). - Any throw from any adapter rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom adapters. ## License diff --git a/src/Cacheables.ts b/src/Cacheables.ts index 7995097..bacb6ec 100644 --- a/src/Cacheables.ts +++ b/src/Cacheables.ts @@ -14,7 +14,7 @@ export class Cacheables { #policy: Policy #maxAge: number | undefined #adapters: IStorageAdapter[] - #namespace: string | undefined + #namespace: string #inflight = new Map>() #hits = new Map() @@ -40,7 +40,7 @@ export class Cacheables { } #fullKey(key: string): string { - return this.#namespace === undefined ? key : `${this.#namespace}:${key}` + return `${this.#namespace}:${key}` } async delete(key: string): Promise { diff --git a/src/types.ts b/src/types.ts index 652bbad..233e684 100644 --- a/src/types.ts +++ b/src/types.ts @@ -98,13 +98,13 @@ export type CacheOptions = CacheOptionsBase & PolicyOptions /** * Constructor options for `Cacheables`. * - * `adapters` is required; the array is L1 first. `namespace`, when - * supplied, is prefixed onto every key passed to adapters as - * `${namespace}:${key}`. + * `adapters` is required; the array is L1 first. `namespace` is + * required and is prefixed onto every key passed to adapters as + * `${namespace}:${key}`, isolating instances that share an adapter. */ export type CacheablesOptions = CacheOptionsBase & PolicyOptions & { adapters: IStorageAdapter[] - namespace?: string + namespace: string } diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index 1f93bf5..12873cd 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -62,9 +62,9 @@ class FakeAdapter implements IStorageAdapter { describe('cascade behavior', () => { it('L1 hit: does not read L2, probes L2 once, no writes', async () => { const seedMeta: IBaseMeta = { storedAt: Date.now() } - const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) + const l1 = new FakeAdapter({ key: 'test:k', value: 'v', meta: seedMeta }) const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) const result = await cache.remember(async () => 'fresh', 'k') @@ -81,13 +81,13 @@ describe('cascade behavior', () => { it('L1 hit + L2 already has it: no writes anywhere', async () => { const seedMeta: IBaseMeta = { storedAt: 1000 } - const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) + const l1 = new FakeAdapter({ key: 'test:k', value: 'v', meta: seedMeta }) const l2 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) await cache.remember(async () => 'fresh', 'k') @@ -98,8 +98,8 @@ describe('cascade behavior', () => { it('L1 miss + L2 hit: L1 backfilled with L2 meta (storedAt preserved)', async () => { const l2Meta: IBaseMeta = { storedAt: 12345 } const l1 = new FakeAdapter() - const l2 = new FakeAdapter({ key: 'k', value: 'v2', meta: l2Meta }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const l2 = new FakeAdapter({ key: 'test:k', value: 'v2', meta: l2Meta }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) const result = await cache.remember(async () => 'fresh', 'k') @@ -115,7 +115,7 @@ describe('cascade behavior', () => { it('Both miss: resource called once; L1 written without meta; L2 written with L1 meta', async () => { const l1 = new FakeAdapter() const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) const before = Date.now() let calls = 0 @@ -141,7 +141,7 @@ describe('cascade behavior', () => { // L1 stored meta synthesized internally; the meta forwarded to L2 must // match L1's stored meta (storedAt preservation). - const l1Meta = await l1.meta('k') + const l1Meta = await l1.meta('test:k') expect(l1Meta?.storedAt).toBe(l2Meta.storedAt) }) @@ -149,8 +149,8 @@ describe('cascade behavior', () => { const l3Meta: IBaseMeta = { storedAt: 42 } const l1 = new FakeAdapter() const l2 = new FakeAdapter() - const l3 = new FakeAdapter({ key: 'k', value: 'deep', meta: l3Meta }) - const cache = new Cacheables({ adapters: [l1, l2, l3] }) + const l3 = new FakeAdapter({ key: 'test:k', value: 'deep', meta: l3Meta }) + const cache = new Cacheables({ adapters: [l1, l2, l3], namespace: 'test' }) const result = await cache.remember(async () => 'fresh', 'k') @@ -165,17 +165,18 @@ describe('cascade behavior', () => { it('max-age across layers: stale L1 with fresh L2 returns L2 value', async () => { const now = Date.now() const l1 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'l1-stale', meta: { storedAt: now - 500 }, }) const l2 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'l2-fresh', meta: { storedAt: now - 50 }, }) const cache = new Cacheables({ adapters: [l1, l2], + namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -193,17 +194,18 @@ describe('cascade behavior', () => { it('max-age miss across all layers calls resource', async () => { const now = Date.now() const l1 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'l1-stale', meta: { storedAt: now - 500 }, }) const l2 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'l2-stale', meta: { storedAt: now - 500 }, }) const cache = new Cacheables({ adapters: [l1, l2], + namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -221,19 +223,19 @@ describe('cascade behavior', () => { it('delete propagates to all adapters', async () => { const l1 = new FakeAdapter() const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) await cache.remember(async () => 'v', 'k') await cache.delete('k') - expect(l1.deleteCalls).toEqual(['k']) - expect(l2.deleteCalls).toEqual(['k']) + expect(l1.deleteCalls).toEqual(['test:k']) + expect(l2.deleteCalls).toEqual(['test:k']) }) it('clear propagates to all adapters', async () => { const l1 = new FakeAdapter() const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) await cache.remember(async () => 'v', 'k') await cache.clear() @@ -245,11 +247,11 @@ describe('cascade behavior', () => { it('isCached returns true if any layer has the key', async () => { const l1 = new FakeAdapter() const l2 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'v', meta: { storedAt: Date.now() }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) expect(await cache.isCached('k')).toBe(true) expect(await cache.isCached('missing')).toBe(false) @@ -258,11 +260,11 @@ describe('cascade behavior', () => { it('meta returns highest-priority layer meta', async () => { const l1 = new FakeAdapter() const l2 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheables({ adapters: [l1, l2], namespace: 'test' }) expect(await cache.meta('k')).toEqual({ storedAt: 999 }) @@ -273,7 +275,7 @@ describe('cascade behavior', () => { it('caches resources that resolve to undefined', async () => { const l1 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) let calls = 0 const resource = async () => { @@ -295,7 +297,7 @@ describe('cascade behavior', () => { // Simulates a delete that races between #cascadeProbe and adapter.read: // meta still reports the entry; read claims absence. const l1 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'stale', meta: { storedAt: Date.now() }, }) @@ -304,7 +306,7 @@ describe('cascade behavior', () => { return undefined } - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) let calls = 0 const result = await cache.remember(async () => { @@ -321,7 +323,7 @@ describe('cascade strict error propagation', () => { it('rejects when meta() throws', async () => { const l1 = new FakeAdapter() l1.throwOn.meta = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) await expect(cache.remember(async () => 'v', 'k')).rejects.toThrow( 'meta failed', @@ -330,12 +332,12 @@ describe('cascade strict error propagation', () => { it('rejects when read() throws on hit', async () => { const l1 = new FakeAdapter({ - key: 'k', + key: 'test:k', value: 'v', meta: { storedAt: Date.now() }, }) l1.throwOn.read = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'read failed', @@ -345,7 +347,7 @@ describe('cascade strict error propagation', () => { it('rejects when write() throws on miss', async () => { const l1 = new FakeAdapter() l1.throwOn.write = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'write failed', @@ -355,7 +357,7 @@ describe('cascade strict error propagation', () => { it('rejects when delete() throws', async () => { const l1 = new FakeAdapter() l1.throwOn.delete = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) await expect(cache.delete('k')).rejects.toThrow('delete failed') }) @@ -363,7 +365,7 @@ describe('cascade strict error propagation', () => { it('rejects when clear() throws', async () => { const l1 = new FakeAdapter() l1.throwOn.clear = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) await expect(cache.clear()).rejects.toThrow('clear failed') }) @@ -374,7 +376,7 @@ describe('concurrent dedup', () => { it('cache-only: deduplicates concurrent miss callers', async () => { const l1 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheables({ adapters: [l1], namespace: 'test' }) let calls = 0 const slow = async () => { @@ -394,6 +396,7 @@ describe('concurrent dedup', () => { const l1 = new FakeAdapter() const cache = new Cacheables({ adapters: [l1], + namespace: 'test', policy: 'network-only-non-concurrent', }) @@ -415,6 +418,7 @@ describe('concurrent dedup', () => { const l1 = new FakeAdapter() const cache = new Cacheables({ adapters: [l1], + namespace: 'test', policy: 'max-age', maxAge: 1000, }) @@ -437,6 +441,7 @@ describe('concurrent dedup', () => { const l1 = new FakeAdapter() const cache = new Cacheables({ adapters: [l1], + namespace: 'test', policy: 'stale-while-revalidate', }) @@ -458,6 +463,7 @@ describe('concurrent dedup', () => { const l1 = new FakeAdapter() const cache = new Cacheables({ adapters: [l1], + namespace: 'test', policy: 'network-only', }) diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index e5c599e..c1dbd13 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -17,6 +17,7 @@ describe('Fetch Policies', () => { it('cache-only', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'cache-only', }) @@ -34,6 +35,7 @@ describe('Fetch Policies', () => { it('network-only-non-concurrent', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'network-only-non-concurrent', }) @@ -58,6 +60,7 @@ describe('Fetch Policies', () => { it('network-only', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'network-only', }) @@ -77,6 +80,7 @@ describe('Fetch Policies', () => { it('max-age', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -96,6 +100,7 @@ describe('Fetch Policies', () => { it('stale-while-revalidate', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'stale-while-revalidate', }) @@ -122,6 +127,7 @@ describe('Fetch Policies', () => { it('stale-while-revalidate with maxAge', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'stale-while-revalidate', maxAge: 200, }) diff --git a/tests/index.test.ts b/tests/index.test.ts index 3ae62e5..73e0f6f 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -22,7 +22,7 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') expect(await cache.isCached('a')).toEqual(true) @@ -30,7 +30,7 @@ describe('Cache operations', () => { }) it('Stores multiple caches', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) const valueA = 10 const valueB = 20 @@ -50,7 +50,7 @@ describe('Cache operations', () => { }) it('Deletes values', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -61,7 +61,7 @@ describe('Cache operations', () => { }) it('Clears the cache', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -79,6 +79,7 @@ describe('Cache operations', () => { it('Returns correctly if disabled', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', enabled: false, }) @@ -96,6 +97,7 @@ describe('Cache operations', () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', log: true, enabled: false, }) @@ -117,7 +119,7 @@ describe('Cache operations', () => { }) it('Throws when constructed without adapters', () => { - expect(() => new Cacheables({ adapters: [] })).toThrow( + expect(() => new Cacheables({ adapters: [], namespace: 'test' })).toThrow( 'At least one storage adapter is required', ) }) @@ -132,6 +134,7 @@ describe('Cache operations', () => { it('Handles race conditions correctly', async () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -161,6 +164,7 @@ describe('Cache operations', () => { const cache = new Cacheables({ adapters: [new MemoryAdapter()], + namespace: 'test', log: true, policy: 'max-age', maxAge: 100, @@ -192,7 +196,7 @@ describe('Cache operations', () => { }) it("Doesn't interfere with error handling", async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) const rejecting = () => { return cache.remember(() => mockedApiRequest(0, 10, true), 'a') } @@ -200,7 +204,7 @@ describe('Cache operations', () => { }) it("Doesn't cache rejected value", async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'test' }) let errNo = 1 const rejecting = () => { return cache.remember(() => Promise.reject(errNo++), 'a') diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts index e3f9f50..3ef2e88 100644 --- a/tests/namespace.test.ts +++ b/tests/namespace.test.ts @@ -50,11 +50,4 @@ describe('namespace', () => { expect(await a.isCached('k')).toBe(false) expect(await b.isCached('k')).toBe(false) }) - - it('omitting namespace stores keys verbatim', async () => { - const adapter = new MemoryAdapter() - const cache = new Cacheables({ adapters: [adapter] }) - await cache.remember(async () => 'v', 'k') - expect(await adapter.read('k')).toEqual({ value: 'v' }) - }) }) From e7cd88b1dc8d4363784ea5608788040944a3680b Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 15:09:30 +0200 Subject: [PATCH 05/23] Rename Cacheables to Cacheable and storage adapters to buckets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the main class from `Cacheables` to `Cacheable` and reframes the storage-adapter concept as buckets — `IStorageAdapter` becomes `IBucket`, `MemoryAdapter` becomes `MemoryBucket`, the `adapters` constructor option becomes `buckets`, and the `CacheablesOptions` type becomes `CacheableOptions`. Files moved from `src/adapters/` to `src/buckets/` (and similarly under `tests/`); README and v3 → v4 migration notes updated accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 121 +++++++-------- src/{Cacheables.ts => Cacheable.ts} | 36 ++--- .../MemoryBucket.ts} | 4 +- src/index.ts | 8 +- src/types.ts | 30 ++-- .../memoryBucket.test.ts} | 18 +-- tests/cascade.test.ts | 140 +++++++++--------- tests/fetchPolicies.test.ts | 26 ++-- tests/index.test.ts | 38 ++--- tests/namespace.test.ts | 40 ++--- 10 files changed, 233 insertions(+), 228 deletions(-) rename src/{Cacheables.ts => Cacheable.ts} (86%) rename src/{adapters/MemoryAdapter.ts => buckets/MemoryBucket.ts} (84%) rename tests/{adapters/memoryAdapter.test.ts => buckets/memoryBucket.test.ts} (83%) diff --git a/README.md b/README.md index baa792a..c37ac27 100644 --- a/README.md +++ b/README.md @@ -4,40 +4,39 @@ ![Language](https://img.shields.io/github/languages/top/grischaerbe/cacheables) ![Build](https://img.shields.io/github/workflow/status/grischaerbe/cacheables/Node.js%20Package) -A small, typed cache with composable storage adapters and a handful of cache policies, written in TypeScript. +A small, typed cache with composable storage buckets and a handful of cache policies, written in TypeScript. - Elegant syntax: **wrap existing async calls** with `cache.remember(...)`. - **Multilayer storage**: compose a fast in-memory L1 with any L2 you write (filesystem, Redis, S3, …). Reads cascade L1 → Ln; on any hit the engine fills missing layers. - **Fully typed results**, including a generic `TMeta` parameter for sidecar metadata. - Supports different **cache policies**. - Helper to build cache keys. -- Optional **namespace** prefix so multiple instances can share an adapter without collisions. +- Optional **namespace** prefix so multiple instances can share a bucket without collisions. - Works in the browser and Node.js. - **No dependencies**. ```ts -import { Cacheables, MemoryAdapter } from 'cacheables' +import { Cacheable, MemoryBucket } from 'cacheables' -const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) +const cache = new Cacheable({ buckets: [new MemoryBucket()] }) cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` * [Installation](#installation) -* [Quickstart](#quickstart) * [Usage](#usage) * [API](#api) - * [new Cacheables(options)](#new-cacheablesoptions-cacheablestmeta) + * [new Cacheable(options)](#new-cacheableoptions-cacheabletmeta) * [cache.remember(resource, key)](#cacherememberresource-key-promiset) * [cache.delete(key)](#cachedeletekey-promisevoid) * [cache.clear()](#cacheclear-promisevoid) * [cache.isCached(key)](#cacheiscachedkey-promiseboolean) * [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - * [Cacheables.key(...args)](#cacheableskeyargs-string) -* [Storage adapters](#storage-adapters) - * [`IStorageAdapter` contract](#istorageadapter-contract) - * [Built-in `MemoryAdapter`](#built-in-memoryadapter) - * [Writing your own adapter](#writing-your-own-adapter) + * [Cacheable.key(...args)](#cacheablekeyargs-string) +* [Buckets](#buckets) + * [`IBucket` contract](#ibucket-contract) + * [Built-in `MemoryBucket`](#built-in-memorybucket) + * [Writing your own bucket](#writing-your-own-bucket) * [Cascade behavior](#cascade-behavior) * [Typed metadata (`TMeta`)](#typed-metadata-tmeta) * [Namespacing](#namespacing) @@ -54,12 +53,12 @@ npm install cacheables ## Usage ```ts -import { Cacheables, MemoryAdapter } from 'cacheables' +import { Cacheable, MemoryBucket } from 'cacheables' const apiUrl = 'https://goweather.herokuapp.com/weather/Karlsruhe' -const cache = new Cacheables({ - adapters: [new MemoryAdapter()], +const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 5_000, }) @@ -70,16 +69,16 @@ await getWeather() // miss — fetched await getWeather() // hit — cached ``` -`remember` is both getter and setter. The first time a key is requested it calls the resource and stores the result in every configured adapter; subsequent reads cascade through the adapters until one returns a hit. +`remember` is both getter and setter. The first time a key is requested it calls the resource and stores the result in every configured bucket; subsequent reads cascade through the buckets until one returns a hit. ## API -### `new Cacheables(options): Cacheables` +### `new Cacheable(options): Cacheable` ```ts -type CacheablesOptions = { - adapters: IStorageAdapter[] // REQUIRED, L1 first - namespace?: string // adapter keys become `${namespace}:${key}` +type CacheableOptions = { + buckets: IBucket[] // REQUIRED, L1 first + namespace?: string // bucket keys become `${namespace}:${key}` enabled?: boolean // default: true log?: boolean // default: false logTiming?: boolean // default: false @@ -92,19 +91,19 @@ type CacheablesOptions = { ) ``` -`adapters` must be a non-empty array; the constructor throws `Error('At least one storage adapter is required')` otherwise. The first adapter is L1 (fastest, queried first); the rest form deeper layers. +`buckets` must be a non-empty array; the constructor throws `Error('At least one bucket is required')` otherwise. The first bucket is L1 (fastest, queried first); the rest form deeper layers. ### `cache.remember(resource, key): Promise` -Resolves to the cached value if present (subject to policy), otherwise calls `resource()` and stores the result in every adapter. +Resolves to the cached value if present (subject to policy), otherwise calls `resource()` and stores the result in every bucket. ### `cache.delete(key): Promise` -Deletes the entry from every adapter. +Deletes the entry from every bucket. ### `cache.clear(): Promise` -Clears every adapter and the in-flight registry. +Clears every bucket and the in-flight registry. ### `cache.isCached(key): Promise` @@ -114,24 +113,26 @@ Clears every adapter and the in-flight registry. Returns the meta from the highest-priority layer that has the key — useful for inspecting sidecar fields like `etag`, `ttl`, etc. -### `Cacheables.key(...args): string` +### `Cacheable.key(...args): string` Joins the parts with `:`. Identical to v3. ```ts -Cacheables.key('user', 42) // 'user:42' +Cacheable.key('user', 42) // 'user:42' ``` -## Storage adapters +## Buckets -### `IStorageAdapter` contract +A bucket is a single storage tier — memory, Redis, disk, S3, anything you can read and write by key. A `Cacheable` instance holds an ordered list of buckets and cascades reads and writes across them. + +### `IBucket` contract ```ts interface IBaseMeta { storedAt: number } -interface IStorageAdapter { +interface IBucket { read(key: string): Promise<{ value: T } | undefined> write(key: string, value: T, meta?: TMeta): Promise meta(key: string): Promise @@ -143,30 +144,30 @@ interface IStorageAdapter { Rules: - `meta` MUST be cheap. The engine probes it on every layer for every read. -- `read` returns `undefined` when the entry is absent and `{ value }` when it's present — the wrapper exists so adapters can store entries whose value is itself `undefined` without colliding with the absence signal. -- When `write` receives a `meta`, the adapter MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This guarantees `max-age` semantics stay coherent across layers. -- When `write` is called with no `meta`, the adapter MUST synthesize one with `storedAt: Date.now()`. -- `clear` MUST remove every entry the adapter manages. -- Any throw from any adapter rejects the surrounding `remember()` call. There is no per-adapter error suppression. +- `read` returns `undefined` when the entry is absent and `{ value }` when it's present — the wrapper exists so buckets can store entries whose value is itself `undefined` without colliding with the absence signal. +- When `write` receives a `meta`, the bucket MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This guarantees `max-age` semantics stay coherent across layers. +- When `write` is called with no `meta`, the bucket MUST synthesize one with `storedAt: Date.now()`. +- `clear` MUST remove every entry the bucket manages. +- Any throw from any bucket rejects the surrounding `remember()` call. There is no per-bucket error suppression. -### Built-in `MemoryAdapter` +### Built-in `MemoryBucket` Ships with the package; covers the common in-memory use case. ```ts -import { Cacheables, MemoryAdapter } from 'cacheables' +import { Cacheable, MemoryBucket } from 'cacheables' -const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) +const cache = new Cacheable({ buckets: [new MemoryBucket()] }) ``` -### Writing your own adapter +### Writing your own bucket -Implement `IStorageAdapter`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 layers are easy to add without ceremony. A typical L2 keeps a sidecar (file, table, key/value entry) so `meta()` is cheap. +Implement `IBucket`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 buckets are easy to add without ceremony. A typical L2 keeps a sidecar (file, table, key/value entry) so `meta()` is cheap. ```ts -import type { IStorageAdapter, IBaseMeta } from 'cacheables' +import type { IBucket, IBaseMeta } from 'cacheables' -class FileSystemAdapter implements IStorageAdapter { +class FileSystemBucket implements IBucket { async read(key: string): Promise<{ value: T } | undefined> { /* … */ } async write(key: string, value: T, meta?: IBaseMeta): Promise { /* … */ } async meta(key: string): Promise { /* … */ } @@ -178,8 +179,8 @@ class FileSystemAdapter implements IStorageAdapter { ### Cascade behavior ```ts -const cache = new Cacheables({ - adapters: [new MemoryAdapter(), new FileSystemAdapter()], +const cache = new Cacheable({ + buckets: [new MemoryBucket(), new FileSystemBucket()], policy: 'max-age', maxAge: 60_000, }) @@ -191,39 +192,39 @@ const cache = new Cacheables({ ### Typed metadata (`TMeta`) -`Cacheables` is generic in `TMeta`. Extend it to carry sidecar fields: +`Cacheable` is generic in `TMeta`. Extend it to carry sidecar fields: ```ts -import { Cacheables, type IBaseMeta, type IStorageAdapter } from 'cacheables' +import { Cacheable, type IBaseMeta, type IBucket } from 'cacheables' interface ETagMeta extends IBaseMeta { etag: string } -class ETagAdapter implements IStorageAdapter { +class ETagBucket implements IBucket { // read / write / meta / delete / clear … } -const cache = new Cacheables({ - adapters: [new ETagAdapter()], +const cache = new Cacheable({ + buckets: [new ETagBucket()], }) const meta = await cache.meta('user:42') // typed as ETagMeta | undefined ``` -Every adapter passed to the constructor must satisfy `IStorageAdapter`, and the TypeScript compiler enforces it. The built-in `MemoryAdapter` only implements `IStorageAdapter`, so it can't be used in a `Cacheables` instance with a custom `TMeta` — write a custom adapter (or wrap `MemoryAdapter`) when you need extended metadata. +Every bucket passed to the constructor must satisfy `IBucket`, and the TypeScript compiler enforces it. The built-in `MemoryBucket` only implements `IBucket`, so it can't be used in a `Cacheable` instance with a custom `TMeta` — write a custom bucket (or wrap `MemoryBucket`) when you need extended metadata. ## Namespacing -Set `namespace` and every adapter call sees keys prefixed with `${namespace}:`: +Set `namespace` and every bucket call sees keys prefixed with `${namespace}:`: ```ts -const adapter = new MemoryAdapter() -const tenantA = new Cacheables({ adapters: [adapter], namespace: 'tenant-a' }) -const tenantB = new Cacheables({ adapters: [adapter], namespace: 'tenant-b' }) +const bucket = new MemoryBucket() +const tenantA = new Cacheable({ buckets: [bucket], namespace: 'tenant-a' }) +const tenantB = new Cacheable({ buckets: [bucket], namespace: 'tenant-b' }) ``` -Two instances can share an adapter without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying adapter (it has no notion of which keys belong to which namespace), so reach for it only when you really mean *everything*. +Two instances can share a bucket without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean *everything*. ## Cache Policies @@ -236,21 +237,25 @@ Two instances can share an adapter without colliding. `delete` and `isCached` re | `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | ```ts -new Cacheables({ adapters: [new MemoryAdapter()], policy: 'max-age', maxAge: 1_000 }) +new Cacheable({ buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 1_000 }) ``` ## Migrating from v3 → v4 Breaking changes: -- `adapters` is now a **required** constructor option. `new Cacheables()` no longer compiles. Pass at least one adapter, e.g. `new Cacheables({ adapters: [new MemoryAdapter()] })`. +- The class `Cacheables` has been renamed to `Cacheable`. Update imports and `new Cacheables(...)` call sites. +- The `IStorageAdapter` interface has been renamed to `IBucket`, and `MemoryAdapter` to `MemoryBucket`. The contract is unchanged. +- `buckets` is now a **required** constructor option (replaces the v3-style implicit memory store, and renamed from `adapters`). `new Cacheable()` no longer compiles. Pass at least one bucket, e.g. `new Cacheable({ buckets: [new MemoryBucket()] })`. +- The constructor's empty-buckets error message is now `'At least one bucket is required'` (was `'At least one storage adapter is required'`). +- The `CacheablesOptions` type has been renamed to `CacheableOptions`. - `delete(key)` returns `Promise` (was `void`). Add `await`. - `clear()` returns `Promise` (was `void`). Add `await`. - `isCached(key)` returns `Promise` (was `boolean`). Add `await`. - `keys()` is **removed**. Enumerating heterogeneous async layers (some non-enumerable, like CDNs) doesn't have a single sensible semantic. -- `Cacheables` is now generic in `TMeta`. Plain `new Cacheables({ adapters })` defaults to `Cacheables` and is source-compatible at the type level. -- New constructor options: `adapters` (required) and `namespace` (optional). -- Any throw from any adapter rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom adapters. +- `Cacheable` is now generic in `TMeta`. Plain `new Cacheable({ buckets })` defaults to `Cacheable` and is source-compatible at the type level. +- New constructor options: `buckets` (required) and `namespace` (optional). +- Any throw from any bucket rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom buckets. ## License diff --git a/src/Cacheables.ts b/src/Cacheable.ts similarity index 86% rename from src/Cacheables.ts rename to src/Cacheable.ts index 7995097..64e90cf 100644 --- a/src/Cacheables.ts +++ b/src/Cacheable.ts @@ -1,28 +1,28 @@ import { Logger } from './Logger' import type { - CacheablesOptions, + CacheableOptions, IBaseMeta, - IStorageAdapter, + IBucket, Policy, } from './types' -export class Cacheables { +export class Cacheable { enabled: boolean log: boolean logTiming: boolean #policy: Policy #maxAge: number | undefined - #adapters: IStorageAdapter[] + #buckets: IBucket[] #namespace: string | undefined #inflight = new Map>() #hits = new Map() - constructor(options: CacheablesOptions) { - if (!options.adapters || options.adapters.length === 0) { - throw new Error('At least one storage adapter is required') + constructor(options: CacheableOptions) { + if (!options.buckets || options.buckets.length === 0) { + throw new Error('At least one bucket is required') } - this.#adapters = options.adapters + this.#buckets = options.buckets this.#namespace = options.namespace this.enabled = options.enabled ?? true this.log = options.log ?? false @@ -46,13 +46,13 @@ export class Cacheables { async delete(key: string): Promise { const fullKey = this.#fullKey(key) this.#hits.delete(fullKey) - await Promise.all(this.#adapters.map((a) => a.delete(fullKey))) + await Promise.all(this.#buckets.map((b) => b.delete(fullKey))) } async clear(): Promise { this.#inflight.clear() this.#hits.clear() - await Promise.all(this.#adapters.map((a) => a.clear())) + await Promise.all(this.#buckets.map((b) => b.clear())) } async isCached(key: string): Promise { @@ -175,7 +175,7 @@ export class Cacheables { } async #cascadeProbe(fullKey: string): Promise<(TMeta | undefined)[]> { - return Promise.all(this.#adapters.map((a) => a.meta(fullKey))) + return Promise.all(this.#buckets.map((b) => b.meta(fullKey))) } async #cascadeRead( @@ -188,8 +188,8 @@ export class Cacheables { ) if (hitIdx === -1) return undefined - const adapter = this.#adapters[hitIdx]! - const result = await adapter.read(fullKey) + const bucket = this.#buckets[hitIdx]! + const result = await bucket.read(fullKey) if (result === undefined) return undefined const value = result.value @@ -206,23 +206,23 @@ export class Cacheables { hitIdx: number, ): Promise { const writes: Promise[] = [] - for (let i = 0; i < this.#adapters.length; i++) { + for (let i = 0; i < this.#buckets.length; i++) { if (i === hitIdx) continue if (probes[i] !== undefined) continue - writes.push(this.#adapters[i]!.write(fullKey, value, hitMeta)) + writes.push(this.#buckets[i]!.write(fullKey, value, hitMeta)) } if (writes.length > 0) await Promise.all(writes) } async #cascadeWrite(fullKey: string, value: T): Promise { - const [l1, ...rest] = this.#adapters + const [l1, ...rest] = this.#buckets if (l1 === undefined) return await l1.write(fullKey, value) if (rest.length === 0) return const meta = await l1.meta(fullKey) if (meta === undefined) { - throw new Error('L1 adapter did not persist meta after write') + throw new Error('L1 bucket did not persist meta after write') } - await Promise.all(rest.map((a) => a.write(fullKey, value, meta))) + await Promise.all(rest.map((b) => b.write(fullKey, value, meta))) } } diff --git a/src/adapters/MemoryAdapter.ts b/src/buckets/MemoryBucket.ts similarity index 84% rename from src/adapters/MemoryAdapter.ts rename to src/buckets/MemoryBucket.ts index 58f6b13..bcdd56f 100644 --- a/src/adapters/MemoryAdapter.ts +++ b/src/buckets/MemoryBucket.ts @@ -1,6 +1,6 @@ -import type { IBaseMeta, IStorageAdapter } from '../types' +import type { IBaseMeta, IBucket } from '../types' -export class MemoryAdapter implements IStorageAdapter { +export class MemoryBucket implements IBucket { #store = new Map() async read(key: string): Promise<{ value: T } | undefined> { diff --git a/src/index.ts b/src/index.ts index fe68eef..d36d055 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -export { Cacheables } from './Cacheables' -export { MemoryAdapter } from './adapters/MemoryAdapter' +export { Cacheable } from './Cacheable' +export { MemoryBucket } from './buckets/MemoryBucket' export type { CacheOptions, - CacheablesOptions, + CacheableOptions, IBaseMeta, - IStorageAdapter, + IBucket, } from './types' diff --git a/src/types.ts b/src/types.ts index 652bbad..3a8f881 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,33 +1,33 @@ /** - * Base shape for all adapter metadata. Adapters MAY extend this with + * Base shape for all bucket metadata. Buckets MAY extend this with * additional sidecar fields (etag, ttl, version, etc.) provided their - * extension is captured by the `TMeta` generic of `Cacheables`. + * extension is captured by the `TMeta` generic of `Cacheable`. */ export interface IBaseMeta { storedAt: number } /** - * Storage adapter contract. Adapters back layers (L1, L2, ...) of a - * `Cacheables` instance. Reads cascade L1 → Ln; on any hit the engine + * Bucket contract. Buckets back the layers (L1, L2, ...) of a + * `Cacheable` instance. Reads cascade L1 → Ln; on any hit the engine * fills missing layers with the hit value preserving `meta.storedAt`. * * Contracts: * - `meta` MUST be cheap (engine probes it on every layer per read). * - `read` returns `undefined` when the entry is absent and `{ value }` - * when present — the wrapper exists so adapters can store entries + * when present — the wrapper exists so buckets can store entries * whose value is itself `undefined` without colliding with the * absence signal. - * - When `write` receives a `meta`, the adapter MUST persist + * - When `write` receives a `meta`, the bucket MUST persist * `meta.storedAt` verbatim. Other fields MAY be transformed. - * - When `write` receives no `meta`, the adapter MUST synthesize one + * - When `write` receives no `meta`, the bucket MUST synthesize one * with `storedAt: Date.now()` (and any other `TMeta` fields it knows * how to populate). - * - `clear` MUST remove every entry the adapter manages. + * - `clear` MUST remove every entry the bucket manages. * - All five methods MAY throw on infrastructure errors. The engine * treats throws as fatal (strict mode). */ -export interface IStorageAdapter { +export interface IBucket { read(key: string): Promise<{ value: T } | undefined> write(key: string, value: T, meta?: TMeta): Promise meta(key: string): Promise @@ -90,21 +90,21 @@ export type Policy = | 'stale-while-revalidate' /** - * Combined cache options without adapter wiring. Kept exported for + * Combined cache options without bucket wiring. Kept exported for * backwards-compatible consumer types that mirror the policy shape. */ export type CacheOptions = CacheOptionsBase & PolicyOptions /** - * Constructor options for `Cacheables`. + * Constructor options for `Cacheable`. * - * `adapters` is required; the array is L1 first. `namespace`, when - * supplied, is prefixed onto every key passed to adapters as + * `buckets` is required; the array is L1 first. `namespace`, when + * supplied, is prefixed onto every key passed to buckets as * `${namespace}:${key}`. */ -export type CacheablesOptions = +export type CacheableOptions = CacheOptionsBase & PolicyOptions & { - adapters: IStorageAdapter[] + buckets: IBucket[] namespace?: string } diff --git a/tests/adapters/memoryAdapter.test.ts b/tests/buckets/memoryBucket.test.ts similarity index 83% rename from tests/adapters/memoryAdapter.test.ts rename to tests/buckets/memoryBucket.test.ts index 8ea2ba7..c470168 100644 --- a/tests/adapters/memoryAdapter.test.ts +++ b/tests/buckets/memoryBucket.test.ts @@ -1,8 +1,8 @@ -import { MemoryAdapter } from '../../src' +import { MemoryBucket } from '../../src' -describe('MemoryAdapter', () => { +describe('MemoryBucket', () => { it('write without meta synthesizes storedAt: Date.now()', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() const before = Date.now() await a.write('k', 'v') const after = Date.now() @@ -13,33 +13,33 @@ describe('MemoryAdapter', () => { }) it('write with meta persists storedAt verbatim', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() await a.write('k', 'v', { storedAt: 12345 }) const meta = await a.meta('k') expect(meta?.storedAt).toBe(12345) }) it('read returns undefined for missing keys', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() expect(await a.read('missing')).toBeUndefined() expect(await a.meta('missing')).toBeUndefined() }) it('read returns stored value wrapped', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() await a.write('k', { hello: 'world' }) expect(await a.read('k')).toEqual({ value: { hello: 'world' } }) }) it('read distinguishes a stored undefined from absence', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() await a.write('k', undefined) expect(await a.read('k')).toEqual({ value: undefined }) expect(await a.read('missing')).toBeUndefined() }) it('delete removes only the specified key', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() await a.write('a', 1) await a.write('b', 2) await a.delete('a') @@ -48,7 +48,7 @@ describe('MemoryAdapter', () => { }) it('clear removes everything', async () => { - const a = new MemoryAdapter() + const a = new MemoryBucket() await a.write('a', 1) await a.write('b', 2) await a.clear() diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index 1f93bf5..59ca045 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -1,5 +1,5 @@ -import { Cacheables } from '../src' -import type { IBaseMeta, IStorageAdapter } from '../src' +import { Cacheable } from '../src' +import type { IBaseMeta, IBucket } from '../src' interface WriteCall { key: string @@ -7,7 +7,7 @@ interface WriteCall { meta: IBaseMeta | undefined } -class FakeAdapter implements IStorageAdapter { +class FakeBucket implements IBucket { store = new Map() readCalls = 0 @@ -62,9 +62,9 @@ class FakeAdapter implements IStorageAdapter { describe('cascade behavior', () => { it('L1 hit: does not read L2, probes L2 once, no writes', async () => { const seedMeta: IBaseMeta = { storedAt: Date.now() } - const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) - const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const l1 = new FakeBucket({ key: 'k', value: 'v', meta: seedMeta }) + const l2 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1, l2] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -81,13 +81,13 @@ describe('cascade behavior', () => { it('L1 hit + L2 already has it: no writes anywhere', async () => { const seedMeta: IBaseMeta = { storedAt: 1000 } - const l1 = new FakeAdapter({ key: 'k', value: 'v', meta: seedMeta }) - const l2 = new FakeAdapter({ + const l1 = new FakeBucket({ key: 'k', value: 'v', meta: seedMeta }) + const l2 = new FakeBucket({ key: 'k', value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheable({ buckets: [l1, l2] }) await cache.remember(async () => 'fresh', 'k') @@ -97,9 +97,9 @@ describe('cascade behavior', () => { it('L1 miss + L2 hit: L1 backfilled with L2 meta (storedAt preserved)', async () => { const l2Meta: IBaseMeta = { storedAt: 12345 } - const l1 = new FakeAdapter() - const l2 = new FakeAdapter({ key: 'k', value: 'v2', meta: l2Meta }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const l1 = new FakeBucket() + const l2 = new FakeBucket({ key: 'k', value: 'v2', meta: l2Meta }) + const cache = new Cacheable({ buckets: [l1, l2] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -113,9 +113,9 @@ describe('cascade behavior', () => { }) it('Both miss: resource called once; L1 written without meta; L2 written with L1 meta', async () => { - const l1 = new FakeAdapter() - const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + const l1 = new FakeBucket() + const l2 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1, l2] }) const before = Date.now() let calls = 0 @@ -147,10 +147,10 @@ describe('cascade behavior', () => { it('3 layers, L3 hit: L1 and L2 both filled with L3 meta', async () => { const l3Meta: IBaseMeta = { storedAt: 42 } - const l1 = new FakeAdapter() - const l2 = new FakeAdapter() - const l3 = new FakeAdapter({ key: 'k', value: 'deep', meta: l3Meta }) - const cache = new Cacheables({ adapters: [l1, l2, l3] }) + const l1 = new FakeBucket() + const l2 = new FakeBucket() + const l3 = new FakeBucket({ key: 'k', value: 'deep', meta: l3Meta }) + const cache = new Cacheable({ buckets: [l1, l2, l3] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -164,18 +164,18 @@ describe('cascade behavior', () => { it('max-age across layers: stale L1 with fresh L2 returns L2 value', async () => { const now = Date.now() - const l1 = new FakeAdapter({ + const l1 = new FakeBucket({ key: 'k', value: 'l1-stale', meta: { storedAt: now - 500 }, }) - const l2 = new FakeAdapter({ + const l2 = new FakeBucket({ key: 'k', value: 'l2-fresh', meta: { storedAt: now - 50 }, }) - const cache = new Cacheables({ - adapters: [l1, l2], + const cache = new Cacheable({ + buckets: [l1, l2], policy: 'max-age', maxAge: 100, }) @@ -192,18 +192,18 @@ describe('cascade behavior', () => { it('max-age miss across all layers calls resource', async () => { const now = Date.now() - const l1 = new FakeAdapter({ + const l1 = new FakeBucket({ key: 'k', value: 'l1-stale', meta: { storedAt: now - 500 }, }) - const l2 = new FakeAdapter({ + const l2 = new FakeBucket({ key: 'k', value: 'l2-stale', meta: { storedAt: now - 500 }, }) - const cache = new Cacheables({ - adapters: [l1, l2], + const cache = new Cacheable({ + buckets: [l1, l2], policy: 'max-age', maxAge: 100, }) @@ -218,10 +218,10 @@ describe('cascade behavior', () => { expect(calls).toBe(1) }) - it('delete propagates to all adapters', async () => { - const l1 = new FakeAdapter() - const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + it('delete propagates to all buckets', async () => { + const l1 = new FakeBucket() + const l2 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1, l2] }) await cache.remember(async () => 'v', 'k') await cache.delete('k') @@ -230,10 +230,10 @@ describe('cascade behavior', () => { expect(l2.deleteCalls).toEqual(['k']) }) - it('clear propagates to all adapters', async () => { - const l1 = new FakeAdapter() - const l2 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1, l2] }) + it('clear propagates to all buckets', async () => { + const l1 = new FakeBucket() + const l2 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1, l2] }) await cache.remember(async () => 'v', 'k') await cache.clear() @@ -243,26 +243,26 @@ describe('cascade behavior', () => { }) it('isCached returns true if any layer has the key', async () => { - const l1 = new FakeAdapter() - const l2 = new FakeAdapter({ + const l1 = new FakeBucket() + const l2 = new FakeBucket({ key: 'k', value: 'v', meta: { storedAt: Date.now() }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheable({ buckets: [l1, l2] }) expect(await cache.isCached('k')).toBe(true) expect(await cache.isCached('missing')).toBe(false) }) it('meta returns highest-priority layer meta', async () => { - const l1 = new FakeAdapter() - const l2 = new FakeAdapter({ + const l1 = new FakeBucket() + const l2 = new FakeBucket({ key: 'k', value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheables({ adapters: [l1, l2] }) + const cache = new Cacheable({ buckets: [l1, l2] }) expect(await cache.meta('k')).toEqual({ storedAt: 999 }) @@ -272,8 +272,8 @@ describe('cascade behavior', () => { }) it('caches resources that resolve to undefined', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1] }) + const l1 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1] }) let calls = 0 const resource = async () => { @@ -292,9 +292,9 @@ describe('cascade behavior', () => { }) it('treats meta-says-yes / read-says-undefined as a miss', async () => { - // Simulates a delete that races between #cascadeProbe and adapter.read: + // Simulates a delete that races between #cascadeProbe and bucket.read: // meta still reports the entry; read claims absence. - const l1 = new FakeAdapter({ + const l1 = new FakeBucket({ key: 'k', value: 'stale', meta: { storedAt: Date.now() }, @@ -304,7 +304,7 @@ describe('cascade behavior', () => { return undefined } - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) let calls = 0 const result = await cache.remember(async () => { @@ -319,9 +319,9 @@ describe('cascade behavior', () => { describe('cascade strict error propagation', () => { it('rejects when meta() throws', async () => { - const l1 = new FakeAdapter() + const l1 = new FakeBucket() l1.throwOn.meta = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) await expect(cache.remember(async () => 'v', 'k')).rejects.toThrow( 'meta failed', @@ -329,13 +329,13 @@ describe('cascade strict error propagation', () => { }) it('rejects when read() throws on hit', async () => { - const l1 = new FakeAdapter({ + const l1 = new FakeBucket({ key: 'k', value: 'v', meta: { storedAt: Date.now() }, }) l1.throwOn.read = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'read failed', @@ -343,9 +343,9 @@ describe('cascade strict error propagation', () => { }) it('rejects when write() throws on miss', async () => { - const l1 = new FakeAdapter() + const l1 = new FakeBucket() l1.throwOn.write = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'write failed', @@ -353,17 +353,17 @@ describe('cascade strict error propagation', () => { }) it('rejects when delete() throws', async () => { - const l1 = new FakeAdapter() + const l1 = new FakeBucket() l1.throwOn.delete = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) await expect(cache.delete('k')).rejects.toThrow('delete failed') }) it('rejects when clear() throws', async () => { - const l1 = new FakeAdapter() + const l1 = new FakeBucket() l1.throwOn.clear = true - const cache = new Cacheables({ adapters: [l1] }) + const cache = new Cacheable({ buckets: [l1] }) await expect(cache.clear()).rejects.toThrow('clear failed') }) @@ -373,8 +373,8 @@ describe('concurrent dedup', () => { const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) it('cache-only: deduplicates concurrent miss callers', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ adapters: [l1] }) + const l1 = new FakeBucket() + const cache = new Cacheable({ buckets: [l1] }) let calls = 0 const slow = async () => { @@ -391,9 +391,9 @@ describe('concurrent dedup', () => { }) it('network-only-non-concurrent: deduplicates concurrent callers', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ - adapters: [l1], + const l1 = new FakeBucket() + const cache = new Cacheable({ + buckets: [l1], policy: 'network-only-non-concurrent', }) @@ -412,9 +412,9 @@ describe('concurrent dedup', () => { }) it('max-age: deduplicates concurrent miss callers', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ - adapters: [l1], + const l1 = new FakeBucket() + const cache = new Cacheable({ + buckets: [l1], policy: 'max-age', maxAge: 1000, }) @@ -434,9 +434,9 @@ describe('concurrent dedup', () => { }) it('SWR miss: deduplicates concurrent miss callers', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ - adapters: [l1], + const l1 = new FakeBucket() + const cache = new Cacheable({ + buckets: [l1], policy: 'stale-while-revalidate', }) @@ -455,9 +455,9 @@ describe('concurrent dedup', () => { }) it('network-only: does NOT deduplicate (control)', async () => { - const l1 = new FakeAdapter() - const cache = new Cacheables({ - adapters: [l1], + const l1 = new FakeBucket() + const cache = new Cacheable({ + buckets: [l1], policy: 'network-only', }) diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index e5c599e..416f391 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -1,4 +1,4 @@ -import { Cacheables, MemoryAdapter } from '../src' +import { Cacheable, MemoryBucket } from '../src' const mockedApiRequest = (value: T, duration = 0): Promise => new Promise((resolve) => { @@ -15,8 +15,8 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Fetch Policies', () => { it('cache-only', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'cache-only', }) @@ -32,8 +32,8 @@ describe('Fetch Policies', () => { }) it('network-only-non-concurrent', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'network-only-non-concurrent', }) @@ -56,8 +56,8 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 0, 0, 3]) }) it('network-only', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'network-only', }) @@ -75,8 +75,8 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 1, 2]) }) it('max-age', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 100, }) @@ -94,8 +94,8 @@ describe('Fetch Policies', () => { expect([a, b, c, d]).toEqual([0, 0, 2, 2]) }) it('stale-while-revalidate', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'stale-while-revalidate', }) @@ -120,8 +120,8 @@ describe('Fetch Policies', () => { }) it('stale-while-revalidate with maxAge', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'stale-while-revalidate', maxAge: 200, }) diff --git a/tests/index.test.ts b/tests/index.test.ts index 3ae62e5..75dbe0c 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,4 +1,4 @@ -import { Cacheables, MemoryAdapter } from '../src' +import { Cacheable, MemoryBucket } from '../src' const errorMessage = 'This is an error message.' @@ -22,7 +22,7 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') expect(await cache.isCached('a')).toEqual(true) @@ -30,7 +30,7 @@ describe('Cache operations', () => { }) it('Stores multiple caches', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) const valueA = 10 const valueB = 20 @@ -50,7 +50,7 @@ describe('Cache operations', () => { }) it('Deletes values', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -61,7 +61,7 @@ describe('Cache operations', () => { }) it('Clears the cache', async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -72,13 +72,13 @@ describe('Cache operations', () => { }) it('Creates proper keys', () => { - const key = Cacheables.key('aaa', 'bbb', 'ccc', 'ddd', 10, 20) + const key = Cacheable.key('aaa', 'bbb', 'ccc', 'ddd', 10, 20) expect(key).toEqual('aaa:bbb:ccc:ddd:10:20') }) it('Returns correctly if disabled', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], enabled: false, }) @@ -94,8 +94,8 @@ describe('Cache operations', () => { it('Logs correctly', async () => { console.log = jest.fn() - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], log: true, enabled: false, }) @@ -116,9 +116,9 @@ describe('Cache operations', () => { expect(console.log).lastCalledWith('Cacheable "a": hits: 2') }) - it('Throws when constructed without adapters', () => { - expect(() => new Cacheables({ adapters: [] })).toThrow( - 'At least one storage adapter is required', + it('Throws when constructed without buckets', () => { + expect(() => new Cacheable({ buckets: [] })).toThrow( + 'At least one bucket is required', ) }) @@ -130,8 +130,8 @@ describe('Cache operations', () => { * Assuming the time starts at 0 */ it('Handles race conditions correctly', async () => { - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 100, }) @@ -159,8 +159,8 @@ describe('Cache operations', () => { it('Handles multiple calls correctly', async () => { console.log = jest.fn() - const cache = new Cacheables({ - adapters: [new MemoryAdapter()], + const cache = new Cacheable({ + buckets: [new MemoryBucket()], log: true, policy: 'max-age', maxAge: 100, @@ -192,7 +192,7 @@ describe('Cache operations', () => { }) it("Doesn't interfere with error handling", async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) const rejecting = () => { return cache.remember(() => mockedApiRequest(0, 10, true), 'a') } @@ -200,7 +200,7 @@ describe('Cache operations', () => { }) it("Doesn't cache rejected value", async () => { - const cache = new Cacheables({ adapters: [new MemoryAdapter()] }) + const cache = new Cacheable({ buckets: [new MemoryBucket()] }) let errNo = 1 const rejecting = () => { return cache.remember(() => Promise.reject(errNo++), 'a') diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts index e3f9f50..9f4155d 100644 --- a/tests/namespace.test.ts +++ b/tests/namespace.test.ts @@ -1,10 +1,10 @@ -import { Cacheables, MemoryAdapter } from '../src' +import { Cacheable, MemoryBucket } from '../src' describe('namespace', () => { - it('isolates entries between two instances sharing one adapter', async () => { - const adapter = new MemoryAdapter() - const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) - const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + it('isolates entries between two instances sharing one bucket', async () => { + const bucket = new MemoryBucket() + const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) + const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') @@ -13,20 +13,20 @@ describe('namespace', () => { expect(await b.remember(async () => 'fresh', 'k')).toBe('B') }) - it('writes adapter keys with namespace prefix', async () => { - const adapter = new MemoryAdapter() - const a = new Cacheables({ adapters: [adapter], namespace: 'tenant1' }) + it('writes bucket keys with namespace prefix', async () => { + const bucket = new MemoryBucket() + const a = new Cacheable({ buckets: [bucket], namespace: 'tenant1' }) await a.remember(async () => 'v', 'user:42') - expect(await adapter.read('tenant1:user:42')).toEqual({ value: 'v' }) - expect(await adapter.read('user:42')).toBeUndefined() + expect(await bucket.read('tenant1:user:42')).toEqual({ value: 'v' }) + expect(await bucket.read('user:42')).toBeUndefined() }) it('delete on one namespace does not affect another', async () => { - const adapter = new MemoryAdapter() - const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) - const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + const bucket = new MemoryBucket() + const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) + const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') @@ -37,10 +37,10 @@ describe('namespace', () => { expect(await b.isCached('k')).toBe(true) }) - it('clear from one namespace wipes shared adapter (documented caveat)', async () => { - const adapter = new MemoryAdapter() - const a = new Cacheables({ adapters: [adapter], namespace: 'a' }) - const b = new Cacheables({ adapters: [adapter], namespace: 'b' }) + it('clear from one namespace wipes shared bucket (documented caveat)', async () => { + const bucket = new MemoryBucket() + const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) + const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') @@ -52,9 +52,9 @@ describe('namespace', () => { }) it('omitting namespace stores keys verbatim', async () => { - const adapter = new MemoryAdapter() - const cache = new Cacheables({ adapters: [adapter] }) + const bucket = new MemoryBucket() + const cache = new Cacheable({ buckets: [bucket] }) await cache.remember(async () => 'v', 'k') - expect(await adapter.read('k')).toEqual({ value: 'v' }) + expect(await bucket.read('k')).toEqual({ value: 'v' }) }) }) From 67ea471ec639d2b69562a218ce4e15824616e313 Mon Sep 17 00:00:00 2001 From: Grischa Erbe Date: Tue, 28 Apr 2026 15:10:28 +0200 Subject: [PATCH 06/23] =?UTF-8?q?Rename=20migration=20section=20to=20v2=20?= =?UTF-8?q?=E2=86=92=20v3=20in=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The latest published version on npm is v2.0.0; the breaking changes land in v3, not v4. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c5bd19..4c82d81 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') * [Typed metadata (`TMeta`)](#typed-metadata-tmeta) * [Namespacing](#namespacing) * [Cache Policies](#cache-policies) -* [Migrating from v3 → v4](#migrating-from-v3--v4) +* [Migrating from v2 → v3](#migrating-from-v2--v3) * [License](#license) ## Installation @@ -117,7 +117,7 @@ Returns the meta from the highest-priority layer that has the key — useful for ### `Cacheables.key(...args): string` -Joins the parts with `:`. Identical to v3. +Joins the parts with `:`. Identical to v2. ```ts Cacheables.key('user', 42) // 'user:42' @@ -242,7 +242,7 @@ Two instances can share an adapter without colliding. `delete` and `isCached` re new Cacheables({ adapters: [new MemoryAdapter()], namespace: 'app', policy: 'max-age', maxAge: 1_000 }) ``` -## Migrating from v3 → v4 +## Migrating from v2 → v3 Breaking changes: From 55431ac1146ec1769f5f27854b256bd7b2aed8a0 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:35:00 +0200 Subject: [PATCH 07/23] Remove the "enabled" option from Cacheable (#15) Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 2 +- src/Cacheable.ts | 7 ------- src/Logger.ts | 5 ----- src/types.ts | 4 ---- tests/index.test.ts | 21 --------------------- 5 files changed, 1 insertion(+), 38 deletions(-) diff --git a/README.md b/README.md index f15b55d..e6b2d8b 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,6 @@ await getWeather() // hit — cached type CacheableOptions = { buckets: IBucket[] // REQUIRED, L1 first namespace: string // REQUIRED — bucket keys become `${namespace}:${key}` - enabled?: boolean // default: true log?: boolean // default: false logTiming?: boolean // default: false } & ( @@ -256,6 +255,7 @@ Breaking changes: - `clear()` returns `Promise` (was `void`). Add `await`. - `isCached(key)` returns `Promise` (was `boolean`). Add `await`. - `keys()` is **removed**. Enumerating heterogeneous async layers (some non-enumerable, like CDNs) doesn't have a single sensible semantic. +- The `enabled` option is **removed**. If you need to bypass caching, call `resource()` directly instead of `cache.remember()`. - `Cacheable` is now generic in `TMeta`. Plain `new Cacheable({ buckets, namespace })` defaults to `Cacheable` and is source-compatible at the type level. - New constructor options: `buckets` (required) and `namespace` (required). - Any throw from any bucket rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom buckets. diff --git a/src/Cacheable.ts b/src/Cacheable.ts index c46993f..296c567 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -7,7 +7,6 @@ import type { } from './types' export class Cacheable { - enabled: boolean log: boolean logTiming: boolean @@ -24,7 +23,6 @@ export class Cacheable { } this.#buckets = options.buckets this.#namespace = options.namespace - this.enabled = options.enabled ?? true this.log = options.log ?? false this.logTiming = options.logTiming ?? false this.#policy = options.policy ?? 'cache-only' @@ -68,11 +66,6 @@ export class Cacheable { } async remember(resource: () => Promise, key: string): Promise { - if (!this.enabled) { - if (this.log) Logger.logDisabled() - return resource() - } - const { logTiming, log } = this const logId = Logger.getLogId(key) if (logTiming) Logger.logTime(logId) diff --git a/src/Logger.ts b/src/Logger.ts index e280dc6..3d1c4bb 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -13,11 +13,6 @@ export class Logger { console.timeEnd(key) } - static logDisabled(): void { - // eslint-disable-next-line no-console - console.log('CACHE: Caching disabled') - } - static logStats(key: string, hits: number): void { // eslint-disable-next-line no-console console.log(`Cacheable "${key}": hits: ${hits}`) diff --git a/src/types.ts b/src/types.ts index 05cb81a..3c9bbef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,10 +36,6 @@ export interface IBucket { } type CacheOptionsBase = { - /** - * Enables caching. - */ - enabled?: boolean /** * Enable/disable logging of cache hits. */ diff --git a/tests/index.test.ts b/tests/index.test.ts index 4b0f4f6..ec3f13e 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -76,22 +76,6 @@ describe('Cache operations', () => { expect(key).toEqual('aaa:bbb:ccc:ddd:10:20') }) - it('Returns correctly if disabled', async () => { - const cache = new Cacheable({ - buckets: [new MemoryBucket()], - namespace: 'test', - enabled: false, - }) - - const value = 10 - const uncachedValue = await cache.remember( - () => mockedApiRequest(value), - 'a', - ) - - expect(uncachedValue).toEqual(value) - }) - it('Logs correctly', async () => { console.log = jest.fn() @@ -99,15 +83,10 @@ describe('Cache operations', () => { buckets: [new MemoryBucket()], namespace: 'test', log: true, - enabled: false, }) const cachedRequest = () => cache.remember(() => mockedApiRequest(1), 'a') - await cachedRequest() - expect(console.log).lastCalledWith('CACHE: Caching disabled') - cache.enabled = true - await cachedRequest() expect(console.log).lastCalledWith('Cacheable "a": hits: 0') From e4c629d6d2e3c61142874e4bea816235c9703b7b Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:42:43 +0200 Subject: [PATCH 08/23] Replace log/logTiming flags with a pluggable ILogger (#16) Introduces an ILogger interface and a default ConsoleLogger implementation, replacing the boolean log and logTiming options with a single logger option. Consumers can now route cache messages into any logging stack via a one-line adapter; omitting the logger keeps the engine silent. Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++-- src/Cacheable.ts | 19 +++++++------- src/ConsoleLogger.ts | 8 ++++++ src/Logger.ts | 20 --------------- src/index.ts | 2 ++ src/types.ts | 20 ++++++++++----- tests/index.test.ts | 6 ++--- 7 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 src/ConsoleLogger.ts delete mode 100644 src/Logger.ts diff --git a/README.md b/README.md index e6b2d8b..bb12822 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') * [Writing your own bucket](#writing-your-own-bucket) * [Cascade behavior](#cascade-behavior) * [Typed metadata (`TMeta`)](#typed-metadata-tmeta) +* [Logger](#logger) + * [`ILogger` contract](#ilogger-contract) + * [Built-in `ConsoleLogger`](#built-in-consolelogger) + * [Writing your own logger](#writing-your-own-logger) * [Namespacing](#namespacing) * [Cache Policies](#cache-policies) * [Migrating from v2 → v3](#migrating-from-v2--v3) @@ -80,8 +84,7 @@ await getWeather() // hit — cached type CacheableOptions = { buckets: IBucket[] // REQUIRED, L1 first namespace: string // REQUIRED — bucket keys become `${namespace}:${key}` - log?: boolean // default: false - logTiming?: boolean // default: false + logger?: ILogger // default: undefined (no logging) } & ( | { policy?: 'cache-only' } // default | { policy: 'network-only' } @@ -216,6 +219,59 @@ const meta = await cache.meta('user:42') // typed as ETagMeta | undefined Every bucket passed to the constructor must satisfy `IBucket`, and the TypeScript compiler enforces it. The built-in `MemoryBucket` only implements `IBucket`, so it can't be used in a `Cacheable` instance with a custom `TMeta` — write a custom bucket (or wrap `MemoryBucket`) when you need extended metadata. +## Logger + +Pass a `logger` to surface what the engine is doing. Without one, the engine is silent. + +### `ILogger` contract + +```ts +interface ILogger { + log(message: string): void +} +``` + +When a `logger` is configured, every `cache.remember(...)` call emits two messages — timing, then hit count: + +``` +Cacheable "weather": 12ms +Cacheable "weather": hits: 1 +``` + +### Built-in `ConsoleLogger` + +Forwards each message to `console.log`. Useful as a default during development. + +```ts +import { Cacheable, ConsoleLogger, MemoryBucket } from 'cacheables' + +const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'app', + logger: new ConsoleLogger(), +}) +``` + +### Writing your own logger + +Any object with a `log(message: string)` method satisfies `ILogger`, so wrapping an existing logger is a one-liner: + +```ts +import pino from 'pino' +import { Cacheable, MemoryBucket, type ILogger } from 'cacheables' + +const pinoLogger = pino() +const logger: ILogger = { log: (m) => pinoLogger.info(m) } + +const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'app', + logger, +}) +``` + +The `logger` field on a `Cacheable` instance is mutable — assign a new logger (or `undefined`) at runtime to flip logging on or off. + ## Namespacing `namespace` is required: every bucket call sees keys prefixed with `${namespace}:`. This isolates instances that share the same bucket, so you must pick a namespace at construction time even when only one instance uses a bucket. @@ -259,6 +315,7 @@ Breaking changes: - `Cacheable` is now generic in `TMeta`. Plain `new Cacheable({ buckets, namespace })` defaults to `Cacheable` and is source-compatible at the type level. - New constructor options: `buckets` (required) and `namespace` (required). - Any throw from any bucket rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom buckets. +- The `log` and `logTiming` boolean options have been replaced by a single `logger?: ILogger` option. Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time`/`timeEnd`. ## License diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 296c567..74fbdd1 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -1,14 +1,13 @@ -import { Logger } from './Logger' import type { CacheableOptions, IBaseMeta, IBucket, + ILogger, Policy, } from './types' export class Cacheable { - log: boolean - logTiming: boolean + logger: ILogger | undefined #policy: Policy #maxAge: number | undefined @@ -23,8 +22,7 @@ export class Cacheable { } this.#buckets = options.buckets this.#namespace = options.namespace - this.log = options.log ?? false - this.logTiming = options.logTiming ?? false + this.logger = options.logger this.#policy = options.policy ?? 'cache-only' this.#maxAge = options.policy === 'max-age' || @@ -66,9 +64,8 @@ export class Cacheable { } async remember(resource: () => Promise, key: string): Promise { - const { logTiming, log } = this - const logId = Logger.getLogId(key) - if (logTiming) Logger.logTime(logId) + const { logger } = this + const start = logger ? Date.now() : 0 const fullKey = this.#fullKey(key) const { value, hit } = await this.#runPolicy(resource, fullKey) @@ -80,8 +77,10 @@ export class Cacheable { this.#hits.set(fullKey, 0) } - if (logTiming) Logger.logTimeEnd(logId) - if (log) Logger.logStats(key, this.#hits.get(fullKey) ?? 0) + if (logger) { + logger.log(`Cacheable "${key}": ${Date.now() - start}ms`) + logger.log(`Cacheable "${key}": hits: ${this.#hits.get(fullKey) ?? 0}`) + } return value } diff --git a/src/ConsoleLogger.ts b/src/ConsoleLogger.ts new file mode 100644 index 0000000..f19a1b3 --- /dev/null +++ b/src/ConsoleLogger.ts @@ -0,0 +1,8 @@ +import type { ILogger } from './types' + +export class ConsoleLogger implements ILogger { + log(message: string): void { + // eslint-disable-next-line no-console + console.log(message) + } +} diff --git a/src/Logger.ts b/src/Logger.ts deleted file mode 100644 index 3d1c4bb..0000000 --- a/src/Logger.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class Logger { - static getLogId(key: string): string { - return key + '---' + Math.random().toString(36).substr(2, 9) - } - - static logTime(key: string): void { - // eslint-disable-next-line no-console - console.time(key) - } - - static logTimeEnd(key: string): void { - // eslint-disable-next-line no-console - console.timeEnd(key) - } - - static logStats(key: string, hits: number): void { - // eslint-disable-next-line no-console - console.log(`Cacheable "${key}": hits: ${hits}`) - } -} diff --git a/src/index.ts b/src/index.ts index d36d055..f36a879 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ export { Cacheable } from './Cacheable' +export { ConsoleLogger } from './ConsoleLogger' export { MemoryBucket } from './buckets/MemoryBucket' export type { CacheOptions, CacheableOptions, IBaseMeta, IBucket, + ILogger, } from './types' diff --git a/src/types.ts b/src/types.ts index 3c9bbef..c4875eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,15 +35,23 @@ export interface IBucket { clear(): Promise } +/** + * Logger contract. Implement to route cache messages into your own + * logging stack. When a `Cacheable` is constructed with a `logger`, + * the engine emits a timing message and a hit-count message on every + * `remember()` invocation. When no logger is provided, the engine is + * silent. + */ +export interface ILogger { + log(message: string): void +} + type CacheOptionsBase = { /** - * Enable/disable logging of cache hits. - */ - log?: boolean - /** - * Enable/disable timings. + * Optional logger. When provided, the engine emits cache messages + * via `logger.log(...)`. Omit to keep the engine silent. */ - logTiming?: boolean + logger?: ILogger } type CacheOnlyPolicy = { diff --git a/tests/index.test.ts b/tests/index.test.ts index ec3f13e..1d11638 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,4 +1,4 @@ -import { Cacheable, MemoryBucket } from '../src' +import { Cacheable, ConsoleLogger, MemoryBucket } from '../src' const errorMessage = 'This is an error message.' @@ -82,7 +82,7 @@ describe('Cache operations', () => { const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test', - log: true, + logger: new ConsoleLogger(), }) const cachedRequest = () => cache.remember(() => mockedApiRequest(1), 'a') @@ -144,7 +144,7 @@ describe('Cache operations', () => { const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test', - log: true, + logger: new ConsoleLogger(), policy: 'max-age', maxAge: 100, }) From 9730cd4d29813cc6dd271f6a97c083999439041d Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:09:12 +0200 Subject: [PATCH 09/23] =?UTF-8?q?Revise=20the=20v2=20=E2=86=92=20v3=20migr?= =?UTF-8?q?ation=20section=20in=20the=20README=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops two inaccurate claims (`IStorageAdapter`/`MemoryAdapter` and `CacheablesOptions` renames never existed in v2), adds the missing high-impact migration steps (`cacheable()` → `remember()`, per-call options removed, `Cacheables.key` → `Cacheable.key`, `namespace` required), and leads with a v2/v3 before-after example. Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index bb12822..924209b 100644 --- a/README.md +++ b/README.md @@ -300,22 +300,55 @@ new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app', policy: 'max-ag ## Migrating from v2 → v3 +v2 was an in-memory cache with per-call options and a synchronous surface. v3 introduces pluggable storage (buckets), required namespacing, an instance-level cache policy, and a fully async API. + +Before / after: + +```ts +// v2 +import { Cacheables } from 'cacheables' + +const cache = new Cacheables({ log: true, logTiming: true }) + +await cache.cacheable(() => fetch(url), 'weather', { + cachePolicy: 'max-age', + maxAge: 5_000, +}) +``` + +```ts +// v3 +import { Cacheable, MemoryBucket, ConsoleLogger } from 'cacheables' + +const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'weather', + policy: 'max-age', + maxAge: 5_000, + logger: new ConsoleLogger(), +}) + +await cache.remember(() => fetch(url), 'weather') +``` + Breaking changes: -- The class `Cacheables` has been renamed to `Cacheable`. Update imports and `new Cacheables(...)` call sites. -- The `IStorageAdapter` interface has been renamed to `IBucket`, and `MemoryAdapter` to `MemoryBucket`. The contract is unchanged. -- `buckets` is now a **required** constructor option (replaces the v2-style implicit memory store). `new Cacheable()` no longer compiles. Pass at least one bucket, e.g. `new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' })`. -- The constructor's empty-buckets error message is now `'At least one bucket is required'`. -- The `CacheablesOptions` type has been renamed to `CacheableOptions`. -- `delete(key)` returns `Promise` (was `void`). Add `await`. -- `clear()` returns `Promise` (was `void`). Add `await`. -- `isCached(key)` returns `Promise` (was `boolean`). Add `await`. -- `keys()` is **removed**. Enumerating heterogeneous async layers (some non-enumerable, like CDNs) doesn't have a single sensible semantic. -- The `enabled` option is **removed**. If you need to bypass caching, call `resource()` directly instead of `cache.remember()`. -- `Cacheable` is now generic in `TMeta`. Plain `new Cacheable({ buckets, namespace })` defaults to `Cacheable` and is source-compatible at the type level. -- New constructor options: `buckets` (required) and `namespace` (required). -- Any throw from any bucket rejects `remember()`. Previously the in-memory store couldn't fail; this is new strict-error surface for users with custom buckets. -- The `log` and `logTiming` boolean options have been replaced by a single `logger?: ILogger` option. Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time`/`timeEnd`. +- **Class renamed** `Cacheables` → `Cacheable`. Update imports and `new Cacheables(...)` call sites. The static helper moves with it: `Cacheables.key(...)` → `Cacheable.key(...)` (behaviour unchanged). +- **Method renamed** `cache.cacheable(...)` → `cache.remember(...)`. +- **Cache policy moved to the constructor.** v2 took `cachePolicy` and `maxAge` as a per-call third argument; v3 has no per-call options. Pass `policy` (and `maxAge` where required) once on `new Cacheable({ ... })`. The field is `policy`, not `cachePolicy`. A single instance now serves a single policy — split into multiple instances if you previously mixed policies on one cache. +- **`buckets` is required** (replaces v2's implicit in-memory store). `new Cacheable()` no longer compiles. `new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' })` reproduces the v2 default. +- **`namespace` is required.** Every bucket call sees keys prefixed with `${namespace}:`. Pick one even if only one instance writes to the bucket. +- **`enabled` option removed.** If you need to bypass the cache, call `resource()` directly instead of `cache.remember(...)`. +- **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. +- **`delete`, `clear`, `isCached` are async.** They now return `Promise` / `Promise` — add `await`. +- **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time` / `timeEnd`. +- **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `namespace`, `policy`, and `logger`). +- **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. + +What's new: + +- **Multilayer storage.** Pass several buckets to compose tiers (e.g. `[memory, filesystem]`); reads cascade L1 → Ln and back-fill missing layers on every hit. +- **Typed sidecar metadata.** `Cacheable` is generic; bucket implementations can persist fields like `etag` or `ttl` and `cache.meta(key)` returns them typed. Plain `new Cacheable({ buckets, namespace })` defaults to `Cacheable` and needs no type changes. ## License From 034d50da36aa5cada552195f0addfc14f3b5e276 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:24:31 +0200 Subject: [PATCH 10/23] Add PR CI checks: typecheck, build, lint (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add CI workflow with typecheck, build, and lint checks Co-Authored-By: Claude Opus 4.7 (1M context) * Rename prepublish to prepublishOnly so it doesn't run on npm ci The deprecated prepublish lifecycle is aliased to prepare in modern npm, so it ran during CI's npm ci and triggered the full test suite — masking the actual job step. prepublishOnly runs only on npm publish. Co-Authored-By: Claude Opus 4.7 (1M context) * Remove prepublishOnly script Co-Authored-By: Claude Opus 4.7 (1M context) * Add test job to CI workflow Co-Authored-By: Claude Opus 4.7 (1M context) * Add prettier check to lint CI job Adds a format:check script (prettier --check .) and a .prettierignore so the existing eslint-plugin-prettier coverage of src/**/*.ts is extended to README, configs, and tests. Reformats the few files that were drifting from the project prettier config. Co-Authored-By: Claude Opus 4.7 (1M context) * Stop sampling the maxAge boundary in fetch-policy tests The max-age and stale-while-revalidate-with-maxAge tests waited for exactly maxAge ms after caching, leaving the age check sitting on its boundary (`age <= maxAge` / `age > maxAge`). Tiny event-loop jitter flipped the result, so the tests passed locally but failed in CI. Wait long enough past maxAge that boundary jitter cannot affect the outcome. Verified 10/10 under CPU contention. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 52 ++++++++++++++++++ .prettierignore | 4 ++ README.md | 103 +++++++++++++++++++++--------------- package.json | 3 +- tests/fetchPolicies.test.ts | 20 +++---- tests/index.test.ts | 30 ++++++++--- tests/tsconfig.json | 2 +- 7 files changed, 153 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .prettierignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0d7e398 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + pull_request: + push: + branches: [master, next] + +jobs: + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run typecheck + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npm run format:check + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..379b8a5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +dist +package-lock.json +.context diff --git a/README.md b/README.md index 924209b..b8403ad 100644 --- a/README.md +++ b/README.md @@ -23,30 +23,30 @@ const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' }) cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` -* [Installation](#installation) -* [Usage](#usage) -* [API](#api) - * [new Cacheable(options)](#new-cacheableoptions-cacheabletmeta) - * [cache.remember(resource, key)](#cacherememberresource-key-promiset) - * [cache.delete(key)](#cachedeletekey-promisevoid) - * [cache.clear()](#cacheclear-promisevoid) - * [cache.isCached(key)](#cacheiscachedkey-promiseboolean) - * [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - * [Cacheable.key(...args)](#cacheablekeyargs-string) -* [Buckets](#buckets) - * [`IBucket` contract](#ibucket-contract) - * [Built-in `MemoryBucket`](#built-in-memorybucket) - * [Writing your own bucket](#writing-your-own-bucket) - * [Cascade behavior](#cascade-behavior) - * [Typed metadata (`TMeta`)](#typed-metadata-tmeta) -* [Logger](#logger) - * [`ILogger` contract](#ilogger-contract) - * [Built-in `ConsoleLogger`](#built-in-consolelogger) - * [Writing your own logger](#writing-your-own-logger) -* [Namespacing](#namespacing) -* [Cache Policies](#cache-policies) -* [Migrating from v2 → v3](#migrating-from-v2--v3) -* [License](#license) +- [Installation](#installation) +- [Usage](#usage) +- [API](#api) + - [new Cacheable(options)](#new-cacheableoptions-cacheabletmeta) + - [cache.remember(resource, key)](#cacherememberresource-key-promiset) + - [cache.delete(key)](#cachedeletekey-promisevoid) + - [cache.clear()](#cacheclear-promisevoid) + - [cache.isCached(key)](#cacheiscachedkey-promiseboolean) + - [cache.meta(key)](#cachemetakey-promisetmeta--undefined) + - [Cacheable.key(...args)](#cacheablekeyargs-string) +- [Buckets](#buckets) + - [`IBucket` contract](#ibucket-contract) + - [Built-in `MemoryBucket`](#built-in-memorybucket) + - [Writing your own bucket](#writing-your-own-bucket) + - [Cascade behavior](#cascade-behavior) + - [Typed metadata (`TMeta`)](#typed-metadata-tmeta) +- [Logger](#logger) + - [`ILogger` contract](#ilogger-contract) + - [Built-in `ConsoleLogger`](#built-in-consolelogger) + - [Writing your own logger](#writing-your-own-logger) +- [Namespacing](#namespacing) +- [Cache Policies](#cache-policies) +- [Migrating from v2 → v3](#migrating-from-v2--v3) +- [License](#license) ## Installation @@ -82,15 +82,15 @@ await getWeather() // hit — cached ```ts type CacheableOptions = { - buckets: IBucket[] // REQUIRED, L1 first - namespace: string // REQUIRED — bucket keys become `${namespace}:${key}` - logger?: ILogger // default: undefined (no logging) + buckets: IBucket[] // REQUIRED, L1 first + namespace: string // REQUIRED — bucket keys become `${namespace}:${key}` + logger?: ILogger // default: undefined (no logging) } & ( - | { policy?: 'cache-only' } // default + | { policy?: 'cache-only' } // default | { policy: 'network-only' } | { policy: 'network-only-non-concurrent' } - | { policy: 'max-age', maxAge: number } - | { policy: 'stale-while-revalidate', maxAge?: number } + | { policy: 'max-age'; maxAge: number } + | { policy: 'stale-while-revalidate'; maxAge?: number } ) ``` @@ -171,11 +171,21 @@ Implement `IBucket`. The contract is small enough that filesystem, Redis, Indexe import type { IBucket, IBaseMeta } from 'cacheables' class FileSystemBucket implements IBucket { - async read(key: string): Promise<{ value: T } | undefined> { /* … */ } - async write(key: string, value: T, meta?: IBaseMeta): Promise { /* … */ } - async meta(key: string): Promise { /* … */ } - async delete(key: string): Promise { /* … */ } - async clear(): Promise { /* … */ } + async read(key: string): Promise<{ value: T } | undefined> { + /* … */ + } + async write(key: string, value: T, meta?: IBaseMeta): Promise { + /* … */ + } + async meta(key: string): Promise { + /* … */ + } + async delete(key: string): Promise { + /* … */ + } + async clear(): Promise { + /* … */ + } } ``` @@ -282,20 +292,25 @@ const tenantA = new Cacheable({ buckets: [bucket], namespace: 'tenant-a' }) const tenantB = new Cacheable({ buckets: [bucket], namespace: 'tenant-b' }) ``` -Two instances can share a bucket without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean *everything*. +Two instances can share a bucket without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean _everything_. ## Cache Policies -| Policy | Behaviour | -|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `cache-only` *(default)* | Return any cached value; on miss, call `resource()`. Concurrent miss callers share one fetch. | -| `network-only` | Always call `resource()`; concurrent calls each get their own. | -| `network-only-non-concurrent` | Always call `resource()`, but concurrent calls share one in-flight request. | -| `max-age` *(maxAge required)* | Return cache if `Date.now() - storedAt <= maxAge`, otherwise fetch. | -| `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | +| Policy | Behaviour | +| ----------------------------- | ------------------------------------------------------------------------------------------------------ | +| `cache-only` _(default)_ | Return any cached value; on miss, call `resource()`. Concurrent miss callers share one fetch. | +| `network-only` | Always call `resource()`; concurrent calls each get their own. | +| `network-only-non-concurrent` | Always call `resource()`, but concurrent calls share one in-flight request. | +| `max-age` _(maxAge required)_ | Return cache if `Date.now() - storedAt <= maxAge`, otherwise fetch. | +| `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | ```ts -new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app', policy: 'max-age', maxAge: 1_000 }) +new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'app', + policy: 'max-age', + maxAge: 1_000, +}) ``` ## Migrating from v2 → v3 diff --git a/package.json b/package.json index b3d4629..e54555c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test:watch": "jest --watch", "typecheck": "tsc --noEmit", "eslint": "eslint --fix src/**/*.ts", - "prepublish": "npm run eslint && npm run typecheck && npm test && npm run build", + "lint": "eslint src/**/*.ts", + "format:check": "prettier --check .", "build": "rimraf dist && tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json && tsx ./fixup-packages.ts" }, "repository": { diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index 9be470a..ad2ffed 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -90,7 +90,8 @@ describe('Fetch Policies', () => { const a = await ma(0) const b = await ma(1) - await wait(100) + // Wait well past maxAge so the staleness check isn't on the boundary. + await wait(300) const c = await ma(2) const d = await ma(3) @@ -134,22 +135,23 @@ describe('Fetch Policies', () => { const swr = (v: any) => cache.remember(() => mockedApiRequest(v, 50), 'key') - // Preheat cache, takes ~50ms + // Preheat cache, fetcher takes ~50ms. await swr(0) - await wait(100) - - // ~150ms on the clock, maxAge not reached + // Still well within maxAge. + await wait(50) const a = await swr(1) - await wait(100) + // Wait clearly past maxAge so the entry is unambiguously stale. + await wait(400) - // ~250ms on the clock, maxAge reached, cache updates silently + // Stale read: returns the cached value and kicks off a background refetch. const b = await swr(2) - await wait(100) + // Give the background refetch (~50ms) plenty of time to land. + await wait(150) - // ~350ms on the clock, cache should be updated silently with value `2` + // Cache has been silently updated to 2 and is fresh again. const c = await swr(3) expect([a, b, c]).toEqual([0, 0, 2]) diff --git a/tests/index.test.ts b/tests/index.test.ts index 1d11638..04093cd 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -22,7 +22,10 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') expect(await cache.isCached('a')).toEqual(true) @@ -30,7 +33,10 @@ describe('Cache operations', () => { }) it('Stores multiple caches', async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) const valueA = 10 const valueB = 20 @@ -50,7 +56,10 @@ describe('Cache operations', () => { }) it('Deletes values', async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -61,7 +70,10 @@ describe('Cache operations', () => { }) it('Clears the cache', async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') @@ -175,7 +187,10 @@ describe('Cache operations', () => { }) it("Doesn't interfere with error handling", async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) const rejecting = () => { return cache.remember(() => mockedApiRequest(0, 10, true), 'a') } @@ -183,7 +198,10 @@ describe('Cache operations', () => { }) it("Doesn't cache rejected value", async () => { - const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'test' }) + const cache = new Cacheable({ + buckets: [new MemoryBucket()], + namespace: 'test', + }) let errNo = 1 const rejecting = () => { return cache.remember(() => Promise.reject(errNo++), 'a') diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 8777942..e0580c5 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -5,4 +5,4 @@ "rootDir": ".." }, "include": ["."] -} \ No newline at end of file +} From 8e639b72e236cd6e0babc6b2f1e38629f6cd2343 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:32:28 +0200 Subject: [PATCH 11/23] Remove isCached from the public API (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existence checks are subsumed by `cache.meta(key)`, which returns `undefined` when no layer has the key. Tests and docs are updated accordingly, and the v2 → v3 migration note now points users at `meta()` as the replacement. Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 10 +++------- src/Cacheable.ts | 6 ------ tests/cascade.test.ts | 13 ------------- tests/index.test.ts | 14 +++++++------- tests/namespace.test.ts | 8 ++++---- 5 files changed, 14 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index b8403ad..2b406cf 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [cache.remember(resource, key)](#cacherememberresource-key-promiset) - [cache.delete(key)](#cachedeletekey-promisevoid) - [cache.clear()](#cacheclear-promisevoid) - - [cache.isCached(key)](#cacheiscachedkey-promiseboolean) - [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - [Cacheable.key(...args)](#cacheablekeyargs-string) - [Buckets](#buckets) @@ -108,10 +107,6 @@ Deletes the entry from every bucket. Clears every bucket and the in-flight registry. -### `cache.isCached(key): Promise` - -`true` if any layer reports the key (existence-only — does not consider freshness). - ### `cache.meta(key): Promise` Returns the meta from the highest-priority layer that has the key — useful for inspecting sidecar fields like `etag`, `ttl`, etc. @@ -292,7 +287,7 @@ const tenantA = new Cacheable({ buckets: [bucket], namespace: 'tenant-a' }) const tenantB = new Cacheable({ buckets: [bucket], namespace: 'tenant-b' }) ``` -Two instances can share a bucket without colliding. `delete` and `isCached` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean _everything_. +Two instances can share a bucket without colliding. `delete` and `meta` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean _everything_. ## Cache Policies @@ -355,7 +350,8 @@ Breaking changes: - **`namespace` is required.** Every bucket call sees keys prefixed with `${namespace}:`. Pick one even if only one instance writes to the bucket. - **`enabled` option removed.** If you need to bypass the cache, call `resource()` directly instead of `cache.remember(...)`. - **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. -- **`delete`, `clear`, `isCached` are async.** They now return `Promise` / `Promise` — add `await`. +- **`delete` and `clear` are async.** They now return `Promise` — add `await`. +- **`isCached` removed.** Use `cache.meta(key)` instead — it returns `undefined` when the key is absent and the meta object otherwise. - **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time` / `timeEnd`. - **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `namespace`, `policy`, and `logger`). - **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 74fbdd1..19cf1d5 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -51,12 +51,6 @@ export class Cacheable { await Promise.all(this.#buckets.map((b) => b.clear())) } - async isCached(key: string): Promise { - const fullKey = this.#fullKey(key) - const probes = await this.#cascadeProbe(fullKey) - return probes.some((m) => m !== undefined) - } - async meta(key: string): Promise { const fullKey = this.#fullKey(key) const probes = await this.#cascadeProbe(fullKey) diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index eb38b55..7fd102b 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -244,19 +244,6 @@ describe('cascade behavior', () => { expect(l2.clearCalls).toBe(1) }) - it('isCached returns true if any layer has the key', async () => { - const l1 = new FakeBucket() - const l2 = new FakeBucket({ - key: 'test:k', - value: 'v', - meta: { storedAt: Date.now() }, - }) - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) - - expect(await cache.isCached('k')).toBe(true) - expect(await cache.isCached('missing')).toBe(false) - }) - it('meta returns highest-priority layer meta', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket({ diff --git a/tests/index.test.ts b/tests/index.test.ts index 04093cd..d542cde 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -28,7 +28,7 @@ describe('Cache operations', () => { }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.isCached('a')).toEqual(true) + expect(await cache.meta('a')).toBeDefined() expect(cachedValue).toEqual(value) }) @@ -50,8 +50,8 @@ describe('Cache operations', () => { 'b', ) - expect(await cache.isCached('a')).toEqual(true) - expect(await cache.isCached('b')).toEqual(true) + expect(await cache.meta('a')).toBeDefined() + expect(await cache.meta('b')).toBeDefined() expect([cachedValueA, cachedValueB]).toEqual([valueA, valueB]) }) @@ -64,9 +64,9 @@ describe('Cache operations', () => { const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.isCached('a')).toEqual(true) + expect(await cache.meta('a')).toBeDefined() await cache.delete('a') - expect(await cache.isCached('a')).toEqual(false) + expect(await cache.meta('a')).toBeUndefined() }) it('Clears the cache', async () => { @@ -78,9 +78,9 @@ describe('Cache operations', () => { const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.isCached('a')).toEqual(true) + expect(await cache.meta('a')).toBeDefined() await cache.clear() - expect(await cache.isCached('a')).toEqual(false) + expect(await cache.meta('a')).toBeUndefined() }) it('Creates proper keys', () => { diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts index c6e4f9a..62ac7b7 100644 --- a/tests/namespace.test.ts +++ b/tests/namespace.test.ts @@ -33,8 +33,8 @@ describe('namespace', () => { await a.delete('k') - expect(await a.isCached('k')).toBe(false) - expect(await b.isCached('k')).toBe(true) + expect(await a.meta('k')).toBeUndefined() + expect(await b.meta('k')).toBeDefined() }) it('clear from one namespace wipes shared bucket (documented caveat)', async () => { @@ -47,7 +47,7 @@ describe('namespace', () => { await a.clear() - expect(await a.isCached('k')).toBe(false) - expect(await b.isCached('k')).toBe(false) + expect(await a.meta('k')).toBeUndefined() + expect(await b.meta('k')).toBeUndefined() }) }) From 0ac855aae3013f869d37b9c0a705470b2ef786c2 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:52:32 +0200 Subject: [PATCH 12/23] Make namespace a positional argument to the Cacheable constructor (#21) The constructor signature changes from `new Cacheable(options)` to `new Cacheable(namespace, options)`. The namespace is the instance's identity and is almost always a literal at the call-site, so leading with it reads more naturally and saves a line in the common case. The remaining fields (`buckets`, `policy`, `maxAge`, `logger`) stay in the options bag, preserving the discriminated union that ties `maxAge` to its policies. Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 53 +++++++++++++++++-------------------- src/Cacheable.ts | 4 +-- src/types.ts | 8 +++--- tests/cascade.test.ts | 50 +++++++++++++++------------------- tests/fetchPolicies.test.ts | 18 +++++-------- tests/index.test.ts | 29 +++++++------------- tests/namespace.test.ts | 14 +++++----- 7 files changed, 75 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 2b406cf..f19d6a6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A small, typed cache with composable storage buckets and a handful of cache poli ```ts import { Cacheable, MemoryBucket } from 'cacheables' -const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' }) +const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) cache.remember(() => fetch('https://some-url.com/api'), 'key') ``` @@ -26,7 +26,7 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [Installation](#installation) - [Usage](#usage) - [API](#api) - - [new Cacheable(options)](#new-cacheableoptions-cacheabletmeta) + - [new Cacheable(namespace, options)](#new-cacheablenamespace-options-cacheabletmeta) - [cache.remember(resource, key)](#cacherememberresource-key-promiset) - [cache.delete(key)](#cachedeletekey-promisevoid) - [cache.clear()](#cacheclear-promisevoid) @@ -60,9 +60,8 @@ import { Cacheable, MemoryBucket } from 'cacheables' const apiUrl = 'https://goweather.herokuapp.com/weather/Karlsruhe' -const cache = new Cacheable({ +const cache = new Cacheable('weather', { buckets: [new MemoryBucket()], - namespace: 'weather', policy: 'max-age', maxAge: 5_000, }) @@ -77,12 +76,16 @@ await getWeather() // hit — cached ## API -### `new Cacheable(options): Cacheable` +### `new Cacheable(namespace, options): Cacheable` ```ts +new Cacheable( + namespace: string, + options: CacheableOptions, +) + type CacheableOptions = { buckets: IBucket[] // REQUIRED, L1 first - namespace: string // REQUIRED — bucket keys become `${namespace}:${key}` logger?: ILogger // default: undefined (no logging) } & ( | { policy?: 'cache-only' } // default @@ -93,7 +96,7 @@ type CacheableOptions = { ) ``` -`buckets` must be a non-empty array; the constructor throws `Error('At least one bucket is required')` otherwise. The first bucket is L1 (fastest, queried first); the rest form deeper layers. +`namespace` is prefixed onto every key passed to buckets as `${namespace}:${key}`, isolating instances that share a bucket. `buckets` must be a non-empty array; the constructor throws `Error('At least one bucket is required')` otherwise. The first bucket is L1 (fastest, queried first); the rest form deeper layers. ### `cache.remember(resource, key): Promise` @@ -155,7 +158,7 @@ Ships with the package; covers the common in-memory use case. ```ts import { Cacheable, MemoryBucket } from 'cacheables' -const cache = new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' }) +const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) ``` ### Writing your own bucket @@ -187,9 +190,8 @@ class FileSystemBucket implements IBucket { ### Cascade behavior ```ts -const cache = new Cacheable({ +const cache = new Cacheable('app', { buckets: [new MemoryBucket(), new FileSystemBucket()], - namespace: 'app', policy: 'max-age', maxAge: 60_000, }) @@ -214,9 +216,8 @@ class ETagBucket implements IBucket { // read / write / meta / delete / clear … } -const cache = new Cacheable({ +const cache = new Cacheable('app', { buckets: [new ETagBucket()], - namespace: 'app', }) const meta = await cache.meta('user:42') // typed as ETagMeta | undefined @@ -250,9 +251,8 @@ Forwards each message to `console.log`. Useful as a default during development. ```ts import { Cacheable, ConsoleLogger, MemoryBucket } from 'cacheables' -const cache = new Cacheable({ +const cache = new Cacheable('app', { buckets: [new MemoryBucket()], - namespace: 'app', logger: new ConsoleLogger(), }) ``` @@ -268,9 +268,8 @@ import { Cacheable, MemoryBucket, type ILogger } from 'cacheables' const pinoLogger = pino() const logger: ILogger = { log: (m) => pinoLogger.info(m) } -const cache = new Cacheable({ +const cache = new Cacheable('app', { buckets: [new MemoryBucket()], - namespace: 'app', logger, }) ``` @@ -279,12 +278,12 @@ The `logger` field on a `Cacheable` instance is mutable — assign a new logger ## Namespacing -`namespace` is required: every bucket call sees keys prefixed with `${namespace}:`. This isolates instances that share the same bucket, so you must pick a namespace at construction time even when only one instance uses a bucket. +`namespace` is required and is the constructor's first positional argument: every bucket call sees keys prefixed with `${namespace}:`. This isolates instances that share the same bucket, so you must pick a namespace at construction time even when only one instance uses a bucket. ```ts const bucket = new MemoryBucket() -const tenantA = new Cacheable({ buckets: [bucket], namespace: 'tenant-a' }) -const tenantB = new Cacheable({ buckets: [bucket], namespace: 'tenant-b' }) +const tenantA = new Cacheable('tenant-a', { buckets: [bucket] }) +const tenantB = new Cacheable('tenant-b', { buckets: [bucket] }) ``` Two instances can share a bucket without colliding. `delete` and `meta` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean _everything_. @@ -300,9 +299,8 @@ Two instances can share a bucket without colliding. `delete` and `meta` respect | `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | ```ts -new Cacheable({ +new Cacheable('app', { buckets: [new MemoryBucket()], - namespace: 'app', policy: 'max-age', maxAge: 1_000, }) @@ -330,9 +328,8 @@ await cache.cacheable(() => fetch(url), 'weather', { // v3 import { Cacheable, MemoryBucket, ConsoleLogger } from 'cacheables' -const cache = new Cacheable({ +const cache = new Cacheable('weather', { buckets: [new MemoryBucket()], - namespace: 'weather', policy: 'max-age', maxAge: 5_000, logger: new ConsoleLogger(), @@ -345,21 +342,21 @@ Breaking changes: - **Class renamed** `Cacheables` → `Cacheable`. Update imports and `new Cacheables(...)` call sites. The static helper moves with it: `Cacheables.key(...)` → `Cacheable.key(...)` (behaviour unchanged). - **Method renamed** `cache.cacheable(...)` → `cache.remember(...)`. -- **Cache policy moved to the constructor.** v2 took `cachePolicy` and `maxAge` as a per-call third argument; v3 has no per-call options. Pass `policy` (and `maxAge` where required) once on `new Cacheable({ ... })`. The field is `policy`, not `cachePolicy`. A single instance now serves a single policy — split into multiple instances if you previously mixed policies on one cache. -- **`buckets` is required** (replaces v2's implicit in-memory store). `new Cacheable()` no longer compiles. `new Cacheable({ buckets: [new MemoryBucket()], namespace: 'app' })` reproduces the v2 default. -- **`namespace` is required.** Every bucket call sees keys prefixed with `${namespace}:`. Pick one even if only one instance writes to the bucket. +- **Cache policy moved to the constructor.** v2 took `cachePolicy` and `maxAge` as a per-call third argument; v3 has no per-call options. Pass `policy` (and `maxAge` where required) once on `new Cacheable(namespace, { ... })`. The field is `policy`, not `cachePolicy`. A single instance now serves a single policy — split into multiple instances if you previously mixed policies on one cache. +- **`buckets` is required** (replaces v2's implicit in-memory store). `new Cacheable()` no longer compiles. `new Cacheable('app', { buckets: [new MemoryBucket()] })` reproduces the v2 default. +- **`namespace` is required and positional.** It's the constructor's first argument, prefixed onto every bucket key as `${namespace}:`. Pick one even if only one instance writes to the bucket. - **`enabled` option removed.** If you need to bypass the cache, call `resource()` directly instead of `cache.remember(...)`. - **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. - **`delete` and `clear` are async.** They now return `Promise` — add `await`. - **`isCached` removed.** Use `cache.meta(key)` instead — it returns `undefined` when the key is absent and the meta object otherwise. - **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time` / `timeEnd`. -- **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `namespace`, `policy`, and `logger`). +- **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `policy`, and `logger`; `namespace` is the constructor's first positional argument). - **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. What's new: - **Multilayer storage.** Pass several buckets to compose tiers (e.g. `[memory, filesystem]`); reads cascade L1 → Ln and back-fill missing layers on every hit. -- **Typed sidecar metadata.** `Cacheable` is generic; bucket implementations can persist fields like `etag` or `ttl` and `cache.meta(key)` returns them typed. Plain `new Cacheable({ buckets, namespace })` defaults to `Cacheable` and needs no type changes. +- **Typed sidecar metadata.** `Cacheable` is generic; bucket implementations can persist fields like `etag` or `ttl` and `cache.meta(key)` returns them typed. Plain `new Cacheable(namespace, { buckets })` defaults to `Cacheable` and needs no type changes. ## License diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 19cf1d5..b48f39b 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -16,12 +16,12 @@ export class Cacheable { #inflight = new Map>() #hits = new Map() - constructor(options: CacheableOptions) { + constructor(namespace: string, options: CacheableOptions) { if (!options.buckets || options.buckets.length === 0) { throw new Error('At least one bucket is required') } this.#buckets = options.buckets - this.#namespace = options.namespace + this.#namespace = namespace this.logger = options.logger this.#policy = options.policy ?? 'cache-only' this.#maxAge = diff --git a/src/types.ts b/src/types.ts index c4875eb..2f1fbab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,15 +100,13 @@ export type Policy = export type CacheOptions = CacheOptionsBase & PolicyOptions /** - * Constructor options for `Cacheable`. + * Constructor options for `Cacheable`. The namespace is passed + * as a positional argument; this bag carries everything else. * - * `buckets` is required; the array is L1 first. `namespace` is - * required and is prefixed onto every key passed to buckets as - * `${namespace}:${key}`, isolating instances that share a bucket. + * `buckets` is required; the array is L1 first. */ export type CacheableOptions = CacheOptionsBase & PolicyOptions & { buckets: IBucket[] - namespace: string } diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index 7fd102b..29cf7aa 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -64,7 +64,7 @@ describe('cascade behavior', () => { const seedMeta: IBaseMeta = { storedAt: Date.now() } const l1 = new FakeBucket({ key: 'test:k', value: 'v', meta: seedMeta }) const l2 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -87,7 +87,7 @@ describe('cascade behavior', () => { value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) await cache.remember(async () => 'fresh', 'k') @@ -99,7 +99,7 @@ describe('cascade behavior', () => { const l2Meta: IBaseMeta = { storedAt: 12345 } const l1 = new FakeBucket() const l2 = new FakeBucket({ key: 'test:k', value: 'v2', meta: l2Meta }) - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -115,7 +115,7 @@ describe('cascade behavior', () => { it('Both miss: resource called once; L1 written without meta; L2 written with L1 meta', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) const before = Date.now() let calls = 0 @@ -150,7 +150,7 @@ describe('cascade behavior', () => { const l1 = new FakeBucket() const l2 = new FakeBucket() const l3 = new FakeBucket({ key: 'test:k', value: 'deep', meta: l3Meta }) - const cache = new Cacheable({ buckets: [l1, l2, l3], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2, l3] }) const result = await cache.remember(async () => 'fresh', 'k') @@ -174,9 +174,8 @@ describe('cascade behavior', () => { value: 'l2-fresh', meta: { storedAt: now - 50 }, }) - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1, l2], - namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -203,9 +202,8 @@ describe('cascade behavior', () => { value: 'l2-stale', meta: { storedAt: now - 500 }, }) - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1, l2], - namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -223,7 +221,7 @@ describe('cascade behavior', () => { it('delete propagates to all buckets', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) await cache.remember(async () => 'v', 'k') await cache.delete('k') @@ -235,7 +233,7 @@ describe('cascade behavior', () => { it('clear propagates to all buckets', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) await cache.remember(async () => 'v', 'k') await cache.clear() @@ -251,7 +249,7 @@ describe('cascade behavior', () => { value: 'v', meta: { storedAt: 999 }, }) - const cache = new Cacheable({ buckets: [l1, l2], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) expect(await cache.meta('k')).toEqual({ storedAt: 999 }) @@ -262,7 +260,7 @@ describe('cascade behavior', () => { it('caches resources that resolve to undefined', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) let calls = 0 const resource = async () => { @@ -293,7 +291,7 @@ describe('cascade behavior', () => { return undefined } - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) let calls = 0 const result = await cache.remember(async () => { @@ -310,7 +308,7 @@ describe('cascade strict error propagation', () => { it('rejects when meta() throws', async () => { const l1 = new FakeBucket() l1.throwOn.meta = true - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) await expect(cache.remember(async () => 'v', 'k')).rejects.toThrow( 'meta failed', @@ -324,7 +322,7 @@ describe('cascade strict error propagation', () => { meta: { storedAt: Date.now() }, }) l1.throwOn.read = true - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'read failed', @@ -334,7 +332,7 @@ describe('cascade strict error propagation', () => { it('rejects when write() throws on miss', async () => { const l1 = new FakeBucket() l1.throwOn.write = true - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) await expect(cache.remember(async () => 'fresh', 'k')).rejects.toThrow( 'write failed', @@ -344,7 +342,7 @@ describe('cascade strict error propagation', () => { it('rejects when delete() throws', async () => { const l1 = new FakeBucket() l1.throwOn.delete = true - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) await expect(cache.delete('k')).rejects.toThrow('delete failed') }) @@ -352,7 +350,7 @@ describe('cascade strict error propagation', () => { it('rejects when clear() throws', async () => { const l1 = new FakeBucket() l1.throwOn.clear = true - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) await expect(cache.clear()).rejects.toThrow('clear failed') }) @@ -363,7 +361,7 @@ describe('concurrent dedup', () => { it('cache-only: deduplicates concurrent miss callers', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ buckets: [l1], namespace: 'test' }) + const cache = new Cacheable('test', { buckets: [l1] }) let calls = 0 const slow = async () => { @@ -381,9 +379,8 @@ describe('concurrent dedup', () => { it('network-only-non-concurrent: deduplicates concurrent callers', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1], - namespace: 'test', policy: 'network-only-non-concurrent', }) @@ -403,9 +400,8 @@ describe('concurrent dedup', () => { it('max-age: deduplicates concurrent miss callers', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1], - namespace: 'test', policy: 'max-age', maxAge: 1000, }) @@ -426,9 +422,8 @@ describe('concurrent dedup', () => { it('SWR miss: deduplicates concurrent miss callers', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1], - namespace: 'test', policy: 'stale-while-revalidate', }) @@ -448,9 +443,8 @@ describe('concurrent dedup', () => { it('network-only: does NOT deduplicate (control)', async () => { const l1 = new FakeBucket() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [l1], - namespace: 'test', policy: 'network-only', }) diff --git a/tests/fetchPolicies.test.ts b/tests/fetchPolicies.test.ts index ad2ffed..3381458 100644 --- a/tests/fetchPolicies.test.ts +++ b/tests/fetchPolicies.test.ts @@ -15,9 +15,8 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Fetch Policies', () => { it('cache-only', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'cache-only', }) @@ -33,9 +32,8 @@ describe('Fetch Policies', () => { }) it('network-only-non-concurrent', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'network-only-non-concurrent', }) @@ -58,9 +56,8 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 0, 0, 3]) }) it('network-only', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'network-only', }) @@ -78,9 +75,8 @@ describe('Fetch Policies', () => { expect(values).toEqual([0, 1, 2]) }) it('max-age', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -99,9 +95,8 @@ describe('Fetch Policies', () => { expect([a, b, c, d]).toEqual([0, 0, 2, 2]) }) it('stale-while-revalidate', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'stale-while-revalidate', }) @@ -126,9 +121,8 @@ describe('Fetch Policies', () => { }) it('stale-while-revalidate with maxAge', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'stale-while-revalidate', maxAge: 200, }) diff --git a/tests/index.test.ts b/tests/index.test.ts index d542cde..8eb55f3 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -22,9 +22,8 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') @@ -33,9 +32,8 @@ describe('Cache operations', () => { }) it('Stores multiple caches', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) const valueA = 10 @@ -56,9 +54,8 @@ describe('Cache operations', () => { }) it('Deletes values', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) const value = 10 @@ -70,9 +67,8 @@ describe('Cache operations', () => { }) it('Clears the cache', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) const value = 10 @@ -91,9 +87,8 @@ describe('Cache operations', () => { it('Logs correctly', async () => { console.log = jest.fn() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', logger: new ConsoleLogger(), }) @@ -110,7 +105,7 @@ describe('Cache operations', () => { }) it('Throws when constructed without buckets', () => { - expect(() => new Cacheable({ buckets: [], namespace: 'test' })).toThrow( + expect(() => new Cacheable('test', { buckets: [] })).toThrow( 'At least one bucket is required', ) }) @@ -123,9 +118,8 @@ describe('Cache operations', () => { * Assuming the time starts at 0 */ it('Handles race conditions correctly', async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', policy: 'max-age', maxAge: 100, }) @@ -153,9 +147,8 @@ describe('Cache operations', () => { it('Handles multiple calls correctly', async () => { console.log = jest.fn() - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', logger: new ConsoleLogger(), policy: 'max-age', maxAge: 100, @@ -187,9 +180,8 @@ describe('Cache operations', () => { }) it("Doesn't interfere with error handling", async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) const rejecting = () => { return cache.remember(() => mockedApiRequest(0, 10, true), 'a') @@ -198,9 +190,8 @@ describe('Cache operations', () => { }) it("Doesn't cache rejected value", async () => { - const cache = new Cacheable({ + const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - namespace: 'test', }) let errNo = 1 const rejecting = () => { diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts index 62ac7b7..76ebadc 100644 --- a/tests/namespace.test.ts +++ b/tests/namespace.test.ts @@ -3,8 +3,8 @@ import { Cacheable, MemoryBucket } from '../src' describe('namespace', () => { it('isolates entries between two instances sharing one bucket', async () => { const bucket = new MemoryBucket() - const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) - const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) + const a = new Cacheable('a', { buckets: [bucket] }) + const b = new Cacheable('b', { buckets: [bucket] }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') @@ -15,7 +15,7 @@ describe('namespace', () => { it('writes bucket keys with namespace prefix', async () => { const bucket = new MemoryBucket() - const a = new Cacheable({ buckets: [bucket], namespace: 'tenant1' }) + const a = new Cacheable('tenant1', { buckets: [bucket] }) await a.remember(async () => 'v', 'user:42') @@ -25,8 +25,8 @@ describe('namespace', () => { it('delete on one namespace does not affect another', async () => { const bucket = new MemoryBucket() - const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) - const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) + const a = new Cacheable('a', { buckets: [bucket] }) + const b = new Cacheable('b', { buckets: [bucket] }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') @@ -39,8 +39,8 @@ describe('namespace', () => { it('clear from one namespace wipes shared bucket (documented caveat)', async () => { const bucket = new MemoryBucket() - const a = new Cacheable({ buckets: [bucket], namespace: 'a' }) - const b = new Cacheable({ buckets: [bucket], namespace: 'b' }) + const a = new Cacheable('a', { buckets: [bucket] }) + const b = new Cacheable('b', { buckets: [bucket] }) await a.remember(async () => 'A', 'k') await b.remember(async () => 'B', 'k') From 238b4d60e48ebf836bfedaa0590421be6a488c94 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:54:02 +0200 Subject: [PATCH 13/23] Tighten README and expand cache policy mechanics (#22) * Tighten README and expand cache policy mechanics Compact the intro and structural sections, annotate the headline example, and split Cache Policies into per-policy subsections with dedicated runnable examples covering freshness and concurrent dedup behavior. Co-Authored-By: Claude Opus 4.7 (1M context) * Apply prettier formatting to README Co-Authored-By: Claude Opus 4.7 (1M context) * Restore compact headline example, move comments to Usage section Co-Authored-By: Claude Opus 4.7 (1M context) * Rename Usage example namespace to weather-data and key to karlsruhe Co-Authored-By: Claude Opus 4.7 (1M context) * Add summary table at the top of the Cache Policies section Co-Authored-By: Claude Opus 4.7 (1M context) * Align policy 'use this' closers and add one to max-age Co-Authored-By: Claude Opus 4.7 (1M context) * Tone down network-only-non-concurrent 'use this' line Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 277 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 187 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index f19d6a6..14330dd 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,13 @@ A small, typed cache with composable storage buckets and a handful of cache policies, written in TypeScript. -- Elegant syntax: **wrap existing async calls** with `cache.remember(...)`. -- **Multilayer storage**: compose a fast in-memory L1 with any L2 you write (filesystem, Redis, S3, …). Reads cascade L1 → Ln; on any hit the engine fills missing layers. -- **Fully typed results**, including a generic `TMeta` parameter for sidecar metadata. -- Supports different **cache policies**. +- **Wrap any async call** with `cache.remember(...)` — `remember` is both getter and setter. +- **Multilayer storage**: stack a fast in-memory L1 with any L2 you write (filesystem, Redis, S3, …). Reads cascade L1 → Ln; on any hit, missing layers are back-filled. +- **Five cache policies**, including `stale-while-revalidate`, with concurrency-safe deduplication where it makes sense. +- **Fully typed**, with a generic `TMeta` parameter for sidecar metadata (etag, ttl, version, …). +- **Required namespace prefix** so multiple instances can share a bucket without collisions. - Helper to build cache keys. -- Required **namespace** prefix so multiple instances can share a bucket without collisions. -- Works in the browser and Node.js. -- **No dependencies**. +- Works in browser and Node.js. **No dependencies.** ```ts import { Cacheable, MemoryBucket } from 'cacheables' @@ -28,22 +27,18 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [API](#api) - [new Cacheable(namespace, options)](#new-cacheablenamespace-options-cacheabletmeta) - [cache.remember(resource, key)](#cacherememberresource-key-promiset) - - [cache.delete(key)](#cachedeletekey-promisevoid) - - [cache.clear()](#cacheclear-promisevoid) + - [cache.delete(key) / cache.clear()](#cachedeletekey-promisevoid--cacheclear-promisevoid) - [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - [Cacheable.key(...args)](#cacheablekeyargs-string) - [Buckets](#buckets) - - [`IBucket` contract](#ibucket-contract) - - [Built-in `MemoryBucket`](#built-in-memorybucket) - - [Writing your own bucket](#writing-your-own-bucket) - - [Cascade behavior](#cascade-behavior) - - [Typed metadata (`TMeta`)](#typed-metadata-tmeta) +- [Cache Policies](#cache-policies) + - [`cache-only` (default)](#cache-only-default) + - [`network-only`](#network-only) + - [`network-only-non-concurrent`](#network-only-non-concurrent) + - [`max-age`](#max-age) + - [`stale-while-revalidate`](#stale-while-revalidate) - [Logger](#logger) - - [`ILogger` contract](#ilogger-contract) - - [Built-in `ConsoleLogger`](#built-in-consolelogger) - - [Writing your own logger](#writing-your-own-logger) - [Namespacing](#namespacing) -- [Cache Policies](#cache-policies) - [Migrating from v2 → v3](#migrating-from-v2--v3) - [License](#license) @@ -60,20 +55,24 @@ import { Cacheable, MemoryBucket } from 'cacheables' const apiUrl = 'https://goweather.herokuapp.com/weather/Karlsruhe' -const cache = new Cacheable('weather', { +// 'weather-data' is the namespace — every key is stored under `weather-data:`. +// `buckets` is the layered storage stack; the first entry is L1. +// `policy: 'max-age'` returns the cached value while it is younger than +// `maxAge` (in ms), and re-fetches when it has aged past that. +const cache = new Cacheable('weather-data', { buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 5_000, }) -const getWeather = () => cache.remember(() => fetch(apiUrl), 'weather') +// `remember` is both getter and setter: on a miss it calls the resource +// and writes to every bucket; on a hit it returns the cached value. +const getWeather = () => cache.remember(() => fetch(apiUrl), 'karlsruhe') await getWeather() // miss — fetched await getWeather() // hit — cached ``` -`remember` is both getter and setter. The first time a key is requested it calls the resource and stores the result in every configured bucket; subsequent reads cascade through the buckets until one returns a hit. - ## API ### `new Cacheable(namespace, options): Cacheable` @@ -96,27 +95,23 @@ type CacheableOptions = { ) ``` -`namespace` is prefixed onto every key passed to buckets as `${namespace}:${key}`, isolating instances that share a bucket. `buckets` must be a non-empty array; the constructor throws `Error('At least one bucket is required')` otherwise. The first bucket is L1 (fastest, queried first); the rest form deeper layers. +`namespace` is prefixed onto every key as `${namespace}:${key}`, isolating instances that share a bucket. `buckets` must be a non-empty array (the constructor throws `Error('At least one bucket is required')` otherwise); the first bucket is L1, the rest form deeper layers. ### `cache.remember(resource, key): Promise` -Resolves to the cached value if present (subject to policy), otherwise calls `resource()` and stores the result in every bucket. +Resolves to the cached value if present (subject to policy); otherwise calls `resource()` and writes to every bucket. -### `cache.delete(key): Promise` +### `cache.delete(key): Promise` / `cache.clear(): Promise` -Deletes the entry from every bucket. - -### `cache.clear(): Promise` - -Clears every bucket and the in-flight registry. +`delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. Both are async — `await` them. ### `cache.meta(key): Promise` -Returns the meta from the highest-priority layer that has the key — useful for inspecting sidecar fields like `etag`, `ttl`, etc. +Returns the meta from the highest-priority layer that has the key, or `undefined` if no layer has it. Useful to inspect sidecar fields (`etag`, `ttl`, …) and to test for presence. ### `Cacheable.key(...args): string` -Joins the parts with `:`. Identical to v2. +Joins parts with `:`. ```ts Cacheable.key('user', 42) // 'user:42' @@ -144,16 +139,30 @@ interface IBucket { Rules: -- `meta` MUST be cheap. The engine probes it on every layer for every read. -- `read` returns `undefined` when the entry is absent and `{ value }` when it's present — the wrapper exists so buckets can store entries whose value is itself `undefined` without colliding with the absence signal. -- When `write` receives a `meta`, the bucket MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This guarantees `max-age` semantics stay coherent across layers. +- `meta` MUST be cheap. The engine probes it on every layer for every read. A typical L2 keeps a sidecar (file, table, key/value entry) so probes don't hit the value blob. +- `read` returns `undefined` for absence and `{ value }` for presence — the wrapper lets buckets store entries whose value is itself `undefined` without colliding with the absence signal. +- When `write` receives a `meta`, the bucket MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This keeps `max-age` semantics coherent across layers. - When `write` is called with no `meta`, the bucket MUST synthesize one with `storedAt: Date.now()`. - `clear` MUST remove every entry the bucket manages. - Any throw from any bucket rejects the surrounding `remember()` call. There is no per-bucket error suppression. +### Cascade behavior + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket(), new FileSystemBucket()], + policy: 'max-age', + maxAge: 60_000, +}) +``` + +- **Read**: probe `meta()` on every layer in parallel; the first layer satisfying the freshness predicate is the hit. Read its value, then back-fill every layer above and below that is still missing the key, using the hit layer's meta — `storedAt` is preserved everywhere. +- **Miss + `resource()`**: write to L1 with no meta (L1 synthesizes its own), read meta back from L1, then write to L2..Ln with that exact meta. All layers converge on the same `storedAt`. +- **Stale L1 + fresh L2** (under `max-age`): the freshness predicate filters per-layer, so the engine returns the fresh L2 value and back-fills L1. + ### Built-in `MemoryBucket` -Ships with the package; covers the common in-memory use case. +Ships with the package; covers the common in-memory case. ```ts import { Cacheable, MemoryBucket } from 'cacheables' @@ -163,7 +172,7 @@ const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) ### Writing your own bucket -Implement `IBucket`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 buckets are easy to add without ceremony. A typical L2 keeps a sidecar (file, table, key/value entry) so `meta()` is cheap. +Implement `IBucket`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 buckets are easy to add. ```ts import type { IBucket, IBaseMeta } from 'cacheables' @@ -187,23 +196,9 @@ class FileSystemBucket implements IBucket { } ``` -### Cascade behavior - -```ts -const cache = new Cacheable('app', { - buckets: [new MemoryBucket(), new FileSystemBucket()], - policy: 'max-age', - maxAge: 60_000, -}) -``` - -- **Read**: probe `meta()` on every layer in parallel; the first layer that satisfies the freshness predicate is the hit. Read its value, and back-fill every other layer that is still missing the key — using the hit layer's meta so `storedAt` is preserved everywhere. -- **Miss + resource()**: write to L1 with no meta (L1 synthesizes its own), read meta back from L1, then write to L2..Ln with that exact meta. All layers converge to the same `storedAt`. -- **Stale L1 + fresh L2 (under `max-age`)**: the freshness predicate filters per-layer, so the engine returns the fresh L2 value and back-fills L1. - ### Typed metadata (`TMeta`) -`Cacheable` is generic in `TMeta`. Extend it to carry sidecar fields: +`Cacheable` is generic in `TMeta`. Extend `IBaseMeta` to carry sidecar fields: ```ts import { Cacheable, type IBaseMeta, type IBucket } from 'cacheables' @@ -216,37 +211,164 @@ class ETagBucket implements IBucket { // read / write / meta / delete / clear … } -const cache = new Cacheable('app', { - buckets: [new ETagBucket()], -}) +const cache = new Cacheable('app', { buckets: [new ETagBucket()] }) const meta = await cache.meta('user:42') // typed as ETagMeta | undefined ``` -Every bucket passed to the constructor must satisfy `IBucket`, and the TypeScript compiler enforces it. The built-in `MemoryBucket` only implements `IBucket`, so it can't be used in a `Cacheable` instance with a custom `TMeta` — write a custom bucket (or wrap `MemoryBucket`) when you need extended metadata. +Every bucket passed to the constructor must satisfy `IBucket`, enforced by the compiler. The built-in `MemoryBucket` only implements `IBucket`, so it can't be used in a `Cacheable` instance with a custom `TMeta` — write a custom bucket (or wrap `MemoryBucket`) when you need extended metadata. + +## Cache Policies + +The policy is set once on the constructor and applies to every `remember()` call on that instance. Two mechanics matter across policies: + +- **Freshness**: whether a cached value qualifies for return without re-fetching. Only `max-age` and `stale-while-revalidate` look at `storedAt`. +- **In-flight deduplication**: when two callers ask for the same key concurrently, an instance keeps a per-key promise so only one `resource()` runs and both callers receive its result. Dedup is policy-dependent (see each section below). + +| Policy | Returns cached value | Calls `resource()` | In-flight dedup | +| ------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------ | --------------- | +| [`cache-only`](#cache-only-default) _(default)_ | Always, if present | Only on miss | Yes | +| [`network-only`](#network-only) | Never | Every call | No | +| [`network-only-non-concurrent`](#network-only-non-concurrent) | Never | Every call (one per concurrent burst) | Yes | +| [`max-age`](#max-age) | If `Date.now() - storedAt <= maxAge` | On miss or expiry | Yes | +| [`stale-while-revalidate`](#stale-while-revalidate) | Always, if present (even stale) | On miss, or in background when `maxAge` exceeded | Yes | + +### `cache-only` _(default)_ + +Returns any cached value, regardless of age. On miss, calls `resource()` once and writes to every bucket. Concurrent miss callers share one in-flight `resource()` call. + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket()], + // policy: 'cache-only' (the default — can be omitted) +}) + +const u = () => cache.remember(() => fetchUser(1), 'user:1') + +await u() // miss — fetches once, writes +await u() // hit — returns cached value, no fetch + +// Concurrent miss: a single fetch is shared across both awaiters. +await Promise.all([u(), u()]) +``` + +Use this policy when the data is effectively immutable for the lifetime of the cache (e.g. content addressed by hash), or when you invalidate keys yourself with `cache.delete(key)`. + +### `network-only` + +Always calls `resource()`, always overwrites the cache. **No deduplication** — concurrent callers each fire their own `resource()`. The cache exists only to seed reads from sibling instances or to populate downstream layers. + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket()], + policy: 'network-only', +}) + +// Both calls fetch in parallel; both writes land on the cache. +await Promise.all([ + cache.remember(() => fetchUser(1), 'user:1'), + cache.remember(() => fetchUser(1), 'user:1'), +]) +``` + +Use this policy when stale data is unacceptable and concurrent calls must not be coalesced — for instance, side-effecting POSTs. + +### `network-only-non-concurrent` + +Always calls `resource()`, always overwrites the cache, **but** concurrent callers share one in-flight request. Equivalent to `network-only` for serial calls; equivalent to `cache-only`'s dedup behavior for concurrent calls. + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket()], + policy: 'network-only-non-concurrent', +}) + +// One fetch shared across the three concurrent awaiters; one write to the cache. +const [a, b, c] = await Promise.all([ + cache.remember(() => fetchUser(1), 'user:1'), + cache.remember(() => fetchUser(1), 'user:1'), + cache.remember(() => fetchUser(1), 'user:1'), +]) +// Subsequent calls fetch again; this policy never returns the previously cached value. +``` + +Use this policy when you always want fresh data but want concurrent callers to share a single fetch. + +### `max-age` + +Returns the cached value if `Date.now() - meta.storedAt <= maxAge`, otherwise calls `resource()` and overwrites the cache. Concurrent miss/expired callers share one in-flight `resource()`. The freshness predicate runs per-layer during the cascade probe, so a stale L1 with a fresh L2 yields a hit on L2 and a back-fill of L1. + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket()], + policy: 'max-age', + maxAge: 5_000, // 5 seconds +}) + +await cache.remember(() => fetchUser(1), 'user:1') // miss — fetches +await cache.remember(() => fetchUser(1), 'user:1') // hit (within 5s) + +// 6 seconds later … +await cache.remember(() => fetchUser(1), 'user:1') // expired — re-fetches, overwrites +``` + +Multilayer note: a stale L1 doesn't force a network call when L2 still has a fresh value: + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket(/* short-lived */), new FileSystemBucket()], + policy: 'max-age', + maxAge: 60_000, +}) +// L1 evicts after 10s but L2 still has a value with storedAt 30s ago: +// the engine returns the L2 value and back-fills L1 with the same storedAt. +``` + +Use this policy when data has a known freshness window and a re-fetch past that window is acceptable. + +### `stale-while-revalidate` + +Returns the cached value immediately when it exists, **even if stale**. If `maxAge` is unset _or_ exceeded, fires a background `resource()` call to refresh — the current caller does not wait for it. With no cached value, it behaves like `network-only-non-concurrent` (caller waits, concurrent callers dedup). + +```ts +const cache = new Cacheable('app', { + buckets: [new MemoryBucket()], + policy: 'stale-while-revalidate', + maxAge: 5_000, // optional — without it, every read triggers a background revalidation +}) + +await cache.remember(() => fetchUser(1), 'user:1') // miss — caller waits + +// Within 5s: pure cache hit, no revalidation. +await cache.remember(() => fetchUser(1), 'user:1') + +// After 5s: returns stale value immediately, kicks off a background fetch +// that overwrites the cache when it resolves. +const stale = await cache.remember(() => fetchUser(1), 'user:1') +``` + +Background revalidation errors are swallowed (the stale value has already been served). Concurrent stale reads share one revalidation. + +Use this policy when latency matters more than absolute freshness — e.g. dashboards where a slightly outdated reading beats a loading spinner. ## Logger Pass a `logger` to surface what the engine is doing. Without one, the engine is silent. -### `ILogger` contract - ```ts interface ILogger { log(message: string): void } ``` -When a `logger` is configured, every `cache.remember(...)` call emits two messages — timing, then hit count: +Every `cache.remember(...)` emits two messages — timing, then hit count: ``` Cacheable "weather": 12ms Cacheable "weather": hits: 1 ``` -### Built-in `ConsoleLogger` - -Forwards each message to `console.log`. Useful as a default during development. +The built-in `ConsoleLogger` forwards to `console.log`: ```ts import { Cacheable, ConsoleLogger, MemoryBucket } from 'cacheables' @@ -257,8 +379,6 @@ const cache = new Cacheable('app', { }) ``` -### Writing your own logger - Any object with a `log(message: string)` method satisfies `ILogger`, so wrapping an existing logger is a one-liner: ```ts @@ -268,17 +388,14 @@ import { Cacheable, MemoryBucket, type ILogger } from 'cacheables' const pinoLogger = pino() const logger: ILogger = { log: (m) => pinoLogger.info(m) } -const cache = new Cacheable('app', { - buckets: [new MemoryBucket()], - logger, -}) +const cache = new Cacheable('app', { buckets: [new MemoryBucket()], logger }) ``` The `logger` field on a `Cacheable` instance is mutable — assign a new logger (or `undefined`) at runtime to flip logging on or off. ## Namespacing -`namespace` is required and is the constructor's first positional argument: every bucket call sees keys prefixed with `${namespace}:`. This isolates instances that share the same bucket, so you must pick a namespace at construction time even when only one instance uses a bucket. +`namespace` is the constructor's first positional argument and is required. Every bucket call sees keys prefixed with `${namespace}:`, so two instances can safely share a bucket: ```ts const bucket = new MemoryBucket() @@ -286,32 +403,12 @@ const tenantA = new Cacheable('tenant-a', { buckets: [bucket] }) const tenantB = new Cacheable('tenant-b', { buckets: [bucket] }) ``` -Two instances can share a bucket without colliding. `delete` and `meta` respect the namespace; `clear()` wipes the entire underlying bucket (it has no notion of which keys belong to which namespace), so reach for it only when you really mean _everything_. - -## Cache Policies - -| Policy | Behaviour | -| ----------------------------- | ------------------------------------------------------------------------------------------------------ | -| `cache-only` _(default)_ | Return any cached value; on miss, call `resource()`. Concurrent miss callers share one fetch. | -| `network-only` | Always call `resource()`; concurrent calls each get their own. | -| `network-only-non-concurrent` | Always call `resource()`, but concurrent calls share one in-flight request. | -| `max-age` _(maxAge required)_ | Return cache if `Date.now() - storedAt <= maxAge`, otherwise fetch. | -| `stale-while-revalidate` | Return the cached value immediately; if `maxAge` is unset or exceeded, fire a background revalidation. | - -```ts -new Cacheable('app', { - buckets: [new MemoryBucket()], - policy: 'max-age', - maxAge: 1_000, -}) -``` +`delete` and `meta` respect the namespace; `clear()` wipes the entire underlying bucket — it has no notion of which keys belong to which namespace. Reach for `clear()` only when you mean _everything_. ## Migrating from v2 → v3 v2 was an in-memory cache with per-call options and a synchronous surface. v3 introduces pluggable storage (buckets), required namespacing, an instance-level cache policy, and a fully async API. -Before / after: - ```ts // v2 import { Cacheables } from 'cacheables' From bd4e89ce3c31201bf42f207c1350fd2301b05474 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:56:09 +0200 Subject: [PATCH 14/23] Merge timing and hit-count logs into one HIT/MISS line per call (#23) Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 8 ++++---- src/Cacheable.ts | 16 +++------------- tests/index.test.ts | 28 +++++++++++++++++++++------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 14330dd..a1a47a9 100644 --- a/README.md +++ b/README.md @@ -361,11 +361,11 @@ interface ILogger { } ``` -Every `cache.remember(...)` emits two messages — timing, then hit count: +Every `cache.remember(...)` emits one message, tagged `HIT` or `MISS` with the elapsed time: ``` -Cacheable "weather": 12ms -Cacheable "weather": hits: 1 +Cacheable "weather": MISS 12ms +Cacheable "weather": HIT 0.2ms ``` The built-in `ConsoleLogger` forwards to `console.log`: @@ -446,7 +446,7 @@ Breaking changes: - **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. - **`delete` and `clear` are async.** They now return `Promise` — add `await`. - **`isCached` removed.** Use `cache.meta(key)` instead — it returns `undefined` when the key is absent and the meta object otherwise. -- **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Timing now ships as a formatted string (`Cacheable "": `) instead of `console.time` / `timeEnd`. +- **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Each `remember()` call emits a single formatted message (`Cacheable "": HIT|MISS `) instead of `console.time` / `timeEnd`. - **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `policy`, and `logger`; `namespace` is the constructor's first positional argument). - **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. diff --git a/src/Cacheable.ts b/src/Cacheable.ts index b48f39b..9648e1a 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -14,7 +14,6 @@ export class Cacheable { #buckets: IBucket[] #namespace: string #inflight = new Map>() - #hits = new Map() constructor(namespace: string, options: CacheableOptions) { if (!options.buckets || options.buckets.length === 0) { @@ -41,13 +40,11 @@ export class Cacheable { async delete(key: string): Promise { const fullKey = this.#fullKey(key) - this.#hits.delete(fullKey) await Promise.all(this.#buckets.map((b) => b.delete(fullKey))) } async clear(): Promise { this.#inflight.clear() - this.#hits.clear() await Promise.all(this.#buckets.map((b) => b.clear())) } @@ -59,21 +56,14 @@ export class Cacheable { async remember(resource: () => Promise, key: string): Promise { const { logger } = this - const start = logger ? Date.now() : 0 + const start = logger ? performance.now() : 0 const fullKey = this.#fullKey(key) const { value, hit } = await this.#runPolicy(resource, fullKey) - if (hit) { - const next = (this.#hits.get(fullKey) ?? 0) + 1 - this.#hits.set(fullKey, next) - } else if (!this.#hits.has(fullKey)) { - this.#hits.set(fullKey, 0) - } - if (logger) { - logger.log(`Cacheable "${key}": ${Date.now() - start}ms`) - logger.log(`Cacheable "${key}": hits: ${this.#hits.get(fullKey) ?? 0}`) + const elapsed = Math.round((performance.now() - start) * 10) / 10 + logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) } return value diff --git a/tests/index.test.ts b/tests/index.test.ts index 8eb55f3..d6f5d99 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -95,13 +95,19 @@ describe('Cache operations', () => { const cachedRequest = () => cache.remember(() => mockedApiRequest(1), 'a') await cachedRequest() - expect(console.log).lastCalledWith('Cacheable "a": hits: 0') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + ) await cachedRequest() - expect(console.log).lastCalledWith('Cacheable "a": hits: 1') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + ) await cachedRequest() - expect(console.log).lastCalledWith('Cacheable "a": hits: 2') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + ) }) it('Throws when constructed without buckets', () => { @@ -160,23 +166,31 @@ describe('Cache operations', () => { // This should be a miss and take ~10ms await hitCache() - expect(console.log).lastCalledWith('Cacheable "a": hits: 0') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + ) // This should be a hit and take ~0ms await hitCache() - expect(console.log).lastCalledWith('Cacheable "a": hits: 1') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + ) await wait(60) // This should be a hit and take ~0ms await hitCache() - expect(console.log).lastCalledWith('Cacheable "a": hits: 2') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + ) await wait(60) // This should be a miss and take ~10ms await hitCache() - expect(console.log).lastCalledWith('Cacheable "a": hits: 2') + expect(console.log).lastCalledWith( + expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + ) }) it("Doesn't interfere with error handling", async () => { From bfb5b736eaddb71fe287da5c2a032e8a35ace079 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:37:17 +0200 Subject: [PATCH 15/23] Add cache.resolve(producer, key) returning bucket meta (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `resolve` shares the policy and dedup pipeline with `remember` but returns `Promise` instead of the producer's value. This fits use cases where the bucket projects derived data through `TMeta` (e.g. a filesystem bucket that stores bytes and exposes a local URL on its meta) — callers can stay on a freshness-aware path instead of falling back to the policy-bypassing `cache.meta(key)` for the data they actually want. Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 30 ++++++- src/Cacheable.ts | 63 +++++++++----- tests/resolve.test.ts | 190 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 tests/resolve.test.ts diff --git a/README.md b/README.md index a1a47a9..bd8393a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [API](#api) - [new Cacheable(namespace, options)](#new-cacheablenamespace-options-cacheabletmeta) - [cache.remember(resource, key)](#cacherememberresource-key-promiset) + - [cache.resolve(resource, key)](#cacheresolveresource-key-promisetmeta) - [cache.delete(key) / cache.clear()](#cachedeletekey-promisevoid--cacheclear-promisevoid) - [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - [Cacheable.key(...args)](#cacheablekeyargs-string) @@ -101,13 +102,34 @@ type CacheableOptions = { Resolves to the cached value if present (subject to policy); otherwise calls `resource()` and writes to every bucket. +### `cache.resolve(resource, key): Promise` + +Same fresh-or-fetch behavior as `remember`, but returns the bucket's meta instead of the producer's value. Useful when the projection a bucket exposes through `TMeta` is what callers actually need — for example, a filesystem bucket that fetches and stores a remote image as bytes, then exposes a local URL on its meta: + +```ts +interface FilesystemMeta extends IBaseMeta { + url: string +} + +const cache = new Cacheable('images', { + buckets: [new FilesystemBucket()], +}) + +const { url } = await cache.resolve( + () => fetch(imageUrl).then((r) => r.arrayBuffer()), + imageUrl, +) +``` + +`resolve` and `remember` share the same in-flight registry: a concurrent pair against the same key triggers `resource()` once and both observers see the same `storedAt`. Unlike `cache.meta(key)`, `resolve` honors the cache policy — a stale entry will trigger a producer call (or a background revalidation under `stale-while-revalidate`). + ### `cache.delete(key): Promise` / `cache.clear(): Promise` `delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. Both are async — `await` them. ### `cache.meta(key): Promise` -Returns the meta from the highest-priority layer that has the key, or `undefined` if no layer has it. Useful to inspect sidecar fields (`etag`, `ttl`, …) and to test for presence. +Returns the meta from the highest-priority layer that has the key, or `undefined` if no layer has it. **Bypasses the policy** — it returns whatever the cache holds, fresh or stale. Reach for `cache.resolve(...)` instead when you want a meta that has been refreshed against the policy; reach for `cache.meta(key)` for raw inspection (debugging, eviction logic, presence checks). ### `Cacheable.key(...args): string` @@ -220,10 +242,10 @@ Every bucket passed to the constructor must satisfy `IBucket`, enforce ## Cache Policies -The policy is set once on the constructor and applies to every `remember()` call on that instance. Two mechanics matter across policies: +The policy is set once on the constructor and applies to every `remember()` and `resolve()` call on that instance. Two mechanics matter across policies: - **Freshness**: whether a cached value qualifies for return without re-fetching. Only `max-age` and `stale-while-revalidate` look at `storedAt`. -- **In-flight deduplication**: when two callers ask for the same key concurrently, an instance keeps a per-key promise so only one `resource()` runs and both callers receive its result. Dedup is policy-dependent (see each section below). +- **In-flight deduplication**: when two callers ask for the same key concurrently, an instance keeps a per-key promise so only one `resource()` runs and both callers receive its result. `remember` and `resolve` share that registry — a concurrent pair against the same key triggers one `resource()` call. Dedup is policy-dependent (see each section below). | Policy | Returns cached value | Calls `resource()` | In-flight dedup | | ------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------ | --------------- | @@ -361,7 +383,7 @@ interface ILogger { } ``` -Every `cache.remember(...)` emits one message, tagged `HIT` or `MISS` with the elapsed time: +Every `cache.remember(...)` and `cache.resolve(...)` emits one message, tagged `HIT` or `MISS` with the elapsed time: ``` Cacheable "weather": MISS 12ms diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 9648e1a..8704459 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -69,30 +69,46 @@ export class Cacheable { return value } + async resolve(resource: () => Promise, key: string): Promise { + const { logger } = this + const start = logger ? performance.now() : 0 + + const fullKey = this.#fullKey(key) + const { meta, hit } = await this.#runPolicy(resource, fullKey) + + if (logger) { + const elapsed = Math.round((performance.now() - start) * 10) / 10 + logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) + } + + return meta + } + async #runPolicy( resource: () => Promise, fullKey: string, - ): Promise<{ value: T; hit: boolean }> { + ): Promise<{ value: T; hit: boolean; meta: TMeta }> { switch (this.#policy) { case 'cache-only': { return this.#dedup(fullKey, async () => { const cached = await this.#cascadeRead(fullKey) - if (cached) return { value: cached.value, hit: true } + if (cached) + return { value: cached.value, hit: true, meta: cached.meta } const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } }) } case 'network-only': { const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } } case 'network-only-non-concurrent': { return this.#dedup(fullKey, async () => { const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } }) } case 'max-age': { @@ -102,10 +118,11 @@ export class Cacheable { fullKey, (m) => Date.now() - m.storedAt <= maxAge, ) - if (cached) return { value: cached.value, hit: true } + if (cached) + return { value: cached.value, hit: true, meta: cached.meta } const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } }) } case 'stale-while-revalidate': { @@ -117,24 +134,24 @@ export class Cacheable { Date.now() - cached.meta.storedAt > maxAge if (cached && !isStale) { - return { value: cached.value, hit: true } + return { value: cached.value, hit: true, meta: cached.meta } } if (cached && isStale) { this.#dedup(fullKey, async () => { const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } }).catch(() => { /* swallow background revalidation errors */ }) - return { value: cached.value, hit: true } + return { value: cached.value, hit: true, meta: cached.meta } } return this.#dedup(fullKey, async () => { const value = await resource() - await this.#cascadeWrite(fullKey, value) - return { value, hit: false } + const meta = await this.#cascadeWrite(fullKey, value) + return { value, hit: false, meta } }) } } @@ -190,15 +207,19 @@ export class Cacheable { if (writes.length > 0) await Promise.all(writes) } - async #cascadeWrite(fullKey: string, value: T): Promise { + async #cascadeWrite(fullKey: string, value: T): Promise { const [l1, ...rest] = this.#buckets - if (l1 === undefined) return + if (l1 === undefined) { + throw new Error('At least one bucket is required') + } await l1.write(fullKey, value) - if (rest.length === 0) return const meta = await l1.meta(fullKey) if (meta === undefined) { throw new Error('L1 bucket did not persist meta after write') } - await Promise.all(rest.map((b) => b.write(fullKey, value, meta))) + if (rest.length > 0) { + await Promise.all(rest.map((b) => b.write(fullKey, value, meta))) + } + return meta } } diff --git a/tests/resolve.test.ts b/tests/resolve.test.ts new file mode 100644 index 0000000..a94d98e --- /dev/null +++ b/tests/resolve.test.ts @@ -0,0 +1,190 @@ +import { Cacheable, MemoryBucket } from '../src' +import type { IBaseMeta, IBucket } from '../src' + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +describe('resolve', () => { + it('cache-only: hit returns cached meta; miss writes fresh meta', async () => { + const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) + + const before = Date.now() + const a = await cache.resolve(async () => 'v', 'k') + const after = Date.now() + + expect(a.storedAt).toBeGreaterThanOrEqual(before) + expect(a.storedAt).toBeLessThanOrEqual(after) + + await wait(10) + const b = await cache.resolve(async () => 'v2', 'k') + expect(b.storedAt).toBe(a.storedAt) + }) + + it('network-only: every call writes fresh meta', async () => { + const cache = new Cacheable('test', { + buckets: [new MemoryBucket()], + policy: 'network-only', + }) + + const a = await cache.resolve(async () => 'v', 'k') + await wait(10) + const b = await cache.resolve(async () => 'v', 'k') + expect(b.storedAt).toBeGreaterThan(a.storedAt) + }) + + it('network-only-non-concurrent: every serial call writes fresh meta', async () => { + const cache = new Cacheable('test', { + buckets: [new MemoryBucket()], + policy: 'network-only-non-concurrent', + }) + + const a = await cache.resolve(async () => 'v', 'k') + await wait(10) + const b = await cache.resolve(async () => 'v', 'k') + expect(b.storedAt).toBeGreaterThan(a.storedAt) + }) + + it('max-age: meta is stable within window, refreshes when expired', async () => { + const cache = new Cacheable('test', { + buckets: [new MemoryBucket()], + policy: 'max-age', + maxAge: 100, + }) + + const a = await cache.resolve(async () => 'v', 'k') + const b = await cache.resolve(async () => 'v', 'k') + expect(b.storedAt).toBe(a.storedAt) + + await wait(200) + const c = await cache.resolve(async () => 'v', 'k') + expect(c.storedAt).toBeGreaterThan(a.storedAt) + }) + + it('SWR: returns stale meta immediately, background revalidation updates it', async () => { + const cache = new Cacheable('test', { + buckets: [new MemoryBucket()], + policy: 'stale-while-revalidate', + maxAge: 50, + }) + + const a = await cache.resolve(async () => 'v', 'k') + await wait(150) + + // Stale: returns cached meta immediately and kicks off a background refetch. + const b = await cache.resolve(async () => 'v', 'k') + expect(b.storedAt).toBe(a.storedAt) + + // Give the background refetch plenty of time to land. + await wait(150) + + const c = await cache.resolve(async () => 'v', 'k') + expect(c.storedAt).toBeGreaterThan(a.storedAt) + }) + + it('concurrent remember + resolve share one producer call and one storedAt', async () => { + const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + const [value, meta] = await Promise.all([ + cache.remember(slow, 'k'), + cache.resolve(slow, 'k'), + ]) + + expect(calls).toBe(1) + expect(value).toBe('v') + + const peeked = await cache.meta('k') + expect(peeked?.storedAt).toBe(meta.storedAt) + }) + + it('resolve-returned meta matches cache.meta on fresh entries', async () => { + const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) + const meta = await cache.resolve(async () => 'v', 'k') + const peeked = await cache.meta('k') + expect(peeked).toEqual(meta) + }) + + it('logs HIT/MISS with elapsed time, same shape as remember', async () => { + const log = jest.fn() + const cache = new Cacheable('test', { + buckets: [new MemoryBucket()], + logger: { log }, + }) + + await cache.resolve(async () => 'v', 'k') + expect(log).lastCalledWith( + expect.stringMatching(/^Cacheable "k": MISS \d+(\.\d+)?ms$/), + ) + + await cache.resolve(async () => 'v', 'k') + expect(log).lastCalledWith( + expect.stringMatching(/^Cacheable "k": HIT \d+(\.\d+)?ms$/), + ) + }) + + describe('cascade', () => { + interface WriteCall { + key: string + value: unknown + meta: IBaseMeta | undefined + } + + class FakeBucket implements IBucket { + store = new Map() + writeCalls: WriteCall[] = [] + + constructor(seed?: { key: string; value: unknown; meta: IBaseMeta }) { + if (seed) + this.store.set(seed.key, { value: seed.value, meta: seed.meta }) + } + + async read(key: string): Promise<{ value: T } | undefined> { + const entry = this.store.get(key) + return entry === undefined ? undefined : { value: entry.value as T } + } + + async write(key: string, value: T, meta?: IBaseMeta): Promise { + this.writeCalls.push({ key, value, meta }) + this.store.set(key, { value, meta: meta ?? { storedAt: Date.now() } }) + } + + async meta(key: string): Promise { + return this.store.get(key)?.meta + } + + async delete(key: string): Promise { + this.store.delete(key) + } + + async clear(): Promise { + this.store.clear() + } + } + + it('L1 miss + L2 hit: resolve returns the L2 meta (storedAt preserved)', async () => { + const l2Meta: IBaseMeta = { storedAt: 12345 } + const l1 = new FakeBucket() + const l2 = new FakeBucket({ key: 'test:k', value: 'v', meta: l2Meta }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) + + const meta = await cache.resolve(async () => 'fresh', 'k') + expect(meta.storedAt).toBe(12345) + }) + + it('both miss: resolve returns L1 meta after write; storedAt agrees with what L2 received', async () => { + const l1 = new FakeBucket() + const l2 = new FakeBucket() + const cache = new Cacheable('test', { buckets: [l1, l2] }) + + const meta = await cache.resolve(async () => 'fresh', 'k') + + expect(l2.writeCalls.length).toBe(1) + expect(l2.writeCalls[0]!.meta?.storedAt).toBe(meta.storedAt) + }) + }) +}) From c0ac95f2f2090ed96c834a62fc2142de8f7ddb48 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:58:16 +0200 Subject: [PATCH 16/23] Mint cascade write meta in the engine, fan out in parallel (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mint cascade write meta in the engine, fan out in parallel The engine now mints `{ storedAt: Date.now() }` upfront and writes to every bucket in parallel, instead of writing L1 first, probing it for synthesized meta, and only then fanning out to L2+. Read and write paths now share the same rule: the engine owns meta, buckets persist `storedAt` verbatim. Bucket meta synthesis remains the standalone-use fallback. Co-Authored-By: Claude Opus 4.7 (1M context) * Split engine meta from bucket view (TView) Previously `Cacheable` mixed engine probing data (`storedAt`) with bucket-defined sidecar fields (etag, url, ...) in a single typed extension. This split makes the two concerns orthogonal: - `BucketEntryMeta = { storedAt: number }` — fixed, non-generic, engine-internal. Used by `bucket.meta()` for freshness probing. - `TView` — bucket-defined user-facing projection. Surfaced exclusively through a new `bucket.resolve(key)` method and `cache.resolve(...)`. `bucket.write(meta)` is now required (no synthesis branch) and non-generic. `cache.meta()` is removed; users wanting `storedAt` include it in `TView`. The engine carries only `storedAt` between buckets — bucket-specific fields no longer cross bucket boundaries. Co-Authored-By: Claude Opus 4.7 (1M context) * Fix prettier formatting on three files Co-Authored-By: Claude Opus 4.7 (1M context) * Split value and view cascades; refresh stale layers on fill cache.resolve() now uses a separate view-cascade that skips the value read on the L1 hot path, so a filesystem bucket holding 5 MB of bytes returns just the URL without opening the file. bucket.resolve() returns { view } | undefined (mirroring read's wrapper) so void-TView buckets distinguish "present, no projection" from "absent" — which lets the engine race-heal undefined-after-meta-hit and throw a strict-mode error on undefined-after-write. cascadeFill also now refreshes stale lower layers under max-age instead of skipping them as long as anything is present. Co-Authored-By: Claude Opus 4.7 (1M context) * Add two-tier dedup: per-mode policy outer, per-key producer inner Restores the per-policy outer dedup that the value/view split lost, but keys it by mode (`${fullKey}:value` vs `${fullKey}:view`) so cross-mode callers don't share an inflight with mismatched shape. The producer dedup stays keyed by fullKey alone, so concurrent cache.remember + cache.resolve still trigger one producer call. Concurrent same-mode hit callers now do 1 meta probe per layer again, not N. New tests assert metaCalls counts to lock the behavior in. Co-Authored-By: Claude Opus 4.7 (1M context) * Rename #dedupInto to #dedup Co-Authored-By: Claude Opus 4.7 (1M context) * Rename IBucket.resolve to IBucket.view The bucket-level method now matches the TView generic and the { view } wrapper it returns. cache.resolve() stays as the public API — it "resolves a view from the buckets," with the engine calling bucket.view() under the hood. Also picks up some internal cleanups: CascadeFn type, #dedupKey helper, #cascadeRead / #cascadeResolve naming that mirrors cache.remember / cache.resolve. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .eslintrc.js | 1 + README.md | 122 ++++++---- src/Cacheable.ts | 244 +++++++++++++------- src/buckets/MemoryBucket.ts | 19 +- src/index.ts | 2 +- src/types.ts | 56 +++-- tests/buckets/memoryBucket.test.ts | 32 ++- tests/cascade.test.ts | 140 +++++++++--- tests/index.test.ts | 34 ++- tests/namespace.test.ts | 8 +- tests/resolve.test.ts | 356 +++++++++++++++++++++-------- 11 files changed, 682 insertions(+), 332 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9bdc4e1..4c74b36 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,6 +11,7 @@ module.exports = { ], rules: { '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, ignorePatterns: ['dist'], } diff --git a/README.md b/README.md index bd8393a..ce11fb3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A small, typed cache with composable storage buckets and a handful of cache poli - **Wrap any async call** with `cache.remember(...)` — `remember` is both getter and setter. - **Multilayer storage**: stack a fast in-memory L1 with any L2 you write (filesystem, Redis, S3, …). Reads cascade L1 → Ln; on any hit, missing layers are back-filled. - **Five cache policies**, including `stale-while-revalidate`, with concurrency-safe deduplication where it makes sense. -- **Fully typed**, with a generic `TMeta` parameter for sidecar metadata (etag, ttl, version, …). +- **Fully typed**, with a generic `TView` parameter for the bucket's user-facing projection (a URL from a filesystem bucket, a presigned link from S3, …) surfaced via `cache.resolve(...)`. - **Required namespace prefix** so multiple instances can share a bucket without collisions. - Helper to build cache keys. - Works in browser and Node.js. **No dependencies.** @@ -25,11 +25,10 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [Installation](#installation) - [Usage](#usage) - [API](#api) - - [new Cacheable(namespace, options)](#new-cacheablenamespace-options-cacheabletmeta) + - [new Cacheable(namespace, options)](#new-cacheablenamespace-options-cacheabletview) - [cache.remember(resource, key)](#cacherememberresource-key-promiset) - - [cache.resolve(resource, key)](#cacheresolveresource-key-promisetmeta) + - [cache.resolve(resource, key)](#cacheresolveresource-key-promisetview) - [cache.delete(key) / cache.clear()](#cachedeletekey-promisevoid--cacheclear-promisevoid) - - [cache.meta(key)](#cachemetakey-promisetmeta--undefined) - [Cacheable.key(...args)](#cacheablekeyargs-string) - [Buckets](#buckets) - [Cache Policies](#cache-policies) @@ -76,16 +75,16 @@ await getWeather() // hit — cached ## API -### `new Cacheable(namespace, options): Cacheable` +### `new Cacheable(namespace, options): Cacheable` ```ts -new Cacheable( +new Cacheable( namespace: string, - options: CacheableOptions, + options: CacheableOptions, ) -type CacheableOptions = { - buckets: IBucket[] // REQUIRED, L1 first +type CacheableOptions = { + buckets: IBucket[] // REQUIRED, L1 first logger?: ILogger // default: undefined (no logging) } & ( | { policy?: 'cache-only' } // default @@ -102,16 +101,23 @@ type CacheableOptions = { Resolves to the cached value if present (subject to policy); otherwise calls `resource()` and writes to every bucket. -### `cache.resolve(resource, key): Promise` +### `cache.resolve(resource, key): Promise` -Same fresh-or-fetch behavior as `remember`, but returns the bucket's meta instead of the producer's value. Useful when the projection a bucket exposes through `TMeta` is what callers actually need — for example, a filesystem bucket that fetches and stores a remote image as bytes, then exposes a local URL on its meta: +Same fresh-or-fetch behavior as `remember`, but returns the L1 bucket's view instead of the producer's value. Use this when the bucket produces a domain-specific projection that callers actually need — a filesystem bucket exposing a local URL after caching the bytes, a CDN bucket returning a presigned link, an IndexedDB bucket returning an `ObjectURL`: ```ts -interface FilesystemMeta extends IBaseMeta { +interface UrlView { url: string } -const cache = new Cacheable('images', { +class FilesystemBucket implements IBucket { + // read / write / meta / delete / clear … + async view(key: string): Promise<{ view: UrlView } | undefined> { + /* return { view: { url: pathFor(key) } } when the entry exists */ + } +} + +const cache = new Cacheable('images', { buckets: [new FilesystemBucket()], }) @@ -121,15 +127,15 @@ const { url } = await cache.resolve( ) ``` -`resolve` and `remember` share the same in-flight registry: a concurrent pair against the same key triggers `resource()` once and both observers see the same `storedAt`. Unlike `cache.meta(key)`, `resolve` honors the cache policy — a stale entry will trigger a producer call (or a background revalidation under `stale-while-revalidate`). +`resolve` runs a separate **view-cascade**: the engine probes `meta()` on every layer and, on an L1 hit where no other layer needs back-filling, calls `bucket.view()` directly without ever reading the value. A filesystem bucket holding a 5 MB ArrayBuffer never opens the file on the hot path — only the projection (URL) is materialized. -### `cache.delete(key): Promise` / `cache.clear(): Promise` +`resolve` and `remember` share the same in-flight registry: a concurrent pair against the same key triggers `resource()` once. `resolve` honors the cache policy — a stale entry will trigger a producer call (or a background revalidation under `stale-while-revalidate`). -`delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. Both are async — `await` them. +For buckets without a meaningful projection (e.g. the built-in `MemoryBucket`), `TView = void` and `cache.resolve()` resolves to `undefined`. Use `cache.remember()` instead in that case. -### `cache.meta(key): Promise` +### `cache.delete(key): Promise` / `cache.clear(): Promise` -Returns the meta from the highest-priority layer that has the key, or `undefined` if no layer has it. **Bypasses the policy** — it returns whatever the cache holds, fresh or stale. Reach for `cache.resolve(...)` instead when you want a meta that has been refreshed against the policy; reach for `cache.meta(key)` for raw inspection (debugging, eviction logic, presence checks). +`delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. Both are async — `await` them. ### `Cacheable.key(...args): string` @@ -146,27 +152,30 @@ A bucket is a single storage tier — memory, Redis, disk, S3, anything you can ### `IBucket` contract ```ts -interface IBaseMeta { +interface BucketEntryMeta { storedAt: number } -interface IBucket { +interface IBucket { read(key: string): Promise<{ value: T } | undefined> - write(key: string, value: T, meta?: TMeta): Promise - meta(key: string): Promise + write(key: string, value: T, meta: BucketEntryMeta): Promise + meta(key: string): Promise + view(key: string): Promise<{ view: TView } | undefined> delete(key: string): Promise clear(): Promise } ``` +The engine carries only `BucketEntryMeta` (i.e. `storedAt`) between buckets. `TView` is what the bucket exposes through `cache.resolve(...)` — it never crosses bucket boundaries. + Rules: - `meta` MUST be cheap. The engine probes it on every layer for every read. A typical L2 keeps a sidecar (file, table, key/value entry) so probes don't hit the value blob. - `read` returns `undefined` for absence and `{ value }` for presence — the wrapper lets buckets store entries whose value is itself `undefined` without colliding with the absence signal. -- When `write` receives a `meta`, the bucket MUST persist `meta.storedAt` verbatim (other fields MAY be transformed). This keeps `max-age` semantics coherent across layers. -- When `write` is called with no `meta`, the bucket MUST synthesize one with `storedAt: Date.now()`. +- `write` MUST persist `meta.storedAt` verbatim. The engine always supplies a meta; there is no synthesis branch. +- `view` returns `undefined` for absence and `{ view }` for presence. The same wrapper pattern as `read` lets `TView = void` buckets distinguish "entry present, no projection" (`{ view: undefined }`) from "entry absent" (`undefined`). The engine treats absence after a meta-probe hit as a race and heals it by running the producer; absence after a successful cascade write is a strict-mode error and the engine throws. - `clear` MUST remove every entry the bucket manages. -- Any throw from any bucket rejects the surrounding `remember()` call. There is no per-bucket error suppression. +- Any throw from any bucket rejects the surrounding `remember()` / `resolve()` call. There is no per-bucket error suppression. ### Cascade behavior @@ -178,9 +187,10 @@ const cache = new Cacheable('app', { }) ``` -- **Read**: probe `meta()` on every layer in parallel; the first layer satisfying the freshness predicate is the hit. Read its value, then back-fill every layer above and below that is still missing the key, using the hit layer's meta — `storedAt` is preserved everywhere. -- **Miss + `resource()`**: write to L1 with no meta (L1 synthesizes its own), read meta back from L1, then write to L2..Ln with that exact meta. All layers converge on the same `storedAt`. -- **Stale L1 + fresh L2** (under `max-age`): the freshness predicate filters per-layer, so the engine returns the fresh L2 value and back-fills L1. +- **Read (`cache.remember`)**: probe `meta()` on every layer in parallel; the first layer satisfying the freshness predicate is the hit. Read its value, then back-fill every layer that is missing OR stale, using the hit layer's `storedAt` verbatim. +- **Read (`cache.resolve`)**: probe `meta()` on every layer; on an L1 hit where no other layer needs back-filling, call `bucket.view()` directly without reading the value. When a deeper layer hits or upper layers need refilling, the value is read from the hit layer to fill the others, then L1's view is returned. +- **Miss + `resource()`**: the engine mints a single `{ storedAt }` and writes to every layer in parallel — all layers converge on the same `storedAt`. +- **Stale L1 + fresh L2** (under `max-age`): the freshness predicate filters per-layer, so the engine returns the fresh L2 value AND refreshes L1 with L2's value and `storedAt` verbatim. ### Built-in `MemoryBucket` @@ -197,18 +207,21 @@ const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) Implement `IBucket`. The contract is small enough that filesystem, Redis, IndexedDB, or S3 buckets are easy to add. ```ts -import type { IBucket, IBaseMeta } from 'cacheables' +import type { IBucket, BucketEntryMeta } from 'cacheables' class FileSystemBucket implements IBucket { async read(key: string): Promise<{ value: T } | undefined> { /* … */ } - async write(key: string, value: T, meta?: IBaseMeta): Promise { - /* … */ + async write(key: string, value: T, meta: BucketEntryMeta): Promise { + /* persist the value AND meta.storedAt verbatim */ } - async meta(key: string): Promise { + async meta(key: string): Promise { /* … */ } + async view(key: string): Promise<{ view: void } | undefined> { + /* no projection — return { view: undefined } if the entry exists */ + } async delete(key: string): Promise { /* … */ } @@ -218,27 +231,46 @@ class FileSystemBucket implements IBucket { } ``` -### Typed metadata (`TMeta`) +### Bucket views (`TView`) -`Cacheable` is generic in `TMeta`. Extend `IBaseMeta` to carry sidecar fields: +`IBucket` is generic in `TView` — the projection the bucket exposes through `cache.resolve(...)`. A filesystem bucket caching remote bytes can publish the local URL it stored them at: ```ts -import { Cacheable, type IBaseMeta, type IBucket } from 'cacheables' +import { Cacheable, type IBucket, type BucketEntryMeta } from 'cacheables' -interface ETagMeta extends IBaseMeta { - etag: string +interface UrlView { + url: string } -class ETagBucket implements IBucket { - // read / write / meta / delete / clear … +class FilesystemBucket implements IBucket { + async read(key: string): Promise<{ value: T } | undefined> { + /* … */ + } + async write(key: string, value: T, meta: BucketEntryMeta): Promise { + /* persist value to disk under a deterministic path */ + } + async meta(key: string): Promise { + /* read the sidecar */ + } + async view(key: string): Promise<{ view: UrlView } | undefined> { + /* return { view: { url: pathFor(key) } } when the entry exists */ + } + async delete(key: string): Promise { + /* … */ + } + async clear(): Promise { + /* … */ + } } -const cache = new Cacheable('app', { buckets: [new ETagBucket()] }) +const cache = new Cacheable('images', { + buckets: [new FilesystemBucket()], +}) -const meta = await cache.meta('user:42') // typed as ETagMeta | undefined +const { url } = await cache.resolve(() => fetchBytes(remoteUrl), remoteUrl) ``` -Every bucket passed to the constructor must satisfy `IBucket`, enforced by the compiler. The built-in `MemoryBucket` only implements `IBucket`, so it can't be used in a `Cacheable` instance with a custom `TMeta` — write a custom bucket (or wrap `MemoryBucket`) when you need extended metadata. +Every bucket passed to the constructor must satisfy `IBucket`, enforced by the compiler. The built-in `MemoryBucket` is `IBucket`, so it can't be used in a `Cacheable` with a non-`void` `TView` — write a bucket whose `view(key)` produces the projection you want. ## Cache Policies @@ -425,7 +457,7 @@ const tenantA = new Cacheable('tenant-a', { buckets: [bucket] }) const tenantB = new Cacheable('tenant-b', { buckets: [bucket] }) ``` -`delete` and `meta` respect the namespace; `clear()` wipes the entire underlying bucket — it has no notion of which keys belong to which namespace. Reach for `clear()` only when you mean _everything_. +`delete` respects the namespace; `clear()` wipes the entire underlying bucket — it has no notion of which keys belong to which namespace. Reach for `clear()` only when you mean _everything_. ## Migrating from v2 → v3 @@ -467,7 +499,7 @@ Breaking changes: - **`enabled` option removed.** If you need to bypass the cache, call `resource()` directly instead of `cache.remember(...)`. - **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. - **`delete` and `clear` are async.** They now return `Promise` — add `await`. -- **`isCached` removed.** Use `cache.meta(key)` instead — it returns `undefined` when the key is absent and the meta object otherwise. +- **`isCached` removed.** v3 has no public presence-check API; if you need one, query your bucket directly (e.g. `await bucket.meta(fullKey)`). - **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Each `remember()` call emits a single formatted message (`Cacheable "": HIT|MISS `) instead of `console.time` / `timeEnd`. - **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `policy`, and `logger`; `namespace` is the constructor's first positional argument). - **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. @@ -475,7 +507,7 @@ Breaking changes: What's new: - **Multilayer storage.** Pass several buckets to compose tiers (e.g. `[memory, filesystem]`); reads cascade L1 → Ln and back-fill missing layers on every hit. -- **Typed sidecar metadata.** `Cacheable` is generic; bucket implementations can persist fields like `etag` or `ttl` and `cache.meta(key)` returns them typed. Plain `new Cacheable(namespace, { buckets })` defaults to `Cacheable` and needs no type changes. +- **Bucket views.** `Cacheable` is generic; a bucket can publish a domain-specific projection (a local URL, a presigned link, an `ObjectURL`) via its `view()` method, returned by `cache.resolve(...)`. Plain `new Cacheable(namespace, { buckets })` defaults to `Cacheable` and needs no type changes. ## License diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 8704459..23a7cb5 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -1,21 +1,29 @@ import type { + BucketEntryMeta, CacheableOptions, - IBaseMeta, IBucket, ILogger, Policy, } from './types' -export class Cacheable { +type FreshnessPredicate = (meta: BucketEntryMeta) => boolean + +type CascadeFn = ( + fullKey: string, + isFresh?: FreshnessPredicate, +) => Promise<{ result: R; meta: BucketEntryMeta } | undefined> + +export class Cacheable { logger: ILogger | undefined #policy: Policy #maxAge: number | undefined - #buckets: IBucket[] + #buckets: IBucket[] #namespace: string - #inflight = new Map>() + #policyInflight = new Map>() + #producerInflight = new Map>() - constructor(namespace: string, options: CacheableOptions) { + constructor(namespace: string, options: CacheableOptions) { if (!options.buckets || options.buckets.length === 0) { throw new Error('At least one bucket is required') } @@ -38,95 +46,116 @@ export class Cacheable { return `${this.#namespace}:${key}` } + #dedupKey(key: string, type: 'value' | 'view'): string { + return `${this.#namespace}:${key}:${type}` + } + async delete(key: string): Promise { const fullKey = this.#fullKey(key) await Promise.all(this.#buckets.map((b) => b.delete(fullKey))) } async clear(): Promise { - this.#inflight.clear() + this.#policyInflight.clear() + this.#producerInflight.clear() await Promise.all(this.#buckets.map((b) => b.clear())) } - async meta(key: string): Promise { - const fullKey = this.#fullKey(key) - const probes = await this.#cascadeProbe(fullKey) - return probes.find((m) => m !== undefined) - } - async remember(resource: () => Promise, key: string): Promise { const { logger } = this const start = logger ? performance.now() : 0 const fullKey = this.#fullKey(key) - const { value, hit } = await this.#runPolicy(resource, fullKey) + const dedupKey = this.#dedupKey(key, 'value') + const { result, hit } = await this.#runPolicy( + resource, + fullKey, + dedupKey, + (k, isFresh) => this.#cascadeRead(k, isFresh), + async (value) => value, + ) if (logger) { const elapsed = Math.round((performance.now() - start) * 10) / 10 logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) } - return value + return result } - async resolve(resource: () => Promise, key: string): Promise { + async resolve(resource: () => Promise, key: string): Promise { const { logger } = this const start = logger ? performance.now() : 0 const fullKey = this.#fullKey(key) - const { meta, hit } = await this.#runPolicy(resource, fullKey) + const dedupKey = this.#dedupKey(key, 'view') + const { result, hit } = await this.#runPolicy( + resource, + fullKey, + dedupKey, + (k, isFresh) => this.#cascadeResolve(k, isFresh), + () => this.#viewFromL1(fullKey), + ) if (logger) { const elapsed = Math.round((performance.now() - start) * 10) / 10 logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) } - return meta + return result } - async #runPolicy( + async #viewFromL1(fullKey: string): Promise { + const wrapped = await this.#buckets[0]!.view(fullKey) + if (wrapped === undefined) { + throw new Error( + `Cacheable: L1 bucket returned no view for "${fullKey}" after a successful cascade write`, + ) + } + return wrapped.view + } + + async #runPolicy( resource: () => Promise, fullKey: string, - ): Promise<{ value: T; hit: boolean; meta: TMeta }> { + dedupKey: string, + cascadeFn: CascadeFn, + fromValue: (value: T) => Promise, + ): Promise<{ result: R; hit: boolean }> { switch (this.#policy) { case 'cache-only': { - return this.#dedup(fullKey, async () => { - const cached = await this.#cascadeRead(fullKey) - if (cached) - return { value: cached.value, hit: true, meta: cached.meta } - const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } + return this.#dedupPolicy(dedupKey, async () => { + const cached = await cascadeFn(fullKey) + if (cached) return { result: cached.result, hit: true } + const value = await this.#produceAndWrite(fullKey, resource) + return { result: await fromValue(value), hit: false } }) } case 'network-only': { const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } + await this.#cascadeWrite(fullKey, value) + return { result: await fromValue(value), hit: false } } case 'network-only-non-concurrent': { - return this.#dedup(fullKey, async () => { - const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } + return this.#dedupPolicy(dedupKey, async () => { + const value = await this.#produceAndWrite(fullKey, resource) + return { result: await fromValue(value), hit: false } }) } case 'max-age': { const maxAge = this.#maxAge as number - return this.#dedup(fullKey, async () => { - const cached = await this.#cascadeRead( + return this.#dedupPolicy(dedupKey, async () => { + const cached = await cascadeFn( fullKey, (m) => Date.now() - m.storedAt <= maxAge, ) - if (cached) - return { value: cached.value, hit: true, meta: cached.meta } - const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } + if (cached) return { result: cached.result, hit: true } + const value = await this.#produceAndWrite(fullKey, resource) + return { result: await fromValue(value), hit: false } }) } case 'stale-while-revalidate': { - const cached = await this.#cascadeRead(fullKey) + const cached = await cascadeFn(fullKey) const maxAge = this.#maxAge const isStale = !cached || @@ -134,51 +163,72 @@ export class Cacheable { Date.now() - cached.meta.storedAt > maxAge if (cached && !isStale) { - return { value: cached.value, hit: true, meta: cached.meta } + return { result: cached.result, hit: true } } if (cached && isStale) { - this.#dedup(fullKey, async () => { - const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } - }).catch(() => { + this.#produceAndWrite(fullKey, resource).catch(() => { /* swallow background revalidation errors */ }) - return { value: cached.value, hit: true, meta: cached.meta } + return { result: cached.result, hit: true } } - return this.#dedup(fullKey, async () => { - const value = await resource() - const meta = await this.#cascadeWrite(fullKey, value) - return { value, hit: false, meta } - }) + const value = await this.#produceAndWrite(fullKey, resource) + return { result: await fromValue(value), hit: false } } } } - #dedup(fullKey: string, run: () => Promise): Promise { - const existing = this.#inflight.get(fullKey) as Promise | undefined + async #produceAndWrite( + fullKey: string, + resource: () => Promise, + ): Promise { + return this.#dedup(this.#producerInflight, fullKey, async () => { + const value = await resource() + await this.#cascadeWrite(fullKey, value) + return value + }) + } + + #dedupPolicy(dedupKey: string, run: () => Promise): Promise { + return this.#dedup(this.#policyInflight, dedupKey, run) + } + + #dedup( + inflight: Map>, + key: string, + run: () => Promise, + ): Promise { + const existing = inflight.get(key) as Promise | undefined if (existing) return existing const p = run().finally(() => { - if (this.#inflight.get(fullKey) === p) this.#inflight.delete(fullKey) + if (inflight.get(key) === p) inflight.delete(key) }) - this.#inflight.set(fullKey, p) + inflight.set(key, p) return p } - async #cascadeProbe(fullKey: string): Promise<(TMeta | undefined)[]> { + async #cascadeProbe( + fullKey: string, + ): Promise<(BucketEntryMeta | undefined)[]> { return Promise.all(this.#buckets.map((b) => b.meta(fullKey))) } + #findHitIdx( + probes: (BucketEntryMeta | undefined)[], + isFresh?: FreshnessPredicate, + ): number { + return probes.findIndex( + (m) => m !== undefined && (isFresh ? isFresh(m) : true), + ) + } + async #cascadeRead( fullKey: string, - isFresh?: (meta: TMeta) => boolean, - ): Promise<{ value: T; meta: TMeta } | undefined> { + isFresh?: FreshnessPredicate, + ): Promise<{ result: T; meta: BucketEntryMeta } | undefined> { const probes = await this.#cascadeProbe(fullKey) - const hitIdx = probes.findIndex( - (m) => m !== undefined && (isFresh ? isFresh(m) : true), - ) + const hitIdx = this.#findHitIdx(probes, isFresh) if (hitIdx === -1) return undefined const bucket = this.#buckets[hitIdx]! @@ -186,40 +236,68 @@ export class Cacheable { if (result === undefined) return undefined const value = result.value - const hitMeta = probes[hitIdx] as TMeta - await this.#cascadeFill(fullKey, value, hitMeta, probes, hitIdx) - return { value, meta: hitMeta } + const hitMeta = probes[hitIdx]! + await this.#cascadeFill(fullKey, value, hitMeta, probes, hitIdx, isFresh) + return { result: value, meta: hitMeta } + } + + async #cascadeResolve( + fullKey: string, + isFresh?: FreshnessPredicate, + ): Promise<{ result: TView; meta: BucketEntryMeta } | undefined> { + const probes = await this.#cascadeProbe(fullKey) + const hitIdx = this.#findHitIdx(probes, isFresh) + if (hitIdx === -1) return undefined + + const hitMeta = probes[hitIdx]! + const needsFill = probes.some( + (m, i) => + i !== hitIdx && (m === undefined || (isFresh ? !isFresh(m) : false)), + ) + + if (!needsFill) { + const wrapped = await this.#buckets[0]!.view(fullKey) + if (wrapped === undefined) return undefined + return { result: wrapped.view, meta: hitMeta } + } + + const result = await this.#buckets[hitIdx]!.read(fullKey) + if (result === undefined) return undefined + + await this.#cascadeFill( + fullKey, + result.value, + hitMeta, + probes, + hitIdx, + isFresh, + ) + const wrapped = await this.#buckets[0]!.view(fullKey) + if (wrapped === undefined) return undefined + return { result: wrapped.view, meta: hitMeta } } async #cascadeFill( fullKey: string, value: T, - hitMeta: TMeta, - probes: (TMeta | undefined)[], + hitMeta: BucketEntryMeta, + probes: (BucketEntryMeta | undefined)[], hitIdx: number, + isFresh?: FreshnessPredicate, ): Promise { + const meta: BucketEntryMeta = { storedAt: hitMeta.storedAt } const writes: Promise[] = [] for (let i = 0; i < this.#buckets.length; i++) { if (i === hitIdx) continue - if (probes[i] !== undefined) continue - writes.push(this.#buckets[i]!.write(fullKey, value, hitMeta)) + const probe = probes[i] + if (probe !== undefined && (!isFresh || isFresh(probe))) continue + writes.push(this.#buckets[i]!.write(fullKey, value, meta)) } if (writes.length > 0) await Promise.all(writes) } - async #cascadeWrite(fullKey: string, value: T): Promise { - const [l1, ...rest] = this.#buckets - if (l1 === undefined) { - throw new Error('At least one bucket is required') - } - await l1.write(fullKey, value) - const meta = await l1.meta(fullKey) - if (meta === undefined) { - throw new Error('L1 bucket did not persist meta after write') - } - if (rest.length > 0) { - await Promise.all(rest.map((b) => b.write(fullKey, value, meta))) - } - return meta + async #cascadeWrite(fullKey: string, value: T): Promise { + const meta: BucketEntryMeta = { storedAt: Date.now() } + await Promise.all(this.#buckets.map((b) => b.write(fullKey, value, meta))) } } diff --git a/src/buckets/MemoryBucket.ts b/src/buckets/MemoryBucket.ts index bcdd56f..7031d1c 100644 --- a/src/buckets/MemoryBucket.ts +++ b/src/buckets/MemoryBucket.ts @@ -1,24 +1,25 @@ -import type { IBaseMeta, IBucket } from '../types' +import type { BucketEntryMeta, IBucket } from '../types' -export class MemoryBucket implements IBucket { - #store = new Map() +export class MemoryBucket implements IBucket { + #store = new Map() async read(key: string): Promise<{ value: T } | undefined> { const entry = this.#store.get(key) return entry === undefined ? undefined : { value: entry.value as T } } - async write(key: string, value: T, meta?: IBaseMeta): Promise { - this.#store.set(key, { - value, - meta: meta ?? ({ storedAt: Date.now() } as IBaseMeta), - }) + async write(key: string, value: T, meta: BucketEntryMeta): Promise { + this.#store.set(key, { value, meta }) } - async meta(key: string): Promise { + async meta(key: string): Promise { return this.#store.get(key)?.meta } + async view(key: string): Promise<{ view: void } | undefined> { + return this.#store.has(key) ? { view: undefined } : undefined + } + async delete(key: string): Promise { this.#store.delete(key) } diff --git a/src/index.ts b/src/index.ts index f36a879..a69e892 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ export { Cacheable } from './Cacheable' export { ConsoleLogger } from './ConsoleLogger' export { MemoryBucket } from './buckets/MemoryBucket' export type { + BucketEntryMeta, CacheOptions, CacheableOptions, - IBaseMeta, IBucket, ILogger, } from './types' diff --git a/src/types.ts b/src/types.ts index 2f1fbab..4980e96 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,16 +1,29 @@ /** - * Base shape for all bucket metadata. Buckets MAY extend this with - * additional sidecar fields (etag, ttl, version, etc.) provided their - * extension is captured by the `TMeta` generic of `Cacheable`. + * Engine-internal probe shape. The engine mints a `BucketEntryMeta` + * for every cascade write and reads it back via `bucket.meta(key)` to + * decide hits and apply freshness predicates. Bucket implementers + * type their `meta()` method against this shape; consumers of + * `Cacheable` never see it. */ -export interface IBaseMeta { +export interface BucketEntryMeta { storedAt: number } /** * Bucket contract. Buckets back the layers (L1, L2, ...) of a * `Cacheable` instance. Reads cascade L1 → Ln; on any hit the engine - * fills missing layers with the hit value preserving `meta.storedAt`. + * fills missing or stale layers with the hit value preserving + * `meta.storedAt`. + * + * `TView` is the bucket's user-facing projection — what + * `cache.resolve()` returns. A bucket without a meaningful projection + * (e.g. `MemoryBucket`) sets `TView = void` and returns + * `{ view: undefined }` from `view()` when the entry is present. + * + * The engine maintains two parallel cascade paths: `cache.remember()` + * uses `read` (value-cascade), `cache.resolve()` uses `view` + * (view-cascade). The view-cascade hot path skips the value read + * entirely on L1 hits when no other layer needs back-filling. * * Contracts: * - `meta` MUST be cheap (engine probes it on every layer per read). @@ -18,19 +31,23 @@ export interface IBaseMeta { * when present — the wrapper exists so buckets can store entries * whose value is itself `undefined` without colliding with the * absence signal. - * - When `write` receives a `meta`, the bucket MUST persist - * `meta.storedAt` verbatim. Other fields MAY be transformed. - * - When `write` receives no `meta`, the bucket MUST synthesize one - * with `storedAt: Date.now()` (and any other `TMeta` fields it knows - * how to populate). + * - `write` MUST persist `meta.storedAt` verbatim. + * - `view` returns `undefined` when the entry is absent and + * `{ view }` when present. The wrapper mirrors `read`: it lets + * `TView = void` buckets distinguish "entry present, no projection" + * (`{ view: undefined }`) from "entry absent" (`undefined`). The + * engine treats absence after a meta-probe hit as a race and heals + * it like the read path; absence after a successful cascade + * write/fill is a strict-mode error and the engine throws. * - `clear` MUST remove every entry the bucket manages. - * - All five methods MAY throw on infrastructure errors. The engine + * - All six methods MAY throw on infrastructure errors. The engine * treats throws as fatal (strict mode). */ -export interface IBucket { +export interface IBucket { read(key: string): Promise<{ value: T } | undefined> - write(key: string, value: T, meta?: TMeta): Promise - meta(key: string): Promise + write(key: string, value: T, meta: BucketEntryMeta): Promise + meta(key: string): Promise + view(key: string): Promise<{ view: TView } | undefined> delete(key: string): Promise clear(): Promise } @@ -100,13 +117,12 @@ export type Policy = export type CacheOptions = CacheOptionsBase & PolicyOptions /** - * Constructor options for `Cacheable`. The namespace is passed + * Constructor options for `Cacheable`. The namespace is passed * as a positional argument; this bag carries everything else. * * `buckets` is required; the array is L1 first. */ -export type CacheableOptions = - CacheOptionsBase & - PolicyOptions & { - buckets: IBucket[] - } +export type CacheableOptions = CacheOptionsBase & + PolicyOptions & { + buckets: IBucket[] + } diff --git a/tests/buckets/memoryBucket.test.ts b/tests/buckets/memoryBucket.test.ts index c470168..ede8c9e 100644 --- a/tests/buckets/memoryBucket.test.ts +++ b/tests/buckets/memoryBucket.test.ts @@ -1,18 +1,7 @@ import { MemoryBucket } from '../../src' describe('MemoryBucket', () => { - it('write without meta synthesizes storedAt: Date.now()', async () => { - const a = new MemoryBucket() - const before = Date.now() - await a.write('k', 'v') - const after = Date.now() - const meta = await a.meta('k') - expect(meta).toBeDefined() - expect(meta!.storedAt).toBeGreaterThanOrEqual(before) - expect(meta!.storedAt).toBeLessThanOrEqual(after) - }) - - it('write with meta persists storedAt verbatim', async () => { + it('write persists storedAt verbatim', async () => { const a = new MemoryBucket() await a.write('k', 'v', { storedAt: 12345 }) const meta = await a.meta('k') @@ -27,21 +16,28 @@ describe('MemoryBucket', () => { it('read returns stored value wrapped', async () => { const a = new MemoryBucket() - await a.write('k', { hello: 'world' }) + await a.write('k', { hello: 'world' }, { storedAt: 1 }) expect(await a.read('k')).toEqual({ value: { hello: 'world' } }) }) it('read distinguishes a stored undefined from absence', async () => { const a = new MemoryBucket() - await a.write('k', undefined) + await a.write('k', undefined, { storedAt: 1 }) expect(await a.read('k')).toEqual({ value: undefined }) expect(await a.read('missing')).toBeUndefined() }) + it('view wraps presence as { view: undefined } and returns undefined for absence', async () => { + const a = new MemoryBucket() + await a.write('k', 'v', { storedAt: 1 }) + expect(await a.view('k')).toEqual({ view: undefined }) + expect(await a.view('missing')).toBeUndefined() + }) + it('delete removes only the specified key', async () => { const a = new MemoryBucket() - await a.write('a', 1) - await a.write('b', 2) + await a.write('a', 1, { storedAt: 1 }) + await a.write('b', 2, { storedAt: 2 }) await a.delete('a') expect(await a.read('a')).toBeUndefined() expect(await a.read('b')).toEqual({ value: 2 }) @@ -49,8 +45,8 @@ describe('MemoryBucket', () => { it('clear removes everything', async () => { const a = new MemoryBucket() - await a.write('a', 1) - await a.write('b', 2) + await a.write('a', 1, { storedAt: 1 }) + await a.write('b', 2, { storedAt: 2 }) await a.clear() expect(await a.read('a')).toBeUndefined() expect(await a.read('b')).toBeUndefined() diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index 29cf7aa..6b0387b 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -1,26 +1,27 @@ import { Cacheable } from '../src' -import type { IBaseMeta, IBucket } from '../src' +import type { BucketEntryMeta, IBucket } from '../src' interface WriteCall { key: string value: unknown - meta: IBaseMeta | undefined + meta: BucketEntryMeta } -class FakeBucket implements IBucket { - store = new Map() +class FakeBucket implements IBucket { + store = new Map() readCalls = 0 metaCalls = 0 + viewCalls = 0 writeCalls: WriteCall[] = [] deleteCalls: string[] = [] clearCalls = 0 throwOn: Partial< - Record<'read' | 'meta' | 'write' | 'delete' | 'clear', boolean> + Record<'read' | 'meta' | 'view' | 'write' | 'delete' | 'clear', boolean> > = {} - constructor(seed?: { key: string; value: unknown; meta: IBaseMeta }) { + constructor(seed?: { key: string; value: unknown; meta: BucketEntryMeta }) { if (seed) this.store.set(seed.key, { value: seed.value, meta: seed.meta }) } @@ -31,21 +32,24 @@ class FakeBucket implements IBucket { return entry === undefined ? undefined : { value: entry.value as T } } - async write(key: string, value: T, meta?: IBaseMeta): Promise { + async write(key: string, value: T, meta: BucketEntryMeta): Promise { this.writeCalls.push({ key, value, meta }) if (this.throwOn.write) throw new Error('write failed') - this.store.set(key, { - value, - meta: meta ?? { storedAt: Date.now() }, - }) + this.store.set(key, { value, meta }) } - async meta(key: string): Promise { + async meta(key: string): Promise { this.metaCalls += 1 if (this.throwOn.meta) throw new Error('meta failed') return this.store.get(key)?.meta } + async view(key: string): Promise<{ view: void } | undefined> { + this.viewCalls += 1 + if (this.throwOn.view) throw new Error('view failed') + return this.store.has(key) ? { view: undefined } : undefined + } + async delete(key: string): Promise { this.deleteCalls.push(key) if (this.throwOn.delete) throw new Error('delete failed') @@ -61,7 +65,7 @@ class FakeBucket implements IBucket { describe('cascade behavior', () => { it('L1 hit: does not read L2, probes L2 once, no writes', async () => { - const seedMeta: IBaseMeta = { storedAt: Date.now() } + const seedMeta: BucketEntryMeta = { storedAt: Date.now() } const l1 = new FakeBucket({ key: 'test:k', value: 'v', meta: seedMeta }) const l2 = new FakeBucket() const cache = new Cacheable('test', { buckets: [l1, l2] }) @@ -74,13 +78,13 @@ describe('cascade behavior', () => { expect(l2.metaCalls).toBe(1) // L2 had no entry → backfill writes once with L1's meta. expect(l2.writeCalls.length).toBe(1) - expect(l2.writeCalls[0]!.meta?.storedAt).toBe(seedMeta.storedAt) + expect(l2.writeCalls[0]!.meta.storedAt).toBe(seedMeta.storedAt) // L1 already had it → no L1 write. expect(l1.writeCalls.length).toBe(0) }) it('L1 hit + L2 already has it: no writes anywhere', async () => { - const seedMeta: IBaseMeta = { storedAt: 1000 } + const seedMeta: BucketEntryMeta = { storedAt: 1000 } const l1 = new FakeBucket({ key: 'test:k', value: 'v', meta: seedMeta }) const l2 = new FakeBucket({ key: 'test:k', @@ -96,7 +100,7 @@ describe('cascade behavior', () => { }) it('L1 miss + L2 hit: L1 backfilled with L2 meta (storedAt preserved)', async () => { - const l2Meta: IBaseMeta = { storedAt: 12345 } + const l2Meta: BucketEntryMeta = { storedAt: 12345 } const l1 = new FakeBucket() const l2 = new FakeBucket({ key: 'test:k', value: 'v2', meta: l2Meta }) const cache = new Cacheable('test', { buckets: [l1, l2] }) @@ -106,13 +110,13 @@ describe('cascade behavior', () => { expect(result).toEqual('v2') expect(l1.writeCalls.length).toBe(1) expect(l1.writeCalls[0]!.value).toBe('v2') - expect(l1.writeCalls[0]!.meta?.storedAt).toBe(l2Meta.storedAt) + expect(l1.writeCalls[0]!.meta.storedAt).toBe(l2Meta.storedAt) // L2 already had it → no extra L2 write. expect(l2.writeCalls.length).toBe(0) expect(l2.readCalls).toBe(1) }) - it('Both miss: resource called once; L1 written without meta; L2 written with L1 meta', async () => { + it('Both miss: resource called once; both layers written with the same engine-minted meta', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket() const cache = new Cacheable('test', { buckets: [l1, l2] }) @@ -130,23 +134,17 @@ describe('cascade behavior', () => { expect(l1.writeCalls.length).toBe(1) expect(l1.writeCalls[0]!.value).toBe('fresh') - expect(l1.writeCalls[0]!.meta).toBeUndefined() + const l1WriteMeta = l1.writeCalls[0]!.meta + expect(l1WriteMeta.storedAt).toBeGreaterThanOrEqual(before) + expect(l1WriteMeta.storedAt).toBeLessThanOrEqual(after) expect(l2.writeCalls.length).toBe(1) expect(l2.writeCalls[0]!.value).toBe('fresh') - const l2Meta = l2.writeCalls[0]!.meta! - expect(l2Meta).toBeDefined() - expect(l2Meta.storedAt).toBeGreaterThanOrEqual(before) - expect(l2Meta.storedAt).toBeLessThanOrEqual(after) - - // L1 stored meta synthesized internally; the meta forwarded to L2 must - // match L1's stored meta (storedAt preservation). - const l1Meta = await l1.meta('test:k') - expect(l1Meta?.storedAt).toBe(l2Meta.storedAt) + expect(l2.writeCalls[0]!.meta.storedAt).toBe(l1WriteMeta.storedAt) }) it('3 layers, L3 hit: L1 and L2 both filled with L3 meta', async () => { - const l3Meta: IBaseMeta = { storedAt: 42 } + const l3Meta: BucketEntryMeta = { storedAt: 42 } const l1 = new FakeBucket() const l2 = new FakeBucket() const l3 = new FakeBucket({ key: 'test:k', value: 'deep', meta: l3Meta }) @@ -158,8 +156,8 @@ describe('cascade behavior', () => { expect(l1.writeCalls.length).toBe(1) expect(l2.writeCalls.length).toBe(1) expect(l3.writeCalls.length).toBe(0) - expect(l1.writeCalls[0]!.meta?.storedAt).toBe(42) - expect(l2.writeCalls[0]!.meta?.storedAt).toBe(42) + expect(l1.writeCalls[0]!.meta.storedAt).toBe(42) + expect(l2.writeCalls[0]!.meta.storedAt).toBe(42) }) it('max-age across layers: stale L1 with fresh L2 returns L2 value', async () => { @@ -190,6 +188,31 @@ describe('cascade behavior', () => { expect(calls).toBe(0) }) + it('max-age stale L1 + fresh L2: cascade fill refreshes L1 with L2 value and meta', async () => { + const now = Date.now() + const l1 = new FakeBucket({ + key: 'test:k', + value: 'l1-stale', + meta: { storedAt: now - 500 }, + }) + const l2 = new FakeBucket({ + key: 'test:k', + value: 'l2-fresh', + meta: { storedAt: now - 50 }, + }) + const cache = new Cacheable('test', { + buckets: [l1, l2], + policy: 'max-age', + maxAge: 100, + }) + + const result = await cache.remember(async () => 'network', 'k') + expect(result).toEqual('l2-fresh') + expect(l1.writeCalls.length).toBe(1) + expect(l1.writeCalls[0]!.value).toBe('l2-fresh') + expect(l1.writeCalls[0]!.meta.storedAt).toBe(now - 50) + }) + it('max-age miss across all layers calls resource', async () => { const now = Date.now() const l1 = new FakeBucket({ @@ -242,7 +265,7 @@ describe('cascade behavior', () => { expect(l2.clearCalls).toBe(1) }) - it('meta returns highest-priority layer meta', async () => { + it('cascade fill propagates highest-priority layer meta to lower layers', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket({ key: 'test:k', @@ -251,11 +274,13 @@ describe('cascade behavior', () => { }) const cache = new Cacheable('test', { buckets: [l1, l2] }) - expect(await cache.meta('k')).toEqual({ storedAt: 999 }) + expect((await l1.meta('test:k'))?.storedAt).toBeUndefined() + expect((await l2.meta('test:k'))?.storedAt).toBe(999) await cache.remember(async () => 'fresh', 'k') - // Now L1 is filled with L2 meta. - expect((await cache.meta('k'))?.storedAt).toBe(999) + + // L1 is now backfilled with L2's storedAt verbatim. + expect((await l1.meta('test:k'))?.storedAt).toBe(999) }) it('caches resources that resolve to undefined', async () => { @@ -441,6 +466,51 @@ describe('concurrent dedup', () => { expect(calls).toBe(1) }) + it('cache-only: 100 concurrent hit callers share the policy run (1 meta probe, 1 read)', async () => { + const seedMeta: BucketEntryMeta = { storedAt: Date.now() } + const l1 = new FakeBucket({ + key: 'test:k', + value: 'cached', + meta: seedMeta, + }) + const cache = new Cacheable('test', { buckets: [l1] }) + + l1.metaCalls = 0 + l1.readCalls = 0 + + const results = await Promise.all( + Array.from({ length: 100 }, () => + cache.remember(async () => 'fresh', 'k'), + ), + ) + + expect(results.every((r) => r === 'cached')).toBe(true) + expect(l1.metaCalls).toBe(1) + expect(l1.readCalls).toBe(1) + }) + + it('cross-mode: concurrent remember + resolve probe each mode once but share producer', async () => { + const l1 = new FakeBucket() + const cache = new Cacheable('test', { buckets: [l1] }) + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'v' + } + + await Promise.all([ + ...Array.from({ length: 50 }, () => cache.remember(slow, 'k')), + ...Array.from({ length: 50 }, () => cache.resolve(slow, 'k')), + ]) + + // Each mode runs its own outer dedup → each does 1 cascade probe. + expect(l1.metaCalls).toBe(2) + // Inner producer dedup is shared across modes → producer called once. + expect(calls).toBe(1) + }) + it('network-only: does NOT deduplicate (control)', async () => { const l1 = new FakeBucket() const cache = new Cacheable('test', { diff --git a/tests/index.test.ts b/tests/index.test.ts index d6f5d99..b7abcd1 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -22,19 +22,17 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) describe('Cache operations', () => { it('Returns correct values', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], - }) + const bucket = new MemoryBucket() + const cache = new Cacheable('test', { buckets: [bucket] }) const value = 10 const cachedValue = await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.meta('a')).toBeDefined() + expect(await bucket.meta('test:a')).toBeDefined() expect(cachedValue).toEqual(value) }) it('Stores multiple caches', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], - }) + const bucket = new MemoryBucket() + const cache = new Cacheable('test', { buckets: [bucket] }) const valueA = 10 const valueB = 20 @@ -48,35 +46,33 @@ describe('Cache operations', () => { 'b', ) - expect(await cache.meta('a')).toBeDefined() - expect(await cache.meta('b')).toBeDefined() + expect(await bucket.meta('test:a')).toBeDefined() + expect(await bucket.meta('test:b')).toBeDefined() expect([cachedValueA, cachedValueB]).toEqual([valueA, valueB]) }) it('Deletes values', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], - }) + const bucket = new MemoryBucket() + const cache = new Cacheable('test', { buckets: [bucket] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.meta('a')).toBeDefined() + expect(await bucket.meta('test:a')).toBeDefined() await cache.delete('a') - expect(await cache.meta('a')).toBeUndefined() + expect(await bucket.meta('test:a')).toBeUndefined() }) it('Clears the cache', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], - }) + const bucket = new MemoryBucket() + const cache = new Cacheable('test', { buckets: [bucket] }) const value = 10 await cache.remember(() => mockedApiRequest(value), 'a') - expect(await cache.meta('a')).toBeDefined() + expect(await bucket.meta('test:a')).toBeDefined() await cache.clear() - expect(await cache.meta('a')).toBeUndefined() + expect(await bucket.meta('test:a')).toBeUndefined() }) it('Creates proper keys', () => { diff --git a/tests/namespace.test.ts b/tests/namespace.test.ts index 76ebadc..1d7bd4b 100644 --- a/tests/namespace.test.ts +++ b/tests/namespace.test.ts @@ -33,8 +33,8 @@ describe('namespace', () => { await a.delete('k') - expect(await a.meta('k')).toBeUndefined() - expect(await b.meta('k')).toBeDefined() + expect(await bucket.meta('a:k')).toBeUndefined() + expect(await bucket.meta('b:k')).toBeDefined() }) it('clear from one namespace wipes shared bucket (documented caveat)', async () => { @@ -47,7 +47,7 @@ describe('namespace', () => { await a.clear() - expect(await a.meta('k')).toBeUndefined() - expect(await b.meta('k')).toBeUndefined() + expect(await bucket.meta('a:k')).toBeUndefined() + expect(await bucket.meta('b:k')).toBeUndefined() }) }) diff --git a/tests/resolve.test.ts b/tests/resolve.test.ts index a94d98e..2ab642d 100644 --- a/tests/resolve.test.ts +++ b/tests/resolve.test.ts @@ -1,87 +1,194 @@ import { Cacheable, MemoryBucket } from '../src' -import type { IBaseMeta, IBucket } from '../src' +import type { BucketEntryMeta, IBucket } from '../src' const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) -describe('resolve', () => { - it('cache-only: hit returns cached meta; miss writes fresh meta', async () => { +interface UrlView { + url: string +} + +class FakeViewBucket implements IBucket { + store = new Map() + + readCalls = 0 + viewCalls = 0 + + constructor(seed?: { key: string; value: unknown; meta: BucketEntryMeta }) { + if (seed) this.store.set(seed.key, { value: seed.value, meta: seed.meta }) + } + + async read(key: string): Promise<{ value: T } | undefined> { + this.readCalls += 1 + const entry = this.store.get(key) + return entry === undefined ? undefined : { value: entry.value as T } + } + + async write(key: string, value: T, meta: BucketEntryMeta): Promise { + this.store.set(key, { value, meta }) + } + + async meta(key: string): Promise { + return this.store.get(key)?.meta + } + + async view(key: string): Promise<{ view: UrlView } | undefined> { + this.viewCalls += 1 + return this.store.has(key) ? { view: { url: `fake://${key}` } } : undefined + } + + async delete(key: string): Promise { + this.store.delete(key) + } + + async clear(): Promise { + this.store.clear() + } +} + +describe('cache.resolve(): TView round-trip', () => { + it('miss: producer runs, cascade writes, resolve returns L1 view', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { buckets: [l1] }) + + const view = await cache.resolve(async () => 'fresh', 'k') + expect(view).toEqual({ url: 'fake://test:k' }) + }) + + it('hit at L1: no producer, resolve returns L1 view', async () => { + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'cached', + meta: { storedAt: 12345 }, + }) + const cache = new Cacheable('test', { buckets: [l1] }) + + let calls = 0 + const view = await cache.resolve(async () => { + calls += 1 + return 'fresh' + }, 'k') + + expect(view).toEqual({ url: 'fake://test:k' }) + expect(calls).toBe(0) + }) + + it('hit at L2: cascade fills L1, resolve returns L1 view', async () => { + const l1 = new FakeViewBucket() + const l2 = new FakeViewBucket({ + key: 'test:k', + value: 'l2-cached', + meta: { storedAt: 12345 }, + }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) + + const view = await cache.resolve(async () => 'fresh', 'k') + expect(view).toEqual({ url: 'fake://test:k' }) + // L1 was backfilled from L2. + expect((await l1.meta('test:k'))?.storedAt).toBe(12345) + }) + + it('void-view buckets: resolve returns undefined (typed as void)', async () => { const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) + const view = await cache.resolve(async () => 'v', 'k') + expect(view).toBeUndefined() + }) +}) + +describe('cache.resolve(): policy semantics', () => { + it('cache-only: hit reuses storedAt from L1', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { buckets: [l1] }) const before = Date.now() - const a = await cache.resolve(async () => 'v', 'k') + await cache.resolve(async () => 'v', 'k') const after = Date.now() - expect(a.storedAt).toBeGreaterThanOrEqual(before) - expect(a.storedAt).toBeLessThanOrEqual(after) + const stored = (await l1.meta('test:k'))!.storedAt + expect(stored).toBeGreaterThanOrEqual(before) + expect(stored).toBeLessThanOrEqual(after) await wait(10) - const b = await cache.resolve(async () => 'v2', 'k') - expect(b.storedAt).toBe(a.storedAt) + await cache.resolve(async () => 'v2', 'k') + expect((await l1.meta('test:k'))!.storedAt).toBe(stored) }) - it('network-only: every call writes fresh meta', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], + it('network-only: every call writes fresh storedAt', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { + buckets: [l1], policy: 'network-only', }) - const a = await cache.resolve(async () => 'v', 'k') + await cache.resolve(async () => 'v', 'k') + const a = (await l1.meta('test:k'))!.storedAt await wait(10) - const b = await cache.resolve(async () => 'v', 'k') - expect(b.storedAt).toBeGreaterThan(a.storedAt) + await cache.resolve(async () => 'v', 'k') + const b = (await l1.meta('test:k'))!.storedAt + + expect(b).toBeGreaterThan(a) }) - it('network-only-non-concurrent: every serial call writes fresh meta', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], + it('network-only-non-concurrent: every serial call writes fresh storedAt', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { + buckets: [l1], policy: 'network-only-non-concurrent', }) - const a = await cache.resolve(async () => 'v', 'k') + await cache.resolve(async () => 'v', 'k') + const a = (await l1.meta('test:k'))!.storedAt await wait(10) - const b = await cache.resolve(async () => 'v', 'k') - expect(b.storedAt).toBeGreaterThan(a.storedAt) + await cache.resolve(async () => 'v', 'k') + const b = (await l1.meta('test:k'))!.storedAt + + expect(b).toBeGreaterThan(a) }) - it('max-age: meta is stable within window, refreshes when expired', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], + it('max-age: storedAt is stable within window, refreshes when expired', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { + buckets: [l1], policy: 'max-age', maxAge: 100, }) - const a = await cache.resolve(async () => 'v', 'k') - const b = await cache.resolve(async () => 'v', 'k') - expect(b.storedAt).toBe(a.storedAt) + await cache.resolve(async () => 'v', 'k') + const a = (await l1.meta('test:k'))!.storedAt + await cache.resolve(async () => 'v', 'k') + expect((await l1.meta('test:k'))!.storedAt).toBe(a) await wait(200) - const c = await cache.resolve(async () => 'v', 'k') - expect(c.storedAt).toBeGreaterThan(a.storedAt) + await cache.resolve(async () => 'v', 'k') + expect((await l1.meta('test:k'))!.storedAt).toBeGreaterThan(a) }) - it('SWR: returns stale meta immediately, background revalidation updates it', async () => { - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], + it('SWR: returns stale view immediately, background revalidation refreshes storedAt', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { + buckets: [l1], policy: 'stale-while-revalidate', maxAge: 50, }) - const a = await cache.resolve(async () => 'v', 'k') + await cache.resolve(async () => 'v', 'k') + const a = (await l1.meta('test:k'))!.storedAt await wait(150) - // Stale: returns cached meta immediately and kicks off a background refetch. - const b = await cache.resolve(async () => 'v', 'k') - expect(b.storedAt).toBe(a.storedAt) + // Stale: returns cached view immediately and kicks off a background refetch. + await cache.resolve(async () => 'v', 'k') + // The synchronous resolve still observes the stale storedAt. + // (The background revalidation may or may not have landed yet.) // Give the background refetch plenty of time to land. await wait(150) - const c = await cache.resolve(async () => 'v', 'k') - expect(c.storedAt).toBeGreaterThan(a.storedAt) + await cache.resolve(async () => 'v', 'k') + expect((await l1.meta('test:k'))!.storedAt).toBeGreaterThan(a) }) - it('concurrent remember + resolve share one producer call and one storedAt', async () => { - const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) + it('concurrent remember + resolve share one producer call', async () => { + const l1 = new FakeViewBucket() + const cache = new Cacheable('test', { buckets: [l1] }) let calls = 0 const slow = async () => { @@ -90,29 +197,20 @@ describe('resolve', () => { return 'v' } - const [value, meta] = await Promise.all([ + const [value, view] = await Promise.all([ cache.remember(slow, 'k'), cache.resolve(slow, 'k'), ]) expect(calls).toBe(1) expect(value).toBe('v') - - const peeked = await cache.meta('k') - expect(peeked?.storedAt).toBe(meta.storedAt) - }) - - it('resolve-returned meta matches cache.meta on fresh entries', async () => { - const cache = new Cacheable('test', { buckets: [new MemoryBucket()] }) - const meta = await cache.resolve(async () => 'v', 'k') - const peeked = await cache.meta('k') - expect(peeked).toEqual(meta) + expect(view).toEqual({ url: 'fake://test:k' }) }) it('logs HIT/MISS with elapsed time, same shape as remember', async () => { const log = jest.fn() - const cache = new Cacheable('test', { - buckets: [new MemoryBucket()], + const cache = new Cacheable('test', { + buckets: [new FakeViewBucket()], logger: { log }, }) @@ -126,65 +224,127 @@ describe('resolve', () => { expect.stringMatching(/^Cacheable "k": HIT \d+(\.\d+)?ms$/), ) }) +}) - describe('cascade', () => { - interface WriteCall { - key: string - value: unknown - meta: IBaseMeta | undefined - } +describe('cache.resolve(): view-cascade hot path', () => { + it('L1 hit (single layer): no value read, only meta + view', async () => { + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'cached', + meta: { storedAt: 1 }, + }) + const cache = new Cacheable('test', { buckets: [l1] }) - class FakeBucket implements IBucket { - store = new Map() - writeCalls: WriteCall[] = [] + l1.readCalls = 0 + l1.viewCalls = 0 - constructor(seed?: { key: string; value: unknown; meta: IBaseMeta }) { - if (seed) - this.store.set(seed.key, { value: seed.value, meta: seed.meta }) - } + await cache.resolve(async () => 'fresh', 'k') + expect(l1.readCalls).toBe(0) + expect(l1.viewCalls).toBe(1) + }) - async read(key: string): Promise<{ value: T } | undefined> { - const entry = this.store.get(key) - return entry === undefined ? undefined : { value: entry.value as T } - } + it('L1 hit + L2 also fresh: still skips value read', async () => { + const now = Date.now() + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'l1', + meta: { storedAt: now }, + }) + const l2 = new FakeViewBucket({ + key: 'test:k', + value: 'l2', + meta: { storedAt: now - 10 }, + }) + const cache = new Cacheable('test', { + buckets: [l1, l2], + policy: 'max-age', + maxAge: 1000, + }) - async write(key: string, value: T, meta?: IBaseMeta): Promise { - this.writeCalls.push({ key, value, meta }) - this.store.set(key, { value, meta: meta ?? { storedAt: Date.now() } }) - } + l1.readCalls = 0 + l2.readCalls = 0 - async meta(key: string): Promise { - return this.store.get(key)?.meta - } + await cache.resolve(async () => 'fresh', 'k') + expect(l1.readCalls).toBe(0) + expect(l2.readCalls).toBe(0) + }) - async delete(key: string): Promise { - this.store.delete(key) - } + it('L1 hit + L2 missing: reads value to fill L2, then returns L1 view', async () => { + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'l1', + meta: { storedAt: 1 }, + }) + const l2 = new FakeViewBucket() + const cache = new Cacheable('test', { buckets: [l1, l2] }) - async clear(): Promise { - this.store.clear() - } - } + l1.readCalls = 0 - it('L1 miss + L2 hit: resolve returns the L2 meta (storedAt preserved)', async () => { - const l2Meta: IBaseMeta = { storedAt: 12345 } - const l1 = new FakeBucket() - const l2 = new FakeBucket({ key: 'test:k', value: 'v', meta: l2Meta }) - const cache = new Cacheable('test', { buckets: [l1, l2] }) + const view = await cache.resolve(async () => 'fresh', 'k') + expect(view).toEqual({ url: 'fake://test:k' }) + expect(l1.readCalls).toBe(1) + expect(l2.store.has('test:k')).toBe(true) + }) - const meta = await cache.resolve(async () => 'fresh', 'k') - expect(meta.storedAt).toBe(12345) + it('max-age stale L1 + fresh L2: cascade fill refreshes L1, returns L1 view', async () => { + const now = Date.now() + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'l1-stale', + meta: { storedAt: now - 500 }, + }) + const l2 = new FakeViewBucket({ + key: 'test:k', + value: 'l2-fresh', + meta: { storedAt: now - 50 }, + }) + const cache = new Cacheable('test', { + buckets: [l1, l2], + policy: 'max-age', + maxAge: 100, }) - it('both miss: resolve returns L1 meta after write; storedAt agrees with what L2 received', async () => { - const l1 = new FakeBucket() - const l2 = new FakeBucket() - const cache = new Cacheable('test', { buckets: [l1, l2] }) - - const meta = await cache.resolve(async () => 'fresh', 'k') + const view = await cache.resolve(async () => 'network', 'k') + expect(view).toEqual({ url: 'fake://test:k' }) + expect((await l1.meta('test:k'))?.storedAt).toBe(now - 50) + }) +}) - expect(l2.writeCalls.length).toBe(1) - expect(l2.writeCalls[0]!.meta?.storedAt).toBe(meta.storedAt) +describe('cache.resolve(): race healing and strict-mode', () => { + it('heals race when bucket.view returns undefined despite meta hit', async () => { + const l1 = new FakeViewBucket({ + key: 'test:k', + value: 'cached', + meta: { storedAt: Date.now() }, }) + + let viewCount = 0 + const realView = l1.view.bind(l1) + l1.view = async (key: string) => { + viewCount += 1 + if (viewCount === 1) return undefined // simulate eviction race + return realView(key) + } + + const cache = new Cacheable('test', { buckets: [l1] }) + + let calls = 0 + const view = await cache.resolve(async () => { + calls += 1 + return 'fresh' + }, 'k') + + expect(view).toEqual({ url: 'fake://test:k' }) + expect(calls).toBe(1) // race healed via running resource + }) + + it('throws strict-mode error when bucket.view returns undefined after a successful cascade write', async () => { + const l1 = new FakeViewBucket() + l1.view = async () => undefined // bucket never produces a view + const cache = new Cacheable('test', { buckets: [l1] }) + + await expect(cache.resolve(async () => 'fresh', 'k')).rejects.toThrow( + /returned no view.*after a successful cascade write/, + ) }) }) From a1a7a74a557d29e383fa1c66d2f00e7daf34b533 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:03:04 +0200 Subject: [PATCH 17/23] Pass hitMeta through cascade fill instead of reconstructing (#26) Co-authored-by: Claude Opus 4.7 (1M context) --- src/Cacheable.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 23a7cb5..08281c1 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -285,13 +285,12 @@ export class Cacheable { hitIdx: number, isFresh?: FreshnessPredicate, ): Promise { - const meta: BucketEntryMeta = { storedAt: hitMeta.storedAt } const writes: Promise[] = [] for (let i = 0; i < this.#buckets.length; i++) { if (i === hitIdx) continue const probe = probes[i] if (probe !== undefined && (!isFresh || isFresh(probe))) continue - writes.push(this.#buckets[i]!.write(fullKey, value, meta)) + writes.push(this.#buckets[i]!.write(fullKey, value, hitMeta)) } if (writes.length > 0) await Promise.all(writes) } From d773a86988f6a4a981963eb1408151413765df94 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:26:10 +0200 Subject: [PATCH 18/23] Remove Cacheable.key from the public API (#27) Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 20 ++++++-------------- src/Cacheable.ts | 4 ---- tests/index.test.ts | 5 ----- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ce11fb3..12d8783 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ cache.remember(() => fetch('https://some-url.com/api'), 'key') - [cache.remember(resource, key)](#cacherememberresource-key-promiset) - [cache.resolve(resource, key)](#cacheresolveresource-key-promisetview) - [cache.delete(key) / cache.clear()](#cachedeletekey-promisevoid--cacheclear-promisevoid) - - [Cacheable.key(...args)](#cacheablekeyargs-string) - [Buckets](#buckets) - [Cache Policies](#cache-policies) - [`cache-only` (default)](#cache-only-default) @@ -99,7 +98,7 @@ type CacheableOptions = { ### `cache.remember(resource, key): Promise` -Resolves to the cached value if present (subject to policy); otherwise calls `resource()` and writes to every bucket. +Returns the cached value if present (subject to policy); otherwise calls `resource()` and writes to every bucket. ### `cache.resolve(resource, key): Promise` @@ -131,19 +130,11 @@ const { url } = await cache.resolve( `resolve` and `remember` share the same in-flight registry: a concurrent pair against the same key triggers `resource()` once. `resolve` honors the cache policy — a stale entry will trigger a producer call (or a background revalidation under `stale-while-revalidate`). -For buckets without a meaningful projection (e.g. the built-in `MemoryBucket`), `TView = void` and `cache.resolve()` resolves to `undefined`. Use `cache.remember()` instead in that case. +For buckets without a meaningful projection (e.g. the built-in `MemoryBucket`), `TView = void` and `cache.resolve()` resolves to `undefined`. ### `cache.delete(key): Promise` / `cache.clear(): Promise` -`delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. Both are async — `await` them. - -### `Cacheable.key(...args): string` - -Joins parts with `:`. - -```ts -Cacheable.key('user', 42) // 'user:42' -``` +`delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. ## Buckets @@ -175,7 +166,7 @@ Rules: - `write` MUST persist `meta.storedAt` verbatim. The engine always supplies a meta; there is no synthesis branch. - `view` returns `undefined` for absence and `{ view }` for presence. The same wrapper pattern as `read` lets `TView = void` buckets distinguish "entry present, no projection" (`{ view: undefined }`) from "entry absent" (`undefined`). The engine treats absence after a meta-probe hit as a race and heals it by running the producer; absence after a successful cascade write is a strict-mode error and the engine throws. - `clear` MUST remove every entry the bucket manages. -- Any throw from any bucket rejects the surrounding `remember()` / `resolve()` call. There is no per-bucket error suppression. +- Any throw from any bucket rejects the surrounding `remember()` / `resolve()` call. ### Cascade behavior @@ -491,7 +482,8 @@ await cache.remember(() => fetch(url), 'weather') Breaking changes: -- **Class renamed** `Cacheables` → `Cacheable`. Update imports and `new Cacheables(...)` call sites. The static helper moves with it: `Cacheables.key(...)` → `Cacheable.key(...)` (behaviour unchanged). +- **Class renamed** `Cacheables` → `Cacheable`. Update imports and `new Cacheables(...)` call sites. +- **`Cacheables.key(...)` removed.** The static key-joining helper is gone; build keys with template literals or `[...].join(':')`. - **Method renamed** `cache.cacheable(...)` → `cache.remember(...)`. - **Cache policy moved to the constructor.** v2 took `cachePolicy` and `maxAge` as a per-call third argument; v3 has no per-call options. Pass `policy` (and `maxAge` where required) once on `new Cacheable(namespace, { ... })`. The field is `policy`, not `cachePolicy`. A single instance now serves a single policy — split into multiple instances if you previously mixed policies on one cache. - **`buckets` is required** (replaces v2's implicit in-memory store). `new Cacheable()` no longer compiles. `new Cacheable('app', { buckets: [new MemoryBucket()] })` reproduces the v2 default. diff --git a/src/Cacheable.ts b/src/Cacheable.ts index 08281c1..cfbcd96 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -38,10 +38,6 @@ export class Cacheable { : undefined } - static key(...args: (string | number)[]): string { - return args.join(':') - } - #fullKey(key: string): string { return `${this.#namespace}:${key}` } diff --git a/tests/index.test.ts b/tests/index.test.ts index b7abcd1..34426bd 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -75,11 +75,6 @@ describe('Cache operations', () => { expect(await bucket.meta('test:a')).toBeUndefined() }) - it('Creates proper keys', () => { - const key = Cacheable.key('aaa', 'bbb', 'ccc', 'ddd', 10, 20) - expect(key).toEqual('aaa:bbb:ccc:ddd:10:20') - }) - it('Logs correctly', async () => { console.log = jest.fn() From c3383e6ae7b0e7c85a67ab9d22cdc426e9a643b1 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:31:52 +0200 Subject: [PATCH 19/23] Drop README mention of cache key helper (#28) Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 12d8783..b12e1f6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ A small, typed cache with composable storage buckets and a handful of cache poli - **Five cache policies**, including `stale-while-revalidate`, with concurrency-safe deduplication where it makes sense. - **Fully typed**, with a generic `TView` parameter for the bucket's user-facing projection (a URL from a filesystem bucket, a presigned link from S3, …) surfaced via `cache.resolve(...)`. - **Required namespace prefix** so multiple instances can share a bucket without collisions. -- Helper to build cache keys. - Works in browser and Node.js. **No dependencies.** ```ts From 9456572821f4e881e95470d3441cbb076ede45bd Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:21:59 +0200 Subject: [PATCH 20/23] v3 hardening: packaging, NodeNext, SWR dedup, strict-mode resolve, dep refresh (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Restrict published files to dist + README + LICENSE Without an explicit `files` allow-list, npm packed every tracked file in the repo (.idea, .github, src, tests, tsconfigs, agent collaboration files under .context, etc.). Whitelist only what consumers need so the tarball drops from 56 files / 126kB to 25 files / 66kB. Co-Authored-By: Claude Opus 4.7 (1M context) * Include namespace in HIT/MISS log lines Two Cacheable instances sharing one logger were indistinguishable when the format only carried the unprefixed key. Switch to `Cacheable ":": …` so log streams remain unambiguous. Updates the README example to match the implementation it sits next to, and updates tests to assert the new format. Co-Authored-By: Claude Opus 4.7 (1M context) * Update Build badge to current shields.io API The previous URL used the deprecated /github/workflow/status/ endpoint, which has been 404ing since shields.io retired it. Switch to the current /github/actions/workflow/status/ endpoint and target the live ci.yml workflow. Co-Authored-By: Claude Opus 4.7 (1M context) * Update ILogger jsdoc to match the merged HIT/MISS log format The doc still described two log lines per call (timing + hit-count) even though those were merged in #23. Brought the jsdoc in line with the implementation so IDE hover docs match what consumers actually see. Co-Authored-By: Claude Opus 4.7 (1M context) * Drop the misleading CacheOptions type re-export CacheOptions in v3 had a completely different shape from v2's CacheOptions (logger/policy/maxAge vs enabled/log/logTiming), so its "backwards-compatible" comment was misleading — no v2 type would satisfy it. Remove the type from types.ts and the public re-export from index.ts. CacheableOptions is the single options type now. Co-Authored-By: Claude Opus 4.7 (1M context) * Re-export Policy and PolicyOptions from the package entry Both types were defined in src/types.ts but never reached consumers through src/index.ts. Surfacing them lets users type a policy literal or build option-bag types in their own code. Co-Authored-By: Claude Opus 4.7 (1M context) * Throw strict-mode error when L1 view is absent post-cascade-fill #cascadeResolve previously returned undefined and let the run fall through to the producer when L1's view came back absent after a successful cascadeFill. The IBucket contract calls this case a strict-mode error — the bucket just got a write and immediately denies the entry exists. Throw and surface the bug instead of silently masking it with another producer call. The hitIdx === 0 branch keeps healing: cascadeFill skipped L1 there, so an absent view is a probe-vs-view race against an external delete, not a post-fill contract violation. Co-Authored-By: Claude Opus 4.7 (1M context) * Drop in-flight registrations on delete Before this change delete() only wiped the bucket — the policy and producer in-flight maps still pointed at the deleted key, so a remember() racing the delete could attach to a producer whose write was about to land back on top of the deletion. Clear those entries synchronously alongside the bucket.delete() fan-out. The producer itself is not aborted — that requires threading an AbortSignal through #produceAndWrite and is left as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) * Fix the fetch examples to cache the parsed body, not the Response Caching a Response object directly is a footgun — the body can be consumed exactly once, so the first .json()/.text() works and every subsequent call on the cached Response throws. Both the README hero snippet and the Usage section example showed this antipattern. Switch them to .then((r) => r.json()) so the cache stores the parsed data, and add an `await` to the hero snippet so the example is a runnable program rather than a fire-and-forget that would silently swallow rejections. Co-Authored-By: Claude Opus 4.7 (1M context) * Wrap stale-while-revalidate runs in the outer policy dedup SWR was the only policy whose body ran outside #dedupPolicy, so 100 concurrent stale reads each issued their own cascade probe and value read even though the producer was already shared via the inner producer-inflight map. With multilayer buckets that meant N×M extra meta calls plus N reads on the hit layer for every stale burst. Wrapping the run in #dedupPolicy makes SWR consistent with the other deduplicating policies: concurrent stale reads share one cascade probe + one value read + one background revalidation. The README claim "Concurrent stale reads share one revalidation" now also holds for the cascade itself, not just the producer call. Sequential semantics are unchanged (dedup only affects concurrent calls), so the existing fetchPolicies SWR tests pass without edits. Co-Authored-By: Claude Opus 4.7 (1M context) * Replace ConsoleLogger class with a consoleLogger singleton The class wrapped a single one-line method and required users to write `new ConsoleLogger()` for what is functionally a constant. Export the singleton ILogger directly instead. Pre-3.0 we still get to make this shape change for free. Co-Authored-By: Claude Opus 4.7 (1M context) * Bump CJS target to ES2022 for native private fields Targeting ES2015 forced TypeScript to transpile every #private field access through __classPrivateFieldGet/Set helpers, ballooning the CJS Cacheable.js to ~16kB. Bumping to ES2022 (Node 16.4+) lets the runtime use native private fields directly. Cacheable.js shrinks from 15.8kB to ~12kB and the helper preamble disappears. Engines below Node 16.4 are no longer supported by the CJS build — acceptable as part of v3's breaking changes, and matches the floor we will pin in package.json.engines. Co-Authored-By: Claude Opus 4.7 (1M context) * Move "types" first in the exports conditional map Per the conditional-exports resolution rules, "types" should come before "import"/"require" because it is matched ahead of them in TypeScript's resolver. Order matters even when all keys point to the right files; publint and arethetypeswrong both flag this. Co-Authored-By: Claude Opus 4.7 (1M context) * Switch to NodeNext module resolution Brings the dual-publish setup onto modern TypeScript: - Root tsconfig: module/moduleResolution = NodeNext. - src/package.json marks the source tree as ESM so the mjs build emits ESM and the cjs build (still moduleResolution = Node + module = CommonJS) emits CJS, with fixup-packages.ts continuing to set the per-dist `type` field. - All relative imports in src/ now carry .js extensions, as required under NodeNext. - tests/tsconfig.json overrides module/moduleResolution back to CommonJS/Node so ts-jest keeps working without ESM jest plumbing. jest.config.js wires the test-specific tsconfig and adds a moduleNameMapper that strips trailing .js so jest-resolve can find the .ts source. - Drops the stale `./example/` ignore pattern from the jest config in the same touch. Co-Authored-By: Claude Opus 4.7 (1M context) * Pin lib to ES2022 instead of ESNext ESNext drifts every TS release and silently grants the source access to runtime APIs that may not exist on the floor we promise to support (Node 16.4+ for native private fields). ES2022 matches the CJS target, so any accidental use of newer APIs is caught by tsc rather than at runtime in older environments. DOM stays — `performance.now()` and the "works in browser" claim both rely on it. Co-Authored-By: Claude Opus 4.7 (1M context) * Bump publish workflow actions to v4 actions/checkout@v2 and actions/setup-node@v2 are end-of-life and already throw deprecation warnings in CI logs. Match the @v4 versions the ci.yml workflow already uses. Co-Authored-By: Claude Opus 4.7 (1M context) * Pin minimum Node to >=18 CJS target is now ES2022 (native private fields, Node 16.4+) and the public CI matrix runs on Node 20. Pinning the floor at 18 (the oldest LTS still in maintenance through 2025) makes the support window explicit so npm warns users on older Node before they hit a runtime error. Co-Authored-By: Claude Opus 4.7 (1M context) * Declare the package side-effect-free for tree-shaking All exports are pure value declarations and a singleton object — no top-level side effects. Telling bundlers so lets them drop unused exports (e.g. consoleLogger in apps that pass a custom ILogger) during tree-shaking. Co-Authored-By: Claude Opus 4.7 (1M context) * Refresh package description for v3 The previous description still pitched v2's "simple in-memory cache". v3's headline features are multilayer buckets, five policies, and concurrency-safe dedup — surface those to npm, npms.io, and search crawlers. Co-Authored-By: Claude Opus 4.7 (1M context) * Expand keywords for v3 positioning Add multilayer/multi-tier, swr / stale-while-revalidate, and redis/s3 to surface the package for searches that match the new pluggable-bucket design. Co-Authored-By: Claude Opus 4.7 (1M context) * Spell out all six IBucket methods in the resolve example The truncated `// read / write / meta / delete / clear …` placeholder made it look like only view() needed to be implemented. Replace with the same six-method skeleton the later "Writing your own bucket" section uses, so anyone copy-pasting from the cache.resolve docs sees the full surface they need to satisfy. Co-Authored-By: Claude Opus 4.7 (1M context) * Drop the void-resolve sentence from the API section Calling resolve() on a Cacheable is strictly slower than remember() and yields nothing useful, so we don't want to encourage the pattern. The MemoryBucket sentence framed it as a feature. The IBucket section already covers TView = void for users actually implementing a bucket. Co-Authored-By: Claude Opus 4.7 (1M context) * Drop the non-null assertions from the cascade engine #findHitIdx now returns the bucket + meta together as a CascadeHit object, so callers narrow the result through a falsy check instead of re-indexing with `!`. The L1 reference is captured once in the constructor as #l1 (one isolated assertion at construction time, where the precondition is enforced) and used directly elsewhere. #cascadeFill switched to forEach so the bucket parameter is already narrowed. No behavior change — same tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) * Bump typescript, prettier, size-limit, tsx Patch/minor floors moved up to current releases. TypeScript pinned to 5.9 because ts-jest 29 isn't yet compatible with TS 6.x — bumping ts-jest is part of the Jest 30 migration in a follow-up commit. - typescript 5.4.5 → 5.9.x - prettier 3.3.1 → 3.8.x - size-limit 11.1.4 → 12.1.x (+ preset) - tsx 4.11.2 → 4.21.x Co-Authored-By: Claude Opus 4.7 (1M context) * Migrate ESLint to v9 flat config + tseslint v8 - eslint 8.57 → 9.x - @typescript-eslint stack 7.12 → 8.x (via the typescript-eslint helper) - eslint-config-prettier 9.1 → 10.x - new eslint.config.mjs replaces .eslintrc.js with the flat-config shape. The .mjs extension is needed because the root package.json is still CJS; the source tree's separate src/package.json declares type: module for the build. Also picks up two prettier reflow nits exposed by running prettier through the new pipeline (long imports/signatures wrapped to 80 cols). Co-Authored-By: Claude Opus 4.7 (1M context) * Bump jest stack to v30 + @types/jest v30 - jest 29.7 → 30.x - @types/jest 29.5 → 30.x - ts-jest 29.1 → 29.4 (last 29.x line; supports both jest 29/30 and TS 5.x. ts-jest 30 is not yet released.) @types/jest 30 dropped the .lastCalledWith shorthand; switch the test files to .toHaveBeenLastCalledWith. Behavior is identical. Co-Authored-By: Claude Opus 4.7 (1M context) * Add publint + arethetypeswrong checks to CI Both tools caught real issues in the dual-publish setup: - The shared "types" condition resolved CJS consumers to the ESM declaration file (FalseESM masquerade). Split exports into per-condition import/require blocks where each carries its own "types" entry pointing at the matching dist (mjs vs cjs). - publint asked for an explicit pkg.type so Node doesn't have to detect-and-cache. Set "type": "commonjs" at the root; src/package.json still overrides for the build, and the dist/{cjs,mjs}/package.json fixup is unchanged. CI gains a `publish-shape` job that runs `publint` + `attw --pack .` on the freshly built dist, so packaging regressions land in PR review rather than after publish. attw matrix is now 🟢 across node10, node16 (from CJS), node16 (from ESM), and bundler. publint reports "All good!". Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .eslintrc.js | 17 - .github/workflows/ci.yml | 13 + .github/workflows/npm-publish.yml | 8 +- README.md | 57 +- eslint.config.mjs | 21 + jest.config.js | 14 +- package-lock.json | 6370 +++++++++++++++++------------ package.json | 65 +- src/Cacheable.ts | 155 +- src/ConsoleLogger.ts | 10 +- src/buckets/MemoryBucket.ts | 2 +- src/index.ts | 11 +- src/package.json | 3 + src/types.ts | 13 +- tests/cascade.test.ts | 74 + tests/index.test.ts | 34 +- tests/resolve.test.ts | 29 +- tests/tsconfig.json | 4 +- tsconfig.cjs.json | 5 +- tsconfig.json | 6 +- tsconfig.mjs.json | 3 +- 21 files changed, 4033 insertions(+), 2881 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.mjs create mode 100644 src/package.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 4c74b36..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - }, - extends: [ - 'plugin:@typescript-eslint/recommended', - 'prettier', - 'plugin:prettier/recommended', - ], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - }, - ignorePatterns: ['dist'], -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d7e398..4dc225e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,3 +50,16 @@ jobs: cache: npm - run: npm ci - run: npm test + + publish-shape: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - run: npm run publint + - run: npm run attw diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index cb702d5..b1cc468 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -11,8 +11,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci @@ -22,8 +22,8 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org/ diff --git a/README.md b/README.md index b12e1f6..c44366e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![Language](https://img.shields.io/github/languages/top/grischaerbe/cacheables) -![Build](https://img.shields.io/github/workflow/status/grischaerbe/cacheables/Node.js%20Package) +![Build](https://img.shields.io/github/actions/workflow/status/grischaerbe/cacheables/ci.yml?branch=master) A small, typed cache with composable storage buckets and a handful of cache policies, written in TypeScript. @@ -18,7 +18,13 @@ import { Cacheable, MemoryBucket } from 'cacheables' const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) -cache.remember(() => fetch('https://some-url.com/api'), 'key') +// Cache the parsed JSON, not the Response — a Response body can be +// consumed exactly once, so caching the Response itself would break +// every call after the first .json(). +const data = await cache.remember( + () => fetch('https://some-url.com/api').then((r) => r.json()), + 'key', +) ``` - [Installation](#installation) @@ -65,7 +71,10 @@ const cache = new Cacheable('weather-data', { // `remember` is both getter and setter: on a miss it calls the resource // and writes to every bucket; on a hit it returns the cached value. -const getWeather = () => cache.remember(() => fetch(apiUrl), 'karlsruhe') +// Cache the parsed JSON rather than the Response object — the Response +// body can only be read once. +const getWeather = () => + cache.remember(() => fetch(apiUrl).then((r) => r.json()), 'karlsruhe') await getWeather() // miss — fetched await getWeather() // hit — cached @@ -104,15 +113,31 @@ Returns the cached value if present (subject to policy); otherwise calls `resour Same fresh-or-fetch behavior as `remember`, but returns the L1 bucket's view instead of the producer's value. Use this when the bucket produces a domain-specific projection that callers actually need — a filesystem bucket exposing a local URL after caching the bytes, a CDN bucket returning a presigned link, an IndexedDB bucket returning an `ObjectURL`: ```ts +import { Cacheable, type IBucket, type BucketEntryMeta } from 'cacheables' + interface UrlView { url: string } class FilesystemBucket implements IBucket { - // read / write / meta / delete / clear … + async read(key: string): Promise<{ value: T } | undefined> { + /* read bytes back from disk */ + } + async write(key: string, value: T, meta: BucketEntryMeta): Promise { + /* persist value to disk under a deterministic path AND store meta.storedAt */ + } + async meta(key: string): Promise { + /* read the sidecar */ + } async view(key: string): Promise<{ view: UrlView } | undefined> { /* return { view: { url: pathFor(key) } } when the entry exists */ } + async delete(key: string): Promise { + /* … */ + } + async clear(): Promise { + /* … */ + } } const cache = new Cacheable('images', { @@ -129,8 +154,6 @@ const { url } = await cache.resolve( `resolve` and `remember` share the same in-flight registry: a concurrent pair against the same key triggers `resource()` once. `resolve` honors the cache policy — a stale entry will trigger a producer call (or a background revalidation under `stale-while-revalidate`). -For buckets without a meaningful projection (e.g. the built-in `MemoryBucket`), `TView = void` and `cache.resolve()` resolves to `undefined`. - ### `cache.delete(key): Promise` / `cache.clear(): Promise` `delete` removes the entry from every bucket. `clear` wipes every bucket and the in-flight registry. @@ -405,21 +428,21 @@ interface ILogger { } ``` -Every `cache.remember(...)` and `cache.resolve(...)` emits one message, tagged `HIT` or `MISS` with the elapsed time: +Every `cache.remember(...)` and `cache.resolve(...)` emits one message, tagged `HIT` or `MISS` with the elapsed time. The format is `Cacheable "${namespace}:${key}": …` so two namespaces sharing one logger remain distinguishable: ``` -Cacheable "weather": MISS 12ms -Cacheable "weather": HIT 0.2ms +Cacheable "weather-data:karlsruhe": MISS 12ms +Cacheable "weather-data:karlsruhe": HIT 0.2ms ``` -The built-in `ConsoleLogger` forwards to `console.log`: +The built-in `consoleLogger` forwards to `console.log`: ```ts -import { Cacheable, ConsoleLogger, MemoryBucket } from 'cacheables' +import { Cacheable, consoleLogger, MemoryBucket } from 'cacheables' const cache = new Cacheable('app', { buckets: [new MemoryBucket()], - logger: new ConsoleLogger(), + logger: consoleLogger, }) ``` @@ -467,16 +490,16 @@ await cache.cacheable(() => fetch(url), 'weather', { ```ts // v3 -import { Cacheable, MemoryBucket, ConsoleLogger } from 'cacheables' +import { Cacheable, MemoryBucket, consoleLogger } from 'cacheables' const cache = new Cacheable('weather', { buckets: [new MemoryBucket()], policy: 'max-age', maxAge: 5_000, - logger: new ConsoleLogger(), + logger: consoleLogger, }) -await cache.remember(() => fetch(url), 'weather') +await cache.remember(() => fetch(url).then((r) => r.json()), 'weather') ``` Breaking changes: @@ -491,8 +514,8 @@ Breaking changes: - **`keys()` removed.** Enumerating heterogeneous async layers (some non-enumerable, like CDNs) has no single sensible semantic. - **`delete` and `clear` are async.** They now return `Promise` — add `await`. - **`isCached` removed.** v3 has no public presence-check API; if you need one, query your bucket directly (e.g. `await bucket.meta(fullKey)`). -- **`log` / `logTiming` replaced by `logger`.** Pass `new ConsoleLogger()` to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Each `remember()` call emits a single formatted message (`Cacheable "": HIT|MISS `) instead of `console.time` / `timeEnd`. -- **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's constructor options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `policy`, and `logger`; `namespace` is the constructor's first positional argument). +- **`log` / `logTiming` replaced by `logger`.** Pass the exported `consoleLogger` singleton to restore the previous default-on logging, or implement `ILogger` to route messages elsewhere. Each `remember()` call emits a single formatted message (`Cacheable ":": HIT|MISS `) instead of `console.time` / `timeEnd`. +- **Options types reshaped.** v2's `CacheOptions` (constructor) and `CacheableOptions` (per-call) are gone. v3's only exported options type is `CacheableOptions` — same name as v2's per-call type, completely different shape (it now carries `buckets`, `policy`, and `logger`; `namespace` is the constructor's first positional argument). v2's `CacheOptions` is no longer exported. - **Buckets can throw.** Any throw from any bucket rejects `remember()`. v2's in-memory store couldn't fail, so this is a new error surface to be aware of once you wire up a custom bucket. What's new: diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..f2942f2 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import tseslint from 'typescript-eslint' +import prettier from 'eslint-config-prettier' +import prettierPlugin from 'eslint-plugin-prettier/recommended' + +export default tseslint.config( + { ignores: ['dist', 'node_modules', 'coverage'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + prettier, + prettierPlugin, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_' }, + ], + }, + }, +) diff --git a/jest.config.js b/jest.config.js index 79bf5cf..9752586 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,14 @@ module.exports = { - preset: 'ts-jest', testEnvironment: 'node', - testPathIgnorePatterns: ['/node_modules/', './example/', './dist/'], - watchPathIgnorePatterns: ['/node_modules/', './example/', './dist/'], + transform: { + '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tests/tsconfig.json' }], + }, + // NodeNext source uses `.js` extensions in relative imports, but + // ts-jest resolves the underlying `.ts` files at test time. Strip + // the trailing `.js` so jest-resolve can find them. + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + watchPathIgnorePatterns: ['/node_modules/', '/dist/'], } diff --git a/package-lock.json b/package-lock.json index 31b9d83..8c3db08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,73 +9,150 @@ "version": "3.0.0", "license": "MIT", "devDependencies": { - "@size-limit/preset-small-lib": "^11.1.4", - "@types/jest": "^29.5.12", - "@typescript-eslint/eslint-plugin": "^7.12.0", - "@typescript-eslint/parser": "^7.12.0", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", - "jest": "^29.7.0", - "prettier": "^3.3.1", + "@arethetypeswrong/cli": "^0.18.2", + "@eslint/js": "^9.39.4", + "@size-limit/preset-small-lib": "^12.1.0", + "@types/jest": "^30.0.0", + "@typescript-eslint/eslint-plugin": "^8.59.1", + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "jest": "^30.3.0", + "prettier": "^3.8.3", + "publint": "^0.3.18", "rimraf": "^5.0.7", - "size-limit": "^11.1.4", - "ts-jest": "^29.1.4", - "tsx": "^4.11.2", - "typescript": "^5.4.5" + "size-limit": "^12.1.0", + "ts-jest": "^29.4.9", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.59.1" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.2.tgz", + "integrity": "sha512-PcFM20JNlevEDKBg4Re29Rtv2xvjvQZzg7ENnrWFSS0PHgdP2njibVFw+dRUhNkPgNfac9iUqO0ohAXqQL4hbw==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@arethetypeswrong/core": "0.18.2", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" }, "engines": { - "node": ">=6.0.0" + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.2.tgz", + "integrity": "sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.2", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@arethetypeswrong/core/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "node_modules/@babel/code-frame": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", - "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.6", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", - "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", - "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helpers": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/template": "^7.24.6", - "@babel/traverse": "^7.24.6", - "@babel/types": "^7.24.6", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -100,29 +177,32 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", - "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.6", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", - "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.24.6", - "@babel/helper-validator-option": "^7.24.6", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -130,87 +210,50 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", - "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", - "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", - "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", - "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.6" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", - "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-module-imports": "^7.24.6", - "@babel/helper-simple-access": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -220,169 +263,68 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz", - "integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", - "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", - "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", - "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", - "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", - "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.6", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.29.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", - "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -395,6 +337,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -407,6 +350,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -419,6 +363,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -426,11 +371,44 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -443,6 +421,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -451,12 +430,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz", - "integrity": "sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -470,6 +450,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -482,6 +463,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -494,6 +476,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -506,6 +489,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -518,6 +502,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -530,6 +515,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -537,11 +523,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -553,12 +556,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz", - "integrity": "sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -568,58 +572,48 @@ } }, "node_modules/@babel/template": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", - "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", - "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-hoist-variables": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6", - "debug": "^4.3.1", - "globals": "^11.1.0" + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", - "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -629,444 +623,680 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", - "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1082,11 +1312,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1189,6 +1427,7 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -1205,6 +1444,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -1214,6 +1454,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1223,10 +1464,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1240,6 +1482,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -1252,6 +1495,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -1267,6 +1511,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -1279,6 +1524,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1293,59 +1539,60 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1356,111 +1603,150 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, + "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", "dev": true, + "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@types/node": "*", + "jest-regex-util": "30.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1471,147 +1757,147 @@ } } }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", "dev": true, + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1623,64 +1909,45 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@loaderkit/resolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.5.tgz", + "integrity": "sha512-fhkdGM57xhJ7CO91MUgbQlb0ClP0AJ9vB3yoVnBTslYJqrJOCVEbOprZcxZlexdMbmTBPQqVcQYr+j4oRRtIZA==", "dev": true, + "license": "ISC", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "@braidai/lang": "^1.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, "node_modules/@pkgjs/parseargs": { @@ -1694,33 +1961,49 @@ } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@publint/pack": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.4.tgz", + "integrity": "sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@sinonjs/commons": { @@ -1728,721 +2011,362 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "15.3.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", + "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@size-limit/esbuild": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/@size-limit/esbuild/-/esbuild-11.1.4.tgz", - "integrity": "sha512-Nxh+Fw4Z7sFjRLeT7GDZIy297VXyJrMvG20UDSWP31QgglriEBDkW9U77T7W6js5FaEr89bYVrGzpHfmE1CLFw==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@size-limit/esbuild/-/esbuild-12.1.0.tgz", + "integrity": "sha512-Um6MVrX+05kIxI4+zk0ZByG9dA/Th1f+sfGc571D95BnCPc90/pl2+2OdsQuOyoWEbeAMqfcTKo0v07i+E65Vw==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "nanoid": "^5.0.7" + "esbuild": "^0.28.0", + "nanoid": "^5.1.7" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "size-limit": "11.1.4" + "size-limit": "12.1.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz", - "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==", - "cpu": [ - "ppc64" - ], + "node_modules/@size-limit/file": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-12.1.0.tgz", + "integrity": "sha512-eGwDcIufnNnvJRzv3liDOn6MAOGgmOTUdpeGQ2KuRTlgIgO54AJH1ilvktlJc6PIjNfwpYY0dOGyap1QgM1swQ==", "dev": true, - "optional": true, - "os": [ - "aix" - ], + "license": "MIT", "engines": { - "node": ">=12" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "size-limit": "12.1.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz", - "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==", - "cpu": [ - "arm" - ], + "node_modules/@size-limit/preset-small-lib": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@size-limit/preset-small-lib/-/preset-small-lib-12.1.0.tgz", + "integrity": "sha512-TVVQ/iuHbaGtHJrjur5s4XKYEyGk0nIwUAqhuzhKPbTyV9nYOH/laDelQ4vg3cGmm8sayRx998wxEdnwM/Yewg==", "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@size-limit/esbuild": "12.1.0", + "@size-limit/file": "12.1.0", + "size-limit": "12.1.0" + }, + "peerDependencies": { + "size-limit": "12.1.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz", - "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==", - "cpu": [ - "arm64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, + "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz", - "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==", - "cpu": [ - "x64" - ], + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz", - "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz", - "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==", - "cpu": [ - "x64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz", - "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz", - "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz", - "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==", - "cpu": [ - "arm" - ], + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz", - "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz", - "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==", - "cpu": [ - "ia32" - ], + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz", - "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==", - "cpu": [ - "loong64" - ], + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz", - "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==", - "cpu": [ - "mips64el" - ], + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz", - "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", + "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "undici-types": "~5.26.4" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz", - "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz", - "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==", - "cpu": [ - "s390x" - ], + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz", - "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz", - "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz", - "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz", - "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz", - "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz", - "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@size-limit/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz", - "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==", - "cpu": [ - "x64" - ], + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/@size-limit/esbuild/node_modules/esbuild": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz", - "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.4", - "@esbuild/android-arm": "0.21.4", - "@esbuild/android-arm64": "0.21.4", - "@esbuild/android-x64": "0.21.4", - "@esbuild/darwin-arm64": "0.21.4", - "@esbuild/darwin-x64": "0.21.4", - "@esbuild/freebsd-arm64": "0.21.4", - "@esbuild/freebsd-x64": "0.21.4", - "@esbuild/linux-arm": "0.21.4", - "@esbuild/linux-arm64": "0.21.4", - "@esbuild/linux-ia32": "0.21.4", - "@esbuild/linux-loong64": "0.21.4", - "@esbuild/linux-mips64el": "0.21.4", - "@esbuild/linux-ppc64": "0.21.4", - "@esbuild/linux-riscv64": "0.21.4", - "@esbuild/linux-s390x": "0.21.4", - "@esbuild/linux-x64": "0.21.4", - "@esbuild/netbsd-x64": "0.21.4", - "@esbuild/openbsd-x64": "0.21.4", - "@esbuild/sunos-x64": "0.21.4", - "@esbuild/win32-arm64": "0.21.4", - "@esbuild/win32-ia32": "0.21.4", - "@esbuild/win32-x64": "0.21.4" - } - }, - "node_modules/@size-limit/file": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-11.1.4.tgz", - "integrity": "sha512-QxnGj9cxhCEuqMAV01gqonXIKcc+caZqFHZpV51oL2ZJNGSPP9Q/yyf+7HbVe00faOFd1dZZwMwzZmX7HQ9LbA==", - "dev": true, - "engines": { - "node": "^18.0.0 || >=20.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "size-limit": "11.1.4" + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@size-limit/preset-small-lib": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/@size-limit/preset-small-lib/-/preset-small-lib-11.1.4.tgz", - "integrity": "sha512-wELW374esv+2Nlzf7g+qW4Af9L69duLoO9F52f0sGk/nzb6et7u8gLRvweWrBfm3itUrqHCpGSSVabTsIU8kNw==", + "node_modules/@typescript-eslint/parser": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, + "license": "MIT", "dependencies": { - "@size-limit/esbuild": "11.1.4", - "@size-limit/file": "11.1.4", - "size-limit": "11.1.4" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "size-limit": "11.1.4" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", - "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/type-utils": "7.12.0", - "@typescript-eslint/utils": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", - "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", - "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", - "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/utils": "7.12.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2450,119 +2374,409 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", - "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", - "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", - "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.12.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2575,15 +2789,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2600,6 +2816,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -2610,18 +2827,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2646,11 +2851,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2659,135 +2872,123 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "Python-2.0" }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, + "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -2796,16 +2997,17 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", "dev": true, - "engines": { - "node": ">=8" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { @@ -2818,22 +3020,10 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -2849,11 +3039,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -2884,16 +3076,18 @@ } }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" }, "node_modules/bytes-iec": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/bytes-iec/-/bytes-iec-3.1.1.tgz", "integrity": "sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2912,14 +3106,15 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001628", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001628.tgz", - "integrity": "sha512-S3BnR4Kh26TBxbi5t5kpbcUlLJb9lhtDXISDPwOfI+JoC+ik0QksvkZtUVyikw3hjnkgkMPSJ8oIM9yMm9vflA==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "dev": true, "funding": [ { @@ -2934,13 +3129,15 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2957,38 +3154,15 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -2996,21 +3170,103 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", - "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", - "dev": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -3025,16 +3281,18 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", @@ -3054,6 +3312,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3066,32 +3334,12 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3102,12 +3350,13 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3119,10 +3368,11 @@ } }, "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, + "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -3143,6 +3393,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3152,43 +3403,11 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3196,16 +3415,18 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.790", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.790.tgz", - "integrity": "sha512-eVGeQxpaBYbomDBa/Mehrs28MdvCXfJmEFzaMFsv8jH/MJDLIylJN81eTJ5kvx7B7p18OiPK0BkC06lydEy63A==", - "dev": true + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3219,58 +3440,84 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3288,80 +3535,90 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3372,7 +3629,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -3384,27 +3641,29 @@ } } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3412,13 +3671,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/glob-parent": { @@ -3433,18 +3696,42 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3455,6 +3742,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3475,20 +3763,12 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3496,11 +3776,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3510,6 +3791,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3519,6 +3801,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -3537,58 +3820,47 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } + "license": "Apache-2.0" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -3602,15 +3874,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3620,28 +3883,24 @@ "bser": "2.1.1" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } + "license": "MIT" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, "node_modules/find-up": { @@ -3661,39 +3920,25 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/foreground-child": { "version": "3.1.1", @@ -3726,8 +3971,9 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3743,15 +3989,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3766,6 +4003,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3775,6 +4013,7 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -3784,6 +4023,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -3804,68 +4044,61 @@ } }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "balanced-match": "^1.0.0" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "type-fest": "^0.20.2" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3877,11 +4110,27 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } }, "node_modules/has-flag": { "version": "4.0.0", @@ -3892,47 +4141,49 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.4" + "node": "*" } }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3945,10 +4196,11 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, + "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -3975,9 +4227,10 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3987,37 +4240,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1", @@ -4042,6 +4273,7 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4058,29 +4290,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -4104,10 +4319,11 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", - "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -4119,23 +4335,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -4146,24 +4351,26 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -4191,21 +4398,22 @@ } }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -4217,73 +4425,75 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -4295,234 +4505,210 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, + "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.3.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, + "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -4530,6 +4716,7 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -4543,280 +4730,265 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, + "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, + "license": "MIT", "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jiti": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.1.tgz", - "integrity": "sha512-KMXpzEJMsOFyRj6ZpDTnnlJrdr9umUY+eut5vlRvjVixohitnRFIRTFw9MEu9zPlBxTHZo6xD5ftKYiQZuJYQw==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -4825,28 +4997,38 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -4866,13 +5048,14 @@ "node": ">=6" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" } }, "node_modules/leven": { @@ -4880,6 +5063,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4898,10 +5082,11 @@ } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -4913,7 +5098,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -4942,11 +5128,22 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -4957,18 +5154,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4984,48 +5169,105 @@ "tmpl": "1.0.5" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, "engines": { - "node": ">= 8" + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" } }, - "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "environment": "^1.0.0" }, "engines": { - "node": ">=8.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked-terminal/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5033,6 +5275,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -5042,16 +5294,39 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } }, "node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", "dev": true, "funding": [ { @@ -5059,6 +5334,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.js" }, @@ -5067,12 +5343,29 @@ } }, "node_modules/nanospinner": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nanospinner/-/nanospinner-1.1.0.tgz", - "integrity": "sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/nanospinner/-/nanospinner-1.2.2.tgz", + "integrity": "sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==", "dev": true, + "license": "MIT", "dependencies": { - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" } }, "node_modules/natural-compare": { @@ -5081,6 +5374,29 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5088,16 +5404,18 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5107,6 +5425,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -5114,11 +5433,22 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -5128,6 +5458,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -5190,15 +5521,31 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -5211,6 +5558,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -5224,6 +5572,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5236,8 +5608,9 @@ "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5251,12 +5624,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -5282,38 +5649,32 @@ "node": "14 || >=16.14" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -5323,6 +5684,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -5335,6 +5697,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -5348,6 +5711,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -5360,6 +5724,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -5375,6 +5740,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -5392,10 +5758,11 @@ } }, "node_modules/prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.1.tgz", - "integrity": "sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -5407,10 +5774,11 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -5419,17 +5787,18 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -5437,6 +5806,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5444,38 +5814,42 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "node_modules/publint": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.18.tgz", + "integrity": "sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==", "dev": true, + "license": "MIT", "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" + "@publint/pack": "^0.1.4", + "package-manager-detector": "^1.6.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" }, "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -5486,71 +5860,32 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + ], + "license": "MIT" }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -5563,6 +5898,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5572,6 +5908,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -5585,25 +5922,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rimraf": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", @@ -5622,73 +5940,30 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "mri": "^1.1.0" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=6" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" + "node": ">=10" } }, "node_modules/shebang-command": { @@ -5718,75 +5993,45 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "node_modules/size-limit": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-11.1.4.tgz", - "integrity": "sha512-V2JAI/Z7h8sEuxU3V+Ig3XKA5FcYbI4CZ7sh6s7wvuy+TUwDZYqw7sAqrHhQ4cgcNfPKIAHAaH8VaqOdbcwJDA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-12.1.0.tgz", + "integrity": "sha512-VnDS2fycANrJFVPQwjaD+h+hkISY7EB3LsPsYWje4lBCjQwwsZLxjwwRwVJKHrcj2ZqyG+DdXykWm9mbZklZrw==", "dev": true, + "license": "MIT", "dependencies": { "bytes-iec": "^3.1.1", - "chokidar": "^3.6.0", - "globby": "^14.0.1", - "jiti": "^1.21.0", - "lilconfig": "^3.1.1", - "nanospinner": "^1.1.0", - "picocolors": "^1.0.1" + "lilconfig": "^3.1.3", + "nanospinner": "^1.2.2", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.16" }, "bin": { "size-limit": "bin.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/size-limit/node_modules/globby": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", - "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, - "engines": { - "node": ">=18" + "peerDependencies": { + "jiti": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/size-limit/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/size-limit/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/slash": { @@ -5803,21 +6048,35 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -5830,6 +6089,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5839,6 +6099,7 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -5906,6 +6167,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5915,6 +6177,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5925,193 +6188,747 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": ">=18" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8.0" + "node": ">=18" } }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" + "node": ">=18" } }, - "node_modules/ts-jest": { - "version": "29.1.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz", - "integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==", + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } + "node": ">=18" } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true - }, - "node_modules/tsx": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.2.tgz", - "integrity": "sha512-V5DL5v1BuItjsQ2FN9+4OjR7n5cr8hSgN+VGmm/fd2/0cgQdBIWHcQ3bFYm/5ZTmyxkTDBUIaRuW2divgfPe0A==", + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, - "dependencies": { - "esbuild": "~0.20.2", - "get-tsconfig": "^4.7.5" - }, + "hasInstallScript": true, + "license": "MIT", "bin": { - "tsx": "dist/cli.mjs" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" }, "optionalDependencies": { - "fsevents": "~2.3.3" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/type-check": { @@ -6131,15 +6948,17 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -6148,10 +6967,11 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6160,28 +6980,99 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=4" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -6197,9 +7088,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -6213,15 +7105,17 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -6231,6 +7125,16 @@ "node": ">=10.12.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -6264,11 +7168,19 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6302,20 +7214,35 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/y18n": { @@ -6323,15 +7250,24 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", diff --git a/package.json b/package.json index e54555c..b6418aa 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,30 @@ { "name": "cacheables", "version": "3.0.0", - "description": "A simple in-memory cache written in Typescript with automatic cache invalidation and an elegant syntax.", + "description": "A small, typed cache with multilayer storage buckets, five cache policies (incl. stale-while-revalidate) and concurrency-safe deduplication. Zero dependencies, browser + Node.", + "type": "commonjs", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", "types": "dist/mjs/index.d.ts", + "files": [ + "dist", + "README.md", + "LICENSE.md" + ], + "engines": { + "node": ">=18" + }, + "sideEffects": false, "exports": { ".": { - "import": "./dist/mjs/index.js", - "require": "./dist/cjs/index.js", - "types": "./dist/mjs/index.d.ts" + "import": { + "types": "./dist/mjs/index.d.ts", + "default": "./dist/mjs/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } } }, "scripts": { @@ -20,7 +35,9 @@ "eslint": "eslint --fix src/**/*.ts", "lint": "eslint src/**/*.ts", "format:check": "prettier --check .", - "build": "rimraf dist && tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json && tsx ./fixup-packages.ts" + "build": "rimraf dist && tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json && tsx ./fixup-packages.ts", + "publint": "publint", + "attw": "attw --pack ." }, "repository": { "type": "git", @@ -38,7 +55,13 @@ "in-memory", "typescript", "remember", - "cacheables" + "cacheables", + "multilayer", + "multi-tier", + "stale-while-revalidate", + "swr", + "redis", + "s3" ], "author": "Grischa Erbe ", "license": "MIT", @@ -47,19 +70,23 @@ }, "homepage": "https://github.com/grischaerbe/cacheables#readme", "devDependencies": { - "@size-limit/preset-small-lib": "^11.1.4", - "@types/jest": "^29.5.12", - "@typescript-eslint/eslint-plugin": "^7.12.0", - "@typescript-eslint/parser": "^7.12.0", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", - "jest": "^29.7.0", - "prettier": "^3.3.1", + "@arethetypeswrong/cli": "^0.18.2", + "@eslint/js": "^9.39.4", + "@size-limit/preset-small-lib": "^12.1.0", + "@types/jest": "^30.0.0", + "@typescript-eslint/eslint-plugin": "^8.59.1", + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "jest": "^30.3.0", + "prettier": "^3.8.3", + "publint": "^0.3.18", "rimraf": "^5.0.7", - "size-limit": "^11.1.4", - "ts-jest": "^29.1.4", - "tsx": "^4.11.2", - "typescript": "^5.4.5" + "size-limit": "^12.1.0", + "ts-jest": "^29.4.9", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.59.1" } } diff --git a/src/Cacheable.ts b/src/Cacheable.ts index cfbcd96..2339eae 100644 --- a/src/Cacheable.ts +++ b/src/Cacheable.ts @@ -4,7 +4,7 @@ import type { IBucket, ILogger, Policy, -} from './types' +} from './types.js' type FreshnessPredicate = (meta: BucketEntryMeta) => boolean @@ -13,21 +13,30 @@ type CascadeFn = ( isFresh?: FreshnessPredicate, ) => Promise<{ result: R; meta: BucketEntryMeta } | undefined> +interface CascadeHit { + bucket: IBucket + idx: number + meta: BucketEntryMeta +} + export class Cacheable { logger: ILogger | undefined #policy: Policy #maxAge: number | undefined #buckets: IBucket[] + #l1: IBucket #namespace: string #policyInflight = new Map>() #producerInflight = new Map>() constructor(namespace: string, options: CacheableOptions) { - if (!options.buckets || options.buckets.length === 0) { + const [l1] = options.buckets ?? [] + if (!l1) { throw new Error('At least one bucket is required') } this.#buckets = options.buckets + this.#l1 = l1 this.#namespace = namespace this.logger = options.logger this.#policy = options.policy ?? 'cache-only' @@ -48,6 +57,16 @@ export class Cacheable { async delete(key: string): Promise { const fullKey = this.#fullKey(key) + // Drop any in-flight registrations for this key so a producer that + // is mid-fetch when delete() is called can't be reused as if it + // were fresh, and so a subsequent remember()/resolve() does not + // attach to a soon-to-be-stale promise. + // TODO: thread an AbortSignal through #produceAndWrite so an + // in-flight producer's network work is actually cancelled here, + // not just orphaned. + this.#policyInflight.delete(this.#dedupKey(key, 'value')) + this.#policyInflight.delete(this.#dedupKey(key, 'view')) + this.#producerInflight.delete(fullKey) await Promise.all(this.#buckets.map((b) => b.delete(fullKey))) } @@ -73,7 +92,9 @@ export class Cacheable { if (logger) { const elapsed = Math.round((performance.now() - start) * 10) / 10 - logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) + logger.log( + `Cacheable "${this.#namespace}:${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`, + ) } return result @@ -95,14 +116,16 @@ export class Cacheable { if (logger) { const elapsed = Math.round((performance.now() - start) * 10) / 10 - logger.log(`Cacheable "${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`) + logger.log( + `Cacheable "${this.#namespace}:${key}": ${hit ? 'HIT' : 'MISS'} ${elapsed}ms`, + ) } return result } async #viewFromL1(fullKey: string): Promise { - const wrapped = await this.#buckets[0]!.view(fullKey) + const wrapped = await this.#l1.view(fullKey) if (wrapped === undefined) { throw new Error( `Cacheable: L1 bucket returned no view for "${fullKey}" after a successful cascade write`, @@ -151,26 +174,28 @@ export class Cacheable { }) } case 'stale-while-revalidate': { - const cached = await cascadeFn(fullKey) - const maxAge = this.#maxAge - const isStale = - !cached || - maxAge === undefined || - Date.now() - cached.meta.storedAt > maxAge - - if (cached && !isStale) { - return { result: cached.result, hit: true } - } - - if (cached && isStale) { - this.#produceAndWrite(fullKey, resource).catch(() => { - /* swallow background revalidation errors */ - }) - return { result: cached.result, hit: true } - } - - const value = await this.#produceAndWrite(fullKey, resource) - return { result: await fromValue(value), hit: false } + return this.#dedupPolicy(dedupKey, async () => { + const cached = await cascadeFn(fullKey) + const maxAge = this.#maxAge + const isStale = + !cached || + maxAge === undefined || + Date.now() - cached.meta.storedAt > maxAge + + if (cached && !isStale) { + return { result: cached.result, hit: true } + } + + if (cached && isStale) { + this.#produceAndWrite(fullKey, resource).catch(() => { + /* swallow background revalidation errors */ + }) + return { result: cached.result, hit: true } + } + + const value = await this.#produceAndWrite(fullKey, resource) + return { result: await fromValue(value), hit: false } + }) } } } @@ -210,13 +235,18 @@ export class Cacheable { return Promise.all(this.#buckets.map((b) => b.meta(fullKey))) } - #findHitIdx( + #findHit( probes: (BucketEntryMeta | undefined)[], isFresh?: FreshnessPredicate, - ): number { - return probes.findIndex( - (m) => m !== undefined && (isFresh ? isFresh(m) : true), - ) + ): CascadeHit | undefined { + for (let i = 0; i < this.#buckets.length; i++) { + const meta = probes[i] + const bucket = this.#buckets[i] + if (!bucket || meta === undefined) continue + if (isFresh && !isFresh(meta)) continue + return { bucket, idx: i, meta } + } + return undefined } async #cascadeRead( @@ -224,17 +254,21 @@ export class Cacheable { isFresh?: FreshnessPredicate, ): Promise<{ result: T; meta: BucketEntryMeta } | undefined> { const probes = await this.#cascadeProbe(fullKey) - const hitIdx = this.#findHitIdx(probes, isFresh) - if (hitIdx === -1) return undefined + const hit = this.#findHit(probes, isFresh) + if (!hit) return undefined - const bucket = this.#buckets[hitIdx]! - const result = await bucket.read(fullKey) + const result = await (hit.bucket as IBucket).read(fullKey) if (result === undefined) return undefined - const value = result.value - const hitMeta = probes[hitIdx]! - await this.#cascadeFill(fullKey, value, hitMeta, probes, hitIdx, isFresh) - return { result: value, meta: hitMeta } + await this.#cascadeFill( + fullKey, + result.value, + hit.meta, + probes, + hit.idx, + isFresh, + ) + return { result: result.value, meta: hit.meta } } async #cascadeResolve( @@ -242,35 +276,46 @@ export class Cacheable { isFresh?: FreshnessPredicate, ): Promise<{ result: TView; meta: BucketEntryMeta } | undefined> { const probes = await this.#cascadeProbe(fullKey) - const hitIdx = this.#findHitIdx(probes, isFresh) - if (hitIdx === -1) return undefined + const hit = this.#findHit(probes, isFresh) + if (!hit) return undefined - const hitMeta = probes[hitIdx]! const needsFill = probes.some( (m, i) => - i !== hitIdx && (m === undefined || (isFresh ? !isFresh(m) : false)), + i !== hit.idx && (m === undefined || (isFresh ? !isFresh(m) : false)), ) if (!needsFill) { - const wrapped = await this.#buckets[0]!.view(fullKey) + const wrapped = await this.#l1.view(fullKey) if (wrapped === undefined) return undefined - return { result: wrapped.view, meta: hitMeta } + return { result: wrapped.view, meta: hit.meta } } - const result = await this.#buckets[hitIdx]!.read(fullKey) + const result = await (hit.bucket as IBucket).read(fullKey) if (result === undefined) return undefined await this.#cascadeFill( fullKey, result.value, - hitMeta, + hit.meta, probes, - hitIdx, + hit.idx, isFresh, ) - const wrapped = await this.#buckets[0]!.view(fullKey) - if (wrapped === undefined) return undefined - return { result: wrapped.view, meta: hitMeta } + const wrapped = await this.#l1.view(fullKey) + if (wrapped === undefined) { + if (hit.idx !== 0) { + // cascadeFill just wrote to L1. Absence here is a strict-mode + // error per the IBucket contract. + throw new Error( + `Cacheable: L1 bucket returned no view for "${fullKey}" after a successful cascade fill`, + ) + } + // hit.idx === 0: cascadeFill skipped L1; the entry was raced away + // between the meta probe and the post-fill view. Heal by falling + // through to the producer. + return undefined + } + return { result: wrapped.view, meta: hit.meta } } async #cascadeFill( @@ -282,12 +327,12 @@ export class Cacheable { isFresh?: FreshnessPredicate, ): Promise { const writes: Promise[] = [] - for (let i = 0; i < this.#buckets.length; i++) { - if (i === hitIdx) continue + this.#buckets.forEach((bucket, i) => { + if (i === hitIdx) return const probe = probes[i] - if (probe !== undefined && (!isFresh || isFresh(probe))) continue - writes.push(this.#buckets[i]!.write(fullKey, value, hitMeta)) - } + if (probe !== undefined && (!isFresh || isFresh(probe))) return + writes.push(bucket.write(fullKey, value, hitMeta)) + }) if (writes.length > 0) await Promise.all(writes) } diff --git a/src/ConsoleLogger.ts b/src/ConsoleLogger.ts index f19a1b3..bce337d 100644 --- a/src/ConsoleLogger.ts +++ b/src/ConsoleLogger.ts @@ -1,8 +1,6 @@ -import type { ILogger } from './types' +import type { ILogger } from './types.js' -export class ConsoleLogger implements ILogger { - log(message: string): void { - // eslint-disable-next-line no-console - console.log(message) - } +export const consoleLogger: ILogger = { + // eslint-disable-next-line no-console + log: (message) => console.log(message), } diff --git a/src/buckets/MemoryBucket.ts b/src/buckets/MemoryBucket.ts index 7031d1c..881618f 100644 --- a/src/buckets/MemoryBucket.ts +++ b/src/buckets/MemoryBucket.ts @@ -1,4 +1,4 @@ -import type { BucketEntryMeta, IBucket } from '../types' +import type { BucketEntryMeta, IBucket } from '../types.js' export class MemoryBucket implements IBucket { #store = new Map() diff --git a/src/index.ts b/src/index.ts index a69e892..ed9b5a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,11 @@ -export { Cacheable } from './Cacheable' -export { ConsoleLogger } from './ConsoleLogger' -export { MemoryBucket } from './buckets/MemoryBucket' +export { Cacheable } from './Cacheable.js' +export { consoleLogger } from './ConsoleLogger.js' +export { MemoryBucket } from './buckets/MemoryBucket.js' export type { BucketEntryMeta, - CacheOptions, CacheableOptions, IBucket, ILogger, -} from './types' + Policy, + PolicyOptions, +} from './types.js' diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/src/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/src/types.ts b/src/types.ts index 4980e96..7338194 100644 --- a/src/types.ts +++ b/src/types.ts @@ -55,9 +55,10 @@ export interface IBucket { /** * Logger contract. Implement to route cache messages into your own * logging stack. When a `Cacheable` is constructed with a `logger`, - * the engine emits a timing message and a hit-count message on every - * `remember()` invocation. When no logger is provided, the engine is - * silent. + * the engine emits one combined `HIT`/`MISS` message per + * `remember()` or `resolve()` call, formatted as + * `Cacheable ":": HIT|MISS `. When no logger is + * provided, the engine is silent. */ export interface ILogger { log(message: string): void @@ -110,12 +111,6 @@ export type Policy = | 'max-age' | 'stale-while-revalidate' -/** - * Combined cache options without bucket wiring. Kept exported for - * backwards-compatible consumer types that mirror the policy shape. - */ -export type CacheOptions = CacheOptionsBase & PolicyOptions - /** * Constructor options for `Cacheable`. The namespace is passed * as a positional argument; this bag carries everything else. diff --git a/tests/cascade.test.ts b/tests/cascade.test.ts index 6b0387b..93fffeb 100644 --- a/tests/cascade.test.ts +++ b/tests/cascade.test.ts @@ -253,6 +253,45 @@ describe('cascade behavior', () => { expect(l2.deleteCalls).toEqual(['test:k']) }) + it('delete clears in-flight registrations so a later call does not attach to an orphaned producer', async () => { + const tick = () => new Promise((resolve) => setTimeout(resolve, 0)) + + let resolveFirst!: (value: string) => void + const firstResource = () => + new Promise((resolve) => { + resolveFirst = resolve + }) + + const l1 = new FakeBucket() + const cache = new Cacheable('test', { buckets: [l1] }) + + // Kick off a remember; do NOT await it. After one macrotask its + // producer is registered in the in-flight map but suspended on + // firstResource. + const first = cache.remember(firstResource, 'k') + await tick() + + await cache.delete('k') + + // After delete the in-flight registry must be empty for this key, + // so this second remember has to run its own producer instead of + // sharing the orphaned one above. + let secondCalls = 0 + const second = cache.remember(async () => { + secondCalls += 1 + return 'fresh' + }, 'k') + + // Let the second producer land before unblocking the orphan. + await tick() + resolveFirst('orphan') + + const [a, b] = await Promise.all([first, second]) + expect(a).toBe('orphan') + expect(b).toBe('fresh') + expect(secondCalls).toBe(1) + }) + it('clear propagates to all buckets', async () => { const l1 = new FakeBucket() const l2 = new FakeBucket() @@ -466,6 +505,41 @@ describe('concurrent dedup', () => { expect(calls).toBe(1) }) + it('SWR stale: 100 concurrent stale reads share one cascade probe and one revalidation', async () => { + const seedMeta: BucketEntryMeta = { storedAt: Date.now() - 10_000 } + const l1 = new FakeBucket({ key: 'test:k', value: 'stale', meta: seedMeta }) + const cache = new Cacheable('test', { + buckets: [l1], + policy: 'stale-while-revalidate', + maxAge: 100, + }) + + l1.metaCalls = 0 + l1.readCalls = 0 + + let calls = 0 + const slow = async () => { + calls += 1 + await wait(20) + return 'fresh' + } + + const results = await Promise.all( + Array.from({ length: 100 }, () => cache.remember(slow, 'k')), + ) + + // All 100 callers see the stale value immediately. + expect(results.every((r) => r === 'stale')).toBe(true) + // Outer dedup: one cascade probe + one value read shared by all. + expect(l1.metaCalls).toBe(1) + expect(l1.readCalls).toBe(1) + // Background revalidation fires exactly once. + // Wait for it to land before checking calls so we don't race the + // promise's queued callback. + await wait(50) + expect(calls).toBe(1) + }) + it('cache-only: 100 concurrent hit callers share the policy run (1 meta probe, 1 read)', async () => { const seedMeta: BucketEntryMeta = { storedAt: Date.now() } const l1 = new FakeBucket({ diff --git a/tests/index.test.ts b/tests/index.test.ts index 34426bd..77ccb0a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,4 +1,4 @@ -import { Cacheable, ConsoleLogger, MemoryBucket } from '../src' +import { Cacheable, consoleLogger, MemoryBucket } from '../src' const errorMessage = 'This is an error message.' @@ -80,24 +80,24 @@ describe('Cache operations', () => { const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - logger: new ConsoleLogger(), + logger: consoleLogger, }) const cachedRequest = () => cache.remember(() => mockedApiRequest(1), 'a') await cachedRequest() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": MISS \d+(\.\d+)?ms$/), ) await cachedRequest() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": HIT \d+(\.\d+)?ms$/), ) await cachedRequest() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": HIT \d+(\.\d+)?ms$/), ) }) @@ -146,7 +146,7 @@ describe('Cache operations', () => { const cache = new Cacheable('test', { buckets: [new MemoryBucket()], - logger: new ConsoleLogger(), + logger: consoleLogger, policy: 'max-age', maxAge: 100, }) @@ -157,30 +157,30 @@ describe('Cache operations', () => { // This should be a miss and take ~10ms await hitCache() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": MISS \d+(\.\d+)?ms$/), ) // This should be a hit and take ~0ms await hitCache() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": HIT \d+(\.\d+)?ms$/), ) await wait(60) // This should be a hit and take ~0ms await hitCache() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": HIT \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": HIT \d+(\.\d+)?ms$/), ) await wait(60) // This should be a miss and take ~10ms await hitCache() - expect(console.log).lastCalledWith( - expect.stringMatching(/^Cacheable "a": MISS \d+(\.\d+)?ms$/), + expect(console.log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:a": MISS \d+(\.\d+)?ms$/), ) }) diff --git a/tests/resolve.test.ts b/tests/resolve.test.ts index 2ab642d..f0a406f 100644 --- a/tests/resolve.test.ts +++ b/tests/resolve.test.ts @@ -215,13 +215,13 @@ describe('cache.resolve(): policy semantics', () => { }) await cache.resolve(async () => 'v', 'k') - expect(log).lastCalledWith( - expect.stringMatching(/^Cacheable "k": MISS \d+(\.\d+)?ms$/), + expect(log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:k": MISS \d+(\.\d+)?ms$/), ) await cache.resolve(async () => 'v', 'k') - expect(log).lastCalledWith( - expect.stringMatching(/^Cacheable "k": HIT \d+(\.\d+)?ms$/), + expect(log).toHaveBeenLastCalledWith( + expect.stringMatching(/^Cacheable "test:k": HIT \d+(\.\d+)?ms$/), ) }) }) @@ -347,4 +347,25 @@ describe('cache.resolve(): race healing and strict-mode', () => { /returned no view.*after a successful cascade write/, ) }) + + it('throws strict-mode error when L1 view absent after cascade fill from a deeper hit', async () => { + const l1 = new FakeViewBucket() + l1.view = async () => undefined // L1 never exposes a view + const l2 = new FakeViewBucket({ + key: 'test:k', + value: 'l2', + meta: { storedAt: Date.now() }, + }) + const cache = new Cacheable('test', { buckets: [l1, l2] }) + + let producerCalls = 0 + await expect( + cache.resolve(async () => { + producerCalls += 1 + return 'fresh' + }, 'k'), + ).rejects.toThrow(/returned no view.*after a successful cascade fill/) + // Strict: we did not silently re-run the producer to mask the bug. + expect(producerCalls).toBe(0) + }) }) diff --git a/tests/tsconfig.json b/tests/tsconfig.json index e0580c5..f735e3f 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../tsconfig.json", "compilerOptions": { "noEmit": true, - "rootDir": ".." + "rootDir": "..", + "module": "CommonJS", + "moduleResolution": "Node" }, "include": ["."] } diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 17a525f..4423650 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -1,8 +1,9 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "CommonJS", + "moduleResolution": "Node", "outDir": "dist/cjs", - "target": "es2015" + "target": "es2022" } } diff --git a/tsconfig.json b/tsconfig.json index bc65c1d..f333f4b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "target": "ESNext", - "module": "ESNext", + "module": "NodeNext", "declaration": true, - "lib": ["ESNext", "DOM"], - "moduleResolution": "Node", + "lib": ["ES2022", "DOM"], + "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "removeComments": false, diff --git a/tsconfig.mjs.json b/tsconfig.mjs.json index 0813561..1a0c169 100644 --- a/tsconfig.mjs.json +++ b/tsconfig.mjs.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "esnext", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "dist/mjs", "target": "esnext" } From 54b9a03949dbbd85bcb23f54df005f00e49ac469 Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:46:59 +0200 Subject: [PATCH 21/23] Switch build pipeline to tsdown and fix attw resolution (#30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor build pipeline to tsdown Replace dual tsc invocation (tsconfig.cjs.json + tsconfig.mjs.json) and the post-build fixup script with a single tsdown config that emits both CJS and ESM with type declarations. Co-Authored-By: Claude Opus 4.7 (1M context) * Fix attw resolution errors Point package.json paths at the files tsdown actually emits and split types per condition so node10, node16 (CJS+ESM) and bundler resolve correctly: - Pin tsdown entry to ./src/index.ts so output is dist/index.* (was dist/src.*). - Update main/types and exports to .cjs/.mjs/.d.cts/.d.mts. - Fix size-limit path that referenced the old dist/mjs layout. - Move tsdown into devDependencies — it is a build tool, not runtime. Co-Authored-By: Claude Opus 4.7 (1M context) * Rename jest.config to .cjs The package is now type=module, so Node loads .js as ESM and the CommonJS jest config blew up with "module is not defined". Use the .cjs extension to keep it as CommonJS without rewriting the config. Co-Authored-By: Claude Opus 4.7 (1M context) * Migrate tests from jest to vitest Vitest is ESM-first and uses Vite's resolver, so the workarounds we needed for jest under "type": "module" all go away: - Drop jest.config.cjs (and the .cjs rename); replace with a small vitest.config.ts that enables globals so describe/it/expect keep working without imports. - Drop ts-jest, jest, @types/jest; add vitest. Tests transform via esbuild, no NodeNext .js suffix mapping needed. - Switch the two jest.fn() calls to vi.fn() (vi is a vitest global). - Restore tests/tsconfig.json with moduleResolution: "Bundler" and vitest/globals types so the editor type-checks tests against how vitest actually resolves them. Co-Authored-By: Claude Opus 4.7 (1M context) * Convert .prettierrc.js to ESM Same problem as jest.config.js: under "type": "module" Node loads .js as ESM, so module.exports throws ReferenceError when prettier (via eslint-plugin-prettier) loads the config. Switching to export default keeps the filename and works under either module system. Co-Authored-By: Claude Opus 4.7 (1M context) * Upgrade ESLint to v10 and switch config to TypeScript - eslint and @eslint/js to v10 (typescript-eslint already declares ^10.0.0 in its peer range, so no other plugin updates needed). - Rename eslint.config.mjs to eslint.config.ts; ESLint 9.18+ loads TypeScript configs natively when jiti is present. - Add jiti as a devDependency (the loader ESLint uses for .ts configs). - Drop @typescript-eslint/eslint-plugin and @typescript-eslint/parser from direct deps — the unified typescript-eslint meta-package already bundles them. Co-Authored-By: Claude Opus 4.7 (1M context) * Move Prettier config to TypeScript Prettier 3.5+ loads .ts config files via Node's built-in type stripping (stable on >=22.6, on by default from 22.18). Convert .prettierrc.js to .prettierrc.ts and add a `satisfies Config` for typed feedback in the editor. Bump CI from Node 20 to Node 22 so the new config loads in CI too. The package's own engines.node stays at >=18 — this is a dev-tooling requirement, not a runtime one. Prettier itself is already at the latest 3.8.3, no upgrade needed. Co-Authored-By: Claude Opus 4.7 (1M context) * Drop jiti from devDependencies ESLint only needs jiti to load eslint.config.ts on runtimes without native TypeScript stripping. We pinned CI to Node 22, where strip-types is on by default (>=22.18), so ESLint just imports the TS config directly — verified via --debug, no jiti loader involvement. It's still pulled in transitively by ESLint's optional peer and by size-limit / vite, so it remains in node_modules; we just no longer need to declare it ourselves. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 10 +- .github/workflows/npm-publish.yml | 4 +- .gitignore | 7 +- .prettierrc.js => .prettierrc.ts | 6 +- eslint.config.mjs => eslint.config.ts | 0 fixup-packages.ts | 19 - jest.config.js | 14 - package-lock.json | 6066 ++++++++----------------- package.json | 40 +- tests/index.test.ts | 4 +- tests/resolve.test.ts | 2 +- tests/tsconfig.json | 5 +- tsconfig.cjs.json | 9 - tsconfig.mjs.json | 9 - tsdown.config.ts | 10 + vitest.config.ts | 8 + 16 files changed, 1916 insertions(+), 4297 deletions(-) rename .prettierrc.js => .prettierrc.ts (55%) rename eslint.config.mjs => eslint.config.ts (100%) delete mode 100644 fixup-packages.ts delete mode 100644 jest.config.js delete mode 100644 tsconfig.cjs.json delete mode 100644 tsconfig.mjs.json create mode 100644 tsdown.config.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dc225e..231711a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci - run: npm run typecheck @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci - run: npm run build @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci - run: npm run lint @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci - run: npm test @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci - run: npm run build diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index b1cc468..663e89f 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - run: npm ci - run: npm test @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm publish diff --git a/.gitignore b/.gitignore index 4d9a407..f967cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -227,4 +227,9 @@ fabric.properties # https://plugins.jetbrains.com/plugin/12206-codestream .idea/codestream.xml -# End of https://www.toptal.com/developers/gitignore/api/webstorm,node \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/webstorm,node + +.context +.agents +.claude +skills-lock.json \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.ts similarity index 55% rename from .prettierrc.js rename to .prettierrc.ts index 467ad32..a206197 100644 --- a/.prettierrc.js +++ b/.prettierrc.ts @@ -1,7 +1,9 @@ -module.exports = { +import type { Config } from 'prettier' + +export default { semi: false, trailingComma: 'all', singleQuote: true, printWidth: 80, tabWidth: 2, -} +} satisfies Config diff --git a/eslint.config.mjs b/eslint.config.ts similarity index 100% rename from eslint.config.mjs rename to eslint.config.ts diff --git a/fixup-packages.ts b/fixup-packages.ts deleted file mode 100644 index 2fac702..0000000 --- a/fixup-packages.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { writeFileSync } from 'node:fs' -import { join, dirname } from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -const fixupPackages = () => { - writeFileSync( - join(__dirname, 'dist', 'mjs', 'package.json'), - JSON.stringify({ type: 'module' }, null, 2), - ) - - writeFileSync( - join(__dirname, 'dist', 'cjs', 'package.json'), - JSON.stringify({ type: 'commonjs' }, null, 2), - ) -} - -fixupPackages() diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 9752586..0000000 --- a/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - testEnvironment: 'node', - transform: { - '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tests/tsconfig.json' }], - }, - // NodeNext source uses `.js` extensions in relative imports, but - // ts-jest resolves the underlying `.ts` files at test time. Strip - // the trailing `.js` so jest-resolve can find them. - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/'], - watchPathIgnorePatterns: ['/node_modules/', '/dist/'], -} diff --git a/package-lock.json b/package-lock.json index 8c3db08..e18a6f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,23 +10,18 @@ "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@size-limit/preset-small-lib": "^12.1.0", - "@types/jest": "^30.0.0", - "@typescript-eslint/eslint-plugin": "^8.59.1", - "@typescript-eslint/parser": "^8.59.1", - "eslint": "^9.39.4", + "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", - "jest": "^30.3.0", "prettier": "^3.8.3", "publint": "^0.3.18", - "rimraf": "^5.0.7", "size-limit": "^12.1.0", - "ts-jest": "^29.4.9", - "tsx": "^4.21.0", + "tsdown": "^0.21.10", "typescript": "^5.9.3", - "typescript-eslint": "^8.59.1" + "typescript-eslint": "^8.59.1", + "vitest": "^4.0.10" }, "engines": { "node": ">=18" @@ -111,611 +106,351 @@ "node": ">=14.17" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } + "license": "ISC" }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=6.9.0" + "node": ">=0.1.90" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@braidai/lang": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", - "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "node": ">=18" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/linux-s390x": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ - "ppc64" + "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "aix" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm": { + "node_modules/@esbuild/linux-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm64": { + "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -723,16 +458,16 @@ "license": "MIT", "optional": true, "os": [ - "android" + "netbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-x64": { + "node_modules/@esbuild/netbsd-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -740,16 +475,16 @@ "license": "MIT", "optional": true, "os": [ - "android" + "netbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/darwin-arm64": { + "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -757,16 +492,16 @@ "license": "MIT", "optional": true, "os": [ - "darwin" + "openbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { + "node_modules/@esbuild/openbsd-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -774,16 +509,16 @@ "license": "MIT", "optional": true, "os": [ - "darwin" + "openbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/freebsd-arm64": { + "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -791,16 +526,16 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" + "openharmony" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/freebsd-x64": { + "node_modules/@esbuild/sunos-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -808,273 +543,18 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" + "sunos" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-arm": { + "node_modules/@esbuild/win32-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" + "arm64" ], "dev": true, "license": "MIT", @@ -1150,115 +630,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { @@ -1326,670 +780,383 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } + "license": "MIT" }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@loaderkit/resolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.5.tgz", + "integrity": "sha512-fhkdGM57xhJ7CO91MUgbQlb0ClP0AJ9vB3yoVnBTslYJqrJOCVEbOprZcxZlexdMbmTBPQqVcQYr+j4oRRtIZA==", "dev": true, "license": "ISC", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" + "@braidai/lang": "^1.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", "dev": true, "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@publint/pack": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.4.tgz", + "integrity": "sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==", "dev": true, "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@quansync/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" + "quansync": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", - "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@jest/test-result": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "slash": "^3.0.0" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@loaderkit/resolve": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.5.tgz", - "integrity": "sha512-fhkdGM57xhJ7CO91MUgbQlb0ClP0AJ9vB3yoVnBTslYJqrJOCVEbOprZcxZlexdMbmTBPQqVcQYr+j4oRRtIZA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@braidai/lang": "^1.0.0" + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", + "os": [ + "win32" + ], "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@publint/pack": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.4.tgz", - "integrity": "sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://bjornlu.com/sponsor" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", "dev": true, "license": "MIT" }, @@ -2006,26 +1173,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", - "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, "node_modules/@size-limit/esbuild": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@size-limit/esbuild/-/esbuild-12.1.0.tgz", @@ -2071,6 +1218,13 @@ "size-limit": "12.1.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2082,50 +1236,23 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } + "license": "MIT" }, "node_modules/@types/eslint": { "version": "8.56.10", @@ -2139,6 +1266,13 @@ "@types/json-schema": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2146,44 +1280,13 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", "dev": true, "license": "MIT" }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2191,38 +1294,17 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@types/yargs-parser": "*" + "undici-types": "~7.19.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", @@ -2401,49 +1483,10 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", - "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "license": "MIT", "dependencies": { @@ -2495,281 +1538,118 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, "node_modules/acorn": { "version": "8.16.0", @@ -2811,22 +1691,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2851,6 +1715,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2858,230 +1732,117 @@ "dev": true, "license": "MIT" }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/ast-kit": { + "version": "3.0.0-beta.1", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-3.0.0-beta.1.tgz", + "integrity": "sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0-beta.4", + "estree-walker": "^3.0.3", + "pathe": "^2.0.3" + }, "engines": { - "node": ">=8.6" + "node": ">=20.19.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/ast-kit/node_modules/@babel/helper-string-parser": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.3.tgz", + "integrity": "sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==", "dev": true, - "license": "Python-2.0" + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "node_modules/ast-kit/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.3.tgz", + "integrity": "sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/transform": "30.3.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "node_modules/ast-kit/node_modules/@babel/parser": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.3.tgz", + "integrity": "sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==", "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "@babel/types": "^8.0.0-rc.3" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "node_modules/ast-kit/node_modules/@babel/types": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.3.tgz", + "integrity": "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/babel__core": "^7.20.5" + "@babel/helper-string-parser": "^8.0.0-rc.3", + "@babel/helper-validator-identifier": "^8.0.0-rc.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", "dev": true, "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": "18 || 20 || >=22" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, + "license": "MIT", "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" + "node": "18 || 20 || >=22" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/bytes-iec": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/bytes-iec/-/bytes-iec-3.1.1.tgz", @@ -3092,46 +1853,16 @@ "node": ">= 0.8" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3159,29 +1890,6 @@ "node": ">=10" } }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cli-highlight": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", @@ -3261,39 +1969,6 @@ "@colors/colors": "1.5.0" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3322,12 +1997,6 @@ "node": ">=14" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3367,71 +2036,48 @@ } } }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.344", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", - "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/dts-resolver": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.3.tgz", + "integrity": "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=20.19.0" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } } }, "node_modules/emoji-regex": { @@ -3447,6 +2093,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -3460,15 +2116,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } + "license": "MIT" }, "node_modules/esbuild": { "version": "0.28.0", @@ -3535,33 +2188,30 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -3571,8 +2221,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -3580,7 +2229,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -3638,1362 +2287,398 @@ }, "eslint-config-prettier": { "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", - "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + } } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "is-extglob": "^2.1.1" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=10" + "node": ">=10.13.0" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/jackspeak": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.3.0.tgz", - "integrity": "sha512-glPiBfKguqA7v8JsXO3iLjJWZ9FV1vNpoI0I9hI9Mnk5yetO9uPLSpiCEmiVijAssv2f54HpvtzvAHfhPieiDQ==", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://opencollective.com/eslint" } }, - "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", - "import-local": "^3.2.0", - "jest-cli": "30.3.0" - }, - "bin": { - "jest": "bin/jest.js" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.3.0", - "p-limit": "^3.1.0" + "estraverse": "^5.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10" } }, - "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "p-limit": "^3.1.0", - "pretty-format": "30.3.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "estraverse": "^5.2.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=4.0" } }, - "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, + "license": "BSD-2-Clause", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=4.0" } }, - "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "parse-json": "^5.2.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "@types/estree": "^1.0.0" } }, - "node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.3.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.3.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12.0.0" } }, - "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "Apache-2.0" }, - "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "picomatch": "^4.0.3", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } + "license": "MIT" }, - "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=16.0.0" } }, - "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-util": "30.3.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=16" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, + "license": "ISC", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "*" } }, - "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 4" } }, - "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "node_modules/import-without-cache": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.3.3.tgz", + "integrity": "sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.3.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, - "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.8.19" } }, - "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.3.0", - "string-length": "^4.0.2" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, + "optional": true, + "peer": true, "bin": { - "js-yaml": "bin/js-yaml.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/jsesc": { @@ -5016,13 +2701,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5036,49 +2714,288 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "json5": "lib/cli.js" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lilconfig": { @@ -5094,13 +3011,6 @@ "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5116,57 +3026,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/marked": { @@ -5246,52 +3113,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", + "node": "18 || 20 || >=22" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mri": { @@ -5352,35 +3187,12 @@ "picocolors": "^1.1.1" } }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-emoji": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", @@ -5397,42 +3209,6 @@ "node": ">=18" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5443,31 +3219,16 @@ "node": ">=0.10.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" }, "node_modules/optionator": { "version": "0.9.4", @@ -5516,23 +3277,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/package-manager-detector": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", @@ -5540,38 +3284,6 @@ "dev": true, "license": "MIT" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -5605,16 +3317,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5624,30 +3326,12 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -5663,89 +3347,58 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10 || ^12 || >=14" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/prelude-ls": { @@ -5786,34 +3439,6 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/publint": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.18.tgz", @@ -5846,30 +3471,23 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", "dev": true, "funding": [ { "type": "individual", - "url": "https://github.com/sponsors/dubzzz" + "url": "https://github.com/sponsors/antfu" }, { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" + "type": "individual", + "url": "https://github.com/sponsors/sxzz" } ], "license": "MIT" }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5880,64 +3498,161 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", "dev": true, "license": "MIT", "dependencies": { - "resolve-from": "^5.0.0" + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/rolldown-plugin-dts": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.23.2.tgz", + "integrity": "sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "8.0.0-rc.3", + "@babel/helper-validator-identifier": "8.0.0-rc.3", + "@babel/parser": "8.0.0-rc.3", + "@babel/types": "8.0.0-rc.3", + "ast-kit": "^3.0.0-beta.1", + "birpc": "^4.0.0", + "dts-resolver": "^2.1.3", + "get-tsconfig": "^4.13.7", + "obug": "^2.1.1", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@ts-macro/tsc": "^0.3.6", + "@typescript/native-preview": ">=7.0.0-dev.20260325.1", + "rolldown": "^1.0.0-rc.12", + "typescript": "^5.0.0 || ^6.0.0", + "vue-tsc": "~3.2.0" + }, + "peerDependenciesMeta": { + "@ts-macro/tsc": { + "optional": true + }, + "@typescript/native-preview": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/rolldown-plugin-dts/node_modules/@babel/generator": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.3.tgz", + "integrity": "sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/rolldown-plugin-dts/node_modules/@babel/helper-string-parser": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.3.tgz", + "integrity": "sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "node_modules/rolldown-plugin-dts/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.3.tgz", + "integrity": "sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==", "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "node_modules/rolldown-plugin-dts/node_modules/@babel/parser": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.3.tgz", + "integrity": "sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^10.3.7" + "@babel/types": "^8.0.0-rc.3" }, "bin": { - "rimraf": "dist/esm/bin.mjs" + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=14.18" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/types": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.3.tgz", + "integrity": "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0-rc.3", + "@babel/helper-validator-identifier": "^8.0.0-rc.3" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/sade": { @@ -5987,11 +3702,12 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" }, "node_modules/size-limit": { "version": "12.1.0", @@ -6034,79 +3750,29 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", @@ -6122,21 +3788,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6149,51 +3800,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6239,43 +3845,6 @@ "url": "https://opencollective.com/synckit" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6299,6 +3868,23 @@ "node": ">=0.8" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -6334,11 +3920,25 @@ } } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } }, "node_modules/ts-api-utils": { "version": "2.5.0", @@ -6353,70 +3953,80 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-jest": { - "version": "29.4.9", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", - "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "node_modules/tsdown": { + "version": "0.21.10", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.21.10.tgz", + "integrity": "sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA==", "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.9", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", + "ansis": "^4.2.0", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.0", + "hookable": "^6.1.1", + "import-without-cache": "^0.3.3", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "rolldown": "1.0.0-rc.17", + "rolldown-plugin-dts": "^0.23.2", "semver": "^7.7.4", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" + "tinyexec": "^1.1.1", + "tinyglobby": "^0.2.16", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0", + "unrun": "^0.2.37" }, "bin": { - "ts-jest": "cli.js" + "tsdown": "dist/run.mjs" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" }, "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <7" + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.21.10", + "@tsdown/exe": "0.21.10", + "@vitejs/devtools": "*", + "publint": "^0.3.0", + "typescript": "^5.0.0 || ^6.0.0", + "unplugin-unused": "^0.5.0" }, "peerDependenciesMeta": { - "@babel/core": { + "@arethetypeswrong/core": { "optional": true }, - "@jest/transform": { + "@tsdown/css": { "optional": true }, - "@jest/types": { + "@tsdown/exe": { "optional": true }, - "babel-jest": { + "@vitejs/devtools": { "optional": true }, - "esbuild": { + "publint": { + "optional": true + }, + "typescript": { "optional": true }, - "jest-util": { + "unplugin-unused": { "optional": true } } }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "node_modules/tsdown/node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20.19.0" } }, "node_modules/tslib": { @@ -6433,6 +4043,8 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -6460,6 +4072,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -6477,6 +4090,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -6494,6 +4108,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -6511,6 +4126,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -6528,6 +4144,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -6545,6 +4162,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -6562,6 +4180,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6579,6 +4198,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6596,6 +4216,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6613,6 +4234,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6630,6 +4252,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6647,6 +4270,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6664,6 +4288,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6681,6 +4306,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6698,6 +4324,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6715,6 +4342,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6732,6 +4360,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -6749,6 +4378,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6766,6 +4396,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6783,6 +4414,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6800,6 +4432,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -6817,6 +4450,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -6834,6 +4468,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -6851,6 +4486,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -6868,6 +4504,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -6885,6 +4522,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -6896,6 +4534,8 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6943,29 +4583,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -7004,25 +4621,28 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" }, - "engines": { - "node": ">=0.8.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", @@ -7034,70 +4654,31 @@ "node": ">=4" } }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "node_modules/unrun": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/unrun/-/unrun-0.2.37.tgz", + "integrity": "sha512-AA7vDuYsgeSYVzJMm16UKA+aXFKhy7nFqW9z5l7q44K4ppFWZAMqYS58ePRZbugMLPH0fwwMzD5A8nP0avxwZQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "rolldown": "1.0.0-rc.17" }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" + "bin": { + "unrun": "dist/cli.mjs" }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "engines": { + "node": ">=20.19.0" }, - "bin": { - "update-browserslist-db": "cli.js" + "funding": { + "url": "https://github.com/sponsors/Gugustinette" }, "peerDependencies": { - "browserslist": ">= 4.21.0" + "synckit": "^0.11.11" + }, + "peerDependenciesMeta": { + "synckit": { + "optional": true + } } }, "node_modules/uri-js": { @@ -7110,21 +4691,6 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/validate-npm-package-name": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", @@ -7135,13 +4701,172 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vite": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, + "license": "MIT", "dependencies": { - "makeerror": "1.0.12" + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, "node_modules/which": { @@ -7159,6 +4884,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7168,13 +4910,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7193,58 +4928,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7255,41 +4938,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b6418aa..ef2cb3e 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,13 @@ "name": "cacheables", "version": "3.0.0", "description": "A small, typed cache with multilayer storage buckets, five cache policies (incl. stale-while-revalidate) and concurrency-safe deduplication. Zero dependencies, browser + Node.", - "type": "commonjs", - "main": "dist/cjs/index.js", - "module": "dist/mjs/index.js", - "types": "dist/mjs/index.d.ts", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.cts", "files": [ "dist", + "package.json", "README.md", "LICENSE.md" ], @@ -18,24 +19,24 @@ "exports": { ".": { "import": { - "types": "./dist/mjs/index.d.ts", - "default": "./dist/mjs/index.js" + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" }, "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" } } }, "scripts": { "size": "size-limit", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:watch": "vitest", "typecheck": "tsc --noEmit", "eslint": "eslint --fix src/**/*.ts", "lint": "eslint src/**/*.ts", "format:check": "prettier --check .", - "build": "rimraf dist && tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json && tsx ./fixup-packages.ts", + "build": "tsdown", "publint": "publint", "attw": "attw --pack ." }, @@ -45,7 +46,7 @@ }, "size-limit": [ { - "path": "./dist/mjs/index.js" + "path": "./dist/index.mjs" } ], "keywords": [ @@ -71,22 +72,17 @@ "homepage": "https://github.com/grischaerbe/cacheables#readme", "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@size-limit/preset-small-lib": "^12.1.0", - "@types/jest": "^30.0.0", - "@typescript-eslint/eslint-plugin": "^8.59.1", - "@typescript-eslint/parser": "^8.59.1", - "eslint": "^9.39.4", + "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", - "jest": "^30.3.0", "prettier": "^3.8.3", "publint": "^0.3.18", - "rimraf": "^5.0.7", "size-limit": "^12.1.0", - "ts-jest": "^29.4.9", - "tsx": "^4.21.0", + "tsdown": "^0.21.10", "typescript": "^5.9.3", - "typescript-eslint": "^8.59.1" + "typescript-eslint": "^8.59.1", + "vitest": "^4.0.10" } } diff --git a/tests/index.test.ts b/tests/index.test.ts index 77ccb0a..12d7082 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -76,7 +76,7 @@ describe('Cache operations', () => { }) it('Logs correctly', async () => { - console.log = jest.fn() + console.log = vi.fn() const cache = new Cacheable('test', { buckets: [new MemoryBucket()], @@ -142,7 +142,7 @@ describe('Cache operations', () => { }) it('Handles multiple calls correctly', async () => { - console.log = jest.fn() + console.log = vi.fn() const cache = new Cacheable('test', { buckets: [new MemoryBucket()], diff --git a/tests/resolve.test.ts b/tests/resolve.test.ts index f0a406f..77c5715 100644 --- a/tests/resolve.test.ts +++ b/tests/resolve.test.ts @@ -208,7 +208,7 @@ describe('cache.resolve(): policy semantics', () => { }) it('logs HIT/MISS with elapsed time, same shape as remember', async () => { - const log = jest.fn() + const log = vi.fn() const cache = new Cacheable('test', { buckets: [new FakeViewBucket()], logger: { log }, diff --git a/tests/tsconfig.json b/tests/tsconfig.json index f735e3f..3458c7e 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -3,8 +3,9 @@ "compilerOptions": { "noEmit": true, "rootDir": "..", - "module": "CommonJS", - "moduleResolution": "Node" + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["vitest/globals"] }, "include": ["."] } diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json deleted file mode 100644 index 4423650..0000000 --- a/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "moduleResolution": "Node", - "outDir": "dist/cjs", - "target": "es2022" - } -} diff --git a/tsconfig.mjs.json b/tsconfig.mjs.json deleted file mode 100644 index 1a0c169..0000000 --- a/tsconfig.mjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "outDir": "dist/mjs", - "target": "esnext" - } -} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000..9e239ac --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: ['./src/index.ts'], + target: 'es2020', + format: ['cjs', 'esm'], + sourcemap: true, + clean: true, + dts: true, +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..88528db --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + include: ['tests/**/*.test.ts'], + }, +}) From 6b7a86d3e24599968b12963eeeb7323127b91b3c Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:51:19 +0200 Subject: [PATCH 22/23] Build before publishing in the release workflow (#31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The publish-npm job ran `npm ci && npm publish` with no build step. Combined with `dist/` being gitignored and no prepack/prepublishOnly hook in package.json, a release would publish a tarball whose `files` list ("dist", ...) points at a directory that doesn't exist — i.e. an empty package. Insert `npm run build` between install and publish. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/npm-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 663e89f..339dc79 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -28,6 +28,7 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org/ - run: npm ci + - run: npm run build - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} From 43655df45b09e73c50df9cc107e13b61c6e4a58a Mon Sep 17 00:00:00 2001 From: Grischa Erbe <46897060+grischaerbe@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:55:48 +0200 Subject: [PATCH 23/23] Trim verbose Response-body comments from README (#32) Co-authored-by: Claude Opus 4.7 (1M context) --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index c44366e..2fea30b 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,7 @@ import { Cacheable, MemoryBucket } from 'cacheables' const cache = new Cacheable('app', { buckets: [new MemoryBucket()] }) -// Cache the parsed JSON, not the Response — a Response body can be -// consumed exactly once, so caching the Response itself would break -// every call after the first .json(). -const data = await cache.remember( - () => fetch('https://some-url.com/api').then((r) => r.json()), - 'key', -) +const data = await cache.remember(() => fetchData(), 'key') ``` - [Installation](#installation) @@ -71,8 +65,6 @@ const cache = new Cacheable('weather-data', { // `remember` is both getter and setter: on a miss it calls the resource // and writes to every bucket; on a hit it returns the cached value. -// Cache the parsed JSON rather than the Response object — the Response -// body can only be read once. const getWeather = () => cache.remember(() => fetch(apiUrl).then((r) => r.json()), 'karlsruhe')