wip library to store cold objects in s3, warm objects on disk, and hot objects in memory
nodejs typescript

init commit

+136
.env.example
··· 1 + # Tiered Storage Configuration 2 + # Copy this file to .env and configure for your environment 3 + 4 + # ============================================================================ 5 + # S3 Configuration (Cold Tier - Required) 6 + # ============================================================================ 7 + 8 + # AWS S3 bucket name (or S3-compatible bucket) 9 + S3_BUCKET=tiered-storage-cache 10 + 11 + # Optional: Separate bucket for metadata (RECOMMENDED for production!) 12 + # When set, metadata is stored as separate JSON objects instead of S3 object metadata. 13 + # This allows fast, cheap metadata updates without copying entire objects. 14 + # Leave blank to store metadata in S3 object metadata fields (slower, more expensive). 15 + S3_METADATA_BUCKET=tiered-storage-metadata 16 + 17 + # AWS region 18 + S3_REGION=us-east-1 19 + 20 + # S3 endpoint (optional - for S3-compatible services like R2, Minio) 21 + # Leave blank for AWS S3 22 + # For Cloudflare R2: https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com 23 + # For MinIO: http://localhost:9000 24 + # For other S3-compatible: https://s3.your-service.com 25 + # S3_ENDPOINT= 26 + 27 + # Force path-style URLs (usually needed for S3-compatible services) 28 + # Default: true (recommended for most S3-compatible services) 29 + # Set to false only if your service requires virtual-host-style URLs 30 + # S3_FORCE_PATH_STYLE=true 31 + 32 + # AWS credentials 33 + # If not provided, uses default AWS credential chain 34 + # (environment variables, ~/.aws/credentials, IAM roles, etc.) 35 + AWS_ACCESS_KEY_ID=your_access_key_id 36 + AWS_SECRET_ACCESS_KEY=your_secret_access_key 37 + 38 + # ============================================================================ 39 + # Cloudflare R2 Example Configuration 40 + # ============================================================================ 41 + # Uncomment these to use Cloudflare R2 instead of AWS S3: 42 + # 43 + # S3_BUCKET=my-r2-bucket 44 + # S3_METADATA_BUCKET=my-r2-metadata-bucket 45 + # S3_REGION=auto 46 + # S3_ENDPOINT=https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com 47 + # AWS_ACCESS_KEY_ID=your_r2_access_key_id 48 + # AWS_SECRET_ACCESS_KEY=your_r2_secret_access_key 49 + 50 + # ============================================================================ 51 + # Memory Tier Configuration (Hot) 52 + # ============================================================================ 53 + 54 + # Maximum size in bytes for hot (memory) tier 55 + # Default: 100MB 56 + MEMORY_MAX_SIZE_BYTES=104857600 57 + 58 + # Maximum number of items in hot tier 59 + # Optional - if not set, only size limit applies 60 + MEMORY_MAX_ITEMS=1000 61 + 62 + # ============================================================================ 63 + # Disk Tier Configuration (Warm) 64 + # ============================================================================ 65 + 66 + # Directory for warm tier cache 67 + # Default: ./cache/warm 68 + DISK_WARM_DIRECTORY=./cache/warm 69 + 70 + # Maximum size in bytes for warm tier 71 + # Optional - if not set, no size limit 72 + DISK_WARM_MAX_SIZE_BYTES=10737418240 73 + 74 + # Eviction policy when size limit reached 75 + # Options: lru, fifo, size 76 + # Default: lru 77 + DISK_WARM_EVICTION_POLICY=lru 78 + 79 + # ============================================================================ 80 + # Storage Options 81 + # ============================================================================ 82 + 83 + # Enable compression (gzip) 84 + # Default: false 85 + COMPRESSION_ENABLED=true 86 + 87 + # Default TTL in milliseconds 88 + # Optional - if not set, data never expires 89 + # Example: 1209600000 = 14 days 90 + DEFAULT_TTL_MS=1209600000 91 + 92 + # Promotion strategy: 'eager' or 'lazy' 93 + # eager: Automatically promote data to upper tiers on read 94 + # lazy: Only promote on explicit bootstrap or write 95 + # Default: lazy 96 + PROMOTION_STRATEGY=lazy 97 + 98 + # ============================================================================ 99 + # Bootstrap Configuration 100 + # ============================================================================ 101 + 102 + # Number of items to load into hot tier on bootstrap 103 + # Optional - if not set, loads all items 104 + BOOTSTRAP_HOT_LIMIT=1000 105 + 106 + # Number of days to look back when bootstrapping warm tier 107 + # Example: 7 = only load items accessed in last 7 days 108 + BOOTSTRAP_WARM_DAYS=7 109 + 110 + # Maximum items to load into warm tier on bootstrap 111 + # Optional - if not set, loads all matching items 112 + BOOTSTRAP_WARM_LIMIT=10000 113 + 114 + # ============================================================================ 115 + # Performance Tuning 116 + # ============================================================================ 117 + 118 + # Maximum concurrent operations for bootstrap 119 + # Default: 10 120 + BOOTSTRAP_CONCURRENCY=10 121 + 122 + # Timeout for tier operations in milliseconds 123 + # Default: 30000 (30 seconds) 124 + TIER_OPERATION_TIMEOUT_MS=30000 125 + 126 + # ============================================================================ 127 + # Monitoring & Observability 128 + # ============================================================================ 129 + 130 + # Enable statistics tracking 131 + # Default: true 132 + STATS_ENABLED=true 133 + 134 + # Log level: debug, info, warn, error 135 + # Default: info 136 + LOG_LEVEL=info
+28
.eslintrc.cjs
··· 1 + module.exports = { 2 + parser: '@typescript-eslint/parser', 3 + parserOptions: { 4 + ecmaVersion: 2022, 5 + sourceType: 'module', 6 + project: './tsconfig.json', 7 + }, 8 + plugins: ['@typescript-eslint'], 9 + extends: [ 10 + 'eslint:recommended', 11 + '@typescript-eslint/recommended', 12 + '@typescript-eslint/recommended-requiring-type-checking', 13 + ], 14 + root: true, 15 + env: { 16 + node: true, 17 + es2022: true, 18 + }, 19 + rules: { 20 + '@typescript-eslint/no-unused-vars': 'error', 21 + '@typescript-eslint/explicit-function-return-type': 'warn', 22 + '@typescript-eslint/no-explicit-any': 'warn', 23 + '@typescript-eslint/prefer-nullish-coalescing': 'error', 24 + '@typescript-eslint/prefer-optional-chain': 'error', 25 + '@typescript-eslint/no-floating-promises': 'error', 26 + '@typescript-eslint/await-thenable': 'error', 27 + }, 28 + };
+17
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + .env 4 + *.log 5 + .DS_Store 6 + 7 + # Example cache directories 8 + example-cache/* 9 + example-cache/ 10 + cache/* 11 + cache/ 12 + 13 + # Test cache directories 14 + test-cache/ 15 + 16 + # Build artifacts 17 + *.tsbuildinfo
+574
README.md
··· 1 + # Tiered Storage 2 + 3 + A lightweight, pluggable tiered storage library that orchestrates caching across hot (memory), warm (disk/database), and cold (S3/object storage) tiers. 4 + 5 + ## Features 6 + 7 + - **Cascading Containment Model**: Hot ⊆ Warm ⊆ Cold (lower tiers contain all data from upper tiers) 8 + - **Pluggable Backends**: Bring your own Redis, Postgres, SQLite, or use built-in implementations 9 + - **Automatic Promotion**: Configurable eager/lazy promotion strategies for cache warming 10 + - **TTL Management**: Per-key TTL with automatic expiration and renewal 11 + - **Prefix Invalidation**: Efficiently delete groups of keys by prefix 12 + - **Bootstrap Support**: Warm up caches from lower tiers on startup 13 + - **Compression**: Optional transparent gzip compression 14 + - **TypeScript First**: Full type safety with comprehensive TSDoc comments 15 + - **Zero Forced Dependencies**: Only require what you use 16 + 17 + ## Installation 18 + 19 + ```bash 20 + npm install tiered-storage 21 + # or 22 + bun add tiered-storage 23 + ``` 24 + 25 + ## Quick Start 26 + 27 + ```typescript 28 + import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage'; 29 + 30 + const storage = new TieredStorage({ 31 + tiers: { 32 + hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB 33 + warm: new DiskStorageTier({ directory: './cache' }), 34 + cold: new S3StorageTier({ 35 + bucket: 'my-bucket', 36 + region: 'us-east-1', 37 + credentials: { 38 + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, 39 + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, 40 + }, 41 + }), 42 + }, 43 + compression: true, 44 + defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days 45 + promotionStrategy: 'lazy', 46 + }); 47 + 48 + // Store data (cascades to all tiers) 49 + await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' }); 50 + 51 + // Retrieve data (bubbles up from cold → warm → hot) 52 + const user = await storage.get('user:123'); 53 + 54 + // Get data with metadata and source tier 55 + const result = await storage.getWithMetadata('user:123'); 56 + console.log(`Served from ${result.source}`); // 'hot', 'warm', or 'cold' 57 + 58 + // Invalidate all keys with prefix 59 + await storage.invalidate('user:'); 60 + 61 + // Renew TTL 62 + await storage.touch('user:123'); 63 + ``` 64 + 65 + ## Core Concepts 66 + 67 + ### Cascading Containment Model 68 + 69 + ``` 70 + ┌──────────────────────────────────────────────────────┐ 71 + │ Cold Storage (S3/Object Storage) │ 72 + │ • Contains ALL objects (source of truth) │ 73 + │ • Slowest access, unlimited capacity │ 74 + ├──────────────────────────────────────────────────────┤ 75 + │ Warm Storage (Disk/Database) │ 76 + │ • Contains ALL hot objects + additional warm objects │ 77 + │ • Medium access speed, large capacity │ 78 + ├──────────────────────────────────────────────────────┤ 79 + │ Hot Storage (Memory) │ 80 + │ • Contains only the hottest objects │ 81 + │ • Fastest access, limited capacity │ 82 + └──────────────────────────────────────────────────────┘ 83 + ``` 84 + 85 + **Write Strategy (Cascading Down):** 86 + - Write to **hot** → also writes to **warm** and **cold** 87 + - Write to **warm** → also writes to **cold** 88 + - Write to **cold** → only writes to **cold** 89 + 90 + **Read Strategy (Bubbling Up):** 91 + - Check **hot** first → if miss, check **warm** → if miss, check **cold** 92 + - On cache miss, optionally promote data up through tiers 93 + 94 + ### Selective Tier Placement 95 + 96 + For use cases like static site hosting, you can control which files go into which tiers: 97 + 98 + ```typescript 99 + // Small, critical file (index.html) - store in all tiers for instant serving 100 + await storage.set('site:abc/index.html', htmlContent); 101 + 102 + // Large file (video) - skip hot tier to avoid memory bloat 103 + await storage.set('site:abc/video.mp4', videoData, { skipTiers: ['hot'] }); 104 + 105 + // Medium files (images, CSS) - skip hot, use warm + cold 106 + await storage.set('site:abc/style.css', cssData, { skipTiers: ['hot'] }); 107 + ``` 108 + 109 + This pattern ensures: 110 + - Hot tier stays small and fast (only critical files) 111 + - Warm tier caches everything (all site files on disk) 112 + - Cold tier is source of truth (all data) 113 + 114 + ## API Reference 115 + 116 + ### `TieredStorage` 117 + 118 + Main orchestrator class for tiered storage. 119 + 120 + #### Constructor 121 + 122 + ```typescript 123 + new TieredStorage<T>(config: TieredStorageConfig) 124 + ``` 125 + 126 + **Config Options:** 127 + 128 + ```typescript 129 + interface TieredStorageConfig { 130 + tiers: { 131 + hot?: StorageTier; // Optional: fastest tier (memory/Redis) 132 + warm?: StorageTier; // Optional: medium tier (disk/SQLite/Postgres) 133 + cold: StorageTier; // Required: slowest tier (S3/object storage) 134 + }; 135 + compression?: boolean; // Auto-compress before storing (default: false) 136 + defaultTTL?: number; // Default TTL in milliseconds 137 + promotionStrategy?: 'eager' | 'lazy'; // When to promote to upper tiers (default: 'lazy') 138 + serialization?: { // Custom serialization (default: JSON) 139 + serialize: (data: unknown) => Promise<Uint8Array>; 140 + deserialize: (data: Uint8Array) => Promise<unknown>; 141 + }; 142 + } 143 + ``` 144 + 145 + #### Methods 146 + 147 + **`get(key: string): Promise<T | null>`** 148 + 149 + Retrieve data for a key. Returns null if not found or expired. 150 + 151 + **`getWithMetadata(key: string): Promise<StorageResult<T> | null>`** 152 + 153 + Retrieve data with metadata and source tier information. 154 + 155 + ```typescript 156 + const result = await storage.getWithMetadata('user:123'); 157 + console.log(result.data); // The actual data 158 + console.log(result.source); // 'hot' | 'warm' | 'cold' 159 + console.log(result.metadata); // Metadata (size, timestamps, TTL, etc.) 160 + ``` 161 + 162 + **`set(key: string, data: T, options?: SetOptions): Promise<SetResult>`** 163 + 164 + Store data with optional configuration. 165 + 166 + ```typescript 167 + await storage.set('key', data, { 168 + ttl: 24 * 60 * 60 * 1000, // Custom TTL (24 hours) 169 + metadata: { contentType: 'application/json' }, // Custom metadata 170 + skipTiers: ['hot'], // Skip specific tiers 171 + }); 172 + ``` 173 + 174 + **`delete(key: string): Promise<void>`** 175 + 176 + Delete data from all tiers. 177 + 178 + **`exists(key: string): Promise<boolean>`** 179 + 180 + Check if a key exists (and hasn't expired). 181 + 182 + **`touch(key: string, ttlMs?: number): Promise<void>`** 183 + 184 + Renew TTL for a key. Useful for "keep alive" behavior. 185 + 186 + **`invalidate(prefix: string): Promise<number>`** 187 + 188 + Delete all keys matching a prefix. Returns number of keys deleted. 189 + 190 + ```typescript 191 + await storage.invalidate('user:'); // Delete all user keys 192 + await storage.invalidate('site:abc/'); // Delete all files for site 'abc' 193 + await storage.invalidate(''); // Delete everything 194 + ``` 195 + 196 + **`listKeys(prefix?: string): AsyncIterableIterator<string>`** 197 + 198 + List all keys, optionally filtered by prefix. 199 + 200 + ```typescript 201 + for await (const key of storage.listKeys('user:')) { 202 + console.log(key); // 'user:123', 'user:456', etc. 203 + } 204 + ``` 205 + 206 + **`getStats(): Promise<AllTierStats>`** 207 + 208 + Get aggregated statistics across all tiers. 209 + 210 + ```typescript 211 + const stats = await storage.getStats(); 212 + console.log(stats.hot); // Hot tier stats (size, items, hits, misses) 213 + console.log(stats.hitRate); // Overall hit rate (0-1) 214 + ``` 215 + 216 + **`bootstrapHot(limit?: number): Promise<number>`** 217 + 218 + Load most frequently accessed items from warm into hot. Returns number of items loaded. 219 + 220 + ```typescript 221 + // On server startup: warm up hot tier 222 + const loaded = await storage.bootstrapHot(1000); // Load top 1000 items 223 + console.log(`Loaded ${loaded} items into hot tier`); 224 + ``` 225 + 226 + **`bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number>`** 227 + 228 + Load recent items from cold into warm. Returns number of items loaded. 229 + 230 + ```typescript 231 + // Load items accessed in last 7 days 232 + const loaded = await storage.bootstrapWarm({ 233 + sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), 234 + limit: 10000, 235 + }); 236 + ``` 237 + 238 + **`export(): Promise<StorageSnapshot>`** 239 + 240 + Export metadata snapshot for backup or migration. 241 + 242 + **`import(snapshot: StorageSnapshot): Promise<void>`** 243 + 244 + Import metadata snapshot. 245 + 246 + **`clear(): Promise<void>`** 247 + 248 + Clear all data from all tiers. ⚠️ Use with extreme caution! 249 + 250 + **`clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void>`** 251 + 252 + Clear a specific tier. 253 + 254 + ### Built-in Storage Tiers 255 + 256 + #### `MemoryStorageTier` 257 + 258 + In-memory storage using TinyLRU for efficient LRU eviction. 259 + 260 + ```typescript 261 + import { MemoryStorageTier } from 'tiered-storage'; 262 + 263 + const tier = new MemoryStorageTier({ 264 + maxSizeBytes: 100 * 1024 * 1024, // 100MB 265 + maxItems: 1000, // Optional: max number of items 266 + }); 267 + ``` 268 + 269 + **Features:** 270 + - Battle-tested TinyLRU library 271 + - Automatic LRU eviction 272 + - Size-based and count-based limits 273 + - Single process only (not distributed) 274 + 275 + #### `DiskStorageTier` 276 + 277 + Filesystem-based storage with `.meta` files. 278 + 279 + ```typescript 280 + import { DiskStorageTier } from 'tiered-storage'; 281 + 282 + const tier = new DiskStorageTier({ 283 + directory: './cache', 284 + maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB (optional) 285 + evictionPolicy: 'lru', // 'lru' | 'fifo' | 'size' 286 + }); 287 + ``` 288 + 289 + **Features:** 290 + - Human-readable file structure 291 + - Optional size-based eviction 292 + - Three eviction policies: LRU, FIFO, size-based 293 + - Atomic writes with `.meta` files 294 + - Zero external dependencies 295 + 296 + **File structure:** 297 + ``` 298 + cache/ 299 + ├── user%3A123 # Data file (encoded key) 300 + ├── user%3A123.meta # Metadata JSON 301 + ├── site%3Aabc%2Findex.html 302 + └── site%3Aabc%2Findex.html.meta 303 + ``` 304 + 305 + #### `S3StorageTier` 306 + 307 + AWS S3 or S3-compatible object storage. 308 + 309 + ```typescript 310 + import { S3StorageTier } from 'tiered-storage'; 311 + 312 + // AWS S3 with separate metadata bucket (RECOMMENDED!) 313 + const tier = new S3StorageTier({ 314 + bucket: 'my-data-bucket', 315 + metadataBucket: 'my-metadata-bucket', // Stores metadata separately for fast updates 316 + region: 'us-east-1', 317 + credentials: { 318 + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, 319 + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, 320 + }, 321 + prefix: 'cache/', // Optional key prefix 322 + }); 323 + 324 + // Cloudflare R2 with metadata bucket 325 + const r2Tier = new S3StorageTier({ 326 + bucket: 'my-r2-data-bucket', 327 + metadataBucket: 'my-r2-metadata-bucket', 328 + region: 'auto', 329 + endpoint: 'https://account-id.r2.cloudflarestorage.com', 330 + credentials: { 331 + accessKeyId: process.env.R2_ACCESS_KEY_ID!, 332 + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, 333 + }, 334 + }); 335 + 336 + // Without metadata bucket (legacy mode - slower, more expensive) 337 + const legacyTier = new S3StorageTier({ 338 + bucket: 'my-bucket', 339 + region: 'us-east-1', 340 + // No metadataBucket - metadata stored in S3 object metadata fields 341 + }); 342 + ``` 343 + 344 + **Features:** 345 + - Compatible with AWS S3, Cloudflare R2, MinIO, and other S3-compatible services 346 + - **Separate metadata bucket support (RECOMMENDED)** - stores metadata as JSON objects for fast, cheap updates 347 + - Legacy mode: metadata in S3 object metadata fields (requires object copying for updates) 348 + - Efficient batch deletions (up to 1000 keys per request) 349 + - Optional key prefixing for multi-tenant scenarios 350 + - Typically used as cold tier (source of truth) 351 + 352 + **⚠️ Important:** Without `metadataBucket`, updating metadata (e.g., access counts) requires copying the entire object, which is slow and expensive for large files. Use a separate metadata bucket in production! 353 + 354 + ## Usage Patterns 355 + 356 + ### Pattern 1: Simple Single-Server Setup 357 + 358 + ```typescript 359 + import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage'; 360 + 361 + const storage = new TieredStorage({ 362 + tiers: { 363 + hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), 364 + warm: new DiskStorageTier({ directory: './cache' }), 365 + cold: new DiskStorageTier({ directory: './storage' }), 366 + }, 367 + compression: true, 368 + defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days 369 + }); 370 + 371 + await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' }); 372 + const user = await storage.get('user:123'); 373 + ``` 374 + 375 + ### Pattern 2: Static Site Hosting (wisp.place-style) 376 + 377 + ```typescript 378 + import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage'; 379 + 380 + const storage = new TieredStorage({ 381 + tiers: { 382 + hot: new MemoryStorageTier({ 383 + maxSizeBytes: 100 * 1024 * 1024, // 100MB 384 + maxItems: 500, 385 + }), 386 + warm: new DiskStorageTier({ 387 + directory: './cache/sites', 388 + maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB 389 + }), 390 + // Cold tier is PDS (fetched on demand via custom tier implementation) 391 + }, 392 + compression: true, 393 + defaultTTL: 14 * 24 * 60 * 60 * 1000, 394 + promotionStrategy: 'lazy', // Don't auto-promote large files to hot 395 + }); 396 + 397 + // Store index.html in all tiers (fast access) 398 + await storage.set(`${did}/${rkey}/index.html`, htmlBuffer, { 399 + metadata: { mimeType: 'text/html', encoding: 'gzip' }, 400 + }); 401 + 402 + // Store large files only in warm + cold (skip hot) 403 + await storage.set(`${did}/${rkey}/video.mp4`, videoBuffer, { 404 + skipTiers: ['hot'], 405 + metadata: { mimeType: 'video/mp4' }, 406 + }); 407 + 408 + // Get file with source tracking 409 + const result = await storage.getWithMetadata(`${did}/${rkey}/index.html`); 410 + console.log(`Served from ${result.source}`); // Likely 'hot' for index.html 411 + 412 + // Invalidate entire site 413 + await storage.invalidate(`${did}/${rkey}/`); 414 + 415 + // Renew TTL when site is accessed 416 + await storage.touch(`${did}/${rkey}/index.html`); 417 + ``` 418 + 419 + ### Pattern 3: Custom Backend (SQLite) 420 + 421 + Implement the `StorageTier` interface to use any backend: 422 + 423 + ```typescript 424 + import { StorageTier, StorageMetadata, TierStats } from 'tiered-storage'; 425 + import Database from 'better-sqlite3'; 426 + 427 + class SQLiteStorageTier implements StorageTier { 428 + private db: Database.Database; 429 + 430 + constructor(dbPath: string) { 431 + this.db = new Database(dbPath); 432 + this.db.exec(` 433 + CREATE TABLE IF NOT EXISTS cache ( 434 + key TEXT PRIMARY KEY, 435 + data BLOB NOT NULL, 436 + metadata TEXT NOT NULL 437 + ) 438 + `); 439 + } 440 + 441 + async get(key: string): Promise<Uint8Array | null> { 442 + const row = this.db.prepare('SELECT data FROM cache WHERE key = ?').get(key); 443 + return row ? new Uint8Array(row.data) : null; 444 + } 445 + 446 + async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> { 447 + this.db.prepare('INSERT OR REPLACE INTO cache (key, data, metadata) VALUES (?, ?, ?)') 448 + .run(key, Buffer.from(data), JSON.stringify(metadata)); 449 + } 450 + 451 + async delete(key: string): Promise<void> { 452 + this.db.prepare('DELETE FROM cache WHERE key = ?').run(key); 453 + } 454 + 455 + async exists(key: string): Promise<boolean> { 456 + const row = this.db.prepare('SELECT 1 FROM cache WHERE key = ?').get(key); 457 + return !!row; 458 + } 459 + 460 + async *listKeys(prefix?: string): AsyncIterableIterator<string> { 461 + const query = prefix 462 + ? this.db.prepare('SELECT key FROM cache WHERE key LIKE ?') 463 + : this.db.prepare('SELECT key FROM cache'); 464 + 465 + const rows = prefix ? query.all(`${prefix}%`) : query.all(); 466 + 467 + for (const row of rows) { 468 + yield row.key; 469 + } 470 + } 471 + 472 + async deleteMany(keys: string[]): Promise<void> { 473 + const placeholders = keys.map(() => '?').join(','); 474 + this.db.prepare(`DELETE FROM cache WHERE key IN (${placeholders})`).run(...keys); 475 + } 476 + 477 + async getMetadata(key: string): Promise<StorageMetadata | null> { 478 + const row = this.db.prepare('SELECT metadata FROM cache WHERE key = ?').get(key); 479 + return row ? JSON.parse(row.metadata) : null; 480 + } 481 + 482 + async setMetadata(key: string, metadata: StorageMetadata): Promise<void> { 483 + this.db.prepare('UPDATE cache SET metadata = ? WHERE key = ?') 484 + .run(JSON.stringify(metadata), key); 485 + } 486 + 487 + async getStats(): Promise<TierStats> { 488 + const row = this.db.prepare('SELECT COUNT(*) as count, SUM(LENGTH(data)) as bytes FROM cache').get(); 489 + return { items: row.count, bytes: row.bytes || 0 }; 490 + } 491 + 492 + async clear(): Promise<void> { 493 + this.db.prepare('DELETE FROM cache').run(); 494 + } 495 + } 496 + 497 + // Use it 498 + const storage = new TieredStorage({ 499 + tiers: { 500 + warm: new SQLiteStorageTier('./cache.db'), 501 + cold: new DiskStorageTier({ directory: './storage' }), 502 + }, 503 + }); 504 + ``` 505 + 506 + ## Running Examples 507 + 508 + ### Interactive Demo Server 509 + 510 + Run a **real HTTP server** that serves the example site using tiered storage: 511 + 512 + ```bash 513 + # Configure S3 credentials first (copy .env.example to .env and fill in) 514 + cp .env.example .env 515 + 516 + # Start the demo server 517 + bun run serve 518 + ``` 519 + 520 + Then visit: 521 + - **http://localhost:3000/** - The demo site served from tiered storage 522 + - **http://localhost:3000/admin/stats** - Live cache statistics dashboard 523 + 524 + Watch the console to see which tier serves each request: 525 + - 🔥 **Hot tier (memory)** - index.html served instantly 526 + - 💾 **Warm tier (disk)** - Other pages served from disk cache 527 + - ☁️ **Cold tier (S3)** - First access fetches from S3, then cached 528 + 529 + ### Command-Line Examples 530 + 531 + Or run the non-interactive examples: 532 + 533 + ```bash 534 + bun run example 535 + ``` 536 + 537 + The examples include: 538 + - **Basic CRUD operations** with statistics tracking 539 + - **Static site hosting** using the real site in `example-site/` directory 540 + - **Bootstrap demonstrations** (warming caches from lower tiers) 541 + - **Promotion strategy comparisons** (eager vs lazy) 542 + 543 + The `example-site/` directory contains a complete static website with: 544 + - `index.html` - Stored in hot + warm + cold (instant serving) 545 + - `about.html`, `docs.html` - Stored in warm + cold (skips hot) 546 + - `style.css`, `script.js` - Stored in warm + cold (skips hot) 547 + 548 + This demonstrates the exact pattern you'd use for wisp.place: critical files in memory, everything else on disk/S3. 549 + 550 + ## Testing 551 + 552 + ```bash 553 + bun test 554 + ``` 555 + 556 + ## Development 557 + 558 + ```bash 559 + # Install dependencies 560 + bun install 561 + 562 + # Type check 563 + bun run check 564 + 565 + # Build 566 + bun run build 567 + 568 + # Run tests 569 + bun test 570 + 571 + ``` 572 + ## License 573 + 574 + MIT
+27
agents.md
··· 1 + You are working on a project that stores cold objects in s3, warm objects on disk, and hot objects in memory. It serves APIs for library consumers to use. 2 + 3 + ## Package management 4 + - Use bun for package management (bun install) 5 + - Use npm run test to test 6 + - Use npm run lint to lint 7 + - Use npm run check to typecheck 8 + 9 + Please test and typecheck always whenever you think you are done. 10 + 11 + ## Code style 12 + - Use tabs for indentation, spaces allowed for diagrams in comments 13 + - Use single quotes and add trailing commas 14 + - Use template literals for user-facing strings and error messages 15 + 16 + ## Commenting 17 + Add JSDoc comments to all new exported functions, methods, classes, fields, and enums 18 + JSDoc should include proper annotations: 19 + - use @param for parameters (no dashes after param names) 20 + - use @returns for return values 21 + - use @throws for exceptions when applicable 22 + - keep descriptions concise but informative 23 + 24 + ## Misc 25 + the .research/ directory serves as a workspace for temporary experiments, analysis, and planning materials. create it if necessary (it's gitignored). this directory may contain cloned repositories or other reference materials that can help inform implementation decisions 26 + 27 + **don't make assumptions or speculate about code, plans, or requirements without exploring first; pause and ask for clarification when you're still unsure after looking into it**
+713
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "tiered-storage", 7 + "dependencies": { 8 + "@aws-sdk/client-s3": "^3.500.0", 9 + "hono": "^4.10.7", 10 + "mime-types": "^3.0.2", 11 + "tiny-lru": "^11.0.0", 12 + }, 13 + "devDependencies": { 14 + "@types/node": "^24.10.1", 15 + "@typescript-eslint/eslint-plugin": "^8.48.1", 16 + "@typescript-eslint/parser": "^8.48.1", 17 + "eslint": "^8.0.0", 18 + "tsx": "^4.0.0", 19 + "typescript": "^5.3.0", 20 + "vitest": "^4.0.15", 21 + }, 22 + }, 23 + }, 24 + "packages": { 25 + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], 26 + 27 + "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], 28 + 29 + "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], 30 + 31 + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], 32 + 33 + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], 34 + 35 + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], 36 + 37 + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], 38 + 39 + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.946.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/credential-provider-node": "3.946.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", "@aws-sdk/middleware-flexible-checksums": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-sdk-s3": "3.946.0", "@aws-sdk/middleware-ssec": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-Y3ww3yd1wzmS2r3qgH3jg4MxCTdeNrae2J1BmdV+IW/2R2gFWJva5U5GbS6KUSUxanJBRG7gd8uOIi1b0EMOng=="], 40 + 41 + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.946.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kGAs5iIVyUz4p6TX3pzG5q3cNxXnVpC4pwRC6DCSaSv9ozyPjc2d74FsK4fZ+J+ejtvCdJk72uiuQtWJc86Wuw=="], 42 + 43 + "@aws-sdk/core": ["@aws-sdk/core@3.946.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg=="], 44 + 45 + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-P4l+K6wX1tf8LmWUvZofdQ+BgCNyk6Tb9u1H10npvqpuCD+dCM4pXIBq3PQcv/juUBOvLGGREo+Govuh3lfD0Q=="], 46 + 47 + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-/zeOJ6E7dGZQ/l2k7KytEoPJX0APIhwt0A79hPf/bUpMF4dDs2P6JmchDrotk0a0Y/MIdNF8sBQ/MEOPnBiYoQ=="], 48 + 49 + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/credential-provider-env": "3.946.0", "@aws-sdk/credential-provider-http": "3.946.0", "@aws-sdk/credential-provider-login": "3.946.0", "@aws-sdk/credential-provider-process": "3.946.0", "@aws-sdk/credential-provider-sso": "3.946.0", "@aws-sdk/credential-provider-web-identity": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Pdgcra3RivWj/TuZmfFaHbqsvvgnSKO0CxlRUMMr0PgBiCnUhyl+zBktdNOeGsOPH2fUzQpYhcUjYUgVSdcSDQ=="], 50 + 51 + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5iqLNc15u2Zx+7jOdQkIbP62N7n2031tw5hkmIG0DLnozhnk64osOh2CliiOE9x3c4P9Pf4frAwgyy9GzNTk2g=="], 52 + 53 + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.946.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.946.0", "@aws-sdk/credential-provider-http": "3.946.0", "@aws-sdk/credential-provider-ini": "3.946.0", "@aws-sdk/credential-provider-process": "3.946.0", "@aws-sdk/credential-provider-sso": "3.946.0", "@aws-sdk/credential-provider-web-identity": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-I7URUqnBPng1a5y81OImxrwERysZqMBREG6svhhGeZgxmqcpAZ8z5ywILeQXdEOCuuES8phUp/ojzxFjPXp/eA=="], 54 + 55 + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GtGHX7OGqIeVQ3DlVm5RRF43Qmf3S1+PLJv9svrdvAhAdy2bUb044FdXXqrtSsIfpzTKlHgQUiRo5MWLd35Ntw=="], 56 + 57 + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.946.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.946.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/token-providers": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-LeGSSt2V5iwYey1ENGY75RmoDP3bA2iE/py8QBKW8EDA8hn74XBLkprhrK5iccOvU3UGWY8WrEKFAFGNjJOL9g=="], 58 + 59 + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ocBCvjWfkbjxElBI1QUxOnHldsNhoU0uOICFvuRDAZAoxvypJHN3m5BJkqb7gqorBbcv3LRgmBdEnWXOAvq+7Q=="], 60 + 61 + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg=="], 62 + 63 + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA=="], 64 + 65 + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.946.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-HJA7RIWsnxcChyZ1hNF/3JICkYCqDonxoeG8FkrmLRBknZ8WVdJiPD420/UwrWaa5F2MuTDA92jxk77rI09h1w=="], 66 + 67 + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], 68 + 69 + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw=="], 70 + 71 + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], 72 + 73 + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], 74 + 75 + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-0UTFmFd8PX2k/jLu/DBmR+mmLQWAtUGHYps9Rjx3dcXNwaMLaa/39NoV3qn7Dwzfpqc6JZlZzBk+NDOCJIHW9g=="], 76 + 77 + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA=="], 78 + 79 + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.7", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw=="], 80 + 81 + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.946.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-rjAtEguukeW8mlyEQMQI56vxFoyWlaNwowmz1p1rav948SUjtrzjHAp4TOQWhibb7AR7BUTHBCgIcyCRjBEf4g=="], 82 + 83 + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], 84 + 85 + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.946.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-61FZ685lKiJuQ06g6U7K3PL9EwKCxNm51wNlxyKV57nnl1GrLD0NC8O3/hDNkCQLNBArT9y3IXl2H7TtIxP8Jg=="], 86 + 87 + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-a5c+rM6CUPX2ExmUZ3DlbLlS5rQr4tbdoGcgBsjnAHiYx8MuMNAI+8M7wfjF13i2yvUQj5WEIddvLpayfEZj9g=="], 88 + 89 + "@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], 90 + 91 + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], 92 + 93 + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], 94 + 95 + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="], 96 + 97 + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], 98 + 99 + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.946.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q=="], 100 + 101 + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], 102 + 103 + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="], 104 + 105 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="], 106 + 107 + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="], 108 + 109 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.1", "", { "os": "android", "cpu": "arm64" }, "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ=="], 110 + 111 + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.1", "", { "os": "android", "cpu": "x64" }, "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ=="], 112 + 113 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ=="], 114 + 115 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ=="], 116 + 117 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg=="], 118 + 119 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ=="], 120 + 121 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA=="], 122 + 123 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q=="], 124 + 125 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw=="], 126 + 127 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg=="], 128 + 129 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA=="], 130 + 131 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ=="], 132 + 133 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ=="], 134 + 135 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw=="], 136 + 137 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA=="], 138 + 139 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ=="], 140 + 141 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.1", "", { "os": "none", "cpu": "x64" }, "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg=="], 142 + 143 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g=="], 144 + 145 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg=="], 146 + 147 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg=="], 148 + 149 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA=="], 150 + 151 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg=="], 152 + 153 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ=="], 154 + 155 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="], 156 + 157 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 158 + 159 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 160 + 161 + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], 162 + 163 + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], 164 + 165 + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], 166 + 167 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 168 + 169 + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], 170 + 171 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 172 + 173 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 174 + 175 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 176 + 177 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 178 + 179 + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="], 180 + 181 + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="], 182 + 183 + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="], 184 + 185 + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="], 186 + 187 + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="], 188 + 189 + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="], 190 + 191 + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="], 192 + 193 + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="], 194 + 195 + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="], 196 + 197 + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="], 198 + 199 + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="], 200 + 201 + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="], 202 + 203 + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="], 204 + 205 + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="], 206 + 207 + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="], 208 + 209 + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="], 210 + 211 + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="], 212 + 213 + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="], 214 + 215 + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="], 216 + 217 + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="], 218 + 219 + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="], 220 + 221 + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="], 222 + 223 + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], 224 + 225 + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="], 226 + 227 + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="], 228 + 229 + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], 230 + 231 + "@smithy/core": ["@smithy/core@3.18.7", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw=="], 232 + 233 + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], 234 + 235 + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="], 236 + 237 + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="], 238 + 239 + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ=="], 240 + 241 + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg=="], 242 + 243 + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.5", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q=="], 244 + 245 + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], 246 + 247 + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.6", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw=="], 248 + 249 + "@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], 250 + 251 + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q=="], 252 + 253 + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], 254 + 255 + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], 256 + 257 + "@smithy/md5-js": ["@smithy/md5-js@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg=="], 258 + 259 + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], 260 + 261 + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.14", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg=="], 262 + 263 + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q=="], 264 + 265 + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], 266 + 267 + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], 268 + 269 + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], 270 + 271 + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], 272 + 273 + "@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], 274 + 275 + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], 276 + 277 + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], 278 + 279 + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], 280 + 281 + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], 282 + 283 + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], 284 + 285 + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], 286 + 287 + "@smithy/smithy-client": ["@smithy/smithy-client@4.9.10", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ=="], 288 + 289 + "@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], 290 + 291 + "@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], 292 + 293 + "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], 294 + 295 + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], 296 + 297 + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], 298 + 299 + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], 300 + 301 + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], 302 + 303 + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.13", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA=="], 304 + 305 + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.16", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg=="], 306 + 307 + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], 308 + 309 + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], 310 + 311 + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], 312 + 313 + "@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], 314 + 315 + "@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], 316 + 317 + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], 318 + 319 + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], 320 + 321 + "@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="], 322 + 323 + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], 324 + 325 + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], 326 + 327 + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], 328 + 329 + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], 330 + 331 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 332 + 333 + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], 334 + 335 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="], 336 + 337 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="], 338 + 339 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="], 340 + 341 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="], 342 + 343 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="], 344 + 345 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="], 346 + 347 + "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="], 348 + 349 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="], 350 + 351 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="], 352 + 353 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="], 354 + 355 + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], 356 + 357 + "@vitest/expect": ["@vitest/expect@4.0.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w=="], 358 + 359 + "@vitest/mocker": ["@vitest/mocker@4.0.15", "", { "dependencies": { "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ=="], 360 + 361 + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.15", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A=="], 362 + 363 + "@vitest/runner": ["@vitest/runner@4.0.15", "", { "dependencies": { "@vitest/utils": "4.0.15", "pathe": "^2.0.3" } }, "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw=="], 364 + 365 + "@vitest/snapshot": ["@vitest/snapshot@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g=="], 366 + 367 + "@vitest/spy": ["@vitest/spy@4.0.15", "", {}, "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw=="], 368 + 369 + "@vitest/utils": ["@vitest/utils@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" } }, "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA=="], 370 + 371 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 372 + 373 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 374 + 375 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 376 + 377 + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 378 + 379 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 380 + 381 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 382 + 383 + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], 384 + 385 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 386 + 387 + "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], 388 + 389 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 390 + 391 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 392 + 393 + "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="], 394 + 395 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 396 + 397 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 398 + 399 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 400 + 401 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 402 + 403 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 404 + 405 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 406 + 407 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 408 + 409 + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], 410 + 411 + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], 412 + 413 + "esbuild": ["esbuild@0.27.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.1", "@esbuild/android-arm": "0.27.1", "@esbuild/android-arm64": "0.27.1", "@esbuild/android-x64": "0.27.1", "@esbuild/darwin-arm64": "0.27.1", "@esbuild/darwin-x64": "0.27.1", "@esbuild/freebsd-arm64": "0.27.1", "@esbuild/freebsd-x64": "0.27.1", "@esbuild/linux-arm": "0.27.1", "@esbuild/linux-arm64": "0.27.1", "@esbuild/linux-ia32": "0.27.1", "@esbuild/linux-loong64": "0.27.1", "@esbuild/linux-mips64el": "0.27.1", "@esbuild/linux-ppc64": "0.27.1", "@esbuild/linux-riscv64": "0.27.1", "@esbuild/linux-s390x": "0.27.1", "@esbuild/linux-x64": "0.27.1", "@esbuild/netbsd-arm64": "0.27.1", "@esbuild/netbsd-x64": "0.27.1", "@esbuild/openbsd-arm64": "0.27.1", "@esbuild/openbsd-x64": "0.27.1", "@esbuild/openharmony-arm64": "0.27.1", "@esbuild/sunos-x64": "0.27.1", "@esbuild/win32-arm64": "0.27.1", "@esbuild/win32-ia32": "0.27.1", "@esbuild/win32-x64": "0.27.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA=="], 414 + 415 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 416 + 417 + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], 418 + 419 + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], 420 + 421 + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 422 + 423 + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], 424 + 425 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 426 + 427 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 428 + 429 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 430 + 431 + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], 432 + 433 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 434 + 435 + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], 436 + 437 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 438 + 439 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 440 + 441 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 442 + 443 + "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], 444 + 445 + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 446 + 447 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 448 + 449 + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], 450 + 451 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 452 + 453 + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], 454 + 455 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 456 + 457 + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], 458 + 459 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 460 + 461 + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], 462 + 463 + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], 464 + 465 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 466 + 467 + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], 468 + 469 + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 470 + 471 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 472 + 473 + "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], 474 + 475 + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 476 + 477 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 478 + 479 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 480 + 481 + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], 482 + 483 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 484 + 485 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 486 + 487 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 488 + 489 + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], 490 + 491 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 492 + 493 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 494 + 495 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 496 + 497 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 498 + 499 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 500 + 501 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 502 + 503 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 504 + 505 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 506 + 507 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 508 + 509 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 510 + 511 + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 512 + 513 + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], 514 + 515 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 516 + 517 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 518 + 519 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 520 + 521 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 522 + 523 + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], 524 + 525 + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 526 + 527 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 528 + 529 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 530 + 531 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 532 + 533 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 534 + 535 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 536 + 537 + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], 538 + 539 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 540 + 541 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 542 + 543 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 544 + 545 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 546 + 547 + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 548 + 549 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 550 + 551 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 552 + 553 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 554 + 555 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 556 + 557 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 558 + 559 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 560 + 561 + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], 562 + 563 + "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], 564 + 565 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 566 + 567 + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 568 + 569 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 570 + 571 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 572 + 573 + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], 574 + 575 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 576 + 577 + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], 578 + 579 + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], 580 + 581 + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 582 + 583 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 584 + 585 + "strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], 586 + 587 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 588 + 589 + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], 590 + 591 + "tiny-lru": ["tiny-lru@11.4.5", "", {}, "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw=="], 592 + 593 + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], 594 + 595 + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], 596 + 597 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 598 + 599 + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], 600 + 601 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 602 + 603 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 604 + 605 + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 606 + 607 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 608 + 609 + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], 610 + 611 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 612 + 613 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 614 + 615 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 616 + 617 + "vite": ["vite@7.2.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ=="], 618 + 619 + "vitest": ["vitest@4.0.15", "", { "dependencies": { "@vitest/expect": "4.0.15", "@vitest/mocker": "4.0.15", "@vitest/pretty-format": "4.0.15", "@vitest/runner": "4.0.15", "@vitest/snapshot": "4.0.15", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.15", "@vitest/browser-preview": "4.0.15", "@vitest/browser-webdriverio": "4.0.15", "@vitest/ui": "4.0.15", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA=="], 620 + 621 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 622 + 623 + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], 624 + 625 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 626 + 627 + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 628 + 629 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 630 + 631 + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], 632 + 633 + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], 634 + 635 + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], 636 + 637 + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 638 + 639 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 640 + 641 + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 642 + 643 + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 644 + 645 + "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 646 + 647 + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], 648 + 649 + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], 650 + 651 + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], 652 + 653 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 654 + 655 + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 656 + 657 + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 658 + 659 + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 660 + 661 + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 662 + 663 + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 664 + 665 + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 666 + 667 + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 668 + 669 + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 670 + 671 + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 672 + 673 + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 674 + 675 + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 676 + 677 + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 678 + 679 + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 680 + 681 + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 682 + 683 + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 684 + 685 + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 686 + 687 + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 688 + 689 + "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 690 + 691 + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 692 + 693 + "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 694 + 695 + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 696 + 697 + "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 698 + 699 + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 700 + 701 + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 702 + 703 + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 704 + 705 + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 706 + 707 + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], 708 + 709 + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], 710 + 711 + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], 712 + } 713 + }
+47
example-site/README.md
··· 1 + # Example Static Site 2 + 3 + This is a demonstration static website used in the tiered-storage library examples. 4 + 5 + ## Files 6 + 7 + - **index.html** (3.5 KB) - Homepage, stored in hot + warm + cold tiers 8 + - **about.html** (4.2 KB) - About page, stored in warm + cold (skips hot) 9 + - **docs.html** (3.8 KB) - Documentation, stored in warm + cold (skips hot) 10 + - **style.css** (7.1 KB) - Stylesheet, stored in warm + cold (skips hot) 11 + - **script.js** (1.9 KB) - JavaScript, stored in warm + cold (skips hot) 12 + 13 + ## Usage in Examples 14 + 15 + The `example.ts` file demonstrates how this site would be stored using the tiered-storage library: 16 + 17 + 1. **index.html** is stored in all tiers (hot + warm + cold) because it's the entry point and needs instant serving 18 + 2. Other HTML pages skip the hot tier to save memory 19 + 3. CSS and JS files are stored in warm + cold 20 + 4. If there were large media files, they would be stored in cold tier only 21 + 22 + ## Tier Strategy 23 + 24 + ``` 25 + Hot Tier (Memory - 100MB): 26 + └── index.html (3.5 KB) 27 + 28 + Warm Tier (Disk - 10GB): 29 + ├── index.html (3.5 KB) 30 + ├── about.html (4.2 KB) 31 + ├── docs.html (3.8 KB) 32 + ├── style.css (7.1 KB) 33 + └── script.js (1.9 KB) 34 + 35 + Cold Tier (S3 - Unlimited): 36 + ├── index.html (3.5 KB) 37 + ├── about.html (4.2 KB) 38 + ├── docs.html (3.8 KB) 39 + ├── style.css (7.1 KB) 40 + └── script.js (1.9 KB) 41 + ``` 42 + 43 + This strategy ensures: 44 + - Lightning-fast serving of the homepage (from memory) 45 + - Efficient use of hot tier capacity 46 + - All files are cached on disk for fast local access 47 + - S3 acts as the source of truth for disaster recovery
+86
example-site/about.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>About - Tiered Storage Demo</title> 7 + <link rel="stylesheet" href="style.css"> 8 + </head> 9 + <body> 10 + <header> 11 + <nav> 12 + <div class="logo">🗄️ TieredCache</div> 13 + <ul> 14 + <li><a href="index.html">Home</a></li> 15 + <li><a href="about.html" class="active">About</a></li> 16 + <li><a href="docs.html">Docs</a></li> 17 + </ul> 18 + </nav> 19 + </header> 20 + 21 + <main> 22 + <section class="content"> 23 + <h1>About Tiered Storage</h1> 24 + 25 + <h2>The Problem</h2> 26 + <p>Modern applications need to balance three competing concerns:</p> 27 + <ul> 28 + <li><strong>Speed:</strong> Users expect instant responses</li> 29 + <li><strong>Cost:</strong> Keeping everything in memory is expensive</li> 30 + <li><strong>Reliability:</strong> Data must be durable and recoverable</li> 31 + </ul> 32 + 33 + <h2>The Solution</h2> 34 + <p>Tiered storage provides the best of all worlds by automatically managing data across multiple storage tiers:</p> 35 + 36 + <div class="solution-grid"> 37 + <div class="solution-item"> 38 + <h3>🚀 Performance</h3> 39 + <p>Hot tier (memory) serves critical files like index.html in microseconds</p> 40 + </div> 41 + <div class="solution-item"> 42 + <h3>💰 Cost-Effective</h3> 43 + <p>Warm tier (disk) and cold tier (S3) handle bulk storage efficiently</p> 44 + </div> 45 + <div class="solution-item"> 46 + <h3>🛡️ Reliability</h3> 47 + <p>Cold tier acts as source of truth with automatic backups</p> 48 + </div> 49 + </div> 50 + 51 + <h2>Use Cases</h2> 52 + <div class="use-cases"> 53 + <div class="use-case"> 54 + <h4>Static Site Hosting</h4> 55 + <p>Store index.html in memory for instant serving, while keeping images and videos on disk/S3. Perfect for CDN-like performance on a single server.</p> 56 + </div> 57 + <div class="use-case"> 58 + <h4>Content Delivery</h4> 59 + <p>Automatically promote popular content to hot tier based on access patterns. Rarely accessed content stays in cold storage.</p> 60 + </div> 61 + <div class="use-case"> 62 + <h4>Database Caching</h4> 63 + <p>Cache query results in memory, with overflow to disk and S3. Automatic TTL management keeps data fresh.</p> 64 + </div> 65 + </div> 66 + 67 + <h2>How This Demo Works</h2> 68 + <p>This example site is stored using the tiered-storage library:</p> 69 + <ol> 70 + <li><code>index.html</code> - Stored in all tiers (hot + warm + cold) for instant access</li> 71 + <li><code>about.html</code> - Stored in warm + cold (skips hot to save memory)</li> 72 + <li><code>style.css</code> - Stored in warm + cold</li> 73 + <li><code>script.js</code> - Stored in warm + cold</li> 74 + <li><code>hero-image.jpg</code> - Large file, stored in cold tier only</li> 75 + </ol> 76 + <p>When you request a page, the library automatically checks hot → warm → cold and serves from the fastest available tier.</p> 77 + </section> 78 + </main> 79 + 80 + <footer> 81 + <p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p> 82 + </footer> 83 + 84 + <script src="script.js"></script> 85 + </body> 86 + </html>
+105
example-site/docs.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Documentation - Tiered Storage</title> 7 + <link rel="stylesheet" href="style.css"> 8 + </head> 9 + <body> 10 + <header> 11 + <nav> 12 + <div class="logo">🗄️ TieredCache</div> 13 + <ul> 14 + <li><a href="index.html">Home</a></li> 15 + <li><a href="about.html">About</a></li> 16 + <li><a href="docs.html" class="active">Docs</a></li> 17 + </ul> 18 + </nav> 19 + </header> 20 + 21 + <main> 22 + <section class="content"> 23 + <h1>Quick Start Guide</h1> 24 + 25 + <h2>Installation</h2> 26 + <pre><code>npm install tiered-storage 27 + # or 28 + bun add tiered-storage</code></pre> 29 + 30 + <h2>Basic Usage</h2> 31 + <pre><code>import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage'; 32 + 33 + const storage = new TieredStorage({ 34 + tiers: { 35 + hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), 36 + warm: new DiskStorageTier({ directory: './cache' }), 37 + cold: new S3StorageTier({ 38 + bucket: 'my-bucket', 39 + region: 'us-east-1', 40 + }), 41 + }, 42 + compression: true, 43 + defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days 44 + }); 45 + 46 + // Store data 47 + await storage.set('user:123', { name: 'Alice' }); 48 + 49 + // Retrieve data 50 + const user = await storage.get('user:123'); 51 + 52 + // Invalidate by prefix 53 + await storage.invalidate('user:');</code></pre> 54 + 55 + <h2>Selective Tier Placement</h2> 56 + <p>Control which tiers receive specific files:</p> 57 + <pre><code>// Critical file - store in all tiers 58 + await storage.set('index.html', htmlContent); 59 + 60 + // Large file - skip hot tier to save memory 61 + await storage.set('video.mp4', videoData, { 62 + skipTiers: ['hot'] 63 + });</code></pre> 64 + 65 + <h2>Bootstrap on Startup</h2> 66 + <pre><code>// Warm up hot tier from warm tier 67 + await storage.bootstrapHot(1000); // Load top 1000 items 68 + 69 + // Warm up warm tier from cold tier 70 + await storage.bootstrapWarm({ 71 + sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), 72 + limit: 10000, 73 + });</code></pre> 74 + 75 + <h2>Statistics & Monitoring</h2> 76 + <pre><code>const stats = await storage.getStats(); 77 + console.log('Hot tier:', stats.hot); 78 + console.log('Warm tier:', stats.warm); 79 + console.log('Cold tier:', stats.cold); 80 + console.log('Hit rate:', stats.hitRate);</code></pre> 81 + 82 + <h2>API Reference</h2> 83 + <ul> 84 + <li><code>get(key)</code> - Retrieve data</li> 85 + <li><code>getWithMetadata(key)</code> - Retrieve with metadata and source tier</li> 86 + <li><code>set(key, data, options)</code> - Store data</li> 87 + <li><code>delete(key)</code> - Delete from all tiers</li> 88 + <li><code>exists(key)</code> - Check if key exists</li> 89 + <li><code>touch(key, ttlMs)</code> - Renew TTL</li> 90 + <li><code>invalidate(prefix)</code> - Delete by prefix</li> 91 + <li><code>listKeys(prefix)</code> - List keys</li> 92 + <li><code>getStats()</code> - Get statistics</li> 93 + <li><code>bootstrapHot(limit)</code> - Warm up hot tier</li> 94 + <li><code>bootstrapWarm(options)</code> - Warm up warm tier</li> 95 + </ul> 96 + </section> 97 + </main> 98 + 99 + <footer> 100 + <p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p> 101 + </footer> 102 + 103 + <script src="script.js"></script> 104 + </body> 105 + </html>
+103
example-site/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Tiered Storage Demo Site</title> 7 + <link rel="stylesheet" href="style.css"> 8 + </head> 9 + <body> 10 + <header> 11 + <nav> 12 + <div class="logo">🗄️ TieredCache</div> 13 + <ul> 14 + <li><a href="index.html" class="active">Home</a></li> 15 + <li><a href="about.html">About</a></li> 16 + <li><a href="docs.html">Docs</a></li> 17 + </ul> 18 + </nav> 19 + </header> 20 + 21 + <main> 22 + <section class="hero"> 23 + <h1>Lightning-Fast Multi-Tier Caching</h1> 24 + <p>Store your data across memory, disk, and cloud storage with automatic promotion and intelligent eviction.</p> 25 + <div class="cta-buttons"> 26 + <a href="#features" class="btn btn-primary">Learn More</a> 27 + <a href="docs.html" class="btn btn-secondary">Documentation</a> 28 + </div> 29 + </section> 30 + 31 + <section id="features" class="features"> 32 + <h2>Features</h2> 33 + <div class="feature-grid"> 34 + <div class="feature-card"> 35 + <div class="feature-icon">⚡</div> 36 + <h3>Hot Tier (Memory)</h3> 37 + <p>Lightning-fast access with LRU eviction. Perfect for frequently accessed data like index.html.</p> 38 + </div> 39 + <div class="feature-card"> 40 + <div class="feature-icon">💾</div> 41 + <h3>Warm Tier (Disk)</h3> 42 + <p>Fast local storage with configurable eviction policies. Ideal for site assets and media.</p> 43 + </div> 44 + <div class="feature-card"> 45 + <div class="feature-icon">☁️</div> 46 + <h3>Cold Tier (S3)</h3> 47 + <p>Unlimited cloud storage as your source of truth. Supports S3, R2, MinIO, and more.</p> 48 + </div> 49 + </div> 50 + </section> 51 + 52 + <section class="architecture"> 53 + <h2>How It Works</h2> 54 + <div class="tier-diagram"> 55 + <div class="tier hot"> 56 + <div class="tier-label">Hot (Memory)</div> 57 + <div class="tier-content">index.html ✓</div> 58 + </div> 59 + <div class="arrow">↓</div> 60 + <div class="tier warm"> 61 + <div class="tier-label">Warm (Disk)</div> 62 + <div class="tier-content">index.html, style.css, images ✓</div> 63 + </div> 64 + <div class="arrow">↓</div> 65 + <div class="tier cold"> 66 + <div class="tier-label">Cold (S3)</div> 67 + <div class="tier-content">All files (source of truth) ✓</div> 68 + </div> 69 + </div> 70 + <p class="diagram-note">Data cascades down on writes, bubbles up on reads</p> 71 + </section> 72 + 73 + <section class="stats" id="cache-stats"> 74 + <h2>Live Cache Statistics</h2> 75 + <div class="stats-grid"> 76 + <div class="stat-card"> 77 + <div class="stat-value" id="hot-items">-</div> 78 + <div class="stat-label">Hot Tier Items</div> 79 + </div> 80 + <div class="stat-card"> 81 + <div class="stat-value" id="warm-items">-</div> 82 + <div class="stat-label">Warm Tier Items</div> 83 + </div> 84 + <div class="stat-card"> 85 + <div class="stat-value" id="cold-items">-</div> 86 + <div class="stat-label">Cold Tier Items</div> 87 + </div> 88 + <div class="stat-card"> 89 + <div class="stat-value" id="hit-rate">-</div> 90 + <div class="stat-label">Cache Hit Rate</div> 91 + </div> 92 + </div> 93 + </section> 94 + </main> 95 + 96 + <footer> 97 + <p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p> 98 + <p><small>This is a demo site to showcase the tiered-storage library capabilities.</small></p> 99 + </footer> 100 + 101 + <script src="script.js"></script> 102 + </body> 103 + </html>
+84
example-site/script.js
··· 1 + /** 2 + * Tiered Storage Demo Site - Client-side JavaScript 3 + * 4 + * This script demonstrates how a static site can interact with 5 + * the tiered storage system (in a real scenario, stats would be 6 + * fetched from a backend API that uses the storage library) 7 + */ 8 + 9 + // Simulated cache statistics 10 + // In a real implementation, this would fetch from your backend 11 + function simulateCacheStats() { 12 + return { 13 + hot: { 14 + items: 1, 15 + bytes: 3547, 16 + hits: 42, 17 + misses: 3, 18 + }, 19 + warm: { 20 + items: 5, 21 + bytes: 127438, 22 + hits: 15, 23 + misses: 2, 24 + }, 25 + cold: { 26 + items: 5, 27 + bytes: 127438, 28 + }, 29 + totalHits: 57, 30 + totalMisses: 5, 31 + hitRate: 0.919, 32 + }; 33 + } 34 + 35 + // Update stats display 36 + function updateStatsDisplay() { 37 + const stats = simulateCacheStats(); 38 + 39 + const hotItems = document.getElementById('hot-items'); 40 + const warmItems = document.getElementById('warm-items'); 41 + const coldItems = document.getElementById('cold-items'); 42 + const hitRate = document.getElementById('hit-rate'); 43 + 44 + if (hotItems) hotItems.textContent = stats.hot.items; 45 + if (warmItems) warmItems.textContent = stats.warm.items; 46 + if (coldItems) coldItems.textContent = stats.cold.items; 47 + if (hitRate) hitRate.textContent = `${(stats.hitRate * 100).toFixed(1)}%`; 48 + } 49 + 50 + // Smooth scrolling for anchor links 51 + document.querySelectorAll('a[href^="#"]').forEach(anchor => { 52 + anchor.addEventListener('click', function (e) { 53 + e.preventDefault(); 54 + const target = document.querySelector(this.getAttribute('href')); 55 + if (target) { 56 + target.scrollIntoView({ 57 + behavior: 'smooth', 58 + block: 'start' 59 + }); 60 + } 61 + }); 62 + }); 63 + 64 + // Initialize stats when page loads 65 + if (document.readyState === 'loading') { 66 + document.addEventListener('DOMContentLoaded', updateStatsDisplay); 67 + } else { 68 + updateStatsDisplay(); 69 + } 70 + 71 + // Update stats periodically (simulate real-time updates) 72 + setInterval(updateStatsDisplay, 5000); 73 + 74 + // Add active class to navigation based on current page 75 + const currentPage = window.location.pathname.split('/').pop() || 'index.html'; 76 + document.querySelectorAll('nav a').forEach(link => { 77 + if (link.getAttribute('href') === currentPage) { 78 + link.classList.add('active'); 79 + } 80 + }); 81 + 82 + // Log page view (in real app, would send to analytics) 83 + console.log(`[TieredCache] Page viewed: ${currentPage}`); 84 + console.log(`[TieredCache] This page was likely served from ${currentPage === 'index.html' ? 'hot tier (memory)' : 'warm tier (disk)'}`);
+439
example-site/style.css
··· 1 + /* Tiered Storage Demo Site - Stylesheet */ 2 + 3 + :root { 4 + --primary-color: #3b82f6; 5 + --secondary-color: #8b5cf6; 6 + --success-color: #10b981; 7 + --background: #0f172a; 8 + --surface: #1e293b; 9 + --text: #f1f5f9; 10 + --text-muted: #94a3b8; 11 + --border: #334155; 12 + } 13 + 14 + * { 15 + margin: 0; 16 + padding: 0; 17 + box-sizing: border-box; 18 + } 19 + 20 + body { 21 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 22 + background: var(--background); 23 + color: var(--text); 24 + line-height: 1.6; 25 + min-height: 100vh; 26 + display: flex; 27 + flex-direction: column; 28 + } 29 + 30 + /* Header & Navigation */ 31 + header { 32 + background: var(--surface); 33 + border-bottom: 1px solid var(--border); 34 + position: sticky; 35 + top: 0; 36 + z-index: 100; 37 + } 38 + 39 + nav { 40 + max-width: 1200px; 41 + margin: 0 auto; 42 + padding: 1rem 2rem; 43 + display: flex; 44 + justify-content: space-between; 45 + align-items: center; 46 + } 47 + 48 + .logo { 49 + font-size: 1.5rem; 50 + font-weight: 700; 51 + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); 52 + -webkit-background-clip: text; 53 + -webkit-text-fill-color: transparent; 54 + background-clip: text; 55 + } 56 + 57 + nav ul { 58 + display: flex; 59 + gap: 2rem; 60 + list-style: none; 61 + } 62 + 63 + nav a { 64 + color: var(--text-muted); 65 + text-decoration: none; 66 + transition: color 0.2s; 67 + padding: 0.5rem 1rem; 68 + border-radius: 0.5rem; 69 + } 70 + 71 + nav a:hover, 72 + nav a.active { 73 + color: var(--text); 74 + background: rgba(59, 130, 246, 0.1); 75 + } 76 + 77 + /* Main Content */ 78 + main { 79 + flex: 1; 80 + max-width: 1200px; 81 + margin: 0 auto; 82 + padding: 2rem; 83 + width: 100%; 84 + } 85 + 86 + /* Hero Section */ 87 + .hero { 88 + text-align: center; 89 + padding: 4rem 2rem; 90 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1)); 91 + border-radius: 1rem; 92 + margin-bottom: 3rem; 93 + } 94 + 95 + .hero h1 { 96 + font-size: 3rem; 97 + margin-bottom: 1rem; 98 + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); 99 + -webkit-background-clip: text; 100 + -webkit-text-fill-color: transparent; 101 + background-clip: text; 102 + } 103 + 104 + .hero p { 105 + font-size: 1.25rem; 106 + color: var(--text-muted); 107 + max-width: 600px; 108 + margin: 0 auto 2rem; 109 + } 110 + 111 + .cta-buttons { 112 + display: flex; 113 + gap: 1rem; 114 + justify-content: center; 115 + } 116 + 117 + .btn { 118 + padding: 0.75rem 2rem; 119 + border-radius: 0.5rem; 120 + text-decoration: none; 121 + font-weight: 600; 122 + transition: all 0.2s; 123 + display: inline-block; 124 + } 125 + 126 + .btn-primary { 127 + background: var(--primary-color); 128 + color: white; 129 + } 130 + 131 + .btn-primary:hover { 132 + background: #2563eb; 133 + transform: translateY(-2px); 134 + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); 135 + } 136 + 137 + .btn-secondary { 138 + background: var(--surface); 139 + color: var(--text); 140 + border: 1px solid var(--border); 141 + } 142 + 143 + .btn-secondary:hover { 144 + background: var(--border); 145 + } 146 + 147 + /* Features */ 148 + .features { 149 + margin-bottom: 3rem; 150 + } 151 + 152 + .features h2 { 153 + text-align: center; 154 + font-size: 2rem; 155 + margin-bottom: 2rem; 156 + } 157 + 158 + .feature-grid { 159 + display: grid; 160 + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 161 + gap: 2rem; 162 + } 163 + 164 + .feature-card { 165 + background: var(--surface); 166 + padding: 2rem; 167 + border-radius: 1rem; 168 + border: 1px solid var(--border); 169 + transition: transform 0.2s, box-shadow 0.2s; 170 + } 171 + 172 + .feature-card:hover { 173 + transform: translateY(-4px); 174 + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); 175 + } 176 + 177 + .feature-icon { 178 + font-size: 3rem; 179 + margin-bottom: 1rem; 180 + } 181 + 182 + .feature-card h3 { 183 + color: var(--primary-color); 184 + margin-bottom: 0.5rem; 185 + } 186 + 187 + .feature-card p { 188 + color: var(--text-muted); 189 + } 190 + 191 + /* Architecture Diagram */ 192 + .architecture { 193 + margin-bottom: 3rem; 194 + } 195 + 196 + .architecture h2 { 197 + text-align: center; 198 + font-size: 2rem; 199 + margin-bottom: 2rem; 200 + } 201 + 202 + .tier-diagram { 203 + max-width: 600px; 204 + margin: 0 auto; 205 + } 206 + 207 + .tier { 208 + background: var(--surface); 209 + border: 2px solid var(--border); 210 + border-radius: 0.5rem; 211 + padding: 1.5rem; 212 + margin-bottom: 1rem; 213 + } 214 + 215 + .tier.hot { 216 + border-color: #ef4444; 217 + background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), var(--surface)); 218 + } 219 + 220 + .tier.warm { 221 + border-color: #f59e0b; 222 + background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), var(--surface)); 223 + } 224 + 225 + .tier.cold { 226 + border-color: var(--primary-color); 227 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), var(--surface)); 228 + } 229 + 230 + .tier-label { 231 + font-weight: 700; 232 + margin-bottom: 0.5rem; 233 + font-size: 1.1rem; 234 + } 235 + 236 + .tier-content { 237 + color: var(--text-muted); 238 + font-size: 0.9rem; 239 + } 240 + 241 + .arrow { 242 + text-align: center; 243 + font-size: 2rem; 244 + color: var(--text-muted); 245 + margin: -0.5rem 0; 246 + } 247 + 248 + .diagram-note { 249 + text-align: center; 250 + color: var(--text-muted); 251 + font-style: italic; 252 + margin-top: 1rem; 253 + } 254 + 255 + /* Stats */ 256 + .stats { 257 + margin-bottom: 3rem; 258 + } 259 + 260 + .stats h2 { 261 + text-align: center; 262 + font-size: 2rem; 263 + margin-bottom: 2rem; 264 + } 265 + 266 + .stats-grid { 267 + display: grid; 268 + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 269 + gap: 1.5rem; 270 + } 271 + 272 + .stat-card { 273 + background: var(--surface); 274 + padding: 2rem; 275 + border-radius: 1rem; 276 + border: 1px solid var(--border); 277 + text-align: center; 278 + } 279 + 280 + .stat-value { 281 + font-size: 2.5rem; 282 + font-weight: 700; 283 + color: var(--primary-color); 284 + margin-bottom: 0.5rem; 285 + } 286 + 287 + .stat-label { 288 + color: var(--text-muted); 289 + font-size: 0.9rem; 290 + } 291 + 292 + /* Content Pages */ 293 + .content { 294 + max-width: 800px; 295 + margin: 0 auto; 296 + } 297 + 298 + .content h1 { 299 + font-size: 2.5rem; 300 + margin-bottom: 1.5rem; 301 + color: var(--primary-color); 302 + } 303 + 304 + .content h2 { 305 + font-size: 1.8rem; 306 + margin-top: 2rem; 307 + margin-bottom: 1rem; 308 + } 309 + 310 + .content h3 { 311 + font-size: 1.3rem; 312 + margin-top: 1.5rem; 313 + margin-bottom: 0.5rem; 314 + } 315 + 316 + .content h4 { 317 + font-size: 1.1rem; 318 + margin-top: 1rem; 319 + margin-bottom: 0.5rem; 320 + color: var(--primary-color); 321 + } 322 + 323 + .content p { 324 + margin-bottom: 1rem; 325 + color: var(--text-muted); 326 + } 327 + 328 + .content ul, .content ol { 329 + margin-bottom: 1rem; 330 + margin-left: 2rem; 331 + color: var(--text-muted); 332 + } 333 + 334 + .content li { 335 + margin-bottom: 0.5rem; 336 + } 337 + 338 + .content code { 339 + background: var(--surface); 340 + padding: 0.2rem 0.5rem; 341 + border-radius: 0.25rem; 342 + font-family: 'Monaco', 'Courier New', monospace; 343 + font-size: 0.9em; 344 + color: var(--success-color); 345 + } 346 + 347 + .content pre { 348 + background: var(--surface); 349 + padding: 1.5rem; 350 + border-radius: 0.5rem; 351 + overflow-x: auto; 352 + margin-bottom: 1rem; 353 + border: 1px solid var(--border); 354 + } 355 + 356 + .content pre code { 357 + background: none; 358 + padding: 0; 359 + color: var(--text); 360 + } 361 + 362 + .solution-grid { 363 + display: grid; 364 + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 365 + gap: 1.5rem; 366 + margin: 2rem 0; 367 + } 368 + 369 + .solution-item { 370 + background: var(--surface); 371 + padding: 1.5rem; 372 + border-radius: 0.5rem; 373 + border: 1px solid var(--border); 374 + } 375 + 376 + .solution-item h3 { 377 + margin-top: 0; 378 + } 379 + 380 + .use-cases { 381 + display: flex; 382 + flex-direction: column; 383 + gap: 1.5rem; 384 + margin-top: 2rem; 385 + } 386 + 387 + .use-case { 388 + background: var(--surface); 389 + padding: 1.5rem; 390 + border-radius: 0.5rem; 391 + border-left: 4px solid var(--primary-color); 392 + } 393 + 394 + .use-case h4 { 395 + margin-top: 0; 396 + } 397 + 398 + /* Footer */ 399 + footer { 400 + background: var(--surface); 401 + border-top: 1px solid var(--border); 402 + padding: 2rem; 403 + text-align: center; 404 + color: var(--text-muted); 405 + margin-top: auto; 406 + } 407 + 408 + footer p { 409 + margin: 0.5rem 0; 410 + } 411 + 412 + /* Responsive */ 413 + @media (max-width: 768px) { 414 + .hero h1 { 415 + font-size: 2rem; 416 + } 417 + 418 + .hero p { 419 + font-size: 1rem; 420 + } 421 + 422 + nav ul { 423 + gap: 1rem; 424 + } 425 + 426 + .feature-grid, 427 + .stats-grid { 428 + grid-template-columns: 1fr; 429 + } 430 + 431 + .solution-grid { 432 + grid-template-columns: 1fr; 433 + } 434 + 435 + .cta-buttons { 436 + flex-direction: column; 437 + align-items: stretch; 438 + } 439 + }
+434
example.ts
··· 1 + /** 2 + * Example usage of the tiered-storage library 3 + * 4 + * Run with: bun run example 5 + * 6 + * Note: This example uses S3 for cold storage. You'll need to configure 7 + * AWS credentials and an S3 bucket in .env (see .env.example) 8 + */ 9 + 10 + import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js'; 11 + import { rm } from 'node:fs/promises'; 12 + 13 + // Configuration from environment variables 14 + const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example'; 15 + const S3_REGION = process.env.S3_REGION || 'us-east-1'; 16 + const S3_ENDPOINT = process.env.S3_ENDPOINT; 17 + const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false'; // Default true 18 + const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; 19 + const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; 20 + 21 + async function basicExample() { 22 + console.log('\n=== Basic Example ===\n'); 23 + 24 + const storage = new TieredStorage({ 25 + tiers: { 26 + hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }), // 10MB 27 + warm: new DiskStorageTier({ directory: './example-cache/basic/warm' }), 28 + cold: new S3StorageTier({ 29 + bucket: S3_BUCKET, 30 + region: S3_REGION, 31 + endpoint: S3_ENDPOINT, 32 + forcePathStyle: S3_FORCE_PATH_STYLE, 33 + credentials: 34 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 35 + ? { 36 + accessKeyId: AWS_ACCESS_KEY_ID, 37 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 38 + } 39 + : undefined, 40 + prefix: 'example/basic/', 41 + }), 42 + }, 43 + compression: true, 44 + defaultTTL: 60 * 60 * 1000, // 1 hour 45 + }); 46 + 47 + // Store some data 48 + console.log('Storing user data...'); 49 + await storage.set('user:alice', { 50 + name: 'Alice', 51 + email: 'alice@example.com', 52 + role: 'admin', 53 + }); 54 + 55 + await storage.set('user:bob', { 56 + name: 'Bob', 57 + email: 'bob@example.com', 58 + role: 'user', 59 + }); 60 + 61 + // Retrieve with metadata 62 + const result = await storage.getWithMetadata('user:alice'); 63 + if (result) { 64 + console.log(`Retrieved user:alice from ${result.source} tier:`); 65 + console.log(result.data); 66 + console.log('Metadata:', { 67 + size: result.metadata.size, 68 + compressed: result.metadata.compressed, 69 + accessCount: result.metadata.accessCount, 70 + }); 71 + } 72 + 73 + // Get statistics 74 + const stats = await storage.getStats(); 75 + console.log('\nStorage Statistics:'); 76 + console.log(`Hot tier: ${stats.hot?.items} items, ${stats.hot?.bytes} bytes`); 77 + console.log(`Warm tier: ${stats.warm?.items} items, ${stats.warm?.bytes} bytes`); 78 + console.log(`Cold tier (S3): ${stats.cold.items} items, ${stats.cold.bytes} bytes`); 79 + console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(2)}%`); 80 + 81 + // List all keys with prefix 82 + console.log('\nAll user keys:'); 83 + for await (const key of storage.listKeys('user:')) { 84 + console.log(` - ${key}`); 85 + } 86 + 87 + // Invalidate by prefix 88 + console.log('\nInvalidating all user keys...'); 89 + const deleted = await storage.invalidate('user:'); 90 + console.log(`Deleted ${deleted} keys`); 91 + } 92 + 93 + async function staticSiteHostingExample() { 94 + console.log('\n=== Static Site Hosting Example (wisp.place pattern) ===\n'); 95 + 96 + const storage = new TieredStorage({ 97 + tiers: { 98 + hot: new MemoryStorageTier({ 99 + maxSizeBytes: 50 * 1024 * 1024, // 50MB 100 + maxItems: 500, 101 + }), 102 + warm: new DiskStorageTier({ 103 + directory: './example-cache/sites/warm', 104 + maxSizeBytes: 1024 * 1024 * 1024, // 1GB 105 + }), 106 + cold: new S3StorageTier({ 107 + bucket: S3_BUCKET, 108 + region: S3_REGION, 109 + endpoint: S3_ENDPOINT, 110 + forcePathStyle: S3_FORCE_PATH_STYLE, 111 + credentials: 112 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 113 + ? { 114 + accessKeyId: AWS_ACCESS_KEY_ID, 115 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 116 + } 117 + : undefined, 118 + prefix: 'example/sites/', 119 + }), 120 + }, 121 + compression: true, 122 + defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days 123 + promotionStrategy: 'lazy', // Don't auto-promote large files 124 + }); 125 + 126 + const siteId = 'did:plc:abc123'; 127 + const siteName = 'tiered-cache-demo'; 128 + 129 + console.log('Loading real static site from example-site/...\n'); 130 + 131 + // Load actual site files 132 + const { readFile } = await import('node:fs/promises'); 133 + 134 + const files = [ 135 + { name: 'index.html', skipTiers: [], mimeType: 'text/html' }, 136 + { name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' }, 137 + { name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' }, 138 + { name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' }, 139 + { name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' }, 140 + ]; 141 + 142 + console.log('Storing site files with selective tier placement:\n'); 143 + 144 + for (const file of files) { 145 + const content = await readFile(`./example-site/${file.name}`, 'utf-8'); 146 + const key = `${siteId}/${siteName}/${file.name}`; 147 + 148 + await storage.set(key, content, { 149 + skipTiers: file.skipTiers as ('hot' | 'warm')[], 150 + metadata: { mimeType: file.mimeType }, 151 + }); 152 + 153 + const tierInfo = 154 + file.skipTiers.length === 0 155 + ? 'hot + warm + cold (S3)' 156 + : `warm + cold (S3) - skipped ${file.skipTiers.join(', ')}`; 157 + const sizeKB = (content.length / 1024).toFixed(2); 158 + console.log(`✓ ${file.name} (${sizeKB} KB) → ${tierInfo}`); 159 + } 160 + 161 + // Check where each file is served from 162 + console.log('\nServing files (checking which tier):'); 163 + for (const file of files) { 164 + const result = await storage.getWithMetadata(`${siteId}/${siteName}/${file.name}`); 165 + if (result) { 166 + const sizeKB = (result.metadata.size / 1024).toFixed(2); 167 + console.log(` ${file.name}: served from ${result.source} (${sizeKB} KB)`); 168 + } 169 + } 170 + 171 + // Show hot tier only has index.html 172 + console.log('\nHot tier contents (should only contain index.html):'); 173 + const stats = await storage.getStats(); 174 + console.log(` Items: ${stats.hot?.items}`); 175 + console.log(` Size: ${((stats.hot?.bytes ?? 0) / 1024).toFixed(2)} KB`); 176 + console.log(` Files: index.html only`); 177 + 178 + console.log('\nWarm tier contents (all site files):'); 179 + console.log(` Items: ${stats.warm?.items}`); 180 + console.log(` Size: ${((stats.warm?.bytes ?? 0) / 1024).toFixed(2)} KB`); 181 + console.log(` Files: all ${files.length} files`); 182 + 183 + // Demonstrate accessing a page 184 + console.log('\nSimulating page request for about.html:'); 185 + const aboutPage = await storage.getWithMetadata(`${siteId}/${siteName}/about.html`); 186 + if (aboutPage) { 187 + console.log(` Source: ${aboutPage.source} tier`); 188 + console.log(` Access count: ${aboutPage.metadata.accessCount}`); 189 + console.log(` Preview: ${aboutPage.data.toString().slice(0, 100)}...`); 190 + } 191 + 192 + // Invalidate entire site 193 + console.log(`\nInvalidating entire site: ${siteId}/${siteName}/`); 194 + const deleted = await storage.invalidate(`${siteId}/${siteName}/`); 195 + console.log(`Deleted ${deleted} files from all tiers`); 196 + } 197 + 198 + async function bootstrapExample() { 199 + console.log('\n=== Bootstrap Example ===\n'); 200 + 201 + const hot = new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }); 202 + const warm = new DiskStorageTier({ directory: './example-cache/bootstrap/warm' }); 203 + const cold = new S3StorageTier({ 204 + bucket: S3_BUCKET, 205 + region: S3_REGION, 206 + endpoint: S3_ENDPOINT, 207 + forcePathStyle: S3_FORCE_PATH_STYLE, 208 + credentials: 209 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 210 + ? { 211 + accessKeyId: AWS_ACCESS_KEY_ID, 212 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 213 + } 214 + : undefined, 215 + prefix: 'example/bootstrap/', 216 + }); 217 + 218 + const storage = new TieredStorage({ 219 + tiers: { hot, warm, cold }, 220 + }); 221 + 222 + // Populate with some data 223 + console.log('Populating storage with test data...'); 224 + for (let i = 0; i < 10; i++) { 225 + await storage.set(`item:${i}`, { 226 + id: i, 227 + name: `Item ${i}`, 228 + description: `This is item number ${i}`, 229 + }); 230 + } 231 + 232 + // Access some items to build up access counts 233 + console.log('Accessing some items to simulate usage patterns...'); 234 + await storage.get('item:0'); // Most accessed 235 + await storage.get('item:0'); 236 + await storage.get('item:0'); 237 + await storage.get('item:1'); // Second most accessed 238 + await storage.get('item:1'); 239 + await storage.get('item:2'); // Third most accessed 240 + 241 + // Clear hot tier to simulate server restart 242 + console.log('\nSimulating server restart (clearing hot tier)...'); 243 + await hot.clear(); 244 + 245 + let hotStats = await hot.getStats(); 246 + console.log(`Hot tier after clear: ${hotStats.items} items`); 247 + 248 + // Bootstrap hot from warm (loads most accessed items) 249 + console.log('\nBootstrapping hot tier from warm (loading top 3 items)...'); 250 + const loaded = await storage.bootstrapHot(3); 251 + console.log(`Loaded ${loaded} items into hot tier`); 252 + 253 + hotStats = await hot.getStats(); 254 + console.log(`Hot tier after bootstrap: ${hotStats.items} items`); 255 + 256 + // Verify the right items were loaded 257 + console.log('\nVerifying loaded items are served from hot:'); 258 + for (let i = 0; i < 3; i++) { 259 + const result = await storage.getWithMetadata(`item:${i}`); 260 + console.log(` item:${i}: ${result?.source}`); 261 + } 262 + 263 + // Cleanup this example's data 264 + console.log('\nCleaning up bootstrap example data...'); 265 + await storage.invalidate('item:'); 266 + } 267 + 268 + async function promotionStrategyExample() { 269 + console.log('\n=== Promotion Strategy Example ===\n'); 270 + 271 + // Lazy promotion (default) 272 + console.log('Testing LAZY promotion:'); 273 + const lazyStorage = new TieredStorage({ 274 + tiers: { 275 + hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }), 276 + warm: new DiskStorageTier({ directory: './example-cache/promo-lazy/warm' }), 277 + cold: new S3StorageTier({ 278 + bucket: S3_BUCKET, 279 + region: S3_REGION, 280 + endpoint: S3_ENDPOINT, 281 + forcePathStyle: S3_FORCE_PATH_STYLE, 282 + credentials: 283 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 284 + ? { 285 + accessKeyId: AWS_ACCESS_KEY_ID, 286 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 287 + } 288 + : undefined, 289 + prefix: 'example/promo-lazy/', 290 + }), 291 + }, 292 + promotionStrategy: 'lazy', 293 + }); 294 + 295 + // Write data and clear hot 296 + await lazyStorage.set('test:lazy', { value: 'lazy test' }); 297 + await lazyStorage.clearTier('hot'); 298 + 299 + // Read from cold (should NOT auto-promote to hot) 300 + const lazyResult = await lazyStorage.getWithMetadata('test:lazy'); 301 + console.log(` First read served from: ${lazyResult?.source}`); 302 + 303 + const lazyResult2 = await lazyStorage.getWithMetadata('test:lazy'); 304 + console.log(` Second read served from: ${lazyResult2?.source} (lazy = no auto-promotion)`); 305 + 306 + // Eager promotion 307 + console.log('\nTesting EAGER promotion:'); 308 + const eagerStorage = new TieredStorage({ 309 + tiers: { 310 + hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }), 311 + warm: new DiskStorageTier({ directory: './example-cache/promo-eager/warm' }), 312 + cold: new S3StorageTier({ 313 + bucket: S3_BUCKET, 314 + region: S3_REGION, 315 + endpoint: S3_ENDPOINT, 316 + forcePathStyle: S3_FORCE_PATH_STYLE, 317 + credentials: 318 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 319 + ? { 320 + accessKeyId: AWS_ACCESS_KEY_ID, 321 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 322 + } 323 + : undefined, 324 + prefix: 'example/promo-eager/', 325 + }), 326 + }, 327 + promotionStrategy: 'eager', 328 + }); 329 + 330 + // Write data and clear hot 331 + await eagerStorage.set('test:eager', { value: 'eager test' }); 332 + await eagerStorage.clearTier('hot'); 333 + 334 + // Read from cold (SHOULD auto-promote to hot) 335 + const eagerResult = await eagerStorage.getWithMetadata('test:eager'); 336 + console.log(` First read served from: ${eagerResult?.source}`); 337 + 338 + const eagerResult2 = await eagerStorage.getWithMetadata('test:eager'); 339 + console.log(` Second read served from: ${eagerResult2?.source} (eager = promoted to hot)`); 340 + 341 + // Cleanup 342 + await lazyStorage.invalidate('test:'); 343 + await eagerStorage.invalidate('test:'); 344 + } 345 + 346 + async function cleanup() { 347 + console.log('\n=== Cleanup ===\n'); 348 + console.log('Removing example cache directories...'); 349 + await rm('./example-cache', { recursive: true, force: true }); 350 + console.log('✓ Local cache directories removed'); 351 + console.log('\nNote: S3 objects with prefix "example/" remain in bucket'); 352 + console.log(' (remove manually if needed)'); 353 + } 354 + 355 + async function main() { 356 + console.log('╔════════════════════════════════════════════════╗'); 357 + console.log('║ Tiered Storage Library - Usage Examples ║'); 358 + console.log('║ Cold Tier: S3 (or S3-compatible storage) ║'); 359 + console.log('╚════════════════════════════════════════════════╝'); 360 + 361 + // Check for S3 configuration 362 + if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) { 363 + console.log('\n⚠️ Warning: AWS credentials not configured'); 364 + console.log(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env'); 365 + console.log(' (See .env.example for configuration options)\n'); 366 + } 367 + 368 + console.log('\nConfiguration:'); 369 + console.log(` S3 Bucket: ${S3_BUCKET}`); 370 + console.log(` S3 Region: ${S3_REGION}`); 371 + console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`); 372 + console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`); 373 + console.log(` Credentials: ${AWS_ACCESS_KEY_ID ? '✓ Configured' : '✗ Not configured (using IAM role)'}`); 374 + 375 + try { 376 + // Test S3 connection first 377 + console.log('\nTesting S3 connection...'); 378 + const testStorage = new S3StorageTier({ 379 + bucket: S3_BUCKET, 380 + region: S3_REGION, 381 + endpoint: S3_ENDPOINT, 382 + forcePathStyle: S3_FORCE_PATH_STYLE, 383 + credentials: 384 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 385 + ? { 386 + accessKeyId: AWS_ACCESS_KEY_ID, 387 + secretAccessKey: AWS_SECRET_ACCESS_KEY, 388 + } 389 + : undefined, 390 + prefix: 'test/', 391 + }); 392 + 393 + try { 394 + await testStorage.set('connection-test', new TextEncoder().encode('test'), { 395 + key: 'connection-test', 396 + size: 4, 397 + createdAt: new Date(), 398 + lastAccessed: new Date(), 399 + accessCount: 0, 400 + compressed: false, 401 + checksum: 'test', 402 + }); 403 + console.log('✓ S3 connection successful!\n'); 404 + await testStorage.delete('connection-test'); 405 + } catch (error: any) { 406 + console.error('✗ S3 connection failed:', error.message); 407 + console.error('\nPossible issues:'); 408 + console.error(' 1. Check that the bucket exists on your S3 service'); 409 + console.error(' 2. Verify credentials have read/write permissions'); 410 + console.error(' 3. Confirm the endpoint URL is correct'); 411 + console.error(' 4. Try setting S3_REGION to a different value (e.g., "us-east-1" or "auto")'); 412 + console.error('\nSkipping examples due to S3 connection error.\n'); 413 + return; 414 + } 415 + 416 + await basicExample(); 417 + await staticSiteHostingExample(); 418 + await bootstrapExample(); 419 + await promotionStrategyExample(); 420 + } catch (error: any) { 421 + console.error('\n❌ Error:', error.message); 422 + if (error.name === 'NoSuchBucket') { 423 + console.error(`\n The S3 bucket "${S3_BUCKET}" does not exist.`); 424 + console.error(' Create it first or set S3_BUCKET in .env to an existing bucket.\n'); 425 + } 426 + } finally { 427 + await cleanup(); 428 + } 429 + 430 + console.log('\n✅ All examples completed successfully!'); 431 + console.log('\nTry modifying this file to experiment with different patterns.'); 432 + } 433 + 434 + main().catch(console.error);
+4668
package-lock.json
··· 1 + { 2 + "name": "tiered-storage", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "tiered-storage", 9 + "version": "1.0.0", 10 + "dependencies": { 11 + "@aws-sdk/client-s3": "^3.500.0", 12 + "hono": "^4.10.7", 13 + "mime-types": "^3.0.2", 14 + "tiny-lru": "^11.0.0" 15 + }, 16 + "devDependencies": { 17 + "@types/bun": "^1.3.4", 18 + "@types/mime-types": "^3.0.1", 19 + "@types/node": "^24.10.1", 20 + "@typescript-eslint/eslint-plugin": "^8.48.1", 21 + "@typescript-eslint/parser": "^8.48.1", 22 + "eslint": "^9.39.1", 23 + "tsx": "^4.0.0", 24 + "typescript": "^5.3.0", 25 + "vitest": "^4.0.15" 26 + }, 27 + "engines": { 28 + "node": ">=18.0.0" 29 + } 30 + }, 31 + "node_modules/@aws-crypto/crc32": { 32 + "version": "5.2.0", 33 + "license": "Apache-2.0", 34 + "dependencies": { 35 + "@aws-crypto/util": "^5.2.0", 36 + "@aws-sdk/types": "^3.222.0", 37 + "tslib": "^2.6.2" 38 + }, 39 + "engines": { 40 + "node": ">=16.0.0" 41 + } 42 + }, 43 + "node_modules/@aws-crypto/crc32c": { 44 + "version": "5.2.0", 45 + "license": "Apache-2.0", 46 + "dependencies": { 47 + "@aws-crypto/util": "^5.2.0", 48 + "@aws-sdk/types": "^3.222.0", 49 + "tslib": "^2.6.2" 50 + } 51 + }, 52 + "node_modules/@aws-crypto/sha1-browser": { 53 + "version": "5.2.0", 54 + "license": "Apache-2.0", 55 + "dependencies": { 56 + "@aws-crypto/supports-web-crypto": "^5.2.0", 57 + "@aws-crypto/util": "^5.2.0", 58 + "@aws-sdk/types": "^3.222.0", 59 + "@aws-sdk/util-locate-window": "^3.0.0", 60 + "@smithy/util-utf8": "^2.0.0", 61 + "tslib": "^2.6.2" 62 + } 63 + }, 64 + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { 65 + "version": "2.3.0", 66 + "license": "Apache-2.0", 67 + "dependencies": { 68 + "@smithy/util-buffer-from": "^2.2.0", 69 + "tslib": "^2.6.2" 70 + }, 71 + "engines": { 72 + "node": ">=14.0.0" 73 + } 74 + }, 75 + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { 76 + "version": "2.2.0", 77 + "license": "Apache-2.0", 78 + "dependencies": { 79 + "@smithy/is-array-buffer": "^2.2.0", 80 + "tslib": "^2.6.2" 81 + }, 82 + "engines": { 83 + "node": ">=14.0.0" 84 + } 85 + }, 86 + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": { 87 + "version": "2.2.0", 88 + "license": "Apache-2.0", 89 + "dependencies": { 90 + "tslib": "^2.6.2" 91 + }, 92 + "engines": { 93 + "node": ">=14.0.0" 94 + } 95 + }, 96 + "node_modules/@aws-crypto/sha256-browser": { 97 + "version": "5.2.0", 98 + "license": "Apache-2.0", 99 + "dependencies": { 100 + "@aws-crypto/sha256-js": "^5.2.0", 101 + "@aws-crypto/supports-web-crypto": "^5.2.0", 102 + "@aws-crypto/util": "^5.2.0", 103 + "@aws-sdk/types": "^3.222.0", 104 + "@aws-sdk/util-locate-window": "^3.0.0", 105 + "@smithy/util-utf8": "^2.0.0", 106 + "tslib": "^2.6.2" 107 + } 108 + }, 109 + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { 110 + "version": "2.3.0", 111 + "license": "Apache-2.0", 112 + "dependencies": { 113 + "@smithy/util-buffer-from": "^2.2.0", 114 + "tslib": "^2.6.2" 115 + }, 116 + "engines": { 117 + "node": ">=14.0.0" 118 + } 119 + }, 120 + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { 121 + "version": "2.2.0", 122 + "license": "Apache-2.0", 123 + "dependencies": { 124 + "@smithy/is-array-buffer": "^2.2.0", 125 + "tslib": "^2.6.2" 126 + }, 127 + "engines": { 128 + "node": ">=14.0.0" 129 + } 130 + }, 131 + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": { 132 + "version": "2.2.0", 133 + "license": "Apache-2.0", 134 + "dependencies": { 135 + "tslib": "^2.6.2" 136 + }, 137 + "engines": { 138 + "node": ">=14.0.0" 139 + } 140 + }, 141 + "node_modules/@aws-crypto/sha256-js": { 142 + "version": "5.2.0", 143 + "license": "Apache-2.0", 144 + "dependencies": { 145 + "@aws-crypto/util": "^5.2.0", 146 + "@aws-sdk/types": "^3.222.0", 147 + "tslib": "^2.6.2" 148 + }, 149 + "engines": { 150 + "node": ">=16.0.0" 151 + } 152 + }, 153 + "node_modules/@aws-crypto/supports-web-crypto": { 154 + "version": "5.2.0", 155 + "license": "Apache-2.0", 156 + "dependencies": { 157 + "tslib": "^2.6.2" 158 + } 159 + }, 160 + "node_modules/@aws-crypto/util": { 161 + "version": "5.2.0", 162 + "license": "Apache-2.0", 163 + "dependencies": { 164 + "@aws-sdk/types": "^3.222.0", 165 + "@smithy/util-utf8": "^2.0.0", 166 + "tslib": "^2.6.2" 167 + } 168 + }, 169 + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { 170 + "version": "2.3.0", 171 + "license": "Apache-2.0", 172 + "dependencies": { 173 + "@smithy/util-buffer-from": "^2.2.0", 174 + "tslib": "^2.6.2" 175 + }, 176 + "engines": { 177 + "node": ">=14.0.0" 178 + } 179 + }, 180 + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { 181 + "version": "2.2.0", 182 + "license": "Apache-2.0", 183 + "dependencies": { 184 + "@smithy/is-array-buffer": "^2.2.0", 185 + "tslib": "^2.6.2" 186 + }, 187 + "engines": { 188 + "node": ">=14.0.0" 189 + } 190 + }, 191 + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": { 192 + "version": "2.2.0", 193 + "license": "Apache-2.0", 194 + "dependencies": { 195 + "tslib": "^2.6.2" 196 + }, 197 + "engines": { 198 + "node": ">=14.0.0" 199 + } 200 + }, 201 + "node_modules/@aws-sdk/client-s3": { 202 + "version": "3.946.0", 203 + "license": "Apache-2.0", 204 + "dependencies": { 205 + "@aws-crypto/sha1-browser": "5.2.0", 206 + "@aws-crypto/sha256-browser": "5.2.0", 207 + "@aws-crypto/sha256-js": "5.2.0", 208 + "@aws-sdk/core": "3.946.0", 209 + "@aws-sdk/credential-provider-node": "3.946.0", 210 + "@aws-sdk/middleware-bucket-endpoint": "3.936.0", 211 + "@aws-sdk/middleware-expect-continue": "3.936.0", 212 + "@aws-sdk/middleware-flexible-checksums": "3.946.0", 213 + "@aws-sdk/middleware-host-header": "3.936.0", 214 + "@aws-sdk/middleware-location-constraint": "3.936.0", 215 + "@aws-sdk/middleware-logger": "3.936.0", 216 + "@aws-sdk/middleware-recursion-detection": "3.936.0", 217 + "@aws-sdk/middleware-sdk-s3": "3.946.0", 218 + "@aws-sdk/middleware-ssec": "3.936.0", 219 + "@aws-sdk/middleware-user-agent": "3.946.0", 220 + "@aws-sdk/region-config-resolver": "3.936.0", 221 + "@aws-sdk/signature-v4-multi-region": "3.946.0", 222 + "@aws-sdk/types": "3.936.0", 223 + "@aws-sdk/util-endpoints": "3.936.0", 224 + "@aws-sdk/util-user-agent-browser": "3.936.0", 225 + "@aws-sdk/util-user-agent-node": "3.946.0", 226 + "@smithy/config-resolver": "^4.4.3", 227 + "@smithy/core": "^3.18.7", 228 + "@smithy/eventstream-serde-browser": "^4.2.5", 229 + "@smithy/eventstream-serde-config-resolver": "^4.3.5", 230 + "@smithy/eventstream-serde-node": "^4.2.5", 231 + "@smithy/fetch-http-handler": "^5.3.6", 232 + "@smithy/hash-blob-browser": "^4.2.6", 233 + "@smithy/hash-node": "^4.2.5", 234 + "@smithy/hash-stream-node": "^4.2.5", 235 + "@smithy/invalid-dependency": "^4.2.5", 236 + "@smithy/md5-js": "^4.2.5", 237 + "@smithy/middleware-content-length": "^4.2.5", 238 + "@smithy/middleware-endpoint": "^4.3.14", 239 + "@smithy/middleware-retry": "^4.4.14", 240 + "@smithy/middleware-serde": "^4.2.6", 241 + "@smithy/middleware-stack": "^4.2.5", 242 + "@smithy/node-config-provider": "^4.3.5", 243 + "@smithy/node-http-handler": "^4.4.5", 244 + "@smithy/protocol-http": "^5.3.5", 245 + "@smithy/smithy-client": "^4.9.10", 246 + "@smithy/types": "^4.9.0", 247 + "@smithy/url-parser": "^4.2.5", 248 + "@smithy/util-base64": "^4.3.0", 249 + "@smithy/util-body-length-browser": "^4.2.0", 250 + "@smithy/util-body-length-node": "^4.2.1", 251 + "@smithy/util-defaults-mode-browser": "^4.3.13", 252 + "@smithy/util-defaults-mode-node": "^4.2.16", 253 + "@smithy/util-endpoints": "^3.2.5", 254 + "@smithy/util-middleware": "^4.2.5", 255 + "@smithy/util-retry": "^4.2.5", 256 + "@smithy/util-stream": "^4.5.6", 257 + "@smithy/util-utf8": "^4.2.0", 258 + "@smithy/util-waiter": "^4.2.5", 259 + "tslib": "^2.6.2" 260 + }, 261 + "engines": { 262 + "node": ">=18.0.0" 263 + } 264 + }, 265 + "node_modules/@aws-sdk/client-sso": { 266 + "version": "3.946.0", 267 + "license": "Apache-2.0", 268 + "dependencies": { 269 + "@aws-crypto/sha256-browser": "5.2.0", 270 + "@aws-crypto/sha256-js": "5.2.0", 271 + "@aws-sdk/core": "3.946.0", 272 + "@aws-sdk/middleware-host-header": "3.936.0", 273 + "@aws-sdk/middleware-logger": "3.936.0", 274 + "@aws-sdk/middleware-recursion-detection": "3.936.0", 275 + "@aws-sdk/middleware-user-agent": "3.946.0", 276 + "@aws-sdk/region-config-resolver": "3.936.0", 277 + "@aws-sdk/types": "3.936.0", 278 + "@aws-sdk/util-endpoints": "3.936.0", 279 + "@aws-sdk/util-user-agent-browser": "3.936.0", 280 + "@aws-sdk/util-user-agent-node": "3.946.0", 281 + "@smithy/config-resolver": "^4.4.3", 282 + "@smithy/core": "^3.18.7", 283 + "@smithy/fetch-http-handler": "^5.3.6", 284 + "@smithy/hash-node": "^4.2.5", 285 + "@smithy/invalid-dependency": "^4.2.5", 286 + "@smithy/middleware-content-length": "^4.2.5", 287 + "@smithy/middleware-endpoint": "^4.3.14", 288 + "@smithy/middleware-retry": "^4.4.14", 289 + "@smithy/middleware-serde": "^4.2.6", 290 + "@smithy/middleware-stack": "^4.2.5", 291 + "@smithy/node-config-provider": "^4.3.5", 292 + "@smithy/node-http-handler": "^4.4.5", 293 + "@smithy/protocol-http": "^5.3.5", 294 + "@smithy/smithy-client": "^4.9.10", 295 + "@smithy/types": "^4.9.0", 296 + "@smithy/url-parser": "^4.2.5", 297 + "@smithy/util-base64": "^4.3.0", 298 + "@smithy/util-body-length-browser": "^4.2.0", 299 + "@smithy/util-body-length-node": "^4.2.1", 300 + "@smithy/util-defaults-mode-browser": "^4.3.13", 301 + "@smithy/util-defaults-mode-node": "^4.2.16", 302 + "@smithy/util-endpoints": "^3.2.5", 303 + "@smithy/util-middleware": "^4.2.5", 304 + "@smithy/util-retry": "^4.2.5", 305 + "@smithy/util-utf8": "^4.2.0", 306 + "tslib": "^2.6.2" 307 + }, 308 + "engines": { 309 + "node": ">=18.0.0" 310 + } 311 + }, 312 + "node_modules/@aws-sdk/core": { 313 + "version": "3.946.0", 314 + "license": "Apache-2.0", 315 + "dependencies": { 316 + "@aws-sdk/types": "3.936.0", 317 + "@aws-sdk/xml-builder": "3.930.0", 318 + "@smithy/core": "^3.18.7", 319 + "@smithy/node-config-provider": "^4.3.5", 320 + "@smithy/property-provider": "^4.2.5", 321 + "@smithy/protocol-http": "^5.3.5", 322 + "@smithy/signature-v4": "^5.3.5", 323 + "@smithy/smithy-client": "^4.9.10", 324 + "@smithy/types": "^4.9.0", 325 + "@smithy/util-base64": "^4.3.0", 326 + "@smithy/util-middleware": "^4.2.5", 327 + "@smithy/util-utf8": "^4.2.0", 328 + "tslib": "^2.6.2" 329 + }, 330 + "engines": { 331 + "node": ">=18.0.0" 332 + } 333 + }, 334 + "node_modules/@aws-sdk/credential-provider-env": { 335 + "version": "3.946.0", 336 + "license": "Apache-2.0", 337 + "dependencies": { 338 + "@aws-sdk/core": "3.946.0", 339 + "@aws-sdk/types": "3.936.0", 340 + "@smithy/property-provider": "^4.2.5", 341 + "@smithy/types": "^4.9.0", 342 + "tslib": "^2.6.2" 343 + }, 344 + "engines": { 345 + "node": ">=18.0.0" 346 + } 347 + }, 348 + "node_modules/@aws-sdk/credential-provider-http": { 349 + "version": "3.946.0", 350 + "license": "Apache-2.0", 351 + "dependencies": { 352 + "@aws-sdk/core": "3.946.0", 353 + "@aws-sdk/types": "3.936.0", 354 + "@smithy/fetch-http-handler": "^5.3.6", 355 + "@smithy/node-http-handler": "^4.4.5", 356 + "@smithy/property-provider": "^4.2.5", 357 + "@smithy/protocol-http": "^5.3.5", 358 + "@smithy/smithy-client": "^4.9.10", 359 + "@smithy/types": "^4.9.0", 360 + "@smithy/util-stream": "^4.5.6", 361 + "tslib": "^2.6.2" 362 + }, 363 + "engines": { 364 + "node": ">=18.0.0" 365 + } 366 + }, 367 + "node_modules/@aws-sdk/credential-provider-ini": { 368 + "version": "3.946.0", 369 + "license": "Apache-2.0", 370 + "dependencies": { 371 + "@aws-sdk/core": "3.946.0", 372 + "@aws-sdk/credential-provider-env": "3.946.0", 373 + "@aws-sdk/credential-provider-http": "3.946.0", 374 + "@aws-sdk/credential-provider-login": "3.946.0", 375 + "@aws-sdk/credential-provider-process": "3.946.0", 376 + "@aws-sdk/credential-provider-sso": "3.946.0", 377 + "@aws-sdk/credential-provider-web-identity": "3.946.0", 378 + "@aws-sdk/nested-clients": "3.946.0", 379 + "@aws-sdk/types": "3.936.0", 380 + "@smithy/credential-provider-imds": "^4.2.5", 381 + "@smithy/property-provider": "^4.2.5", 382 + "@smithy/shared-ini-file-loader": "^4.4.0", 383 + "@smithy/types": "^4.9.0", 384 + "tslib": "^2.6.2" 385 + }, 386 + "engines": { 387 + "node": ">=18.0.0" 388 + } 389 + }, 390 + "node_modules/@aws-sdk/credential-provider-login": { 391 + "version": "3.946.0", 392 + "license": "Apache-2.0", 393 + "dependencies": { 394 + "@aws-sdk/core": "3.946.0", 395 + "@aws-sdk/nested-clients": "3.946.0", 396 + "@aws-sdk/types": "3.936.0", 397 + "@smithy/property-provider": "^4.2.5", 398 + "@smithy/protocol-http": "^5.3.5", 399 + "@smithy/shared-ini-file-loader": "^4.4.0", 400 + "@smithy/types": "^4.9.0", 401 + "tslib": "^2.6.2" 402 + }, 403 + "engines": { 404 + "node": ">=18.0.0" 405 + } 406 + }, 407 + "node_modules/@aws-sdk/credential-provider-node": { 408 + "version": "3.946.0", 409 + "license": "Apache-2.0", 410 + "dependencies": { 411 + "@aws-sdk/credential-provider-env": "3.946.0", 412 + "@aws-sdk/credential-provider-http": "3.946.0", 413 + "@aws-sdk/credential-provider-ini": "3.946.0", 414 + "@aws-sdk/credential-provider-process": "3.946.0", 415 + "@aws-sdk/credential-provider-sso": "3.946.0", 416 + "@aws-sdk/credential-provider-web-identity": "3.946.0", 417 + "@aws-sdk/types": "3.936.0", 418 + "@smithy/credential-provider-imds": "^4.2.5", 419 + "@smithy/property-provider": "^4.2.5", 420 + "@smithy/shared-ini-file-loader": "^4.4.0", 421 + "@smithy/types": "^4.9.0", 422 + "tslib": "^2.6.2" 423 + }, 424 + "engines": { 425 + "node": ">=18.0.0" 426 + } 427 + }, 428 + "node_modules/@aws-sdk/credential-provider-process": { 429 + "version": "3.946.0", 430 + "license": "Apache-2.0", 431 + "dependencies": { 432 + "@aws-sdk/core": "3.946.0", 433 + "@aws-sdk/types": "3.936.0", 434 + "@smithy/property-provider": "^4.2.5", 435 + "@smithy/shared-ini-file-loader": "^4.4.0", 436 + "@smithy/types": "^4.9.0", 437 + "tslib": "^2.6.2" 438 + }, 439 + "engines": { 440 + "node": ">=18.0.0" 441 + } 442 + }, 443 + "node_modules/@aws-sdk/credential-provider-sso": { 444 + "version": "3.946.0", 445 + "license": "Apache-2.0", 446 + "dependencies": { 447 + "@aws-sdk/client-sso": "3.946.0", 448 + "@aws-sdk/core": "3.946.0", 449 + "@aws-sdk/token-providers": "3.946.0", 450 + "@aws-sdk/types": "3.936.0", 451 + "@smithy/property-provider": "^4.2.5", 452 + "@smithy/shared-ini-file-loader": "^4.4.0", 453 + "@smithy/types": "^4.9.0", 454 + "tslib": "^2.6.2" 455 + }, 456 + "engines": { 457 + "node": ">=18.0.0" 458 + } 459 + }, 460 + "node_modules/@aws-sdk/credential-provider-web-identity": { 461 + "version": "3.946.0", 462 + "license": "Apache-2.0", 463 + "dependencies": { 464 + "@aws-sdk/core": "3.946.0", 465 + "@aws-sdk/nested-clients": "3.946.0", 466 + "@aws-sdk/types": "3.936.0", 467 + "@smithy/property-provider": "^4.2.5", 468 + "@smithy/shared-ini-file-loader": "^4.4.0", 469 + "@smithy/types": "^4.9.0", 470 + "tslib": "^2.6.2" 471 + }, 472 + "engines": { 473 + "node": ">=18.0.0" 474 + } 475 + }, 476 + "node_modules/@aws-sdk/middleware-bucket-endpoint": { 477 + "version": "3.936.0", 478 + "license": "Apache-2.0", 479 + "dependencies": { 480 + "@aws-sdk/types": "3.936.0", 481 + "@aws-sdk/util-arn-parser": "3.893.0", 482 + "@smithy/node-config-provider": "^4.3.5", 483 + "@smithy/protocol-http": "^5.3.5", 484 + "@smithy/types": "^4.9.0", 485 + "@smithy/util-config-provider": "^4.2.0", 486 + "tslib": "^2.6.2" 487 + }, 488 + "engines": { 489 + "node": ">=18.0.0" 490 + } 491 + }, 492 + "node_modules/@aws-sdk/middleware-expect-continue": { 493 + "version": "3.936.0", 494 + "license": "Apache-2.0", 495 + "dependencies": { 496 + "@aws-sdk/types": "3.936.0", 497 + "@smithy/protocol-http": "^5.3.5", 498 + "@smithy/types": "^4.9.0", 499 + "tslib": "^2.6.2" 500 + }, 501 + "engines": { 502 + "node": ">=18.0.0" 503 + } 504 + }, 505 + "node_modules/@aws-sdk/middleware-flexible-checksums": { 506 + "version": "3.946.0", 507 + "license": "Apache-2.0", 508 + "dependencies": { 509 + "@aws-crypto/crc32": "5.2.0", 510 + "@aws-crypto/crc32c": "5.2.0", 511 + "@aws-crypto/util": "5.2.0", 512 + "@aws-sdk/core": "3.946.0", 513 + "@aws-sdk/types": "3.936.0", 514 + "@smithy/is-array-buffer": "^4.2.0", 515 + "@smithy/node-config-provider": "^4.3.5", 516 + "@smithy/protocol-http": "^5.3.5", 517 + "@smithy/types": "^4.9.0", 518 + "@smithy/util-middleware": "^4.2.5", 519 + "@smithy/util-stream": "^4.5.6", 520 + "@smithy/util-utf8": "^4.2.0", 521 + "tslib": "^2.6.2" 522 + }, 523 + "engines": { 524 + "node": ">=18.0.0" 525 + } 526 + }, 527 + "node_modules/@aws-sdk/middleware-host-header": { 528 + "version": "3.936.0", 529 + "license": "Apache-2.0", 530 + "dependencies": { 531 + "@aws-sdk/types": "3.936.0", 532 + "@smithy/protocol-http": "^5.3.5", 533 + "@smithy/types": "^4.9.0", 534 + "tslib": "^2.6.2" 535 + }, 536 + "engines": { 537 + "node": ">=18.0.0" 538 + } 539 + }, 540 + "node_modules/@aws-sdk/middleware-location-constraint": { 541 + "version": "3.936.0", 542 + "license": "Apache-2.0", 543 + "dependencies": { 544 + "@aws-sdk/types": "3.936.0", 545 + "@smithy/types": "^4.9.0", 546 + "tslib": "^2.6.2" 547 + }, 548 + "engines": { 549 + "node": ">=18.0.0" 550 + } 551 + }, 552 + "node_modules/@aws-sdk/middleware-logger": { 553 + "version": "3.936.0", 554 + "license": "Apache-2.0", 555 + "dependencies": { 556 + "@aws-sdk/types": "3.936.0", 557 + "@smithy/types": "^4.9.0", 558 + "tslib": "^2.6.2" 559 + }, 560 + "engines": { 561 + "node": ">=18.0.0" 562 + } 563 + }, 564 + "node_modules/@aws-sdk/middleware-recursion-detection": { 565 + "version": "3.936.0", 566 + "license": "Apache-2.0", 567 + "dependencies": { 568 + "@aws-sdk/types": "3.936.0", 569 + "@aws/lambda-invoke-store": "^0.2.0", 570 + "@smithy/protocol-http": "^5.3.5", 571 + "@smithy/types": "^4.9.0", 572 + "tslib": "^2.6.2" 573 + }, 574 + "engines": { 575 + "node": ">=18.0.0" 576 + } 577 + }, 578 + "node_modules/@aws-sdk/middleware-sdk-s3": { 579 + "version": "3.946.0", 580 + "license": "Apache-2.0", 581 + "dependencies": { 582 + "@aws-sdk/core": "3.946.0", 583 + "@aws-sdk/types": "3.936.0", 584 + "@aws-sdk/util-arn-parser": "3.893.0", 585 + "@smithy/core": "^3.18.7", 586 + "@smithy/node-config-provider": "^4.3.5", 587 + "@smithy/protocol-http": "^5.3.5", 588 + "@smithy/signature-v4": "^5.3.5", 589 + "@smithy/smithy-client": "^4.9.10", 590 + "@smithy/types": "^4.9.0", 591 + "@smithy/util-config-provider": "^4.2.0", 592 + "@smithy/util-middleware": "^4.2.5", 593 + "@smithy/util-stream": "^4.5.6", 594 + "@smithy/util-utf8": "^4.2.0", 595 + "tslib": "^2.6.2" 596 + }, 597 + "engines": { 598 + "node": ">=18.0.0" 599 + } 600 + }, 601 + "node_modules/@aws-sdk/middleware-ssec": { 602 + "version": "3.936.0", 603 + "license": "Apache-2.0", 604 + "dependencies": { 605 + "@aws-sdk/types": "3.936.0", 606 + "@smithy/types": "^4.9.0", 607 + "tslib": "^2.6.2" 608 + }, 609 + "engines": { 610 + "node": ">=18.0.0" 611 + } 612 + }, 613 + "node_modules/@aws-sdk/middleware-user-agent": { 614 + "version": "3.946.0", 615 + "license": "Apache-2.0", 616 + "dependencies": { 617 + "@aws-sdk/core": "3.946.0", 618 + "@aws-sdk/types": "3.936.0", 619 + "@aws-sdk/util-endpoints": "3.936.0", 620 + "@smithy/core": "^3.18.7", 621 + "@smithy/protocol-http": "^5.3.5", 622 + "@smithy/types": "^4.9.0", 623 + "tslib": "^2.6.2" 624 + }, 625 + "engines": { 626 + "node": ">=18.0.0" 627 + } 628 + }, 629 + "node_modules/@aws-sdk/nested-clients": { 630 + "version": "3.946.0", 631 + "license": "Apache-2.0", 632 + "dependencies": { 633 + "@aws-crypto/sha256-browser": "5.2.0", 634 + "@aws-crypto/sha256-js": "5.2.0", 635 + "@aws-sdk/core": "3.946.0", 636 + "@aws-sdk/middleware-host-header": "3.936.0", 637 + "@aws-sdk/middleware-logger": "3.936.0", 638 + "@aws-sdk/middleware-recursion-detection": "3.936.0", 639 + "@aws-sdk/middleware-user-agent": "3.946.0", 640 + "@aws-sdk/region-config-resolver": "3.936.0", 641 + "@aws-sdk/types": "3.936.0", 642 + "@aws-sdk/util-endpoints": "3.936.0", 643 + "@aws-sdk/util-user-agent-browser": "3.936.0", 644 + "@aws-sdk/util-user-agent-node": "3.946.0", 645 + "@smithy/config-resolver": "^4.4.3", 646 + "@smithy/core": "^3.18.7", 647 + "@smithy/fetch-http-handler": "^5.3.6", 648 + "@smithy/hash-node": "^4.2.5", 649 + "@smithy/invalid-dependency": "^4.2.5", 650 + "@smithy/middleware-content-length": "^4.2.5", 651 + "@smithy/middleware-endpoint": "^4.3.14", 652 + "@smithy/middleware-retry": "^4.4.14", 653 + "@smithy/middleware-serde": "^4.2.6", 654 + "@smithy/middleware-stack": "^4.2.5", 655 + "@smithy/node-config-provider": "^4.3.5", 656 + "@smithy/node-http-handler": "^4.4.5", 657 + "@smithy/protocol-http": "^5.3.5", 658 + "@smithy/smithy-client": "^4.9.10", 659 + "@smithy/types": "^4.9.0", 660 + "@smithy/url-parser": "^4.2.5", 661 + "@smithy/util-base64": "^4.3.0", 662 + "@smithy/util-body-length-browser": "^4.2.0", 663 + "@smithy/util-body-length-node": "^4.2.1", 664 + "@smithy/util-defaults-mode-browser": "^4.3.13", 665 + "@smithy/util-defaults-mode-node": "^4.2.16", 666 + "@smithy/util-endpoints": "^3.2.5", 667 + "@smithy/util-middleware": "^4.2.5", 668 + "@smithy/util-retry": "^4.2.5", 669 + "@smithy/util-utf8": "^4.2.0", 670 + "tslib": "^2.6.2" 671 + }, 672 + "engines": { 673 + "node": ">=18.0.0" 674 + } 675 + }, 676 + "node_modules/@aws-sdk/region-config-resolver": { 677 + "version": "3.936.0", 678 + "license": "Apache-2.0", 679 + "dependencies": { 680 + "@aws-sdk/types": "3.936.0", 681 + "@smithy/config-resolver": "^4.4.3", 682 + "@smithy/node-config-provider": "^4.3.5", 683 + "@smithy/types": "^4.9.0", 684 + "tslib": "^2.6.2" 685 + }, 686 + "engines": { 687 + "node": ">=18.0.0" 688 + } 689 + }, 690 + "node_modules/@aws-sdk/signature-v4-multi-region": { 691 + "version": "3.946.0", 692 + "license": "Apache-2.0", 693 + "dependencies": { 694 + "@aws-sdk/middleware-sdk-s3": "3.946.0", 695 + "@aws-sdk/types": "3.936.0", 696 + "@smithy/protocol-http": "^5.3.5", 697 + "@smithy/signature-v4": "^5.3.5", 698 + "@smithy/types": "^4.9.0", 699 + "tslib": "^2.6.2" 700 + }, 701 + "engines": { 702 + "node": ">=18.0.0" 703 + } 704 + }, 705 + "node_modules/@aws-sdk/token-providers": { 706 + "version": "3.946.0", 707 + "license": "Apache-2.0", 708 + "dependencies": { 709 + "@aws-sdk/core": "3.946.0", 710 + "@aws-sdk/nested-clients": "3.946.0", 711 + "@aws-sdk/types": "3.936.0", 712 + "@smithy/property-provider": "^4.2.5", 713 + "@smithy/shared-ini-file-loader": "^4.4.0", 714 + "@smithy/types": "^4.9.0", 715 + "tslib": "^2.6.2" 716 + }, 717 + "engines": { 718 + "node": ">=18.0.0" 719 + } 720 + }, 721 + "node_modules/@aws-sdk/types": { 722 + "version": "3.936.0", 723 + "license": "Apache-2.0", 724 + "dependencies": { 725 + "@smithy/types": "^4.9.0", 726 + "tslib": "^2.6.2" 727 + }, 728 + "engines": { 729 + "node": ">=18.0.0" 730 + } 731 + }, 732 + "node_modules/@aws-sdk/util-arn-parser": { 733 + "version": "3.893.0", 734 + "license": "Apache-2.0", 735 + "dependencies": { 736 + "tslib": "^2.6.2" 737 + }, 738 + "engines": { 739 + "node": ">=18.0.0" 740 + } 741 + }, 742 + "node_modules/@aws-sdk/util-endpoints": { 743 + "version": "3.936.0", 744 + "license": "Apache-2.0", 745 + "dependencies": { 746 + "@aws-sdk/types": "3.936.0", 747 + "@smithy/types": "^4.9.0", 748 + "@smithy/url-parser": "^4.2.5", 749 + "@smithy/util-endpoints": "^3.2.5", 750 + "tslib": "^2.6.2" 751 + }, 752 + "engines": { 753 + "node": ">=18.0.0" 754 + } 755 + }, 756 + "node_modules/@aws-sdk/util-locate-window": { 757 + "version": "3.893.0", 758 + "license": "Apache-2.0", 759 + "dependencies": { 760 + "tslib": "^2.6.2" 761 + }, 762 + "engines": { 763 + "node": ">=18.0.0" 764 + } 765 + }, 766 + "node_modules/@aws-sdk/util-user-agent-browser": { 767 + "version": "3.936.0", 768 + "license": "Apache-2.0", 769 + "dependencies": { 770 + "@aws-sdk/types": "3.936.0", 771 + "@smithy/types": "^4.9.0", 772 + "bowser": "^2.11.0", 773 + "tslib": "^2.6.2" 774 + } 775 + }, 776 + "node_modules/@aws-sdk/util-user-agent-node": { 777 + "version": "3.946.0", 778 + "license": "Apache-2.0", 779 + "dependencies": { 780 + "@aws-sdk/middleware-user-agent": "3.946.0", 781 + "@aws-sdk/types": "3.936.0", 782 + "@smithy/node-config-provider": "^4.3.5", 783 + "@smithy/types": "^4.9.0", 784 + "tslib": "^2.6.2" 785 + }, 786 + "engines": { 787 + "node": ">=18.0.0" 788 + }, 789 + "peerDependencies": { 790 + "aws-crt": ">=1.0.0" 791 + }, 792 + "peerDependenciesMeta": { 793 + "aws-crt": { 794 + "optional": true 795 + } 796 + } 797 + }, 798 + "node_modules/@aws-sdk/xml-builder": { 799 + "version": "3.930.0", 800 + "license": "Apache-2.0", 801 + "dependencies": { 802 + "@smithy/types": "^4.9.0", 803 + "fast-xml-parser": "5.2.5", 804 + "tslib": "^2.6.2" 805 + }, 806 + "engines": { 807 + "node": ">=18.0.0" 808 + } 809 + }, 810 + "node_modules/@aws/lambda-invoke-store": { 811 + "version": "0.2.2", 812 + "license": "Apache-2.0", 813 + "engines": { 814 + "node": ">=18.0.0" 815 + } 816 + }, 817 + "node_modules/@esbuild/aix-ppc64": { 818 + "version": "0.27.1", 819 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", 820 + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", 821 + "cpu": [ 822 + "ppc64" 823 + ], 824 + "dev": true, 825 + "license": "MIT", 826 + "optional": true, 827 + "os": [ 828 + "aix" 829 + ], 830 + "engines": { 831 + "node": ">=18" 832 + } 833 + }, 834 + "node_modules/@esbuild/android-arm": { 835 + "version": "0.27.1", 836 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", 837 + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", 838 + "cpu": [ 839 + "arm" 840 + ], 841 + "dev": true, 842 + "license": "MIT", 843 + "optional": true, 844 + "os": [ 845 + "android" 846 + ], 847 + "engines": { 848 + "node": ">=18" 849 + } 850 + }, 851 + "node_modules/@esbuild/android-arm64": { 852 + "version": "0.27.1", 853 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", 854 + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", 855 + "cpu": [ 856 + "arm64" 857 + ], 858 + "dev": true, 859 + "license": "MIT", 860 + "optional": true, 861 + "os": [ 862 + "android" 863 + ], 864 + "engines": { 865 + "node": ">=18" 866 + } 867 + }, 868 + "node_modules/@esbuild/android-x64": { 869 + "version": "0.27.1", 870 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", 871 + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", 872 + "cpu": [ 873 + "x64" 874 + ], 875 + "dev": true, 876 + "license": "MIT", 877 + "optional": true, 878 + "os": [ 879 + "android" 880 + ], 881 + "engines": { 882 + "node": ">=18" 883 + } 884 + }, 885 + "node_modules/@esbuild/darwin-arm64": { 886 + "version": "0.27.1", 887 + "cpu": [ 888 + "arm64" 889 + ], 890 + "dev": true, 891 + "license": "MIT", 892 + "optional": true, 893 + "os": [ 894 + "darwin" 895 + ], 896 + "engines": { 897 + "node": ">=18" 898 + } 899 + }, 900 + "node_modules/@esbuild/darwin-x64": { 901 + "version": "0.27.1", 902 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", 903 + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", 904 + "cpu": [ 905 + "x64" 906 + ], 907 + "dev": true, 908 + "license": "MIT", 909 + "optional": true, 910 + "os": [ 911 + "darwin" 912 + ], 913 + "engines": { 914 + "node": ">=18" 915 + } 916 + }, 917 + "node_modules/@esbuild/freebsd-arm64": { 918 + "version": "0.27.1", 919 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", 920 + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", 921 + "cpu": [ 922 + "arm64" 923 + ], 924 + "dev": true, 925 + "license": "MIT", 926 + "optional": true, 927 + "os": [ 928 + "freebsd" 929 + ], 930 + "engines": { 931 + "node": ">=18" 932 + } 933 + }, 934 + "node_modules/@esbuild/freebsd-x64": { 935 + "version": "0.27.1", 936 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", 937 + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", 938 + "cpu": [ 939 + "x64" 940 + ], 941 + "dev": true, 942 + "license": "MIT", 943 + "optional": true, 944 + "os": [ 945 + "freebsd" 946 + ], 947 + "engines": { 948 + "node": ">=18" 949 + } 950 + }, 951 + "node_modules/@esbuild/linux-arm": { 952 + "version": "0.27.1", 953 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", 954 + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", 955 + "cpu": [ 956 + "arm" 957 + ], 958 + "dev": true, 959 + "license": "MIT", 960 + "optional": true, 961 + "os": [ 962 + "linux" 963 + ], 964 + "engines": { 965 + "node": ">=18" 966 + } 967 + }, 968 + "node_modules/@esbuild/linux-arm64": { 969 + "version": "0.27.1", 970 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", 971 + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", 972 + "cpu": [ 973 + "arm64" 974 + ], 975 + "dev": true, 976 + "license": "MIT", 977 + "optional": true, 978 + "os": [ 979 + "linux" 980 + ], 981 + "engines": { 982 + "node": ">=18" 983 + } 984 + }, 985 + "node_modules/@esbuild/linux-ia32": { 986 + "version": "0.27.1", 987 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", 988 + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", 989 + "cpu": [ 990 + "ia32" 991 + ], 992 + "dev": true, 993 + "license": "MIT", 994 + "optional": true, 995 + "os": [ 996 + "linux" 997 + ], 998 + "engines": { 999 + "node": ">=18" 1000 + } 1001 + }, 1002 + "node_modules/@esbuild/linux-loong64": { 1003 + "version": "0.27.1", 1004 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", 1005 + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", 1006 + "cpu": [ 1007 + "loong64" 1008 + ], 1009 + "dev": true, 1010 + "license": "MIT", 1011 + "optional": true, 1012 + "os": [ 1013 + "linux" 1014 + ], 1015 + "engines": { 1016 + "node": ">=18" 1017 + } 1018 + }, 1019 + "node_modules/@esbuild/linux-mips64el": { 1020 + "version": "0.27.1", 1021 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", 1022 + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", 1023 + "cpu": [ 1024 + "mips64el" 1025 + ], 1026 + "dev": true, 1027 + "license": "MIT", 1028 + "optional": true, 1029 + "os": [ 1030 + "linux" 1031 + ], 1032 + "engines": { 1033 + "node": ">=18" 1034 + } 1035 + }, 1036 + "node_modules/@esbuild/linux-ppc64": { 1037 + "version": "0.27.1", 1038 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", 1039 + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", 1040 + "cpu": [ 1041 + "ppc64" 1042 + ], 1043 + "dev": true, 1044 + "license": "MIT", 1045 + "optional": true, 1046 + "os": [ 1047 + "linux" 1048 + ], 1049 + "engines": { 1050 + "node": ">=18" 1051 + } 1052 + }, 1053 + "node_modules/@esbuild/linux-riscv64": { 1054 + "version": "0.27.1", 1055 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", 1056 + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", 1057 + "cpu": [ 1058 + "riscv64" 1059 + ], 1060 + "dev": true, 1061 + "license": "MIT", 1062 + "optional": true, 1063 + "os": [ 1064 + "linux" 1065 + ], 1066 + "engines": { 1067 + "node": ">=18" 1068 + } 1069 + }, 1070 + "node_modules/@esbuild/linux-s390x": { 1071 + "version": "0.27.1", 1072 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", 1073 + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", 1074 + "cpu": [ 1075 + "s390x" 1076 + ], 1077 + "dev": true, 1078 + "license": "MIT", 1079 + "optional": true, 1080 + "os": [ 1081 + "linux" 1082 + ], 1083 + "engines": { 1084 + "node": ">=18" 1085 + } 1086 + }, 1087 + "node_modules/@esbuild/linux-x64": { 1088 + "version": "0.27.1", 1089 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", 1090 + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", 1091 + "cpu": [ 1092 + "x64" 1093 + ], 1094 + "dev": true, 1095 + "license": "MIT", 1096 + "optional": true, 1097 + "os": [ 1098 + "linux" 1099 + ], 1100 + "engines": { 1101 + "node": ">=18" 1102 + } 1103 + }, 1104 + "node_modules/@esbuild/netbsd-arm64": { 1105 + "version": "0.27.1", 1106 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", 1107 + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", 1108 + "cpu": [ 1109 + "arm64" 1110 + ], 1111 + "dev": true, 1112 + "license": "MIT", 1113 + "optional": true, 1114 + "os": [ 1115 + "netbsd" 1116 + ], 1117 + "engines": { 1118 + "node": ">=18" 1119 + } 1120 + }, 1121 + "node_modules/@esbuild/netbsd-x64": { 1122 + "version": "0.27.1", 1123 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", 1124 + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", 1125 + "cpu": [ 1126 + "x64" 1127 + ], 1128 + "dev": true, 1129 + "license": "MIT", 1130 + "optional": true, 1131 + "os": [ 1132 + "netbsd" 1133 + ], 1134 + "engines": { 1135 + "node": ">=18" 1136 + } 1137 + }, 1138 + "node_modules/@esbuild/openbsd-arm64": { 1139 + "version": "0.27.1", 1140 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", 1141 + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", 1142 + "cpu": [ 1143 + "arm64" 1144 + ], 1145 + "dev": true, 1146 + "license": "MIT", 1147 + "optional": true, 1148 + "os": [ 1149 + "openbsd" 1150 + ], 1151 + "engines": { 1152 + "node": ">=18" 1153 + } 1154 + }, 1155 + "node_modules/@esbuild/openbsd-x64": { 1156 + "version": "0.27.1", 1157 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", 1158 + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", 1159 + "cpu": [ 1160 + "x64" 1161 + ], 1162 + "dev": true, 1163 + "license": "MIT", 1164 + "optional": true, 1165 + "os": [ 1166 + "openbsd" 1167 + ], 1168 + "engines": { 1169 + "node": ">=18" 1170 + } 1171 + }, 1172 + "node_modules/@esbuild/openharmony-arm64": { 1173 + "version": "0.27.1", 1174 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", 1175 + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", 1176 + "cpu": [ 1177 + "arm64" 1178 + ], 1179 + "dev": true, 1180 + "license": "MIT", 1181 + "optional": true, 1182 + "os": [ 1183 + "openharmony" 1184 + ], 1185 + "engines": { 1186 + "node": ">=18" 1187 + } 1188 + }, 1189 + "node_modules/@esbuild/sunos-x64": { 1190 + "version": "0.27.1", 1191 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", 1192 + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", 1193 + "cpu": [ 1194 + "x64" 1195 + ], 1196 + "dev": true, 1197 + "license": "MIT", 1198 + "optional": true, 1199 + "os": [ 1200 + "sunos" 1201 + ], 1202 + "engines": { 1203 + "node": ">=18" 1204 + } 1205 + }, 1206 + "node_modules/@esbuild/win32-arm64": { 1207 + "version": "0.27.1", 1208 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", 1209 + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", 1210 + "cpu": [ 1211 + "arm64" 1212 + ], 1213 + "dev": true, 1214 + "license": "MIT", 1215 + "optional": true, 1216 + "os": [ 1217 + "win32" 1218 + ], 1219 + "engines": { 1220 + "node": ">=18" 1221 + } 1222 + }, 1223 + "node_modules/@esbuild/win32-ia32": { 1224 + "version": "0.27.1", 1225 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", 1226 + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", 1227 + "cpu": [ 1228 + "ia32" 1229 + ], 1230 + "dev": true, 1231 + "license": "MIT", 1232 + "optional": true, 1233 + "os": [ 1234 + "win32" 1235 + ], 1236 + "engines": { 1237 + "node": ">=18" 1238 + } 1239 + }, 1240 + "node_modules/@esbuild/win32-x64": { 1241 + "version": "0.27.1", 1242 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", 1243 + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", 1244 + "cpu": [ 1245 + "x64" 1246 + ], 1247 + "dev": true, 1248 + "license": "MIT", 1249 + "optional": true, 1250 + "os": [ 1251 + "win32" 1252 + ], 1253 + "engines": { 1254 + "node": ">=18" 1255 + } 1256 + }, 1257 + "node_modules/@eslint-community/eslint-utils": { 1258 + "version": "4.9.0", 1259 + "dev": true, 1260 + "license": "MIT", 1261 + "dependencies": { 1262 + "eslint-visitor-keys": "^3.4.3" 1263 + }, 1264 + "engines": { 1265 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1266 + }, 1267 + "funding": { 1268 + "url": "https://opencollective.com/eslint" 1269 + }, 1270 + "peerDependencies": { 1271 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 1272 + } 1273 + }, 1274 + "node_modules/@eslint-community/regexpp": { 1275 + "version": "4.12.2", 1276 + "dev": true, 1277 + "license": "MIT", 1278 + "engines": { 1279 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 1280 + } 1281 + }, 1282 + "node_modules/@eslint/config-array": { 1283 + "version": "0.21.1", 1284 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", 1285 + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", 1286 + "dev": true, 1287 + "license": "Apache-2.0", 1288 + "dependencies": { 1289 + "@eslint/object-schema": "^2.1.7", 1290 + "debug": "^4.3.1", 1291 + "minimatch": "^3.1.2" 1292 + }, 1293 + "engines": { 1294 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1295 + } 1296 + }, 1297 + "node_modules/@eslint/config-helpers": { 1298 + "version": "0.4.2", 1299 + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", 1300 + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", 1301 + "dev": true, 1302 + "license": "Apache-2.0", 1303 + "dependencies": { 1304 + "@eslint/core": "^0.17.0" 1305 + }, 1306 + "engines": { 1307 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1308 + } 1309 + }, 1310 + "node_modules/@eslint/core": { 1311 + "version": "0.17.0", 1312 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", 1313 + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", 1314 + "dev": true, 1315 + "license": "Apache-2.0", 1316 + "dependencies": { 1317 + "@types/json-schema": "^7.0.15" 1318 + }, 1319 + "engines": { 1320 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1321 + } 1322 + }, 1323 + "node_modules/@eslint/eslintrc": { 1324 + "version": "3.3.3", 1325 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", 1326 + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", 1327 + "dev": true, 1328 + "license": "MIT", 1329 + "dependencies": { 1330 + "ajv": "^6.12.4", 1331 + "debug": "^4.3.2", 1332 + "espree": "^10.0.1", 1333 + "globals": "^14.0.0", 1334 + "ignore": "^5.2.0", 1335 + "import-fresh": "^3.2.1", 1336 + "js-yaml": "^4.1.1", 1337 + "minimatch": "^3.1.2", 1338 + "strip-json-comments": "^3.1.1" 1339 + }, 1340 + "engines": { 1341 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1342 + }, 1343 + "funding": { 1344 + "url": "https://opencollective.com/eslint" 1345 + } 1346 + }, 1347 + "node_modules/@eslint/eslintrc/node_modules/ignore": { 1348 + "version": "5.3.2", 1349 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1350 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1351 + "dev": true, 1352 + "license": "MIT", 1353 + "engines": { 1354 + "node": ">= 4" 1355 + } 1356 + }, 1357 + "node_modules/@eslint/js": { 1358 + "version": "9.39.1", 1359 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", 1360 + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", 1361 + "dev": true, 1362 + "license": "MIT", 1363 + "engines": { 1364 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1365 + }, 1366 + "funding": { 1367 + "url": "https://eslint.org/donate" 1368 + } 1369 + }, 1370 + "node_modules/@eslint/object-schema": { 1371 + "version": "2.1.7", 1372 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", 1373 + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", 1374 + "dev": true, 1375 + "license": "Apache-2.0", 1376 + "engines": { 1377 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1378 + } 1379 + }, 1380 + "node_modules/@eslint/plugin-kit": { 1381 + "version": "0.4.1", 1382 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", 1383 + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", 1384 + "dev": true, 1385 + "license": "Apache-2.0", 1386 + "dependencies": { 1387 + "@eslint/core": "^0.17.0", 1388 + "levn": "^0.4.1" 1389 + }, 1390 + "engines": { 1391 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1392 + } 1393 + }, 1394 + "node_modules/@humanfs/core": { 1395 + "version": "0.19.1", 1396 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 1397 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 1398 + "dev": true, 1399 + "license": "Apache-2.0", 1400 + "engines": { 1401 + "node": ">=18.18.0" 1402 + } 1403 + }, 1404 + "node_modules/@humanfs/node": { 1405 + "version": "0.16.7", 1406 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 1407 + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 1408 + "dev": true, 1409 + "license": "Apache-2.0", 1410 + "dependencies": { 1411 + "@humanfs/core": "^0.19.1", 1412 + "@humanwhocodes/retry": "^0.4.0" 1413 + }, 1414 + "engines": { 1415 + "node": ">=18.18.0" 1416 + } 1417 + }, 1418 + "node_modules/@humanwhocodes/module-importer": { 1419 + "version": "1.0.1", 1420 + "dev": true, 1421 + "license": "Apache-2.0", 1422 + "engines": { 1423 + "node": ">=12.22" 1424 + }, 1425 + "funding": { 1426 + "type": "github", 1427 + "url": "https://github.com/sponsors/nzakas" 1428 + } 1429 + }, 1430 + "node_modules/@humanwhocodes/retry": { 1431 + "version": "0.4.3", 1432 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 1433 + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 1434 + "dev": true, 1435 + "license": "Apache-2.0", 1436 + "engines": { 1437 + "node": ">=18.18" 1438 + }, 1439 + "funding": { 1440 + "type": "github", 1441 + "url": "https://github.com/sponsors/nzakas" 1442 + } 1443 + }, 1444 + "node_modules/@jridgewell/sourcemap-codec": { 1445 + "version": "1.5.5", 1446 + "dev": true, 1447 + "license": "MIT" 1448 + }, 1449 + "node_modules/@rollup/rollup-android-arm-eabi": { 1450 + "version": "4.53.3", 1451 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", 1452 + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", 1453 + "cpu": [ 1454 + "arm" 1455 + ], 1456 + "dev": true, 1457 + "license": "MIT", 1458 + "optional": true, 1459 + "os": [ 1460 + "android" 1461 + ] 1462 + }, 1463 + "node_modules/@rollup/rollup-android-arm64": { 1464 + "version": "4.53.3", 1465 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", 1466 + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", 1467 + "cpu": [ 1468 + "arm64" 1469 + ], 1470 + "dev": true, 1471 + "license": "MIT", 1472 + "optional": true, 1473 + "os": [ 1474 + "android" 1475 + ] 1476 + }, 1477 + "node_modules/@rollup/rollup-darwin-arm64": { 1478 + "version": "4.53.3", 1479 + "cpu": [ 1480 + "arm64" 1481 + ], 1482 + "dev": true, 1483 + "license": "MIT", 1484 + "optional": true, 1485 + "os": [ 1486 + "darwin" 1487 + ] 1488 + }, 1489 + "node_modules/@rollup/rollup-darwin-x64": { 1490 + "version": "4.53.3", 1491 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", 1492 + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", 1493 + "cpu": [ 1494 + "x64" 1495 + ], 1496 + "dev": true, 1497 + "license": "MIT", 1498 + "optional": true, 1499 + "os": [ 1500 + "darwin" 1501 + ] 1502 + }, 1503 + "node_modules/@rollup/rollup-freebsd-arm64": { 1504 + "version": "4.53.3", 1505 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", 1506 + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", 1507 + "cpu": [ 1508 + "arm64" 1509 + ], 1510 + "dev": true, 1511 + "license": "MIT", 1512 + "optional": true, 1513 + "os": [ 1514 + "freebsd" 1515 + ] 1516 + }, 1517 + "node_modules/@rollup/rollup-freebsd-x64": { 1518 + "version": "4.53.3", 1519 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", 1520 + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", 1521 + "cpu": [ 1522 + "x64" 1523 + ], 1524 + "dev": true, 1525 + "license": "MIT", 1526 + "optional": true, 1527 + "os": [ 1528 + "freebsd" 1529 + ] 1530 + }, 1531 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1532 + "version": "4.53.3", 1533 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", 1534 + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", 1535 + "cpu": [ 1536 + "arm" 1537 + ], 1538 + "dev": true, 1539 + "license": "MIT", 1540 + "optional": true, 1541 + "os": [ 1542 + "linux" 1543 + ] 1544 + }, 1545 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1546 + "version": "4.53.3", 1547 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", 1548 + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", 1549 + "cpu": [ 1550 + "arm" 1551 + ], 1552 + "dev": true, 1553 + "license": "MIT", 1554 + "optional": true, 1555 + "os": [ 1556 + "linux" 1557 + ] 1558 + }, 1559 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1560 + "version": "4.53.3", 1561 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", 1562 + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", 1563 + "cpu": [ 1564 + "arm64" 1565 + ], 1566 + "dev": true, 1567 + "license": "MIT", 1568 + "optional": true, 1569 + "os": [ 1570 + "linux" 1571 + ] 1572 + }, 1573 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1574 + "version": "4.53.3", 1575 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", 1576 + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", 1577 + "cpu": [ 1578 + "arm64" 1579 + ], 1580 + "dev": true, 1581 + "license": "MIT", 1582 + "optional": true, 1583 + "os": [ 1584 + "linux" 1585 + ] 1586 + }, 1587 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 1588 + "version": "4.53.3", 1589 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", 1590 + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", 1591 + "cpu": [ 1592 + "loong64" 1593 + ], 1594 + "dev": true, 1595 + "license": "MIT", 1596 + "optional": true, 1597 + "os": [ 1598 + "linux" 1599 + ] 1600 + }, 1601 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1602 + "version": "4.53.3", 1603 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", 1604 + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", 1605 + "cpu": [ 1606 + "ppc64" 1607 + ], 1608 + "dev": true, 1609 + "license": "MIT", 1610 + "optional": true, 1611 + "os": [ 1612 + "linux" 1613 + ] 1614 + }, 1615 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1616 + "version": "4.53.3", 1617 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", 1618 + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", 1619 + "cpu": [ 1620 + "riscv64" 1621 + ], 1622 + "dev": true, 1623 + "license": "MIT", 1624 + "optional": true, 1625 + "os": [ 1626 + "linux" 1627 + ] 1628 + }, 1629 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1630 + "version": "4.53.3", 1631 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", 1632 + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", 1633 + "cpu": [ 1634 + "riscv64" 1635 + ], 1636 + "dev": true, 1637 + "license": "MIT", 1638 + "optional": true, 1639 + "os": [ 1640 + "linux" 1641 + ] 1642 + }, 1643 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1644 + "version": "4.53.3", 1645 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", 1646 + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", 1647 + "cpu": [ 1648 + "s390x" 1649 + ], 1650 + "dev": true, 1651 + "license": "MIT", 1652 + "optional": true, 1653 + "os": [ 1654 + "linux" 1655 + ] 1656 + }, 1657 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1658 + "version": "4.53.3", 1659 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", 1660 + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", 1661 + "cpu": [ 1662 + "x64" 1663 + ], 1664 + "dev": true, 1665 + "license": "MIT", 1666 + "optional": true, 1667 + "os": [ 1668 + "linux" 1669 + ] 1670 + }, 1671 + "node_modules/@rollup/rollup-linux-x64-musl": { 1672 + "version": "4.53.3", 1673 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", 1674 + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", 1675 + "cpu": [ 1676 + "x64" 1677 + ], 1678 + "dev": true, 1679 + "license": "MIT", 1680 + "optional": true, 1681 + "os": [ 1682 + "linux" 1683 + ] 1684 + }, 1685 + "node_modules/@rollup/rollup-openharmony-arm64": { 1686 + "version": "4.53.3", 1687 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", 1688 + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", 1689 + "cpu": [ 1690 + "arm64" 1691 + ], 1692 + "dev": true, 1693 + "license": "MIT", 1694 + "optional": true, 1695 + "os": [ 1696 + "openharmony" 1697 + ] 1698 + }, 1699 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1700 + "version": "4.53.3", 1701 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", 1702 + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", 1703 + "cpu": [ 1704 + "arm64" 1705 + ], 1706 + "dev": true, 1707 + "license": "MIT", 1708 + "optional": true, 1709 + "os": [ 1710 + "win32" 1711 + ] 1712 + }, 1713 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1714 + "version": "4.53.3", 1715 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", 1716 + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", 1717 + "cpu": [ 1718 + "ia32" 1719 + ], 1720 + "dev": true, 1721 + "license": "MIT", 1722 + "optional": true, 1723 + "os": [ 1724 + "win32" 1725 + ] 1726 + }, 1727 + "node_modules/@rollup/rollup-win32-x64-gnu": { 1728 + "version": "4.53.3", 1729 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", 1730 + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", 1731 + "cpu": [ 1732 + "x64" 1733 + ], 1734 + "dev": true, 1735 + "license": "MIT", 1736 + "optional": true, 1737 + "os": [ 1738 + "win32" 1739 + ] 1740 + }, 1741 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1742 + "version": "4.53.3", 1743 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", 1744 + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", 1745 + "cpu": [ 1746 + "x64" 1747 + ], 1748 + "dev": true, 1749 + "license": "MIT", 1750 + "optional": true, 1751 + "os": [ 1752 + "win32" 1753 + ] 1754 + }, 1755 + "node_modules/@smithy/abort-controller": { 1756 + "version": "4.2.5", 1757 + "license": "Apache-2.0", 1758 + "dependencies": { 1759 + "@smithy/types": "^4.9.0", 1760 + "tslib": "^2.6.2" 1761 + }, 1762 + "engines": { 1763 + "node": ">=18.0.0" 1764 + } 1765 + }, 1766 + "node_modules/@smithy/chunked-blob-reader": { 1767 + "version": "5.2.0", 1768 + "license": "Apache-2.0", 1769 + "dependencies": { 1770 + "tslib": "^2.6.2" 1771 + }, 1772 + "engines": { 1773 + "node": ">=18.0.0" 1774 + } 1775 + }, 1776 + "node_modules/@smithy/chunked-blob-reader-native": { 1777 + "version": "4.2.1", 1778 + "license": "Apache-2.0", 1779 + "dependencies": { 1780 + "@smithy/util-base64": "^4.3.0", 1781 + "tslib": "^2.6.2" 1782 + }, 1783 + "engines": { 1784 + "node": ">=18.0.0" 1785 + } 1786 + }, 1787 + "node_modules/@smithy/config-resolver": { 1788 + "version": "4.4.3", 1789 + "license": "Apache-2.0", 1790 + "dependencies": { 1791 + "@smithy/node-config-provider": "^4.3.5", 1792 + "@smithy/types": "^4.9.0", 1793 + "@smithy/util-config-provider": "^4.2.0", 1794 + "@smithy/util-endpoints": "^3.2.5", 1795 + "@smithy/util-middleware": "^4.2.5", 1796 + "tslib": "^2.6.2" 1797 + }, 1798 + "engines": { 1799 + "node": ">=18.0.0" 1800 + } 1801 + }, 1802 + "node_modules/@smithy/core": { 1803 + "version": "3.18.7", 1804 + "license": "Apache-2.0", 1805 + "dependencies": { 1806 + "@smithy/middleware-serde": "^4.2.6", 1807 + "@smithy/protocol-http": "^5.3.5", 1808 + "@smithy/types": "^4.9.0", 1809 + "@smithy/util-base64": "^4.3.0", 1810 + "@smithy/util-body-length-browser": "^4.2.0", 1811 + "@smithy/util-middleware": "^4.2.5", 1812 + "@smithy/util-stream": "^4.5.6", 1813 + "@smithy/util-utf8": "^4.2.0", 1814 + "@smithy/uuid": "^1.1.0", 1815 + "tslib": "^2.6.2" 1816 + }, 1817 + "engines": { 1818 + "node": ">=18.0.0" 1819 + } 1820 + }, 1821 + "node_modules/@smithy/credential-provider-imds": { 1822 + "version": "4.2.5", 1823 + "license": "Apache-2.0", 1824 + "dependencies": { 1825 + "@smithy/node-config-provider": "^4.3.5", 1826 + "@smithy/property-provider": "^4.2.5", 1827 + "@smithy/types": "^4.9.0", 1828 + "@smithy/url-parser": "^4.2.5", 1829 + "tslib": "^2.6.2" 1830 + }, 1831 + "engines": { 1832 + "node": ">=18.0.0" 1833 + } 1834 + }, 1835 + "node_modules/@smithy/eventstream-codec": { 1836 + "version": "4.2.5", 1837 + "license": "Apache-2.0", 1838 + "dependencies": { 1839 + "@aws-crypto/crc32": "5.2.0", 1840 + "@smithy/types": "^4.9.0", 1841 + "@smithy/util-hex-encoding": "^4.2.0", 1842 + "tslib": "^2.6.2" 1843 + }, 1844 + "engines": { 1845 + "node": ">=18.0.0" 1846 + } 1847 + }, 1848 + "node_modules/@smithy/eventstream-serde-browser": { 1849 + "version": "4.2.5", 1850 + "license": "Apache-2.0", 1851 + "dependencies": { 1852 + "@smithy/eventstream-serde-universal": "^4.2.5", 1853 + "@smithy/types": "^4.9.0", 1854 + "tslib": "^2.6.2" 1855 + }, 1856 + "engines": { 1857 + "node": ">=18.0.0" 1858 + } 1859 + }, 1860 + "node_modules/@smithy/eventstream-serde-config-resolver": { 1861 + "version": "4.3.5", 1862 + "license": "Apache-2.0", 1863 + "dependencies": { 1864 + "@smithy/types": "^4.9.0", 1865 + "tslib": "^2.6.2" 1866 + }, 1867 + "engines": { 1868 + "node": ">=18.0.0" 1869 + } 1870 + }, 1871 + "node_modules/@smithy/eventstream-serde-node": { 1872 + "version": "4.2.5", 1873 + "license": "Apache-2.0", 1874 + "dependencies": { 1875 + "@smithy/eventstream-serde-universal": "^4.2.5", 1876 + "@smithy/types": "^4.9.0", 1877 + "tslib": "^2.6.2" 1878 + }, 1879 + "engines": { 1880 + "node": ">=18.0.0" 1881 + } 1882 + }, 1883 + "node_modules/@smithy/eventstream-serde-universal": { 1884 + "version": "4.2.5", 1885 + "license": "Apache-2.0", 1886 + "dependencies": { 1887 + "@smithy/eventstream-codec": "^4.2.5", 1888 + "@smithy/types": "^4.9.0", 1889 + "tslib": "^2.6.2" 1890 + }, 1891 + "engines": { 1892 + "node": ">=18.0.0" 1893 + } 1894 + }, 1895 + "node_modules/@smithy/fetch-http-handler": { 1896 + "version": "5.3.6", 1897 + "license": "Apache-2.0", 1898 + "dependencies": { 1899 + "@smithy/protocol-http": "^5.3.5", 1900 + "@smithy/querystring-builder": "^4.2.5", 1901 + "@smithy/types": "^4.9.0", 1902 + "@smithy/util-base64": "^4.3.0", 1903 + "tslib": "^2.6.2" 1904 + }, 1905 + "engines": { 1906 + "node": ">=18.0.0" 1907 + } 1908 + }, 1909 + "node_modules/@smithy/hash-blob-browser": { 1910 + "version": "4.2.6", 1911 + "license": "Apache-2.0", 1912 + "dependencies": { 1913 + "@smithy/chunked-blob-reader": "^5.2.0", 1914 + "@smithy/chunked-blob-reader-native": "^4.2.1", 1915 + "@smithy/types": "^4.9.0", 1916 + "tslib": "^2.6.2" 1917 + }, 1918 + "engines": { 1919 + "node": ">=18.0.0" 1920 + } 1921 + }, 1922 + "node_modules/@smithy/hash-node": { 1923 + "version": "4.2.5", 1924 + "license": "Apache-2.0", 1925 + "dependencies": { 1926 + "@smithy/types": "^4.9.0", 1927 + "@smithy/util-buffer-from": "^4.2.0", 1928 + "@smithy/util-utf8": "^4.2.0", 1929 + "tslib": "^2.6.2" 1930 + }, 1931 + "engines": { 1932 + "node": ">=18.0.0" 1933 + } 1934 + }, 1935 + "node_modules/@smithy/hash-stream-node": { 1936 + "version": "4.2.5", 1937 + "license": "Apache-2.0", 1938 + "dependencies": { 1939 + "@smithy/types": "^4.9.0", 1940 + "@smithy/util-utf8": "^4.2.0", 1941 + "tslib": "^2.6.2" 1942 + }, 1943 + "engines": { 1944 + "node": ">=18.0.0" 1945 + } 1946 + }, 1947 + "node_modules/@smithy/invalid-dependency": { 1948 + "version": "4.2.5", 1949 + "license": "Apache-2.0", 1950 + "dependencies": { 1951 + "@smithy/types": "^4.9.0", 1952 + "tslib": "^2.6.2" 1953 + }, 1954 + "engines": { 1955 + "node": ">=18.0.0" 1956 + } 1957 + }, 1958 + "node_modules/@smithy/is-array-buffer": { 1959 + "version": "4.2.0", 1960 + "license": "Apache-2.0", 1961 + "dependencies": { 1962 + "tslib": "^2.6.2" 1963 + }, 1964 + "engines": { 1965 + "node": ">=18.0.0" 1966 + } 1967 + }, 1968 + "node_modules/@smithy/md5-js": { 1969 + "version": "4.2.5", 1970 + "license": "Apache-2.0", 1971 + "dependencies": { 1972 + "@smithy/types": "^4.9.0", 1973 + "@smithy/util-utf8": "^4.2.0", 1974 + "tslib": "^2.6.2" 1975 + }, 1976 + "engines": { 1977 + "node": ">=18.0.0" 1978 + } 1979 + }, 1980 + "node_modules/@smithy/middleware-content-length": { 1981 + "version": "4.2.5", 1982 + "license": "Apache-2.0", 1983 + "dependencies": { 1984 + "@smithy/protocol-http": "^5.3.5", 1985 + "@smithy/types": "^4.9.0", 1986 + "tslib": "^2.6.2" 1987 + }, 1988 + "engines": { 1989 + "node": ">=18.0.0" 1990 + } 1991 + }, 1992 + "node_modules/@smithy/middleware-endpoint": { 1993 + "version": "4.3.14", 1994 + "license": "Apache-2.0", 1995 + "dependencies": { 1996 + "@smithy/core": "^3.18.7", 1997 + "@smithy/middleware-serde": "^4.2.6", 1998 + "@smithy/node-config-provider": "^4.3.5", 1999 + "@smithy/shared-ini-file-loader": "^4.4.0", 2000 + "@smithy/types": "^4.9.0", 2001 + "@smithy/url-parser": "^4.2.5", 2002 + "@smithy/util-middleware": "^4.2.5", 2003 + "tslib": "^2.6.2" 2004 + }, 2005 + "engines": { 2006 + "node": ">=18.0.0" 2007 + } 2008 + }, 2009 + "node_modules/@smithy/middleware-retry": { 2010 + "version": "4.4.14", 2011 + "license": "Apache-2.0", 2012 + "dependencies": { 2013 + "@smithy/node-config-provider": "^4.3.5", 2014 + "@smithy/protocol-http": "^5.3.5", 2015 + "@smithy/service-error-classification": "^4.2.5", 2016 + "@smithy/smithy-client": "^4.9.10", 2017 + "@smithy/types": "^4.9.0", 2018 + "@smithy/util-middleware": "^4.2.5", 2019 + "@smithy/util-retry": "^4.2.5", 2020 + "@smithy/uuid": "^1.1.0", 2021 + "tslib": "^2.6.2" 2022 + }, 2023 + "engines": { 2024 + "node": ">=18.0.0" 2025 + } 2026 + }, 2027 + "node_modules/@smithy/middleware-serde": { 2028 + "version": "4.2.6", 2029 + "license": "Apache-2.0", 2030 + "dependencies": { 2031 + "@smithy/protocol-http": "^5.3.5", 2032 + "@smithy/types": "^4.9.0", 2033 + "tslib": "^2.6.2" 2034 + }, 2035 + "engines": { 2036 + "node": ">=18.0.0" 2037 + } 2038 + }, 2039 + "node_modules/@smithy/middleware-stack": { 2040 + "version": "4.2.5", 2041 + "license": "Apache-2.0", 2042 + "dependencies": { 2043 + "@smithy/types": "^4.9.0", 2044 + "tslib": "^2.6.2" 2045 + }, 2046 + "engines": { 2047 + "node": ">=18.0.0" 2048 + } 2049 + }, 2050 + "node_modules/@smithy/node-config-provider": { 2051 + "version": "4.3.5", 2052 + "license": "Apache-2.0", 2053 + "dependencies": { 2054 + "@smithy/property-provider": "^4.2.5", 2055 + "@smithy/shared-ini-file-loader": "^4.4.0", 2056 + "@smithy/types": "^4.9.0", 2057 + "tslib": "^2.6.2" 2058 + }, 2059 + "engines": { 2060 + "node": ">=18.0.0" 2061 + } 2062 + }, 2063 + "node_modules/@smithy/node-http-handler": { 2064 + "version": "4.4.5", 2065 + "license": "Apache-2.0", 2066 + "dependencies": { 2067 + "@smithy/abort-controller": "^4.2.5", 2068 + "@smithy/protocol-http": "^5.3.5", 2069 + "@smithy/querystring-builder": "^4.2.5", 2070 + "@smithy/types": "^4.9.0", 2071 + "tslib": "^2.6.2" 2072 + }, 2073 + "engines": { 2074 + "node": ">=18.0.0" 2075 + } 2076 + }, 2077 + "node_modules/@smithy/property-provider": { 2078 + "version": "4.2.5", 2079 + "license": "Apache-2.0", 2080 + "dependencies": { 2081 + "@smithy/types": "^4.9.0", 2082 + "tslib": "^2.6.2" 2083 + }, 2084 + "engines": { 2085 + "node": ">=18.0.0" 2086 + } 2087 + }, 2088 + "node_modules/@smithy/protocol-http": { 2089 + "version": "5.3.5", 2090 + "license": "Apache-2.0", 2091 + "dependencies": { 2092 + "@smithy/types": "^4.9.0", 2093 + "tslib": "^2.6.2" 2094 + }, 2095 + "engines": { 2096 + "node": ">=18.0.0" 2097 + } 2098 + }, 2099 + "node_modules/@smithy/querystring-builder": { 2100 + "version": "4.2.5", 2101 + "license": "Apache-2.0", 2102 + "dependencies": { 2103 + "@smithy/types": "^4.9.0", 2104 + "@smithy/util-uri-escape": "^4.2.0", 2105 + "tslib": "^2.6.2" 2106 + }, 2107 + "engines": { 2108 + "node": ">=18.0.0" 2109 + } 2110 + }, 2111 + "node_modules/@smithy/querystring-parser": { 2112 + "version": "4.2.5", 2113 + "license": "Apache-2.0", 2114 + "dependencies": { 2115 + "@smithy/types": "^4.9.0", 2116 + "tslib": "^2.6.2" 2117 + }, 2118 + "engines": { 2119 + "node": ">=18.0.0" 2120 + } 2121 + }, 2122 + "node_modules/@smithy/service-error-classification": { 2123 + "version": "4.2.5", 2124 + "license": "Apache-2.0", 2125 + "dependencies": { 2126 + "@smithy/types": "^4.9.0" 2127 + }, 2128 + "engines": { 2129 + "node": ">=18.0.0" 2130 + } 2131 + }, 2132 + "node_modules/@smithy/shared-ini-file-loader": { 2133 + "version": "4.4.0", 2134 + "license": "Apache-2.0", 2135 + "dependencies": { 2136 + "@smithy/types": "^4.9.0", 2137 + "tslib": "^2.6.2" 2138 + }, 2139 + "engines": { 2140 + "node": ">=18.0.0" 2141 + } 2142 + }, 2143 + "node_modules/@smithy/signature-v4": { 2144 + "version": "5.3.5", 2145 + "license": "Apache-2.0", 2146 + "dependencies": { 2147 + "@smithy/is-array-buffer": "^4.2.0", 2148 + "@smithy/protocol-http": "^5.3.5", 2149 + "@smithy/types": "^4.9.0", 2150 + "@smithy/util-hex-encoding": "^4.2.0", 2151 + "@smithy/util-middleware": "^4.2.5", 2152 + "@smithy/util-uri-escape": "^4.2.0", 2153 + "@smithy/util-utf8": "^4.2.0", 2154 + "tslib": "^2.6.2" 2155 + }, 2156 + "engines": { 2157 + "node": ">=18.0.0" 2158 + } 2159 + }, 2160 + "node_modules/@smithy/smithy-client": { 2161 + "version": "4.9.10", 2162 + "license": "Apache-2.0", 2163 + "dependencies": { 2164 + "@smithy/core": "^3.18.7", 2165 + "@smithy/middleware-endpoint": "^4.3.14", 2166 + "@smithy/middleware-stack": "^4.2.5", 2167 + "@smithy/protocol-http": "^5.3.5", 2168 + "@smithy/types": "^4.9.0", 2169 + "@smithy/util-stream": "^4.5.6", 2170 + "tslib": "^2.6.2" 2171 + }, 2172 + "engines": { 2173 + "node": ">=18.0.0" 2174 + } 2175 + }, 2176 + "node_modules/@smithy/types": { 2177 + "version": "4.9.0", 2178 + "license": "Apache-2.0", 2179 + "dependencies": { 2180 + "tslib": "^2.6.2" 2181 + }, 2182 + "engines": { 2183 + "node": ">=18.0.0" 2184 + } 2185 + }, 2186 + "node_modules/@smithy/url-parser": { 2187 + "version": "4.2.5", 2188 + "license": "Apache-2.0", 2189 + "dependencies": { 2190 + "@smithy/querystring-parser": "^4.2.5", 2191 + "@smithy/types": "^4.9.0", 2192 + "tslib": "^2.6.2" 2193 + }, 2194 + "engines": { 2195 + "node": ">=18.0.0" 2196 + } 2197 + }, 2198 + "node_modules/@smithy/util-base64": { 2199 + "version": "4.3.0", 2200 + "license": "Apache-2.0", 2201 + "dependencies": { 2202 + "@smithy/util-buffer-from": "^4.2.0", 2203 + "@smithy/util-utf8": "^4.2.0", 2204 + "tslib": "^2.6.2" 2205 + }, 2206 + "engines": { 2207 + "node": ">=18.0.0" 2208 + } 2209 + }, 2210 + "node_modules/@smithy/util-body-length-browser": { 2211 + "version": "4.2.0", 2212 + "license": "Apache-2.0", 2213 + "dependencies": { 2214 + "tslib": "^2.6.2" 2215 + }, 2216 + "engines": { 2217 + "node": ">=18.0.0" 2218 + } 2219 + }, 2220 + "node_modules/@smithy/util-body-length-node": { 2221 + "version": "4.2.1", 2222 + "license": "Apache-2.0", 2223 + "dependencies": { 2224 + "tslib": "^2.6.2" 2225 + }, 2226 + "engines": { 2227 + "node": ">=18.0.0" 2228 + } 2229 + }, 2230 + "node_modules/@smithy/util-buffer-from": { 2231 + "version": "4.2.0", 2232 + "license": "Apache-2.0", 2233 + "dependencies": { 2234 + "@smithy/is-array-buffer": "^4.2.0", 2235 + "tslib": "^2.6.2" 2236 + }, 2237 + "engines": { 2238 + "node": ">=18.0.0" 2239 + } 2240 + }, 2241 + "node_modules/@smithy/util-config-provider": { 2242 + "version": "4.2.0", 2243 + "license": "Apache-2.0", 2244 + "dependencies": { 2245 + "tslib": "^2.6.2" 2246 + }, 2247 + "engines": { 2248 + "node": ">=18.0.0" 2249 + } 2250 + }, 2251 + "node_modules/@smithy/util-defaults-mode-browser": { 2252 + "version": "4.3.13", 2253 + "license": "Apache-2.0", 2254 + "dependencies": { 2255 + "@smithy/property-provider": "^4.2.5", 2256 + "@smithy/smithy-client": "^4.9.10", 2257 + "@smithy/types": "^4.9.0", 2258 + "tslib": "^2.6.2" 2259 + }, 2260 + "engines": { 2261 + "node": ">=18.0.0" 2262 + } 2263 + }, 2264 + "node_modules/@smithy/util-defaults-mode-node": { 2265 + "version": "4.2.16", 2266 + "license": "Apache-2.0", 2267 + "dependencies": { 2268 + "@smithy/config-resolver": "^4.4.3", 2269 + "@smithy/credential-provider-imds": "^4.2.5", 2270 + "@smithy/node-config-provider": "^4.3.5", 2271 + "@smithy/property-provider": "^4.2.5", 2272 + "@smithy/smithy-client": "^4.9.10", 2273 + "@smithy/types": "^4.9.0", 2274 + "tslib": "^2.6.2" 2275 + }, 2276 + "engines": { 2277 + "node": ">=18.0.0" 2278 + } 2279 + }, 2280 + "node_modules/@smithy/util-endpoints": { 2281 + "version": "3.2.5", 2282 + "license": "Apache-2.0", 2283 + "dependencies": { 2284 + "@smithy/node-config-provider": "^4.3.5", 2285 + "@smithy/types": "^4.9.0", 2286 + "tslib": "^2.6.2" 2287 + }, 2288 + "engines": { 2289 + "node": ">=18.0.0" 2290 + } 2291 + }, 2292 + "node_modules/@smithy/util-hex-encoding": { 2293 + "version": "4.2.0", 2294 + "license": "Apache-2.0", 2295 + "dependencies": { 2296 + "tslib": "^2.6.2" 2297 + }, 2298 + "engines": { 2299 + "node": ">=18.0.0" 2300 + } 2301 + }, 2302 + "node_modules/@smithy/util-middleware": { 2303 + "version": "4.2.5", 2304 + "license": "Apache-2.0", 2305 + "dependencies": { 2306 + "@smithy/types": "^4.9.0", 2307 + "tslib": "^2.6.2" 2308 + }, 2309 + "engines": { 2310 + "node": ">=18.0.0" 2311 + } 2312 + }, 2313 + "node_modules/@smithy/util-retry": { 2314 + "version": "4.2.5", 2315 + "license": "Apache-2.0", 2316 + "dependencies": { 2317 + "@smithy/service-error-classification": "^4.2.5", 2318 + "@smithy/types": "^4.9.0", 2319 + "tslib": "^2.6.2" 2320 + }, 2321 + "engines": { 2322 + "node": ">=18.0.0" 2323 + } 2324 + }, 2325 + "node_modules/@smithy/util-stream": { 2326 + "version": "4.5.6", 2327 + "license": "Apache-2.0", 2328 + "dependencies": { 2329 + "@smithy/fetch-http-handler": "^5.3.6", 2330 + "@smithy/node-http-handler": "^4.4.5", 2331 + "@smithy/types": "^4.9.0", 2332 + "@smithy/util-base64": "^4.3.0", 2333 + "@smithy/util-buffer-from": "^4.2.0", 2334 + "@smithy/util-hex-encoding": "^4.2.0", 2335 + "@smithy/util-utf8": "^4.2.0", 2336 + "tslib": "^2.6.2" 2337 + }, 2338 + "engines": { 2339 + "node": ">=18.0.0" 2340 + } 2341 + }, 2342 + "node_modules/@smithy/util-uri-escape": { 2343 + "version": "4.2.0", 2344 + "license": "Apache-2.0", 2345 + "dependencies": { 2346 + "tslib": "^2.6.2" 2347 + }, 2348 + "engines": { 2349 + "node": ">=18.0.0" 2350 + } 2351 + }, 2352 + "node_modules/@smithy/util-utf8": { 2353 + "version": "4.2.0", 2354 + "license": "Apache-2.0", 2355 + "dependencies": { 2356 + "@smithy/util-buffer-from": "^4.2.0", 2357 + "tslib": "^2.6.2" 2358 + }, 2359 + "engines": { 2360 + "node": ">=18.0.0" 2361 + } 2362 + }, 2363 + "node_modules/@smithy/util-waiter": { 2364 + "version": "4.2.5", 2365 + "license": "Apache-2.0", 2366 + "dependencies": { 2367 + "@smithy/abort-controller": "^4.2.5", 2368 + "@smithy/types": "^4.9.0", 2369 + "tslib": "^2.6.2" 2370 + }, 2371 + "engines": { 2372 + "node": ">=18.0.0" 2373 + } 2374 + }, 2375 + "node_modules/@smithy/uuid": { 2376 + "version": "1.1.0", 2377 + "license": "Apache-2.0", 2378 + "dependencies": { 2379 + "tslib": "^2.6.2" 2380 + }, 2381 + "engines": { 2382 + "node": ">=18.0.0" 2383 + } 2384 + }, 2385 + "node_modules/@standard-schema/spec": { 2386 + "version": "1.0.0", 2387 + "dev": true, 2388 + "license": "MIT" 2389 + }, 2390 + "node_modules/@types/bun": { 2391 + "version": "1.3.4", 2392 + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.4.tgz", 2393 + "integrity": "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA==", 2394 + "dev": true, 2395 + "license": "MIT", 2396 + "dependencies": { 2397 + "bun-types": "1.3.4" 2398 + } 2399 + }, 2400 + "node_modules/@types/chai": { 2401 + "version": "5.2.3", 2402 + "dev": true, 2403 + "license": "MIT", 2404 + "dependencies": { 2405 + "@types/deep-eql": "*", 2406 + "assertion-error": "^2.0.1" 2407 + } 2408 + }, 2409 + "node_modules/@types/deep-eql": { 2410 + "version": "4.0.2", 2411 + "dev": true, 2412 + "license": "MIT" 2413 + }, 2414 + "node_modules/@types/estree": { 2415 + "version": "1.0.8", 2416 + "dev": true, 2417 + "license": "MIT" 2418 + }, 2419 + "node_modules/@types/json-schema": { 2420 + "version": "7.0.15", 2421 + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 2422 + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 2423 + "dev": true, 2424 + "license": "MIT" 2425 + }, 2426 + "node_modules/@types/mime-types": { 2427 + "version": "3.0.1", 2428 + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", 2429 + "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==", 2430 + "dev": true, 2431 + "license": "MIT" 2432 + }, 2433 + "node_modules/@types/node": { 2434 + "version": "24.10.1", 2435 + "dev": true, 2436 + "license": "MIT", 2437 + "peer": true, 2438 + "dependencies": { 2439 + "undici-types": "~7.16.0" 2440 + } 2441 + }, 2442 + "node_modules/@typescript-eslint/eslint-plugin": { 2443 + "version": "8.49.0", 2444 + "dev": true, 2445 + "license": "MIT", 2446 + "dependencies": { 2447 + "@eslint-community/regexpp": "^4.10.0", 2448 + "@typescript-eslint/scope-manager": "8.49.0", 2449 + "@typescript-eslint/type-utils": "8.49.0", 2450 + "@typescript-eslint/utils": "8.49.0", 2451 + "@typescript-eslint/visitor-keys": "8.49.0", 2452 + "ignore": "^7.0.0", 2453 + "natural-compare": "^1.4.0", 2454 + "ts-api-utils": "^2.1.0" 2455 + }, 2456 + "engines": { 2457 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2458 + }, 2459 + "funding": { 2460 + "type": "opencollective", 2461 + "url": "https://opencollective.com/typescript-eslint" 2462 + }, 2463 + "peerDependencies": { 2464 + "@typescript-eslint/parser": "^8.49.0", 2465 + "eslint": "^8.57.0 || ^9.0.0", 2466 + "typescript": ">=4.8.4 <6.0.0" 2467 + } 2468 + }, 2469 + "node_modules/@typescript-eslint/parser": { 2470 + "version": "8.49.0", 2471 + "dev": true, 2472 + "license": "MIT", 2473 + "peer": true, 2474 + "dependencies": { 2475 + "@typescript-eslint/scope-manager": "8.49.0", 2476 + "@typescript-eslint/types": "8.49.0", 2477 + "@typescript-eslint/typescript-estree": "8.49.0", 2478 + "@typescript-eslint/visitor-keys": "8.49.0", 2479 + "debug": "^4.3.4" 2480 + }, 2481 + "engines": { 2482 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2483 + }, 2484 + "funding": { 2485 + "type": "opencollective", 2486 + "url": "https://opencollective.com/typescript-eslint" 2487 + }, 2488 + "peerDependencies": { 2489 + "eslint": "^8.57.0 || ^9.0.0", 2490 + "typescript": ">=4.8.4 <6.0.0" 2491 + } 2492 + }, 2493 + "node_modules/@typescript-eslint/project-service": { 2494 + "version": "8.49.0", 2495 + "dev": true, 2496 + "license": "MIT", 2497 + "dependencies": { 2498 + "@typescript-eslint/tsconfig-utils": "^8.49.0", 2499 + "@typescript-eslint/types": "^8.49.0", 2500 + "debug": "^4.3.4" 2501 + }, 2502 + "engines": { 2503 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2504 + }, 2505 + "funding": { 2506 + "type": "opencollective", 2507 + "url": "https://opencollective.com/typescript-eslint" 2508 + }, 2509 + "peerDependencies": { 2510 + "typescript": ">=4.8.4 <6.0.0" 2511 + } 2512 + }, 2513 + "node_modules/@typescript-eslint/scope-manager": { 2514 + "version": "8.49.0", 2515 + "dev": true, 2516 + "license": "MIT", 2517 + "dependencies": { 2518 + "@typescript-eslint/types": "8.49.0", 2519 + "@typescript-eslint/visitor-keys": "8.49.0" 2520 + }, 2521 + "engines": { 2522 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2523 + }, 2524 + "funding": { 2525 + "type": "opencollective", 2526 + "url": "https://opencollective.com/typescript-eslint" 2527 + } 2528 + }, 2529 + "node_modules/@typescript-eslint/tsconfig-utils": { 2530 + "version": "8.49.0", 2531 + "dev": true, 2532 + "license": "MIT", 2533 + "engines": { 2534 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2535 + }, 2536 + "funding": { 2537 + "type": "opencollective", 2538 + "url": "https://opencollective.com/typescript-eslint" 2539 + }, 2540 + "peerDependencies": { 2541 + "typescript": ">=4.8.4 <6.0.0" 2542 + } 2543 + }, 2544 + "node_modules/@typescript-eslint/type-utils": { 2545 + "version": "8.49.0", 2546 + "dev": true, 2547 + "license": "MIT", 2548 + "dependencies": { 2549 + "@typescript-eslint/types": "8.49.0", 2550 + "@typescript-eslint/typescript-estree": "8.49.0", 2551 + "@typescript-eslint/utils": "8.49.0", 2552 + "debug": "^4.3.4", 2553 + "ts-api-utils": "^2.1.0" 2554 + }, 2555 + "engines": { 2556 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2557 + }, 2558 + "funding": { 2559 + "type": "opencollective", 2560 + "url": "https://opencollective.com/typescript-eslint" 2561 + }, 2562 + "peerDependencies": { 2563 + "eslint": "^8.57.0 || ^9.0.0", 2564 + "typescript": ">=4.8.4 <6.0.0" 2565 + } 2566 + }, 2567 + "node_modules/@typescript-eslint/types": { 2568 + "version": "8.49.0", 2569 + "dev": true, 2570 + "license": "MIT", 2571 + "engines": { 2572 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2573 + }, 2574 + "funding": { 2575 + "type": "opencollective", 2576 + "url": "https://opencollective.com/typescript-eslint" 2577 + } 2578 + }, 2579 + "node_modules/@typescript-eslint/typescript-estree": { 2580 + "version": "8.49.0", 2581 + "dev": true, 2582 + "license": "MIT", 2583 + "dependencies": { 2584 + "@typescript-eslint/project-service": "8.49.0", 2585 + "@typescript-eslint/tsconfig-utils": "8.49.0", 2586 + "@typescript-eslint/types": "8.49.0", 2587 + "@typescript-eslint/visitor-keys": "8.49.0", 2588 + "debug": "^4.3.4", 2589 + "minimatch": "^9.0.4", 2590 + "semver": "^7.6.0", 2591 + "tinyglobby": "^0.2.15", 2592 + "ts-api-utils": "^2.1.0" 2593 + }, 2594 + "engines": { 2595 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2596 + }, 2597 + "funding": { 2598 + "type": "opencollective", 2599 + "url": "https://opencollective.com/typescript-eslint" 2600 + }, 2601 + "peerDependencies": { 2602 + "typescript": ">=4.8.4 <6.0.0" 2603 + } 2604 + }, 2605 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 2606 + "version": "9.0.5", 2607 + "dev": true, 2608 + "license": "ISC", 2609 + "dependencies": { 2610 + "brace-expansion": "^2.0.1" 2611 + }, 2612 + "engines": { 2613 + "node": ">=16 || 14 >=14.17" 2614 + }, 2615 + "funding": { 2616 + "url": "https://github.com/sponsors/isaacs" 2617 + } 2618 + }, 2619 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { 2620 + "version": "2.0.2", 2621 + "dev": true, 2622 + "license": "MIT", 2623 + "dependencies": { 2624 + "balanced-match": "^1.0.0" 2625 + } 2626 + }, 2627 + "node_modules/@typescript-eslint/utils": { 2628 + "version": "8.49.0", 2629 + "dev": true, 2630 + "license": "MIT", 2631 + "dependencies": { 2632 + "@eslint-community/eslint-utils": "^4.7.0", 2633 + "@typescript-eslint/scope-manager": "8.49.0", 2634 + "@typescript-eslint/types": "8.49.0", 2635 + "@typescript-eslint/typescript-estree": "8.49.0" 2636 + }, 2637 + "engines": { 2638 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2639 + }, 2640 + "funding": { 2641 + "type": "opencollective", 2642 + "url": "https://opencollective.com/typescript-eslint" 2643 + }, 2644 + "peerDependencies": { 2645 + "eslint": "^8.57.0 || ^9.0.0", 2646 + "typescript": ">=4.8.4 <6.0.0" 2647 + } 2648 + }, 2649 + "node_modules/@typescript-eslint/visitor-keys": { 2650 + "version": "8.49.0", 2651 + "dev": true, 2652 + "license": "MIT", 2653 + "dependencies": { 2654 + "@typescript-eslint/types": "8.49.0", 2655 + "eslint-visitor-keys": "^4.2.1" 2656 + }, 2657 + "engines": { 2658 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2659 + }, 2660 + "funding": { 2661 + "type": "opencollective", 2662 + "url": "https://opencollective.com/typescript-eslint" 2663 + } 2664 + }, 2665 + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { 2666 + "version": "4.2.1", 2667 + "dev": true, 2668 + "license": "Apache-2.0", 2669 + "engines": { 2670 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2671 + }, 2672 + "funding": { 2673 + "url": "https://opencollective.com/eslint" 2674 + } 2675 + }, 2676 + "node_modules/@vitest/expect": { 2677 + "version": "4.0.15", 2678 + "dev": true, 2679 + "license": "MIT", 2680 + "dependencies": { 2681 + "@standard-schema/spec": "^1.0.0", 2682 + "@types/chai": "^5.2.2", 2683 + "@vitest/spy": "4.0.15", 2684 + "@vitest/utils": "4.0.15", 2685 + "chai": "^6.2.1", 2686 + "tinyrainbow": "^3.0.3" 2687 + }, 2688 + "funding": { 2689 + "url": "https://opencollective.com/vitest" 2690 + } 2691 + }, 2692 + "node_modules/@vitest/mocker": { 2693 + "version": "4.0.15", 2694 + "dev": true, 2695 + "license": "MIT", 2696 + "dependencies": { 2697 + "@vitest/spy": "4.0.15", 2698 + "estree-walker": "^3.0.3", 2699 + "magic-string": "^0.30.21" 2700 + }, 2701 + "funding": { 2702 + "url": "https://opencollective.com/vitest" 2703 + }, 2704 + "peerDependencies": { 2705 + "msw": "^2.4.9", 2706 + "vite": "^6.0.0 || ^7.0.0-0" 2707 + }, 2708 + "peerDependenciesMeta": { 2709 + "msw": { 2710 + "optional": true 2711 + }, 2712 + "vite": { 2713 + "optional": true 2714 + } 2715 + } 2716 + }, 2717 + "node_modules/@vitest/pretty-format": { 2718 + "version": "4.0.15", 2719 + "dev": true, 2720 + "license": "MIT", 2721 + "dependencies": { 2722 + "tinyrainbow": "^3.0.3" 2723 + }, 2724 + "funding": { 2725 + "url": "https://opencollective.com/vitest" 2726 + } 2727 + }, 2728 + "node_modules/@vitest/runner": { 2729 + "version": "4.0.15", 2730 + "dev": true, 2731 + "license": "MIT", 2732 + "dependencies": { 2733 + "@vitest/utils": "4.0.15", 2734 + "pathe": "^2.0.3" 2735 + }, 2736 + "funding": { 2737 + "url": "https://opencollective.com/vitest" 2738 + } 2739 + }, 2740 + "node_modules/@vitest/snapshot": { 2741 + "version": "4.0.15", 2742 + "dev": true, 2743 + "license": "MIT", 2744 + "dependencies": { 2745 + "@vitest/pretty-format": "4.0.15", 2746 + "magic-string": "^0.30.21", 2747 + "pathe": "^2.0.3" 2748 + }, 2749 + "funding": { 2750 + "url": "https://opencollective.com/vitest" 2751 + } 2752 + }, 2753 + "node_modules/@vitest/spy": { 2754 + "version": "4.0.15", 2755 + "dev": true, 2756 + "license": "MIT", 2757 + "funding": { 2758 + "url": "https://opencollective.com/vitest" 2759 + } 2760 + }, 2761 + "node_modules/@vitest/utils": { 2762 + "version": "4.0.15", 2763 + "dev": true, 2764 + "license": "MIT", 2765 + "dependencies": { 2766 + "@vitest/pretty-format": "4.0.15", 2767 + "tinyrainbow": "^3.0.3" 2768 + }, 2769 + "funding": { 2770 + "url": "https://opencollective.com/vitest" 2771 + } 2772 + }, 2773 + "node_modules/acorn": { 2774 + "version": "8.15.0", 2775 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 2776 + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 2777 + "dev": true, 2778 + "license": "MIT", 2779 + "peer": true, 2780 + "bin": { 2781 + "acorn": "bin/acorn" 2782 + }, 2783 + "engines": { 2784 + "node": ">=0.4.0" 2785 + } 2786 + }, 2787 + "node_modules/acorn-jsx": { 2788 + "version": "5.3.2", 2789 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 2790 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 2791 + "dev": true, 2792 + "license": "MIT", 2793 + "peerDependencies": { 2794 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2795 + } 2796 + }, 2797 + "node_modules/ajv": { 2798 + "version": "6.12.6", 2799 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2800 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2801 + "dev": true, 2802 + "license": "MIT", 2803 + "dependencies": { 2804 + "fast-deep-equal": "^3.1.1", 2805 + "fast-json-stable-stringify": "^2.0.0", 2806 + "json-schema-traverse": "^0.4.1", 2807 + "uri-js": "^4.2.2" 2808 + }, 2809 + "funding": { 2810 + "type": "github", 2811 + "url": "https://github.com/sponsors/epoberezkin" 2812 + } 2813 + }, 2814 + "node_modules/ansi-styles": { 2815 + "version": "4.3.0", 2816 + "dev": true, 2817 + "license": "MIT", 2818 + "dependencies": { 2819 + "color-convert": "^2.0.1" 2820 + }, 2821 + "engines": { 2822 + "node": ">=8" 2823 + }, 2824 + "funding": { 2825 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2826 + } 2827 + }, 2828 + "node_modules/argparse": { 2829 + "version": "2.0.1", 2830 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 2831 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 2832 + "dev": true, 2833 + "license": "Python-2.0" 2834 + }, 2835 + "node_modules/assertion-error": { 2836 + "version": "2.0.1", 2837 + "dev": true, 2838 + "license": "MIT", 2839 + "engines": { 2840 + "node": ">=12" 2841 + } 2842 + }, 2843 + "node_modules/balanced-match": { 2844 + "version": "1.0.2", 2845 + "dev": true, 2846 + "license": "MIT" 2847 + }, 2848 + "node_modules/bowser": { 2849 + "version": "2.13.1", 2850 + "license": "MIT" 2851 + }, 2852 + "node_modules/brace-expansion": { 2853 + "version": "1.1.12", 2854 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 2855 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 2856 + "dev": true, 2857 + "license": "MIT", 2858 + "dependencies": { 2859 + "balanced-match": "^1.0.0", 2860 + "concat-map": "0.0.1" 2861 + } 2862 + }, 2863 + "node_modules/bun-types": { 2864 + "version": "1.3.4", 2865 + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.4.tgz", 2866 + "integrity": "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ==", 2867 + "dev": true, 2868 + "license": "MIT", 2869 + "dependencies": { 2870 + "@types/node": "*" 2871 + } 2872 + }, 2873 + "node_modules/callsites": { 2874 + "version": "3.1.0", 2875 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 2876 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 2877 + "dev": true, 2878 + "license": "MIT", 2879 + "engines": { 2880 + "node": ">=6" 2881 + } 2882 + }, 2883 + "node_modules/chai": { 2884 + "version": "6.2.1", 2885 + "dev": true, 2886 + "license": "MIT", 2887 + "engines": { 2888 + "node": ">=18" 2889 + } 2890 + }, 2891 + "node_modules/chalk": { 2892 + "version": "4.1.2", 2893 + "dev": true, 2894 + "license": "MIT", 2895 + "dependencies": { 2896 + "ansi-styles": "^4.1.0", 2897 + "supports-color": "^7.1.0" 2898 + }, 2899 + "engines": { 2900 + "node": ">=10" 2901 + }, 2902 + "funding": { 2903 + "url": "https://github.com/chalk/chalk?sponsor=1" 2904 + } 2905 + }, 2906 + "node_modules/color-convert": { 2907 + "version": "2.0.1", 2908 + "dev": true, 2909 + "license": "MIT", 2910 + "dependencies": { 2911 + "color-name": "~1.1.4" 2912 + }, 2913 + "engines": { 2914 + "node": ">=7.0.0" 2915 + } 2916 + }, 2917 + "node_modules/color-name": { 2918 + "version": "1.1.4", 2919 + "dev": true, 2920 + "license": "MIT" 2921 + }, 2922 + "node_modules/concat-map": { 2923 + "version": "0.0.1", 2924 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 2925 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 2926 + "dev": true, 2927 + "license": "MIT" 2928 + }, 2929 + "node_modules/cross-spawn": { 2930 + "version": "7.0.6", 2931 + "dev": true, 2932 + "license": "MIT", 2933 + "dependencies": { 2934 + "path-key": "^3.1.0", 2935 + "shebang-command": "^2.0.0", 2936 + "which": "^2.0.1" 2937 + }, 2938 + "engines": { 2939 + "node": ">= 8" 2940 + } 2941 + }, 2942 + "node_modules/debug": { 2943 + "version": "4.4.3", 2944 + "dev": true, 2945 + "license": "MIT", 2946 + "dependencies": { 2947 + "ms": "^2.1.3" 2948 + }, 2949 + "engines": { 2950 + "node": ">=6.0" 2951 + }, 2952 + "peerDependenciesMeta": { 2953 + "supports-color": { 2954 + "optional": true 2955 + } 2956 + } 2957 + }, 2958 + "node_modules/deep-is": { 2959 + "version": "0.1.4", 2960 + "dev": true, 2961 + "license": "MIT" 2962 + }, 2963 + "node_modules/es-module-lexer": { 2964 + "version": "1.7.0", 2965 + "dev": true, 2966 + "license": "MIT" 2967 + }, 2968 + "node_modules/esbuild": { 2969 + "version": "0.27.1", 2970 + "dev": true, 2971 + "hasInstallScript": true, 2972 + "license": "MIT", 2973 + "bin": { 2974 + "esbuild": "bin/esbuild" 2975 + }, 2976 + "engines": { 2977 + "node": ">=18" 2978 + }, 2979 + "optionalDependencies": { 2980 + "@esbuild/aix-ppc64": "0.27.1", 2981 + "@esbuild/android-arm": "0.27.1", 2982 + "@esbuild/android-arm64": "0.27.1", 2983 + "@esbuild/android-x64": "0.27.1", 2984 + "@esbuild/darwin-arm64": "0.27.1", 2985 + "@esbuild/darwin-x64": "0.27.1", 2986 + "@esbuild/freebsd-arm64": "0.27.1", 2987 + "@esbuild/freebsd-x64": "0.27.1", 2988 + "@esbuild/linux-arm": "0.27.1", 2989 + "@esbuild/linux-arm64": "0.27.1", 2990 + "@esbuild/linux-ia32": "0.27.1", 2991 + "@esbuild/linux-loong64": "0.27.1", 2992 + "@esbuild/linux-mips64el": "0.27.1", 2993 + "@esbuild/linux-ppc64": "0.27.1", 2994 + "@esbuild/linux-riscv64": "0.27.1", 2995 + "@esbuild/linux-s390x": "0.27.1", 2996 + "@esbuild/linux-x64": "0.27.1", 2997 + "@esbuild/netbsd-arm64": "0.27.1", 2998 + "@esbuild/netbsd-x64": "0.27.1", 2999 + "@esbuild/openbsd-arm64": "0.27.1", 3000 + "@esbuild/openbsd-x64": "0.27.1", 3001 + "@esbuild/openharmony-arm64": "0.27.1", 3002 + "@esbuild/sunos-x64": "0.27.1", 3003 + "@esbuild/win32-arm64": "0.27.1", 3004 + "@esbuild/win32-ia32": "0.27.1", 3005 + "@esbuild/win32-x64": "0.27.1" 3006 + } 3007 + }, 3008 + "node_modules/escape-string-regexp": { 3009 + "version": "4.0.0", 3010 + "dev": true, 3011 + "license": "MIT", 3012 + "engines": { 3013 + "node": ">=10" 3014 + }, 3015 + "funding": { 3016 + "url": "https://github.com/sponsors/sindresorhus" 3017 + } 3018 + }, 3019 + "node_modules/eslint": { 3020 + "version": "9.39.1", 3021 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", 3022 + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", 3023 + "dev": true, 3024 + "license": "MIT", 3025 + "peer": true, 3026 + "dependencies": { 3027 + "@eslint-community/eslint-utils": "^4.8.0", 3028 + "@eslint-community/regexpp": "^4.12.1", 3029 + "@eslint/config-array": "^0.21.1", 3030 + "@eslint/config-helpers": "^0.4.2", 3031 + "@eslint/core": "^0.17.0", 3032 + "@eslint/eslintrc": "^3.3.1", 3033 + "@eslint/js": "9.39.1", 3034 + "@eslint/plugin-kit": "^0.4.1", 3035 + "@humanfs/node": "^0.16.6", 3036 + "@humanwhocodes/module-importer": "^1.0.1", 3037 + "@humanwhocodes/retry": "^0.4.2", 3038 + "@types/estree": "^1.0.6", 3039 + "ajv": "^6.12.4", 3040 + "chalk": "^4.0.0", 3041 + "cross-spawn": "^7.0.6", 3042 + "debug": "^4.3.2", 3043 + "escape-string-regexp": "^4.0.0", 3044 + "eslint-scope": "^8.4.0", 3045 + "eslint-visitor-keys": "^4.2.1", 3046 + "espree": "^10.4.0", 3047 + "esquery": "^1.5.0", 3048 + "esutils": "^2.0.2", 3049 + "fast-deep-equal": "^3.1.3", 3050 + "file-entry-cache": "^8.0.0", 3051 + "find-up": "^5.0.0", 3052 + "glob-parent": "^6.0.2", 3053 + "ignore": "^5.2.0", 3054 + "imurmurhash": "^0.1.4", 3055 + "is-glob": "^4.0.0", 3056 + "json-stable-stringify-without-jsonify": "^1.0.1", 3057 + "lodash.merge": "^4.6.2", 3058 + "minimatch": "^3.1.2", 3059 + "natural-compare": "^1.4.0", 3060 + "optionator": "^0.9.3" 3061 + }, 3062 + "bin": { 3063 + "eslint": "bin/eslint.js" 3064 + }, 3065 + "engines": { 3066 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3067 + }, 3068 + "funding": { 3069 + "url": "https://eslint.org/donate" 3070 + }, 3071 + "peerDependencies": { 3072 + "jiti": "*" 3073 + }, 3074 + "peerDependenciesMeta": { 3075 + "jiti": { 3076 + "optional": true 3077 + } 3078 + } 3079 + }, 3080 + "node_modules/eslint-scope": { 3081 + "version": "8.4.0", 3082 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 3083 + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 3084 + "dev": true, 3085 + "license": "BSD-2-Clause", 3086 + "dependencies": { 3087 + "esrecurse": "^4.3.0", 3088 + "estraverse": "^5.2.0" 3089 + }, 3090 + "engines": { 3091 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3092 + }, 3093 + "funding": { 3094 + "url": "https://opencollective.com/eslint" 3095 + } 3096 + }, 3097 + "node_modules/eslint-visitor-keys": { 3098 + "version": "3.4.3", 3099 + "dev": true, 3100 + "license": "Apache-2.0", 3101 + "engines": { 3102 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 3103 + }, 3104 + "funding": { 3105 + "url": "https://opencollective.com/eslint" 3106 + } 3107 + }, 3108 + "node_modules/eslint/node_modules/eslint-visitor-keys": { 3109 + "version": "4.2.1", 3110 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 3111 + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 3112 + "dev": true, 3113 + "license": "Apache-2.0", 3114 + "engines": { 3115 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3116 + }, 3117 + "funding": { 3118 + "url": "https://opencollective.com/eslint" 3119 + } 3120 + }, 3121 + "node_modules/eslint/node_modules/ignore": { 3122 + "version": "5.3.2", 3123 + "dev": true, 3124 + "license": "MIT", 3125 + "engines": { 3126 + "node": ">= 4" 3127 + } 3128 + }, 3129 + "node_modules/espree": { 3130 + "version": "10.4.0", 3131 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 3132 + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 3133 + "dev": true, 3134 + "license": "BSD-2-Clause", 3135 + "dependencies": { 3136 + "acorn": "^8.15.0", 3137 + "acorn-jsx": "^5.3.2", 3138 + "eslint-visitor-keys": "^4.2.1" 3139 + }, 3140 + "engines": { 3141 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3142 + }, 3143 + "funding": { 3144 + "url": "https://opencollective.com/eslint" 3145 + } 3146 + }, 3147 + "node_modules/espree/node_modules/eslint-visitor-keys": { 3148 + "version": "4.2.1", 3149 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 3150 + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 3151 + "dev": true, 3152 + "license": "Apache-2.0", 3153 + "engines": { 3154 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3155 + }, 3156 + "funding": { 3157 + "url": "https://opencollective.com/eslint" 3158 + } 3159 + }, 3160 + "node_modules/esquery": { 3161 + "version": "1.6.0", 3162 + "dev": true, 3163 + "license": "BSD-3-Clause", 3164 + "dependencies": { 3165 + "estraverse": "^5.1.0" 3166 + }, 3167 + "engines": { 3168 + "node": ">=0.10" 3169 + } 3170 + }, 3171 + "node_modules/esrecurse": { 3172 + "version": "4.3.0", 3173 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 3174 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 3175 + "dev": true, 3176 + "license": "BSD-2-Clause", 3177 + "dependencies": { 3178 + "estraverse": "^5.2.0" 3179 + }, 3180 + "engines": { 3181 + "node": ">=4.0" 3182 + } 3183 + }, 3184 + "node_modules/estraverse": { 3185 + "version": "5.3.0", 3186 + "dev": true, 3187 + "license": "BSD-2-Clause", 3188 + "engines": { 3189 + "node": ">=4.0" 3190 + } 3191 + }, 3192 + "node_modules/estree-walker": { 3193 + "version": "3.0.3", 3194 + "dev": true, 3195 + "license": "MIT", 3196 + "dependencies": { 3197 + "@types/estree": "^1.0.0" 3198 + } 3199 + }, 3200 + "node_modules/esutils": { 3201 + "version": "2.0.3", 3202 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 3203 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 3204 + "dev": true, 3205 + "license": "BSD-2-Clause", 3206 + "engines": { 3207 + "node": ">=0.10.0" 3208 + } 3209 + }, 3210 + "node_modules/expect-type": { 3211 + "version": "1.3.0", 3212 + "dev": true, 3213 + "license": "Apache-2.0", 3214 + "engines": { 3215 + "node": ">=12.0.0" 3216 + } 3217 + }, 3218 + "node_modules/fast-deep-equal": { 3219 + "version": "3.1.3", 3220 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 3221 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 3222 + "dev": true, 3223 + "license": "MIT" 3224 + }, 3225 + "node_modules/fast-json-stable-stringify": { 3226 + "version": "2.1.0", 3227 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 3228 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 3229 + "dev": true, 3230 + "license": "MIT" 3231 + }, 3232 + "node_modules/fast-levenshtein": { 3233 + "version": "2.0.6", 3234 + "dev": true, 3235 + "license": "MIT" 3236 + }, 3237 + "node_modules/fast-xml-parser": { 3238 + "version": "5.2.5", 3239 + "funding": [ 3240 + { 3241 + "type": "github", 3242 + "url": "https://github.com/sponsors/NaturalIntelligence" 3243 + } 3244 + ], 3245 + "license": "MIT", 3246 + "dependencies": { 3247 + "strnum": "^2.1.0" 3248 + }, 3249 + "bin": { 3250 + "fxparser": "src/cli/cli.js" 3251 + } 3252 + }, 3253 + "node_modules/fdir": { 3254 + "version": "6.5.0", 3255 + "dev": true, 3256 + "license": "MIT", 3257 + "engines": { 3258 + "node": ">=12.0.0" 3259 + }, 3260 + "peerDependencies": { 3261 + "picomatch": "^3 || ^4" 3262 + }, 3263 + "peerDependenciesMeta": { 3264 + "picomatch": { 3265 + "optional": true 3266 + } 3267 + } 3268 + }, 3269 + "node_modules/file-entry-cache": { 3270 + "version": "8.0.0", 3271 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 3272 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 3273 + "dev": true, 3274 + "license": "MIT", 3275 + "dependencies": { 3276 + "flat-cache": "^4.0.0" 3277 + }, 3278 + "engines": { 3279 + "node": ">=16.0.0" 3280 + } 3281 + }, 3282 + "node_modules/find-up": { 3283 + "version": "5.0.0", 3284 + "dev": true, 3285 + "license": "MIT", 3286 + "dependencies": { 3287 + "locate-path": "^6.0.0", 3288 + "path-exists": "^4.0.0" 3289 + }, 3290 + "engines": { 3291 + "node": ">=10" 3292 + }, 3293 + "funding": { 3294 + "url": "https://github.com/sponsors/sindresorhus" 3295 + } 3296 + }, 3297 + "node_modules/flat-cache": { 3298 + "version": "4.0.1", 3299 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 3300 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 3301 + "dev": true, 3302 + "license": "MIT", 3303 + "dependencies": { 3304 + "flatted": "^3.2.9", 3305 + "keyv": "^4.5.4" 3306 + }, 3307 + "engines": { 3308 + "node": ">=16" 3309 + } 3310 + }, 3311 + "node_modules/flatted": { 3312 + "version": "3.3.3", 3313 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 3314 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 3315 + "dev": true, 3316 + "license": "ISC" 3317 + }, 3318 + "node_modules/fsevents": { 3319 + "version": "2.3.3", 3320 + "dev": true, 3321 + "license": "MIT", 3322 + "optional": true, 3323 + "os": [ 3324 + "darwin" 3325 + ], 3326 + "engines": { 3327 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 3328 + } 3329 + }, 3330 + "node_modules/get-tsconfig": { 3331 + "version": "4.13.0", 3332 + "dev": true, 3333 + "license": "MIT", 3334 + "dependencies": { 3335 + "resolve-pkg-maps": "^1.0.0" 3336 + }, 3337 + "funding": { 3338 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 3339 + } 3340 + }, 3341 + "node_modules/glob-parent": { 3342 + "version": "6.0.2", 3343 + "dev": true, 3344 + "license": "ISC", 3345 + "dependencies": { 3346 + "is-glob": "^4.0.3" 3347 + }, 3348 + "engines": { 3349 + "node": ">=10.13.0" 3350 + } 3351 + }, 3352 + "node_modules/globals": { 3353 + "version": "14.0.0", 3354 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 3355 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 3356 + "dev": true, 3357 + "license": "MIT", 3358 + "engines": { 3359 + "node": ">=18" 3360 + }, 3361 + "funding": { 3362 + "url": "https://github.com/sponsors/sindresorhus" 3363 + } 3364 + }, 3365 + "node_modules/has-flag": { 3366 + "version": "4.0.0", 3367 + "dev": true, 3368 + "license": "MIT", 3369 + "engines": { 3370 + "node": ">=8" 3371 + } 3372 + }, 3373 + "node_modules/hono": { 3374 + "version": "4.10.7", 3375 + "license": "MIT", 3376 + "engines": { 3377 + "node": ">=16.9.0" 3378 + } 3379 + }, 3380 + "node_modules/ignore": { 3381 + "version": "7.0.5", 3382 + "dev": true, 3383 + "license": "MIT", 3384 + "engines": { 3385 + "node": ">= 4" 3386 + } 3387 + }, 3388 + "node_modules/import-fresh": { 3389 + "version": "3.3.1", 3390 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 3391 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 3392 + "dev": true, 3393 + "license": "MIT", 3394 + "dependencies": { 3395 + "parent-module": "^1.0.0", 3396 + "resolve-from": "^4.0.0" 3397 + }, 3398 + "engines": { 3399 + "node": ">=6" 3400 + }, 3401 + "funding": { 3402 + "url": "https://github.com/sponsors/sindresorhus" 3403 + } 3404 + }, 3405 + "node_modules/imurmurhash": { 3406 + "version": "0.1.4", 3407 + "dev": true, 3408 + "license": "MIT", 3409 + "engines": { 3410 + "node": ">=0.8.19" 3411 + } 3412 + }, 3413 + "node_modules/is-extglob": { 3414 + "version": "2.1.1", 3415 + "dev": true, 3416 + "license": "MIT", 3417 + "engines": { 3418 + "node": ">=0.10.0" 3419 + } 3420 + }, 3421 + "node_modules/is-glob": { 3422 + "version": "4.0.3", 3423 + "dev": true, 3424 + "license": "MIT", 3425 + "dependencies": { 3426 + "is-extglob": "^2.1.1" 3427 + }, 3428 + "engines": { 3429 + "node": ">=0.10.0" 3430 + } 3431 + }, 3432 + "node_modules/isexe": { 3433 + "version": "2.0.0", 3434 + "dev": true, 3435 + "license": "ISC" 3436 + }, 3437 + "node_modules/js-yaml": { 3438 + "version": "4.1.1", 3439 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 3440 + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 3441 + "dev": true, 3442 + "license": "MIT", 3443 + "dependencies": { 3444 + "argparse": "^2.0.1" 3445 + }, 3446 + "bin": { 3447 + "js-yaml": "bin/js-yaml.js" 3448 + } 3449 + }, 3450 + "node_modules/json-buffer": { 3451 + "version": "3.0.1", 3452 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 3453 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 3454 + "dev": true, 3455 + "license": "MIT" 3456 + }, 3457 + "node_modules/json-schema-traverse": { 3458 + "version": "0.4.1", 3459 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 3460 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 3461 + "dev": true, 3462 + "license": "MIT" 3463 + }, 3464 + "node_modules/json-stable-stringify-without-jsonify": { 3465 + "version": "1.0.1", 3466 + "dev": true, 3467 + "license": "MIT" 3468 + }, 3469 + "node_modules/keyv": { 3470 + "version": "4.5.4", 3471 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 3472 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 3473 + "dev": true, 3474 + "license": "MIT", 3475 + "dependencies": { 3476 + "json-buffer": "3.0.1" 3477 + } 3478 + }, 3479 + "node_modules/levn": { 3480 + "version": "0.4.1", 3481 + "dev": true, 3482 + "license": "MIT", 3483 + "dependencies": { 3484 + "prelude-ls": "^1.2.1", 3485 + "type-check": "~0.4.0" 3486 + }, 3487 + "engines": { 3488 + "node": ">= 0.8.0" 3489 + } 3490 + }, 3491 + "node_modules/locate-path": { 3492 + "version": "6.0.0", 3493 + "dev": true, 3494 + "license": "MIT", 3495 + "dependencies": { 3496 + "p-locate": "^5.0.0" 3497 + }, 3498 + "engines": { 3499 + "node": ">=10" 3500 + }, 3501 + "funding": { 3502 + "url": "https://github.com/sponsors/sindresorhus" 3503 + } 3504 + }, 3505 + "node_modules/lodash.merge": { 3506 + "version": "4.6.2", 3507 + "dev": true, 3508 + "license": "MIT" 3509 + }, 3510 + "node_modules/magic-string": { 3511 + "version": "0.30.21", 3512 + "dev": true, 3513 + "license": "MIT", 3514 + "dependencies": { 3515 + "@jridgewell/sourcemap-codec": "^1.5.5" 3516 + } 3517 + }, 3518 + "node_modules/mime-db": { 3519 + "version": "1.54.0", 3520 + "license": "MIT", 3521 + "engines": { 3522 + "node": ">= 0.6" 3523 + } 3524 + }, 3525 + "node_modules/mime-types": { 3526 + "version": "3.0.2", 3527 + "license": "MIT", 3528 + "dependencies": { 3529 + "mime-db": "^1.54.0" 3530 + }, 3531 + "engines": { 3532 + "node": ">=18" 3533 + }, 3534 + "funding": { 3535 + "type": "opencollective", 3536 + "url": "https://opencollective.com/express" 3537 + } 3538 + }, 3539 + "node_modules/minimatch": { 3540 + "version": "3.1.2", 3541 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 3542 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 3543 + "dev": true, 3544 + "license": "ISC", 3545 + "dependencies": { 3546 + "brace-expansion": "^1.1.7" 3547 + }, 3548 + "engines": { 3549 + "node": "*" 3550 + } 3551 + }, 3552 + "node_modules/ms": { 3553 + "version": "2.1.3", 3554 + "dev": true, 3555 + "license": "MIT" 3556 + }, 3557 + "node_modules/nanoid": { 3558 + "version": "3.3.11", 3559 + "dev": true, 3560 + "funding": [ 3561 + { 3562 + "type": "github", 3563 + "url": "https://github.com/sponsors/ai" 3564 + } 3565 + ], 3566 + "license": "MIT", 3567 + "bin": { 3568 + "nanoid": "bin/nanoid.cjs" 3569 + }, 3570 + "engines": { 3571 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 3572 + } 3573 + }, 3574 + "node_modules/natural-compare": { 3575 + "version": "1.4.0", 3576 + "dev": true, 3577 + "license": "MIT" 3578 + }, 3579 + "node_modules/obug": { 3580 + "version": "2.1.1", 3581 + "dev": true, 3582 + "funding": [ 3583 + "https://github.com/sponsors/sxzz", 3584 + "https://opencollective.com/debug" 3585 + ], 3586 + "license": "MIT" 3587 + }, 3588 + "node_modules/optionator": { 3589 + "version": "0.9.4", 3590 + "dev": true, 3591 + "license": "MIT", 3592 + "dependencies": { 3593 + "deep-is": "^0.1.3", 3594 + "fast-levenshtein": "^2.0.6", 3595 + "levn": "^0.4.1", 3596 + "prelude-ls": "^1.2.1", 3597 + "type-check": "^0.4.0", 3598 + "word-wrap": "^1.2.5" 3599 + }, 3600 + "engines": { 3601 + "node": ">= 0.8.0" 3602 + } 3603 + }, 3604 + "node_modules/p-limit": { 3605 + "version": "3.1.0", 3606 + "dev": true, 3607 + "license": "MIT", 3608 + "dependencies": { 3609 + "yocto-queue": "^0.1.0" 3610 + }, 3611 + "engines": { 3612 + "node": ">=10" 3613 + }, 3614 + "funding": { 3615 + "url": "https://github.com/sponsors/sindresorhus" 3616 + } 3617 + }, 3618 + "node_modules/p-locate": { 3619 + "version": "5.0.0", 3620 + "dev": true, 3621 + "license": "MIT", 3622 + "dependencies": { 3623 + "p-limit": "^3.0.2" 3624 + }, 3625 + "engines": { 3626 + "node": ">=10" 3627 + }, 3628 + "funding": { 3629 + "url": "https://github.com/sponsors/sindresorhus" 3630 + } 3631 + }, 3632 + "node_modules/parent-module": { 3633 + "version": "1.0.1", 3634 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 3635 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 3636 + "dev": true, 3637 + "license": "MIT", 3638 + "dependencies": { 3639 + "callsites": "^3.0.0" 3640 + }, 3641 + "engines": { 3642 + "node": ">=6" 3643 + } 3644 + }, 3645 + "node_modules/path-exists": { 3646 + "version": "4.0.0", 3647 + "dev": true, 3648 + "license": "MIT", 3649 + "engines": { 3650 + "node": ">=8" 3651 + } 3652 + }, 3653 + "node_modules/path-key": { 3654 + "version": "3.1.1", 3655 + "dev": true, 3656 + "license": "MIT", 3657 + "engines": { 3658 + "node": ">=8" 3659 + } 3660 + }, 3661 + "node_modules/pathe": { 3662 + "version": "2.0.3", 3663 + "dev": true, 3664 + "license": "MIT" 3665 + }, 3666 + "node_modules/picocolors": { 3667 + "version": "1.1.1", 3668 + "dev": true, 3669 + "license": "ISC" 3670 + }, 3671 + "node_modules/picomatch": { 3672 + "version": "4.0.3", 3673 + "dev": true, 3674 + "license": "MIT", 3675 + "peer": true, 3676 + "engines": { 3677 + "node": ">=12" 3678 + }, 3679 + "funding": { 3680 + "url": "https://github.com/sponsors/jonschlinkert" 3681 + } 3682 + }, 3683 + "node_modules/postcss": { 3684 + "version": "8.5.6", 3685 + "dev": true, 3686 + "funding": [ 3687 + { 3688 + "type": "opencollective", 3689 + "url": "https://opencollective.com/postcss/" 3690 + }, 3691 + { 3692 + "type": "tidelift", 3693 + "url": "https://tidelift.com/funding/github/npm/postcss" 3694 + }, 3695 + { 3696 + "type": "github", 3697 + "url": "https://github.com/sponsors/ai" 3698 + } 3699 + ], 3700 + "license": "MIT", 3701 + "dependencies": { 3702 + "nanoid": "^3.3.11", 3703 + "picocolors": "^1.1.1", 3704 + "source-map-js": "^1.2.1" 3705 + }, 3706 + "engines": { 3707 + "node": "^10 || ^12 || >=14" 3708 + } 3709 + }, 3710 + "node_modules/prelude-ls": { 3711 + "version": "1.2.1", 3712 + "dev": true, 3713 + "license": "MIT", 3714 + "engines": { 3715 + "node": ">= 0.8.0" 3716 + } 3717 + }, 3718 + "node_modules/punycode": { 3719 + "version": "2.3.1", 3720 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 3721 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 3722 + "dev": true, 3723 + "license": "MIT", 3724 + "engines": { 3725 + "node": ">=6" 3726 + } 3727 + }, 3728 + "node_modules/resolve-from": { 3729 + "version": "4.0.0", 3730 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 3731 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 3732 + "dev": true, 3733 + "license": "MIT", 3734 + "engines": { 3735 + "node": ">=4" 3736 + } 3737 + }, 3738 + "node_modules/resolve-pkg-maps": { 3739 + "version": "1.0.0", 3740 + "dev": true, 3741 + "license": "MIT", 3742 + "funding": { 3743 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 3744 + } 3745 + }, 3746 + "node_modules/rollup": { 3747 + "version": "4.53.3", 3748 + "dev": true, 3749 + "license": "MIT", 3750 + "dependencies": { 3751 + "@types/estree": "1.0.8" 3752 + }, 3753 + "bin": { 3754 + "rollup": "dist/bin/rollup" 3755 + }, 3756 + "engines": { 3757 + "node": ">=18.0.0", 3758 + "npm": ">=8.0.0" 3759 + }, 3760 + "optionalDependencies": { 3761 + "@rollup/rollup-android-arm-eabi": "4.53.3", 3762 + "@rollup/rollup-android-arm64": "4.53.3", 3763 + "@rollup/rollup-darwin-arm64": "4.53.3", 3764 + "@rollup/rollup-darwin-x64": "4.53.3", 3765 + "@rollup/rollup-freebsd-arm64": "4.53.3", 3766 + "@rollup/rollup-freebsd-x64": "4.53.3", 3767 + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", 3768 + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", 3769 + "@rollup/rollup-linux-arm64-gnu": "4.53.3", 3770 + "@rollup/rollup-linux-arm64-musl": "4.53.3", 3771 + "@rollup/rollup-linux-loong64-gnu": "4.53.3", 3772 + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", 3773 + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", 3774 + "@rollup/rollup-linux-riscv64-musl": "4.53.3", 3775 + "@rollup/rollup-linux-s390x-gnu": "4.53.3", 3776 + "@rollup/rollup-linux-x64-gnu": "4.53.3", 3777 + "@rollup/rollup-linux-x64-musl": "4.53.3", 3778 + "@rollup/rollup-openharmony-arm64": "4.53.3", 3779 + "@rollup/rollup-win32-arm64-msvc": "4.53.3", 3780 + "@rollup/rollup-win32-ia32-msvc": "4.53.3", 3781 + "@rollup/rollup-win32-x64-gnu": "4.53.3", 3782 + "@rollup/rollup-win32-x64-msvc": "4.53.3", 3783 + "fsevents": "~2.3.2" 3784 + } 3785 + }, 3786 + "node_modules/semver": { 3787 + "version": "7.7.3", 3788 + "dev": true, 3789 + "license": "ISC", 3790 + "bin": { 3791 + "semver": "bin/semver.js" 3792 + }, 3793 + "engines": { 3794 + "node": ">=10" 3795 + } 3796 + }, 3797 + "node_modules/shebang-command": { 3798 + "version": "2.0.0", 3799 + "dev": true, 3800 + "license": "MIT", 3801 + "dependencies": { 3802 + "shebang-regex": "^3.0.0" 3803 + }, 3804 + "engines": { 3805 + "node": ">=8" 3806 + } 3807 + }, 3808 + "node_modules/shebang-regex": { 3809 + "version": "3.0.0", 3810 + "dev": true, 3811 + "license": "MIT", 3812 + "engines": { 3813 + "node": ">=8" 3814 + } 3815 + }, 3816 + "node_modules/siginfo": { 3817 + "version": "2.0.0", 3818 + "dev": true, 3819 + "license": "ISC" 3820 + }, 3821 + "node_modules/source-map-js": { 3822 + "version": "1.2.1", 3823 + "dev": true, 3824 + "license": "BSD-3-Clause", 3825 + "engines": { 3826 + "node": ">=0.10.0" 3827 + } 3828 + }, 3829 + "node_modules/stackback": { 3830 + "version": "0.0.2", 3831 + "dev": true, 3832 + "license": "MIT" 3833 + }, 3834 + "node_modules/std-env": { 3835 + "version": "3.10.0", 3836 + "dev": true, 3837 + "license": "MIT" 3838 + }, 3839 + "node_modules/strip-json-comments": { 3840 + "version": "3.1.1", 3841 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 3842 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 3843 + "dev": true, 3844 + "license": "MIT", 3845 + "engines": { 3846 + "node": ">=8" 3847 + }, 3848 + "funding": { 3849 + "url": "https://github.com/sponsors/sindresorhus" 3850 + } 3851 + }, 3852 + "node_modules/strnum": { 3853 + "version": "2.1.1", 3854 + "funding": [ 3855 + { 3856 + "type": "github", 3857 + "url": "https://github.com/sponsors/NaturalIntelligence" 3858 + } 3859 + ], 3860 + "license": "MIT" 3861 + }, 3862 + "node_modules/supports-color": { 3863 + "version": "7.2.0", 3864 + "dev": true, 3865 + "license": "MIT", 3866 + "dependencies": { 3867 + "has-flag": "^4.0.0" 3868 + }, 3869 + "engines": { 3870 + "node": ">=8" 3871 + } 3872 + }, 3873 + "node_modules/tiny-lru": { 3874 + "version": "11.4.5", 3875 + "license": "BSD-3-Clause", 3876 + "engines": { 3877 + "node": ">=12" 3878 + } 3879 + }, 3880 + "node_modules/tinybench": { 3881 + "version": "2.9.0", 3882 + "dev": true, 3883 + "license": "MIT" 3884 + }, 3885 + "node_modules/tinyexec": { 3886 + "version": "1.0.2", 3887 + "dev": true, 3888 + "license": "MIT", 3889 + "engines": { 3890 + "node": ">=18" 3891 + } 3892 + }, 3893 + "node_modules/tinyglobby": { 3894 + "version": "0.2.15", 3895 + "dev": true, 3896 + "license": "MIT", 3897 + "dependencies": { 3898 + "fdir": "^6.5.0", 3899 + "picomatch": "^4.0.3" 3900 + }, 3901 + "engines": { 3902 + "node": ">=12.0.0" 3903 + }, 3904 + "funding": { 3905 + "url": "https://github.com/sponsors/SuperchupuDev" 3906 + } 3907 + }, 3908 + "node_modules/tinyrainbow": { 3909 + "version": "3.0.3", 3910 + "dev": true, 3911 + "license": "MIT", 3912 + "engines": { 3913 + "node": ">=14.0.0" 3914 + } 3915 + }, 3916 + "node_modules/ts-api-utils": { 3917 + "version": "2.1.0", 3918 + "dev": true, 3919 + "license": "MIT", 3920 + "engines": { 3921 + "node": ">=18.12" 3922 + }, 3923 + "peerDependencies": { 3924 + "typescript": ">=4.8.4" 3925 + } 3926 + }, 3927 + "node_modules/tslib": { 3928 + "version": "2.8.1", 3929 + "license": "0BSD" 3930 + }, 3931 + "node_modules/tsx": { 3932 + "version": "4.21.0", 3933 + "dev": true, 3934 + "license": "MIT", 3935 + "peer": true, 3936 + "dependencies": { 3937 + "esbuild": "~0.27.0", 3938 + "get-tsconfig": "^4.7.5" 3939 + }, 3940 + "bin": { 3941 + "tsx": "dist/cli.mjs" 3942 + }, 3943 + "engines": { 3944 + "node": ">=18.0.0" 3945 + }, 3946 + "optionalDependencies": { 3947 + "fsevents": "~2.3.3" 3948 + } 3949 + }, 3950 + "node_modules/type-check": { 3951 + "version": "0.4.0", 3952 + "dev": true, 3953 + "license": "MIT", 3954 + "dependencies": { 3955 + "prelude-ls": "^1.2.1" 3956 + }, 3957 + "engines": { 3958 + "node": ">= 0.8.0" 3959 + } 3960 + }, 3961 + "node_modules/typescript": { 3962 + "version": "5.9.3", 3963 + "dev": true, 3964 + "license": "Apache-2.0", 3965 + "peer": true, 3966 + "bin": { 3967 + "tsc": "bin/tsc", 3968 + "tsserver": "bin/tsserver" 3969 + }, 3970 + "engines": { 3971 + "node": ">=14.17" 3972 + } 3973 + }, 3974 + "node_modules/undici-types": { 3975 + "version": "7.16.0", 3976 + "dev": true, 3977 + "license": "MIT" 3978 + }, 3979 + "node_modules/uri-js": { 3980 + "version": "4.4.1", 3981 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 3982 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 3983 + "dev": true, 3984 + "license": "BSD-2-Clause", 3985 + "dependencies": { 3986 + "punycode": "^2.1.0" 3987 + } 3988 + }, 3989 + "node_modules/vite": { 3990 + "version": "7.2.7", 3991 + "dev": true, 3992 + "license": "MIT", 3993 + "peer": true, 3994 + "dependencies": { 3995 + "esbuild": "^0.25.0", 3996 + "fdir": "^6.5.0", 3997 + "picomatch": "^4.0.3", 3998 + "postcss": "^8.5.6", 3999 + "rollup": "^4.43.0", 4000 + "tinyglobby": "^0.2.15" 4001 + }, 4002 + "bin": { 4003 + "vite": "bin/vite.js" 4004 + }, 4005 + "engines": { 4006 + "node": "^20.19.0 || >=22.12.0" 4007 + }, 4008 + "funding": { 4009 + "url": "https://github.com/vitejs/vite?sponsor=1" 4010 + }, 4011 + "optionalDependencies": { 4012 + "fsevents": "~2.3.3" 4013 + }, 4014 + "peerDependencies": { 4015 + "@types/node": "^20.19.0 || >=22.12.0", 4016 + "jiti": ">=1.21.0", 4017 + "less": "^4.0.0", 4018 + "lightningcss": "^1.21.0", 4019 + "sass": "^1.70.0", 4020 + "sass-embedded": "^1.70.0", 4021 + "stylus": ">=0.54.8", 4022 + "sugarss": "^5.0.0", 4023 + "terser": "^5.16.0", 4024 + "tsx": "^4.8.1", 4025 + "yaml": "^2.4.2" 4026 + }, 4027 + "peerDependenciesMeta": { 4028 + "@types/node": { 4029 + "optional": true 4030 + }, 4031 + "jiti": { 4032 + "optional": true 4033 + }, 4034 + "less": { 4035 + "optional": true 4036 + }, 4037 + "lightningcss": { 4038 + "optional": true 4039 + }, 4040 + "sass": { 4041 + "optional": true 4042 + }, 4043 + "sass-embedded": { 4044 + "optional": true 4045 + }, 4046 + "stylus": { 4047 + "optional": true 4048 + }, 4049 + "sugarss": { 4050 + "optional": true 4051 + }, 4052 + "terser": { 4053 + "optional": true 4054 + }, 4055 + "tsx": { 4056 + "optional": true 4057 + }, 4058 + "yaml": { 4059 + "optional": true 4060 + } 4061 + } 4062 + }, 4063 + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { 4064 + "version": "0.25.12", 4065 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 4066 + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 4067 + "cpu": [ 4068 + "ppc64" 4069 + ], 4070 + "dev": true, 4071 + "license": "MIT", 4072 + "optional": true, 4073 + "os": [ 4074 + "aix" 4075 + ], 4076 + "engines": { 4077 + "node": ">=18" 4078 + } 4079 + }, 4080 + "node_modules/vite/node_modules/@esbuild/android-arm": { 4081 + "version": "0.25.12", 4082 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 4083 + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 4084 + "cpu": [ 4085 + "arm" 4086 + ], 4087 + "dev": true, 4088 + "license": "MIT", 4089 + "optional": true, 4090 + "os": [ 4091 + "android" 4092 + ], 4093 + "engines": { 4094 + "node": ">=18" 4095 + } 4096 + }, 4097 + "node_modules/vite/node_modules/@esbuild/android-arm64": { 4098 + "version": "0.25.12", 4099 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 4100 + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 4101 + "cpu": [ 4102 + "arm64" 4103 + ], 4104 + "dev": true, 4105 + "license": "MIT", 4106 + "optional": true, 4107 + "os": [ 4108 + "android" 4109 + ], 4110 + "engines": { 4111 + "node": ">=18" 4112 + } 4113 + }, 4114 + "node_modules/vite/node_modules/@esbuild/android-x64": { 4115 + "version": "0.25.12", 4116 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 4117 + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 4118 + "cpu": [ 4119 + "x64" 4120 + ], 4121 + "dev": true, 4122 + "license": "MIT", 4123 + "optional": true, 4124 + "os": [ 4125 + "android" 4126 + ], 4127 + "engines": { 4128 + "node": ">=18" 4129 + } 4130 + }, 4131 + "node_modules/vite/node_modules/@esbuild/darwin-x64": { 4132 + "version": "0.25.12", 4133 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 4134 + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 4135 + "cpu": [ 4136 + "x64" 4137 + ], 4138 + "dev": true, 4139 + "license": "MIT", 4140 + "optional": true, 4141 + "os": [ 4142 + "darwin" 4143 + ], 4144 + "engines": { 4145 + "node": ">=18" 4146 + } 4147 + }, 4148 + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { 4149 + "version": "0.25.12", 4150 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 4151 + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 4152 + "cpu": [ 4153 + "arm64" 4154 + ], 4155 + "dev": true, 4156 + "license": "MIT", 4157 + "optional": true, 4158 + "os": [ 4159 + "freebsd" 4160 + ], 4161 + "engines": { 4162 + "node": ">=18" 4163 + } 4164 + }, 4165 + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { 4166 + "version": "0.25.12", 4167 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 4168 + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 4169 + "cpu": [ 4170 + "x64" 4171 + ], 4172 + "dev": true, 4173 + "license": "MIT", 4174 + "optional": true, 4175 + "os": [ 4176 + "freebsd" 4177 + ], 4178 + "engines": { 4179 + "node": ">=18" 4180 + } 4181 + }, 4182 + "node_modules/vite/node_modules/@esbuild/linux-arm": { 4183 + "version": "0.25.12", 4184 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 4185 + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 4186 + "cpu": [ 4187 + "arm" 4188 + ], 4189 + "dev": true, 4190 + "license": "MIT", 4191 + "optional": true, 4192 + "os": [ 4193 + "linux" 4194 + ], 4195 + "engines": { 4196 + "node": ">=18" 4197 + } 4198 + }, 4199 + "node_modules/vite/node_modules/@esbuild/linux-arm64": { 4200 + "version": "0.25.12", 4201 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 4202 + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 4203 + "cpu": [ 4204 + "arm64" 4205 + ], 4206 + "dev": true, 4207 + "license": "MIT", 4208 + "optional": true, 4209 + "os": [ 4210 + "linux" 4211 + ], 4212 + "engines": { 4213 + "node": ">=18" 4214 + } 4215 + }, 4216 + "node_modules/vite/node_modules/@esbuild/linux-ia32": { 4217 + "version": "0.25.12", 4218 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 4219 + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 4220 + "cpu": [ 4221 + "ia32" 4222 + ], 4223 + "dev": true, 4224 + "license": "MIT", 4225 + "optional": true, 4226 + "os": [ 4227 + "linux" 4228 + ], 4229 + "engines": { 4230 + "node": ">=18" 4231 + } 4232 + }, 4233 + "node_modules/vite/node_modules/@esbuild/linux-loong64": { 4234 + "version": "0.25.12", 4235 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 4236 + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 4237 + "cpu": [ 4238 + "loong64" 4239 + ], 4240 + "dev": true, 4241 + "license": "MIT", 4242 + "optional": true, 4243 + "os": [ 4244 + "linux" 4245 + ], 4246 + "engines": { 4247 + "node": ">=18" 4248 + } 4249 + }, 4250 + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { 4251 + "version": "0.25.12", 4252 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 4253 + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 4254 + "cpu": [ 4255 + "mips64el" 4256 + ], 4257 + "dev": true, 4258 + "license": "MIT", 4259 + "optional": true, 4260 + "os": [ 4261 + "linux" 4262 + ], 4263 + "engines": { 4264 + "node": ">=18" 4265 + } 4266 + }, 4267 + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { 4268 + "version": "0.25.12", 4269 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 4270 + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 4271 + "cpu": [ 4272 + "ppc64" 4273 + ], 4274 + "dev": true, 4275 + "license": "MIT", 4276 + "optional": true, 4277 + "os": [ 4278 + "linux" 4279 + ], 4280 + "engines": { 4281 + "node": ">=18" 4282 + } 4283 + }, 4284 + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { 4285 + "version": "0.25.12", 4286 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 4287 + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 4288 + "cpu": [ 4289 + "riscv64" 4290 + ], 4291 + "dev": true, 4292 + "license": "MIT", 4293 + "optional": true, 4294 + "os": [ 4295 + "linux" 4296 + ], 4297 + "engines": { 4298 + "node": ">=18" 4299 + } 4300 + }, 4301 + "node_modules/vite/node_modules/@esbuild/linux-s390x": { 4302 + "version": "0.25.12", 4303 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 4304 + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 4305 + "cpu": [ 4306 + "s390x" 4307 + ], 4308 + "dev": true, 4309 + "license": "MIT", 4310 + "optional": true, 4311 + "os": [ 4312 + "linux" 4313 + ], 4314 + "engines": { 4315 + "node": ">=18" 4316 + } 4317 + }, 4318 + "node_modules/vite/node_modules/@esbuild/linux-x64": { 4319 + "version": "0.25.12", 4320 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 4321 + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 4322 + "cpu": [ 4323 + "x64" 4324 + ], 4325 + "dev": true, 4326 + "license": "MIT", 4327 + "optional": true, 4328 + "os": [ 4329 + "linux" 4330 + ], 4331 + "engines": { 4332 + "node": ">=18" 4333 + } 4334 + }, 4335 + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { 4336 + "version": "0.25.12", 4337 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 4338 + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 4339 + "cpu": [ 4340 + "arm64" 4341 + ], 4342 + "dev": true, 4343 + "license": "MIT", 4344 + "optional": true, 4345 + "os": [ 4346 + "netbsd" 4347 + ], 4348 + "engines": { 4349 + "node": ">=18" 4350 + } 4351 + }, 4352 + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { 4353 + "version": "0.25.12", 4354 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 4355 + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 4356 + "cpu": [ 4357 + "x64" 4358 + ], 4359 + "dev": true, 4360 + "license": "MIT", 4361 + "optional": true, 4362 + "os": [ 4363 + "netbsd" 4364 + ], 4365 + "engines": { 4366 + "node": ">=18" 4367 + } 4368 + }, 4369 + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { 4370 + "version": "0.25.12", 4371 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 4372 + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 4373 + "cpu": [ 4374 + "arm64" 4375 + ], 4376 + "dev": true, 4377 + "license": "MIT", 4378 + "optional": true, 4379 + "os": [ 4380 + "openbsd" 4381 + ], 4382 + "engines": { 4383 + "node": ">=18" 4384 + } 4385 + }, 4386 + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { 4387 + "version": "0.25.12", 4388 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 4389 + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 4390 + "cpu": [ 4391 + "x64" 4392 + ], 4393 + "dev": true, 4394 + "license": "MIT", 4395 + "optional": true, 4396 + "os": [ 4397 + "openbsd" 4398 + ], 4399 + "engines": { 4400 + "node": ">=18" 4401 + } 4402 + }, 4403 + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { 4404 + "version": "0.25.12", 4405 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 4406 + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 4407 + "cpu": [ 4408 + "arm64" 4409 + ], 4410 + "dev": true, 4411 + "license": "MIT", 4412 + "optional": true, 4413 + "os": [ 4414 + "openharmony" 4415 + ], 4416 + "engines": { 4417 + "node": ">=18" 4418 + } 4419 + }, 4420 + "node_modules/vite/node_modules/@esbuild/sunos-x64": { 4421 + "version": "0.25.12", 4422 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 4423 + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 4424 + "cpu": [ 4425 + "x64" 4426 + ], 4427 + "dev": true, 4428 + "license": "MIT", 4429 + "optional": true, 4430 + "os": [ 4431 + "sunos" 4432 + ], 4433 + "engines": { 4434 + "node": ">=18" 4435 + } 4436 + }, 4437 + "node_modules/vite/node_modules/@esbuild/win32-arm64": { 4438 + "version": "0.25.12", 4439 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 4440 + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 4441 + "cpu": [ 4442 + "arm64" 4443 + ], 4444 + "dev": true, 4445 + "license": "MIT", 4446 + "optional": true, 4447 + "os": [ 4448 + "win32" 4449 + ], 4450 + "engines": { 4451 + "node": ">=18" 4452 + } 4453 + }, 4454 + "node_modules/vite/node_modules/@esbuild/win32-ia32": { 4455 + "version": "0.25.12", 4456 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 4457 + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 4458 + "cpu": [ 4459 + "ia32" 4460 + ], 4461 + "dev": true, 4462 + "license": "MIT", 4463 + "optional": true, 4464 + "os": [ 4465 + "win32" 4466 + ], 4467 + "engines": { 4468 + "node": ">=18" 4469 + } 4470 + }, 4471 + "node_modules/vite/node_modules/@esbuild/win32-x64": { 4472 + "version": "0.25.12", 4473 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 4474 + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 4475 + "cpu": [ 4476 + "x64" 4477 + ], 4478 + "dev": true, 4479 + "license": "MIT", 4480 + "optional": true, 4481 + "os": [ 4482 + "win32" 4483 + ], 4484 + "engines": { 4485 + "node": ">=18" 4486 + } 4487 + }, 4488 + "node_modules/vite/node_modules/esbuild": { 4489 + "version": "0.25.12", 4490 + "dev": true, 4491 + "hasInstallScript": true, 4492 + "license": "MIT", 4493 + "bin": { 4494 + "esbuild": "bin/esbuild" 4495 + }, 4496 + "engines": { 4497 + "node": ">=18" 4498 + }, 4499 + "optionalDependencies": { 4500 + "@esbuild/aix-ppc64": "0.25.12", 4501 + "@esbuild/android-arm": "0.25.12", 4502 + "@esbuild/android-arm64": "0.25.12", 4503 + "@esbuild/android-x64": "0.25.12", 4504 + "@esbuild/darwin-arm64": "0.25.12", 4505 + "@esbuild/darwin-x64": "0.25.12", 4506 + "@esbuild/freebsd-arm64": "0.25.12", 4507 + "@esbuild/freebsd-x64": "0.25.12", 4508 + "@esbuild/linux-arm": "0.25.12", 4509 + "@esbuild/linux-arm64": "0.25.12", 4510 + "@esbuild/linux-ia32": "0.25.12", 4511 + "@esbuild/linux-loong64": "0.25.12", 4512 + "@esbuild/linux-mips64el": "0.25.12", 4513 + "@esbuild/linux-ppc64": "0.25.12", 4514 + "@esbuild/linux-riscv64": "0.25.12", 4515 + "@esbuild/linux-s390x": "0.25.12", 4516 + "@esbuild/linux-x64": "0.25.12", 4517 + "@esbuild/netbsd-arm64": "0.25.12", 4518 + "@esbuild/netbsd-x64": "0.25.12", 4519 + "@esbuild/openbsd-arm64": "0.25.12", 4520 + "@esbuild/openbsd-x64": "0.25.12", 4521 + "@esbuild/openharmony-arm64": "0.25.12", 4522 + "@esbuild/sunos-x64": "0.25.12", 4523 + "@esbuild/win32-arm64": "0.25.12", 4524 + "@esbuild/win32-ia32": "0.25.12", 4525 + "@esbuild/win32-x64": "0.25.12" 4526 + } 4527 + }, 4528 + "node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { 4529 + "version": "0.25.12", 4530 + "cpu": [ 4531 + "arm64" 4532 + ], 4533 + "dev": true, 4534 + "license": "MIT", 4535 + "optional": true, 4536 + "os": [ 4537 + "darwin" 4538 + ], 4539 + "engines": { 4540 + "node": ">=18" 4541 + } 4542 + }, 4543 + "node_modules/vitest": { 4544 + "version": "4.0.15", 4545 + "dev": true, 4546 + "license": "MIT", 4547 + "dependencies": { 4548 + "@vitest/expect": "4.0.15", 4549 + "@vitest/mocker": "4.0.15", 4550 + "@vitest/pretty-format": "4.0.15", 4551 + "@vitest/runner": "4.0.15", 4552 + "@vitest/snapshot": "4.0.15", 4553 + "@vitest/spy": "4.0.15", 4554 + "@vitest/utils": "4.0.15", 4555 + "es-module-lexer": "^1.7.0", 4556 + "expect-type": "^1.2.2", 4557 + "magic-string": "^0.30.21", 4558 + "obug": "^2.1.1", 4559 + "pathe": "^2.0.3", 4560 + "picomatch": "^4.0.3", 4561 + "std-env": "^3.10.0", 4562 + "tinybench": "^2.9.0", 4563 + "tinyexec": "^1.0.2", 4564 + "tinyglobby": "^0.2.15", 4565 + "tinyrainbow": "^3.0.3", 4566 + "vite": "^6.0.0 || ^7.0.0", 4567 + "why-is-node-running": "^2.3.0" 4568 + }, 4569 + "bin": { 4570 + "vitest": "vitest.mjs" 4571 + }, 4572 + "engines": { 4573 + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" 4574 + }, 4575 + "funding": { 4576 + "url": "https://opencollective.com/vitest" 4577 + }, 4578 + "peerDependencies": { 4579 + "@edge-runtime/vm": "*", 4580 + "@opentelemetry/api": "^1.9.0", 4581 + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", 4582 + "@vitest/browser-playwright": "4.0.15", 4583 + "@vitest/browser-preview": "4.0.15", 4584 + "@vitest/browser-webdriverio": "4.0.15", 4585 + "@vitest/ui": "4.0.15", 4586 + "happy-dom": "*", 4587 + "jsdom": "*" 4588 + }, 4589 + "peerDependenciesMeta": { 4590 + "@edge-runtime/vm": { 4591 + "optional": true 4592 + }, 4593 + "@opentelemetry/api": { 4594 + "optional": true 4595 + }, 4596 + "@types/node": { 4597 + "optional": true 4598 + }, 4599 + "@vitest/browser-playwright": { 4600 + "optional": true 4601 + }, 4602 + "@vitest/browser-preview": { 4603 + "optional": true 4604 + }, 4605 + "@vitest/browser-webdriverio": { 4606 + "optional": true 4607 + }, 4608 + "@vitest/ui": { 4609 + "optional": true 4610 + }, 4611 + "happy-dom": { 4612 + "optional": true 4613 + }, 4614 + "jsdom": { 4615 + "optional": true 4616 + } 4617 + } 4618 + }, 4619 + "node_modules/which": { 4620 + "version": "2.0.2", 4621 + "dev": true, 4622 + "license": "ISC", 4623 + "dependencies": { 4624 + "isexe": "^2.0.0" 4625 + }, 4626 + "bin": { 4627 + "node-which": "bin/node-which" 4628 + }, 4629 + "engines": { 4630 + "node": ">= 8" 4631 + } 4632 + }, 4633 + "node_modules/why-is-node-running": { 4634 + "version": "2.3.0", 4635 + "dev": true, 4636 + "license": "MIT", 4637 + "dependencies": { 4638 + "siginfo": "^2.0.0", 4639 + "stackback": "0.0.2" 4640 + }, 4641 + "bin": { 4642 + "why-is-node-running": "cli.js" 4643 + }, 4644 + "engines": { 4645 + "node": ">=8" 4646 + } 4647 + }, 4648 + "node_modules/word-wrap": { 4649 + "version": "1.2.5", 4650 + "dev": true, 4651 + "license": "MIT", 4652 + "engines": { 4653 + "node": ">=0.10.0" 4654 + } 4655 + }, 4656 + "node_modules/yocto-queue": { 4657 + "version": "0.1.0", 4658 + "dev": true, 4659 + "license": "MIT", 4660 + "engines": { 4661 + "node": ">=10" 4662 + }, 4663 + "funding": { 4664 + "url": "https://github.com/sponsors/sindresorhus" 4665 + } 4666 + } 4667 + } 4668 + }
+40
package.json
··· 1 + { 2 + "name": "tiered-storage", 3 + "version": "1.0.0", 4 + "description": "Tiered storage library with S3, disk, and memory caching", 5 + "main": "dist/index.js", 6 + "types": "dist/index.d.ts", 7 + "type": "module", 8 + "scripts": { 9 + "check": "tsc --noEmit", 10 + "build": "tsc", 11 + "dev": "tsx --watch src/index.ts", 12 + "example": "tsx example.ts", 13 + "serve": "tsx serve-example.ts", 14 + "test": "vitest", 15 + "test:watch": "vitest --watch", 16 + "lint": "eslint src --ext .ts", 17 + "lint:fix": "eslint src --ext .ts --fix", 18 + "typecheck": "tsc --noEmit" 19 + }, 20 + "dependencies": { 21 + "@aws-sdk/client-s3": "^3.500.0", 22 + "hono": "^4.10.7", 23 + "mime-types": "^3.0.2", 24 + "tiny-lru": "^11.0.0" 25 + }, 26 + "devDependencies": { 27 + "@types/bun": "^1.3.4", 28 + "@types/mime-types": "^3.0.1", 29 + "@types/node": "^24.10.1", 30 + "@typescript-eslint/eslint-plugin": "^8.48.1", 31 + "@typescript-eslint/parser": "^8.48.1", 32 + "eslint": "^9.39.1", 33 + "tsx": "^4.0.0", 34 + "typescript": "^5.3.0", 35 + "vitest": "^4.0.15" 36 + }, 37 + "engines": { 38 + "node": ">=18.0.0" 39 + } 40 + }
+469
serve-example.ts
··· 1 + /** 2 + * Example HTTP server serving static sites from tiered storage 3 + * 4 + * This demonstrates a real-world use case: serving static websites 5 + * with automatic caching across hot (memory), warm (disk), and cold (S3) tiers. 6 + * 7 + * Run with: bun run serve 8 + */ 9 + 10 + import { Hono } from 'hono'; 11 + import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js'; 12 + import { readFile, readdir } from 'node:fs/promises'; 13 + import { lookup } from 'mime-types'; 14 + 15 + const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example'; 16 + const S3_METADATA_BUCKET = process.env.S3_METADATA_BUCKET; 17 + const S3_REGION = process.env.S3_REGION || 'us-east-1'; 18 + const S3_ENDPOINT = process.env.S3_ENDPOINT; 19 + const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false'; 20 + const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; 21 + const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; 22 + const PORT = parseInt(process.env.PORT || '3000', 10); 23 + 24 + const storage = new TieredStorage({ 25 + tiers: { 26 + hot: new MemoryStorageTier({ 27 + maxSizeBytes: 50 * 1024 * 1024, 28 + maxItems: 500, 29 + }), 30 + warm: new DiskStorageTier({ 31 + directory: './cache/sites', 32 + maxSizeBytes: 1024 * 1024 * 1024, 33 + }), 34 + cold: new S3StorageTier({ 35 + bucket: S3_BUCKET, 36 + metadataBucket: S3_METADATA_BUCKET, 37 + region: S3_REGION, 38 + endpoint: S3_ENDPOINT, 39 + forcePathStyle: S3_FORCE_PATH_STYLE, 40 + credentials: 41 + AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY 42 + ? { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY } 43 + : undefined, 44 + prefix: 'demo-sites/', 45 + }), 46 + }, 47 + compression: true, 48 + defaultTTL: 14 * 24 * 60 * 60 * 1000, 49 + promotionStrategy: 'lazy', 50 + }); 51 + 52 + const app = new Hono(); 53 + 54 + // Site metadata 55 + const siteId = 'did:plc:example123'; 56 + const siteName = 'tiered-cache-demo'; 57 + 58 + /** 59 + * Load the example site into storage 60 + */ 61 + async function loadExampleSite() { 62 + console.log('\n📦 Loading example site into tiered storage...\n'); 63 + 64 + const files = [ 65 + { name: 'index.html', skipTiers: [], mimeType: 'text/html' }, 66 + { name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' }, 67 + { name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' }, 68 + { name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' }, 69 + { name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' }, 70 + ]; 71 + 72 + for (const file of files) { 73 + const content = await readFile(`./example-site/${file.name}`, 'utf-8'); 74 + const key = `${siteId}/${siteName}/${file.name}`; 75 + 76 + await storage.set(key, content, { 77 + skipTiers: file.skipTiers as ('hot' | 'warm')[], 78 + metadata: { mimeType: file.mimeType }, 79 + }); 80 + 81 + const tierInfo = 82 + file.skipTiers.length === 0 83 + ? '🔥 hot + 💾 warm + ☁️ cold' 84 + : `💾 warm + ☁️ cold (skipped hot)`; 85 + const sizeKB = (content.length / 1024).toFixed(2); 86 + console.log(` ✓ ${file.name.padEnd(15)} ${sizeKB.padStart(6)} KB → ${tierInfo}`); 87 + } 88 + 89 + console.log('\n✅ Site loaded successfully!\n'); 90 + } 91 + 92 + /** 93 + * Serve a file from tiered storage 94 + */ 95 + app.get('/sites/:did/:siteName/:path{.*}', async (c) => { 96 + const { did, siteName, path } = c.req.param(); 97 + let filePath = path || 'index.html'; 98 + 99 + if (filePath === '' || filePath.endsWith('/')) { 100 + filePath += 'index.html'; 101 + } 102 + 103 + const key = `${did}/${siteName}/${filePath}`; 104 + 105 + try { 106 + const result = await storage.getWithMetadata(key); 107 + 108 + if (!result) { 109 + return c.text('404 Not Found', 404); 110 + } 111 + 112 + const mimeType = result.metadata.customMetadata?.mimeType || lookup(filePath) || 'application/octet-stream'; 113 + 114 + const headers: Record<string, string> = { 115 + 'Content-Type': mimeType, 116 + 'X-Cache-Tier': result.source, // Which tier served this 117 + 'X-Cache-Size': result.metadata.size.toString(), 118 + 'X-Cache-Compressed': result.metadata.compressed.toString(), 119 + 'X-Cache-Access-Count': result.metadata.accessCount.toString(), 120 + }; 121 + 122 + // Add cache control based on tier 123 + if (result.source === 'hot') { 124 + headers['X-Cache-Status'] = 'HIT-MEMORY'; 125 + } else if (result.source === 'warm') { 126 + headers['X-Cache-Status'] = 'HIT-DISK'; 127 + } else { 128 + headers['X-Cache-Status'] = 'HIT-S3'; 129 + } 130 + 131 + const emoji = result.source === 'hot' ? '🔥' : result.source === 'warm' ? '💾' : '☁️'; 132 + console.log(`${emoji} ${filePath.padEnd(20)} served from ${result.source.padEnd(4)} (${(result.metadata.size / 1024).toFixed(2)} KB, access #${result.metadata.accessCount})`); 133 + 134 + return c.body(result.data as any, 200, headers); 135 + } catch (error: any) { 136 + console.error(`❌ Error serving ${filePath}:`, error.message); 137 + return c.text('500 Internal Server Error', 500); 138 + } 139 + }); 140 + 141 + /** 142 + * Admin endpoint: Cache statistics 143 + */ 144 + app.get('/admin/stats', async (c) => { 145 + const stats = await storage.getStats(); 146 + 147 + const html = ` 148 + <!DOCTYPE html> 149 + <html> 150 + <head> 151 + <title>Tiered Storage Statistics</title> 152 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 153 + <style> 154 + * { margin: 0; padding: 0; box-sizing: border-box; } 155 + body { 156 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 157 + background: #0f172a; 158 + color: #f1f5f9; 159 + padding: 2rem; 160 + line-height: 1.6; 161 + } 162 + .container { max-width: 1200px; margin: 0 auto; } 163 + h1 { 164 + font-size: 2rem; 165 + margin-bottom: 0.5rem; 166 + background: linear-gradient(135deg, #3b82f6, #8b5cf6); 167 + -webkit-background-clip: text; 168 + -webkit-text-fill-color: transparent; 169 + } 170 + .subtitle { color: #94a3b8; margin-bottom: 2rem; } 171 + .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } 172 + .card { 173 + background: #1e293b; 174 + border: 1px solid #334155; 175 + border-radius: 0.5rem; 176 + padding: 1.5rem; 177 + } 178 + .tier-hot { border-left: 4px solid #ef4444; } 179 + .tier-warm { border-left: 4px solid #f59e0b; } 180 + .tier-cold { border-left: 4px solid #3b82f6; } 181 + .card-title { 182 + font-size: 1.2rem; 183 + font-weight: 600; 184 + margin-bottom: 1rem; 185 + display: flex; 186 + align-items: center; 187 + gap: 0.5rem; 188 + } 189 + .stat { margin-bottom: 0.75rem; } 190 + .stat-label { color: #94a3b8; font-size: 0.9rem; } 191 + .stat-value { color: #f1f5f9; font-size: 1.5rem; font-weight: 700; } 192 + .overall { background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1)); } 193 + .refresh { 194 + display: inline-block; 195 + background: #3b82f6; 196 + color: white; 197 + padding: 0.75rem 1.5rem; 198 + border-radius: 0.5rem; 199 + text-decoration: none; 200 + font-weight: 600; 201 + margin-top: 1rem; 202 + } 203 + .refresh:hover { background: #2563eb; } 204 + code { 205 + background: #334155; 206 + padding: 0.2rem 0.5rem; 207 + border-radius: 0.25rem; 208 + font-size: 0.9em; 209 + } 210 + </style> 211 + </head> 212 + <body> 213 + <div class="container"> 214 + <h1>📊 Tiered Storage Statistics</h1> 215 + <p class="subtitle">Real-time cache performance metrics • Auto-refresh every 5 seconds</p> 216 + 217 + <div class="grid"> 218 + <div class="card tier-hot"> 219 + <div class="card-title">🔥 Hot Tier (Memory)</div> 220 + <div class="stat"> 221 + <div class="stat-label">Items</div> 222 + <div class="stat-value">${stats.hot?.items || 0}</div> 223 + </div> 224 + <div class="stat"> 225 + <div class="stat-label">Size</div> 226 + <div class="stat-value">${((stats.hot?.bytes || 0) / 1024).toFixed(2)} KB</div> 227 + </div> 228 + <div class="stat"> 229 + <div class="stat-label">Hits / Misses</div> 230 + <div class="stat-value">${stats.hot?.hits || 0} / ${stats.hot?.misses || 0}</div> 231 + </div> 232 + <div class="stat"> 233 + <div class="stat-label">Evictions</div> 234 + <div class="stat-value">${stats.hot?.evictions || 0}</div> 235 + </div> 236 + </div> 237 + 238 + <div class="card tier-warm"> 239 + <div class="card-title">💾 Warm Tier (Disk)</div> 240 + <div class="stat"> 241 + <div class="stat-label">Items</div> 242 + <div class="stat-value">${stats.warm?.items || 0}</div> 243 + </div> 244 + <div class="stat"> 245 + <div class="stat-label">Size</div> 246 + <div class="stat-value">${((stats.warm?.bytes || 0) / 1024).toFixed(2)} KB</div> 247 + </div> 248 + <div class="stat"> 249 + <div class="stat-label">Hits / Misses</div> 250 + <div class="stat-value">${stats.warm?.hits || 0} / ${stats.warm?.misses || 0}</div> 251 + </div> 252 + </div> 253 + 254 + <div class="card tier-cold"> 255 + <div class="card-title">☁️ Cold Tier (S3)</div> 256 + <div class="stat"> 257 + <div class="stat-label">Items</div> 258 + <div class="stat-value">${stats.cold.items}</div> 259 + </div> 260 + <div class="stat"> 261 + <div class="stat-label">Size</div> 262 + <div class="stat-value">${(stats.cold.bytes / 1024).toFixed(2)} KB</div> 263 + </div> 264 + </div> 265 + </div> 266 + 267 + <div class="card overall"> 268 + <div class="card-title">📈 Overall Performance</div> 269 + <div class="grid" style="grid-template-columns: repeat(3, 1fr);"> 270 + <div class="stat"> 271 + <div class="stat-label">Total Hits</div> 272 + <div class="stat-value">${stats.totalHits}</div> 273 + </div> 274 + <div class="stat"> 275 + <div class="stat-label">Total Misses</div> 276 + <div class="stat-value">${stats.totalMisses}</div> 277 + </div> 278 + <div class="stat"> 279 + <div class="stat-label">Hit Rate</div> 280 + <div class="stat-value">${(stats.hitRate * 100).toFixed(1)}%</div> 281 + </div> 282 + </div> 283 + </div> 284 + 285 + <div style="margin-top: 2rem; padding: 1rem; background: #1e293b; border-radius: 0.5rem; border: 1px solid #334155;"> 286 + <p style="margin-bottom: 0.5rem;"><strong>Try it out:</strong></p> 287 + <p>Visit <code>http://localhost:${PORT}/sites/${siteId}/${siteName}/</code> to see the site</p> 288 + <p>Watch the stats update as you browse different pages!</p> 289 + </div> 290 + </div> 291 + 292 + <script> 293 + // Auto-refresh stats every 5 seconds 294 + setTimeout(() => window.location.reload(), 5000); 295 + </script> 296 + </body> 297 + </html> 298 + `; 299 + 300 + return c.html(html); 301 + }); 302 + 303 + /** 304 + * Admin endpoint: Invalidate cache 305 + */ 306 + app.post('/admin/invalidate/:did/:siteName', async (c) => { 307 + const { did, siteName } = c.req.param(); 308 + const prefix = `${did}/${siteName}/`; 309 + const deleted = await storage.invalidate(prefix); 310 + 311 + console.log(`🗑️ Invalidated ${deleted} files for ${did}/${siteName}`); 312 + 313 + return c.json({ success: true, deleted, prefix }); 314 + }); 315 + 316 + /** 317 + * Admin endpoint: Bootstrap hot cache 318 + */ 319 + app.post('/admin/bootstrap/hot', async (c) => { 320 + const limit = parseInt(c.req.query('limit') || '100', 10); 321 + const loaded = await storage.bootstrapHot(limit); 322 + 323 + console.log(`🔥 Bootstrapped ${loaded} items into hot tier`); 324 + 325 + return c.json({ success: true, loaded, limit }); 326 + }); 327 + 328 + /** 329 + * Root redirect 330 + */ 331 + app.get('/', (c) => { 332 + return c.redirect(`/sites/${siteId}/${siteName}/`); 333 + }); 334 + 335 + /** 336 + * Health check 337 + */ 338 + app.get('/health', (c) => c.json({ status: 'ok' })); 339 + 340 + /** 341 + * Test S3 connection 342 + */ 343 + async function testS3Connection() { 344 + console.log('\n🔍 Testing S3 connection...\n'); 345 + 346 + try { 347 + // Try to get stats (which lists objects) 348 + const stats = await storage.getStats(); 349 + console.log(`✅ S3 connection successful!`); 350 + console.log(` Found ${stats.cold.items} items (${(stats.cold.bytes / 1024).toFixed(2)} KB)\n`); 351 + return true; 352 + } catch (error: any) { 353 + console.error('❌ S3 connection failed:', error.message); 354 + console.error('\nDebug Info:'); 355 + console.error(` Bucket: ${S3_BUCKET}`); 356 + console.error(` Region: ${S3_REGION}`); 357 + console.error(` Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`); 358 + console.error(` Access Key: ${AWS_ACCESS_KEY_ID?.substring(0, 8)}...`); 359 + console.error(` Force Path Style: ${S3_FORCE_PATH_STYLE}`); 360 + console.error('\nCommon issues:'); 361 + console.error(' • Check that bucket exists'); 362 + console.error(' • Verify credentials are correct'); 363 + console.error(' • Ensure endpoint URL is correct'); 364 + console.error(' • Check firewall/network access'); 365 + console.error(' • For S3-compatible services, verify region name\n'); 366 + return false; 367 + } 368 + } 369 + 370 + /** 371 + * Periodic cache clearing - demonstrates tier bootstrapping 372 + */ 373 + function startCacheClearInterval() { 374 + const CLEAR_INTERVAL_MS = 60 * 1000; // 1 minute 375 + 376 + setInterval(async () => { 377 + console.log('\n' + '═'.repeat(60)); 378 + console.log('🧹 CACHE CLEAR - Clearing hot and warm tiers...'); 379 + console.log(' (Cold tier on S3 remains intact)'); 380 + console.log('═'.repeat(60) + '\n'); 381 + 382 + try { 383 + // Clear hot tier (memory) 384 + if (storage['config'].tiers.hot) { 385 + await storage['config'].tiers.hot.clear(); 386 + console.log('✓ Hot tier (memory) cleared'); 387 + } 388 + 389 + // Clear warm tier (disk) 390 + if (storage['config'].tiers.warm) { 391 + await storage['config'].tiers.warm.clear(); 392 + console.log('✓ Warm tier (disk) cleared'); 393 + } 394 + 395 + console.log('\n💡 Next request will bootstrap from S3 (cold tier)\n'); 396 + console.log('─'.repeat(60) + '\n'); 397 + } catch (error: any) { 398 + console.error('❌ Error clearing cache:', error.message); 399 + } 400 + }, CLEAR_INTERVAL_MS); 401 + 402 + console.log(`⏰ Cache clear interval started (every ${CLEAR_INTERVAL_MS / 1000}s)\n`); 403 + } 404 + 405 + /** 406 + * Main startup 407 + */ 408 + async function main() { 409 + console.log('╔════════════════════════════════════════════════╗'); 410 + console.log('║ Tiered Storage Demo Server ║'); 411 + console.log('╚════════════════════════════════════════════════╝\n'); 412 + 413 + console.log('Configuration:'); 414 + console.log(` S3 Bucket: ${S3_BUCKET}`); 415 + console.log(` S3 Region: ${S3_REGION}`); 416 + console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`); 417 + console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`); 418 + console.log(` Port: ${PORT}`); 419 + 420 + try { 421 + // Test S3 connection first 422 + const s3Connected = await testS3Connection(); 423 + if (!s3Connected) { 424 + process.exit(1); 425 + } 426 + 427 + // Load the example site 428 + await loadExampleSite(); 429 + 430 + // Start periodic cache clearing 431 + startCacheClearInterval(); 432 + 433 + // Start the server 434 + console.log('🚀 Starting server...\n'); 435 + 436 + const server = Bun.serve({ 437 + port: PORT, 438 + fetch: app.fetch, 439 + }); 440 + 441 + console.log('╔════════════════════════════════════════════════╗'); 442 + console.log('║ Server Running! ║'); 443 + console.log('╚════════════════════════════════════════════════╝\n'); 444 + console.log(`📍 Demo Site: http://localhost:${PORT}/sites/${siteId}/${siteName}/`); 445 + console.log(`📊 Statistics: http://localhost:${PORT}/admin/stats`); 446 + console.log(`💚 Health: http://localhost:${PORT}/health`); 447 + console.log('\n🎯 Try browsing the site and watch which tier serves each file!\n'); 448 + console.log('💡 Caches clear every 60 seconds - watch files get re-fetched from S3!\n'); 449 + if (S3_METADATA_BUCKET) { 450 + console.log(`✨ Metadata bucket: ${S3_METADATA_BUCKET} (fast updates enabled!)\n`); 451 + } else { 452 + console.log('⚠️ No metadata bucket - using legacy mode (slower updates)\n'); 453 + } 454 + console.log('Press Ctrl+C to stop\n'); 455 + console.log('─'.repeat(60)); 456 + console.log('Request Log:\n'); 457 + } catch (error: any) { 458 + console.error('\n❌ Failed to start server:', error.message); 459 + if (error.message.includes('Forbidden')) { 460 + console.error('\nS3 connection issue. Check:'); 461 + console.error(' 1. Bucket exists on S3 service'); 462 + console.error(' 2. Credentials are correct'); 463 + console.error(' 3. Permissions allow read/write'); 464 + } 465 + process.exit(1); 466 + } 467 + } 468 + 469 + main().catch(console.error);
+682
src/TieredStorage.ts
··· 1 + import type { 2 + TieredStorageConfig, 3 + SetOptions, 4 + StorageResult, 5 + SetResult, 6 + StorageMetadata, 7 + AllTierStats, 8 + StorageSnapshot, 9 + } from './types/index.js'; 10 + import { compress, decompress } from './utils/compression.js'; 11 + import { defaultSerialize, defaultDeserialize } from './utils/serialization.js'; 12 + import { calculateChecksum } from './utils/checksum.js'; 13 + 14 + /** 15 + * Main orchestrator for tiered storage system. 16 + * 17 + * @typeParam T - The type of data being stored 18 + * 19 + * @remarks 20 + * Implements a cascading containment model: 21 + * - **Write Strategy (Cascading Down):** Write to hot → also writes to warm and cold 22 + * - **Read Strategy (Bubbling Up):** Check hot first → if miss, check warm → if miss, check cold 23 + * - **Bootstrap Strategy:** Hot can bootstrap from warm, warm can bootstrap from cold 24 + * 25 + * The cold tier is the source of truth and is required. 26 + * Hot and warm tiers are optional performance optimizations. 27 + * 28 + * @example 29 + * ```typescript 30 + * const storage = new TieredStorage({ 31 + * tiers: { 32 + * hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB 33 + * warm: new DiskStorageTier({ directory: './cache' }), 34 + * cold: new S3StorageTier({ bucket: 'my-bucket', region: 'us-east-1' }), 35 + * }, 36 + * compression: true, 37 + * defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days 38 + * promotionStrategy: 'lazy', 39 + * }); 40 + * 41 + * // Store data (cascades to all tiers) 42 + * await storage.set('user:123', { name: 'Alice' }); 43 + * 44 + * // Retrieve data (bubbles up from cold → warm → hot) 45 + * const user = await storage.get('user:123'); 46 + * 47 + * // Invalidate all keys with prefix 48 + * await storage.invalidate('user:'); 49 + * ``` 50 + */ 51 + export class TieredStorage<T = unknown> { 52 + private serialize: (data: unknown) => Promise<Uint8Array>; 53 + private deserialize: (data: Uint8Array) => Promise<unknown>; 54 + 55 + constructor(private config: TieredStorageConfig) { 56 + if (!config.tiers.cold) { 57 + throw new Error('Cold tier is required'); 58 + } 59 + 60 + this.serialize = config.serialization?.serialize ?? defaultSerialize; 61 + this.deserialize = config.serialization?.deserialize ?? defaultDeserialize; 62 + } 63 + 64 + /** 65 + * Retrieve data for a key. 66 + * 67 + * @param key - The key to retrieve 68 + * @returns The data, or null if not found or expired 69 + * 70 + * @remarks 71 + * Checks tiers in order: hot → warm → cold. 72 + * On cache miss, promotes data to upper tiers based on promotionStrategy. 73 + * Automatically handles decompression and deserialization. 74 + * Returns null if key doesn't exist or has expired (TTL). 75 + */ 76 + async get(key: string): Promise<T | null> { 77 + const result = await this.getWithMetadata(key); 78 + return result ? result.data : null; 79 + } 80 + 81 + /** 82 + * Retrieve data with metadata and source tier information. 83 + * 84 + * @param key - The key to retrieve 85 + * @returns The data, metadata, and source tier, or null if not found 86 + * 87 + * @remarks 88 + * Use this when you need to know: 89 + * - Which tier served the data (for observability) 90 + * - Metadata like access count, TTL, checksum 91 + * - When the data was created/last accessed 92 + */ 93 + async getWithMetadata(key: string): Promise<StorageResult<T> | null> { 94 + // 1. Check hot tier first 95 + if (this.config.tiers.hot) { 96 + const data = await this.config.tiers.hot.get(key); 97 + if (data) { 98 + const metadata = await this.config.tiers.hot.getMetadata(key); 99 + if (!metadata) { 100 + await this.delete(key); 101 + } else if (this.isExpired(metadata)) { 102 + await this.delete(key); 103 + return null; 104 + } else { 105 + await this.updateAccessStats(key, 'hot'); 106 + return { 107 + data: (await this.deserializeData(data)) as T, 108 + metadata, 109 + source: 'hot', 110 + }; 111 + } 112 + } 113 + } 114 + 115 + // 2. Check warm tier 116 + if (this.config.tiers.warm) { 117 + const data = await this.config.tiers.warm.get(key); 118 + if (data) { 119 + const metadata = await this.config.tiers.warm.getMetadata(key); 120 + if (!metadata) { 121 + await this.delete(key); 122 + } else if (this.isExpired(metadata)) { 123 + await this.delete(key); 124 + return null; 125 + } else { 126 + if (this.config.tiers.hot && this.config.promotionStrategy === 'eager') { 127 + await this.config.tiers.hot.set(key, data, metadata); 128 + } 129 + 130 + await this.updateAccessStats(key, 'warm'); 131 + return { 132 + data: (await this.deserializeData(data)) as T, 133 + metadata, 134 + source: 'warm', 135 + }; 136 + } 137 + } 138 + } 139 + 140 + // 3. Check cold tier (source of truth) 141 + const data = await this.config.tiers.cold.get(key); 142 + if (data) { 143 + const metadata = await this.config.tiers.cold.getMetadata(key); 144 + if (!metadata) { 145 + await this.config.tiers.cold.delete(key); 146 + return null; 147 + } 148 + 149 + if (this.isExpired(metadata)) { 150 + await this.delete(key); 151 + return null; 152 + } 153 + 154 + // Promote to warm and hot (if configured) 155 + if (this.config.promotionStrategy === 'eager') { 156 + if (this.config.tiers.warm) { 157 + await this.config.tiers.warm.set(key, data, metadata); 158 + } 159 + if (this.config.tiers.hot) { 160 + await this.config.tiers.hot.set(key, data, metadata); 161 + } 162 + } 163 + 164 + await this.updateAccessStats(key, 'cold'); 165 + return { 166 + data: (await this.deserializeData(data)) as T, 167 + metadata, 168 + source: 'cold', 169 + }; 170 + } 171 + 172 + return null; 173 + } 174 + 175 + /** 176 + * Store data with optional configuration. 177 + * 178 + * @param key - The key to store under 179 + * @param data - The data to store 180 + * @param options - Optional configuration (TTL, metadata, tier skipping) 181 + * @returns Information about what was stored and where 182 + * 183 + * @remarks 184 + * Data cascades down through tiers: 185 + * - If written to hot, also written to warm and cold 186 + * - If written to warm (hot skipped), also written to cold 187 + * - Cold is always written (source of truth) 188 + * 189 + * Use `skipTiers` to control placement. For example: 190 + * - Large files: `skipTiers: ['hot']` to avoid memory bloat 191 + * - Critical small files: Write to all tiers for fastest access 192 + * 193 + * Automatically handles serialization and optional compression. 194 + */ 195 + async set(key: string, data: T, options?: SetOptions): Promise<SetResult> { 196 + // 1. Serialize data 197 + const serialized = await this.serialize(data); 198 + 199 + // 2. Optionally compress 200 + const finalData = this.config.compression ? await compress(serialized) : serialized; 201 + 202 + // 3. Create metadata 203 + const metadata = this.createMetadata(key, finalData, options); 204 + 205 + // 4. Write to all tiers (cascading down) 206 + const tiersWritten: ('hot' | 'warm' | 'cold')[] = []; 207 + 208 + // Write to hot (if configured and not skipped) 209 + if (this.config.tiers.hot && !options?.skipTiers?.includes('hot')) { 210 + await this.config.tiers.hot.set(key, finalData, metadata); 211 + tiersWritten.push('hot'); 212 + 213 + // Hot writes cascade to warm 214 + if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) { 215 + await this.config.tiers.warm.set(key, finalData, metadata); 216 + tiersWritten.push('warm'); 217 + } 218 + } else if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) { 219 + // Write to warm (if hot skipped) 220 + await this.config.tiers.warm.set(key, finalData, metadata); 221 + tiersWritten.push('warm'); 222 + } 223 + 224 + // Always write to cold (source of truth) 225 + await this.config.tiers.cold.set(key, finalData, metadata); 226 + tiersWritten.push('cold'); 227 + 228 + return { key, metadata, tiersWritten }; 229 + } 230 + 231 + /** 232 + * Delete data from all tiers. 233 + * 234 + * @param key - The key to delete 235 + * 236 + * @remarks 237 + * Deletes from all configured tiers in parallel. 238 + * Does not throw if the key doesn't exist. 239 + */ 240 + async delete(key: string): Promise<void> { 241 + await Promise.all([ 242 + this.config.tiers.hot?.delete(key), 243 + this.config.tiers.warm?.delete(key), 244 + this.config.tiers.cold.delete(key), 245 + ]); 246 + } 247 + 248 + /** 249 + * Check if a key exists in any tier. 250 + * 251 + * @param key - The key to check 252 + * @returns true if the key exists and hasn't expired 253 + * 254 + * @remarks 255 + * Checks tiers in order: hot → warm → cold. 256 + * Returns false if key exists but has expired. 257 + */ 258 + async exists(key: string): Promise<boolean> { 259 + // Check hot first (fastest) 260 + if (this.config.tiers.hot && (await this.config.tiers.hot.exists(key))) { 261 + const metadata = await this.config.tiers.hot.getMetadata(key); 262 + if (metadata && !this.isExpired(metadata)) { 263 + return true; 264 + } 265 + } 266 + 267 + // Check warm 268 + if (this.config.tiers.warm && (await this.config.tiers.warm.exists(key))) { 269 + const metadata = await this.config.tiers.warm.getMetadata(key); 270 + if (metadata && !this.isExpired(metadata)) { 271 + return true; 272 + } 273 + } 274 + 275 + // Check cold (source of truth) 276 + if (await this.config.tiers.cold.exists(key)) { 277 + const metadata = await this.config.tiers.cold.getMetadata(key); 278 + if (metadata && !this.isExpired(metadata)) { 279 + return true; 280 + } 281 + } 282 + 283 + return false; 284 + } 285 + 286 + /** 287 + * Renew TTL for a key. 288 + * 289 + * @param key - The key to touch 290 + * @param ttlMs - Optional new TTL in milliseconds (uses default if not provided) 291 + * 292 + * @remarks 293 + * Updates the TTL and lastAccessed timestamp in all tiers. 294 + * Useful for implementing "keep alive" behavior for actively used keys. 295 + * Does nothing if no TTL is configured. 296 + */ 297 + async touch(key: string, ttlMs?: number): Promise<void> { 298 + const ttl = ttlMs ?? this.config.defaultTTL; 299 + if (!ttl) return; 300 + 301 + const newTTL = new Date(Date.now() + ttl); 302 + 303 + for (const tier of [this.config.tiers.hot, this.config.tiers.warm, this.config.tiers.cold]) { 304 + if (!tier) continue; 305 + 306 + const metadata = await tier.getMetadata(key); 307 + if (metadata) { 308 + metadata.ttl = newTTL; 309 + metadata.lastAccessed = new Date(); 310 + await tier.setMetadata(key, metadata); 311 + } 312 + } 313 + } 314 + 315 + /** 316 + * Invalidate all keys matching a prefix. 317 + * 318 + * @param prefix - The prefix to match (e.g., 'user:' matches 'user:123', 'user:456') 319 + * @returns Number of keys deleted 320 + * 321 + * @remarks 322 + * Useful for bulk invalidation: 323 + * - Site invalidation: `invalidate('site:abc:')` 324 + * - User invalidation: `invalidate('user:123:')` 325 + * - Global invalidation: `invalidate('')` (deletes everything) 326 + * 327 + * Deletes from all tiers in parallel for efficiency. 328 + */ 329 + async invalidate(prefix: string): Promise<number> { 330 + const keysToDelete = new Set<string>(); 331 + 332 + // Collect all keys matching prefix from all tiers 333 + if (this.config.tiers.hot) { 334 + for await (const key of this.config.tiers.hot.listKeys(prefix)) { 335 + keysToDelete.add(key); 336 + } 337 + } 338 + 339 + if (this.config.tiers.warm) { 340 + for await (const key of this.config.tiers.warm.listKeys(prefix)) { 341 + keysToDelete.add(key); 342 + } 343 + } 344 + 345 + for await (const key of this.config.tiers.cold.listKeys(prefix)) { 346 + keysToDelete.add(key); 347 + } 348 + 349 + // Delete from all tiers in parallel 350 + const keys = Array.from(keysToDelete); 351 + 352 + await Promise.all([ 353 + this.config.tiers.hot?.deleteMany(keys), 354 + this.config.tiers.warm?.deleteMany(keys), 355 + this.config.tiers.cold.deleteMany(keys), 356 + ]); 357 + 358 + return keys.length; 359 + } 360 + 361 + /** 362 + * List all keys, optionally filtered by prefix. 363 + * 364 + * @param prefix - Optional prefix to filter keys 365 + * @returns Async iterator of keys 366 + * 367 + * @remarks 368 + * Returns keys from the cold tier (source of truth). 369 + * Memory-efficient - streams keys rather than loading all into memory. 370 + * 371 + * @example 372 + * ```typescript 373 + * for await (const key of storage.listKeys('user:')) { 374 + * console.log(key); 375 + * } 376 + * ``` 377 + */ 378 + async *listKeys(prefix?: string): AsyncIterableIterator<string> { 379 + // List from cold tier (source of truth) 380 + for await (const key of this.config.tiers.cold.listKeys(prefix)) { 381 + yield key; 382 + } 383 + } 384 + 385 + /** 386 + * Get aggregated statistics across all tiers. 387 + * 388 + * @returns Statistics including size, item count, hits, misses, hit rate 389 + * 390 + * @remarks 391 + * Useful for monitoring and capacity planning. 392 + * Hit rate is calculated as: hits / (hits + misses). 393 + */ 394 + async getStats(): Promise<AllTierStats> { 395 + const [hot, warm, cold] = await Promise.all([ 396 + this.config.tiers.hot?.getStats(), 397 + this.config.tiers.warm?.getStats(), 398 + this.config.tiers.cold.getStats(), 399 + ]); 400 + 401 + const totalHits = (hot?.hits ?? 0) + (warm?.hits ?? 0) + (cold?.hits ?? 0); 402 + const totalMisses = (hot?.misses ?? 0) + (warm?.misses ?? 0) + (cold?.misses ?? 0); 403 + const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0; 404 + 405 + return { 406 + ...(hot && { hot }), 407 + ...(warm && { warm }), 408 + cold, 409 + totalHits, 410 + totalMisses, 411 + hitRate, 412 + }; 413 + } 414 + 415 + /** 416 + * Clear all data from all tiers. 417 + * 418 + * @remarks 419 + * Use with extreme caution! This will delete all data in the entire storage system. 420 + * Cannot be undone. 421 + */ 422 + async clear(): Promise<void> { 423 + await Promise.all([ 424 + this.config.tiers.hot?.clear(), 425 + this.config.tiers.warm?.clear(), 426 + this.config.tiers.cold.clear(), 427 + ]); 428 + } 429 + 430 + /** 431 + * Clear a specific tier. 432 + * 433 + * @param tier - Which tier to clear 434 + * 435 + * @remarks 436 + * Useful for: 437 + * - Clearing hot tier to test warm/cold performance 438 + * - Clearing warm tier to force rebuilding from cold 439 + * - Clearing cold tier to start fresh (⚠️ loses source of truth!) 440 + */ 441 + async clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void> { 442 + switch (tier) { 443 + case 'hot': 444 + await this.config.tiers.hot?.clear(); 445 + break; 446 + case 'warm': 447 + await this.config.tiers.warm?.clear(); 448 + break; 449 + case 'cold': 450 + await this.config.tiers.cold.clear(); 451 + break; 452 + } 453 + } 454 + 455 + /** 456 + * Export metadata snapshot for backup or migration. 457 + * 458 + * @returns Snapshot containing all keys, metadata, and statistics 459 + * 460 + * @remarks 461 + * The snapshot includes metadata but not the actual data (data remains in tiers). 462 + * Useful for: 463 + * - Backup and restore 464 + * - Migration between storage systems 465 + * - Auditing and compliance 466 + */ 467 + async export(): Promise<StorageSnapshot> { 468 + const keys: string[] = []; 469 + const metadata: Record<string, StorageMetadata> = {}; 470 + 471 + // Export from cold tier (source of truth) 472 + for await (const key of this.config.tiers.cold.listKeys()) { 473 + keys.push(key); 474 + const meta = await this.config.tiers.cold.getMetadata(key); 475 + if (meta) { 476 + metadata[key] = meta; 477 + } 478 + } 479 + 480 + const stats = await this.getStats(); 481 + 482 + return { 483 + version: 1, 484 + exportedAt: new Date(), 485 + keys, 486 + metadata, 487 + stats, 488 + }; 489 + } 490 + 491 + /** 492 + * Import metadata snapshot. 493 + * 494 + * @param snapshot - Snapshot to import 495 + * 496 + * @remarks 497 + * Validates version compatibility before importing. 498 + * Only imports metadata - assumes data already exists in cold tier. 499 + */ 500 + async import(snapshot: StorageSnapshot): Promise<void> { 501 + if (snapshot.version !== 1) { 502 + throw new Error(`Unsupported snapshot version: ${snapshot.version}`); 503 + } 504 + 505 + // Import metadata into all configured tiers 506 + for (const key of snapshot.keys) { 507 + const metadata = snapshot.metadata[key]; 508 + if (!metadata) continue; 509 + 510 + if (this.config.tiers.hot) { 511 + await this.config.tiers.hot.setMetadata(key, metadata); 512 + } 513 + 514 + if (this.config.tiers.warm) { 515 + await this.config.tiers.warm.setMetadata(key, metadata); 516 + } 517 + 518 + await this.config.tiers.cold.setMetadata(key, metadata); 519 + } 520 + } 521 + 522 + /** 523 + * Bootstrap hot tier from warm tier. 524 + * 525 + * @param limit - Optional limit on number of items to load 526 + * @returns Number of items loaded 527 + * 528 + * @remarks 529 + * Loads the most frequently accessed items from warm into hot. 530 + * Useful for warming up the cache after a restart. 531 + * Items are sorted by: accessCount * lastAccessed timestamp (higher is better). 532 + */ 533 + async bootstrapHot(limit?: number): Promise<number> { 534 + if (!this.config.tiers.hot || !this.config.tiers.warm) { 535 + return 0; 536 + } 537 + 538 + let loaded = 0; 539 + const keyMetadata: Array<[string, StorageMetadata]> = []; 540 + 541 + // Load metadata for all keys 542 + for await (const key of this.config.tiers.warm.listKeys()) { 543 + const metadata = await this.config.tiers.warm.getMetadata(key); 544 + if (metadata) { 545 + keyMetadata.push([key, metadata]); 546 + } 547 + } 548 + 549 + // Sort by access count * recency (simple scoring) 550 + keyMetadata.sort((a, b) => { 551 + const scoreA = a[1].accessCount * a[1].lastAccessed.getTime(); 552 + const scoreB = b[1].accessCount * b[1].lastAccessed.getTime(); 553 + return scoreB - scoreA; 554 + }); 555 + 556 + // Load top N keys into hot tier 557 + const keysToLoad = limit ? keyMetadata.slice(0, limit) : keyMetadata; 558 + 559 + for (const [key, metadata] of keysToLoad) { 560 + const data = await this.config.tiers.warm.get(key); 561 + if (data) { 562 + await this.config.tiers.hot.set(key, data, metadata); 563 + loaded++; 564 + } 565 + } 566 + 567 + return loaded; 568 + } 569 + 570 + /** 571 + * Bootstrap warm tier from cold tier. 572 + * 573 + * @param options - Optional limit and date filter 574 + * @returns Number of items loaded 575 + * 576 + * @remarks 577 + * Loads recent items from cold into warm. 578 + * Useful for: 579 + * - Initial cache population 580 + * - Recovering from warm tier failure 581 + * - Migrating to a new warm tier implementation 582 + */ 583 + async bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number> { 584 + if (!this.config.tiers.warm) { 585 + return 0; 586 + } 587 + 588 + let loaded = 0; 589 + 590 + for await (const key of this.config.tiers.cold.listKeys()) { 591 + const metadata = await this.config.tiers.cold.getMetadata(key); 592 + if (!metadata) continue; 593 + 594 + // Skip if too old 595 + if (options?.sinceDate && metadata.lastAccessed < options.sinceDate) { 596 + continue; 597 + } 598 + 599 + const data = await this.config.tiers.cold.get(key); 600 + if (data) { 601 + await this.config.tiers.warm.set(key, data, metadata); 602 + loaded++; 603 + 604 + if (options?.limit && loaded >= options.limit) { 605 + break; 606 + } 607 + } 608 + } 609 + 610 + return loaded; 611 + } 612 + 613 + /** 614 + * Check if data has expired based on TTL. 615 + */ 616 + private isExpired(metadata: StorageMetadata): boolean { 617 + if (!metadata.ttl) return false; 618 + return Date.now() > metadata.ttl.getTime(); 619 + } 620 + 621 + /** 622 + * Update access statistics for a key. 623 + */ 624 + private async updateAccessStats(key: string, tier: 'hot' | 'warm' | 'cold'): Promise<void> { 625 + const tierObj = 626 + tier === 'hot' 627 + ? this.config.tiers.hot 628 + : tier === 'warm' 629 + ? this.config.tiers.warm 630 + : this.config.tiers.cold; 631 + 632 + if (!tierObj) return; 633 + 634 + const metadata = await tierObj.getMetadata(key); 635 + if (metadata) { 636 + metadata.lastAccessed = new Date(); 637 + metadata.accessCount++; 638 + await tierObj.setMetadata(key, metadata); 639 + } 640 + } 641 + 642 + /** 643 + * Create metadata for new data. 644 + */ 645 + private createMetadata(key: string, data: Uint8Array, options?: SetOptions): StorageMetadata { 646 + const now = new Date(); 647 + const ttl = options?.ttl ?? this.config.defaultTTL; 648 + 649 + const metadata: StorageMetadata = { 650 + key, 651 + size: data.byteLength, 652 + createdAt: now, 653 + lastAccessed: now, 654 + accessCount: 0, 655 + compressed: this.config.compression ?? false, 656 + checksum: calculateChecksum(data), 657 + }; 658 + 659 + if (ttl) { 660 + metadata.ttl = new Date(now.getTime() + ttl); 661 + } 662 + 663 + if (options?.metadata) { 664 + metadata.customMetadata = options.metadata; 665 + } 666 + 667 + return metadata; 668 + } 669 + 670 + /** 671 + * Deserialize data, handling compression automatically. 672 + */ 673 + private async deserializeData(data: Uint8Array): Promise<unknown> { 674 + // Decompress if needed (check for gzip magic bytes) 675 + const finalData = 676 + this.config.compression && data[0] === 0x1f && data[1] === 0x8b 677 + ? await decompress(data) 678 + : data; 679 + 680 + return this.deserialize(finalData); 681 + } 682 + }
+35
src/index.ts
··· 1 + /** 2 + * Tiered Storage Library 3 + * 4 + * A lightweight, pluggable tiered storage library that orchestrates caching across 5 + * hot (memory), warm (disk/database), and cold (S3/object storage) tiers. 6 + * 7 + * @packageDocumentation 8 + */ 9 + 10 + // Main class 11 + export { TieredStorage } from './TieredStorage.js'; 12 + 13 + // Built-in tier implementations 14 + export { MemoryStorageTier, type MemoryStorageTierConfig } from './tiers/MemoryStorageTier.js'; 15 + export { DiskStorageTier, type DiskStorageTierConfig, type EvictionPolicy } from './tiers/DiskStorageTier.js'; 16 + export { S3StorageTier, type S3StorageTierConfig } from './tiers/S3StorageTier.js'; 17 + 18 + // Types 19 + export type { 20 + StorageTier, 21 + StorageMetadata, 22 + TierStats, 23 + AllTierStats, 24 + TieredStorageConfig, 25 + SetOptions, 26 + StorageResult, 27 + SetResult, 28 + StorageSnapshot, 29 + } from './types/index.js'; 30 + 31 + // Utilities 32 + export { compress, decompress, isGzipped } from './utils/compression.js'; 33 + export { defaultSerialize, defaultDeserialize } from './utils/serialization.js'; 34 + export { calculateChecksum, verifyChecksum } from './utils/checksum.js'; 35 + export { encodeKey, decodeKey } from './utils/path-encoding.js';
+367
src/tiers/DiskStorageTier.ts
··· 1 + import { readFile, writeFile, unlink, readdir, stat, mkdir, rm, rename } from 'node:fs/promises'; 2 + import { existsSync } from 'node:fs'; 3 + import { join, dirname } from 'node:path'; 4 + import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js'; 5 + import { encodeKey } from '../utils/path-encoding.js'; 6 + 7 + /** 8 + * Eviction policy for disk tier when size limit is reached. 9 + */ 10 + export type EvictionPolicy = 'lru' | 'fifo' | 'size'; 11 + 12 + /** 13 + * Configuration for DiskStorageTier. 14 + */ 15 + export interface DiskStorageTierConfig { 16 + /** 17 + * Directory path where files will be stored. 18 + * 19 + * @remarks 20 + * Created automatically if it doesn't exist. 21 + * Files are stored as: `{directory}/{encoded-key}` 22 + * Metadata is stored as: `{directory}/{encoded-key}.meta` 23 + */ 24 + directory: string; 25 + 26 + /** 27 + * Optional maximum size in bytes. 28 + * 29 + * @remarks 30 + * When this limit is reached, files are evicted according to the eviction policy. 31 + * If not set, no size limit is enforced (grows unbounded). 32 + */ 33 + maxSizeBytes?: number; 34 + 35 + /** 36 + * Eviction policy when maxSizeBytes is reached. 37 + * 38 + * @defaultValue 'lru' 39 + * 40 + * @remarks 41 + * - 'lru': Evict least-recently-accessed files (based on metadata.lastAccessed) 42 + * - 'fifo': Evict oldest files (based on metadata.createdAt) 43 + * - 'size': Evict largest files first 44 + */ 45 + evictionPolicy?: EvictionPolicy; 46 + } 47 + 48 + /** 49 + * Filesystem-based storage tier. 50 + * 51 + * @remarks 52 + * - Stores data files and `.meta` JSON files side-by-side 53 + * - Keys are encoded to be filesystem-safe 54 + * - Human-readable file structure for debugging 55 + * - Optional size-based eviction with configurable policy 56 + * - Zero external dependencies (uses Node.js fs APIs) 57 + * 58 + * File structure: 59 + * ``` 60 + * cache/ 61 + * ├── user%3A123 # Data file (encoded key) 62 + * ├── user%3A123.meta # Metadata JSON 63 + * ├── site%3Aabc%2Findex.html 64 + * └── site%3Aabc%2Findex.html.meta 65 + * ``` 66 + * 67 + * @example 68 + * ```typescript 69 + * const tier = new DiskStorageTier({ 70 + * directory: './cache', 71 + * maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB 72 + * evictionPolicy: 'lru', 73 + * }); 74 + * 75 + * await tier.set('key', data, metadata); 76 + * const retrieved = await tier.get('key'); 77 + * ``` 78 + */ 79 + export class DiskStorageTier implements StorageTier { 80 + private metadataIndex = new Map< 81 + string, 82 + { size: number; createdAt: Date; lastAccessed: Date } 83 + >(); 84 + private currentSize = 0; 85 + 86 + constructor(private config: DiskStorageTierConfig) { 87 + if (!config.directory) { 88 + throw new Error('directory is required'); 89 + } 90 + if (config.maxSizeBytes !== undefined && config.maxSizeBytes <= 0) { 91 + throw new Error('maxSizeBytes must be positive'); 92 + } 93 + 94 + void this.ensureDirectory(); 95 + void this.rebuildIndex(); 96 + } 97 + 98 + private async rebuildIndex(): Promise<void> { 99 + if (!existsSync(this.config.directory)) { 100 + return; 101 + } 102 + 103 + const files = await readdir(this.config.directory); 104 + 105 + for (const file of files) { 106 + if (file.endsWith('.meta')) { 107 + continue; 108 + } 109 + 110 + try { 111 + const metaPath = join(this.config.directory, `${file}.meta`); 112 + const metaContent = await readFile(metaPath, 'utf-8'); 113 + const metadata = JSON.parse(metaContent) as StorageMetadata; 114 + const filePath = join(this.config.directory, file); 115 + const fileStats = await stat(filePath); 116 + 117 + this.metadataIndex.set(metadata.key, { 118 + size: fileStats.size, 119 + createdAt: new Date(metadata.createdAt), 120 + lastAccessed: new Date(metadata.lastAccessed), 121 + }); 122 + 123 + this.currentSize += fileStats.size; 124 + } catch { 125 + continue; 126 + } 127 + } 128 + } 129 + 130 + async get(key: string): Promise<Uint8Array | null> { 131 + const filePath = this.getFilePath(key); 132 + 133 + try { 134 + const data = await readFile(filePath); 135 + 136 + const metadata = await this.getMetadata(key); 137 + if (metadata) { 138 + metadata.lastAccessed = new Date(); 139 + metadata.accessCount++; 140 + await this.setMetadata(key, metadata); 141 + 142 + const entry = this.metadataIndex.get(key); 143 + if (entry) { 144 + entry.lastAccessed = metadata.lastAccessed; 145 + } 146 + } 147 + 148 + return new Uint8Array(data); 149 + } catch (error) { 150 + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { 151 + return null; 152 + } 153 + throw error; 154 + } 155 + } 156 + 157 + async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> { 158 + const filePath = this.getFilePath(key); 159 + const metaPath = this.getMetaPath(key); 160 + 161 + const dir = dirname(filePath); 162 + if (!existsSync(dir)) { 163 + await mkdir(dir, { recursive: true }); 164 + } 165 + 166 + const existingEntry = this.metadataIndex.get(key); 167 + if (existingEntry) { 168 + this.currentSize -= existingEntry.size; 169 + } 170 + 171 + if (this.config.maxSizeBytes) { 172 + await this.evictIfNeeded(data.byteLength); 173 + } 174 + 175 + const tempMetaPath = `${metaPath}.tmp`; 176 + await writeFile(tempMetaPath, JSON.stringify(metadata, null, 2)); 177 + await writeFile(filePath, data); 178 + await rename(tempMetaPath, metaPath); 179 + 180 + this.metadataIndex.set(key, { 181 + size: data.byteLength, 182 + createdAt: metadata.createdAt, 183 + lastAccessed: metadata.lastAccessed, 184 + }); 185 + this.currentSize += data.byteLength; 186 + } 187 + 188 + async delete(key: string): Promise<void> { 189 + const filePath = this.getFilePath(key); 190 + const metaPath = this.getMetaPath(key); 191 + 192 + const entry = this.metadataIndex.get(key); 193 + if (entry) { 194 + this.currentSize -= entry.size; 195 + this.metadataIndex.delete(key); 196 + } 197 + 198 + await Promise.all([ 199 + unlink(filePath).catch(() => {}), 200 + unlink(metaPath).catch(() => {}), 201 + ]); 202 + } 203 + 204 + async exists(key: string): Promise<boolean> { 205 + const filePath = this.getFilePath(key); 206 + return existsSync(filePath); 207 + } 208 + 209 + async *listKeys(prefix?: string): AsyncIterableIterator<string> { 210 + if (!existsSync(this.config.directory)) { 211 + return; 212 + } 213 + 214 + const files = await readdir(this.config.directory); 215 + 216 + for (const file of files) { 217 + // Skip metadata files 218 + if (file.endsWith('.meta')) { 219 + continue; 220 + } 221 + 222 + // The file name is the encoded key 223 + // We need to read metadata to get the original key for prefix matching 224 + const metaPath = join(this.config.directory, `${file}.meta`); 225 + try { 226 + const metaContent = await readFile(metaPath, 'utf-8'); 227 + const metadata = JSON.parse(metaContent) as StorageMetadata; 228 + const originalKey = metadata.key; 229 + 230 + if (!prefix || originalKey.startsWith(prefix)) { 231 + yield originalKey; 232 + } 233 + } catch { 234 + // If metadata is missing or invalid, skip this file 235 + continue; 236 + } 237 + } 238 + } 239 + 240 + async deleteMany(keys: string[]): Promise<void> { 241 + await Promise.all(keys.map((key) => this.delete(key))); 242 + } 243 + 244 + async getMetadata(key: string): Promise<StorageMetadata | null> { 245 + const metaPath = this.getMetaPath(key); 246 + 247 + try { 248 + const content = await readFile(metaPath, 'utf-8'); 249 + const metadata = JSON.parse(content) as StorageMetadata; 250 + 251 + // Convert date strings back to Date objects 252 + metadata.createdAt = new Date(metadata.createdAt); 253 + metadata.lastAccessed = new Date(metadata.lastAccessed); 254 + if (metadata.ttl) { 255 + metadata.ttl = new Date(metadata.ttl); 256 + } 257 + 258 + return metadata; 259 + } catch (error) { 260 + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { 261 + return null; 262 + } 263 + throw error; 264 + } 265 + } 266 + 267 + async setMetadata(key: string, metadata: StorageMetadata): Promise<void> { 268 + const metaPath = this.getMetaPath(key); 269 + 270 + // Ensure parent directory exists 271 + const dir = dirname(metaPath); 272 + if (!existsSync(dir)) { 273 + await mkdir(dir, { recursive: true }); 274 + } 275 + 276 + await writeFile(metaPath, JSON.stringify(metadata, null, 2)); 277 + } 278 + 279 + async getStats(): Promise<TierStats> { 280 + let bytes = 0; 281 + let items = 0; 282 + 283 + if (!existsSync(this.config.directory)) { 284 + return { bytes: 0, items: 0 }; 285 + } 286 + 287 + const files = await readdir(this.config.directory); 288 + 289 + for (const file of files) { 290 + if (file.endsWith('.meta')) { 291 + continue; 292 + } 293 + 294 + const filePath = join(this.config.directory, file); 295 + const stats = await stat(filePath); 296 + bytes += stats.size; 297 + items++; 298 + } 299 + 300 + return { bytes, items }; 301 + } 302 + 303 + async clear(): Promise<void> { 304 + if (existsSync(this.config.directory)) { 305 + await rm(this.config.directory, { recursive: true, force: true }); 306 + await this.ensureDirectory(); 307 + this.metadataIndex.clear(); 308 + this.currentSize = 0; 309 + } 310 + } 311 + 312 + /** 313 + * Get the filesystem path for a key's data file. 314 + */ 315 + private getFilePath(key: string): string { 316 + const encoded = encodeKey(key); 317 + return join(this.config.directory, encoded); 318 + } 319 + 320 + /** 321 + * Get the filesystem path for a key's metadata file. 322 + */ 323 + private getMetaPath(key: string): string { 324 + return `${this.getFilePath(key)}.meta`; 325 + } 326 + 327 + private async ensureDirectory(): Promise<void> { 328 + await mkdir(this.config.directory, { recursive: true }).catch(() => {}); 329 + } 330 + 331 + private async evictIfNeeded(incomingSize: number): Promise<void> { 332 + if (!this.config.maxSizeBytes) { 333 + return; 334 + } 335 + 336 + if (this.currentSize + incomingSize <= this.config.maxSizeBytes) { 337 + return; 338 + } 339 + 340 + const entries = Array.from(this.metadataIndex.entries()).map(([key, info]) => ({ 341 + key, 342 + ...info, 343 + })); 344 + 345 + const policy = this.config.evictionPolicy ?? 'lru'; 346 + entries.sort((a, b) => { 347 + switch (policy) { 348 + case 'lru': 349 + return a.lastAccessed.getTime() - b.lastAccessed.getTime(); 350 + case 'fifo': 351 + return a.createdAt.getTime() - b.createdAt.getTime(); 352 + case 'size': 353 + return b.size - a.size; 354 + default: 355 + return 0; 356 + } 357 + }); 358 + 359 + for (const entry of entries) { 360 + if (this.currentSize + incomingSize <= this.config.maxSizeBytes) { 361 + break; 362 + } 363 + 364 + await this.delete(entry.key); 365 + } 366 + } 367 + }
+195
src/tiers/MemoryStorageTier.ts
··· 1 + import { lru } from 'tiny-lru'; 2 + import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js'; 3 + 4 + interface CacheEntry { 5 + data: Uint8Array; 6 + metadata: StorageMetadata; 7 + size: number; 8 + } 9 + 10 + /** 11 + * Configuration for MemoryStorageTier. 12 + */ 13 + export interface MemoryStorageTierConfig { 14 + /** 15 + * Maximum total size in bytes. 16 + * 17 + * @remarks 18 + * When this limit is reached, least-recently-used entries are evicted. 19 + */ 20 + maxSizeBytes: number; 21 + 22 + /** 23 + * Maximum number of items. 24 + * 25 + * @remarks 26 + * When this limit is reached, least-recently-used entries are evicted. 27 + * Useful for limiting memory usage when items have variable sizes. 28 + */ 29 + maxItems?: number; 30 + } 31 + 32 + /** 33 + * In-memory storage tier using TinyLRU for LRU eviction. 34 + * 35 + * @remarks 36 + * - Uses the battle-tested TinyLRU library for efficient LRU caching 37 + * - Automatically evicts least-recently-used entries when limits are reached 38 + * - Not distributed - single process only 39 + * - Data is lost on process restart (use warm/cold tiers for persistence) 40 + * - Implements both size-based and count-based eviction 41 + * 42 + * @example 43 + * ```typescript 44 + * const tier = new MemoryStorageTier({ 45 + * maxSizeBytes: 100 * 1024 * 1024, // 100MB 46 + * maxItems: 1000, 47 + * }); 48 + * 49 + * await tier.set('key', data, metadata); 50 + * const retrieved = await tier.get('key'); 51 + * ``` 52 + */ 53 + export class MemoryStorageTier implements StorageTier { 54 + private cache: ReturnType<typeof lru<CacheEntry>>; 55 + private currentSize = 0; 56 + private stats = { 57 + hits: 0, 58 + misses: 0, 59 + evictions: 0, 60 + }; 61 + 62 + constructor(private config: MemoryStorageTierConfig) { 63 + if (config.maxSizeBytes <= 0) { 64 + throw new Error('maxSizeBytes must be positive'); 65 + } 66 + if (config.maxItems !== undefined && config.maxItems <= 0) { 67 + throw new Error('maxItems must be positive'); 68 + } 69 + 70 + // Initialize TinyLRU with max items (we'll handle size limits separately) 71 + const maxItems = config.maxItems ?? 10000; // Default to 10k items if not specified 72 + this.cache = lru<CacheEntry>(maxItems); 73 + } 74 + 75 + async get(key: string): Promise<Uint8Array | null> { 76 + const entry = this.cache.get(key); 77 + 78 + if (!entry) { 79 + this.stats.misses++; 80 + return null; 81 + } 82 + 83 + this.stats.hits++; 84 + return entry.data; 85 + } 86 + 87 + async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> { 88 + const size = data.byteLength; 89 + 90 + // Check existing entry for size accounting 91 + const existing = this.cache.get(key); 92 + if (existing) { 93 + this.currentSize -= existing.size; 94 + } 95 + 96 + // Evict entries until we have space for the new entry 97 + await this.evictIfNeeded(size); 98 + 99 + // Add new entry 100 + const entry: CacheEntry = { data, metadata, size }; 101 + this.cache.set(key, entry); 102 + this.currentSize += size; 103 + } 104 + 105 + async delete(key: string): Promise<void> { 106 + const entry = this.cache.get(key); 107 + if (entry) { 108 + this.cache.delete(key); 109 + this.currentSize -= entry.size; 110 + } 111 + } 112 + 113 + async exists(key: string): Promise<boolean> { 114 + return this.cache.has(key); 115 + } 116 + 117 + async *listKeys(prefix?: string): AsyncIterableIterator<string> { 118 + // TinyLRU doesn't expose keys(), so we need to track them separately 119 + // For now, we'll use the cache's internal structure 120 + const keys = this.cache.keys(); 121 + for (const key of keys) { 122 + if (!prefix || key.startsWith(prefix)) { 123 + yield key; 124 + } 125 + } 126 + } 127 + 128 + async deleteMany(keys: string[]): Promise<void> { 129 + for (const key of keys) { 130 + await this.delete(key); 131 + } 132 + } 133 + 134 + async getMetadata(key: string): Promise<StorageMetadata | null> { 135 + const entry = this.cache.get(key); 136 + return entry ? entry.metadata : null; 137 + } 138 + 139 + async setMetadata(key: string, metadata: StorageMetadata): Promise<void> { 140 + const entry = this.cache.get(key); 141 + if (entry) { 142 + // Update metadata in place 143 + entry.metadata = metadata; 144 + // Re-set to mark as recently used 145 + this.cache.set(key, entry); 146 + } 147 + } 148 + 149 + async getStats(): Promise<TierStats> { 150 + return { 151 + bytes: this.currentSize, 152 + items: this.cache.size, 153 + hits: this.stats.hits, 154 + misses: this.stats.misses, 155 + evictions: this.stats.evictions, 156 + }; 157 + } 158 + 159 + async clear(): Promise<void> { 160 + this.cache.clear(); 161 + this.currentSize = 0; 162 + } 163 + 164 + /** 165 + * Evict least-recently-used entries until there's space for new data. 166 + * 167 + * @param incomingSize - Size of data being added 168 + * 169 + * @remarks 170 + * TinyLRU handles count-based eviction automatically. 171 + * This method handles size-based eviction by using TinyLRU's built-in evict() method, 172 + * which properly removes the LRU item without updating access order. 173 + */ 174 + private async evictIfNeeded(incomingSize: number): Promise<void> { 175 + // Keep evicting until we have enough space 176 + while (this.currentSize + incomingSize > this.config.maxSizeBytes && this.cache.size > 0) { 177 + // Get the LRU key (first in the list) without accessing it 178 + const keys = this.cache.keys(); 179 + if (keys.length === 0) break; 180 + 181 + const lruKey = keys[0]; 182 + if (!lruKey) break; 183 + 184 + // Access the entry directly from internal items without triggering LRU update 185 + // TinyLRU exposes items as a public property for this purpose 186 + const entry = (this.cache as any).items[lruKey] as CacheEntry | undefined; 187 + if (!entry) break; 188 + 189 + // Use TinyLRU's built-in evict() which properly removes the LRU item 190 + this.cache.evict(); 191 + this.currentSize -= entry.size; 192 + this.stats.evictions++; 193 + } 194 + } 195 + }
+567
src/tiers/S3StorageTier.ts
··· 1 + import { 2 + S3Client, 3 + PutObjectCommand, 4 + GetObjectCommand, 5 + DeleteObjectCommand, 6 + HeadObjectCommand, 7 + ListObjectsV2Command, 8 + DeleteObjectsCommand, 9 + CopyObjectCommand, 10 + type S3ClientConfig, 11 + } from '@aws-sdk/client-s3'; 12 + import type { Readable } from 'node:stream'; 13 + import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js'; 14 + 15 + /** 16 + * Configuration for S3StorageTier. 17 + */ 18 + export interface S3StorageTierConfig { 19 + /** 20 + * S3 bucket name. 21 + */ 22 + bucket: string; 23 + 24 + /** 25 + * AWS region. 26 + */ 27 + region: string; 28 + 29 + /** 30 + * Optional S3-compatible endpoint (for R2, Minio, etc.). 31 + * 32 + * @example 'https://s3.us-east-1.amazonaws.com' 33 + * @example 'https://account-id.r2.cloudflarestorage.com' 34 + */ 35 + endpoint?: string; 36 + 37 + /** 38 + * Optional AWS credentials. 39 + * 40 + * @remarks 41 + * If not provided, uses the default AWS credential chain 42 + * (environment variables, ~/.aws/credentials, IAM roles, etc.) 43 + */ 44 + credentials?: { 45 + accessKeyId: string; 46 + secretAccessKey: string; 47 + }; 48 + 49 + /** 50 + * Optional key prefix for namespacing. 51 + * 52 + * @remarks 53 + * All keys will be prefixed with this value. 54 + * Useful for multi-tenant scenarios or organizing data. 55 + * 56 + * @example 'tiered-storage/' 57 + */ 58 + prefix?: string; 59 + 60 + /** 61 + * Force path-style addressing for S3-compatible services. 62 + * 63 + * @defaultValue true 64 + * 65 + * @remarks 66 + * Most S3-compatible services (MinIO, R2, etc.) require path-style URLs. 67 + * AWS S3 uses virtual-hosted-style by default, but path-style also works. 68 + * 69 + * - true: `https://endpoint.com/bucket/key` (path-style) 70 + * - false: `https://bucket.endpoint.com/key` (virtual-hosted-style) 71 + */ 72 + forcePathStyle?: boolean; 73 + 74 + /** 75 + * Optional separate bucket for storing metadata. 76 + * 77 + * @remarks 78 + * **RECOMMENDED for production use!** 79 + * 80 + * By default, metadata is stored in S3 object metadata fields. However, updating 81 + * metadata requires copying the entire object, which is slow and expensive for large files. 82 + * 83 + * When `metadataBucket` is specified, metadata is stored as separate JSON objects 84 + * in this bucket. This allows fast, cheap metadata updates without copying data. 85 + * 86 + * **Benefits:** 87 + * - Fast metadata updates (no object copying) 88 + * - Much cheaper for large objects 89 + * - No impact on data object performance 90 + * 91 + * **Trade-offs:** 92 + * - Requires managing two buckets 93 + * - Metadata and data could become out of sync if not handled carefully 94 + * - Additional S3 API calls for metadata operations 95 + * 96 + * @example 97 + * ```typescript 98 + * const tier = new S3StorageTier({ 99 + * bucket: 'my-data-bucket', 100 + * metadataBucket: 'my-metadata-bucket', // Separate bucket for metadata 101 + * region: 'us-east-1', 102 + * }); 103 + * ``` 104 + */ 105 + metadataBucket?: string; 106 + } 107 + 108 + /** 109 + * AWS S3 (or compatible) storage tier. 110 + * 111 + * @remarks 112 + * - Supports AWS S3, Cloudflare R2, MinIO, and other S3-compatible services 113 + * - Uses object metadata for StorageMetadata 114 + * - Requires `@aws-sdk/client-s3` peer dependency 115 + * - Typically used as the cold tier (source of truth) 116 + * 117 + * **Metadata Storage:** 118 + * Metadata is stored in S3 object metadata fields: 119 + * - Custom metadata fields are prefixed with `x-amz-meta-` 120 + * - Built-in fields use standard S3 headers 121 + * 122 + * @example 123 + * ```typescript 124 + * const tier = new S3StorageTier({ 125 + * bucket: 'my-bucket', 126 + * region: 'us-east-1', 127 + * credentials: { 128 + * accessKeyId: process.env.AWS_ACCESS_KEY_ID!, 129 + * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, 130 + * }, 131 + * prefix: 'cache/', 132 + * }); 133 + * ``` 134 + * 135 + * @example Cloudflare R2 136 + * ```typescript 137 + * const tier = new S3StorageTier({ 138 + * bucket: 'my-bucket', 139 + * region: 'auto', 140 + * endpoint: 'https://account-id.r2.cloudflarestorage.com', 141 + * credentials: { 142 + * accessKeyId: process.env.R2_ACCESS_KEY_ID!, 143 + * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, 144 + * }, 145 + * }); 146 + * ``` 147 + */ 148 + export class S3StorageTier implements StorageTier { 149 + private client: S3Client; 150 + private prefix: string; 151 + private metadataBucket?: string; 152 + 153 + constructor(private config: S3StorageTierConfig) { 154 + const clientConfig: S3ClientConfig = { 155 + region: config.region, 156 + // Most S3-compatible services need path-style URLs 157 + forcePathStyle: config.forcePathStyle ?? true, 158 + ...(config.endpoint && { endpoint: config.endpoint }), 159 + ...(config.credentials && { credentials: config.credentials }), 160 + }; 161 + 162 + this.client = new S3Client(clientConfig); 163 + this.prefix = config.prefix ?? ''; 164 + if (config.metadataBucket) { 165 + this.metadataBucket = config.metadataBucket; 166 + } 167 + } 168 + 169 + async get(key: string): Promise<Uint8Array | null> { 170 + try { 171 + const command = new GetObjectCommand({ 172 + Bucket: this.config.bucket, 173 + Key: this.getS3Key(key), 174 + }); 175 + 176 + const response = await this.client.send(command); 177 + 178 + if (!response.Body) { 179 + return null; 180 + } 181 + 182 + return await this.streamToUint8Array(response.Body as Readable); 183 + } catch (error) { 184 + if (this.isNoSuchKeyError(error)) { 185 + return null; 186 + } 187 + throw error; 188 + } 189 + } 190 + 191 + private async streamToUint8Array(stream: Readable): Promise<Uint8Array> { 192 + const chunks: Uint8Array[] = []; 193 + 194 + for await (const chunk of stream) { 195 + if (Buffer.isBuffer(chunk)) { 196 + chunks.push(new Uint8Array(chunk)); 197 + } else if (chunk instanceof Uint8Array) { 198 + chunks.push(chunk); 199 + } else { 200 + throw new Error('Unexpected chunk type in S3 stream'); 201 + } 202 + } 203 + 204 + const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0); 205 + const result = new Uint8Array(totalLength); 206 + let offset = 0; 207 + for (const chunk of chunks) { 208 + result.set(chunk, offset); 209 + offset += chunk.length; 210 + } 211 + 212 + return result; 213 + } 214 + 215 + private isNoSuchKeyError(error: unknown): boolean { 216 + return ( 217 + typeof error === 'object' && 218 + error !== null && 219 + 'name' in error && 220 + (error.name === 'NoSuchKey' || error.name === 'NotFound') 221 + ); 222 + } 223 + 224 + async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> { 225 + const s3Key = this.getS3Key(key); 226 + 227 + if (this.metadataBucket) { 228 + const dataCommand = new PutObjectCommand({ 229 + Bucket: this.config.bucket, 230 + Key: s3Key, 231 + Body: data, 232 + ContentLength: data.byteLength, 233 + }); 234 + 235 + const metadataJson = JSON.stringify(metadata); 236 + const metadataBuffer = new TextEncoder().encode(metadataJson); 237 + const metadataCommand = new PutObjectCommand({ 238 + Bucket: this.metadataBucket, 239 + Key: s3Key + '.meta', 240 + Body: metadataBuffer, 241 + ContentType: 'application/json', 242 + }); 243 + 244 + await Promise.all([ 245 + this.client.send(dataCommand), 246 + this.client.send(metadataCommand), 247 + ]); 248 + } else { 249 + const command = new PutObjectCommand({ 250 + Bucket: this.config.bucket, 251 + Key: s3Key, 252 + Body: data, 253 + ContentLength: data.byteLength, 254 + Metadata: this.metadataToS3(metadata), 255 + }); 256 + 257 + await this.client.send(command); 258 + } 259 + } 260 + 261 + async delete(key: string): Promise<void> { 262 + const s3Key = this.getS3Key(key); 263 + 264 + try { 265 + const dataCommand = new DeleteObjectCommand({ 266 + Bucket: this.config.bucket, 267 + Key: s3Key, 268 + }); 269 + 270 + if (this.metadataBucket) { 271 + const metadataCommand = new DeleteObjectCommand({ 272 + Bucket: this.metadataBucket, 273 + Key: s3Key + '.meta', 274 + }); 275 + 276 + await Promise.all([ 277 + this.client.send(dataCommand), 278 + this.client.send(metadataCommand).catch((error) => { 279 + if (!this.isNoSuchKeyError(error)) throw error; 280 + }), 281 + ]); 282 + } else { 283 + await this.client.send(dataCommand); 284 + } 285 + } catch (error) { 286 + if (!this.isNoSuchKeyError(error)) { 287 + throw error; 288 + } 289 + } 290 + } 291 + 292 + async exists(key: string): Promise<boolean> { 293 + try { 294 + const command = new HeadObjectCommand({ 295 + Bucket: this.config.bucket, 296 + Key: this.getS3Key(key), 297 + }); 298 + 299 + await this.client.send(command); 300 + return true; 301 + } catch (error) { 302 + if (this.isNoSuchKeyError(error)) { 303 + return false; 304 + } 305 + throw error; 306 + } 307 + } 308 + 309 + async *listKeys(prefix?: string): AsyncIterableIterator<string> { 310 + const s3Prefix = prefix ? this.getS3Key(prefix) : this.prefix; 311 + let continuationToken: string | undefined; 312 + 313 + do { 314 + const command = new ListObjectsV2Command({ 315 + Bucket: this.config.bucket, 316 + Prefix: s3Prefix, 317 + ContinuationToken: continuationToken, 318 + }); 319 + 320 + const response = await this.client.send(command); 321 + 322 + if (response.Contents) { 323 + for (const object of response.Contents) { 324 + if (object.Key) { 325 + // Remove prefix to get original key 326 + const key = this.removePrefix(object.Key); 327 + yield key; 328 + } 329 + } 330 + } 331 + 332 + continuationToken = response.NextContinuationToken; 333 + } while (continuationToken); 334 + } 335 + 336 + async deleteMany(keys: string[]): Promise<void> { 337 + if (keys.length === 0) return; 338 + 339 + const batchSize = 1000; 340 + 341 + for (let i = 0; i < keys.length; i += batchSize) { 342 + const batch = keys.slice(i, i + batchSize); 343 + 344 + const dataCommand = new DeleteObjectsCommand({ 345 + Bucket: this.config.bucket, 346 + Delete: { 347 + Objects: batch.map((key) => ({ Key: this.getS3Key(key) })), 348 + }, 349 + }); 350 + 351 + if (this.metadataBucket) { 352 + const metadataCommand = new DeleteObjectsCommand({ 353 + Bucket: this.metadataBucket, 354 + Delete: { 355 + Objects: batch.map((key) => ({ Key: this.getS3Key(key) + '.meta' })), 356 + }, 357 + }); 358 + 359 + await Promise.all([ 360 + this.client.send(dataCommand), 361 + this.client.send(metadataCommand).catch(() => {}), 362 + ]); 363 + } else { 364 + await this.client.send(dataCommand); 365 + } 366 + } 367 + } 368 + 369 + async getMetadata(key: string): Promise<StorageMetadata | null> { 370 + if (this.metadataBucket) { 371 + try { 372 + const command = new GetObjectCommand({ 373 + Bucket: this.metadataBucket, 374 + Key: this.getS3Key(key) + '.meta', 375 + }); 376 + 377 + const response = await this.client.send(command); 378 + 379 + if (!response.Body) { 380 + return null; 381 + } 382 + 383 + const buffer = await this.streamToUint8Array(response.Body as Readable); 384 + const json = new TextDecoder().decode(buffer); 385 + const metadata = JSON.parse(json) as StorageMetadata; 386 + 387 + metadata.createdAt = new Date(metadata.createdAt); 388 + metadata.lastAccessed = new Date(metadata.lastAccessed); 389 + if (metadata.ttl) { 390 + metadata.ttl = new Date(metadata.ttl); 391 + } 392 + 393 + return metadata; 394 + } catch (error) { 395 + if (this.isNoSuchKeyError(error)) { 396 + return null; 397 + } 398 + throw error; 399 + } 400 + } 401 + 402 + try { 403 + const command = new HeadObjectCommand({ 404 + Bucket: this.config.bucket, 405 + Key: this.getS3Key(key), 406 + }); 407 + 408 + const response = await this.client.send(command); 409 + 410 + if (!response.Metadata) { 411 + return null; 412 + } 413 + 414 + return this.s3ToMetadata(response.Metadata); 415 + } catch (error) { 416 + if (this.isNoSuchKeyError(error)) { 417 + return null; 418 + } 419 + throw error; 420 + } 421 + } 422 + 423 + async setMetadata(key: string, metadata: StorageMetadata): Promise<void> { 424 + if (this.metadataBucket) { 425 + const metadataJson = JSON.stringify(metadata); 426 + const buffer = new TextEncoder().encode(metadataJson); 427 + 428 + const command = new PutObjectCommand({ 429 + Bucket: this.metadataBucket, 430 + Key: this.getS3Key(key) + '.meta', 431 + Body: buffer, 432 + ContentType: 'application/json', 433 + }); 434 + 435 + await this.client.send(command); 436 + return; 437 + } 438 + 439 + const s3Key = this.getS3Key(key); 440 + const command = new CopyObjectCommand({ 441 + Bucket: this.config.bucket, 442 + Key: s3Key, 443 + CopySource: `${this.config.bucket}/${s3Key}`, 444 + Metadata: this.metadataToS3(metadata), 445 + MetadataDirective: 'REPLACE', 446 + }); 447 + 448 + await this.client.send(command); 449 + } 450 + 451 + async getStats(): Promise<TierStats> { 452 + let bytes = 0; 453 + let items = 0; 454 + 455 + // List all objects and sum up sizes 456 + let continuationToken: string | undefined; 457 + 458 + do { 459 + const command = new ListObjectsV2Command({ 460 + Bucket: this.config.bucket, 461 + Prefix: this.prefix, 462 + ContinuationToken: continuationToken, 463 + }); 464 + 465 + const response = await this.client.send(command); 466 + 467 + if (response.Contents) { 468 + for (const object of response.Contents) { 469 + items++; 470 + bytes += object.Size ?? 0; 471 + } 472 + } 473 + 474 + continuationToken = response.NextContinuationToken; 475 + } while (continuationToken); 476 + 477 + return { bytes, items }; 478 + } 479 + 480 + async clear(): Promise<void> { 481 + // List and delete all objects with the prefix 482 + const keys: string[] = []; 483 + 484 + for await (const key of this.listKeys()) { 485 + keys.push(key); 486 + } 487 + 488 + await this.deleteMany(keys); 489 + } 490 + 491 + /** 492 + * Get the full S3 key including prefix. 493 + */ 494 + private getS3Key(key: string): string { 495 + return this.prefix + key; 496 + } 497 + 498 + /** 499 + * Remove the prefix from an S3 key to get the original key. 500 + */ 501 + private removePrefix(s3Key: string): string { 502 + if (this.prefix && s3Key.startsWith(this.prefix)) { 503 + return s3Key.slice(this.prefix.length); 504 + } 505 + return s3Key; 506 + } 507 + 508 + /** 509 + * Convert StorageMetadata to S3 metadata format. 510 + * 511 + * @remarks 512 + * S3 metadata keys must be lowercase and values must be strings. 513 + * We serialize complex values as JSON. 514 + */ 515 + private metadataToS3(metadata: StorageMetadata): Record<string, string> { 516 + return { 517 + key: metadata.key, 518 + size: metadata.size.toString(), 519 + createdat: metadata.createdAt.toISOString(), 520 + lastaccessed: metadata.lastAccessed.toISOString(), 521 + accesscount: metadata.accessCount.toString(), 522 + compressed: metadata.compressed.toString(), 523 + checksum: metadata.checksum, 524 + ...(metadata.ttl && { ttl: metadata.ttl.toISOString() }), 525 + ...(metadata.mimeType && { mimetype: metadata.mimeType }), 526 + ...(metadata.encoding && { encoding: metadata.encoding }), 527 + ...(metadata.customMetadata && { custom: JSON.stringify(metadata.customMetadata) }), 528 + }; 529 + } 530 + 531 + /** 532 + * Convert S3 metadata to StorageMetadata format. 533 + */ 534 + private s3ToMetadata(s3Metadata: Record<string, string>): StorageMetadata { 535 + const metadata: StorageMetadata = { 536 + key: s3Metadata.key ?? '', 537 + size: parseInt(s3Metadata.size ?? '0', 10), 538 + createdAt: new Date(s3Metadata.createdat ?? Date.now()), 539 + lastAccessed: new Date(s3Metadata.lastaccessed ?? Date.now()), 540 + accessCount: parseInt(s3Metadata.accesscount ?? '0', 10), 541 + compressed: s3Metadata.compressed === 'true', 542 + checksum: s3Metadata.checksum ?? '', 543 + }; 544 + 545 + if (s3Metadata.ttl) { 546 + metadata.ttl = new Date(s3Metadata.ttl); 547 + } 548 + 549 + if (s3Metadata.mimetype) { 550 + metadata.mimeType = s3Metadata.mimetype; 551 + } 552 + 553 + if (s3Metadata.encoding) { 554 + metadata.encoding = s3Metadata.encoding; 555 + } 556 + 557 + if (s3Metadata.custom) { 558 + try { 559 + metadata.customMetadata = JSON.parse(s3Metadata.custom); 560 + } catch { 561 + // Ignore invalid JSON 562 + } 563 + } 564 + 565 + return metadata; 566 + } 567 + }
+407
src/types/index.ts
··· 1 + /** 2 + * Metadata associated with stored data in a tier. 3 + * 4 + * @remarks 5 + * This metadata is stored alongside the actual data and is used for: 6 + * - TTL management and expiration 7 + * - Access tracking for LRU/eviction policies 8 + * - Data integrity verification via checksum 9 + * - Content type information for HTTP serving 10 + */ 11 + export interface StorageMetadata { 12 + /** Original key used to store the data (human-readable) */ 13 + key: string; 14 + 15 + /** Size of the data in bytes (uncompressed size) */ 16 + size: number; 17 + 18 + /** Timestamp when the data was first created */ 19 + createdAt: Date; 20 + 21 + /** Timestamp when the data was last accessed */ 22 + lastAccessed: Date; 23 + 24 + /** Number of times this data has been accessed */ 25 + accessCount: number; 26 + 27 + /** Optional expiration timestamp. Data expires when current time > ttl */ 28 + ttl?: Date; 29 + 30 + /** Whether the data is compressed (e.g., with gzip) */ 31 + compressed: boolean; 32 + 33 + /** SHA256 checksum of the data for integrity verification */ 34 + checksum: string; 35 + 36 + /** Optional MIME type (e.g., 'text/html', 'application/json') */ 37 + mimeType?: string; 38 + 39 + /** Optional encoding (e.g., 'gzip', 'base64') */ 40 + encoding?: string; 41 + 42 + /** User-defined metadata fields */ 43 + customMetadata?: Record<string, string>; 44 + } 45 + 46 + /** 47 + * Statistics for a single storage tier. 48 + * 49 + * @remarks 50 + * Used for monitoring cache performance and capacity planning. 51 + */ 52 + export interface TierStats { 53 + /** Total bytes stored in this tier */ 54 + bytes: number; 55 + 56 + /** Total number of items stored in this tier */ 57 + items: number; 58 + 59 + /** Number of cache hits (only tracked if tier implements hit tracking) */ 60 + hits?: number; 61 + 62 + /** Number of cache misses (only tracked if tier implements miss tracking) */ 63 + misses?: number; 64 + 65 + /** Number of evictions due to size/count limits (only tracked if tier implements eviction) */ 66 + evictions?: number; 67 + } 68 + 69 + /** 70 + * Aggregated statistics across all configured tiers. 71 + * 72 + * @remarks 73 + * Provides a complete view of cache performance across the entire storage hierarchy. 74 + */ 75 + export interface AllTierStats { 76 + /** Statistics for hot tier (if configured) */ 77 + hot?: TierStats; 78 + 79 + /** Statistics for warm tier (if configured) */ 80 + warm?: TierStats; 81 + 82 + /** Statistics for cold tier (always present) */ 83 + cold: TierStats; 84 + 85 + /** Total hits across all tiers */ 86 + totalHits: number; 87 + 88 + /** Total misses across all tiers */ 89 + totalMisses: number; 90 + 91 + /** Hit rate as a percentage (0-1) */ 92 + hitRate: number; 93 + } 94 + 95 + /** 96 + * Interface that all storage tier implementations must satisfy. 97 + * 98 + * @remarks 99 + * This is the core abstraction that allows pluggable backends. 100 + * Implementations can be memory-based (Map, Redis), disk-based (filesystem, SQLite), 101 + * or cloud-based (S3, R2, etc.). 102 + * 103 + * @example 104 + * ```typescript 105 + * class RedisStorageTier implements StorageTier { 106 + * constructor(private client: RedisClient) {} 107 + * 108 + * async get(key: string): Promise<Uint8Array | null> { 109 + * const buffer = await this.client.getBuffer(key); 110 + * return buffer ? new Uint8Array(buffer) : null; 111 + * } 112 + * 113 + * // ... implement other methods 114 + * } 115 + * ``` 116 + */ 117 + export interface StorageTier { 118 + /** 119 + * Retrieve data for a key. 120 + * 121 + * @param key - The key to retrieve 122 + * @returns The data as a Uint8Array, or null if not found 123 + */ 124 + get(key: string): Promise<Uint8Array | null>; 125 + 126 + /** 127 + * Store data with associated metadata. 128 + * 129 + * @param key - The key to store under 130 + * @param data - The data to store (as Uint8Array) 131 + * @param metadata - Metadata to store alongside the data 132 + * 133 + * @remarks 134 + * If the key already exists, it should be overwritten. 135 + */ 136 + set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void>; 137 + 138 + /** 139 + * Delete data for a key. 140 + * 141 + * @param key - The key to delete 142 + * 143 + * @remarks 144 + * Should not throw if the key doesn't exist. 145 + */ 146 + delete(key: string): Promise<void>; 147 + 148 + /** 149 + * Check if a key exists in this tier. 150 + * 151 + * @param key - The key to check 152 + * @returns true if the key exists, false otherwise 153 + */ 154 + exists(key: string): Promise<boolean>; 155 + 156 + /** 157 + * List all keys in this tier, optionally filtered by prefix. 158 + * 159 + * @param prefix - Optional prefix to filter keys (e.g., 'user:' matches 'user:123', 'user:456') 160 + * @returns An async iterator of keys 161 + * 162 + * @remarks 163 + * This should be memory-efficient and stream keys rather than loading all into memory. 164 + * Useful for prefix-based invalidation and cache warming. 165 + * 166 + * @example 167 + * ```typescript 168 + * for await (const key of tier.listKeys('site:')) { 169 + * console.log(key); // 'site:abc', 'site:xyz', etc. 170 + * } 171 + * ``` 172 + */ 173 + listKeys(prefix?: string): AsyncIterableIterator<string>; 174 + 175 + /** 176 + * Delete multiple keys in a single operation. 177 + * 178 + * @param keys - Array of keys to delete 179 + * 180 + * @remarks 181 + * This is more efficient than calling delete() in a loop. 182 + * Implementations should batch deletions where possible. 183 + */ 184 + deleteMany(keys: string[]): Promise<void>; 185 + 186 + /** 187 + * Retrieve metadata for a key without fetching the data. 188 + * 189 + * @param key - The key to get metadata for 190 + * @returns The metadata, or null if not found 191 + * 192 + * @remarks 193 + * This is useful for checking TTL, access counts, etc. without loading large data. 194 + */ 195 + getMetadata(key: string): Promise<StorageMetadata | null>; 196 + 197 + /** 198 + * Update metadata for a key without modifying the data. 199 + * 200 + * @param key - The key to update metadata for 201 + * @param metadata - The new metadata 202 + * 203 + * @remarks 204 + * Useful for updating TTL (via touch()) or access counts. 205 + */ 206 + setMetadata(key: string, metadata: StorageMetadata): Promise<void>; 207 + 208 + /** 209 + * Get statistics about this tier. 210 + * 211 + * @returns Statistics including size, item count, hits, misses, etc. 212 + */ 213 + getStats(): Promise<TierStats>; 214 + 215 + /** 216 + * Clear all data from this tier. 217 + * 218 + * @remarks 219 + * Use with caution! This will delete all data in the tier. 220 + */ 221 + clear(): Promise<void>; 222 + } 223 + 224 + /** 225 + * Configuration for the TieredStorage system. 226 + * 227 + * @typeParam T - The type of data being stored (for serialization) 228 + * 229 + * @remarks 230 + * The tiered storage system uses a cascading containment model: 231 + * - Hot tier (optional): Fastest, smallest capacity (memory/Redis) 232 + * - Warm tier (optional): Medium speed, medium capacity (disk/database) 233 + * - Cold tier (required): Slowest, unlimited capacity (S3/object storage) 234 + * 235 + * Data flows down on writes (hot → warm → cold) and bubbles up on reads (cold → warm → hot). 236 + */ 237 + export interface TieredStorageConfig { 238 + /** Storage tier configuration */ 239 + tiers: { 240 + /** Optional hot tier - fastest, smallest capacity (e.g., in-memory, Redis) */ 241 + hot?: StorageTier; 242 + 243 + /** Optional warm tier - medium speed, medium capacity (e.g., disk, SQLite, Postgres) */ 244 + warm?: StorageTier; 245 + 246 + /** Required cold tier - slowest, largest capacity (e.g., S3, R2, object storage) */ 247 + cold: StorageTier; 248 + }; 249 + 250 + /** 251 + * Whether to automatically compress data before storing. 252 + * 253 + * @defaultValue false 254 + * 255 + * @remarks 256 + * Uses gzip compression. Compression is transparent - data is automatically 257 + * decompressed on retrieval. The `compressed` flag in metadata indicates compression state. 258 + */ 259 + compression?: boolean; 260 + 261 + /** 262 + * Default TTL (time-to-live) in milliseconds. 263 + * 264 + * @remarks 265 + * Data will expire after this duration. Can be overridden per-key via SetOptions. 266 + * If not set, data never expires. 267 + */ 268 + defaultTTL?: number; 269 + 270 + /** 271 + * Strategy for promoting data to upper tiers on cache miss. 272 + * 273 + * @defaultValue 'lazy' 274 + * 275 + * @remarks 276 + * - 'eager': Immediately promote data to all upper tiers on read 277 + * - 'lazy': Don't automatically promote; rely on explicit promotion or next write 278 + * 279 + * Eager promotion increases hot tier hit rate but adds write overhead. 280 + * Lazy promotion reduces writes but may serve from lower tiers more often. 281 + */ 282 + promotionStrategy?: 'eager' | 'lazy'; 283 + 284 + /** 285 + * Custom serialization/deserialization functions. 286 + * 287 + * @remarks 288 + * By default, JSON serialization is used. Provide custom functions for: 289 + * - Non-JSON types (e.g., Buffer, custom classes) 290 + * - Performance optimization (e.g., msgpack, protobuf) 291 + * - Encryption (serialize includes encryption, deserialize includes decryption) 292 + */ 293 + serialization?: { 294 + /** Convert data to Uint8Array for storage */ 295 + serialize: (data: unknown) => Promise<Uint8Array>; 296 + 297 + /** Convert Uint8Array back to original data */ 298 + deserialize: (data: Uint8Array) => Promise<unknown>; 299 + }; 300 + } 301 + 302 + /** 303 + * Options for setting data in the cache. 304 + * 305 + * @remarks 306 + * These options allow fine-grained control over where and how data is stored. 307 + */ 308 + export interface SetOptions { 309 + /** 310 + * Custom TTL in milliseconds for this specific key. 311 + * 312 + * @remarks 313 + * Overrides the default TTL from TieredStorageConfig. 314 + * Data will expire after this duration from the current time. 315 + */ 316 + ttl?: number; 317 + 318 + /** 319 + * Custom metadata to attach to this key. 320 + * 321 + * @remarks 322 + * Merged with system-generated metadata (size, checksum, timestamps). 323 + * Useful for storing application-specific information like content-type, encoding, etc. 324 + */ 325 + metadata?: Record<string, string>; 326 + 327 + /** 328 + * Skip writing to specific tiers. 329 + * 330 + * @remarks 331 + * Useful for controlling which tiers receive data. For example: 332 + * - Large files: `skipTiers: ['hot']` to avoid filling memory 333 + * - Small critical files: Write to hot only for fastest access 334 + * 335 + * Note: Cold tier can never be skipped (it's the source of truth). 336 + * 337 + * @example 338 + * ```typescript 339 + * // Store large file only in warm and cold (skip memory) 340 + * await storage.set('large-video.mp4', videoData, { skipTiers: ['hot'] }); 341 + * 342 + * // Store index.html in all tiers for fast access 343 + * await storage.set('index.html', htmlData); // No skipping 344 + * ``` 345 + */ 346 + skipTiers?: ('hot' | 'warm')[]; 347 + } 348 + 349 + /** 350 + * Result from retrieving data with metadata. 351 + * 352 + * @typeParam T - The type of data being retrieved 353 + * 354 + * @remarks 355 + * Includes both the data and information about where it was served from. 356 + */ 357 + export interface StorageResult<T> { 358 + /** The retrieved data */ 359 + data: T; 360 + 361 + /** Metadata associated with the data */ 362 + metadata: StorageMetadata; 363 + 364 + /** Which tier the data was served from */ 365 + source: 'hot' | 'warm' | 'cold'; 366 + } 367 + 368 + /** 369 + * Result from setting data in the cache. 370 + * 371 + * @remarks 372 + * Indicates which tiers successfully received the data. 373 + */ 374 + export interface SetResult { 375 + /** The key that was set */ 376 + key: string; 377 + 378 + /** Metadata that was stored with the data */ 379 + metadata: StorageMetadata; 380 + 381 + /** Which tiers received the data */ 382 + tiersWritten: ('hot' | 'warm' | 'cold')[]; 383 + } 384 + 385 + /** 386 + * Snapshot of the entire storage state. 387 + * 388 + * @remarks 389 + * Used for export/import, backup, and migration scenarios. 390 + * The snapshot includes metadata but not the actual data (data remains in tiers). 391 + */ 392 + export interface StorageSnapshot { 393 + /** Snapshot format version (for compatibility) */ 394 + version: number; 395 + 396 + /** When this snapshot was created */ 397 + exportedAt: Date; 398 + 399 + /** All keys present in cold tier (source of truth) */ 400 + keys: string[]; 401 + 402 + /** Metadata for each key */ 403 + metadata: Record<string, StorageMetadata>; 404 + 405 + /** Statistics at time of export */ 406 + stats: AllTierStats; 407 + }
+57
src/utils/checksum.ts
··· 1 + import { createHash, timingSafeEqual } from 'node:crypto'; 2 + 3 + /** 4 + * Calculate SHA256 checksum of data. 5 + * 6 + * @param data - Data to checksum 7 + * @returns Hex-encoded SHA256 hash 8 + * 9 + * @remarks 10 + * Used for data integrity verification. The checksum is stored in metadata 11 + * and can be used to detect corruption or tampering. 12 + * 13 + * @example 14 + * ```typescript 15 + * const data = new TextEncoder().encode('Hello, world!'); 16 + * const checksum = calculateChecksum(data); 17 + * console.log(checksum); // '315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3' 18 + * ``` 19 + */ 20 + export function calculateChecksum(data: Uint8Array): string { 21 + const hash = createHash('sha256'); 22 + hash.update(data); 23 + return hash.digest('hex'); 24 + } 25 + 26 + /** 27 + * Verify that data matches an expected checksum. 28 + * 29 + * @param data - Data to verify 30 + * @param expectedChecksum - Expected SHA256 checksum (hex-encoded) 31 + * @returns true if checksums match, false otherwise 32 + * 33 + * @remarks 34 + * Uses constant-time comparison to prevent timing attacks. 35 + * 36 + * @example 37 + * ```typescript 38 + * const isValid = verifyChecksum(data, metadata.checksum); 39 + * if (!isValid) { 40 + * throw new Error('Data corruption detected'); 41 + * } 42 + * ``` 43 + */ 44 + export function verifyChecksum(data: Uint8Array, expectedChecksum: string): boolean { 45 + const actualChecksum = calculateChecksum(data); 46 + 47 + // Use constant-time comparison to prevent timing attacks 48 + try { 49 + return timingSafeEqual( 50 + Buffer.from(actualChecksum, 'hex'), 51 + Buffer.from(expectedChecksum, 'hex') 52 + ); 53 + } catch { 54 + // If checksums have different lengths, timingSafeEqual throws 55 + return false; 56 + } 57 + }
+78
src/utils/compression.ts
··· 1 + import { gzip, gunzip } from 'node:zlib'; 2 + import { promisify } from 'node:util'; 3 + 4 + const gzipAsync = promisify(gzip); 5 + const gunzipAsync = promisify(gunzip); 6 + 7 + /** 8 + * Compress data using gzip. 9 + * 10 + * @param data - Data to compress 11 + * @returns Compressed data as Uint8Array 12 + * 13 + * @remarks 14 + * Uses Node.js zlib with default compression level (6). 15 + * Compression is transparent to the user - data is automatically decompressed on retrieval. 16 + * 17 + * @example 18 + * ```typescript 19 + * const original = new TextEncoder().encode('Hello, world!'); 20 + * const compressed = await compress(original); 21 + * console.log(`Compressed from ${original.length} to ${compressed.length} bytes`); 22 + * ``` 23 + */ 24 + export async function compress(data: Uint8Array): Promise<Uint8Array> { 25 + const buffer = Buffer.from(data); 26 + const compressed = await gzipAsync(buffer); 27 + return new Uint8Array(compressed); 28 + } 29 + 30 + /** 31 + * Decompress gzip-compressed data. 32 + * 33 + * @param data - Compressed data 34 + * @returns Decompressed data as Uint8Array 35 + * @throws Error if data is not valid gzip format 36 + * 37 + * @remarks 38 + * Automatically validates gzip magic bytes (0x1f 0x8b) before decompression. 39 + * 40 + * @example 41 + * ```typescript 42 + * const decompressed = await decompress(compressedData); 43 + * const text = new TextDecoder().decode(decompressed); 44 + * ``` 45 + */ 46 + export async function decompress(data: Uint8Array): Promise<Uint8Array> { 47 + // Validate gzip magic bytes 48 + if (data.length < 2 || data[0] !== 0x1f || data[1] !== 0x8b) { 49 + throw new Error('Invalid gzip data: missing magic bytes'); 50 + } 51 + 52 + const buffer = Buffer.from(data); 53 + const decompressed = await gunzipAsync(buffer); 54 + return new Uint8Array(decompressed); 55 + } 56 + 57 + /** 58 + * Check if data appears to be gzip-compressed by inspecting magic bytes. 59 + * 60 + * @param data - Data to check 61 + * @returns true if data starts with gzip magic bytes (0x1f 0x8b) 62 + * 63 + * @remarks 64 + * This is a quick check that doesn't decompress the data. 65 + * Useful for detecting already-compressed data to avoid double compression. 66 + * 67 + * @example 68 + * ```typescript 69 + * if (isGzipped(data)) { 70 + * console.log('Already compressed, skipping compression'); 71 + * } else { 72 + * data = await compress(data); 73 + * } 74 + * ``` 75 + */ 76 + export function isGzipped(data: Uint8Array): boolean { 77 + return data.length >= 2 && data[0] === 0x1f && data[1] === 0x8b; 78 + }
+69
src/utils/path-encoding.ts
··· 1 + /** 2 + * Encode a key to be safe for use as a filesystem path. 3 + * 4 + * @param key - The key to encode 5 + * @returns Filesystem-safe encoded key 6 + * 7 + * @remarks 8 + * Encodes characters that are problematic in filenames: 9 + * - Forward slash (/) → %2F 10 + * - Backslash (\) → %5C 11 + * - Colon (:) → %3A 12 + * - Asterisk (*) → %2A 13 + * - Question mark (?) → %3F 14 + * - Quote (") → %22 15 + * - Less than (<) → %3C 16 + * - Greater than (>) → %3E 17 + * - Pipe (|) → %7C 18 + * - Percent (%) → %25 19 + * - Null byte → %00 20 + * 21 + * @example 22 + * ```typescript 23 + * const key = 'user:123/profile.json'; 24 + * const encoded = encodeKey(key); 25 + * // Result: 'user%3A123%2Fprofile.json' 26 + * ``` 27 + */ 28 + export function encodeKey(key: string): string { 29 + return key 30 + .replace(/%/g, '%25') // Must be first! 31 + .replace(/\//g, '%2F') 32 + .replace(/\\/g, '%5C') 33 + .replace(/:/g, '%3A') 34 + .replace(/\*/g, '%2A') 35 + .replace(/\?/g, '%3F') 36 + .replace(/"/g, '%22') 37 + .replace(/</g, '%3C') 38 + .replace(/>/g, '%3E') 39 + .replace(/\|/g, '%7C') 40 + .replace(/\0/g, '%00'); 41 + } 42 + 43 + /** 44 + * Decode a filesystem-safe key back to original form. 45 + * 46 + * @param encoded - The encoded key 47 + * @returns Original key 48 + * 49 + * @example 50 + * ```typescript 51 + * const encoded = 'user%3A123%2Fprofile.json'; 52 + * const key = decodeKey(encoded); 53 + * // Result: 'user:123/profile.json' 54 + * ``` 55 + */ 56 + export function decodeKey(encoded: string): string { 57 + return encoded 58 + .replace(/%2F/g, '/') 59 + .replace(/%5C/g, '\\') 60 + .replace(/%3A/g, ':') 61 + .replace(/%2A/g, '*') 62 + .replace(/%3F/g, '?') 63 + .replace(/%22/g, '"') 64 + .replace(/%3C/g, '<') 65 + .replace(/%3E/g, '>') 66 + .replace(/%7C/g, '|') 67 + .replace(/%00/g, '\0') 68 + .replace(/%25/g, '%'); // Must be last! 69 + }
+46
src/utils/serialization.ts
··· 1 + /** 2 + * Default JSON serializer. 3 + * 4 + * @param data - Data to serialize (must be JSON-serializable) 5 + * @returns Serialized data as Uint8Array (UTF-8 encoded JSON) 6 + * 7 + * @remarks 8 + * This is the default serializer used if no custom serializer is provided. 9 + * Handles most JavaScript types but cannot serialize: 10 + * - Functions 11 + * - Symbols 12 + * - undefined values (they become null) 13 + * - Circular references 14 + * 15 + * @example 16 + * ```typescript 17 + * const data = { name: 'Alice', age: 30 }; 18 + * const serialized = await defaultSerialize(data); 19 + * ``` 20 + */ 21 + export async function defaultSerialize(data: unknown): Promise<Uint8Array> { 22 + const json = JSON.stringify(data); 23 + return new TextEncoder().encode(json); 24 + } 25 + 26 + /** 27 + * Default JSON deserializer. 28 + * 29 + * @param data - Serialized data (UTF-8 encoded JSON) 30 + * @returns Deserialized data 31 + * @throws SyntaxError if data is not valid JSON 32 + * 33 + * @remarks 34 + * This is the default deserializer used if no custom deserializer is provided. 35 + * Parses UTF-8 encoded JSON back to JavaScript objects. 36 + * 37 + * @example 38 + * ```typescript 39 + * const data = await defaultDeserialize(serialized); 40 + * console.log(data.name); // 'Alice' 41 + * ``` 42 + */ 43 + export async function defaultDeserialize(data: Uint8Array): Promise<unknown> { 44 + const json = new TextDecoder().decode(data); 45 + return JSON.parse(json); 46 + }
+404
test/TieredStorage.test.ts
··· 1 + import { describe, it, expect, beforeEach, afterEach } from 'vitest'; 2 + import { TieredStorage } from '../src/TieredStorage.js'; 3 + import { MemoryStorageTier } from '../src/tiers/MemoryStorageTier.js'; 4 + import { DiskStorageTier } from '../src/tiers/DiskStorageTier.js'; 5 + import { rm } from 'node:fs/promises'; 6 + 7 + describe('TieredStorage', () => { 8 + const testDir = './test-cache'; 9 + 10 + afterEach(async () => { 11 + await rm(testDir, { recursive: true, force: true }); 12 + }); 13 + 14 + describe('Basic Operations', () => { 15 + it('should store and retrieve data', async () => { 16 + const storage = new TieredStorage({ 17 + tiers: { 18 + hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }), 19 + warm: new DiskStorageTier({ directory: `${testDir}/warm` }), 20 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 21 + }, 22 + }); 23 + 24 + await storage.set('test-key', { message: 'Hello, world!' }); 25 + const result = await storage.get('test-key'); 26 + 27 + expect(result).toEqual({ message: 'Hello, world!' }); 28 + }); 29 + 30 + it('should return null for non-existent key', async () => { 31 + const storage = new TieredStorage({ 32 + tiers: { 33 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 34 + }, 35 + }); 36 + 37 + const result = await storage.get('non-existent'); 38 + expect(result).toBeNull(); 39 + }); 40 + 41 + it('should delete data from all tiers', async () => { 42 + const storage = new TieredStorage({ 43 + tiers: { 44 + hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }), 45 + warm: new DiskStorageTier({ directory: `${testDir}/warm` }), 46 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 47 + }, 48 + }); 49 + 50 + await storage.set('test-key', { data: 'test' }); 51 + await storage.delete('test-key'); 52 + const result = await storage.get('test-key'); 53 + 54 + expect(result).toBeNull(); 55 + }); 56 + 57 + it('should check if key exists', async () => { 58 + const storage = new TieredStorage({ 59 + tiers: { 60 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 61 + }, 62 + }); 63 + 64 + await storage.set('test-key', { data: 'test' }); 65 + 66 + expect(await storage.exists('test-key')).toBe(true); 67 + expect(await storage.exists('non-existent')).toBe(false); 68 + }); 69 + }); 70 + 71 + describe('Cascading Write', () => { 72 + it('should write to all configured tiers', async () => { 73 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 74 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 75 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 76 + 77 + const storage = new TieredStorage({ 78 + tiers: { hot, warm, cold }, 79 + }); 80 + 81 + await storage.set('test-key', { data: 'test' }); 82 + 83 + // Verify data exists in all tiers 84 + expect(await hot.exists('test-key')).toBe(true); 85 + expect(await warm.exists('test-key')).toBe(true); 86 + expect(await cold.exists('test-key')).toBe(true); 87 + }); 88 + 89 + it('should skip tiers when specified', async () => { 90 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 91 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 92 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 93 + 94 + const storage = new TieredStorage({ 95 + tiers: { hot, warm, cold }, 96 + }); 97 + 98 + // Skip hot tier 99 + await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] }); 100 + 101 + expect(await hot.exists('test-key')).toBe(false); 102 + expect(await warm.exists('test-key')).toBe(true); 103 + expect(await cold.exists('test-key')).toBe(true); 104 + }); 105 + }); 106 + 107 + describe('Bubbling Read', () => { 108 + it('should read from hot tier first', async () => { 109 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 110 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 111 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 112 + 113 + const storage = new TieredStorage({ 114 + tiers: { hot, warm, cold }, 115 + }); 116 + 117 + await storage.set('test-key', { data: 'test' }); 118 + const result = await storage.getWithMetadata('test-key'); 119 + 120 + expect(result?.source).toBe('hot'); 121 + expect(result?.data).toEqual({ data: 'test' }); 122 + }); 123 + 124 + it('should fall back to warm tier on hot miss', async () => { 125 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 126 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 127 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 128 + 129 + const storage = new TieredStorage({ 130 + tiers: { hot, warm, cold }, 131 + }); 132 + 133 + // Write to warm and cold, skip hot 134 + await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] }); 135 + 136 + const result = await storage.getWithMetadata('test-key'); 137 + 138 + expect(result?.source).toBe('warm'); 139 + expect(result?.data).toEqual({ data: 'test' }); 140 + }); 141 + 142 + it('should fall back to cold tier on hot and warm miss', async () => { 143 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 144 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 145 + 146 + const storage = new TieredStorage({ 147 + tiers: { hot, cold }, 148 + }); 149 + 150 + // Write only to cold 151 + await cold.set( 152 + 'test-key', 153 + new TextEncoder().encode(JSON.stringify({ data: 'test' })), 154 + { 155 + key: 'test-key', 156 + size: 100, 157 + createdAt: new Date(), 158 + lastAccessed: new Date(), 159 + accessCount: 0, 160 + compressed: false, 161 + checksum: 'abc123', 162 + } 163 + ); 164 + 165 + const result = await storage.getWithMetadata('test-key'); 166 + 167 + expect(result?.source).toBe('cold'); 168 + expect(result?.data).toEqual({ data: 'test' }); 169 + }); 170 + }); 171 + 172 + describe('Promotion Strategy', () => { 173 + it('should eagerly promote data to upper tiers', async () => { 174 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 175 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 176 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 177 + 178 + const storage = new TieredStorage({ 179 + tiers: { hot, warm, cold }, 180 + promotionStrategy: 'eager', 181 + }); 182 + 183 + // Write only to cold 184 + await cold.set( 185 + 'test-key', 186 + new TextEncoder().encode(JSON.stringify({ data: 'test' })), 187 + { 188 + key: 'test-key', 189 + size: 100, 190 + createdAt: new Date(), 191 + lastAccessed: new Date(), 192 + accessCount: 0, 193 + compressed: false, 194 + checksum: 'abc123', 195 + } 196 + ); 197 + 198 + // Read should promote to hot and warm 199 + await storage.get('test-key'); 200 + 201 + expect(await hot.exists('test-key')).toBe(true); 202 + expect(await warm.exists('test-key')).toBe(true); 203 + }); 204 + 205 + it('should lazily promote data (not automatic)', async () => { 206 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 207 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 208 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 209 + 210 + const storage = new TieredStorage({ 211 + tiers: { hot, warm, cold }, 212 + promotionStrategy: 'lazy', 213 + }); 214 + 215 + // Write only to cold 216 + await cold.set( 217 + 'test-key', 218 + new TextEncoder().encode(JSON.stringify({ data: 'test' })), 219 + { 220 + key: 'test-key', 221 + size: 100, 222 + createdAt: new Date(), 223 + lastAccessed: new Date(), 224 + accessCount: 0, 225 + compressed: false, 226 + checksum: 'abc123', 227 + } 228 + ); 229 + 230 + // Read should NOT promote to hot and warm 231 + await storage.get('test-key'); 232 + 233 + expect(await hot.exists('test-key')).toBe(false); 234 + expect(await warm.exists('test-key')).toBe(false); 235 + }); 236 + }); 237 + 238 + describe('TTL Management', () => { 239 + it('should expire data after TTL', async () => { 240 + const storage = new TieredStorage({ 241 + tiers: { 242 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 243 + }, 244 + }); 245 + 246 + // Set with 100ms TTL 247 + await storage.set('test-key', { data: 'test' }, { ttl: 100 }); 248 + 249 + // Should exist immediately 250 + expect(await storage.get('test-key')).toEqual({ data: 'test' }); 251 + 252 + // Wait for expiration 253 + await new Promise((resolve) => setTimeout(resolve, 150)); 254 + 255 + // Should be null after expiration 256 + expect(await storage.get('test-key')).toBeNull(); 257 + }); 258 + 259 + it('should renew TTL with touch', async () => { 260 + const storage = new TieredStorage({ 261 + tiers: { 262 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 263 + }, 264 + defaultTTL: 100, 265 + }); 266 + 267 + await storage.set('test-key', { data: 'test' }); 268 + 269 + // Wait 50ms 270 + await new Promise((resolve) => setTimeout(resolve, 50)); 271 + 272 + // Renew TTL 273 + await storage.touch('test-key', 200); 274 + 275 + // Wait another 100ms (would have expired without touch) 276 + await new Promise((resolve) => setTimeout(resolve, 100)); 277 + 278 + // Should still exist 279 + expect(await storage.get('test-key')).toEqual({ data: 'test' }); 280 + }); 281 + }); 282 + 283 + describe('Prefix Invalidation', () => { 284 + it('should invalidate all keys with prefix', async () => { 285 + const storage = new TieredStorage({ 286 + tiers: { 287 + hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }), 288 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 289 + }, 290 + }); 291 + 292 + await storage.set('user:123', { name: 'Alice' }); 293 + await storage.set('user:456', { name: 'Bob' }); 294 + await storage.set('post:789', { title: 'Test' }); 295 + 296 + const deleted = await storage.invalidate('user:'); 297 + 298 + expect(deleted).toBe(2); 299 + expect(await storage.exists('user:123')).toBe(false); 300 + expect(await storage.exists('user:456')).toBe(false); 301 + expect(await storage.exists('post:789')).toBe(true); 302 + }); 303 + }); 304 + 305 + describe('Compression', () => { 306 + it('should compress data when enabled', async () => { 307 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 308 + 309 + const storage = new TieredStorage({ 310 + tiers: { cold }, 311 + compression: true, 312 + }); 313 + 314 + const largeData = { data: 'x'.repeat(10000) }; 315 + const result = await storage.set('test-key', largeData); 316 + 317 + // Check that compressed flag is set 318 + expect(result.metadata.compressed).toBe(true); 319 + 320 + // Verify data can be retrieved correctly 321 + const retrieved = await storage.get('test-key'); 322 + expect(retrieved).toEqual(largeData); 323 + }); 324 + }); 325 + 326 + describe('Bootstrap', () => { 327 + it('should bootstrap hot from warm', async () => { 328 + const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }); 329 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 330 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 331 + 332 + const storage = new TieredStorage({ 333 + tiers: { hot, warm, cold }, 334 + }); 335 + 336 + // Write some data 337 + await storage.set('key1', { data: '1' }); 338 + await storage.set('key2', { data: '2' }); 339 + await storage.set('key3', { data: '3' }); 340 + 341 + // Clear hot tier 342 + await hot.clear(); 343 + 344 + // Bootstrap hot from warm 345 + const loaded = await storage.bootstrapHot(); 346 + 347 + expect(loaded).toBe(3); 348 + expect(await hot.exists('key1')).toBe(true); 349 + expect(await hot.exists('key2')).toBe(true); 350 + expect(await hot.exists('key3')).toBe(true); 351 + }); 352 + 353 + it('should bootstrap warm from cold', async () => { 354 + const warm = new DiskStorageTier({ directory: `${testDir}/warm` }); 355 + const cold = new DiskStorageTier({ directory: `${testDir}/cold` }); 356 + 357 + const storage = new TieredStorage({ 358 + tiers: { warm, cold }, 359 + }); 360 + 361 + // Write directly to cold 362 + await cold.set( 363 + 'key1', 364 + new TextEncoder().encode(JSON.stringify({ data: '1' })), 365 + { 366 + key: 'key1', 367 + size: 100, 368 + createdAt: new Date(), 369 + lastAccessed: new Date(), 370 + accessCount: 0, 371 + compressed: false, 372 + checksum: 'abc', 373 + } 374 + ); 375 + 376 + // Bootstrap warm from cold 377 + const loaded = await storage.bootstrapWarm({ limit: 10 }); 378 + 379 + expect(loaded).toBe(1); 380 + expect(await warm.exists('key1')).toBe(true); 381 + }); 382 + }); 383 + 384 + describe('Statistics', () => { 385 + it('should return statistics for all tiers', async () => { 386 + const storage = new TieredStorage({ 387 + tiers: { 388 + hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }), 389 + warm: new DiskStorageTier({ directory: `${testDir}/warm` }), 390 + cold: new DiskStorageTier({ directory: `${testDir}/cold` }), 391 + }, 392 + }); 393 + 394 + await storage.set('key1', { data: 'test1' }); 395 + await storage.set('key2', { data: 'test2' }); 396 + 397 + const stats = await storage.getStats(); 398 + 399 + expect(stats.cold.items).toBe(2); 400 + expect(stats.warm?.items).toBe(2); 401 + expect(stats.hot?.items).toBe(2); 402 + }); 403 + }); 404 + });
+33
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "node", 6 + "allowSyntheticDefaultImports": true, 7 + "esModuleInterop": true, 8 + "allowJs": true, 9 + "strict": true, 10 + "noImplicitAny": true, 11 + "strictNullChecks": true, 12 + "strictFunctionTypes": true, 13 + "strictBindCallApply": true, 14 + "strictPropertyInitialization": true, 15 + "noImplicitReturns": true, 16 + "noImplicitThis": true, 17 + "noUncheckedIndexedAccess": true, 18 + "noImplicitOverride": true, 19 + "exactOptionalPropertyTypes": true, 20 + "noPropertyAccessFromIndexSignature": false, 21 + "declaration": true, 22 + "declarationMap": true, 23 + "sourceMap": true, 24 + "outDir": "./dist", 25 + "rootDir": "./src", 26 + "removeComments": false, 27 + "skipLibCheck": true, 28 + "forceConsistentCasingInFileNames": true, 29 + "resolveJsonModule": true 30 + }, 31 + "include": ["src/**/*"], 32 + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] 33 + }