diff --git a/CHANGELOG.md b/CHANGELOG.md index dee862f..cc8a63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,66 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- **feat**: Route registration now supports setting up input validation schema. +- **feat**: Global atomic store (accessed through `$.storeGet/Set/Del`) now supports **per-key TTLs (time-to-live)** and **reactive expiry events**. Entries automatically expire after their TTL, emit a `$store:{KEY}:expired` event, and cleanly remove themselves from memory and local storage. +```typescript +$.storeSet(key, value, {ttl?: number /* in milliseconds */, persist?: boolean}); +``` + ### Improved -- **deps**: Upgrade @valkyriestudios/utils to 12.47.0 -- **deps**: Upgrade @cloudflare/workers-types to 4.20251014.0 -- **deps**: Upgrade @types/node to 22.18.2 -- **deps**: Upgrade @vitest/coverage-v8 to 4.0.4 -- **deps**: Upgrade bun-types to 1.3.1 -- **deps**: Upgrade eslint to 9.38.0 +- **deps**: Upgrade @valkyriestudios/utils to 12.48.0 +- **deps**: Upgrade @cloudflare/workers-types to 4.20260103.0 +- **deps**: Upgrade @types/node to 22.19.3 +- **deps**: Upgrade @vitest/coverage-v8 to 4.0.16 +- **deps**: Upgrade bun-types to 1.3.5 +- **deps**: Upgrade eslint to 9.39.2 +- **deps**: Upgrade prettier to 3.7.4 - **deps**: Upgrade typescript to 5.9.3 -- **deps**: Upgrade typescript-eslint to 8.46.2 -- **deps**: Upgrade vitest to 4.0.4 +- **deps**: Upgrade typescript-eslint to 8.51.0 +- **deps**: Upgrade vitest to 4.0.16 + +### Fixed +- Fixed an edge-case issue where if an entry to the atomic-store was previously set using `persist: true` and then set using `persist: false` it would still linger in local storage and only be removed during `storeDel`. + +--- + +### More about TTL expiry +Each key now emits: +- **$store:{KEY}**: On set or manual delete +- **$store:{KEY}:expired**: When its TTL elapses naturally + +This makes the atomic store **time-aware and reactive**, enabling token renewal, cache invalidation, live dashboards, ... **without polling or background loops**. + +Atomic now natively handles **self-expiring state**, fully deterministic and zero-idle. + +**Additional notes**: +- The provided TTL is **in milliseconds** +- Like the `$store:{KEY}` events, the new `$store:{KEY}:expired` events are **fully typed**. +- On expiry, **only** `$store:{KEY}:expired` is emitted, this prevents unnecessary updates for consumers of `$store:{KEY}`, allowing refresh logic to remain isolated.. + +### Examples on TTL expiry +##### Auth token refresh +```typescript +// Expire after 1 hour +$.storeSet('token', 'abc123', { ttl: 3_600_000, persist: true }); + +// Subscribe within a VM +el.$subscribe('$store:token:expired', () => $.fetch('/auth/refresh')); +``` +##### Dashboard auto-refresh +```typescript +async function load () { + // Fetch dashboard data (example) + const data = await $.fetch('/api/dashboard'); + + // Set and expire after 10 seconds + $.storeSet('dashboard_data', data, { ttl: 10_000 }); +} + +// Subscribe within a VM +el.$subscribe('$store:dashboard_data:expired', load); +``` ## [1.4.1] - 2025-09-14 ### Fixed diff --git a/lib/App.ts b/lib/App.ts index c22239f..3724487 100644 --- a/lib/App.ts +++ b/lib/App.ts @@ -1,5 +1,6 @@ import {isIntGt} from '@valkyriestudios/utils/number'; import {isObject} from '@valkyriestudios/utils/object'; +import {hexId} from '@valkyriestudios/utils/hash'; import {type TriFrostCache} from './modules/Cache'; import {type TriFrostCookieOptions} from './modules/Cookies'; import {TriFrostRateLimit, type TriFrostRateLimitLimitFunction} from './modules/RateLimit/_RateLimit'; @@ -26,7 +27,7 @@ import {mount as mountCss} from './modules/JSX/style/mount'; import {mount as mountScript} from './modules/JSX/script/mount'; import {type CssGeneric, type CssInstance} from './modules/JSX/style/use'; import {activateCtx} from './utils/Als'; -import {hexId} from './utils/Generic'; +import {type TFValidator} from './types/validation'; const RGX_RID = /^[a-z0-9-]{8,64}$/i; @@ -300,6 +301,26 @@ class App, State extends Record await ctx.init(match); if (ctx.statusCode >= 400) return await runTriage(path, ctx); + /* If route has a validator, run it */ + if (match.route.input) { + try { + const parsed = match.route.input.parse({ + body: ctx.body, + query: ctx.query, + }); + // overwrite ctx.body/query with parsed values (safe cast) + ctx.body = parsed.body; + ctx.query = parsed.query; + } catch (err) { + if (match.route.input.onInvalid) { + await match.route.input.onInvalid(ctx, err); + } else { + ctx.setStatus(400); + } + return await runTriage(path, ctx); + } + } + /* Run chain */ for (let i = 0; i < match.route.middleware.length; i++) { const el = match.route.middleware[i]; @@ -439,7 +460,10 @@ class App, State extends Record /** * Configure a HTTP Get route */ - get(path: Path, handler: TriFrostRouteHandler>) { + get< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TV>) { super.get(path, handler); return this; } @@ -447,7 +471,10 @@ class App, State extends Record /** * Configure a HTTP Post route */ - post(path: Path, handler: TriFrostRouteHandler>) { + post< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TV>) { super.post(path, handler); return this; } @@ -455,7 +482,10 @@ class App, State extends Record /** * Configure a HTTP Patch route */ - patch(path: Path, handler: TriFrostRouteHandler>) { + patch< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TV>) { super.patch(path, handler); return this; } @@ -463,7 +493,10 @@ class App, State extends Record /** * Configure a HTTP Put route */ - put(path: Path, handler: TriFrostRouteHandler>) { + put< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TV>) { super.put(path, handler); return this; } @@ -471,7 +504,10 @@ class App, State extends Record /** * Configure a HTTP Delete route */ - del(path: Path, handler: TriFrostRouteHandler>) { + del< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TV>) { super.del(path, handler); return this; } diff --git a/lib/Context.ts b/lib/Context.ts index e18dd15..75b9a3f 100644 --- a/lib/Context.ts +++ b/lib/Context.ts @@ -1,5 +1,6 @@ -import {isObject} from '@valkyriestudios/utils/object'; +import {isNeObject, isObject} from '@valkyriestudios/utils/object'; import {isNeString} from '@valkyriestudios/utils/string'; +import {hexId} from '@valkyriestudios/utils/hash'; import {type TriFrostCache} from './modules/Cache'; import {Cookies} from './modules/Cookies'; import {NONCE_WIN_SCRIPT, NONCEMARKER} from './modules/JSX/ctx/nonce'; @@ -28,8 +29,10 @@ import { type TriFrostContextRenderOptions, } from './types/context'; import {encodeFilename, extractDomainFromHost} from './utils/Http'; -import {determineHost, injectBefore, prependDocType, hexId} from './utils/Generic'; -import {type TriFrostBodyParserOptions, type ParsedBody} from './utils/BodyParser/types'; +import {determineHost, injectBefore, prependDocType} from './utils/Generic'; +import {type TriFrostBodyParserOptions} from './utils/BodyParser/types'; +import {type TFInput} from './types/validation'; +import toObject from './utils/Query'; type RequestConfig = { method: HttpMethod; @@ -59,8 +62,12 @@ export const IP_HEADER_CANDIDATES: string[] = [ 'x-appengine-user-ip', ]; -// eslint-disable-next-line prettier/prettier -export abstract class Context = {}, State extends Record = {}> implements TriFrostContext { +export abstract class Context< + Env extends Record = {}, + State extends Record = {}, + TInput extends TFInput = TFInput, +> implements TriFrostContext +{ /** * MARK: Private */ @@ -90,7 +97,10 @@ export abstract class Context = {}, State extend #cache: TriFrostCache | null = null; /* TriFrost Route Query. We compute this on an as-needed basis */ - #query: URLSearchParams | null = null; + #query: TInput['query'] | null = null; + + /* Whether or not a query exists */ + #query_has: boolean = false; /* TriFrost logger instance */ #logger: TriFrostLogger; @@ -118,7 +128,7 @@ export abstract class Context = {}, State extend protected req_id: string | null = null; /* TriFrost Request body */ - protected req_body: Readonly | null = null; + protected req_body: Readonly | null = null; /* Whether or not the context is initialized */ protected is_initialized: boolean = false; @@ -162,6 +172,9 @@ export abstract class Context = {}, State extend } if (!this.req_id) this.req_id = hexId(16); + /* Set this.#query_has */ + this.#query_has = this.req_config.query.length > 0; + /* Instantiate logger */ this.#logger = logger.spawn({ traceId: this.req_id, @@ -244,7 +257,7 @@ export abstract class Context = {}, State extend * Returns the host of the context. */ get host(): string { - if (this.#host) return this.#host; + if (this.#host !== null) return this.#host; this.#host = this.getHostFromHeaders() ?? determineHost(this.ctx_config.env); return this.#host; } @@ -282,11 +295,19 @@ export abstract class Context = {}, State extend /** * Request Query parameters */ - get query(): Readonly { - if (!this.#query) this.#query = new URLSearchParams(this.req_config.query); + get query(): Readonly { + if (!this.#query) { + this.#query = toObject(this.req_config.query); + this.#query_has = isNeObject(this.#query); + } return this.#query; } + set query(val: TInput['query']) { + this.#query = val; + this.#query_has = isNeObject(val); + } + /** * Cache Instance */ @@ -330,8 +351,12 @@ export abstract class Context = {}, State extend /** * Request Body */ - get body(): Readonly> { - return this.req_body || {}; + get body(): Readonly> { + return (this.req_body || {}) as unknown as Readonly>; + } + + set body(val: TInput['body']) { + this.req_body = val; } /** @@ -532,7 +557,7 @@ export abstract class Context = {}, State extend /** * Initializes the request body and parses it into Json or FormData depending on its type */ - async init(match: TriFrostRouteMatch, handler?: (config: TriFrostBodyParserOptions | null) => Promise) { + async init(match: TriFrostRouteMatch, handler?: (config: TriFrostBodyParserOptions | null) => Promise) { try { /* No need to do anything if already initialized */ if (this.is_initialized) return; @@ -559,7 +584,7 @@ export abstract class Context = {}, State extend if (body === null) { this.setStatus(413); } else { - this.req_body = body; + this.req_body = body as TInput['body']; } break; } @@ -879,9 +904,9 @@ export abstract class Context = {}, State extend } /* If keep_query is passed as true and a query exists add it to normalized to */ - if (this.query.size && opts?.keep_query !== false) { + if (this.#query_has && opts?.keep_query !== false) { const prefix = url.indexOf('?') >= 0 ? '&' : '?'; - url += prefix + this.query.toString(); + url += prefix + this.req_config.query; } /* This is a redirect, as such a body should not be present */ diff --git a/lib/middleware/Security.ts b/lib/middleware/Security.ts index 6e1b2aa..23e9466 100644 --- a/lib/middleware/Security.ts +++ b/lib/middleware/Security.ts @@ -3,9 +3,9 @@ import {isBoolean} from '@valkyriestudios/utils/boolean'; import {isIntGt} from '@valkyriestudios/utils/number'; import {isObject} from '@valkyriestudios/utils/object'; import {isNeString} from '@valkyriestudios/utils/string'; +import {hexId} from '@valkyriestudios/utils/hash'; import {Sym_TriFrostDescription, Sym_TriFrostFingerPrint, Sym_TriFrostName} from '../types/constants'; import {type TriFrostContext} from '../types/context'; -import {hexId} from '../utils/Generic'; const RGX_NONCE = /'nonce'/g; diff --git a/lib/modules/JSX/script/atomic.ts b/lib/modules/JSX/script/atomic.ts index 76990c6..ba52d77 100644 --- a/lib/modules/JSX/script/atomic.ts +++ b/lib/modules/JSX/script/atomic.ts @@ -22,7 +22,7 @@ const VM_RELAY_PUBLISH_NAME = '$publish'; const VM_HOOK_UNMOUNT_NAME = '$unmount'; const VM_HOOK_MOUNT_NAME = '$mount'; -type StoreTopics = `$store:${K}`; +type StoreTopics = `$store:${K}` | `$store:${K}:expired`; type DotPathLevels = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; /* Up to depth 10, this prevents infinite recursion */ @@ -227,8 +227,8 @@ export type TriFrostAtomicUtils< storeGet(key: K): Store[K]; storeGet(key: string): unknown; /* Store Set */ - storeSet(key: K, value: Store[K], opts?: {persist?: boolean}): void; - storeSet(key: string, value: unknown, opts?: {persist?: boolean}): void; + storeSet(key: K, value: Store[K], opts?: {ttl?: number; persist?: boolean}): void; + storeSet(key: string, value: unknown, opts?: {ttl?: number; persist?: boolean}): void; /* Store Delete */ storeDel: (key: keyof Store | string) => void; /* CSS variable access */ @@ -376,42 +376,97 @@ export const ATOMIC_GLOBAL = atomicMinify(`(function(w,d,loc){ })()); def("${GLOBAL_STORE_NAME}", (() => { - const s = Object.create(null); + const s = new M.create(null); const kP = "$tfs:"; - const notify = (k, v) => w.${GLOBAL_RELAY_NAME}.publish("$store:" + k, v); + const pub = (k, v) => w.${GLOBAL_RELAY_NAME}.publish("$store:" + k, v); + const pubExp = (k, v) => w.${GLOBAL_RELAY_NAME}.publish("$store:" + k + ":expired", v); + let gttl = null; + let t = null; + let scanning = false; + + // Hydrate persisted try { for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k?.startsWith(kP)) { - const kN = k.slice(kP.length); + const n = k.slice(kP.length); const r = localStorage.getItem(k); - if (r !== null) s[kN] = JSON.parse(r).v; + if (r !== null) { + const {v, e} = JSON.parse(r); + s[n] = {v, exp: e || null}; + if (e && (!gttl || e < gttl)) gttl = e; + } } } } catch {} + const delPersist = (k) => { + try { localStorage.removeItem(kP + k); } catch {} + }; + + + const expire = (k, e, isTTL = false) => { + delete s[k]; + if (e.persist) delPersist(k); + isTTL ? pubExp(k, e.v) : pub(k, undefined); + if (!scanning && e.exp === gttl) scan(); + }; + + const scan = () => { + if (t) clearTimeout(t); + t = null; + scanning = true; + let ttl = null, now = Date.now(); + for (const k in s) { + const e = s[k]; + if (e.exp && e.exp <= now) expire(k, e, true); + else if (e.exp && (!ttl || e.exp < ttl)) ttl = e.exp; + } + gttl = ttl; + scanning = false; + if (ttl) t = setTimeout(scan, Math.max(0, ttl - Date.now())); + }; + return Object.freeze({ get: k => { if (!isStr(k) || !k) return undefined; - return s[k] + const e = s[k]; + if (!e) return undefined; + if (e.exp && e.exp <= Date.now()) { + expire(k, e, true); + return undefined; + } + return e.v; }, set: (k, v, o = {}) => { - if (!isStr(k) || !k || eq(s[k], v)) return; - s[k] = v; - if (o?.persist === true) { - try { - localStorage.setItem(kP + k, JSON.stringify({v})); - } catch {} + if (!isStr(k) || !k) return; + const e = s[k]; + const ttl = o.ttl ? Date.now() + o.ttl : null; + const persist = o.persist === true; + + // skip if value + ttl + persist identical + if (e && eq(e.v, v) && e.exp === ttl && e.persist === persist) return; + + // if previously persisted but now ephemeral, remove persisted copy + if (e?.persist && !persist) delPersist(k); + + s[k] = {v, exp: ttl, persist}; + if (persist) { + try { localStorage.setItem(kP + k, JSON.stringify({v, e: ttl})); } catch {} + } + pub(k, v); + + if (ttl && (!gttl || ttl < gttl)) { + gttl = ttl; + if (t) clearTimeout(t); + t = setTimeout(scan, Math.max(0, ttl - Date.now())); } - notify(k,v); }, del: k => { if (!isStr(k) || !k) return; - delete s[k]; - try { - localStorage.removeItem(kP + k); - } catch {} - notify(k,undefined); + const e = s[k]; + if (!e) return; + expire(k, e, false); }, }); })()); diff --git a/lib/modules/JSX/style/use.ts b/lib/modules/JSX/style/use.ts index e366759..b48ea07 100644 --- a/lib/modules/JSX/style/use.ts +++ b/lib/modules/JSX/style/use.ts @@ -1,10 +1,9 @@ import {LRU} from '@valkyriestudios/utils/caching'; -import {djb2} from '@valkyriestudios/utils/hash'; +import {hexId, djb2} from '@valkyriestudios/utils/hash'; import {isNeObject, merge} from '@valkyriestudios/utils/object'; import {isNeString} from '@valkyriestudios/utils/string'; import {StyleEngine} from './Engine'; import {HTML_TAGS, styleToString} from './util'; -import {hexId} from '../../../utils/Generic'; const RGX_SEPARATOR = /[:.#[]| /; diff --git a/lib/modules/Logger/Logger.ts b/lib/modules/Logger/Logger.ts index e33b152..47688d8 100644 --- a/lib/modules/Logger/Logger.ts +++ b/lib/modules/Logger/Logger.ts @@ -1,4 +1,4 @@ -import {hexId} from '../../utils/Generic'; +import {hexId} from '@valkyriestudios/utils/hash'; import { type TriFrostLogger, type TriFrostLoggerSpan, diff --git a/lib/routing/Router.ts b/lib/routing/Router.ts index 17a5f63..e4fa41e 100644 --- a/lib/routing/Router.ts +++ b/lib/routing/Router.ts @@ -20,6 +20,7 @@ import {RouteTree} from './Tree'; import {isValidBodyParser, isValidGrouper, isValidHandler, isValidLimit, isValidMiddleware, normalizeMiddleware} from './util'; import {type TriFrostBodyParserOptions} from '../utils/BodyParser/types'; import {Lazy} from '../utils/Lazy'; +import {type TFInput, type ExtractInput, TFValidator} from '../types/validation'; const RGX_SLASH = /\/{2,}/g; @@ -205,7 +206,8 @@ class Router = {}, State extends Record, + input: null, + fn: handler as unknown as TriFrostHandler, middleware: normalizeMiddleware(this.#middleware as TriFrostMiddleware[]), kind: 'notfound', timeout: this.#timeout, @@ -228,7 +230,8 @@ class Router = {}, State extends Record, + input: null, + fn: handler as unknown as TriFrostHandler, middleware: normalizeMiddleware(this.#middleware as TriFrostMiddleware[]), kind: 'error', timeout: this.#timeout, @@ -243,46 +246,66 @@ class Router = {}, State extends Record(path: Path, handler: TriFrostRouteHandler>) { + get< + Path extends string = string, + TValidator extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TValidator>) { if (typeof path !== 'string') throw new Error('TriFrostRouter@get: Invalid path'); if (!isValidHandler>(handler)) throw new Error('TriFrostRouter@get: Invalid handler'); - return this.#register(path, [handler], [HttpMethods.GET, HttpMethods.HEAD], this.#bodyParser); + this.#register(path, [handler], [HttpMethods.GET, HttpMethods.HEAD], this.#bodyParser); + return this; } /** * Configure a HTTP Post route */ - post(path: Path, handler: TriFrostRouteHandler>) { + post< + Path extends string = string, + TValidator extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TValidator>) { if (typeof path !== 'string') throw new Error('TriFrostRouter@post: Invalid path'); if (!isValidHandler>(handler)) throw new Error('TriFrostRouter@post: Invalid handler'); - return this.#register(path, [handler], [HttpMethods.POST], this.#bodyParser); + this.#register(path, [handler], [HttpMethods.POST], this.#bodyParser); + return this; } /** * Configure a HTTP Put route */ - put(path: Path, handler: TriFrostRouteHandler>) { + put< + Path extends string = string, + TValidator extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TValidator>) { if (typeof path !== 'string') throw new Error('TriFrostRouter@put: Invalid path'); if (!isValidHandler>(handler)) throw new Error('TriFrostRouter@put: Invalid handler'); - return this.#register(path, [handler], [HttpMethods.PUT], this.#bodyParser); + this.#register(path, [handler], [HttpMethods.PUT], this.#bodyParser); + return this; } /** * Configure a HTTP Patch route */ - patch(path: Path, handler: TriFrostRouteHandler>) { + patch< + Path extends string = string, + TValidator extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TValidator>) { if (typeof path !== 'string') throw new Error('TriFrostRouter@patch: Invalid path'); if (!isValidHandler>(handler)) throw new Error('TriFrostRouter@patch: Invalid handler'); - return this.#register(path, [handler], [HttpMethods.PATCH], this.#bodyParser); + this.#register(path, [handler], [HttpMethods.PATCH], this.#bodyParser); + return this; } /** * Configure a HTTP Delete route */ - del(path: Path, handler: TriFrostRouteHandler>) { + del< + Path extends string = string, + TValidator extends TFValidator> = TFValidator>, + >(path: Path, handler: TriFrostRouteHandler, TValidator>) { if (typeof path !== 'string') throw new Error('TriFrostRouter@del: Invalid path'); if (!isValidHandler>(handler)) throw new Error('TriFrostRouter@del: Invalid handler'); - return this.#register(path, [handler], [HttpMethods.DELETE], this.#bodyParser); + this.#register(path, [handler], [HttpMethods.DELETE], this.#bodyParser); + return this; } /** @@ -342,6 +365,8 @@ class Router = {}, State extends Record((config.middleware || []) as TriFrostMiddleware[]), ]; + type TInputFromConfig = ExtractInput<(typeof config)['input']>; + for (let i = 0; i < methods.length; i++) { const method = methods[i]; @@ -352,11 +377,12 @@ class Router = {}, State extends Record, + fn: config.fn as unknown as TriFrostHandler, + input: (config.input ?? null) as unknown as TFValidator | null, middleware: n_middleware, timeout: n_timeout, bodyParser: n_bodyparser, - } as unknown as TriFrostRoute); + } as unknown as TriFrostRoute); } return this; diff --git a/lib/types/context.ts b/lib/types/context.ts index 709cad1..b49a125 100644 --- a/lib/types/context.ts +++ b/lib/types/context.ts @@ -3,10 +3,11 @@ import {type TriFrostCache} from '../modules/Cache'; import {type Cookies, type TriFrostCookieOptions} from '../modules/Cookies'; import {type TriFrostLogger} from '../modules/Logger'; import {type TriFrostRouteMatch} from './routing'; -import {type TriFrostBodyParserOptions, type ParsedBody} from '../utils/BodyParser/types'; +import {type TriFrostBodyParserOptions} from '../utils/BodyParser/types'; import {type HttpRedirectStatusCode, type HttpStatusCode, type MimeType, type HttpMethod} from './constants'; import {type createCss, type createScript} from '../modules'; import {Lazy} from '../utils/Lazy'; +import {type TFInput} from './validation'; export type TriFrostContextKind = 'std' | 'notfound' | 'error' | 'options' | 'health'; @@ -74,32 +75,42 @@ export type TriFrostContextRedirectOptions = { keep_query?: boolean; }; -export type TriFrostContext = {}, State extends Record = {}> = { - get env(): Readonly; - get method(): HttpMethod; - get path(): string; - get name(): string; - get nonce(): string; - get kind(): TriFrostContextKind; - get host(): string; - get domain(): string | null; - get ip(): string | null; - get requestId(): string; - get query(): Readonly; - get body(): Readonly; - get isInitialized(): boolean; - get isDone(): boolean; - get isAborted(): boolean; - get isLocked(): boolean; - get headers(): Readonly>; - get resHeaders(): Readonly>; - get logger(): TriFrostLogger; - get cookies(): Cookies; - get cache(): TriFrostCache; - get state(): Readonly; - get statusCode(): HttpStatusCode; - get timeout(): number | null; - get afterHooks(): ((ctx: TriFrostContext) => Promise)[]; +export type TriFrostContext< + Env extends Record = {}, + State extends Record = {}, + TInput extends TFInput = TFInput, +> = { + readonly env: Env; + readonly method: HttpMethod; + readonly path: string; + readonly name: string; + readonly nonce: string; + readonly kind: TriFrostContextKind; + readonly host: string; + readonly domain: string | null; + readonly ip: string | null; + readonly requestId: string; + + get query(): Readonly; + set query(val: TInput['query']); + + get body(): Readonly; + set body(val: TInput['body']); + + readonly isInitialized: boolean; + readonly isDone: boolean; + readonly isAborted: boolean; + readonly isLocked: boolean; + + readonly headers: Readonly>; + readonly resHeaders: Readonly>; + readonly logger: TriFrostLogger; + readonly cookies: Cookies; + readonly cache: TriFrostCache; + readonly state: Readonly; + readonly statusCode: HttpStatusCode; + readonly timeout: number | null; + readonly afterHooks: ((ctx: TriFrostContext) => Promise)[]; setState: >(patch: Patch) => TriFrostContext; delState: (keys: K[]) => TriFrostContext>; @@ -117,7 +128,7 @@ export type TriFrostContext = {}, State extends init: ( match: TriFrostRouteMatch, - handler?: (config: TriFrostBodyParserOptions | null) => Promise, + handler?: (config: TriFrostBodyParserOptions | null) => Promise, ) => Promise; abort: (status?: HttpStatusCode) => void; fetch: (input: string | URL, init?: RequestInit) => Promise; diff --git a/lib/types/routing.ts b/lib/types/routing.ts index 5f65b39..a24d7da 100644 --- a/lib/types/routing.ts +++ b/lib/types/routing.ts @@ -8,6 +8,7 @@ import {type Lazy} from '../utils/Lazy'; import {type HttpMethod, Sym_TriFrostDescription, Sym_TriFrostName} from './constants'; import {type TriFrostContext, type TriFrostContextKind} from './context'; import {type Promisify} from './generic'; +import {type TFInput, type TFValidator, type ExtractInput} from './validation'; export type TriFrostType = 'handler' | 'middleware'; @@ -29,6 +30,7 @@ export type PathParam = string extends Path ? {'*': string} : {}; +/** Middleware type */ export type TriFrostMiddleware< Env extends Record = {}, State extends Record = {}, @@ -38,12 +40,21 @@ export type TriFrostMiddleware< [Sym_TriFrostName]?: string; }; -export type TriFrostHandler, State extends Record = {}> = ( - ctx: TriFrostContext, -) => Promisify; +/** Handler type — ALWAYS parameterized by input shape */ +export type TriFrostHandler< + Env extends Record, + State extends Record = {}, + TInput extends TFInput = TFInput, +> = (ctx: TriFrostContext) => Promisify; -export type TriFrostHandlerConfig = {}, State extends Record = {}> = { - fn: TriFrostHandler; +/** Handler config (optional validator) */ +export type TriFrostHandlerConfig< + Env extends Record = {}, + State extends Record = {}, + TV extends TFValidator = TFValidator, +> = { + fn: TriFrostHandler>; + input?: TV | null; name?: string; description?: string; timeout?: number | null; @@ -53,14 +64,28 @@ export type TriFrostHandlerConfig = {}, State ex bodyParser?: TriFrostBodyParserOptions | null; }; -export type TriFrostRouteHandler, State extends Record = {}> = - | TriFrostHandler - | TriFrostHandlerConfig; +/** A route handler can be a bare handler or a config object */ +export type TriFrostRouteHandler< + Env extends Record, + State extends Record = {}, + TV extends TFValidator = TFValidator, +> = TriFrostHandler> | TriFrostHandlerConfig; -export type TriFrostRoute, State extends Record = {}> = { +/** Internal route representation (after registration) */ +export type TriFrostRoute< + Env extends Record = {}, + State extends Record = {}, + TInput extends TFInput = TFInput, +> = { path: string; - middleware: {name: string; description: string | null; fingerprint: any; handler: TriFrostMiddleware}[]; - fn: TriFrostHandler; + middleware: { + name: string; + description: string | null; + fingerprint: any; + handler: TriFrostMiddleware; + }[]; + fn: TriFrostHandler; + input: TFValidator | null; timeout: number | null; kind: TriFrostContextKind; method: HttpMethod; @@ -70,6 +95,7 @@ export type TriFrostRoute, State extends Record< meta: Record | null; }; +/** Groupers (subrouter handlers) */ export type TriFrostGrouper, State extends Record = {}> = ( router: TriFrostRouter, ) => Promisify>; @@ -83,10 +109,12 @@ export type TriFrostGrouperHandler, State extend | TriFrostGrouper | TriFrostGrouperConfig; +/** Route builder handler */ export type TriFrostRouteBuilderHandler, State extends Record = {}> = ( route: Route, ) => void; +/** Router options */ export type TriFrostRouterOptions = {}, State extends Record = {}> = { /** * Tree passed by root router to register routes onto @@ -115,9 +143,9 @@ export type TriFrostRouterOptions = {}, State ex bodyParser: TriFrostBodyParserOptions | null; }; +/** Router interface */ export type TriFrostRouter = {}, State extends Record = {}> = { get path(): string; - get timeout(): number | null | undefined; use: = {}>(val: TriFrostMiddleware) => TriFrostRouter; @@ -138,38 +166,50 @@ export type TriFrostRouter = {}, State extends R ) => TriFrostRouter; onNotFound: (fn: TriFrostHandler) => TriFrostRouter; - onError: (fn: TriFrostHandler) => TriFrostRouter; - get: ( + get< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >( path: Path, - handler: TriFrostRouteHandler>, - ) => TriFrostRouter; + handler: TriFrostRouteHandler, TV>, + ): TriFrostRouter; - post: ( + post< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >( path: Path, - handler: TriFrostRouteHandler>, - ) => TriFrostRouter; + handler: TriFrostRouteHandler, TV>, + ): TriFrostRouter; - put: ( + put< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >( path: Path, - handler: TriFrostRouteHandler>, - ) => TriFrostRouter; + handler: TriFrostRouteHandler, TV>, + ): TriFrostRouter; - patch: ( + patch< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >( path: Path, - handler: TriFrostRouteHandler>, - ) => TriFrostRouter; + handler: TriFrostRouteHandler, TV>, + ): TriFrostRouter; - del: ( + del< + Path extends string = string, + TV extends TFValidator> = TFValidator>, + >( path: Path, - handler: TriFrostRouteHandler>, - ) => TriFrostRouter; + handler: TriFrostRouteHandler, TV>, + ): TriFrostRouter; - health: ( - path: Path, - handler: TriFrostHandler>, - ) => TriFrostRouter; + /** Health doesn’t take a validator */ + health(path: Path, handler: TriFrostHandler>): TriFrostRouter; }; /** diff --git a/lib/types/validation.ts b/lib/types/validation.ts new file mode 100644 index 0000000..3be5429 --- /dev/null +++ b/lib/types/validation.ts @@ -0,0 +1,18 @@ +import {type TriFrostContext} from './context'; + +export type TFInput = Record> = { + body: TBody; + query: TQuery; +}; + +/** + * Extract the input shape carried by a validator + */ +export type ExtractInput = T extends {__inputType?: infer U} ? U : TFInput; + +export type TFValidator = {}, State extends Record = {}> = { + parse: (raw: {body: unknown; query: unknown}) => TInput; + onInvalid?: (ctx: TriFrostContext, err: unknown) => void | Promise; + /** @internal purely for typing, never set in runtime */ + __inputType?: TInput; +}; diff --git a/lib/utils/Generic.ts b/lib/utils/Generic.ts index 447cb94..67f5f2f 100644 --- a/lib/utils/Generic.ts +++ b/lib/utils/Generic.ts @@ -1,77 +1,6 @@ -import {isIntBetween, isIntGt} from '@valkyriestudios/utils/number'; +import {isIntBetween} from '@valkyriestudios/utils/number'; import {isNeString} from '@valkyriestudios/utils/string'; -const HEX_LUT = [ - '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', // eslint-disable-line prettier/prettier - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', // eslint-disable-line prettier/prettier - '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', // eslint-disable-line prettier/prettier - '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', // eslint-disable-line prettier/prettier - '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', // eslint-disable-line prettier/prettier - '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', // eslint-disable-line prettier/prettier - '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', // eslint-disable-line prettier/prettier - '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', // eslint-disable-line prettier/prettier - '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', // eslint-disable-line prettier/prettier - '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', // eslint-disable-line prettier/prettier - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', // eslint-disable-line prettier/prettier - 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', // eslint-disable-line prettier/prettier - 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', // eslint-disable-line prettier/prettier - 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', // eslint-disable-line prettier/prettier - 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', // eslint-disable-line prettier/prettier - 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', // eslint-disable-line prettier/prettier -]; - -export function hexId(lng: number): string { - if (!isIntGt(lng, 0)) return ''; - - switch (lng) { - case 8: { - const a = (Math.random() * 0xffffffff) >>> 0; - const b = (Math.random() * 0xffffffff) >>> 0; - return ( - HEX_LUT[a & 0xff] + - HEX_LUT[(a >>> 8) & 0xff] + - HEX_LUT[(a >>> 16) & 0xff] + - HEX_LUT[(a >>> 24) & 0xff] + - HEX_LUT[b & 0xff] + - HEX_LUT[(b >>> 8) & 0xff] + - HEX_LUT[(b >>> 16) & 0xff] + - HEX_LUT[(b >>> 24) & 0xff] - ); - } - case 16: { - const a = (Math.random() * 0xffffffff) >>> 0; - const b = (Math.random() * 0xffffffff) >>> 0; - const c = (Math.random() * 0xffffffff) >>> 0; - const d = (Math.random() * 0xffffffff) >>> 0; - return ( - HEX_LUT[a & 0xff] + - HEX_LUT[(a >>> 8) & 0xff] + - HEX_LUT[(a >>> 16) & 0xff] + - HEX_LUT[(a >>> 24) & 0xff] + - HEX_LUT[b & 0xff] + - HEX_LUT[(b >>> 8) & 0xff] + - HEX_LUT[(b >>> 16) & 0xff] + - HEX_LUT[(b >>> 24) & 0xff] + - HEX_LUT[c & 0xff] + - HEX_LUT[(c >>> 8) & 0xff] + - HEX_LUT[(c >>> 16) & 0xff] + - HEX_LUT[(c >>> 24) & 0xff] + - HEX_LUT[d & 0xff] + - HEX_LUT[(d >>> 8) & 0xff] + - HEX_LUT[(d >>> 16) & 0xff] + - HEX_LUT[(d >>> 24) & 0xff] - ); - } - default: { - let out = ''; - for (let i = 0; i < lng; i++) { - out += HEX_LUT[(Math.random() * 256) | 0]; - } - return out; - } - } -} - /** * Prepends the provided html string with Doctype if necessary * diff --git a/lib/utils/Query.ts b/lib/utils/Query.ts new file mode 100644 index 0000000..7b2d130 --- /dev/null +++ b/lib/utils/Query.ts @@ -0,0 +1,61 @@ +function assign(acc: Record, key: string, val: Date | number | string | boolean | null) { + const existing = acc[key]; + if (existing === undefined) { + acc[key] = val; + } else if (Array.isArray(existing)) { + existing.push(val); + } else { + acc[key] = [existing, val]; + } +} + +export function toObject>(query: string): T { + if (typeof query !== 'string') throw new Error('query/toObject: Value must be a string'); + if (query[0] === '?') query = query.slice(1); + if (!query) return {} as T; + + const params = new URLSearchParams(query); + const acc: Record = {}; + for (const [key, val] of params) { + const normalized = val.trim(); + if (normalized !== '') { + if (normalized.length <= 5) { + switch (normalized.toLowerCase()) { + case 'true': + assign(acc, key, true); + continue; + case 'false': + assign(acc, key, false); + continue; + case 'null': + assign(acc, key, null); + continue; + default: + break; + } + } + + if (normalized[4] === '-' && normalized[7] === '-' && normalized[10] === 'T') { + const date = new Date(normalized); + if (!isNaN(date as unknown as number)) { + assign(acc, key, date); + continue; + } + } + + if (normalized[0] !== '0') { + const num = Number(normalized); + if (Number.isFinite(num)) { + assign(acc, key, num); + continue; + } + } + + assign(acc, key, normalized); + } + } + + return acc as T; +} + +export default toObject; diff --git a/package-lock.json b/package-lock.json index 83d4005..4341a22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,21 @@ "version": "1.4.1", "license": "MIT", "dependencies": { - "@valkyriestudios/utils": "^12.47.0" + "@valkyriestudios/utils": "^12.48.0" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20251014.0", - "@types/node": "^22.18.12", - "@vitest/coverage-v8": "^4.0.4", - "bun-types": "^1.3.1", + "@cloudflare/workers-types": "^4.20260103.0", + "@types/node": "^22.19.3", + "@vitest/coverage-v8": "^4.0.16", + "bun-types": "^1.3.5", "esbuild-register": "^3.6.0", - "eslint": "^9.38.0", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", - "prettier": "^3.6.2", + "prettier": "^3.7.4", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.2", - "vitest": "^4.0.4" + "typescript-eslint": "^8.51.0", + "vitest": "^4.0.16" } }, "node_modules/@babel/helper-string-parser": { @@ -37,9 +37,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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": { @@ -47,13 +47,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -63,14 +63,14 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -87,288 +87,16 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20251014.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251014.0.tgz", - "integrity": "sha512-tEW98J/kOa0TdylIUOrLKRdwkUw0rvvYVlo+Ce0mqRH3c8kSoxLzUH9gfCvwLe0M89z1RkzFovSKAW2Nwtyn3w==", + "version": "4.20260103.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260103.0.tgz", + "integrity": "sha512-jANmoGpJcXARnwlkvrQOeWyjYD1quTfHcs+++Z544XRHOSfLc4XSlts7snIhbiIGgA5bo66zDhraF+9lKUr2hw==", "dev": true, "license": "MIT OR Apache-2.0" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -382,142 +110,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -576,22 +168,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", - "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "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.16.0" + "@eslint/core": "^0.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "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": { @@ -626,9 +218,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -649,13 +241,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "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": { - "@eslint/core": "^0.16.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -744,353 +336,35 @@ "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/@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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", - "cpu": [ - "arm64" - ], + }, + "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", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", - "cpu": [ - "ia32" - ], + "node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", "cpu": [ "x64" ], @@ -1098,13 +372,13 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", "cpu": [ "x64" ], @@ -1112,13 +386,13 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "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" }, @@ -1155,42 +429,30 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", - "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.0.2" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "graphemer": "^1.4.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1200,7 +462,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.2", + "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -1216,16 +478,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "engines": { @@ -1241,14 +503,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "engines": { @@ -1263,14 +525,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1281,9 +543,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "dev": true, "license": "MIT", "engines": { @@ -1298,17 +560,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1323,9 +585,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "dev": true, "license": "MIT", "engines": { @@ -1337,22 +599,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1392,16 +653,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1416,13 +677,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -1434,36 +695,36 @@ } }, "node_modules/@valkyriestudios/utils": { - "version": "12.47.0", - "resolved": "https://registry.npmjs.org/@valkyriestudios/utils/-/utils-12.47.0.tgz", - "integrity": "sha512-ffgIkys75YG8la1GIjfOAwHwslXxD0eb5HJlI0Jcsc/9Be+3dV7Htvu8Rp2D7d+Ix6+2kQihARiMszwIZ+P//g==", + "version": "12.48.0", + "resolved": "https://registry.npmjs.org/@valkyriestudios/utils/-/utils-12.48.0.tgz", + "integrity": "sha512-LZsyzODZwESR+qpDqZD8H021AdWpOcnzPRWyt3sj3mkO9AiqF1rBQRV1uwk/Vkam/bIb8d1V10IpX8bukR8d2w==", "license": "MIT" }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.4.tgz", - "integrity": "sha512-YM7gDj2TX2AXyGLz0p/B7hvTsTfaQc+kSV/LU0nEnKlep/ZfbdCDppPND4YQiQC43OXyrhkG3y8ZSTqYb2CKqQ==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", + "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.4", - "ast-v8-to-istanbul": "^0.3.5", - "debug": "^4.4.3", + "@vitest/utils": "4.0.16", + "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", - "magicast": "^0.3.5", - "std-env": "^3.9.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.4", - "vitest": "4.0.4" + "@vitest/browser": "4.0.16", + "vitest": "4.0.16" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1487,17 +748,17 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.4.tgz", - "integrity": "sha512-0ioMscWJtfpyH7+P82sGpAi3Si30OVV73jD+tEqXm5+rIx9LgnfdaOn45uaFkKOncABi/PHL00Yn0oW/wK4cXw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.4", - "@vitest/utils": "4.0.4", - "chai": "^6.0.1", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, "funding": { @@ -1505,15 +766,15 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.4.tgz", - "integrity": "sha512-UTtKgpjWj+pvn3lUM55nSg34098obGhSHH+KlJcXesky8b5wCUgg7s60epxrS6yAG8slZ9W8T9jGWg4PisMf5Q==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.4", + "@vitest/spy": "4.0.16", "estree-walker": "^3.0.3", - "magic-string": "^0.30.19" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1532,9 +793,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.4.tgz", - "integrity": "sha512-lHI2rbyrLVSd1TiHGJYyEtbOBo2SDndIsN3qY4o4xe2pBxoJLD6IICghNCvD7P+BFin6jeyHXiUICXqgl6vEaQ==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", "dev": true, "license": "MIT", "dependencies": { @@ -1545,13 +806,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.4.tgz", - "integrity": "sha512-99EDqiCkncCmvIZj3qJXBZbyoQ35ghOwVWNnQ5nj0Hnsv4Qm40HmrMJrceewjLVvsxV/JSU4qyx2CGcfMBmXJw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.4", + "@vitest/utils": "4.0.16", "pathe": "^2.0.3" }, "funding": { @@ -1559,14 +820,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.4.tgz", - "integrity": "sha512-XICqf5Gi4648FGoBIeRgnHWSNDp+7R5tpclGosFaUUFzY6SfcpsfHNMnC7oDu/iOLBxYfxVzaQpylEvpgii3zw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.4", - "magic-string": "^0.30.19", + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -1574,9 +835,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.4.tgz", - "integrity": "sha512-G9L13AFyYECo40QG7E07EdYnZZYCKMTSp83p9W8Vwed0IyCG1GnpDLxObkx8uOGPXfDpdeVf24P1Yka8/q1s9g==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", "dev": true, "license": "MIT", "funding": { @@ -1584,13 +845,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.4.tgz", - "integrity": "sha512-4bJLmSvZLyVbNsYFRpPYdJViG9jZyRvMZ35IF4ymXbRZoS+ycYghmwTGiscTXduUg2lgKK7POWIyXJNute1hjw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.4", + "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" }, "funding": { @@ -1671,9 +932,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", - "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1700,30 +961,14 @@ "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, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/bun-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.1.tgz", - "integrity": "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.5.tgz", + "integrity": "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" - }, - "peerDependencies": { - "@types/react": "^19" } }, "node_modules/callsites": { @@ -1737,9 +982,9 @@ } }, "node_modules/chai": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", - "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -1805,18 +1050,10 @@ "node": ">= 8" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1846,9 +1083,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1859,31 +1096,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/esbuild-register": { @@ -1913,20 +1151,20 @@ } }, "node_modules/eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2124,9 +1362,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "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": "Apache-2.0", "engines": { @@ -2147,36 +1385,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "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.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/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==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "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", @@ -2191,14 +1399,22 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "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": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/file-entry-cache": { @@ -2214,19 +1430,6 @@ "node": ">=16.0.0" } }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2265,21 +1468,6 @@ "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, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2306,13 +1494,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2390,16 +1571,6 @@ "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, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2454,9 +1625,9 @@ "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": { @@ -2545,15 +1716,15 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -2572,30 +1743,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2642,6 +1789,17 @@ "dev": true, "license": "MIT" }, + "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, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2740,13 +1898,13 @@ "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.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -2792,9 +1950,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "bin": { @@ -2830,27 +1988,6 @@ "node": ">=6" } }, - "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/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2861,21 +1998,10 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, "license": "MIT", "dependencies": { @@ -2889,55 +2015,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" } }, - "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" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -3055,11 +2157,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -3078,37 +2183,6 @@ "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/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", @@ -3119,23 +2193,10 @@ "node": ">=14.0.0" } }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -3173,16 +2234,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", - "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2" + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3214,13 +2275,13 @@ } }, "node_modules/vite": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", - "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -3288,60 +2349,29 @@ } } }, - "node_modules/vite/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/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.4.tgz", - "integrity": "sha512-hV31h0/bGbtmDQc0KqaxsTO1v4ZQeF8ojDFuy4sZhFadwAqqvJA0LDw68QUocctI5EDpFMql/jVWKuPYHIf2Ew==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.4", - "@vitest/mocker": "4.0.4", - "@vitest/pretty-format": "4.0.4", - "@vitest/runner": "4.0.4", - "@vitest/snapshot": "4.0.4", - "@vitest/spy": "4.0.4", - "@vitest/utils": "4.0.4", - "debug": "^4.4.3", + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", - "magic-string": "^0.30.19", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.9.0", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", + "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", @@ -3358,12 +2388,12 @@ }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", + "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.4", - "@vitest/browser-preview": "4.0.4", - "@vitest/browser-webdriverio": "4.0.4", - "@vitest/ui": "4.0.4", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, @@ -3371,7 +2401,7 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { @@ -3397,19 +2427,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index d3096d6..3b2504f 100644 --- a/package.json +++ b/package.json @@ -112,20 +112,20 @@ } }, "dependencies": { - "@valkyriestudios/utils": "^12.47.0" + "@valkyriestudios/utils": "^12.48.0" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20251014.0", - "@types/node": "^22.18.12", - "@vitest/coverage-v8": "^4.0.4", - "bun-types": "^1.3.1", + "@cloudflare/workers-types": "^4.20260103.0", + "@types/node": "^22.19.3", + "@vitest/coverage-v8": "^4.0.16", + "bun-types": "^1.3.5", "esbuild-register": "^3.6.0", - "eslint": "^9.38.0", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", - "prettier": "^3.6.2", + "prettier": "^3.7.4", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.2", - "vitest": "^4.0.4" + "typescript-eslint": "^8.51.0", + "vitest": "^4.0.16" } } diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..467dc00 --- /dev/null +++ b/test.ts @@ -0,0 +1,67 @@ +import z, {type ZodTypeAny} from 'zod'; +import {App, type TriFrostRouter} from './lib/'; +import {type TFValidator, type TFInput} from './lib/types/validation'; +import {type TriFrostContext} from './lib/types/context'; + +/** + * Wrap Zod schemas into a TFValidator. + */ +export function zValidator< + TBody extends ZodTypeAny | undefined, + TQuery extends ZodTypeAny | undefined, + Env extends Record = {}, + State extends Record = {}, +>( + schemas: {body?: TBody; query?: TQuery}, + onInvalid?: (ctx: TriFrostContext, err: unknown) => void | Promise, +): TFValidator< + TFInput : {}, TQuery extends ZodTypeAny ? z.infer & Record : {}>, + Env, + State +> { + type TInput = TFInput< + TBody extends ZodTypeAny ? z.infer : {}, + TQuery extends ZodTypeAny ? z.infer & Record : {} + >; + + return { + parse: raw => + ({ + body: schemas.body ? schemas.body.parse(raw.body) : ({} as any), + query: schemas.query ? schemas.query.parse(raw.query) : ({} as any), + }) as TInput, + onInvalid, + } satisfies TFValidator; +} + +/** + * Example schemas + */ +const UserSchema = { + body: z.object({name: z.string(), age: z.number()}), + query: z.object({active: z.boolean().optional()}), +}; + +/** + * Router with routes + */ +export function groupsRouter(r: TriFrostRouter) { + return r + .get('/submit', ctx => { + return ctx.json({hello: 'world'}); + }) + .post('/submitForm', { + input: zValidator(UserSchema), + fn: ctx => { + return ctx.json({ + user: ctx.body.age, + filter: ctx.query, + }); + }, + }); +} + +/** + * Mount onto app + */ +new App().group('', groupsRouter); diff --git a/test/MockContext.ts b/test/MockContext.ts index 178f58b..4808628 100644 --- a/test/MockContext.ts +++ b/test/MockContext.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import {hexId} from '@valkyriestudios/utils/hash'; import {type TriFrostRateLimitLimitFunction} from '../lib/modules/RateLimit'; import {type HttpMethod, type HttpStatusCode, type MimeType} from '../lib/types/constants'; import { @@ -15,9 +16,8 @@ import {Cookies} from '../lib/modules/Cookies'; import {type TriFrostCache} from '../lib/modules/Cache'; import {Logger, type TriFrostLogger} from '../lib/modules/Logger'; import {MemoryCache} from '../lib/storage/Memory'; -import {TriFrostBodyParserOptions, type ParsedBody} from '../lib/utils/BodyParser/types'; +import {TriFrostBodyParserOptions} from '../lib/utils/BodyParser/types'; import {Lazy} from '../lib/utils/Lazy'; -import {hexId} from '../lib/utils/Generic'; export class MockContext = Record> implements TriFrostContext { // eslint-disable-line prettier/prettier @@ -111,7 +111,7 @@ export class MockContext = Record return this.#ip; } get query() { - return this.#query; + return this.#query as unknown as Record; } get body() { return {}; @@ -204,7 +204,7 @@ export class MockContext = Record setTimeout = (_: number | null): void => {}; clearTimeout = (): void => {}; - init = async (_: TriFrostRouteMatch, _handler?: (options: TriFrostBodyParserOptions | null) => Promise) => {}; + init = async (_: TriFrostRouteMatch, _handler?: (options: TriFrostBodyParserOptions | null) => Promise) => {}; abort = (_?: HttpStatusCode): void => {}; diff --git a/test/unit/App.test.ts b/test/unit/App.test.ts index ab6dddf..fb8029c 100644 --- a/test/unit/App.test.ts +++ b/test/unit/App.test.ts @@ -816,7 +816,7 @@ describe('App', () => { it('Propagates bodyParser to route()', () => { const cfg = {limit: 999_999}; app.bodyParser(cfg).route('/files', r => { - r.post(vi.fn()); + r.post(vi.fn() as any); }); const route = tree.stack.find(r => r.path === '/files' && r.method === 'POST'); @@ -1304,6 +1304,7 @@ describe('App', () => { description: 'Healthcheck Route', fn: handler, kind: 'health', + input: null, meta: null, method: 'GET', middleware: [], @@ -1317,6 +1318,7 @@ describe('App', () => { description: 'Healthcheck Route', fn: handler, kind: 'health', + input: null, meta: null, method: 'HEAD', middleware: [], @@ -1352,6 +1354,7 @@ describe('App', () => { description: 'Healthcheck Route', fn: handler, kind: 'health', + input: null, meta: null, method: 'GET', middleware: [ @@ -1372,6 +1375,7 @@ describe('App', () => { description: 'Healthcheck Route', fn: handler, kind: 'health', + input: null, meta: null, method: 'HEAD', middleware: [ diff --git a/test/unit/Context.test.ts b/test/unit/Context.test.ts index 35a6153..88820ac 100644 --- a/test/unit/Context.test.ts +++ b/test/unit/Context.test.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import * as Hash from '@valkyriestudios/utils/hash'; import {describe, it, expect, vi, beforeEach, afterEach, afterAll} from 'vitest'; import {HttpMethods} from '../../lib/types/constants'; import {Context, IP_HEADER_CANDIDATES} from '../../lib/Context'; @@ -46,7 +47,7 @@ describe('Context', () => { let ctx: TestContext; const mockLogger = { spawn: vi.fn().mockReturnValue({ - traceId: Generic.hexId(16), + traceId: Hash.hexId(16), setAttributes: vi.fn(), span: vi.fn((_, fn) => fn()), error: vi.fn(), @@ -72,6 +73,10 @@ describe('Context', () => { ctx = new TestContext(mockLogger as any, baseConfig as any, baseRequest); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + describe('Constructor', () => { it('Uses requestId from headers when present and valid', () => { const ctxWithId = new TestContext( @@ -424,8 +429,8 @@ describe('Context', () => { ...baseRequest, query: 'foo=bar&baz=1', }); - expect(c2.query.get('foo')).toBe('bar'); - expect(c2.query.get('baz')).toBe('1'); + expect(c2.query.foo).toBe('bar'); + expect(c2.query.baz).toBe(1); }); }); @@ -805,16 +810,6 @@ describe('Context', () => { }); describe('host', () => { - const spyDetermine = vi.spyOn(Generic, 'determineHost'); - - beforeEach(() => { - spyDetermine.mockClear(); - }); - - afterAll(() => { - spyDetermine.mockRestore(); - }); - it('Uses getHostFromHeaders() result if available', () => { const ctx = new TestContext(mockLogger as any, baseConfig as any, { ...baseRequest, @@ -823,6 +818,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); expect(ctx.host).toBe('proxy.example.com'); @@ -838,6 +834,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); expect(ctx.host).toBe('proxy.example.com'); expect(ctx.host).toBe('proxy.example.com'); @@ -855,6 +852,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); expect(ctx.host).toBe('0.0.0.0'); expect(spy).toHaveBeenCalledTimes(1); @@ -869,6 +867,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); expect(ctx.host).toBe('0.0.0.0'); expect(ctx.host).toBe('0.0.0.0'); @@ -886,6 +885,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); expect(ctx.host).toBe('0.0.0.0'); expect(spy).toHaveBeenCalledTimes(1); @@ -900,6 +900,7 @@ describe('Context', () => { /* @ts-expect-error should be good */ const spy = vi.spyOn(ctx, 'getHostFromHeaders'); + const spyDetermine = vi.spyOn(Generic, 'determineHost'); const host1 = ctx.host; const host2 = ctx.host; diff --git a/test/unit/modules/JSX/render.test.ts b/test/unit/modules/JSX/render.test.ts index cb9254c..8fa1c90 100644 --- a/test/unit/modules/JSX/render.test.ts +++ b/test/unit/modules/JSX/render.test.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ +import * as Hash from '@valkyriestudios/utils/hash'; import {describe, it, expect, beforeEach, vi, afterEach} from 'vitest'; import {render, escape, rootRender, toLruCookie, fromLruCookie} from '../../../../lib/modules/JSX/render'; import {Fragment} from '../../../../lib/modules/JSX/runtime'; @@ -9,7 +10,6 @@ import {env} from '../../../../lib/modules/JSX/ctx/env'; import {state} from '../../../../lib/modules/JSX/ctx/state'; import {Style} from '../../../../lib/modules/JSX/style/Style'; import {Script} from '../../../../lib/modules/JSX/script/Script'; -import * as Generic from '../../../../lib/utils/Generic'; import {MockContext} from '../../../MockContext'; import {createModule, createScript} from '../../../../lib/modules/JSX/script/use'; import {ARC_GLOBAL, ARC_GLOBAL_OBSERVER, ATOMIC_GLOBAL} from '../../../../lib/modules/JSX/script/atomic'; @@ -524,7 +524,7 @@ describe('Modules - JSX - Renderer', () => { it('Includes css root and script root when passed to render context and not in html', () => { let idCounter = 0; - vi.spyOn(Generic, 'hexId').mockImplementation(() => `id-${++idCounter}`); + vi.spyOn(Hash, 'hexId').mockImplementation(() => `id-${++idCounter}`); const ctx = new MockContext(); @@ -613,7 +613,7 @@ describe('Modules - JSX - Renderer', () => { it('Includes css root and script root when passed to render context and in html', () => { let idCounter = 0; - vi.spyOn(Generic, 'hexId').mockImplementation(() => `id-${++idCounter}`); + vi.spyOn(Hash, 'hexId').mockImplementation(() => `id-${++idCounter}`); const ctx = new MockContext(); @@ -718,7 +718,7 @@ describe('Modules - JSX - Renderer', () => { it('Includes css root and script root when passed to render context and in html BUT with mount paths set', () => { let idCounter = 0; - vi.spyOn(Generic, 'hexId').mockImplementation(() => `id-${++idCounter}`); + vi.spyOn(Hash, 'hexId').mockImplementation(() => `id-${++idCounter}`); const ctx = new MockContext(); @@ -887,7 +887,7 @@ describe('Modules - JSX - Renderer', () => { beforeEach(() => { idCounter = 0; - vi.spyOn(Generic, 'hexId').mockImplementation(() => `id-${++idCounter}`); + vi.spyOn(Hash, 'hexId').mockImplementation(() => `id-${++idCounter}`); }); afterEach(() => { diff --git a/test/unit/modules/JSX/script/atomic.test.ts b/test/unit/modules/JSX/script/atomic.test.ts index 199c387..f02c8bb 100644 --- a/test/unit/modules/JSX/script/atomic.test.ts +++ b/test/unit/modules/JSX/script/atomic.test.ts @@ -90,22 +90,55 @@ describe('Modules - JSX - script - atomic', () => { '})());', /* Store */ 'def("$tfs",(()=>{', - 'const s=Object.create(null);', + 'const s=new M.create(null);', 'const kP="$tfs:";', - 'const notify=(k,v)=>w.$tfr.publish("$store:"+k,v);', + 'const pub=(k,v)=>w.$tfr.publish("$store:"+k,v);', + 'const pubExp=(k,v)=>w.$tfr.publish("$store:"+k+":expired",v);', + 'let gttl=null;', + 'let t=null;', + 'let scanning=false;', 'try{', 'for(let i=0;i{', + 'try{localStorage.removeItem(kP+k);}catch{}};', + 'const expire=(k,e,isTTL=false)=>{', + 'delete s[k];', + 'if(e.persist)delPersist(k);', + 'isTTL?pubExp(k,e.v):pub(k,undefined);', + 'if(!scanning&&e.exp===gttl)scan();', + '};', + 'const scan=()=>{', + 'if(t)clearTimeout(t);', + 't=null;', + 'scanning=true;', + 'let ttl=null,now=Date.now();', + 'for(const k in s){', + 'const e=s[k];', + 'if(e.exp&&e.exp<=now)expire(k,e,true);', + 'else if(e.exp&&(!ttl||e.exp{if(!isStr(k)||!k)return undefined;return s[k]},', - 'set:(k,v,o={})=>{if(!isStr(k)||!k||eq(s[k],v))return;s[k]=v;if(o?.persist===true){try{localStorage.setItem(kP+k,JSON.stringify({v}));}catch{}}notify(k,v);},', - 'del:k=>{if(!isStr(k)||!k)return;delete s[k];try{localStorage.removeItem(kP+k);}catch{}notify(k,undefined);},', - '});})());', + 'get:k=>{if(!isStr(k)||!k)return undefined;const e=s[k];if(!e)return undefined;if(e.exp&&e.exp<=Date.now()){expire(k,e,true);return undefined;}return e.v;},', + 'set:(k,v,o={})=>{if(!isStr(k)||!k)return;const e=s[k];const ttl=o.ttl?Date.now()+o.ttl:null;const persist=o.persist===true;if(e&&eq(e.v,v)&&e.exp===ttl&&e.persist===persist)return;if(e?.persist&&!persist)delPersist(k);s[k]={v,exp:ttl,persist};if(persist){try{localStorage.setItem(kP+k,JSON.stringify({v,e:ttl}));}catch{}}pub(k,v);if(ttl&&(!gttl||ttl{if(!isStr(k)||!k)return;const e=s[k];if(!e)return;expire(k,e,false);},', + '});', + '})());', /* Global clock map */ 'def("$tfc",new Map());', /* Global clock ticker */ diff --git a/test/unit/routing/Router.test.ts b/test/unit/routing/Router.test.ts index 936b0d3..ee5157e 100644 --- a/test/unit/routing/Router.test.ts +++ b/test/unit/routing/Router.test.ts @@ -594,6 +594,7 @@ describe('routing - Router', () => { { path: '/api/*', kind: 'notfound', + input: null, fn: handler, timeout: null, middleware: [], @@ -626,6 +627,7 @@ describe('routing - Router', () => { { path: '/api/*', kind: 'notfound', + input: null, fn: handler, timeout: null, middleware: [ @@ -678,6 +680,7 @@ describe('routing - Router', () => { path: '/api/*', kind: 'error', fn: handler, + input: null, timeout: null, middleware: [], method: 'GET' /* This is a stub */, @@ -709,6 +712,7 @@ describe('routing - Router', () => { { path: '/api/*', kind: 'error', + input: null, fn: handler, timeout: null, middleware: [ @@ -764,6 +768,7 @@ describe('routing - Router', () => { path: '/api/users', method: 'GET', kind: 'std', + input: null, fn: getHandler, timeout: null, middleware: [], @@ -789,6 +794,7 @@ describe('routing - Router', () => { path: '/api/users', method: 'HEAD', kind: 'std', + input: null, fn: getHandler, timeout: null, middleware: [], @@ -801,6 +807,7 @@ describe('routing - Router', () => { path: '/api/users', method: 'POST', kind: 'std', + input: null, fn: postHandler, timeout: null, middleware: [], @@ -840,6 +847,7 @@ describe('routing - Router', () => { path: '/api/secure', method: 'GET', kind: 'std', + input: null, fn: getHandler, timeout: null, middleware: [ @@ -868,6 +876,7 @@ describe('routing - Router', () => { path: '/api/secure', method: 'HEAD', kind: 'std', + input: null, fn: getHandler, timeout: null, middleware: [ @@ -883,6 +892,7 @@ describe('routing - Router', () => { path: '/api/secure', method: 'POST', kind: 'std', + input: null, fn: postHandler, timeout: null, middleware: [ @@ -940,6 +950,7 @@ describe('routing - Router', () => { path: '/api/meta', method: 'GET', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -965,6 +976,7 @@ describe('routing - Router', () => { path: '/api/meta', method: 'HEAD', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1002,6 +1014,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'GET', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1027,6 +1040,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'HEAD', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1060,6 +1074,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'GET', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1088,6 +1103,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'HEAD', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1131,6 +1147,7 @@ describe('routing - Router', () => { path: '/hello/meta', method: 'GET', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1157,6 +1174,7 @@ describe('routing - Router', () => { method: 'HEAD', kind: 'std', fn: handler, + input: null, timeout: 5000, middleware: [], bodyParser: null, @@ -1179,6 +1197,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'GET', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1207,6 +1226,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'HEAD', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1255,6 +1275,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'POST', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1301,6 +1322,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'POST', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1357,6 +1379,7 @@ describe('routing - Router', () => { path: '/hello/meta', method: 'POST', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1393,6 +1416,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'POST', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1455,6 +1479,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'PUT', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1501,6 +1526,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'PUT', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1557,6 +1583,7 @@ describe('routing - Router', () => { path: '/hello/meta', method: 'PUT', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1593,6 +1620,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'PUT', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1655,6 +1683,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'PATCH', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1701,6 +1730,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'PATCH', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1757,6 +1787,7 @@ describe('routing - Router', () => { path: '/hello/meta', method: 'PATCH', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1793,6 +1824,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'PATCH', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1855,6 +1887,7 @@ describe('routing - Router', () => { path: '/hello/test', method: 'DELETE', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [], @@ -1901,6 +1934,7 @@ describe('routing - Router', () => { path: '/with-mware', method: 'DELETE', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -1957,6 +1991,7 @@ describe('routing - Router', () => { path: '/hello/meta', method: 'DELETE', kind: 'std', + input: null, fn: handler, timeout: 5000, middleware: [], @@ -1993,6 +2028,7 @@ describe('routing - Router', () => { path: '/hello/chained', method: 'DELETE', kind: 'std', + input: null, fn: handler, timeout: null, middleware: [ @@ -2055,6 +2091,7 @@ describe('routing - Router', () => { expect(stack.find(r => r.method === 'GET')).toEqual({ path: '/api/ping', method: 'GET', + input: null, kind: 'health', fn: handler, timeout: 30000, @@ -2069,6 +2106,7 @@ describe('routing - Router', () => { path: '/api/ping', method: 'HEAD', kind: 'health', + input: null, fn: handler, timeout: 30000, middleware: [], @@ -2180,7 +2218,7 @@ describe('routing - Router', () => { const handlerGroup = vi.fn(); const handlerRouteGet = vi.fn(); const handlerRoutePost = vi.fn(); - let limitMock; + let limitMock: any; beforeEach(() => { tree = new RouteTree(); limitMock = new Lazy(() => ({limit: vi.fn(() => dummyMiddleware)})); diff --git a/test/unit/runtimes/Bun/Context.test.ts b/test/unit/runtimes/Bun/Context.test.ts index c006e55..57eed1b 100644 --- a/test/unit/runtimes/Bun/Context.test.ts +++ b/test/unit/runtimes/Bun/Context.test.ts @@ -60,8 +60,8 @@ describe('Runtimes - Bun - Context', () => { expect(context.path).toBe('/foo/bar'); const query = context.query; - expect(query.getAll('x')).toEqual(['1', '2']); - expect(query.get('y')).toBe('3'); + expect(query.x).toEqual([1, 2]); + expect(query.y).toBe(3); }); it('Normalizes headers into plain object', () => { diff --git a/test/unit/runtimes/Node/Context.test.ts b/test/unit/runtimes/Node/Context.test.ts index fc7624c..ca578eb 100644 --- a/test/unit/runtimes/Node/Context.test.ts +++ b/test/unit/runtimes/Node/Context.test.ts @@ -65,7 +65,7 @@ describe('Runtimes - Node - Context', () => { it('Parses method, path, headers and query correctly', () => { expect(ctx.method).toBe('POST'); expect(ctx.path).toBe('/test/path'); - expect(ctx.query.get('foo')).toBe('bar'); + expect(ctx.query.foo).toBe('bar'); expect(ctx.headers).toEqual({'content-type': 'application/json'}); }); }); diff --git a/test/unit/runtimes/Node/Runtime.test.ts b/test/unit/runtimes/Node/Runtime.test.ts index b252dd1..2594f57 100644 --- a/test/unit/runtimes/Node/Runtime.test.ts +++ b/test/unit/runtimes/Node/Runtime.test.ts @@ -54,6 +54,7 @@ describe('Runtimes - Node - Runtime', () => { error: vi.fn(), }), }; + vi.clearAllMocks(); }); afterEach(() => { diff --git a/test/unit/runtimes/Runtime.test.ts b/test/unit/runtimes/Runtime.test.ts index 5c9fe13..7e64e80 100644 --- a/test/unit/runtimes/Runtime.test.ts +++ b/test/unit/runtimes/Runtime.test.ts @@ -41,7 +41,7 @@ describe('Modules - JSX - getRuntime', () => { /* @ts-expect-error Should be good */ vi.spyOn(process, 'versions', 'get').mockReturnValue({node: '22.0.0'}); - vi.doMock('../../../lib/runtimes/Node/Runtime.js', () => ({ + vi.mock('../../../lib/runtimes/Node/Runtime.js', () => ({ NodeRuntime: vi.fn().mockImplementation(function NodeRuntime(this: any) { this.name = 'NodeRuntime'; }), diff --git a/test/unit/runtimes/Workerd/Context.test.ts b/test/unit/runtimes/Workerd/Context.test.ts index 181b777..4de3f11 100644 --- a/test/unit/runtimes/Workerd/Context.test.ts +++ b/test/unit/runtimes/Workerd/Context.test.ts @@ -64,8 +64,8 @@ describe('Runtimes - Workerd - Context', () => { expect(context.path).toBe('/foo/bar'); const query = context.query; - expect(query.getAll('x')).toEqual(['1', '2']); - expect(query.get('y')).toBe('3'); + expect(query.x).toEqual([1, 2]); + expect(query.y).toBe(3); }); it('Normalizes headers into plain object', () => { diff --git a/test/unit/utils/Generic.test.ts b/test/unit/utils/Generic.test.ts index b84fd7b..dc3a883 100644 --- a/test/unit/utils/Generic.test.ts +++ b/test/unit/utils/Generic.test.ts @@ -9,63 +9,10 @@ import { prependDocType, determineHost, determineTrustProxy, - hexId, } from '../../../lib/utils/Generic'; import CONSTANTS from '../../constants'; describe('Utils - Generic', () => { - describe('hexId', () => { - it('Returns empty string for non-numeric or non-positive lengths', () => { - for (const el of [...CONSTANTS.NOT_NUMERIC, -100, -1, 0, 0.5, 3.14]) { - expect(hexId(el as number)).toBe(''); - } - }); - - it('Returns 16-char hex string for lng=8', () => { - const id = hexId(8); - expect(id).toMatch(/^[a-f0-9]{16}$/); - }); - - it('Returns 32-char hex string for lng=16', () => { - const id = hexId(16); - expect(id).toMatch(/^[a-f0-9]{32}$/); - }); - - it('Returns correct length for arbitrary valid lengths', () => { - expect(hexId(3)).toMatch(/^[a-f0-9]{6}$/); - expect(hexId(10)).toMatch(/^[a-f0-9]{20}$/); - expect(hexId(32)).toMatch(/^[a-f0-9]{64}$/); - }); - - it('Returns correct length for arbitrary small values', () => { - expect(hexId(1)).toMatch(/^[a-f0-9]{2}$/); - expect(hexId(2)).toMatch(/^[a-f0-9]{4}$/); - expect(hexId(7)).toMatch(/^[a-f0-9]{14}$/); - expect(hexId(9)).toMatch(/^[a-f0-9]{18}$/); - }); - - it('Handles long values without issue', () => { - const id = hexId(100); // 200 chars - expect(id.length).toBe(200); - expect(id).toMatch(/^[a-f0-9]{200}$/); - }); - - it('Returns different values on successive calls (non-repeating)', () => { - const id1 = hexId(8); - const id2 = hexId(8); - expect(id1).not.toBe(id2); - }); - - it('Returns different values on repeated calls (likely unique)', () => { - const seen = new Set(); - for (let i = 0; i < 1000; i++) { - const id = hexId(16); - expect(seen.has(id)).toBe(false); - seen.add(id); - } - }); - }); - describe('prependDocType', () => { it('Returns an empty string if provided a non-string', () => { for (const el of CONSTANTS.NOT_STRING) { diff --git a/test/unit/utils/Query.test.ts b/test/unit/utils/Query.test.ts new file mode 100644 index 0000000..5ada46d --- /dev/null +++ b/test/unit/utils/Query.test.ts @@ -0,0 +1,101 @@ +import {describe, it, expect} from 'vitest'; +import {toObject} from '../../../lib/utils/Query'; + +describe('toObject', () => { + it('should throw when input is not a string', () => { + for (const el of [null, undefined, 123, {}, [], true, false]) { + expect(() => toObject(el as any)).toThrowError(/query\/toObject: Value must be a string/); + } + }); + + it("should return empty object for empty string or '?'", () => { + expect(toObject('')).toEqual({}); + expect(toObject('?')).toEqual({}); + }); + + it('should parse simple key-value pairs', () => { + expect(toObject('a=1&b=hello')).toEqual({a: 1, b: 'hello'}); + }); + + it('should parse booleans correctly', () => { + expect(toObject('x=true&y=false')).toEqual({x: true, y: false}); + expect(toObject('X=True&Y=FALSE')).toEqual({X: true, Y: false}); // case-insensitive + }); + + it('should parse null correctly', () => { + expect(toObject('a=null&b=Null&c=NULL')).toEqual({a: null, b: null, c: null}); + }); + + it('should parse numbers correctly', () => { + expect(toObject('age=30&height=180.5')).toEqual({age: 30, height: 180.5}); + }); + + it('should not parse numbers with leading zeros', () => { + expect(toObject('zip=0123&phone=000999')).toEqual({ + zip: '0123', + phone: '000999', + }); + }); + + it('should parse ISO dates correctly', () => { + const result = toObject('d=2023-12-31T12:34:56Z'); + expect(result.d).toBeInstanceOf(Date); + expect((result.d as Date).toISOString()).toBe('2023-12-31T12:34:56.000Z'); + }); + + it('should fall back to string for invalid dates', () => { + expect(toObject('d=2023-99-99T99:99:99Z')).toEqual({ + d: '2023-99-99T99:99:99Z', + }); + }); + + it('should handle multiple values for same key', () => { + expect(toObject('hobby=reading&hobby=writing')).toEqual({ + hobby: ['reading', 'writing'], + }); + }); + + it('should trim values', () => { + expect(toObject('a= 42 &b= true ')).toEqual({a: 42, b: true}); + }); + + it('should decode percent-encoded values', () => { + expect(toObject('name=Alice%20Smith&greet=hello%2Cworld')).toEqual({ + name: 'Alice Smith', + greet: 'hello,world', + }); + }); + + it('should handle empty values', () => { + expect(toObject('a=&b=')).toEqual({}); + }); + + it('should mix multiple types', () => { + const result = toObject('a=42&b=true&c=null&d=hello&e=2024-02-09T12:00:00Z'); + expect(result.a).toBe(42); + expect(result.b).toBe(true); + expect(result.c).toBe(null); + expect(result.d).toBe('hello'); + expect(result.e).toBeInstanceOf(Date); + }); + + it('should support repeated mixed values for same key', () => { + const result = toObject('x=1&x=true&x=hello&x=null'); + expect(result.x).toEqual([1, true, 'hello', null]); + }); + + it('should handle case-sensitive keys', () => { + expect(toObject('Key=value1&key=value2')).toEqual({ + Key: 'value1', + key: 'value2', + }); + }); + + it('should handle scientific notation', () => { + expect(toObject('n=1.23e10')).toEqual({n: 1.23e10}); + }); + + it("should ignore leading '?'", () => { + expect(toObject('?foo=bar&baz=123')).toEqual({foo: 'bar', baz: 123}); + }); +});