diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e32a83..9e7e491 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: - name: Install dependencies (skip postinstall) run: | - pnpm install --ignore-scripts --frozen-lockfile + pnpm install --ignore-scripts --no-frozen-lockfile - name: Build Packages run: | diff --git a/packages/core/src/Application.ts b/packages/core/src/Application.ts index 29489a6..b659a40 100644 --- a/packages/core/src/Application.ts +++ b/packages/core/src/Application.ts @@ -20,6 +20,7 @@ import semver from 'semver' type AServiceProvider = (new (_app: Application) => ServiceProvider) & Partial export class Application extends Container implements IApplication { + private static instance: Application; public paths = new PathLoader() private tries: number = 0 private booted = false @@ -39,6 +40,8 @@ export class Application extends Container implements IApplication { constructor(basePath: string) { super() + Application.instance = this; + dotenvExpand.expand(dotenv.config({ quiet: true })) this.basePath = basePath @@ -48,6 +51,10 @@ export class Application extends Container implements IApplication { Registerer.register(this) } + public static getInstance(): Application { + return Application.instance; + } + /** * Register core bindings into the container */ diff --git a/packages/queue/README.md b/packages/queue/README.md index 038d297..a4ac842 100644 --- a/packages/queue/README.md +++ b/packages/queue/README.md @@ -41,3 +41,31 @@ The H3ravel framework is open-sourced software licensed under the [MIT license]( [lini]: https://img.shields.io/github/license/h3ravel/framework [tel]: https://github.com/h3ravel/framework/actions/workflows/test.yml [tei]: https://github.com/h3ravel/framework/actions/workflows/test.yml/badge.svg + + +# Queue Package + +This package provides background job processing with multiple drivers. Inspired by Laravel queues. + +## Features +- Dispatch jobs to different drivers (memory, database, redis stub). +- Worker processes jobs and supports retry/backoff. +- Familiar developer experience with some unique twists. + +## Usage + +### Creating a Job +```ts +import { JobContract } from "@h3ravel/queue"; + +export class SendEmailJob implements JobContract { + constructor(private email: string) {} + + async handle() { + console.log(`Sending email to ${this.email}`); + } + + serialize() { + return { email: this.email }; + } +} diff --git a/packages/queue/jest.config.js b/packages/queue/jest.config.js new file mode 100644 index 0000000..5f6f4ac --- /dev/null +++ b/packages/queue/jest.config.js @@ -0,0 +1,14 @@ +import { createDefaultPreset } from "ts-jest"; + +const tsJestTransformCfg = createDefaultPreset().transform; + +/** @type {import("jest").Config} **/ +export default { + testEnvironment: "node", + transform: { + ...tsJestTransformCfg, + }, + moduleNameMapper: { + '^(\.{1,2}/.*)\.js$': '$1', + }, +}; diff --git a/packages/queue/package.json b/packages/queue/package.json index 26b196d..915fc6d 100644 --- a/packages/queue/package.json +++ b/packages/queue/package.json @@ -44,10 +44,14 @@ "test": "jest --passWithNoTests", "version-patch": "pnpm version patch" }, - "peerDependencies": { + "dependencies": { "@h3ravel/core": "workspace:^" }, "devDependencies": { + "@types/jest": "^30.0.0", + "@types/node": "^24.5.2", + "jest": "^30.1.3", + "ts-jest": "^29.4.4", "typescript": "^5.4.0" } } diff --git a/packages/queue/src/Contracts/Job.ts b/packages/queue/src/Contracts/Job.ts new file mode 100644 index 0000000..09514ef --- /dev/null +++ b/packages/queue/src/Contracts/Job.ts @@ -0,0 +1,3 @@ +export interface Job { + handle(): Promise | void; +} diff --git a/packages/queue/src/Contracts/JobContract.ts b/packages/queue/src/Contracts/JobContract.ts new file mode 100644 index 0000000..7ea70be --- /dev/null +++ b/packages/queue/src/Contracts/JobContract.ts @@ -0,0 +1,41 @@ +export interface JobContract { + /** + * The number of times the job may be attempted. + */ + tries?: number; + + /** + * The maximum number of exceptions to allow before failing. + */ + maxExceptions?: number; + + /** + * The number of seconds to wait before retrying the job. + */ + backoff?: number; + + /** + * The number of seconds to wait before timing out the job. + */ + timeout?: number; + + /** + * The number of times the job has been attempted. + */ + attempts?: number; + + /** + * Process the job. + */ + handle(): Promise | void; + + /** + * The job's serialized data. + */ + serialize(): any; + + /** + * The method to call when the job fails. + */ + failed?(error: Error): void; +} \ No newline at end of file diff --git a/packages/queue/src/Contracts/Queue.ts b/packages/queue/src/Contracts/Queue.ts new file mode 100644 index 0000000..2150088 --- /dev/null +++ b/packages/queue/src/Contracts/Queue.ts @@ -0,0 +1,9 @@ +import { Job } from "./Job"; + +export interface Queue { + push(job: Job, payload?: any, queue?: string): Promise; + later(delay: number, job: Job, payload?: any, queue?: string): Promise; + size(queue?: string): Promise; + pop(queue?: string): Promise; + release(job: any, delay: number): Promise; +} diff --git a/packages/queue/src/Contracts/QueueDriverContract.ts b/packages/queue/src/Contracts/QueueDriverContract.ts new file mode 100644 index 0000000..a6b8d2c --- /dev/null +++ b/packages/queue/src/Contracts/QueueDriverContract.ts @@ -0,0 +1,8 @@ +import { JobContract } from '../Contracts/JobContract'; + +export interface QueueDriverContract { + push(job: JobContract): any; + pop(queue?: string): Promise | JobContract | null; + size(queue?: string): Promise | number; + release(job: JobContract, delay?: number): void; +} \ No newline at end of file diff --git a/packages/queue/src/Drivers/ArrayDriver.ts b/packages/queue/src/Drivers/ArrayDriver.ts new file mode 100644 index 0000000..683d34a --- /dev/null +++ b/packages/queue/src/Drivers/ArrayDriver.ts @@ -0,0 +1,37 @@ +import { Job } from "../Contracts/Job"; +import { Queue } from "../Contracts/Queue"; + +export class ArrayDriver implements Queue { + protected jobs: any[] = []; + + public async push(job: Job, payload?: any, queue?: string): Promise { + this.jobs.push({ job, payload, queue }); + return Promise.resolve(); + } + + public async later(delay: number, job: Job, payload?: any, queue?: string): Promise { + return new Promise(resolve => { + setTimeout(async () => { + await this.push(job, payload, queue); + resolve(undefined); + }, delay); + }); + } + + public async size(queue?: string): Promise { + return this.jobs.length; + } + + public async pop(queue?: string): Promise { + return this.jobs.shift(); + } + + public async release(job: any, delay: number): Promise { + return new Promise(resolve => { + setTimeout(async () => { + this.jobs.unshift(job); + resolve(undefined); + }, delay); + }); + } +} diff --git a/packages/queue/src/Drivers/DatabaseDriver.ts b/packages/queue/src/Drivers/DatabaseDriver.ts new file mode 100644 index 0000000..f626e9c --- /dev/null +++ b/packages/queue/src/Drivers/DatabaseDriver.ts @@ -0,0 +1,19 @@ +import { JobContract } from '../Contracts/JobContract'; +import { QueueDriverContract } from '../Contracts/QueueDriverContract'; + +export class DatabaseDriver implements QueueDriverContract { + public push(job: JobContract) { + // TODO: Implement database logic + return null; + } + + public pop(): Promise { + // TODO: Implement database logic + return Promise.resolve(null); + } + + public size(): Promise { + // TODO: Implement database logic + return Promise.resolve(0); + } +} \ No newline at end of file diff --git a/packages/queue/src/Drivers/MemoryDriver.ts b/packages/queue/src/Drivers/MemoryDriver.ts deleted file mode 100644 index 0b1f950..0000000 --- a/packages/queue/src/Drivers/MemoryDriver.ts +++ /dev/null @@ -1 +0,0 @@ -export default class { } diff --git a/packages/queue/src/Drivers/RedisDriver.ts b/packages/queue/src/Drivers/RedisDriver.ts index 0b1f950..fac87d6 100644 --- a/packages/queue/src/Drivers/RedisDriver.ts +++ b/packages/queue/src/Drivers/RedisDriver.ts @@ -1 +1,19 @@ -export default class { } +import { JobContract } from '../Contracts/JobContract'; +import { QueueDriverContract } from '../Contracts/QueueDriverContract'; + +export class RedisDriver implements QueueDriverContract { + public push(job: JobContract) { + // TODO: Implement redis logic + return null; + } + + public pop(): Promise { + // TODO: Implement redis logic + return Promise.resolve(null); + } + + public size(): Promise { + // TODO: Implement redis logic + return Promise.resolve(0); + } +} \ No newline at end of file diff --git a/packages/queue/src/Drivers/SyncDriver.ts b/packages/queue/src/Drivers/SyncDriver.ts new file mode 100644 index 0000000..b8eb8e8 --- /dev/null +++ b/packages/queue/src/Drivers/SyncDriver.ts @@ -0,0 +1,39 @@ +import { Job } from "../Contracts/Job"; +import { Queue } from "../Contracts/Queue"; + +export class SyncDriver implements Queue { + public async push(job: Job, payload?: any, queue?: string): Promise { + const jobInstance = this.resolveJob(job, payload); + await jobInstance.handle(); + } + + public async later(delay: number, job: Job, payload?: any, queue?: string): Promise { + await new Promise(resolve => setTimeout(resolve, delay)); + return this.push(job, payload, queue); + } + + public async size(queue?: string): Promise { + return Promise.resolve(0); + } + + public async pop(queue?: string): Promise { + return Promise.resolve(undefined); + } + + public async release(job: any, delay: number): Promise { + return Promise.resolve(); + } + + protected resolveJob(job: any, payload: any): Job { + if (typeof job === 'object') { + return job; + } + + // Here you might want to resolve the job from a container or instantiate it + // For now, we'll assume the job is a class constructor + const jobInstance = new job(); + // You can pass the payload to the job instance if it has a constructor that accepts it + // Object.assign(jobInstance, payload); + return jobInstance; + } +} diff --git a/packages/queue/src/Jobs/SendEmailJobs.ts b/packages/queue/src/Jobs/SendEmailJobs.ts new file mode 100644 index 0000000..a6951d9 --- /dev/null +++ b/packages/queue/src/Jobs/SendEmailJobs.ts @@ -0,0 +1,13 @@ +import { JobContract } from "../Contracts/JobContract"; + +export class SendEmailJob implements JobContract { + constructor(private email: string) {} + + async handle(): Promise { + console.log(`📧 Sending email to ${this.email}`); + } + + serialize() { + return { email: this.email, name: "SendEmailJob" }; + } +} diff --git a/packages/queue/src/Providers/QueueServiceProvider.ts b/packages/queue/src/Providers/QueueServiceProvider.ts index 57e6a5c..5060410 100644 --- a/packages/queue/src/Providers/QueueServiceProvider.ts +++ b/packages/queue/src/Providers/QueueServiceProvider.ts @@ -1,18 +1,10 @@ -import { ServiceProvider } from '@h3ravel/core' +import { ServiceProvider } from "@h3ravel/core"; +import { QueueManager } from "../QueueManager"; -/** - * Queues and workers. - * - * Register QueueManager. - * Load drivers (Redis, in-memory). - * Register job dispatcher and workers. - * - * Auto-Registered if @h3ravel/queue is installed - */ export class QueueServiceProvider extends ServiceProvider { - public static priority = 991 - - register () { - // Core bindings - } -} + public register(): void { + this.app.singleton("queue", (app) => { + return new QueueManager(app); + }); + } +} \ No newline at end of file diff --git a/packages/queue/src/QueueManager.ts b/packages/queue/src/QueueManager.ts index 0b1f950..8958a11 100644 --- a/packages/queue/src/QueueManager.ts +++ b/packages/queue/src/QueueManager.ts @@ -1 +1,76 @@ -export default class { } +import { Application } from "@h3ravel/core"; +import { Queue } from "./Contracts/Queue"; +import { SyncDriver } from "./Drivers/SyncDriver"; +import { ArrayDriver } from "./Drivers/ArrayDriver"; + +export class QueueManager { + protected app: Application; + protected connections: Map = new Map(); + protected static customDrivers: Map = new Map(); + + constructor(app: Application) { + this.app = app; + } + + public connection(name?: string): Queue { + name = name ?? this.getDefaultDriver(); + + if (!this.connections.has(name)) { + this.connections.set(name, this.resolve(name)); + } + + return this.connections.get(name)!; + } + + protected getDefaultDriver(): string { + return this.app.make('config').get("queue.default", "sync"); + } + + protected resolve(name: string): Queue { + if (QueueManager.customDrivers.has(name)) { + return QueueManager.customDrivers.get(name)!; + } + + const config = this.app.make('config').get(`queue.connections.${name}`); + + if (!config) { + throw new Error(`Queue connection [${name}] not configured.`); + } + + const driverMethod = `create${name.charAt(0).toUpperCase() + name.slice(1)}Driver`; + + if (typeof (this as any)[driverMethod] === "function") { + return (this as any)[driverMethod](config); + } + + throw new Error(`Unsupported driver [${name}].`); + } + + protected createSyncDriver(config: any): Queue { + return new SyncDriver(); + } + + protected createArrayDriver(config: any): Queue { + return new ArrayDriver(); + } + + public push(job: any, payload?: any, queue?: string): Promise { + return this.connection(queue).push(job, payload, queue); + } + + public later(delay: number, job: any, payload?: any, queue?: string): Promise { + return this.connection(queue).later(delay, job, payload, queue); + } + + public static addDriver(name: string, driver: Queue): void { + QueueManager.customDrivers.set(name, driver); + } + + public static dispatch(job: any, queue?: string): Promise { + return Application.getInstance().make('queue').push(job, undefined, queue); + } + + public static via(name: string): Queue { + return Application.getInstance().make('queue').connection(name); + } +} \ No newline at end of file diff --git a/packages/queue/src/Workers/QueueWorker.ts b/packages/queue/src/Workers/QueueWorker.ts new file mode 100644 index 0000000..95e2906 --- /dev/null +++ b/packages/queue/src/Workers/QueueWorker.ts @@ -0,0 +1,42 @@ +import { QueueManager } from '../QueueManager'; + +export class QueueWorker { + public async run(driver: string, queue?: string) { + const queueDriver = QueueManager.via(driver); + + while (true) { + const job = await queueDriver.pop(queue); + + if (job) { + try { + await job.handle(); + } catch (error) { + if (job.attempts && job.attempts > 0) { + job.attempts--; + await this.release(driver, job, job.backoff || 0); + } else { + this.markAsFailed(job); + if (job.failed) { + job.failed(error as Error); + } + } + console.error(error); + } + } else { + // If no job is found, wait for a second before trying again. + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + } + + protected async release(driver: string, job: any, delay: number) { + const queueDriver = QueueManager.via(driver); + await queueDriver.release(job, delay); + } + + protected markAsFailed(job: any) { + // For now, we'll just log the failed job. + // Later, we can move it to a dead-letter queue. + console.log('Job failed:', job); + } +} \ No newline at end of file diff --git a/packages/queue/src/index.ts b/packages/queue/src/index.ts index 5d2dc3c..a4076b5 100644 --- a/packages/queue/src/index.ts +++ b/packages/queue/src/index.ts @@ -1,4 +1,12 @@ -export * from './Drivers/MemoryDriver' +export * from './Contracts/Job' +export * from './Contracts/JobContract' +export * from './Contracts/Queue' +export * from './Contracts/QueueDriverContract' +export * from './Drivers/ArrayDriver' +export * from './Drivers/DatabaseDriver' export * from './Drivers/RedisDriver' +export * from './Drivers/SyncDriver' +export * from './Jobs/SendEmailJobs' export * from './Providers/QueueServiceProvider' export * from './QueueManager' +export * from './Workers/QueueWorker' diff --git a/packages/queue/tests/queue.test.ts b/packages/queue/tests/queue.test.ts new file mode 100644 index 0000000..573a982 --- /dev/null +++ b/packages/queue/tests/queue.test.ts @@ -0,0 +1,114 @@ +import { QueueManager } from '../src/QueueManager'; +import { JobContract } from '../src/Contracts/JobContract'; +import { QueueWorker } from '../src/Workers/QueueWorker'; + +class TestJob implements JobContract { + constructor(public payload: any) {} + + handle() { + console.log('Job handled', this.payload); + } + + serialize() { + return this.payload; + } +} + +class FailingJob implements JobContract { + public tries?: number; + public backoff?: number; + + constructor(public payload: any) {} + + handle() { + throw new Error('Job failed'); + } + + serialize() { + return this.payload; + } + + failed(error: Error) { + console.log('Job failed', error.message); + } +} + +import { ArrayDriver } from '../src/Drivers/ArrayDriver'; + +describe('Queue', () => { + beforeEach(() => { + // Reset drivers before each test + QueueManager.addDriver('array', new ArrayDriver()); + }); + + it('should dispatch a job to the sync driver', () => { + const job = new TestJob({ foo: 'bar' }); + const spy = jest.spyOn(job, 'handle'); + QueueManager.dispatch(job); + expect(spy).toHaveBeenCalled(); + }); + + it('should dispatch a job to the array driver', () => { + const job = new TestJob({ foo: 'bar' }); + QueueManager.dispatch(job, 'array'); + const driver = QueueManager.via('array'); + expect(driver.size()).toBe(1); + }); + + it('should process a job from the array driver', async () => { + const job = new TestJob({ foo: 'bar' }); + const spy = jest.spyOn(job, 'handle'); + QueueManager.dispatch(job, 'array'); + + const worker = new QueueWorker(); + const workerSpy = jest.spyOn(worker, 'run').mockImplementation(async (driver) => { + const queueDriver = QueueManager.via(driver); + const job = await queueDriver.pop(); + if (job) { + await job.handle(); + } + }); + + await worker.run('array'); + + expect(spy).toHaveBeenCalled(); + const driver = QueueManager.via('array'); + expect(driver.size()).toBe(0); + workerSpy.mockRestore(); + }); + + it('should retry a failed job', async () => { + const job = new FailingJob({ foo: 'bar' }); + job.tries = 2; + job.backoff = 1; + + QueueManager.dispatch(job, 'array'); + + const worker = new QueueWorker(); + const workerSpy = jest.spyOn(worker, 'run').mockImplementation(async (driver) => { + const queueDriver = QueueManager.via(driver); + let job = await queueDriver.pop(); + if (job) { + try { + await job.handle(); + } catch (error) { + if (job.attempts && job.attempts > 0) { + job.attempts--; + await queueDriver.release(job, job.backoff || 0); + } else { + if (job.failed) { + job.failed(error as Error); + } + } + } + } + }); + + await worker.run('array'); + + const driver = QueueManager.via('array'); + expect(driver.size()).toBe(1); + + workerSpy.mockRestore(); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cd4119..79083a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,6 +155,9 @@ importers: '@h3ravel/support': specifier: workspace:^ version: link:../../packages/support + '@h3ravel/view': + specifier: workspace:^ + version: link:../../packages/view cross-env: specifier: ^10.0.0 version: 10.0.0 @@ -282,9 +285,6 @@ importers: dotenv-expand: specifier: ^12.0.3 version: 12.0.3 - edge.js: - specifier: ^6.3.0 - version: 6.3.0 h3: specifier: 2.0.0-beta.4 version: 2.0.0-beta.4 @@ -458,6 +458,28 @@ importers: specifier: ^5.4.0 version: 5.8.3 + packages/view: + dependencies: + '@h3ravel/core': + specifier: workspace:* + version: link:../core + '@h3ravel/http': + specifier: workspace:* + version: link:../http + '@h3ravel/shared': + specifier: workspace:* + version: link:../shared + edge.js: + specifier: ^6.3.0 + version: 6.3.0 + devDependencies: + typescript: + specifier: ^5.0.0 + version: 5.9.2 + vitest: + specifier: ^2.0.0 + version: 2.1.9(@types/node@24.5.2) + packages: '@ampproject/remapping@2.3.0': @@ -861,102 +883,204 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.10': resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.10': resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.10': resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.10': resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.10': resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.10': resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.10': resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.10': resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.10': resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.10': resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.10': resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.10': resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} @@ -969,6 +1093,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} @@ -981,6 +1111,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.10': resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} @@ -993,24 +1129,48 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.10': resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.10': resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} @@ -1936,9 +2096,23 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -1950,18 +2124,33 @@ packages: vite: optional: true + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -2446,6 +2635,11 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.10: resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} @@ -3418,6 +3612,9 @@ packages: path@0.12.7: resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3945,10 +4142,18 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -4096,6 +4301,11 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4109,6 +4319,37 @@ packages: vite: optional: true + vite@5.4.20: + resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@7.1.7: resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4149,6 +4390,31 @@ packages: yaml: optional: true + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -5334,81 +5600,150 @@ snapshots: '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.10': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.10': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.10': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.10': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.10': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.10': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.10': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.10': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.10': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.10': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.10': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.10': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.10': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.10': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.10': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.10': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.10': optional: true '@esbuild/netbsd-arm64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.10': optional: true '@esbuild/openbsd-arm64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.10': optional: true '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.10': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.10': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.10': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.10': optional: true @@ -6519,6 +6854,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -6527,6 +6869,14 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 + '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@24.5.2))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 5.4.20(@types/node@24.5.2) + '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@24.5.2)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -6535,26 +6885,51 @@ snapshots: optionalDependencies: vite: 7.1.7(@types/node@24.5.2)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1) + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.19 + pathe: 1.1.2 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.19 pathe: 2.0.3 + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -7002,6 +7377,32 @@ snapshots: es-module-lexer@1.7.0: {} + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.10: optionalDependencies: '@esbuild/aix-ppc64': 0.25.10 @@ -8020,6 +8421,8 @@ snapshots: process: 0.11.10 util: 0.10.4 + pathe@1.1.2: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -8557,8 +8960,12 @@ snapshots: tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} + tinyspy@4.0.4: {} to-regex-range@5.0.1: @@ -8701,6 +9108,24 @@ snapshots: v8-compile-cache-lib@3.0.1: {} + vite-node@2.1.9(@types/node@24.5.2): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.20(@types/node@24.5.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@3.2.4(@types/node@24.5.2)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: cac: 6.7.14 @@ -8733,6 +9158,15 @@ snapshots: - supports-color - typescript + vite@5.4.20(@types/node@24.5.2): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.52.3 + optionalDependencies: + '@types/node': 24.5.2 + fsevents: 2.3.3 + vite@7.1.7(@types/node@24.5.2)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: esbuild: 0.25.10 @@ -8748,6 +9182,41 @@ snapshots: tsx: 4.20.5 yaml: 2.8.1 + vitest@2.1.9(@types/node@24.5.2): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.5.2)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.20(@types/node@24.5.2) + vite-node: 2.1.9(@types/node@24.5.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.5.2 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@3.2.4(@types/node@24.5.2)(jiti@2.5.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2