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

init commit

nekomimi.pet 34ec0d34

verified
+136
.env.example
···
+
# Tiered Storage Configuration
+
# Copy this file to .env and configure for your environment
+
+
# ============================================================================
+
# S3 Configuration (Cold Tier - Required)
+
# ============================================================================
+
+
# AWS S3 bucket name (or S3-compatible bucket)
+
S3_BUCKET=tiered-storage-cache
+
+
# Optional: Separate bucket for metadata (RECOMMENDED for production!)
+
# When set, metadata is stored as separate JSON objects instead of S3 object metadata.
+
# This allows fast, cheap metadata updates without copying entire objects.
+
# Leave blank to store metadata in S3 object metadata fields (slower, more expensive).
+
S3_METADATA_BUCKET=tiered-storage-metadata
+
+
# AWS region
+
S3_REGION=us-east-1
+
+
# S3 endpoint (optional - for S3-compatible services like R2, Minio)
+
# Leave blank for AWS S3
+
# For Cloudflare R2: https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com
+
# For MinIO: http://localhost:9000
+
# For other S3-compatible: https://s3.your-service.com
+
# S3_ENDPOINT=
+
+
# Force path-style URLs (usually needed for S3-compatible services)
+
# Default: true (recommended for most S3-compatible services)
+
# Set to false only if your service requires virtual-host-style URLs
+
# S3_FORCE_PATH_STYLE=true
+
+
# AWS credentials
+
# If not provided, uses default AWS credential chain
+
# (environment variables, ~/.aws/credentials, IAM roles, etc.)
+
AWS_ACCESS_KEY_ID=your_access_key_id
+
AWS_SECRET_ACCESS_KEY=your_secret_access_key
+
+
# ============================================================================
+
# Cloudflare R2 Example Configuration
+
# ============================================================================
+
# Uncomment these to use Cloudflare R2 instead of AWS S3:
+
#
+
# S3_BUCKET=my-r2-bucket
+
# S3_METADATA_BUCKET=my-r2-metadata-bucket
+
# S3_REGION=auto
+
# S3_ENDPOINT=https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com
+
# AWS_ACCESS_KEY_ID=your_r2_access_key_id
+
# AWS_SECRET_ACCESS_KEY=your_r2_secret_access_key
+
+
# ============================================================================
+
# Memory Tier Configuration (Hot)
+
# ============================================================================
+
+
# Maximum size in bytes for hot (memory) tier
+
# Default: 100MB
+
MEMORY_MAX_SIZE_BYTES=104857600
+
+
# Maximum number of items in hot tier
+
# Optional - if not set, only size limit applies
+
MEMORY_MAX_ITEMS=1000
+
+
# ============================================================================
+
# Disk Tier Configuration (Warm)
+
# ============================================================================
+
+
# Directory for warm tier cache
+
# Default: ./cache/warm
+
DISK_WARM_DIRECTORY=./cache/warm
+
+
# Maximum size in bytes for warm tier
+
# Optional - if not set, no size limit
+
DISK_WARM_MAX_SIZE_BYTES=10737418240
+
+
# Eviction policy when size limit reached
+
# Options: lru, fifo, size
+
# Default: lru
+
DISK_WARM_EVICTION_POLICY=lru
+
+
# ============================================================================
+
# Storage Options
+
# ============================================================================
+
+
# Enable compression (gzip)
+
# Default: false
+
COMPRESSION_ENABLED=true
+
+
# Default TTL in milliseconds
+
# Optional - if not set, data never expires
+
# Example: 1209600000 = 14 days
+
DEFAULT_TTL_MS=1209600000
+
+
# Promotion strategy: 'eager' or 'lazy'
+
# eager: Automatically promote data to upper tiers on read
+
# lazy: Only promote on explicit bootstrap or write
+
# Default: lazy
+
PROMOTION_STRATEGY=lazy
+
+
# ============================================================================
+
# Bootstrap Configuration
+
# ============================================================================
+
+
# Number of items to load into hot tier on bootstrap
+
# Optional - if not set, loads all items
+
BOOTSTRAP_HOT_LIMIT=1000
+
+
# Number of days to look back when bootstrapping warm tier
+
# Example: 7 = only load items accessed in last 7 days
+
BOOTSTRAP_WARM_DAYS=7
+
+
# Maximum items to load into warm tier on bootstrap
+
# Optional - if not set, loads all matching items
+
BOOTSTRAP_WARM_LIMIT=10000
+
+
# ============================================================================
+
# Performance Tuning
+
# ============================================================================
+
+
# Maximum concurrent operations for bootstrap
+
# Default: 10
+
BOOTSTRAP_CONCURRENCY=10
+
+
# Timeout for tier operations in milliseconds
+
# Default: 30000 (30 seconds)
+
TIER_OPERATION_TIMEOUT_MS=30000
+
+
# ============================================================================
+
# Monitoring & Observability
+
# ============================================================================
+
+
# Enable statistics tracking
+
# Default: true
+
STATS_ENABLED=true
+
+
# Log level: debug, info, warn, error
+
# Default: info
+
LOG_LEVEL=info
+28
.eslintrc.cjs
···
+
module.exports = {
+
parser: '@typescript-eslint/parser',
+
parserOptions: {
+
ecmaVersion: 2022,
+
sourceType: 'module',
+
project: './tsconfig.json',
+
},
+
plugins: ['@typescript-eslint'],
+
extends: [
+
'eslint:recommended',
+
'@typescript-eslint/recommended',
+
'@typescript-eslint/recommended-requiring-type-checking',
+
],
+
root: true,
+
env: {
+
node: true,
+
es2022: true,
+
},
+
rules: {
+
'@typescript-eslint/no-unused-vars': 'error',
+
'@typescript-eslint/explicit-function-return-type': 'warn',
+
'@typescript-eslint/no-explicit-any': 'warn',
+
'@typescript-eslint/prefer-nullish-coalescing': 'error',
+
'@typescript-eslint/prefer-optional-chain': 'error',
+
'@typescript-eslint/no-floating-promises': 'error',
+
'@typescript-eslint/await-thenable': 'error',
+
},
+
};
+17
.gitignore
···
+
node_modules/
+
dist/
+
.env
+
*.log
+
.DS_Store
+
+
# Example cache directories
+
example-cache/*
+
example-cache/
+
cache/*
+
cache/
+
+
# Test cache directories
+
test-cache/
+
+
# Build artifacts
+
*.tsbuildinfo
+574
README.md
···
+
# Tiered Storage
+
+
A lightweight, pluggable tiered storage library that orchestrates caching across hot (memory), warm (disk/database), and cold (S3/object storage) tiers.
+
+
## Features
+
+
- **Cascading Containment Model**: Hot ⊆ Warm ⊆ Cold (lower tiers contain all data from upper tiers)
+
- **Pluggable Backends**: Bring your own Redis, Postgres, SQLite, or use built-in implementations
+
- **Automatic Promotion**: Configurable eager/lazy promotion strategies for cache warming
+
- **TTL Management**: Per-key TTL with automatic expiration and renewal
+
- **Prefix Invalidation**: Efficiently delete groups of keys by prefix
+
- **Bootstrap Support**: Warm up caches from lower tiers on startup
+
- **Compression**: Optional transparent gzip compression
+
- **TypeScript First**: Full type safety with comprehensive TSDoc comments
+
- **Zero Forced Dependencies**: Only require what you use
+
+
## Installation
+
+
```bash
+
npm install tiered-storage
+
# or
+
bun add tiered-storage
+
```
+
+
## Quick Start
+
+
```typescript
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage';
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB
+
warm: new DiskStorageTier({ directory: './cache' }),
+
cold: new S3StorageTier({
+
bucket: 'my-bucket',
+
region: 'us-east-1',
+
credentials: {
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
+
},
+
}),
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
+
promotionStrategy: 'lazy',
+
});
+
+
// Store data (cascades to all tiers)
+
await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' });
+
+
// Retrieve data (bubbles up from cold → warm → hot)
+
const user = await storage.get('user:123');
+
+
// Get data with metadata and source tier
+
const result = await storage.getWithMetadata('user:123');
+
console.log(`Served from ${result.source}`); // 'hot', 'warm', or 'cold'
+
+
// Invalidate all keys with prefix
+
await storage.invalidate('user:');
+
+
// Renew TTL
+
await storage.touch('user:123');
+
```
+
+
## Core Concepts
+
+
### Cascading Containment Model
+
+
```
+
┌──────────────────────────────────────────────────────┐
+
│ Cold Storage (S3/Object Storage) │
+
│ • Contains ALL objects (source of truth) │
+
│ • Slowest access, unlimited capacity │
+
├──────────────────────────────────────────────────────┤
+
│ Warm Storage (Disk/Database) │
+
│ • Contains ALL hot objects + additional warm objects │
+
│ • Medium access speed, large capacity │
+
├──────────────────────────────────────────────────────┤
+
│ Hot Storage (Memory) │
+
│ • Contains only the hottest objects │
+
│ • Fastest access, limited capacity │
+
└──────────────────────────────────────────────────────┘
+
```
+
+
**Write Strategy (Cascading Down):**
+
- Write to **hot** → also writes to **warm** and **cold**
+
- Write to **warm** → also writes to **cold**
+
- Write to **cold** → only writes to **cold**
+
+
**Read Strategy (Bubbling Up):**
+
- Check **hot** first → if miss, check **warm** → if miss, check **cold**
+
- On cache miss, optionally promote data up through tiers
+
+
### Selective Tier Placement
+
+
For use cases like static site hosting, you can control which files go into which tiers:
+
+
```typescript
+
// Small, critical file (index.html) - store in all tiers for instant serving
+
await storage.set('site:abc/index.html', htmlContent);
+
+
// Large file (video) - skip hot tier to avoid memory bloat
+
await storage.set('site:abc/video.mp4', videoData, { skipTiers: ['hot'] });
+
+
// Medium files (images, CSS) - skip hot, use warm + cold
+
await storage.set('site:abc/style.css', cssData, { skipTiers: ['hot'] });
+
```
+
+
This pattern ensures:
+
- Hot tier stays small and fast (only critical files)
+
- Warm tier caches everything (all site files on disk)
+
- Cold tier is source of truth (all data)
+
+
## API Reference
+
+
### `TieredStorage`
+
+
Main orchestrator class for tiered storage.
+
+
#### Constructor
+
+
```typescript
+
new TieredStorage<T>(config: TieredStorageConfig)
+
```
+
+
**Config Options:**
+
+
```typescript
+
interface TieredStorageConfig {
+
tiers: {
+
hot?: StorageTier; // Optional: fastest tier (memory/Redis)
+
warm?: StorageTier; // Optional: medium tier (disk/SQLite/Postgres)
+
cold: StorageTier; // Required: slowest tier (S3/object storage)
+
};
+
compression?: boolean; // Auto-compress before storing (default: false)
+
defaultTTL?: number; // Default TTL in milliseconds
+
promotionStrategy?: 'eager' | 'lazy'; // When to promote to upper tiers (default: 'lazy')
+
serialization?: { // Custom serialization (default: JSON)
+
serialize: (data: unknown) => Promise<Uint8Array>;
+
deserialize: (data: Uint8Array) => Promise<unknown>;
+
};
+
}
+
```
+
+
#### Methods
+
+
**`get(key: string): Promise<T | null>`**
+
+
Retrieve data for a key. Returns null if not found or expired.
+
+
**`getWithMetadata(key: string): Promise<StorageResult<T> | null>`**
+
+
Retrieve data with metadata and source tier information.
+
+
```typescript
+
const result = await storage.getWithMetadata('user:123');
+
console.log(result.data); // The actual data
+
console.log(result.source); // 'hot' | 'warm' | 'cold'
+
console.log(result.metadata); // Metadata (size, timestamps, TTL, etc.)
+
```
+
+
**`set(key: string, data: T, options?: SetOptions): Promise<SetResult>`**
+
+
Store data with optional configuration.
+
+
```typescript
+
await storage.set('key', data, {
+
ttl: 24 * 60 * 60 * 1000, // Custom TTL (24 hours)
+
metadata: { contentType: 'application/json' }, // Custom metadata
+
skipTiers: ['hot'], // Skip specific tiers
+
});
+
```
+
+
**`delete(key: string): Promise<void>`**
+
+
Delete data from all tiers.
+
+
**`exists(key: string): Promise<boolean>`**
+
+
Check if a key exists (and hasn't expired).
+
+
**`touch(key: string, ttlMs?: number): Promise<void>`**
+
+
Renew TTL for a key. Useful for "keep alive" behavior.
+
+
**`invalidate(prefix: string): Promise<number>`**
+
+
Delete all keys matching a prefix. Returns number of keys deleted.
+
+
```typescript
+
await storage.invalidate('user:'); // Delete all user keys
+
await storage.invalidate('site:abc/'); // Delete all files for site 'abc'
+
await storage.invalidate(''); // Delete everything
+
```
+
+
**`listKeys(prefix?: string): AsyncIterableIterator<string>`**
+
+
List all keys, optionally filtered by prefix.
+
+
```typescript
+
for await (const key of storage.listKeys('user:')) {
+
console.log(key); // 'user:123', 'user:456', etc.
+
}
+
```
+
+
**`getStats(): Promise<AllTierStats>`**
+
+
Get aggregated statistics across all tiers.
+
+
```typescript
+
const stats = await storage.getStats();
+
console.log(stats.hot); // Hot tier stats (size, items, hits, misses)
+
console.log(stats.hitRate); // Overall hit rate (0-1)
+
```
+
+
**`bootstrapHot(limit?: number): Promise<number>`**
+
+
Load most frequently accessed items from warm into hot. Returns number of items loaded.
+
+
```typescript
+
// On server startup: warm up hot tier
+
const loaded = await storage.bootstrapHot(1000); // Load top 1000 items
+
console.log(`Loaded ${loaded} items into hot tier`);
+
```
+
+
**`bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number>`**
+
+
Load recent items from cold into warm. Returns number of items loaded.
+
+
```typescript
+
// Load items accessed in last 7 days
+
const loaded = await storage.bootstrapWarm({
+
sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
+
limit: 10000,
+
});
+
```
+
+
**`export(): Promise<StorageSnapshot>`**
+
+
Export metadata snapshot for backup or migration.
+
+
**`import(snapshot: StorageSnapshot): Promise<void>`**
+
+
Import metadata snapshot.
+
+
**`clear(): Promise<void>`**
+
+
Clear all data from all tiers. ⚠️ Use with extreme caution!
+
+
**`clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void>`**
+
+
Clear a specific tier.
+
+
### Built-in Storage Tiers
+
+
#### `MemoryStorageTier`
+
+
In-memory storage using TinyLRU for efficient LRU eviction.
+
+
```typescript
+
import { MemoryStorageTier } from 'tiered-storage';
+
+
const tier = new MemoryStorageTier({
+
maxSizeBytes: 100 * 1024 * 1024, // 100MB
+
maxItems: 1000, // Optional: max number of items
+
});
+
```
+
+
**Features:**
+
- Battle-tested TinyLRU library
+
- Automatic LRU eviction
+
- Size-based and count-based limits
+
- Single process only (not distributed)
+
+
#### `DiskStorageTier`
+
+
Filesystem-based storage with `.meta` files.
+
+
```typescript
+
import { DiskStorageTier } from 'tiered-storage';
+
+
const tier = new DiskStorageTier({
+
directory: './cache',
+
maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB (optional)
+
evictionPolicy: 'lru', // 'lru' | 'fifo' | 'size'
+
});
+
```
+
+
**Features:**
+
- Human-readable file structure
+
- Optional size-based eviction
+
- Three eviction policies: LRU, FIFO, size-based
+
- Atomic writes with `.meta` files
+
- Zero external dependencies
+
+
**File structure:**
+
```
+
cache/
+
├── user%3A123 # Data file (encoded key)
+
├── user%3A123.meta # Metadata JSON
+
├── site%3Aabc%2Findex.html
+
└── site%3Aabc%2Findex.html.meta
+
```
+
+
#### `S3StorageTier`
+
+
AWS S3 or S3-compatible object storage.
+
+
```typescript
+
import { S3StorageTier } from 'tiered-storage';
+
+
// AWS S3 with separate metadata bucket (RECOMMENDED!)
+
const tier = new S3StorageTier({
+
bucket: 'my-data-bucket',
+
metadataBucket: 'my-metadata-bucket', // Stores metadata separately for fast updates
+
region: 'us-east-1',
+
credentials: {
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
+
},
+
prefix: 'cache/', // Optional key prefix
+
});
+
+
// Cloudflare R2 with metadata bucket
+
const r2Tier = new S3StorageTier({
+
bucket: 'my-r2-data-bucket',
+
metadataBucket: 'my-r2-metadata-bucket',
+
region: 'auto',
+
endpoint: 'https://account-id.r2.cloudflarestorage.com',
+
credentials: {
+
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
+
},
+
});
+
+
// Without metadata bucket (legacy mode - slower, more expensive)
+
const legacyTier = new S3StorageTier({
+
bucket: 'my-bucket',
+
region: 'us-east-1',
+
// No metadataBucket - metadata stored in S3 object metadata fields
+
});
+
```
+
+
**Features:**
+
- Compatible with AWS S3, Cloudflare R2, MinIO, and other S3-compatible services
+
- **Separate metadata bucket support (RECOMMENDED)** - stores metadata as JSON objects for fast, cheap updates
+
- Legacy mode: metadata in S3 object metadata fields (requires object copying for updates)
+
- Efficient batch deletions (up to 1000 keys per request)
+
- Optional key prefixing for multi-tenant scenarios
+
- Typically used as cold tier (source of truth)
+
+
**⚠️ 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!
+
+
## Usage Patterns
+
+
### Pattern 1: Simple Single-Server Setup
+
+
```typescript
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage';
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: './cache' }),
+
cold: new DiskStorageTier({ directory: './storage' }),
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
+
});
+
+
await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' });
+
const user = await storage.get('user:123');
+
```
+
+
### Pattern 2: Static Site Hosting (wisp.place-style)
+
+
```typescript
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage';
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({
+
maxSizeBytes: 100 * 1024 * 1024, // 100MB
+
maxItems: 500,
+
}),
+
warm: new DiskStorageTier({
+
directory: './cache/sites',
+
maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB
+
}),
+
// Cold tier is PDS (fetched on demand via custom tier implementation)
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
+
promotionStrategy: 'lazy', // Don't auto-promote large files to hot
+
});
+
+
// Store index.html in all tiers (fast access)
+
await storage.set(`${did}/${rkey}/index.html`, htmlBuffer, {
+
metadata: { mimeType: 'text/html', encoding: 'gzip' },
+
});
+
+
// Store large files only in warm + cold (skip hot)
+
await storage.set(`${did}/${rkey}/video.mp4`, videoBuffer, {
+
skipTiers: ['hot'],
+
metadata: { mimeType: 'video/mp4' },
+
});
+
+
// Get file with source tracking
+
const result = await storage.getWithMetadata(`${did}/${rkey}/index.html`);
+
console.log(`Served from ${result.source}`); // Likely 'hot' for index.html
+
+
// Invalidate entire site
+
await storage.invalidate(`${did}/${rkey}/`);
+
+
// Renew TTL when site is accessed
+
await storage.touch(`${did}/${rkey}/index.html`);
+
```
+
+
### Pattern 3: Custom Backend (SQLite)
+
+
Implement the `StorageTier` interface to use any backend:
+
+
```typescript
+
import { StorageTier, StorageMetadata, TierStats } from 'tiered-storage';
+
import Database from 'better-sqlite3';
+
+
class SQLiteStorageTier implements StorageTier {
+
private db: Database.Database;
+
+
constructor(dbPath: string) {
+
this.db = new Database(dbPath);
+
this.db.exec(`
+
CREATE TABLE IF NOT EXISTS cache (
+
key TEXT PRIMARY KEY,
+
data BLOB NOT NULL,
+
metadata TEXT NOT NULL
+
)
+
`);
+
}
+
+
async get(key: string): Promise<Uint8Array | null> {
+
const row = this.db.prepare('SELECT data FROM cache WHERE key = ?').get(key);
+
return row ? new Uint8Array(row.data) : null;
+
}
+
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
+
this.db.prepare('INSERT OR REPLACE INTO cache (key, data, metadata) VALUES (?, ?, ?)')
+
.run(key, Buffer.from(data), JSON.stringify(metadata));
+
}
+
+
async delete(key: string): Promise<void> {
+
this.db.prepare('DELETE FROM cache WHERE key = ?').run(key);
+
}
+
+
async exists(key: string): Promise<boolean> {
+
const row = this.db.prepare('SELECT 1 FROM cache WHERE key = ?').get(key);
+
return !!row;
+
}
+
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
+
const query = prefix
+
? this.db.prepare('SELECT key FROM cache WHERE key LIKE ?')
+
: this.db.prepare('SELECT key FROM cache');
+
+
const rows = prefix ? query.all(`${prefix}%`) : query.all();
+
+
for (const row of rows) {
+
yield row.key;
+
}
+
}
+
+
async deleteMany(keys: string[]): Promise<void> {
+
const placeholders = keys.map(() => '?').join(',');
+
this.db.prepare(`DELETE FROM cache WHERE key IN (${placeholders})`).run(...keys);
+
}
+
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
+
const row = this.db.prepare('SELECT metadata FROM cache WHERE key = ?').get(key);
+
return row ? JSON.parse(row.metadata) : null;
+
}
+
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
+
this.db.prepare('UPDATE cache SET metadata = ? WHERE key = ?')
+
.run(JSON.stringify(metadata), key);
+
}
+
+
async getStats(): Promise<TierStats> {
+
const row = this.db.prepare('SELECT COUNT(*) as count, SUM(LENGTH(data)) as bytes FROM cache').get();
+
return { items: row.count, bytes: row.bytes || 0 };
+
}
+
+
async clear(): Promise<void> {
+
this.db.prepare('DELETE FROM cache').run();
+
}
+
}
+
+
// Use it
+
const storage = new TieredStorage({
+
tiers: {
+
warm: new SQLiteStorageTier('./cache.db'),
+
cold: new DiskStorageTier({ directory: './storage' }),
+
},
+
});
+
```
+
+
## Running Examples
+
+
### Interactive Demo Server
+
+
Run a **real HTTP server** that serves the example site using tiered storage:
+
+
```bash
+
# Configure S3 credentials first (copy .env.example to .env and fill in)
+
cp .env.example .env
+
+
# Start the demo server
+
bun run serve
+
```
+
+
Then visit:
+
- **http://localhost:3000/** - The demo site served from tiered storage
+
- **http://localhost:3000/admin/stats** - Live cache statistics dashboard
+
+
Watch the console to see which tier serves each request:
+
- 🔥 **Hot tier (memory)** - index.html served instantly
+
- 💾 **Warm tier (disk)** - Other pages served from disk cache
+
- ☁️ **Cold tier (S3)** - First access fetches from S3, then cached
+
+
### Command-Line Examples
+
+
Or run the non-interactive examples:
+
+
```bash
+
bun run example
+
```
+
+
The examples include:
+
- **Basic CRUD operations** with statistics tracking
+
- **Static site hosting** using the real site in `example-site/` directory
+
- **Bootstrap demonstrations** (warming caches from lower tiers)
+
- **Promotion strategy comparisons** (eager vs lazy)
+
+
The `example-site/` directory contains a complete static website with:
+
- `index.html` - Stored in hot + warm + cold (instant serving)
+
- `about.html`, `docs.html` - Stored in warm + cold (skips hot)
+
- `style.css`, `script.js` - Stored in warm + cold (skips hot)
+
+
This demonstrates the exact pattern you'd use for wisp.place: critical files in memory, everything else on disk/S3.
+
+
## Testing
+
+
```bash
+
bun test
+
```
+
+
## Development
+
+
```bash
+
# Install dependencies
+
bun install
+
+
# Type check
+
bun run check
+
+
# Build
+
bun run build
+
+
# Run tests
+
bun test
+
+
```
+
## License
+
+
MIT
+27
agents.md
···
+
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.
+
+
## Package management
+
- Use bun for package management (bun install)
+
- Use npm run test to test
+
- Use npm run lint to lint
+
- Use npm run check to typecheck
+
+
Please test and typecheck always whenever you think you are done.
+
+
## Code style
+
- Use tabs for indentation, spaces allowed for diagrams in comments
+
- Use single quotes and add trailing commas
+
- Use template literals for user-facing strings and error messages
+
+
## Commenting
+
Add JSDoc comments to all new exported functions, methods, classes, fields, and enums
+
JSDoc should include proper annotations:
+
- use @param for parameters (no dashes after param names)
+
- use @returns for return values
+
- use @throws for exceptions when applicable
+
- keep descriptions concise but informative
+
+
## Misc
+
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
+
+
**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
···
+
{
+
"lockfileVersion": 1,
+
"configVersion": 1,
+
"workspaces": {
+
"": {
+
"name": "tiered-storage",
+
"dependencies": {
+
"@aws-sdk/client-s3": "^3.500.0",
+
"hono": "^4.10.7",
+
"mime-types": "^3.0.2",
+
"tiny-lru": "^11.0.0",
+
},
+
"devDependencies": {
+
"@types/node": "^24.10.1",
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
+
"@typescript-eslint/parser": "^8.48.1",
+
"eslint": "^8.0.0",
+
"tsx": "^4.0.0",
+
"typescript": "^5.3.0",
+
"vitest": "^4.0.15",
+
},
+
},
+
},
+
"packages": {
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="],
+
+
"@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=="],
+
+
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="],
+
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="],
+
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="],
+
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.1", "", { "os": "android", "cpu": "arm64" }, "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ=="],
+
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.1", "", { "os": "android", "cpu": "x64" }, "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ=="],
+
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ=="],
+
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ=="],
+
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg=="],
+
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ=="],
+
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA=="],
+
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q=="],
+
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw=="],
+
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg=="],
+
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA=="],
+
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ=="],
+
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ=="],
+
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw=="],
+
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA=="],
+
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ=="],
+
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.1", "", { "os": "none", "cpu": "x64" }, "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg=="],
+
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g=="],
+
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg=="],
+
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg=="],
+
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA=="],
+
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg=="],
+
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ=="],
+
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
+
+
"@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=="],
+
+
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
+
+
"@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=="],
+
+
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
+
+
"@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=="],
+
+
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
+
+
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
+
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+
"@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=="],
+
+
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="],
+
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="],
+
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="],
+
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="],
+
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="],
+
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="],
+
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="],
+
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="],
+
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="],
+
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="],
+
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="],
+
+
"@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=="],
+
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="],
+
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="],
+
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="],
+
+
"@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=="],
+
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="],
+
+
"@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=="],
+
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="],
+
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="],
+
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="],
+
+
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="],
+
+
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="],
+
+
"@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=="],
+
+
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="],
+
+
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
+
+
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
+
+
"@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=="],
+
+
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
+
+
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
+
+
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
+
+
"@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=="],
+
+
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
+
+
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+
+
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
+
+
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@vitest/pretty-format": ["@vitest/pretty-format@4.0.15", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A=="],
+
+
"@vitest/runner": ["@vitest/runner@4.0.15", "", { "dependencies": { "@vitest/utils": "4.0.15", "pathe": "^2.0.3" } }, "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw=="],
+
+
"@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=="],
+
+
"@vitest/spy": ["@vitest/spy@4.0.15", "", {}, "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw=="],
+
+
"@vitest/utils": ["@vitest/utils@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" } }, "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA=="],
+
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
+
+
"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=="],
+
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+
"bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
+
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+
+
"chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
+
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+
+
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
+
+
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
+
+
"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=="],
+
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
+
"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=="],
+
+
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
+
+
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+
"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=="],
+
+
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
+
+
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
+
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
+
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
+
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+
+
"fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
+
+
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
+
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
+
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
+
+
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
+
+
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+
+
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
+
+
"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=="],
+
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+
+
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
+
+
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
+
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
+
+
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
+
+
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
+
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
+
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+
+
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
+
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
+
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+
+
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+
+
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
+
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
+
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
+
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+
+
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
+
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+
"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=="],
+
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+
+
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
+
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+
+
"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=="],
+
+
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+
+
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
+
"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=="],
+
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
+
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
+
+
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
+
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
+
"strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="],
+
+
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
+
+
"tiny-lru": ["tiny-lru@11.4.5", "", {}, "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw=="],
+
+
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
+
+
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
+
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
+
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
+
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+
"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=="],
+
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+
+
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
+
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+
+
"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=="],
+
+
"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=="],
+
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+
"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=="],
+
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
+
+
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+
"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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
+
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+
"vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+
"vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+
"vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+
"vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+
"vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+
"vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+
"vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+
"vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+
"vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+
"vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+
"vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+
"vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+
"vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+
"vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+
"vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+
"vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+
"vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+
"vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+
"vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+
"vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+
"vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+
"vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+
"vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+
"@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=="],
+
+
"@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=="],
+
+
"@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=="],
+
}
+
}
+47
example-site/README.md
···
+
# Example Static Site
+
+
This is a demonstration static website used in the tiered-storage library examples.
+
+
## Files
+
+
- **index.html** (3.5 KB) - Homepage, stored in hot + warm + cold tiers
+
- **about.html** (4.2 KB) - About page, stored in warm + cold (skips hot)
+
- **docs.html** (3.8 KB) - Documentation, stored in warm + cold (skips hot)
+
- **style.css** (7.1 KB) - Stylesheet, stored in warm + cold (skips hot)
+
- **script.js** (1.9 KB) - JavaScript, stored in warm + cold (skips hot)
+
+
## Usage in Examples
+
+
The `example.ts` file demonstrates how this site would be stored using the tiered-storage library:
+
+
1. **index.html** is stored in all tiers (hot + warm + cold) because it's the entry point and needs instant serving
+
2. Other HTML pages skip the hot tier to save memory
+
3. CSS and JS files are stored in warm + cold
+
4. If there were large media files, they would be stored in cold tier only
+
+
## Tier Strategy
+
+
```
+
Hot Tier (Memory - 100MB):
+
└── index.html (3.5 KB)
+
+
Warm Tier (Disk - 10GB):
+
├── index.html (3.5 KB)
+
├── about.html (4.2 KB)
+
├── docs.html (3.8 KB)
+
├── style.css (7.1 KB)
+
└── script.js (1.9 KB)
+
+
Cold Tier (S3 - Unlimited):
+
├── index.html (3.5 KB)
+
├── about.html (4.2 KB)
+
├── docs.html (3.8 KB)
+
├── style.css (7.1 KB)
+
└── script.js (1.9 KB)
+
```
+
+
This strategy ensures:
+
- Lightning-fast serving of the homepage (from memory)
+
- Efficient use of hot tier capacity
+
- All files are cached on disk for fast local access
+
- S3 acts as the source of truth for disaster recovery
+86
example-site/about.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>About - Tiered Storage Demo</title>
+
<link rel="stylesheet" href="style.css">
+
</head>
+
<body>
+
<header>
+
<nav>
+
<div class="logo">🗄️ TieredCache</div>
+
<ul>
+
<li><a href="index.html">Home</a></li>
+
<li><a href="about.html" class="active">About</a></li>
+
<li><a href="docs.html">Docs</a></li>
+
</ul>
+
</nav>
+
</header>
+
+
<main>
+
<section class="content">
+
<h1>About Tiered Storage</h1>
+
+
<h2>The Problem</h2>
+
<p>Modern applications need to balance three competing concerns:</p>
+
<ul>
+
<li><strong>Speed:</strong> Users expect instant responses</li>
+
<li><strong>Cost:</strong> Keeping everything in memory is expensive</li>
+
<li><strong>Reliability:</strong> Data must be durable and recoverable</li>
+
</ul>
+
+
<h2>The Solution</h2>
+
<p>Tiered storage provides the best of all worlds by automatically managing data across multiple storage tiers:</p>
+
+
<div class="solution-grid">
+
<div class="solution-item">
+
<h3>🚀 Performance</h3>
+
<p>Hot tier (memory) serves critical files like index.html in microseconds</p>
+
</div>
+
<div class="solution-item">
+
<h3>💰 Cost-Effective</h3>
+
<p>Warm tier (disk) and cold tier (S3) handle bulk storage efficiently</p>
+
</div>
+
<div class="solution-item">
+
<h3>🛡️ Reliability</h3>
+
<p>Cold tier acts as source of truth with automatic backups</p>
+
</div>
+
</div>
+
+
<h2>Use Cases</h2>
+
<div class="use-cases">
+
<div class="use-case">
+
<h4>Static Site Hosting</h4>
+
<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>
+
</div>
+
<div class="use-case">
+
<h4>Content Delivery</h4>
+
<p>Automatically promote popular content to hot tier based on access patterns. Rarely accessed content stays in cold storage.</p>
+
</div>
+
<div class="use-case">
+
<h4>Database Caching</h4>
+
<p>Cache query results in memory, with overflow to disk and S3. Automatic TTL management keeps data fresh.</p>
+
</div>
+
</div>
+
+
<h2>How This Demo Works</h2>
+
<p>This example site is stored using the tiered-storage library:</p>
+
<ol>
+
<li><code>index.html</code> - Stored in all tiers (hot + warm + cold) for instant access</li>
+
<li><code>about.html</code> - Stored in warm + cold (skips hot to save memory)</li>
+
<li><code>style.css</code> - Stored in warm + cold</li>
+
<li><code>script.js</code> - Stored in warm + cold</li>
+
<li><code>hero-image.jpg</code> - Large file, stored in cold tier only</li>
+
</ol>
+
<p>When you request a page, the library automatically checks hot → warm → cold and serves from the fastest available tier.</p>
+
</section>
+
</main>
+
+
<footer>
+
<p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
+
</footer>
+
+
<script src="script.js"></script>
+
</body>
+
</html>
+105
example-site/docs.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Documentation - Tiered Storage</title>
+
<link rel="stylesheet" href="style.css">
+
</head>
+
<body>
+
<header>
+
<nav>
+
<div class="logo">🗄️ TieredCache</div>
+
<ul>
+
<li><a href="index.html">Home</a></li>
+
<li><a href="about.html">About</a></li>
+
<li><a href="docs.html" class="active">Docs</a></li>
+
</ul>
+
</nav>
+
</header>
+
+
<main>
+
<section class="content">
+
<h1>Quick Start Guide</h1>
+
+
<h2>Installation</h2>
+
<pre><code>npm install tiered-storage
+
# or
+
bun add tiered-storage</code></pre>
+
+
<h2>Basic Usage</h2>
+
<pre><code>import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage';
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: './cache' }),
+
cold: new S3StorageTier({
+
bucket: 'my-bucket',
+
region: 'us-east-1',
+
}),
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
+
});
+
+
// Store data
+
await storage.set('user:123', { name: 'Alice' });
+
+
// Retrieve data
+
const user = await storage.get('user:123');
+
+
// Invalidate by prefix
+
await storage.invalidate('user:');</code></pre>
+
+
<h2>Selective Tier Placement</h2>
+
<p>Control which tiers receive specific files:</p>
+
<pre><code>// Critical file - store in all tiers
+
await storage.set('index.html', htmlContent);
+
+
// Large file - skip hot tier to save memory
+
await storage.set('video.mp4', videoData, {
+
skipTiers: ['hot']
+
});</code></pre>
+
+
<h2>Bootstrap on Startup</h2>
+
<pre><code>// Warm up hot tier from warm tier
+
await storage.bootstrapHot(1000); // Load top 1000 items
+
+
// Warm up warm tier from cold tier
+
await storage.bootstrapWarm({
+
sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
+
limit: 10000,
+
});</code></pre>
+
+
<h2>Statistics & Monitoring</h2>
+
<pre><code>const stats = await storage.getStats();
+
console.log('Hot tier:', stats.hot);
+
console.log('Warm tier:', stats.warm);
+
console.log('Cold tier:', stats.cold);
+
console.log('Hit rate:', stats.hitRate);</code></pre>
+
+
<h2>API Reference</h2>
+
<ul>
+
<li><code>get(key)</code> - Retrieve data</li>
+
<li><code>getWithMetadata(key)</code> - Retrieve with metadata and source tier</li>
+
<li><code>set(key, data, options)</code> - Store data</li>
+
<li><code>delete(key)</code> - Delete from all tiers</li>
+
<li><code>exists(key)</code> - Check if key exists</li>
+
<li><code>touch(key, ttlMs)</code> - Renew TTL</li>
+
<li><code>invalidate(prefix)</code> - Delete by prefix</li>
+
<li><code>listKeys(prefix)</code> - List keys</li>
+
<li><code>getStats()</code> - Get statistics</li>
+
<li><code>bootstrapHot(limit)</code> - Warm up hot tier</li>
+
<li><code>bootstrapWarm(options)</code> - Warm up warm tier</li>
+
</ul>
+
</section>
+
</main>
+
+
<footer>
+
<p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
+
</footer>
+
+
<script src="script.js"></script>
+
</body>
+
</html>
+103
example-site/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Tiered Storage Demo Site</title>
+
<link rel="stylesheet" href="style.css">
+
</head>
+
<body>
+
<header>
+
<nav>
+
<div class="logo">🗄️ TieredCache</div>
+
<ul>
+
<li><a href="index.html" class="active">Home</a></li>
+
<li><a href="about.html">About</a></li>
+
<li><a href="docs.html">Docs</a></li>
+
</ul>
+
</nav>
+
</header>
+
+
<main>
+
<section class="hero">
+
<h1>Lightning-Fast Multi-Tier Caching</h1>
+
<p>Store your data across memory, disk, and cloud storage with automatic promotion and intelligent eviction.</p>
+
<div class="cta-buttons">
+
<a href="#features" class="btn btn-primary">Learn More</a>
+
<a href="docs.html" class="btn btn-secondary">Documentation</a>
+
</div>
+
</section>
+
+
<section id="features" class="features">
+
<h2>Features</h2>
+
<div class="feature-grid">
+
<div class="feature-card">
+
<div class="feature-icon">⚡</div>
+
<h3>Hot Tier (Memory)</h3>
+
<p>Lightning-fast access with LRU eviction. Perfect for frequently accessed data like index.html.</p>
+
</div>
+
<div class="feature-card">
+
<div class="feature-icon">💾</div>
+
<h3>Warm Tier (Disk)</h3>
+
<p>Fast local storage with configurable eviction policies. Ideal for site assets and media.</p>
+
</div>
+
<div class="feature-card">
+
<div class="feature-icon">☁️</div>
+
<h3>Cold Tier (S3)</h3>
+
<p>Unlimited cloud storage as your source of truth. Supports S3, R2, MinIO, and more.</p>
+
</div>
+
</div>
+
</section>
+
+
<section class="architecture">
+
<h2>How It Works</h2>
+
<div class="tier-diagram">
+
<div class="tier hot">
+
<div class="tier-label">Hot (Memory)</div>
+
<div class="tier-content">index.html ✓</div>
+
</div>
+
<div class="arrow">↓</div>
+
<div class="tier warm">
+
<div class="tier-label">Warm (Disk)</div>
+
<div class="tier-content">index.html, style.css, images ✓</div>
+
</div>
+
<div class="arrow">↓</div>
+
<div class="tier cold">
+
<div class="tier-label">Cold (S3)</div>
+
<div class="tier-content">All files (source of truth) ✓</div>
+
</div>
+
</div>
+
<p class="diagram-note">Data cascades down on writes, bubbles up on reads</p>
+
</section>
+
+
<section class="stats" id="cache-stats">
+
<h2>Live Cache Statistics</h2>
+
<div class="stats-grid">
+
<div class="stat-card">
+
<div class="stat-value" id="hot-items">-</div>
+
<div class="stat-label">Hot Tier Items</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-value" id="warm-items">-</div>
+
<div class="stat-label">Warm Tier Items</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-value" id="cold-items">-</div>
+
<div class="stat-label">Cold Tier Items</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-value" id="hit-rate">-</div>
+
<div class="stat-label">Cache Hit Rate</div>
+
</div>
+
</div>
+
</section>
+
</main>
+
+
<footer>
+
<p>&copy; 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
+
<p><small>This is a demo site to showcase the tiered-storage library capabilities.</small></p>
+
</footer>
+
+
<script src="script.js"></script>
+
</body>
+
</html>
+84
example-site/script.js
···
+
/**
+
* Tiered Storage Demo Site - Client-side JavaScript
+
*
+
* This script demonstrates how a static site can interact with
+
* the tiered storage system (in a real scenario, stats would be
+
* fetched from a backend API that uses the storage library)
+
*/
+
+
// Simulated cache statistics
+
// In a real implementation, this would fetch from your backend
+
function simulateCacheStats() {
+
return {
+
hot: {
+
items: 1,
+
bytes: 3547,
+
hits: 42,
+
misses: 3,
+
},
+
warm: {
+
items: 5,
+
bytes: 127438,
+
hits: 15,
+
misses: 2,
+
},
+
cold: {
+
items: 5,
+
bytes: 127438,
+
},
+
totalHits: 57,
+
totalMisses: 5,
+
hitRate: 0.919,
+
};
+
}
+
+
// Update stats display
+
function updateStatsDisplay() {
+
const stats = simulateCacheStats();
+
+
const hotItems = document.getElementById('hot-items');
+
const warmItems = document.getElementById('warm-items');
+
const coldItems = document.getElementById('cold-items');
+
const hitRate = document.getElementById('hit-rate');
+
+
if (hotItems) hotItems.textContent = stats.hot.items;
+
if (warmItems) warmItems.textContent = stats.warm.items;
+
if (coldItems) coldItems.textContent = stats.cold.items;
+
if (hitRate) hitRate.textContent = `${(stats.hitRate * 100).toFixed(1)}%`;
+
}
+
+
// Smooth scrolling for anchor links
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
+
anchor.addEventListener('click', function (e) {
+
e.preventDefault();
+
const target = document.querySelector(this.getAttribute('href'));
+
if (target) {
+
target.scrollIntoView({
+
behavior: 'smooth',
+
block: 'start'
+
});
+
}
+
});
+
});
+
+
// Initialize stats when page loads
+
if (document.readyState === 'loading') {
+
document.addEventListener('DOMContentLoaded', updateStatsDisplay);
+
} else {
+
updateStatsDisplay();
+
}
+
+
// Update stats periodically (simulate real-time updates)
+
setInterval(updateStatsDisplay, 5000);
+
+
// Add active class to navigation based on current page
+
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
+
document.querySelectorAll('nav a').forEach(link => {
+
if (link.getAttribute('href') === currentPage) {
+
link.classList.add('active');
+
}
+
});
+
+
// Log page view (in real app, would send to analytics)
+
console.log(`[TieredCache] Page viewed: ${currentPage}`);
+
console.log(`[TieredCache] This page was likely served from ${currentPage === 'index.html' ? 'hot tier (memory)' : 'warm tier (disk)'}`);
+439
example-site/style.css
···
+
/* Tiered Storage Demo Site - Stylesheet */
+
+
:root {
+
--primary-color: #3b82f6;
+
--secondary-color: #8b5cf6;
+
--success-color: #10b981;
+
--background: #0f172a;
+
--surface: #1e293b;
+
--text: #f1f5f9;
+
--text-muted: #94a3b8;
+
--border: #334155;
+
}
+
+
* {
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
}
+
+
body {
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+
background: var(--background);
+
color: var(--text);
+
line-height: 1.6;
+
min-height: 100vh;
+
display: flex;
+
flex-direction: column;
+
}
+
+
/* Header & Navigation */
+
header {
+
background: var(--surface);
+
border-bottom: 1px solid var(--border);
+
position: sticky;
+
top: 0;
+
z-index: 100;
+
}
+
+
nav {
+
max-width: 1200px;
+
margin: 0 auto;
+
padding: 1rem 2rem;
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
}
+
+
.logo {
+
font-size: 1.5rem;
+
font-weight: 700;
+
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
background-clip: text;
+
}
+
+
nav ul {
+
display: flex;
+
gap: 2rem;
+
list-style: none;
+
}
+
+
nav a {
+
color: var(--text-muted);
+
text-decoration: none;
+
transition: color 0.2s;
+
padding: 0.5rem 1rem;
+
border-radius: 0.5rem;
+
}
+
+
nav a:hover,
+
nav a.active {
+
color: var(--text);
+
background: rgba(59, 130, 246, 0.1);
+
}
+
+
/* Main Content */
+
main {
+
flex: 1;
+
max-width: 1200px;
+
margin: 0 auto;
+
padding: 2rem;
+
width: 100%;
+
}
+
+
/* Hero Section */
+
.hero {
+
text-align: center;
+
padding: 4rem 2rem;
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1));
+
border-radius: 1rem;
+
margin-bottom: 3rem;
+
}
+
+
.hero h1 {
+
font-size: 3rem;
+
margin-bottom: 1rem;
+
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
background-clip: text;
+
}
+
+
.hero p {
+
font-size: 1.25rem;
+
color: var(--text-muted);
+
max-width: 600px;
+
margin: 0 auto 2rem;
+
}
+
+
.cta-buttons {
+
display: flex;
+
gap: 1rem;
+
justify-content: center;
+
}
+
+
.btn {
+
padding: 0.75rem 2rem;
+
border-radius: 0.5rem;
+
text-decoration: none;
+
font-weight: 600;
+
transition: all 0.2s;
+
display: inline-block;
+
}
+
+
.btn-primary {
+
background: var(--primary-color);
+
color: white;
+
}
+
+
.btn-primary:hover {
+
background: #2563eb;
+
transform: translateY(-2px);
+
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
+
}
+
+
.btn-secondary {
+
background: var(--surface);
+
color: var(--text);
+
border: 1px solid var(--border);
+
}
+
+
.btn-secondary:hover {
+
background: var(--border);
+
}
+
+
/* Features */
+
.features {
+
margin-bottom: 3rem;
+
}
+
+
.features h2 {
+
text-align: center;
+
font-size: 2rem;
+
margin-bottom: 2rem;
+
}
+
+
.feature-grid {
+
display: grid;
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+
gap: 2rem;
+
}
+
+
.feature-card {
+
background: var(--surface);
+
padding: 2rem;
+
border-radius: 1rem;
+
border: 1px solid var(--border);
+
transition: transform 0.2s, box-shadow 0.2s;
+
}
+
+
.feature-card:hover {
+
transform: translateY(-4px);
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+
}
+
+
.feature-icon {
+
font-size: 3rem;
+
margin-bottom: 1rem;
+
}
+
+
.feature-card h3 {
+
color: var(--primary-color);
+
margin-bottom: 0.5rem;
+
}
+
+
.feature-card p {
+
color: var(--text-muted);
+
}
+
+
/* Architecture Diagram */
+
.architecture {
+
margin-bottom: 3rem;
+
}
+
+
.architecture h2 {
+
text-align: center;
+
font-size: 2rem;
+
margin-bottom: 2rem;
+
}
+
+
.tier-diagram {
+
max-width: 600px;
+
margin: 0 auto;
+
}
+
+
.tier {
+
background: var(--surface);
+
border: 2px solid var(--border);
+
border-radius: 0.5rem;
+
padding: 1.5rem;
+
margin-bottom: 1rem;
+
}
+
+
.tier.hot {
+
border-color: #ef4444;
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), var(--surface));
+
}
+
+
.tier.warm {
+
border-color: #f59e0b;
+
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), var(--surface));
+
}
+
+
.tier.cold {
+
border-color: var(--primary-color);
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), var(--surface));
+
}
+
+
.tier-label {
+
font-weight: 700;
+
margin-bottom: 0.5rem;
+
font-size: 1.1rem;
+
}
+
+
.tier-content {
+
color: var(--text-muted);
+
font-size: 0.9rem;
+
}
+
+
.arrow {
+
text-align: center;
+
font-size: 2rem;
+
color: var(--text-muted);
+
margin: -0.5rem 0;
+
}
+
+
.diagram-note {
+
text-align: center;
+
color: var(--text-muted);
+
font-style: italic;
+
margin-top: 1rem;
+
}
+
+
/* Stats */
+
.stats {
+
margin-bottom: 3rem;
+
}
+
+
.stats h2 {
+
text-align: center;
+
font-size: 2rem;
+
margin-bottom: 2rem;
+
}
+
+
.stats-grid {
+
display: grid;
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+
gap: 1.5rem;
+
}
+
+
.stat-card {
+
background: var(--surface);
+
padding: 2rem;
+
border-radius: 1rem;
+
border: 1px solid var(--border);
+
text-align: center;
+
}
+
+
.stat-value {
+
font-size: 2.5rem;
+
font-weight: 700;
+
color: var(--primary-color);
+
margin-bottom: 0.5rem;
+
}
+
+
.stat-label {
+
color: var(--text-muted);
+
font-size: 0.9rem;
+
}
+
+
/* Content Pages */
+
.content {
+
max-width: 800px;
+
margin: 0 auto;
+
}
+
+
.content h1 {
+
font-size: 2.5rem;
+
margin-bottom: 1.5rem;
+
color: var(--primary-color);
+
}
+
+
.content h2 {
+
font-size: 1.8rem;
+
margin-top: 2rem;
+
margin-bottom: 1rem;
+
}
+
+
.content h3 {
+
font-size: 1.3rem;
+
margin-top: 1.5rem;
+
margin-bottom: 0.5rem;
+
}
+
+
.content h4 {
+
font-size: 1.1rem;
+
margin-top: 1rem;
+
margin-bottom: 0.5rem;
+
color: var(--primary-color);
+
}
+
+
.content p {
+
margin-bottom: 1rem;
+
color: var(--text-muted);
+
}
+
+
.content ul, .content ol {
+
margin-bottom: 1rem;
+
margin-left: 2rem;
+
color: var(--text-muted);
+
}
+
+
.content li {
+
margin-bottom: 0.5rem;
+
}
+
+
.content code {
+
background: var(--surface);
+
padding: 0.2rem 0.5rem;
+
border-radius: 0.25rem;
+
font-family: 'Monaco', 'Courier New', monospace;
+
font-size: 0.9em;
+
color: var(--success-color);
+
}
+
+
.content pre {
+
background: var(--surface);
+
padding: 1.5rem;
+
border-radius: 0.5rem;
+
overflow-x: auto;
+
margin-bottom: 1rem;
+
border: 1px solid var(--border);
+
}
+
+
.content pre code {
+
background: none;
+
padding: 0;
+
color: var(--text);
+
}
+
+
.solution-grid {
+
display: grid;
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+
gap: 1.5rem;
+
margin: 2rem 0;
+
}
+
+
.solution-item {
+
background: var(--surface);
+
padding: 1.5rem;
+
border-radius: 0.5rem;
+
border: 1px solid var(--border);
+
}
+
+
.solution-item h3 {
+
margin-top: 0;
+
}
+
+
.use-cases {
+
display: flex;
+
flex-direction: column;
+
gap: 1.5rem;
+
margin-top: 2rem;
+
}
+
+
.use-case {
+
background: var(--surface);
+
padding: 1.5rem;
+
border-radius: 0.5rem;
+
border-left: 4px solid var(--primary-color);
+
}
+
+
.use-case h4 {
+
margin-top: 0;
+
}
+
+
/* Footer */
+
footer {
+
background: var(--surface);
+
border-top: 1px solid var(--border);
+
padding: 2rem;
+
text-align: center;
+
color: var(--text-muted);
+
margin-top: auto;
+
}
+
+
footer p {
+
margin: 0.5rem 0;
+
}
+
+
/* Responsive */
+
@media (max-width: 768px) {
+
.hero h1 {
+
font-size: 2rem;
+
}
+
+
.hero p {
+
font-size: 1rem;
+
}
+
+
nav ul {
+
gap: 1rem;
+
}
+
+
.feature-grid,
+
.stats-grid {
+
grid-template-columns: 1fr;
+
}
+
+
.solution-grid {
+
grid-template-columns: 1fr;
+
}
+
+
.cta-buttons {
+
flex-direction: column;
+
align-items: stretch;
+
}
+
}
+434
example.ts
···
+
/**
+
* Example usage of the tiered-storage library
+
*
+
* Run with: bun run example
+
*
+
* Note: This example uses S3 for cold storage. You'll need to configure
+
* AWS credentials and an S3 bucket in .env (see .env.example)
+
*/
+
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js';
+
import { rm } from 'node:fs/promises';
+
+
// Configuration from environment variables
+
const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example';
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false'; // Default true
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
+
+
async function basicExample() {
+
console.log('\n=== Basic Example ===\n');
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }), // 10MB
+
warm: new DiskStorageTier({ directory: './example-cache/basic/warm' }),
+
cold: new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'example/basic/',
+
}),
+
},
+
compression: true,
+
defaultTTL: 60 * 60 * 1000, // 1 hour
+
});
+
+
// Store some data
+
console.log('Storing user data...');
+
await storage.set('user:alice', {
+
name: 'Alice',
+
email: 'alice@example.com',
+
role: 'admin',
+
});
+
+
await storage.set('user:bob', {
+
name: 'Bob',
+
email: 'bob@example.com',
+
role: 'user',
+
});
+
+
// Retrieve with metadata
+
const result = await storage.getWithMetadata('user:alice');
+
if (result) {
+
console.log(`Retrieved user:alice from ${result.source} tier:`);
+
console.log(result.data);
+
console.log('Metadata:', {
+
size: result.metadata.size,
+
compressed: result.metadata.compressed,
+
accessCount: result.metadata.accessCount,
+
});
+
}
+
+
// Get statistics
+
const stats = await storage.getStats();
+
console.log('\nStorage Statistics:');
+
console.log(`Hot tier: ${stats.hot?.items} items, ${stats.hot?.bytes} bytes`);
+
console.log(`Warm tier: ${stats.warm?.items} items, ${stats.warm?.bytes} bytes`);
+
console.log(`Cold tier (S3): ${stats.cold.items} items, ${stats.cold.bytes} bytes`);
+
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(2)}%`);
+
+
// List all keys with prefix
+
console.log('\nAll user keys:');
+
for await (const key of storage.listKeys('user:')) {
+
console.log(` - ${key}`);
+
}
+
+
// Invalidate by prefix
+
console.log('\nInvalidating all user keys...');
+
const deleted = await storage.invalidate('user:');
+
console.log(`Deleted ${deleted} keys`);
+
}
+
+
async function staticSiteHostingExample() {
+
console.log('\n=== Static Site Hosting Example (wisp.place pattern) ===\n');
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({
+
maxSizeBytes: 50 * 1024 * 1024, // 50MB
+
maxItems: 500,
+
}),
+
warm: new DiskStorageTier({
+
directory: './example-cache/sites/warm',
+
maxSizeBytes: 1024 * 1024 * 1024, // 1GB
+
}),
+
cold: new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'example/sites/',
+
}),
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
+
promotionStrategy: 'lazy', // Don't auto-promote large files
+
});
+
+
const siteId = 'did:plc:abc123';
+
const siteName = 'tiered-cache-demo';
+
+
console.log('Loading real static site from example-site/...\n');
+
+
// Load actual site files
+
const { readFile } = await import('node:fs/promises');
+
+
const files = [
+
{ name: 'index.html', skipTiers: [], mimeType: 'text/html' },
+
{ name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' },
+
{ name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' },
+
{ name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' },
+
{ name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' },
+
];
+
+
console.log('Storing site files with selective tier placement:\n');
+
+
for (const file of files) {
+
const content = await readFile(`./example-site/${file.name}`, 'utf-8');
+
const key = `${siteId}/${siteName}/${file.name}`;
+
+
await storage.set(key, content, {
+
skipTiers: file.skipTiers as ('hot' | 'warm')[],
+
metadata: { mimeType: file.mimeType },
+
});
+
+
const tierInfo =
+
file.skipTiers.length === 0
+
? 'hot + warm + cold (S3)'
+
: `warm + cold (S3) - skipped ${file.skipTiers.join(', ')}`;
+
const sizeKB = (content.length / 1024).toFixed(2);
+
console.log(`✓ ${file.name} (${sizeKB} KB) → ${tierInfo}`);
+
}
+
+
// Check where each file is served from
+
console.log('\nServing files (checking which tier):');
+
for (const file of files) {
+
const result = await storage.getWithMetadata(`${siteId}/${siteName}/${file.name}`);
+
if (result) {
+
const sizeKB = (result.metadata.size / 1024).toFixed(2);
+
console.log(` ${file.name}: served from ${result.source} (${sizeKB} KB)`);
+
}
+
}
+
+
// Show hot tier only has index.html
+
console.log('\nHot tier contents (should only contain index.html):');
+
const stats = await storage.getStats();
+
console.log(` Items: ${stats.hot?.items}`);
+
console.log(` Size: ${((stats.hot?.bytes ?? 0) / 1024).toFixed(2)} KB`);
+
console.log(` Files: index.html only`);
+
+
console.log('\nWarm tier contents (all site files):');
+
console.log(` Items: ${stats.warm?.items}`);
+
console.log(` Size: ${((stats.warm?.bytes ?? 0) / 1024).toFixed(2)} KB`);
+
console.log(` Files: all ${files.length} files`);
+
+
// Demonstrate accessing a page
+
console.log('\nSimulating page request for about.html:');
+
const aboutPage = await storage.getWithMetadata(`${siteId}/${siteName}/about.html`);
+
if (aboutPage) {
+
console.log(` Source: ${aboutPage.source} tier`);
+
console.log(` Access count: ${aboutPage.metadata.accessCount}`);
+
console.log(` Preview: ${aboutPage.data.toString().slice(0, 100)}...`);
+
}
+
+
// Invalidate entire site
+
console.log(`\nInvalidating entire site: ${siteId}/${siteName}/`);
+
const deleted = await storage.invalidate(`${siteId}/${siteName}/`);
+
console.log(`Deleted ${deleted} files from all tiers`);
+
}
+
+
async function bootstrapExample() {
+
console.log('\n=== Bootstrap Example ===\n');
+
+
const hot = new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: './example-cache/bootstrap/warm' });
+
const cold = new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'example/bootstrap/',
+
});
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
// Populate with some data
+
console.log('Populating storage with test data...');
+
for (let i = 0; i < 10; i++) {
+
await storage.set(`item:${i}`, {
+
id: i,
+
name: `Item ${i}`,
+
description: `This is item number ${i}`,
+
});
+
}
+
+
// Access some items to build up access counts
+
console.log('Accessing some items to simulate usage patterns...');
+
await storage.get('item:0'); // Most accessed
+
await storage.get('item:0');
+
await storage.get('item:0');
+
await storage.get('item:1'); // Second most accessed
+
await storage.get('item:1');
+
await storage.get('item:2'); // Third most accessed
+
+
// Clear hot tier to simulate server restart
+
console.log('\nSimulating server restart (clearing hot tier)...');
+
await hot.clear();
+
+
let hotStats = await hot.getStats();
+
console.log(`Hot tier after clear: ${hotStats.items} items`);
+
+
// Bootstrap hot from warm (loads most accessed items)
+
console.log('\nBootstrapping hot tier from warm (loading top 3 items)...');
+
const loaded = await storage.bootstrapHot(3);
+
console.log(`Loaded ${loaded} items into hot tier`);
+
+
hotStats = await hot.getStats();
+
console.log(`Hot tier after bootstrap: ${hotStats.items} items`);
+
+
// Verify the right items were loaded
+
console.log('\nVerifying loaded items are served from hot:');
+
for (let i = 0; i < 3; i++) {
+
const result = await storage.getWithMetadata(`item:${i}`);
+
console.log(` item:${i}: ${result?.source}`);
+
}
+
+
// Cleanup this example's data
+
console.log('\nCleaning up bootstrap example data...');
+
await storage.invalidate('item:');
+
}
+
+
async function promotionStrategyExample() {
+
console.log('\n=== Promotion Strategy Example ===\n');
+
+
// Lazy promotion (default)
+
console.log('Testing LAZY promotion:');
+
const lazyStorage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: './example-cache/promo-lazy/warm' }),
+
cold: new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'example/promo-lazy/',
+
}),
+
},
+
promotionStrategy: 'lazy',
+
});
+
+
// Write data and clear hot
+
await lazyStorage.set('test:lazy', { value: 'lazy test' });
+
await lazyStorage.clearTier('hot');
+
+
// Read from cold (should NOT auto-promote to hot)
+
const lazyResult = await lazyStorage.getWithMetadata('test:lazy');
+
console.log(` First read served from: ${lazyResult?.source}`);
+
+
const lazyResult2 = await lazyStorage.getWithMetadata('test:lazy');
+
console.log(` Second read served from: ${lazyResult2?.source} (lazy = no auto-promotion)`);
+
+
// Eager promotion
+
console.log('\nTesting EAGER promotion:');
+
const eagerStorage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: './example-cache/promo-eager/warm' }),
+
cold: new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'example/promo-eager/',
+
}),
+
},
+
promotionStrategy: 'eager',
+
});
+
+
// Write data and clear hot
+
await eagerStorage.set('test:eager', { value: 'eager test' });
+
await eagerStorage.clearTier('hot');
+
+
// Read from cold (SHOULD auto-promote to hot)
+
const eagerResult = await eagerStorage.getWithMetadata('test:eager');
+
console.log(` First read served from: ${eagerResult?.source}`);
+
+
const eagerResult2 = await eagerStorage.getWithMetadata('test:eager');
+
console.log(` Second read served from: ${eagerResult2?.source} (eager = promoted to hot)`);
+
+
// Cleanup
+
await lazyStorage.invalidate('test:');
+
await eagerStorage.invalidate('test:');
+
}
+
+
async function cleanup() {
+
console.log('\n=== Cleanup ===\n');
+
console.log('Removing example cache directories...');
+
await rm('./example-cache', { recursive: true, force: true });
+
console.log('✓ Local cache directories removed');
+
console.log('\nNote: S3 objects with prefix "example/" remain in bucket');
+
console.log(' (remove manually if needed)');
+
}
+
+
async function main() {
+
console.log('╔════════════════════════════════════════════════╗');
+
console.log('║ Tiered Storage Library - Usage Examples ║');
+
console.log('║ Cold Tier: S3 (or S3-compatible storage) ║');
+
console.log('╚════════════════════════════════════════════════╝');
+
+
// Check for S3 configuration
+
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
+
console.log('\n⚠️ Warning: AWS credentials not configured');
+
console.log(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env');
+
console.log(' (See .env.example for configuration options)\n');
+
}
+
+
console.log('\nConfiguration:');
+
console.log(` S3 Bucket: ${S3_BUCKET}`);
+
console.log(` S3 Region: ${S3_REGION}`);
+
console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
+
console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
+
console.log(` Credentials: ${AWS_ACCESS_KEY_ID ? '✓ Configured' : '✗ Not configured (using IAM role)'}`);
+
+
try {
+
// Test S3 connection first
+
console.log('\nTesting S3 connection...');
+
const testStorage = new S3StorageTier({
+
bucket: S3_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? {
+
accessKeyId: AWS_ACCESS_KEY_ID,
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
+
}
+
: undefined,
+
prefix: 'test/',
+
});
+
+
try {
+
await testStorage.set('connection-test', new TextEncoder().encode('test'), {
+
key: 'connection-test',
+
size: 4,
+
createdAt: new Date(),
+
lastAccessed: new Date(),
+
accessCount: 0,
+
compressed: false,
+
checksum: 'test',
+
});
+
console.log('✓ S3 connection successful!\n');
+
await testStorage.delete('connection-test');
+
} catch (error: any) {
+
console.error('✗ S3 connection failed:', error.message);
+
console.error('\nPossible issues:');
+
console.error(' 1. Check that the bucket exists on your S3 service');
+
console.error(' 2. Verify credentials have read/write permissions');
+
console.error(' 3. Confirm the endpoint URL is correct');
+
console.error(' 4. Try setting S3_REGION to a different value (e.g., "us-east-1" or "auto")');
+
console.error('\nSkipping examples due to S3 connection error.\n');
+
return;
+
}
+
+
await basicExample();
+
await staticSiteHostingExample();
+
await bootstrapExample();
+
await promotionStrategyExample();
+
} catch (error: any) {
+
console.error('\n❌ Error:', error.message);
+
if (error.name === 'NoSuchBucket') {
+
console.error(`\n The S3 bucket "${S3_BUCKET}" does not exist.`);
+
console.error(' Create it first or set S3_BUCKET in .env to an existing bucket.\n');
+
}
+
} finally {
+
await cleanup();
+
}
+
+
console.log('\n✅ All examples completed successfully!');
+
console.log('\nTry modifying this file to experiment with different patterns.');
+
}
+
+
main().catch(console.error);
+4668
package-lock.json
···
+
{
+
"name": "tiered-storage",
+
"version": "1.0.0",
+
"lockfileVersion": 3,
+
"requires": true,
+
"packages": {
+
"": {
+
"name": "tiered-storage",
+
"version": "1.0.0",
+
"dependencies": {
+
"@aws-sdk/client-s3": "^3.500.0",
+
"hono": "^4.10.7",
+
"mime-types": "^3.0.2",
+
"tiny-lru": "^11.0.0"
+
},
+
"devDependencies": {
+
"@types/bun": "^1.3.4",
+
"@types/mime-types": "^3.0.1",
+
"@types/node": "^24.10.1",
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
+
"@typescript-eslint/parser": "^8.48.1",
+
"eslint": "^9.39.1",
+
"tsx": "^4.0.0",
+
"typescript": "^5.3.0",
+
"vitest": "^4.0.15"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/crc32": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-crypto/util": "^5.2.0",
+
"@aws-sdk/types": "^3.222.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=16.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/crc32c": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-crypto/util": "^5.2.0",
+
"@aws-sdk/types": "^3.222.0",
+
"tslib": "^2.6.2"
+
}
+
},
+
"node_modules/@aws-crypto/sha1-browser": {
+
"version": "5.2.0",
+
"license": "Apache-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"
+
}
+
},
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": {
+
"version": "2.3.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-buffer-from": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/is-array-buffer": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha256-browser": {
+
"version": "5.2.0",
+
"license": "Apache-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"
+
}
+
},
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
+
"version": "2.3.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-buffer-from": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/is-array-buffer": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/sha256-js": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-crypto/util": "^5.2.0",
+
"@aws-sdk/types": "^3.222.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=16.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/supports-web-crypto": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
}
+
},
+
"node_modules/@aws-crypto/util": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "^3.222.0",
+
"@smithy/util-utf8": "^2.0.0",
+
"tslib": "^2.6.2"
+
}
+
},
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
+
"version": "2.3.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-buffer-from": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/is-array-buffer": "^2.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
+
"version": "2.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/client-s3": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/client-sso": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/core": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-env": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-http": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-ini": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-login": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-node": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-process": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-sso": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/credential-provider-web-identity": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-bucket-endpoint": {
+
"version": "3.936.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-expect-continue": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/protocol-http": "^5.3.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-flexible-checksums": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-host-header": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/protocol-http": "^5.3.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-location-constraint": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-logger": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-recursion-detection": {
+
"version": "3.936.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-sdk-s3": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-ssec": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/middleware-user-agent": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/nested-clients": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/region-config-resolver": {
+
"version": "3.936.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/signature-v4-multi-region": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/token-providers": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/types": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/util-arn-parser": {
+
"version": "3.893.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/util-endpoints": {
+
"version": "3.936.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/util-locate-window": {
+
"version": "3.893.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws-sdk/util-user-agent-browser": {
+
"version": "3.936.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-sdk/types": "3.936.0",
+
"@smithy/types": "^4.9.0",
+
"bowser": "^2.11.0",
+
"tslib": "^2.6.2"
+
}
+
},
+
"node_modules/@aws-sdk/util-user-agent-node": {
+
"version": "3.946.0",
+
"license": "Apache-2.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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
},
+
"peerDependencies": {
+
"aws-crt": ">=1.0.0"
+
},
+
"peerDependenciesMeta": {
+
"aws-crt": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/@aws-sdk/xml-builder": {
+
"version": "3.930.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"fast-xml-parser": "5.2.5",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@aws/lambda-invoke-store": {
+
"version": "0.2.2",
+
"license": "Apache-2.0",
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@esbuild/aix-ppc64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
+
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"aix"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-arm": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
+
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
+
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
+
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/darwin-arm64": {
+
"version": "0.27.1",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/darwin-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
+
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/freebsd-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
+
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/freebsd-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
+
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-arm": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
+
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
+
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-ia32": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
+
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-loong64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
+
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
+
"cpu": [
+
"loong64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-mips64el": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
+
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
+
"cpu": [
+
"mips64el"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-ppc64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
+
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-riscv64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
+
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-s390x": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
+
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
+
"cpu": [
+
"s390x"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
+
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/netbsd-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
+
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"netbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/netbsd-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
+
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"netbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/openbsd-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
+
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/openbsd-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
+
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/openharmony-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
+
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openharmony"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/sunos-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
+
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"sunos"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-arm64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
+
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-ia32": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
+
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-x64": {
+
"version": "0.27.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
+
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@eslint-community/eslint-utils": {
+
"version": "4.9.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"eslint-visitor-keys": "^3.4.3"
+
},
+
"engines": {
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
},
+
"peerDependencies": {
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+
}
+
},
+
"node_modules/@eslint-community/regexpp": {
+
"version": "4.12.2",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+
}
+
},
+
"node_modules/@eslint/config-array": {
+
"version": "0.21.1",
+
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@eslint/object-schema": "^2.1.7",
+
"debug": "^4.3.1",
+
"minimatch": "^3.1.2"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
}
+
},
+
"node_modules/@eslint/config-helpers": {
+
"version": "0.4.2",
+
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@eslint/core": "^0.17.0"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
}
+
},
+
"node_modules/@eslint/core": {
+
"version": "0.17.0",
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@types/json-schema": "^7.0.15"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
}
+
},
+
"node_modules/@eslint/eslintrc": {
+
"version": "3.3.3",
+
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"ajv": "^6.12.4",
+
"debug": "^4.3.2",
+
"espree": "^10.0.1",
+
"globals": "^14.0.0",
+
"ignore": "^5.2.0",
+
"import-fresh": "^3.2.1",
+
"js-yaml": "^4.1.1",
+
"minimatch": "^3.1.2",
+
"strip-json-comments": "^3.1.1"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/@eslint/eslintrc/node_modules/ignore": {
+
"version": "5.3.2",
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">= 4"
+
}
+
},
+
"node_modules/@eslint/js": {
+
"version": "9.39.1",
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
+
"integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://eslint.org/donate"
+
}
+
},
+
"node_modules/@eslint/object-schema": {
+
"version": "2.1.7",
+
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
}
+
},
+
"node_modules/@eslint/plugin-kit": {
+
"version": "0.4.1",
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@eslint/core": "^0.17.0",
+
"levn": "^0.4.1"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
}
+
},
+
"node_modules/@humanfs/core": {
+
"version": "0.19.1",
+
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": ">=18.18.0"
+
}
+
},
+
"node_modules/@humanfs/node": {
+
"version": "0.16.7",
+
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@humanfs/core": "^0.19.1",
+
"@humanwhocodes/retry": "^0.4.0"
+
},
+
"engines": {
+
"node": ">=18.18.0"
+
}
+
},
+
"node_modules/@humanwhocodes/module-importer": {
+
"version": "1.0.1",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": ">=12.22"
+
},
+
"funding": {
+
"type": "github",
+
"url": "https://github.com/sponsors/nzakas"
+
}
+
},
+
"node_modules/@humanwhocodes/retry": {
+
"version": "0.4.3",
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": ">=18.18"
+
},
+
"funding": {
+
"type": "github",
+
"url": "https://github.com/sponsors/nzakas"
+
}
+
},
+
"node_modules/@jridgewell/sourcemap-codec": {
+
"version": "1.5.5",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@rollup/rollup-android-arm-eabi": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
]
+
},
+
"node_modules/@rollup/rollup-android-arm64": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
]
+
},
+
"node_modules/@rollup/rollup-darwin-arm64": {
+
"version": "4.53.3",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
]
+
},
+
"node_modules/@rollup/rollup-darwin-x64": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
]
+
},
+
"node_modules/@rollup/rollup-freebsd-arm64": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
]
+
},
+
"node_modules/@rollup/rollup-freebsd-x64": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+
"cpu": [
+
"loong64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+
"cpu": [
+
"s390x"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-x64-musl": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-openharmony-arm64": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openharmony"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
+
"version": "4.53.3",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@smithy/abort-controller": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/chunked-blob-reader": {
+
"version": "5.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/chunked-blob-reader-native": {
+
"version": "4.2.1",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-base64": "^4.3.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/config-resolver": {
+
"version": "4.4.3",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/core": {
+
"version": "3.18.7",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/credential-provider-imds": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/eventstream-codec": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@aws-crypto/crc32": "5.2.0",
+
"@smithy/types": "^4.9.0",
+
"@smithy/util-hex-encoding": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/eventstream-serde-browser": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/eventstream-serde-universal": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/eventstream-serde-config-resolver": {
+
"version": "4.3.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/eventstream-serde-node": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/eventstream-serde-universal": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/eventstream-serde-universal": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/eventstream-codec": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/fetch-http-handler": {
+
"version": "5.3.6",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/hash-blob-browser": {
+
"version": "4.2.6",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/hash-node": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"@smithy/util-buffer-from": "^4.2.0",
+
"@smithy/util-utf8": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/hash-stream-node": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"@smithy/util-utf8": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/invalid-dependency": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/is-array-buffer": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/md5-js": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"@smithy/util-utf8": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/middleware-content-length": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/protocol-http": "^5.3.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/middleware-endpoint": {
+
"version": "4.3.14",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/middleware-retry": {
+
"version": "4.4.14",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/middleware-serde": {
+
"version": "4.2.6",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/protocol-http": "^5.3.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/middleware-stack": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/node-config-provider": {
+
"version": "4.3.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/property-provider": "^4.2.5",
+
"@smithy/shared-ini-file-loader": "^4.4.0",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/node-http-handler": {
+
"version": "4.4.5",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/property-provider": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/protocol-http": {
+
"version": "5.3.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/querystring-builder": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"@smithy/util-uri-escape": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/querystring-parser": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/service-error-classification": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/shared-ini-file-loader": {
+
"version": "4.4.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/signature-v4": {
+
"version": "5.3.5",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/smithy-client": {
+
"version": "4.9.10",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/types": {
+
"version": "4.9.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/url-parser": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/querystring-parser": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-base64": {
+
"version": "4.3.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-buffer-from": "^4.2.0",
+
"@smithy/util-utf8": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-body-length-browser": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-body-length-node": {
+
"version": "4.2.1",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-buffer-from": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/is-array-buffer": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-config-provider": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-defaults-mode-browser": {
+
"version": "4.3.13",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/property-provider": "^4.2.5",
+
"@smithy/smithy-client": "^4.9.10",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-defaults-mode-node": {
+
"version": "4.2.16",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-endpoints": {
+
"version": "3.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/node-config-provider": "^4.3.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-hex-encoding": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-middleware": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-retry": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/service-error-classification": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-stream": {
+
"version": "4.5.6",
+
"license": "Apache-2.0",
+
"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"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-uri-escape": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-utf8": {
+
"version": "4.2.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/util-buffer-from": "^4.2.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/util-waiter": {
+
"version": "4.2.5",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"@smithy/abort-controller": "^4.2.5",
+
"@smithy/types": "^4.9.0",
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@smithy/uuid": {
+
"version": "1.1.0",
+
"license": "Apache-2.0",
+
"dependencies": {
+
"tslib": "^2.6.2"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
},
+
"node_modules/@standard-schema/spec": {
+
"version": "1.0.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@types/bun": {
+
"version": "1.3.4",
+
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.4.tgz",
+
"integrity": "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"bun-types": "1.3.4"
+
}
+
},
+
"node_modules/@types/chai": {
+
"version": "5.2.3",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@types/deep-eql": "*",
+
"assertion-error": "^2.0.1"
+
}
+
},
+
"node_modules/@types/deep-eql": {
+
"version": "4.0.2",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@types/estree": {
+
"version": "1.0.8",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@types/json-schema": {
+
"version": "7.0.15",
+
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@types/mime-types": {
+
"version": "3.0.1",
+
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz",
+
"integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/@types/node": {
+
"version": "24.10.1",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"dependencies": {
+
"undici-types": "~7.16.0"
+
}
+
},
+
"node_modules/@typescript-eslint/eslint-plugin": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"@typescript-eslint/parser": "^8.49.0",
+
"eslint": "^8.57.0 || ^9.0.0",
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/parser": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"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"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"eslint": "^8.57.0 || ^9.0.0",
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/project-service": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@typescript-eslint/tsconfig-utils": "^8.49.0",
+
"@typescript-eslint/types": "^8.49.0",
+
"debug": "^4.3.4"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/scope-manager": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@typescript-eslint/types": "8.49.0",
+
"@typescript-eslint/visitor-keys": "8.49.0"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
}
+
},
+
"node_modules/@typescript-eslint/tsconfig-utils": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/type-utils": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"eslint": "^8.57.0 || ^9.0.0",
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/types": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
}
+
},
+
"node_modules/@typescript-eslint/typescript-estree": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+
"version": "9.0.5",
+
"dev": true,
+
"license": "ISC",
+
"dependencies": {
+
"brace-expansion": "^2.0.1"
+
},
+
"engines": {
+
"node": ">=16 || 14 >=14.17"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/isaacs"
+
}
+
},
+
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": {
+
"version": "2.0.2",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"balanced-match": "^1.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/utils": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
},
+
"peerDependencies": {
+
"eslint": "^8.57.0 || ^9.0.0",
+
"typescript": ">=4.8.4 <6.0.0"
+
}
+
},
+
"node_modules/@typescript-eslint/visitor-keys": {
+
"version": "8.49.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@typescript-eslint/types": "8.49.0",
+
"eslint-visitor-keys": "^4.2.1"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/typescript-eslint"
+
}
+
},
+
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+
"version": "4.2.1",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/@vitest/expect": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/@vitest/mocker": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@vitest/spy": "4.0.15",
+
"estree-walker": "^3.0.3",
+
"magic-string": "^0.30.21"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
},
+
"peerDependencies": {
+
"msw": "^2.4.9",
+
"vite": "^6.0.0 || ^7.0.0-0"
+
},
+
"peerDependenciesMeta": {
+
"msw": {
+
"optional": true
+
},
+
"vite": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/@vitest/pretty-format": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"tinyrainbow": "^3.0.3"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/@vitest/runner": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@vitest/utils": "4.0.15",
+
"pathe": "^2.0.3"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/@vitest/snapshot": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@vitest/pretty-format": "4.0.15",
+
"magic-string": "^0.30.21",
+
"pathe": "^2.0.3"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/@vitest/spy": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/@vitest/utils": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@vitest/pretty-format": "4.0.15",
+
"tinyrainbow": "^3.0.3"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
}
+
},
+
"node_modules/acorn": {
+
"version": "8.15.0",
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"bin": {
+
"acorn": "bin/acorn"
+
},
+
"engines": {
+
"node": ">=0.4.0"
+
}
+
},
+
"node_modules/acorn-jsx": {
+
"version": "5.3.2",
+
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+
"dev": true,
+
"license": "MIT",
+
"peerDependencies": {
+
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+
}
+
},
+
"node_modules/ajv": {
+
"version": "6.12.6",
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"funding": {
+
"type": "github",
+
"url": "https://github.com/sponsors/epoberezkin"
+
}
+
},
+
"node_modules/ansi-styles": {
+
"version": "4.3.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"color-convert": "^2.0.1"
+
},
+
"engines": {
+
"node": ">=8"
+
},
+
"funding": {
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
+
}
+
},
+
"node_modules/argparse": {
+
"version": "2.0.1",
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+
"dev": true,
+
"license": "Python-2.0"
+
},
+
"node_modules/assertion-error": {
+
"version": "2.0.1",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=12"
+
}
+
},
+
"node_modules/balanced-match": {
+
"version": "1.0.2",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/bowser": {
+
"version": "2.13.1",
+
"license": "MIT"
+
},
+
"node_modules/brace-expansion": {
+
"version": "1.1.12",
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"balanced-match": "^1.0.0",
+
"concat-map": "0.0.1"
+
}
+
},
+
"node_modules/bun-types": {
+
"version": "1.3.4",
+
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.4.tgz",
+
"integrity": "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@types/node": "*"
+
}
+
},
+
"node_modules/callsites": {
+
"version": "3.1.0",
+
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=6"
+
}
+
},
+
"node_modules/chai": {
+
"version": "6.2.1",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/chalk": {
+
"version": "4.1.2",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"ansi-styles": "^4.1.0",
+
"supports-color": "^7.1.0"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/chalk/chalk?sponsor=1"
+
}
+
},
+
"node_modules/color-convert": {
+
"version": "2.0.1",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"color-name": "~1.1.4"
+
},
+
"engines": {
+
"node": ">=7.0.0"
+
}
+
},
+
"node_modules/color-name": {
+
"version": "1.1.4",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/concat-map": {
+
"version": "0.0.1",
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/cross-spawn": {
+
"version": "7.0.6",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"path-key": "^3.1.0",
+
"shebang-command": "^2.0.0",
+
"which": "^2.0.1"
+
},
+
"engines": {
+
"node": ">= 8"
+
}
+
},
+
"node_modules/debug": {
+
"version": "4.4.3",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"ms": "^2.1.3"
+
},
+
"engines": {
+
"node": ">=6.0"
+
},
+
"peerDependenciesMeta": {
+
"supports-color": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/deep-is": {
+
"version": "0.1.4",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/es-module-lexer": {
+
"version": "1.7.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/esbuild": {
+
"version": "0.27.1",
+
"dev": true,
+
"hasInstallScript": true,
+
"license": "MIT",
+
"bin": {
+
"esbuild": "bin/esbuild"
+
},
+
"engines": {
+
"node": ">=18"
+
},
+
"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"
+
}
+
},
+
"node_modules/escape-string-regexp": {
+
"version": "4.0.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/eslint": {
+
"version": "9.39.1",
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
+
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"dependencies": {
+
"@eslint-community/eslint-utils": "^4.8.0",
+
"@eslint-community/regexpp": "^4.12.1",
+
"@eslint/config-array": "^0.21.1",
+
"@eslint/config-helpers": "^0.4.2",
+
"@eslint/core": "^0.17.0",
+
"@eslint/eslintrc": "^3.3.1",
+
"@eslint/js": "9.39.1",
+
"@eslint/plugin-kit": "^0.4.1",
+
"@humanfs/node": "^0.16.6",
+
"@humanwhocodes/module-importer": "^1.0.1",
+
"@humanwhocodes/retry": "^0.4.2",
+
"@types/estree": "^1.0.6",
+
"ajv": "^6.12.4",
+
"chalk": "^4.0.0",
+
"cross-spawn": "^7.0.6",
+
"debug": "^4.3.2",
+
"escape-string-regexp": "^4.0.0",
+
"eslint-scope": "^8.4.0",
+
"eslint-visitor-keys": "^4.2.1",
+
"espree": "^10.4.0",
+
"esquery": "^1.5.0",
+
"esutils": "^2.0.2",
+
"fast-deep-equal": "^3.1.3",
+
"file-entry-cache": "^8.0.0",
+
"find-up": "^5.0.0",
+
"glob-parent": "^6.0.2",
+
"ignore": "^5.2.0",
+
"imurmurhash": "^0.1.4",
+
"is-glob": "^4.0.0",
+
"json-stable-stringify-without-jsonify": "^1.0.1",
+
"lodash.merge": "^4.6.2",
+
"minimatch": "^3.1.2",
+
"natural-compare": "^1.4.0",
+
"optionator": "^0.9.3"
+
},
+
"bin": {
+
"eslint": "bin/eslint.js"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://eslint.org/donate"
+
},
+
"peerDependencies": {
+
"jiti": "*"
+
},
+
"peerDependenciesMeta": {
+
"jiti": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/eslint-scope": {
+
"version": "8.4.0",
+
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"dependencies": {
+
"esrecurse": "^4.3.0",
+
"estraverse": "^5.2.0"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/eslint-visitor-keys": {
+
"version": "3.4.3",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/eslint/node_modules/eslint-visitor-keys": {
+
"version": "4.2.1",
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/eslint/node_modules/ignore": {
+
"version": "5.3.2",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">= 4"
+
}
+
},
+
"node_modules/espree": {
+
"version": "10.4.0",
+
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"dependencies": {
+
"acorn": "^8.15.0",
+
"acorn-jsx": "^5.3.2",
+
"eslint-visitor-keys": "^4.2.1"
+
},
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/espree/node_modules/eslint-visitor-keys": {
+
"version": "4.2.1",
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/eslint"
+
}
+
},
+
"node_modules/esquery": {
+
"version": "1.6.0",
+
"dev": true,
+
"license": "BSD-3-Clause",
+
"dependencies": {
+
"estraverse": "^5.1.0"
+
},
+
"engines": {
+
"node": ">=0.10"
+
}
+
},
+
"node_modules/esrecurse": {
+
"version": "4.3.0",
+
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"dependencies": {
+
"estraverse": "^5.2.0"
+
},
+
"engines": {
+
"node": ">=4.0"
+
}
+
},
+
"node_modules/estraverse": {
+
"version": "5.3.0",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"engines": {
+
"node": ">=4.0"
+
}
+
},
+
"node_modules/estree-walker": {
+
"version": "3.0.3",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@types/estree": "^1.0.0"
+
}
+
},
+
"node_modules/esutils": {
+
"version": "2.0.3",
+
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"engines": {
+
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/expect-type": {
+
"version": "1.3.0",
+
"dev": true,
+
"license": "Apache-2.0",
+
"engines": {
+
"node": ">=12.0.0"
+
}
+
},
+
"node_modules/fast-deep-equal": {
+
"version": "3.1.3",
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/fast-json-stable-stringify": {
+
"version": "2.1.0",
+
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/fast-levenshtein": {
+
"version": "2.0.6",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/fast-xml-parser": {
+
"version": "5.2.5",
+
"funding": [
+
{
+
"type": "github",
+
"url": "https://github.com/sponsors/NaturalIntelligence"
+
}
+
],
+
"license": "MIT",
+
"dependencies": {
+
"strnum": "^2.1.0"
+
},
+
"bin": {
+
"fxparser": "src/cli/cli.js"
+
}
+
},
+
"node_modules/fdir": {
+
"version": "6.5.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=12.0.0"
+
},
+
"peerDependencies": {
+
"picomatch": "^3 || ^4"
+
},
+
"peerDependenciesMeta": {
+
"picomatch": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/file-entry-cache": {
+
"version": "8.0.0",
+
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"flat-cache": "^4.0.0"
+
},
+
"engines": {
+
"node": ">=16.0.0"
+
}
+
},
+
"node_modules/find-up": {
+
"version": "5.0.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"locate-path": "^6.0.0",
+
"path-exists": "^4.0.0"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/flat-cache": {
+
"version": "4.0.1",
+
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"flatted": "^3.2.9",
+
"keyv": "^4.5.4"
+
},
+
"engines": {
+
"node": ">=16"
+
}
+
},
+
"node_modules/flatted": {
+
"version": "3.3.3",
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+
"dev": true,
+
"license": "ISC"
+
},
+
"node_modules/fsevents": {
+
"version": "2.3.3",
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+
}
+
},
+
"node_modules/get-tsconfig": {
+
"version": "4.13.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"resolve-pkg-maps": "^1.0.0"
+
},
+
"funding": {
+
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+
}
+
},
+
"node_modules/glob-parent": {
+
"version": "6.0.2",
+
"dev": true,
+
"license": "ISC",
+
"dependencies": {
+
"is-glob": "^4.0.3"
+
},
+
"engines": {
+
"node": ">=10.13.0"
+
}
+
},
+
"node_modules/globals": {
+
"version": "14.0.0",
+
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=18"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/has-flag": {
+
"version": "4.0.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/hono": {
+
"version": "4.10.7",
+
"license": "MIT",
+
"engines": {
+
"node": ">=16.9.0"
+
}
+
},
+
"node_modules/ignore": {
+
"version": "7.0.5",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">= 4"
+
}
+
},
+
"node_modules/import-fresh": {
+
"version": "3.3.1",
+
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"parent-module": "^1.0.0",
+
"resolve-from": "^4.0.0"
+
},
+
"engines": {
+
"node": ">=6"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/imurmurhash": {
+
"version": "0.1.4",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=0.8.19"
+
}
+
},
+
"node_modules/is-extglob": {
+
"version": "2.1.1",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/is-glob": {
+
"version": "4.0.3",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"is-extglob": "^2.1.1"
+
},
+
"engines": {
+
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/isexe": {
+
"version": "2.0.0",
+
"dev": true,
+
"license": "ISC"
+
},
+
"node_modules/js-yaml": {
+
"version": "4.1.1",
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"argparse": "^2.0.1"
+
},
+
"bin": {
+
"js-yaml": "bin/js-yaml.js"
+
}
+
},
+
"node_modules/json-buffer": {
+
"version": "3.0.1",
+
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/json-schema-traverse": {
+
"version": "0.4.1",
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/json-stable-stringify-without-jsonify": {
+
"version": "1.0.1",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/keyv": {
+
"version": "4.5.4",
+
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"json-buffer": "3.0.1"
+
}
+
},
+
"node_modules/levn": {
+
"version": "0.4.1",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"prelude-ls": "^1.2.1",
+
"type-check": "~0.4.0"
+
},
+
"engines": {
+
"node": ">= 0.8.0"
+
}
+
},
+
"node_modules/locate-path": {
+
"version": "6.0.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"p-locate": "^5.0.0"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/lodash.merge": {
+
"version": "4.6.2",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/magic-string": {
+
"version": "0.30.21",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@jridgewell/sourcemap-codec": "^1.5.5"
+
}
+
},
+
"node_modules/mime-db": {
+
"version": "1.54.0",
+
"license": "MIT",
+
"engines": {
+
"node": ">= 0.6"
+
}
+
},
+
"node_modules/mime-types": {
+
"version": "3.0.2",
+
"license": "MIT",
+
"dependencies": {
+
"mime-db": "^1.54.0"
+
},
+
"engines": {
+
"node": ">=18"
+
},
+
"funding": {
+
"type": "opencollective",
+
"url": "https://opencollective.com/express"
+
}
+
},
+
"node_modules/minimatch": {
+
"version": "3.1.2",
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+
"dev": true,
+
"license": "ISC",
+
"dependencies": {
+
"brace-expansion": "^1.1.7"
+
},
+
"engines": {
+
"node": "*"
+
}
+
},
+
"node_modules/ms": {
+
"version": "2.1.3",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/nanoid": {
+
"version": "3.3.11",
+
"dev": true,
+
"funding": [
+
{
+
"type": "github",
+
"url": "https://github.com/sponsors/ai"
+
}
+
],
+
"license": "MIT",
+
"bin": {
+
"nanoid": "bin/nanoid.cjs"
+
},
+
"engines": {
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+
}
+
},
+
"node_modules/natural-compare": {
+
"version": "1.4.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/obug": {
+
"version": "2.1.1",
+
"dev": true,
+
"funding": [
+
"https://github.com/sponsors/sxzz",
+
"https://opencollective.com/debug"
+
],
+
"license": "MIT"
+
},
+
"node_modules/optionator": {
+
"version": "0.9.4",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"engines": {
+
"node": ">= 0.8.0"
+
}
+
},
+
"node_modules/p-limit": {
+
"version": "3.1.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"yocto-queue": "^0.1.0"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/p-locate": {
+
"version": "5.0.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"p-limit": "^3.0.2"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/parent-module": {
+
"version": "1.0.1",
+
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"callsites": "^3.0.0"
+
},
+
"engines": {
+
"node": ">=6"
+
}
+
},
+
"node_modules/path-exists": {
+
"version": "4.0.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/path-key": {
+
"version": "3.1.1",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/pathe": {
+
"version": "2.0.3",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/picocolors": {
+
"version": "1.1.1",
+
"dev": true,
+
"license": "ISC"
+
},
+
"node_modules/picomatch": {
+
"version": "4.0.3",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"engines": {
+
"node": ">=12"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/jonschlinkert"
+
}
+
},
+
"node_modules/postcss": {
+
"version": "8.5.6",
+
"dev": true,
+
"funding": [
+
{
+
"type": "opencollective",
+
"url": "https://opencollective.com/postcss/"
+
},
+
{
+
"type": "tidelift",
+
"url": "https://tidelift.com/funding/github/npm/postcss"
+
},
+
{
+
"type": "github",
+
"url": "https://github.com/sponsors/ai"
+
}
+
],
+
"license": "MIT",
+
"dependencies": {
+
"nanoid": "^3.3.11",
+
"picocolors": "^1.1.1",
+
"source-map-js": "^1.2.1"
+
},
+
"engines": {
+
"node": "^10 || ^12 || >=14"
+
}
+
},
+
"node_modules/prelude-ls": {
+
"version": "1.2.1",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">= 0.8.0"
+
}
+
},
+
"node_modules/punycode": {
+
"version": "2.3.1",
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=6"
+
}
+
},
+
"node_modules/resolve-from": {
+
"version": "4.0.0",
+
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=4"
+
}
+
},
+
"node_modules/resolve-pkg-maps": {
+
"version": "1.0.0",
+
"dev": true,
+
"license": "MIT",
+
"funding": {
+
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+
}
+
},
+
"node_modules/rollup": {
+
"version": "4.53.3",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"@types/estree": "1.0.8"
+
},
+
"bin": {
+
"rollup": "dist/bin/rollup"
+
},
+
"engines": {
+
"node": ">=18.0.0",
+
"npm": ">=8.0.0"
+
},
+
"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"
+
}
+
},
+
"node_modules/semver": {
+
"version": "7.7.3",
+
"dev": true,
+
"license": "ISC",
+
"bin": {
+
"semver": "bin/semver.js"
+
},
+
"engines": {
+
"node": ">=10"
+
}
+
},
+
"node_modules/shebang-command": {
+
"version": "2.0.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"shebang-regex": "^3.0.0"
+
},
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/shebang-regex": {
+
"version": "3.0.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/siginfo": {
+
"version": "2.0.0",
+
"dev": true,
+
"license": "ISC"
+
},
+
"node_modules/source-map-js": {
+
"version": "1.2.1",
+
"dev": true,
+
"license": "BSD-3-Clause",
+
"engines": {
+
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/stackback": {
+
"version": "0.0.2",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/std-env": {
+
"version": "3.10.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/strip-json-comments": {
+
"version": "3.1.1",
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=8"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/strnum": {
+
"version": "2.1.1",
+
"funding": [
+
{
+
"type": "github",
+
"url": "https://github.com/sponsors/NaturalIntelligence"
+
}
+
],
+
"license": "MIT"
+
},
+
"node_modules/supports-color": {
+
"version": "7.2.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"has-flag": "^4.0.0"
+
},
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/tiny-lru": {
+
"version": "11.4.5",
+
"license": "BSD-3-Clause",
+
"engines": {
+
"node": ">=12"
+
}
+
},
+
"node_modules/tinybench": {
+
"version": "2.9.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/tinyexec": {
+
"version": "1.0.2",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/tinyglobby": {
+
"version": "0.2.15",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"fdir": "^6.5.0",
+
"picomatch": "^4.0.3"
+
},
+
"engines": {
+
"node": ">=12.0.0"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/SuperchupuDev"
+
}
+
},
+
"node_modules/tinyrainbow": {
+
"version": "3.0.3",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=14.0.0"
+
}
+
},
+
"node_modules/ts-api-utils": {
+
"version": "2.1.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=18.12"
+
},
+
"peerDependencies": {
+
"typescript": ">=4.8.4"
+
}
+
},
+
"node_modules/tslib": {
+
"version": "2.8.1",
+
"license": "0BSD"
+
},
+
"node_modules/tsx": {
+
"version": "4.21.0",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"dependencies": {
+
"esbuild": "~0.27.0",
+
"get-tsconfig": "^4.7.5"
+
},
+
"bin": {
+
"tsx": "dist/cli.mjs"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
},
+
"optionalDependencies": {
+
"fsevents": "~2.3.3"
+
}
+
},
+
"node_modules/type-check": {
+
"version": "0.4.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"prelude-ls": "^1.2.1"
+
},
+
"engines": {
+
"node": ">= 0.8.0"
+
}
+
},
+
"node_modules/typescript": {
+
"version": "5.9.3",
+
"dev": true,
+
"license": "Apache-2.0",
+
"peer": true,
+
"bin": {
+
"tsc": "bin/tsc",
+
"tsserver": "bin/tsserver"
+
},
+
"engines": {
+
"node": ">=14.17"
+
}
+
},
+
"node_modules/undici-types": {
+
"version": "7.16.0",
+
"dev": true,
+
"license": "MIT"
+
},
+
"node_modules/uri-js": {
+
"version": "4.4.1",
+
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+
"dev": true,
+
"license": "BSD-2-Clause",
+
"dependencies": {
+
"punycode": "^2.1.0"
+
}
+
},
+
"node_modules/vite": {
+
"version": "7.2.7",
+
"dev": true,
+
"license": "MIT",
+
"peer": true,
+
"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"
+
},
+
"bin": {
+
"vite": "bin/vite.js"
+
},
+
"engines": {
+
"node": "^20.19.0 || >=22.12.0"
+
},
+
"funding": {
+
"url": "https://github.com/vitejs/vite?sponsor=1"
+
},
+
"optionalDependencies": {
+
"fsevents": "~2.3.3"
+
},
+
"peerDependencies": {
+
"@types/node": "^20.19.0 || >=22.12.0",
+
"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"
+
},
+
"peerDependenciesMeta": {
+
"@types/node": {
+
"optional": true
+
},
+
"jiti": {
+
"optional": true
+
},
+
"less": {
+
"optional": true
+
},
+
"lightningcss": {
+
"optional": true
+
},
+
"sass": {
+
"optional": true
+
},
+
"sass-embedded": {
+
"optional": true
+
},
+
"stylus": {
+
"optional": true
+
},
+
"sugarss": {
+
"optional": true
+
},
+
"terser": {
+
"optional": true
+
},
+
"tsx": {
+
"optional": true
+
},
+
"yaml": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"aix"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/android-arm": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/android-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/android-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-arm": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+
"cpu": [
+
"loong64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+
"cpu": [
+
"mips64el"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+
"cpu": [
+
"s390x"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/linux-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"netbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"netbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"openharmony"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"sunos"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/@esbuild/win32-x64": {
+
"version": "0.25.12",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vite/node_modules/esbuild": {
+
"version": "0.25.12",
+
"dev": true,
+
"hasInstallScript": true,
+
"license": "MIT",
+
"bin": {
+
"esbuild": "bin/esbuild"
+
},
+
"engines": {
+
"node": ">=18"
+
},
+
"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"
+
}
+
},
+
"node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
+
"version": "0.25.12",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"license": "MIT",
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/vitest": {
+
"version": "4.0.15",
+
"dev": true,
+
"license": "MIT",
+
"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"
+
},
+
"bin": {
+
"vitest": "vitest.mjs"
+
},
+
"engines": {
+
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+
},
+
"funding": {
+
"url": "https://opencollective.com/vitest"
+
},
+
"peerDependencies": {
+
"@edge-runtime/vm": "*",
+
"@opentelemetry/api": "^1.9.0",
+
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+
"@vitest/browser-playwright": "4.0.15",
+
"@vitest/browser-preview": "4.0.15",
+
"@vitest/browser-webdriverio": "4.0.15",
+
"@vitest/ui": "4.0.15",
+
"happy-dom": "*",
+
"jsdom": "*"
+
},
+
"peerDependenciesMeta": {
+
"@edge-runtime/vm": {
+
"optional": true
+
},
+
"@opentelemetry/api": {
+
"optional": true
+
},
+
"@types/node": {
+
"optional": true
+
},
+
"@vitest/browser-playwright": {
+
"optional": true
+
},
+
"@vitest/browser-preview": {
+
"optional": true
+
},
+
"@vitest/browser-webdriverio": {
+
"optional": true
+
},
+
"@vitest/ui": {
+
"optional": true
+
},
+
"happy-dom": {
+
"optional": true
+
},
+
"jsdom": {
+
"optional": true
+
}
+
}
+
},
+
"node_modules/which": {
+
"version": "2.0.2",
+
"dev": true,
+
"license": "ISC",
+
"dependencies": {
+
"isexe": "^2.0.0"
+
},
+
"bin": {
+
"node-which": "bin/node-which"
+
},
+
"engines": {
+
"node": ">= 8"
+
}
+
},
+
"node_modules/why-is-node-running": {
+
"version": "2.3.0",
+
"dev": true,
+
"license": "MIT",
+
"dependencies": {
+
"siginfo": "^2.0.0",
+
"stackback": "0.0.2"
+
},
+
"bin": {
+
"why-is-node-running": "cli.js"
+
},
+
"engines": {
+
"node": ">=8"
+
}
+
},
+
"node_modules/word-wrap": {
+
"version": "1.2.5",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/yocto-queue": {
+
"version": "0.1.0",
+
"dev": true,
+
"license": "MIT",
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
}
+
}
+
}
+40
package.json
···
+
{
+
"name": "tiered-storage",
+
"version": "1.0.0",
+
"description": "Tiered storage library with S3, disk, and memory caching",
+
"main": "dist/index.js",
+
"types": "dist/index.d.ts",
+
"type": "module",
+
"scripts": {
+
"check": "tsc --noEmit",
+
"build": "tsc",
+
"dev": "tsx --watch src/index.ts",
+
"example": "tsx example.ts",
+
"serve": "tsx serve-example.ts",
+
"test": "vitest",
+
"test:watch": "vitest --watch",
+
"lint": "eslint src --ext .ts",
+
"lint:fix": "eslint src --ext .ts --fix",
+
"typecheck": "tsc --noEmit"
+
},
+
"dependencies": {
+
"@aws-sdk/client-s3": "^3.500.0",
+
"hono": "^4.10.7",
+
"mime-types": "^3.0.2",
+
"tiny-lru": "^11.0.0"
+
},
+
"devDependencies": {
+
"@types/bun": "^1.3.4",
+
"@types/mime-types": "^3.0.1",
+
"@types/node": "^24.10.1",
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
+
"@typescript-eslint/parser": "^8.48.1",
+
"eslint": "^9.39.1",
+
"tsx": "^4.0.0",
+
"typescript": "^5.3.0",
+
"vitest": "^4.0.15"
+
},
+
"engines": {
+
"node": ">=18.0.0"
+
}
+
}
+469
serve-example.ts
···
+
/**
+
* Example HTTP server serving static sites from tiered storage
+
*
+
* This demonstrates a real-world use case: serving static websites
+
* with automatic caching across hot (memory), warm (disk), and cold (S3) tiers.
+
*
+
* Run with: bun run serve
+
*/
+
+
import { Hono } from 'hono';
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js';
+
import { readFile, readdir } from 'node:fs/promises';
+
import { lookup } from 'mime-types';
+
+
const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example';
+
const S3_METADATA_BUCKET = process.env.S3_METADATA_BUCKET;
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false';
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
+
const PORT = parseInt(process.env.PORT || '3000', 10);
+
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({
+
maxSizeBytes: 50 * 1024 * 1024,
+
maxItems: 500,
+
}),
+
warm: new DiskStorageTier({
+
directory: './cache/sites',
+
maxSizeBytes: 1024 * 1024 * 1024,
+
}),
+
cold: new S3StorageTier({
+
bucket: S3_BUCKET,
+
metadataBucket: S3_METADATA_BUCKET,
+
region: S3_REGION,
+
endpoint: S3_ENDPOINT,
+
forcePathStyle: S3_FORCE_PATH_STYLE,
+
credentials:
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
+
? { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY }
+
: undefined,
+
prefix: 'demo-sites/',
+
}),
+
},
+
compression: true,
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
+
promotionStrategy: 'lazy',
+
});
+
+
const app = new Hono();
+
+
// Site metadata
+
const siteId = 'did:plc:example123';
+
const siteName = 'tiered-cache-demo';
+
+
/**
+
* Load the example site into storage
+
*/
+
async function loadExampleSite() {
+
console.log('\n📦 Loading example site into tiered storage...\n');
+
+
const files = [
+
{ name: 'index.html', skipTiers: [], mimeType: 'text/html' },
+
{ name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' },
+
{ name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' },
+
{ name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' },
+
{ name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' },
+
];
+
+
for (const file of files) {
+
const content = await readFile(`./example-site/${file.name}`, 'utf-8');
+
const key = `${siteId}/${siteName}/${file.name}`;
+
+
await storage.set(key, content, {
+
skipTiers: file.skipTiers as ('hot' | 'warm')[],
+
metadata: { mimeType: file.mimeType },
+
});
+
+
const tierInfo =
+
file.skipTiers.length === 0
+
? '🔥 hot + 💾 warm + ☁️ cold'
+
: `💾 warm + ☁️ cold (skipped hot)`;
+
const sizeKB = (content.length / 1024).toFixed(2);
+
console.log(` ✓ ${file.name.padEnd(15)} ${sizeKB.padStart(6)} KB → ${tierInfo}`);
+
}
+
+
console.log('\n✅ Site loaded successfully!\n');
+
}
+
+
/**
+
* Serve a file from tiered storage
+
*/
+
app.get('/sites/:did/:siteName/:path{.*}', async (c) => {
+
const { did, siteName, path } = c.req.param();
+
let filePath = path || 'index.html';
+
+
if (filePath === '' || filePath.endsWith('/')) {
+
filePath += 'index.html';
+
}
+
+
const key = `${did}/${siteName}/${filePath}`;
+
+
try {
+
const result = await storage.getWithMetadata(key);
+
+
if (!result) {
+
return c.text('404 Not Found', 404);
+
}
+
+
const mimeType = result.metadata.customMetadata?.mimeType || lookup(filePath) || 'application/octet-stream';
+
+
const headers: Record<string, string> = {
+
'Content-Type': mimeType,
+
'X-Cache-Tier': result.source, // Which tier served this
+
'X-Cache-Size': result.metadata.size.toString(),
+
'X-Cache-Compressed': result.metadata.compressed.toString(),
+
'X-Cache-Access-Count': result.metadata.accessCount.toString(),
+
};
+
+
// Add cache control based on tier
+
if (result.source === 'hot') {
+
headers['X-Cache-Status'] = 'HIT-MEMORY';
+
} else if (result.source === 'warm') {
+
headers['X-Cache-Status'] = 'HIT-DISK';
+
} else {
+
headers['X-Cache-Status'] = 'HIT-S3';
+
}
+
+
const emoji = result.source === 'hot' ? '🔥' : result.source === 'warm' ? '💾' : '☁️';
+
console.log(`${emoji} ${filePath.padEnd(20)} served from ${result.source.padEnd(4)} (${(result.metadata.size / 1024).toFixed(2)} KB, access #${result.metadata.accessCount})`);
+
+
return c.body(result.data as any, 200, headers);
+
} catch (error: any) {
+
console.error(`❌ Error serving ${filePath}:`, error.message);
+
return c.text('500 Internal Server Error', 500);
+
}
+
});
+
+
/**
+
* Admin endpoint: Cache statistics
+
*/
+
app.get('/admin/stats', async (c) => {
+
const stats = await storage.getStats();
+
+
const html = `
+
<!DOCTYPE html>
+
<html>
+
<head>
+
<title>Tiered Storage Statistics</title>
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<style>
+
* { margin: 0; padding: 0; box-sizing: border-box; }
+
body {
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+
background: #0f172a;
+
color: #f1f5f9;
+
padding: 2rem;
+
line-height: 1.6;
+
}
+
.container { max-width: 1200px; margin: 0 auto; }
+
h1 {
+
font-size: 2rem;
+
margin-bottom: 0.5rem;
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
}
+
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
+
.card {
+
background: #1e293b;
+
border: 1px solid #334155;
+
border-radius: 0.5rem;
+
padding: 1.5rem;
+
}
+
.tier-hot { border-left: 4px solid #ef4444; }
+
.tier-warm { border-left: 4px solid #f59e0b; }
+
.tier-cold { border-left: 4px solid #3b82f6; }
+
.card-title {
+
font-size: 1.2rem;
+
font-weight: 600;
+
margin-bottom: 1rem;
+
display: flex;
+
align-items: center;
+
gap: 0.5rem;
+
}
+
.stat { margin-bottom: 0.75rem; }
+
.stat-label { color: #94a3b8; font-size: 0.9rem; }
+
.stat-value { color: #f1f5f9; font-size: 1.5rem; font-weight: 700; }
+
.overall { background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1)); }
+
.refresh {
+
display: inline-block;
+
background: #3b82f6;
+
color: white;
+
padding: 0.75rem 1.5rem;
+
border-radius: 0.5rem;
+
text-decoration: none;
+
font-weight: 600;
+
margin-top: 1rem;
+
}
+
.refresh:hover { background: #2563eb; }
+
code {
+
background: #334155;
+
padding: 0.2rem 0.5rem;
+
border-radius: 0.25rem;
+
font-size: 0.9em;
+
}
+
</style>
+
</head>
+
<body>
+
<div class="container">
+
<h1>📊 Tiered Storage Statistics</h1>
+
<p class="subtitle">Real-time cache performance metrics • Auto-refresh every 5 seconds</p>
+
+
<div class="grid">
+
<div class="card tier-hot">
+
<div class="card-title">🔥 Hot Tier (Memory)</div>
+
<div class="stat">
+
<div class="stat-label">Items</div>
+
<div class="stat-value">${stats.hot?.items || 0}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Size</div>
+
<div class="stat-value">${((stats.hot?.bytes || 0) / 1024).toFixed(2)} KB</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Hits / Misses</div>
+
<div class="stat-value">${stats.hot?.hits || 0} / ${stats.hot?.misses || 0}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Evictions</div>
+
<div class="stat-value">${stats.hot?.evictions || 0}</div>
+
</div>
+
</div>
+
+
<div class="card tier-warm">
+
<div class="card-title">💾 Warm Tier (Disk)</div>
+
<div class="stat">
+
<div class="stat-label">Items</div>
+
<div class="stat-value">${stats.warm?.items || 0}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Size</div>
+
<div class="stat-value">${((stats.warm?.bytes || 0) / 1024).toFixed(2)} KB</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Hits / Misses</div>
+
<div class="stat-value">${stats.warm?.hits || 0} / ${stats.warm?.misses || 0}</div>
+
</div>
+
</div>
+
+
<div class="card tier-cold">
+
<div class="card-title">☁️ Cold Tier (S3)</div>
+
<div class="stat">
+
<div class="stat-label">Items</div>
+
<div class="stat-value">${stats.cold.items}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Size</div>
+
<div class="stat-value">${(stats.cold.bytes / 1024).toFixed(2)} KB</div>
+
</div>
+
</div>
+
</div>
+
+
<div class="card overall">
+
<div class="card-title">📈 Overall Performance</div>
+
<div class="grid" style="grid-template-columns: repeat(3, 1fr);">
+
<div class="stat">
+
<div class="stat-label">Total Hits</div>
+
<div class="stat-value">${stats.totalHits}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Total Misses</div>
+
<div class="stat-value">${stats.totalMisses}</div>
+
</div>
+
<div class="stat">
+
<div class="stat-label">Hit Rate</div>
+
<div class="stat-value">${(stats.hitRate * 100).toFixed(1)}%</div>
+
</div>
+
</div>
+
</div>
+
+
<div style="margin-top: 2rem; padding: 1rem; background: #1e293b; border-radius: 0.5rem; border: 1px solid #334155;">
+
<p style="margin-bottom: 0.5rem;"><strong>Try it out:</strong></p>
+
<p>Visit <code>http://localhost:${PORT}/sites/${siteId}/${siteName}/</code> to see the site</p>
+
<p>Watch the stats update as you browse different pages!</p>
+
</div>
+
</div>
+
+
<script>
+
// Auto-refresh stats every 5 seconds
+
setTimeout(() => window.location.reload(), 5000);
+
</script>
+
</body>
+
</html>
+
`;
+
+
return c.html(html);
+
});
+
+
/**
+
* Admin endpoint: Invalidate cache
+
*/
+
app.post('/admin/invalidate/:did/:siteName', async (c) => {
+
const { did, siteName } = c.req.param();
+
const prefix = `${did}/${siteName}/`;
+
const deleted = await storage.invalidate(prefix);
+
+
console.log(`🗑️ Invalidated ${deleted} files for ${did}/${siteName}`);
+
+
return c.json({ success: true, deleted, prefix });
+
});
+
+
/**
+
* Admin endpoint: Bootstrap hot cache
+
*/
+
app.post('/admin/bootstrap/hot', async (c) => {
+
const limit = parseInt(c.req.query('limit') || '100', 10);
+
const loaded = await storage.bootstrapHot(limit);
+
+
console.log(`🔥 Bootstrapped ${loaded} items into hot tier`);
+
+
return c.json({ success: true, loaded, limit });
+
});
+
+
/**
+
* Root redirect
+
*/
+
app.get('/', (c) => {
+
return c.redirect(`/sites/${siteId}/${siteName}/`);
+
});
+
+
/**
+
* Health check
+
*/
+
app.get('/health', (c) => c.json({ status: 'ok' }));
+
+
/**
+
* Test S3 connection
+
*/
+
async function testS3Connection() {
+
console.log('\n🔍 Testing S3 connection...\n');
+
+
try {
+
// Try to get stats (which lists objects)
+
const stats = await storage.getStats();
+
console.log(`✅ S3 connection successful!`);
+
console.log(` Found ${stats.cold.items} items (${(stats.cold.bytes / 1024).toFixed(2)} KB)\n`);
+
return true;
+
} catch (error: any) {
+
console.error('❌ S3 connection failed:', error.message);
+
console.error('\nDebug Info:');
+
console.error(` Bucket: ${S3_BUCKET}`);
+
console.error(` Region: ${S3_REGION}`);
+
console.error(` Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
+
console.error(` Access Key: ${AWS_ACCESS_KEY_ID?.substring(0, 8)}...`);
+
console.error(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
+
console.error('\nCommon issues:');
+
console.error(' • Check that bucket exists');
+
console.error(' • Verify credentials are correct');
+
console.error(' • Ensure endpoint URL is correct');
+
console.error(' • Check firewall/network access');
+
console.error(' • For S3-compatible services, verify region name\n');
+
return false;
+
}
+
}
+
+
/**
+
* Periodic cache clearing - demonstrates tier bootstrapping
+
*/
+
function startCacheClearInterval() {
+
const CLEAR_INTERVAL_MS = 60 * 1000; // 1 minute
+
+
setInterval(async () => {
+
console.log('\n' + '═'.repeat(60));
+
console.log('🧹 CACHE CLEAR - Clearing hot and warm tiers...');
+
console.log(' (Cold tier on S3 remains intact)');
+
console.log('═'.repeat(60) + '\n');
+
+
try {
+
// Clear hot tier (memory)
+
if (storage['config'].tiers.hot) {
+
await storage['config'].tiers.hot.clear();
+
console.log('✓ Hot tier (memory) cleared');
+
}
+
+
// Clear warm tier (disk)
+
if (storage['config'].tiers.warm) {
+
await storage['config'].tiers.warm.clear();
+
console.log('✓ Warm tier (disk) cleared');
+
}
+
+
console.log('\n💡 Next request will bootstrap from S3 (cold tier)\n');
+
console.log('─'.repeat(60) + '\n');
+
} catch (error: any) {
+
console.error('❌ Error clearing cache:', error.message);
+
}
+
}, CLEAR_INTERVAL_MS);
+
+
console.log(`⏰ Cache clear interval started (every ${CLEAR_INTERVAL_MS / 1000}s)\n`);
+
}
+
+
/**
+
* Main startup
+
*/
+
async function main() {
+
console.log('╔════════════════════════════════════════════════╗');
+
console.log('║ Tiered Storage Demo Server ║');
+
console.log('╚════════════════════════════════════════════════╝\n');
+
+
console.log('Configuration:');
+
console.log(` S3 Bucket: ${S3_BUCKET}`);
+
console.log(` S3 Region: ${S3_REGION}`);
+
console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
+
console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
+
console.log(` Port: ${PORT}`);
+
+
try {
+
// Test S3 connection first
+
const s3Connected = await testS3Connection();
+
if (!s3Connected) {
+
process.exit(1);
+
}
+
+
// Load the example site
+
await loadExampleSite();
+
+
// Start periodic cache clearing
+
startCacheClearInterval();
+
+
// Start the server
+
console.log('🚀 Starting server...\n');
+
+
const server = Bun.serve({
+
port: PORT,
+
fetch: app.fetch,
+
});
+
+
console.log('╔════════════════════════════════════════════════╗');
+
console.log('║ Server Running! ║');
+
console.log('╚════════════════════════════════════════════════╝\n');
+
console.log(`📍 Demo Site: http://localhost:${PORT}/sites/${siteId}/${siteName}/`);
+
console.log(`📊 Statistics: http://localhost:${PORT}/admin/stats`);
+
console.log(`💚 Health: http://localhost:${PORT}/health`);
+
console.log('\n🎯 Try browsing the site and watch which tier serves each file!\n');
+
console.log('💡 Caches clear every 60 seconds - watch files get re-fetched from S3!\n');
+
if (S3_METADATA_BUCKET) {
+
console.log(`✨ Metadata bucket: ${S3_METADATA_BUCKET} (fast updates enabled!)\n`);
+
} else {
+
console.log('⚠️ No metadata bucket - using legacy mode (slower updates)\n');
+
}
+
console.log('Press Ctrl+C to stop\n');
+
console.log('─'.repeat(60));
+
console.log('Request Log:\n');
+
} catch (error: any) {
+
console.error('\n❌ Failed to start server:', error.message);
+
if (error.message.includes('Forbidden')) {
+
console.error('\nS3 connection issue. Check:');
+
console.error(' 1. Bucket exists on S3 service');
+
console.error(' 2. Credentials are correct');
+
console.error(' 3. Permissions allow read/write');
+
}
+
process.exit(1);
+
}
+
}
+
+
main().catch(console.error);
+682
src/TieredStorage.ts
···
+
import type {
+
TieredStorageConfig,
+
SetOptions,
+
StorageResult,
+
SetResult,
+
StorageMetadata,
+
AllTierStats,
+
StorageSnapshot,
+
} from './types/index.js';
+
import { compress, decompress } from './utils/compression.js';
+
import { defaultSerialize, defaultDeserialize } from './utils/serialization.js';
+
import { calculateChecksum } from './utils/checksum.js';
+
+
/**
+
* Main orchestrator for tiered storage system.
+
*
+
* @typeParam T - The type of data being stored
+
*
+
* @remarks
+
* Implements a cascading containment model:
+
* - **Write Strategy (Cascading Down):** Write to hot → also writes to warm and cold
+
* - **Read Strategy (Bubbling Up):** Check hot first → if miss, check warm → if miss, check cold
+
* - **Bootstrap Strategy:** Hot can bootstrap from warm, warm can bootstrap from cold
+
*
+
* The cold tier is the source of truth and is required.
+
* Hot and warm tiers are optional performance optimizations.
+
*
+
* @example
+
* ```typescript
+
* const storage = new TieredStorage({
+
* tiers: {
+
* hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB
+
* warm: new DiskStorageTier({ directory: './cache' }),
+
* cold: new S3StorageTier({ bucket: 'my-bucket', region: 'us-east-1' }),
+
* },
+
* compression: true,
+
* defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
+
* promotionStrategy: 'lazy',
+
* });
+
*
+
* // Store data (cascades to all tiers)
+
* await storage.set('user:123', { name: 'Alice' });
+
*
+
* // Retrieve data (bubbles up from cold → warm → hot)
+
* const user = await storage.get('user:123');
+
*
+
* // Invalidate all keys with prefix
+
* await storage.invalidate('user:');
+
* ```
+
*/
+
export class TieredStorage<T = unknown> {
+
private serialize: (data: unknown) => Promise<Uint8Array>;
+
private deserialize: (data: Uint8Array) => Promise<unknown>;
+
+
constructor(private config: TieredStorageConfig) {
+
if (!config.tiers.cold) {
+
throw new Error('Cold tier is required');
+
}
+
+
this.serialize = config.serialization?.serialize ?? defaultSerialize;
+
this.deserialize = config.serialization?.deserialize ?? defaultDeserialize;
+
}
+
+
/**
+
* Retrieve data for a key.
+
*
+
* @param key - The key to retrieve
+
* @returns The data, or null if not found or expired
+
*
+
* @remarks
+
* Checks tiers in order: hot → warm → cold.
+
* On cache miss, promotes data to upper tiers based on promotionStrategy.
+
* Automatically handles decompression and deserialization.
+
* Returns null if key doesn't exist or has expired (TTL).
+
*/
+
async get(key: string): Promise<T | null> {
+
const result = await this.getWithMetadata(key);
+
return result ? result.data : null;
+
}
+
+
/**
+
* Retrieve data with metadata and source tier information.
+
*
+
* @param key - The key to retrieve
+
* @returns The data, metadata, and source tier, or null if not found
+
*
+
* @remarks
+
* Use this when you need to know:
+
* - Which tier served the data (for observability)
+
* - Metadata like access count, TTL, checksum
+
* - When the data was created/last accessed
+
*/
+
async getWithMetadata(key: string): Promise<StorageResult<T> | null> {
+
// 1. Check hot tier first
+
if (this.config.tiers.hot) {
+
const data = await this.config.tiers.hot.get(key);
+
if (data) {
+
const metadata = await this.config.tiers.hot.getMetadata(key);
+
if (!metadata) {
+
await this.delete(key);
+
} else if (this.isExpired(metadata)) {
+
await this.delete(key);
+
return null;
+
} else {
+
await this.updateAccessStats(key, 'hot');
+
return {
+
data: (await this.deserializeData(data)) as T,
+
metadata,
+
source: 'hot',
+
};
+
}
+
}
+
}
+
+
// 2. Check warm tier
+
if (this.config.tiers.warm) {
+
const data = await this.config.tiers.warm.get(key);
+
if (data) {
+
const metadata = await this.config.tiers.warm.getMetadata(key);
+
if (!metadata) {
+
await this.delete(key);
+
} else if (this.isExpired(metadata)) {
+
await this.delete(key);
+
return null;
+
} else {
+
if (this.config.tiers.hot && this.config.promotionStrategy === 'eager') {
+
await this.config.tiers.hot.set(key, data, metadata);
+
}
+
+
await this.updateAccessStats(key, 'warm');
+
return {
+
data: (await this.deserializeData(data)) as T,
+
metadata,
+
source: 'warm',
+
};
+
}
+
}
+
}
+
+
// 3. Check cold tier (source of truth)
+
const data = await this.config.tiers.cold.get(key);
+
if (data) {
+
const metadata = await this.config.tiers.cold.getMetadata(key);
+
if (!metadata) {
+
await this.config.tiers.cold.delete(key);
+
return null;
+
}
+
+
if (this.isExpired(metadata)) {
+
await this.delete(key);
+
return null;
+
}
+
+
// Promote to warm and hot (if configured)
+
if (this.config.promotionStrategy === 'eager') {
+
if (this.config.tiers.warm) {
+
await this.config.tiers.warm.set(key, data, metadata);
+
}
+
if (this.config.tiers.hot) {
+
await this.config.tiers.hot.set(key, data, metadata);
+
}
+
}
+
+
await this.updateAccessStats(key, 'cold');
+
return {
+
data: (await this.deserializeData(data)) as T,
+
metadata,
+
source: 'cold',
+
};
+
}
+
+
return null;
+
}
+
+
/**
+
* Store data with optional configuration.
+
*
+
* @param key - The key to store under
+
* @param data - The data to store
+
* @param options - Optional configuration (TTL, metadata, tier skipping)
+
* @returns Information about what was stored and where
+
*
+
* @remarks
+
* Data cascades down through tiers:
+
* - If written to hot, also written to warm and cold
+
* - If written to warm (hot skipped), also written to cold
+
* - Cold is always written (source of truth)
+
*
+
* Use `skipTiers` to control placement. For example:
+
* - Large files: `skipTiers: ['hot']` to avoid memory bloat
+
* - Critical small files: Write to all tiers for fastest access
+
*
+
* Automatically handles serialization and optional compression.
+
*/
+
async set(key: string, data: T, options?: SetOptions): Promise<SetResult> {
+
// 1. Serialize data
+
const serialized = await this.serialize(data);
+
+
// 2. Optionally compress
+
const finalData = this.config.compression ? await compress(serialized) : serialized;
+
+
// 3. Create metadata
+
const metadata = this.createMetadata(key, finalData, options);
+
+
// 4. Write to all tiers (cascading down)
+
const tiersWritten: ('hot' | 'warm' | 'cold')[] = [];
+
+
// Write to hot (if configured and not skipped)
+
if (this.config.tiers.hot && !options?.skipTiers?.includes('hot')) {
+
await this.config.tiers.hot.set(key, finalData, metadata);
+
tiersWritten.push('hot');
+
+
// Hot writes cascade to warm
+
if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) {
+
await this.config.tiers.warm.set(key, finalData, metadata);
+
tiersWritten.push('warm');
+
}
+
} else if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) {
+
// Write to warm (if hot skipped)
+
await this.config.tiers.warm.set(key, finalData, metadata);
+
tiersWritten.push('warm');
+
}
+
+
// Always write to cold (source of truth)
+
await this.config.tiers.cold.set(key, finalData, metadata);
+
tiersWritten.push('cold');
+
+
return { key, metadata, tiersWritten };
+
}
+
+
/**
+
* Delete data from all tiers.
+
*
+
* @param key - The key to delete
+
*
+
* @remarks
+
* Deletes from all configured tiers in parallel.
+
* Does not throw if the key doesn't exist.
+
*/
+
async delete(key: string): Promise<void> {
+
await Promise.all([
+
this.config.tiers.hot?.delete(key),
+
this.config.tiers.warm?.delete(key),
+
this.config.tiers.cold.delete(key),
+
]);
+
}
+
+
/**
+
* Check if a key exists in any tier.
+
*
+
* @param key - The key to check
+
* @returns true if the key exists and hasn't expired
+
*
+
* @remarks
+
* Checks tiers in order: hot → warm → cold.
+
* Returns false if key exists but has expired.
+
*/
+
async exists(key: string): Promise<boolean> {
+
// Check hot first (fastest)
+
if (this.config.tiers.hot && (await this.config.tiers.hot.exists(key))) {
+
const metadata = await this.config.tiers.hot.getMetadata(key);
+
if (metadata && !this.isExpired(metadata)) {
+
return true;
+
}
+
}
+
+
// Check warm
+
if (this.config.tiers.warm && (await this.config.tiers.warm.exists(key))) {
+
const metadata = await this.config.tiers.warm.getMetadata(key);
+
if (metadata && !this.isExpired(metadata)) {
+
return true;
+
}
+
}
+
+
// Check cold (source of truth)
+
if (await this.config.tiers.cold.exists(key)) {
+
const metadata = await this.config.tiers.cold.getMetadata(key);
+
if (metadata && !this.isExpired(metadata)) {
+
return true;
+
}
+
}
+
+
return false;
+
}
+
+
/**
+
* Renew TTL for a key.
+
*
+
* @param key - The key to touch
+
* @param ttlMs - Optional new TTL in milliseconds (uses default if not provided)
+
*
+
* @remarks
+
* Updates the TTL and lastAccessed timestamp in all tiers.
+
* Useful for implementing "keep alive" behavior for actively used keys.
+
* Does nothing if no TTL is configured.
+
*/
+
async touch(key: string, ttlMs?: number): Promise<void> {
+
const ttl = ttlMs ?? this.config.defaultTTL;
+
if (!ttl) return;
+
+
const newTTL = new Date(Date.now() + ttl);
+
+
for (const tier of [this.config.tiers.hot, this.config.tiers.warm, this.config.tiers.cold]) {
+
if (!tier) continue;
+
+
const metadata = await tier.getMetadata(key);
+
if (metadata) {
+
metadata.ttl = newTTL;
+
metadata.lastAccessed = new Date();
+
await tier.setMetadata(key, metadata);
+
}
+
}
+
}
+
+
/**
+
* Invalidate all keys matching a prefix.
+
*
+
* @param prefix - The prefix to match (e.g., 'user:' matches 'user:123', 'user:456')
+
* @returns Number of keys deleted
+
*
+
* @remarks
+
* Useful for bulk invalidation:
+
* - Site invalidation: `invalidate('site:abc:')`
+
* - User invalidation: `invalidate('user:123:')`
+
* - Global invalidation: `invalidate('')` (deletes everything)
+
*
+
* Deletes from all tiers in parallel for efficiency.
+
*/
+
async invalidate(prefix: string): Promise<number> {
+
const keysToDelete = new Set<string>();
+
+
// Collect all keys matching prefix from all tiers
+
if (this.config.tiers.hot) {
+
for await (const key of this.config.tiers.hot.listKeys(prefix)) {
+
keysToDelete.add(key);
+
}
+
}
+
+
if (this.config.tiers.warm) {
+
for await (const key of this.config.tiers.warm.listKeys(prefix)) {
+
keysToDelete.add(key);
+
}
+
}
+
+
for await (const key of this.config.tiers.cold.listKeys(prefix)) {
+
keysToDelete.add(key);
+
}
+
+
// Delete from all tiers in parallel
+
const keys = Array.from(keysToDelete);
+
+
await Promise.all([
+
this.config.tiers.hot?.deleteMany(keys),
+
this.config.tiers.warm?.deleteMany(keys),
+
this.config.tiers.cold.deleteMany(keys),
+
]);
+
+
return keys.length;
+
}
+
+
/**
+
* List all keys, optionally filtered by prefix.
+
*
+
* @param prefix - Optional prefix to filter keys
+
* @returns Async iterator of keys
+
*
+
* @remarks
+
* Returns keys from the cold tier (source of truth).
+
* Memory-efficient - streams keys rather than loading all into memory.
+
*
+
* @example
+
* ```typescript
+
* for await (const key of storage.listKeys('user:')) {
+
* console.log(key);
+
* }
+
* ```
+
*/
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
+
// List from cold tier (source of truth)
+
for await (const key of this.config.tiers.cold.listKeys(prefix)) {
+
yield key;
+
}
+
}
+
+
/**
+
* Get aggregated statistics across all tiers.
+
*
+
* @returns Statistics including size, item count, hits, misses, hit rate
+
*
+
* @remarks
+
* Useful for monitoring and capacity planning.
+
* Hit rate is calculated as: hits / (hits + misses).
+
*/
+
async getStats(): Promise<AllTierStats> {
+
const [hot, warm, cold] = await Promise.all([
+
this.config.tiers.hot?.getStats(),
+
this.config.tiers.warm?.getStats(),
+
this.config.tiers.cold.getStats(),
+
]);
+
+
const totalHits = (hot?.hits ?? 0) + (warm?.hits ?? 0) + (cold?.hits ?? 0);
+
const totalMisses = (hot?.misses ?? 0) + (warm?.misses ?? 0) + (cold?.misses ?? 0);
+
const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
+
+
return {
+
...(hot && { hot }),
+
...(warm && { warm }),
+
cold,
+
totalHits,
+
totalMisses,
+
hitRate,
+
};
+
}
+
+
/**
+
* Clear all data from all tiers.
+
*
+
* @remarks
+
* Use with extreme caution! This will delete all data in the entire storage system.
+
* Cannot be undone.
+
*/
+
async clear(): Promise<void> {
+
await Promise.all([
+
this.config.tiers.hot?.clear(),
+
this.config.tiers.warm?.clear(),
+
this.config.tiers.cold.clear(),
+
]);
+
}
+
+
/**
+
* Clear a specific tier.
+
*
+
* @param tier - Which tier to clear
+
*
+
* @remarks
+
* Useful for:
+
* - Clearing hot tier to test warm/cold performance
+
* - Clearing warm tier to force rebuilding from cold
+
* - Clearing cold tier to start fresh (⚠️ loses source of truth!)
+
*/
+
async clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void> {
+
switch (tier) {
+
case 'hot':
+
await this.config.tiers.hot?.clear();
+
break;
+
case 'warm':
+
await this.config.tiers.warm?.clear();
+
break;
+
case 'cold':
+
await this.config.tiers.cold.clear();
+
break;
+
}
+
}
+
+
/**
+
* Export metadata snapshot for backup or migration.
+
*
+
* @returns Snapshot containing all keys, metadata, and statistics
+
*
+
* @remarks
+
* The snapshot includes metadata but not the actual data (data remains in tiers).
+
* Useful for:
+
* - Backup and restore
+
* - Migration between storage systems
+
* - Auditing and compliance
+
*/
+
async export(): Promise<StorageSnapshot> {
+
const keys: string[] = [];
+
const metadata: Record<string, StorageMetadata> = {};
+
+
// Export from cold tier (source of truth)
+
for await (const key of this.config.tiers.cold.listKeys()) {
+
keys.push(key);
+
const meta = await this.config.tiers.cold.getMetadata(key);
+
if (meta) {
+
metadata[key] = meta;
+
}
+
}
+
+
const stats = await this.getStats();
+
+
return {
+
version: 1,
+
exportedAt: new Date(),
+
keys,
+
metadata,
+
stats,
+
};
+
}
+
+
/**
+
* Import metadata snapshot.
+
*
+
* @param snapshot - Snapshot to import
+
*
+
* @remarks
+
* Validates version compatibility before importing.
+
* Only imports metadata - assumes data already exists in cold tier.
+
*/
+
async import(snapshot: StorageSnapshot): Promise<void> {
+
if (snapshot.version !== 1) {
+
throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
+
}
+
+
// Import metadata into all configured tiers
+
for (const key of snapshot.keys) {
+
const metadata = snapshot.metadata[key];
+
if (!metadata) continue;
+
+
if (this.config.tiers.hot) {
+
await this.config.tiers.hot.setMetadata(key, metadata);
+
}
+
+
if (this.config.tiers.warm) {
+
await this.config.tiers.warm.setMetadata(key, metadata);
+
}
+
+
await this.config.tiers.cold.setMetadata(key, metadata);
+
}
+
}
+
+
/**
+
* Bootstrap hot tier from warm tier.
+
*
+
* @param limit - Optional limit on number of items to load
+
* @returns Number of items loaded
+
*
+
* @remarks
+
* Loads the most frequently accessed items from warm into hot.
+
* Useful for warming up the cache after a restart.
+
* Items are sorted by: accessCount * lastAccessed timestamp (higher is better).
+
*/
+
async bootstrapHot(limit?: number): Promise<number> {
+
if (!this.config.tiers.hot || !this.config.tiers.warm) {
+
return 0;
+
}
+
+
let loaded = 0;
+
const keyMetadata: Array<[string, StorageMetadata]> = [];
+
+
// Load metadata for all keys
+
for await (const key of this.config.tiers.warm.listKeys()) {
+
const metadata = await this.config.tiers.warm.getMetadata(key);
+
if (metadata) {
+
keyMetadata.push([key, metadata]);
+
}
+
}
+
+
// Sort by access count * recency (simple scoring)
+
keyMetadata.sort((a, b) => {
+
const scoreA = a[1].accessCount * a[1].lastAccessed.getTime();
+
const scoreB = b[1].accessCount * b[1].lastAccessed.getTime();
+
return scoreB - scoreA;
+
});
+
+
// Load top N keys into hot tier
+
const keysToLoad = limit ? keyMetadata.slice(0, limit) : keyMetadata;
+
+
for (const [key, metadata] of keysToLoad) {
+
const data = await this.config.tiers.warm.get(key);
+
if (data) {
+
await this.config.tiers.hot.set(key, data, metadata);
+
loaded++;
+
}
+
}
+
+
return loaded;
+
}
+
+
/**
+
* Bootstrap warm tier from cold tier.
+
*
+
* @param options - Optional limit and date filter
+
* @returns Number of items loaded
+
*
+
* @remarks
+
* Loads recent items from cold into warm.
+
* Useful for:
+
* - Initial cache population
+
* - Recovering from warm tier failure
+
* - Migrating to a new warm tier implementation
+
*/
+
async bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number> {
+
if (!this.config.tiers.warm) {
+
return 0;
+
}
+
+
let loaded = 0;
+
+
for await (const key of this.config.tiers.cold.listKeys()) {
+
const metadata = await this.config.tiers.cold.getMetadata(key);
+
if (!metadata) continue;
+
+
// Skip if too old
+
if (options?.sinceDate && metadata.lastAccessed < options.sinceDate) {
+
continue;
+
}
+
+
const data = await this.config.tiers.cold.get(key);
+
if (data) {
+
await this.config.tiers.warm.set(key, data, metadata);
+
loaded++;
+
+
if (options?.limit && loaded >= options.limit) {
+
break;
+
}
+
}
+
}
+
+
return loaded;
+
}
+
+
/**
+
* Check if data has expired based on TTL.
+
*/
+
private isExpired(metadata: StorageMetadata): boolean {
+
if (!metadata.ttl) return false;
+
return Date.now() > metadata.ttl.getTime();
+
}
+
+
/**
+
* Update access statistics for a key.
+
*/
+
private async updateAccessStats(key: string, tier: 'hot' | 'warm' | 'cold'): Promise<void> {
+
const tierObj =
+
tier === 'hot'
+
? this.config.tiers.hot
+
: tier === 'warm'
+
? this.config.tiers.warm
+
: this.config.tiers.cold;
+
+
if (!tierObj) return;
+
+
const metadata = await tierObj.getMetadata(key);
+
if (metadata) {
+
metadata.lastAccessed = new Date();
+
metadata.accessCount++;
+
await tierObj.setMetadata(key, metadata);
+
}
+
}
+
+
/**
+
* Create metadata for new data.
+
*/
+
private createMetadata(key: string, data: Uint8Array, options?: SetOptions): StorageMetadata {
+
const now = new Date();
+
const ttl = options?.ttl ?? this.config.defaultTTL;
+
+
const metadata: StorageMetadata = {
+
key,
+
size: data.byteLength,
+
createdAt: now,
+
lastAccessed: now,
+
accessCount: 0,
+
compressed: this.config.compression ?? false,
+
checksum: calculateChecksum(data),
+
};
+
+
if (ttl) {
+
metadata.ttl = new Date(now.getTime() + ttl);
+
}
+
+
if (options?.metadata) {
+
metadata.customMetadata = options.metadata;
+
}
+
+
return metadata;
+
}
+
+
/**
+
* Deserialize data, handling compression automatically.
+
*/
+
private async deserializeData(data: Uint8Array): Promise<unknown> {
+
// Decompress if needed (check for gzip magic bytes)
+
const finalData =
+
this.config.compression && data[0] === 0x1f && data[1] === 0x8b
+
? await decompress(data)
+
: data;
+
+
return this.deserialize(finalData);
+
}
+
}
+35
src/index.ts
···
+
/**
+
* Tiered Storage Library
+
*
+
* A lightweight, pluggable tiered storage library that orchestrates caching across
+
* hot (memory), warm (disk/database), and cold (S3/object storage) tiers.
+
*
+
* @packageDocumentation
+
*/
+
+
// Main class
+
export { TieredStorage } from './TieredStorage.js';
+
+
// Built-in tier implementations
+
export { MemoryStorageTier, type MemoryStorageTierConfig } from './tiers/MemoryStorageTier.js';
+
export { DiskStorageTier, type DiskStorageTierConfig, type EvictionPolicy } from './tiers/DiskStorageTier.js';
+
export { S3StorageTier, type S3StorageTierConfig } from './tiers/S3StorageTier.js';
+
+
// Types
+
export type {
+
StorageTier,
+
StorageMetadata,
+
TierStats,
+
AllTierStats,
+
TieredStorageConfig,
+
SetOptions,
+
StorageResult,
+
SetResult,
+
StorageSnapshot,
+
} from './types/index.js';
+
+
// Utilities
+
export { compress, decompress, isGzipped } from './utils/compression.js';
+
export { defaultSerialize, defaultDeserialize } from './utils/serialization.js';
+
export { calculateChecksum, verifyChecksum } from './utils/checksum.js';
+
export { encodeKey, decodeKey } from './utils/path-encoding.js';
+367
src/tiers/DiskStorageTier.ts
···
+
import { readFile, writeFile, unlink, readdir, stat, mkdir, rm, rename } from 'node:fs/promises';
+
import { existsSync } from 'node:fs';
+
import { join, dirname } from 'node:path';
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
+
import { encodeKey } from '../utils/path-encoding.js';
+
+
/**
+
* Eviction policy for disk tier when size limit is reached.
+
*/
+
export type EvictionPolicy = 'lru' | 'fifo' | 'size';
+
+
/**
+
* Configuration for DiskStorageTier.
+
*/
+
export interface DiskStorageTierConfig {
+
/**
+
* Directory path where files will be stored.
+
*
+
* @remarks
+
* Created automatically if it doesn't exist.
+
* Files are stored as: `{directory}/{encoded-key}`
+
* Metadata is stored as: `{directory}/{encoded-key}.meta`
+
*/
+
directory: string;
+
+
/**
+
* Optional maximum size in bytes.
+
*
+
* @remarks
+
* When this limit is reached, files are evicted according to the eviction policy.
+
* If not set, no size limit is enforced (grows unbounded).
+
*/
+
maxSizeBytes?: number;
+
+
/**
+
* Eviction policy when maxSizeBytes is reached.
+
*
+
* @defaultValue 'lru'
+
*
+
* @remarks
+
* - 'lru': Evict least-recently-accessed files (based on metadata.lastAccessed)
+
* - 'fifo': Evict oldest files (based on metadata.createdAt)
+
* - 'size': Evict largest files first
+
*/
+
evictionPolicy?: EvictionPolicy;
+
}
+
+
/**
+
* Filesystem-based storage tier.
+
*
+
* @remarks
+
* - Stores data files and `.meta` JSON files side-by-side
+
* - Keys are encoded to be filesystem-safe
+
* - Human-readable file structure for debugging
+
* - Optional size-based eviction with configurable policy
+
* - Zero external dependencies (uses Node.js fs APIs)
+
*
+
* File structure:
+
* ```
+
* cache/
+
* ├── user%3A123 # Data file (encoded key)
+
* ├── user%3A123.meta # Metadata JSON
+
* ├── site%3Aabc%2Findex.html
+
* └── site%3Aabc%2Findex.html.meta
+
* ```
+
*
+
* @example
+
* ```typescript
+
* const tier = new DiskStorageTier({
+
* directory: './cache',
+
* maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB
+
* evictionPolicy: 'lru',
+
* });
+
*
+
* await tier.set('key', data, metadata);
+
* const retrieved = await tier.get('key');
+
* ```
+
*/
+
export class DiskStorageTier implements StorageTier {
+
private metadataIndex = new Map<
+
string,
+
{ size: number; createdAt: Date; lastAccessed: Date }
+
>();
+
private currentSize = 0;
+
+
constructor(private config: DiskStorageTierConfig) {
+
if (!config.directory) {
+
throw new Error('directory is required');
+
}
+
if (config.maxSizeBytes !== undefined && config.maxSizeBytes <= 0) {
+
throw new Error('maxSizeBytes must be positive');
+
}
+
+
void this.ensureDirectory();
+
void this.rebuildIndex();
+
}
+
+
private async rebuildIndex(): Promise<void> {
+
if (!existsSync(this.config.directory)) {
+
return;
+
}
+
+
const files = await readdir(this.config.directory);
+
+
for (const file of files) {
+
if (file.endsWith('.meta')) {
+
continue;
+
}
+
+
try {
+
const metaPath = join(this.config.directory, `${file}.meta`);
+
const metaContent = await readFile(metaPath, 'utf-8');
+
const metadata = JSON.parse(metaContent) as StorageMetadata;
+
const filePath = join(this.config.directory, file);
+
const fileStats = await stat(filePath);
+
+
this.metadataIndex.set(metadata.key, {
+
size: fileStats.size,
+
createdAt: new Date(metadata.createdAt),
+
lastAccessed: new Date(metadata.lastAccessed),
+
});
+
+
this.currentSize += fileStats.size;
+
} catch {
+
continue;
+
}
+
}
+
}
+
+
async get(key: string): Promise<Uint8Array | null> {
+
const filePath = this.getFilePath(key);
+
+
try {
+
const data = await readFile(filePath);
+
+
const metadata = await this.getMetadata(key);
+
if (metadata) {
+
metadata.lastAccessed = new Date();
+
metadata.accessCount++;
+
await this.setMetadata(key, metadata);
+
+
const entry = this.metadataIndex.get(key);
+
if (entry) {
+
entry.lastAccessed = metadata.lastAccessed;
+
}
+
}
+
+
return new Uint8Array(data);
+
} catch (error) {
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
+
return null;
+
}
+
throw error;
+
}
+
}
+
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
+
const filePath = this.getFilePath(key);
+
const metaPath = this.getMetaPath(key);
+
+
const dir = dirname(filePath);
+
if (!existsSync(dir)) {
+
await mkdir(dir, { recursive: true });
+
}
+
+
const existingEntry = this.metadataIndex.get(key);
+
if (existingEntry) {
+
this.currentSize -= existingEntry.size;
+
}
+
+
if (this.config.maxSizeBytes) {
+
await this.evictIfNeeded(data.byteLength);
+
}
+
+
const tempMetaPath = `${metaPath}.tmp`;
+
await writeFile(tempMetaPath, JSON.stringify(metadata, null, 2));
+
await writeFile(filePath, data);
+
await rename(tempMetaPath, metaPath);
+
+
this.metadataIndex.set(key, {
+
size: data.byteLength,
+
createdAt: metadata.createdAt,
+
lastAccessed: metadata.lastAccessed,
+
});
+
this.currentSize += data.byteLength;
+
}
+
+
async delete(key: string): Promise<void> {
+
const filePath = this.getFilePath(key);
+
const metaPath = this.getMetaPath(key);
+
+
const entry = this.metadataIndex.get(key);
+
if (entry) {
+
this.currentSize -= entry.size;
+
this.metadataIndex.delete(key);
+
}
+
+
await Promise.all([
+
unlink(filePath).catch(() => {}),
+
unlink(metaPath).catch(() => {}),
+
]);
+
}
+
+
async exists(key: string): Promise<boolean> {
+
const filePath = this.getFilePath(key);
+
return existsSync(filePath);
+
}
+
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
+
if (!existsSync(this.config.directory)) {
+
return;
+
}
+
+
const files = await readdir(this.config.directory);
+
+
for (const file of files) {
+
// Skip metadata files
+
if (file.endsWith('.meta')) {
+
continue;
+
}
+
+
// The file name is the encoded key
+
// We need to read metadata to get the original key for prefix matching
+
const metaPath = join(this.config.directory, `${file}.meta`);
+
try {
+
const metaContent = await readFile(metaPath, 'utf-8');
+
const metadata = JSON.parse(metaContent) as StorageMetadata;
+
const originalKey = metadata.key;
+
+
if (!prefix || originalKey.startsWith(prefix)) {
+
yield originalKey;
+
}
+
} catch {
+
// If metadata is missing or invalid, skip this file
+
continue;
+
}
+
}
+
}
+
+
async deleteMany(keys: string[]): Promise<void> {
+
await Promise.all(keys.map((key) => this.delete(key)));
+
}
+
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
+
const metaPath = this.getMetaPath(key);
+
+
try {
+
const content = await readFile(metaPath, 'utf-8');
+
const metadata = JSON.parse(content) as StorageMetadata;
+
+
// Convert date strings back to Date objects
+
metadata.createdAt = new Date(metadata.createdAt);
+
metadata.lastAccessed = new Date(metadata.lastAccessed);
+
if (metadata.ttl) {
+
metadata.ttl = new Date(metadata.ttl);
+
}
+
+
return metadata;
+
} catch (error) {
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
+
return null;
+
}
+
throw error;
+
}
+
}
+
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
+
const metaPath = this.getMetaPath(key);
+
+
// Ensure parent directory exists
+
const dir = dirname(metaPath);
+
if (!existsSync(dir)) {
+
await mkdir(dir, { recursive: true });
+
}
+
+
await writeFile(metaPath, JSON.stringify(metadata, null, 2));
+
}
+
+
async getStats(): Promise<TierStats> {
+
let bytes = 0;
+
let items = 0;
+
+
if (!existsSync(this.config.directory)) {
+
return { bytes: 0, items: 0 };
+
}
+
+
const files = await readdir(this.config.directory);
+
+
for (const file of files) {
+
if (file.endsWith('.meta')) {
+
continue;
+
}
+
+
const filePath = join(this.config.directory, file);
+
const stats = await stat(filePath);
+
bytes += stats.size;
+
items++;
+
}
+
+
return { bytes, items };
+
}
+
+
async clear(): Promise<void> {
+
if (existsSync(this.config.directory)) {
+
await rm(this.config.directory, { recursive: true, force: true });
+
await this.ensureDirectory();
+
this.metadataIndex.clear();
+
this.currentSize = 0;
+
}
+
}
+
+
/**
+
* Get the filesystem path for a key's data file.
+
*/
+
private getFilePath(key: string): string {
+
const encoded = encodeKey(key);
+
return join(this.config.directory, encoded);
+
}
+
+
/**
+
* Get the filesystem path for a key's metadata file.
+
*/
+
private getMetaPath(key: string): string {
+
return `${this.getFilePath(key)}.meta`;
+
}
+
+
private async ensureDirectory(): Promise<void> {
+
await mkdir(this.config.directory, { recursive: true }).catch(() => {});
+
}
+
+
private async evictIfNeeded(incomingSize: number): Promise<void> {
+
if (!this.config.maxSizeBytes) {
+
return;
+
}
+
+
if (this.currentSize + incomingSize <= this.config.maxSizeBytes) {
+
return;
+
}
+
+
const entries = Array.from(this.metadataIndex.entries()).map(([key, info]) => ({
+
key,
+
...info,
+
}));
+
+
const policy = this.config.evictionPolicy ?? 'lru';
+
entries.sort((a, b) => {
+
switch (policy) {
+
case 'lru':
+
return a.lastAccessed.getTime() - b.lastAccessed.getTime();
+
case 'fifo':
+
return a.createdAt.getTime() - b.createdAt.getTime();
+
case 'size':
+
return b.size - a.size;
+
default:
+
return 0;
+
}
+
});
+
+
for (const entry of entries) {
+
if (this.currentSize + incomingSize <= this.config.maxSizeBytes) {
+
break;
+
}
+
+
await this.delete(entry.key);
+
}
+
}
+
}
+195
src/tiers/MemoryStorageTier.ts
···
+
import { lru } from 'tiny-lru';
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
+
+
interface CacheEntry {
+
data: Uint8Array;
+
metadata: StorageMetadata;
+
size: number;
+
}
+
+
/**
+
* Configuration for MemoryStorageTier.
+
*/
+
export interface MemoryStorageTierConfig {
+
/**
+
* Maximum total size in bytes.
+
*
+
* @remarks
+
* When this limit is reached, least-recently-used entries are evicted.
+
*/
+
maxSizeBytes: number;
+
+
/**
+
* Maximum number of items.
+
*
+
* @remarks
+
* When this limit is reached, least-recently-used entries are evicted.
+
* Useful for limiting memory usage when items have variable sizes.
+
*/
+
maxItems?: number;
+
}
+
+
/**
+
* In-memory storage tier using TinyLRU for LRU eviction.
+
*
+
* @remarks
+
* - Uses the battle-tested TinyLRU library for efficient LRU caching
+
* - Automatically evicts least-recently-used entries when limits are reached
+
* - Not distributed - single process only
+
* - Data is lost on process restart (use warm/cold tiers for persistence)
+
* - Implements both size-based and count-based eviction
+
*
+
* @example
+
* ```typescript
+
* const tier = new MemoryStorageTier({
+
* maxSizeBytes: 100 * 1024 * 1024, // 100MB
+
* maxItems: 1000,
+
* });
+
*
+
* await tier.set('key', data, metadata);
+
* const retrieved = await tier.get('key');
+
* ```
+
*/
+
export class MemoryStorageTier implements StorageTier {
+
private cache: ReturnType<typeof lru<CacheEntry>>;
+
private currentSize = 0;
+
private stats = {
+
hits: 0,
+
misses: 0,
+
evictions: 0,
+
};
+
+
constructor(private config: MemoryStorageTierConfig) {
+
if (config.maxSizeBytes <= 0) {
+
throw new Error('maxSizeBytes must be positive');
+
}
+
if (config.maxItems !== undefined && config.maxItems <= 0) {
+
throw new Error('maxItems must be positive');
+
}
+
+
// Initialize TinyLRU with max items (we'll handle size limits separately)
+
const maxItems = config.maxItems ?? 10000; // Default to 10k items if not specified
+
this.cache = lru<CacheEntry>(maxItems);
+
}
+
+
async get(key: string): Promise<Uint8Array | null> {
+
const entry = this.cache.get(key);
+
+
if (!entry) {
+
this.stats.misses++;
+
return null;
+
}
+
+
this.stats.hits++;
+
return entry.data;
+
}
+
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
+
const size = data.byteLength;
+
+
// Check existing entry for size accounting
+
const existing = this.cache.get(key);
+
if (existing) {
+
this.currentSize -= existing.size;
+
}
+
+
// Evict entries until we have space for the new entry
+
await this.evictIfNeeded(size);
+
+
// Add new entry
+
const entry: CacheEntry = { data, metadata, size };
+
this.cache.set(key, entry);
+
this.currentSize += size;
+
}
+
+
async delete(key: string): Promise<void> {
+
const entry = this.cache.get(key);
+
if (entry) {
+
this.cache.delete(key);
+
this.currentSize -= entry.size;
+
}
+
}
+
+
async exists(key: string): Promise<boolean> {
+
return this.cache.has(key);
+
}
+
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
+
// TinyLRU doesn't expose keys(), so we need to track them separately
+
// For now, we'll use the cache's internal structure
+
const keys = this.cache.keys();
+
for (const key of keys) {
+
if (!prefix || key.startsWith(prefix)) {
+
yield key;
+
}
+
}
+
}
+
+
async deleteMany(keys: string[]): Promise<void> {
+
for (const key of keys) {
+
await this.delete(key);
+
}
+
}
+
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
+
const entry = this.cache.get(key);
+
return entry ? entry.metadata : null;
+
}
+
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
+
const entry = this.cache.get(key);
+
if (entry) {
+
// Update metadata in place
+
entry.metadata = metadata;
+
// Re-set to mark as recently used
+
this.cache.set(key, entry);
+
}
+
}
+
+
async getStats(): Promise<TierStats> {
+
return {
+
bytes: this.currentSize,
+
items: this.cache.size,
+
hits: this.stats.hits,
+
misses: this.stats.misses,
+
evictions: this.stats.evictions,
+
};
+
}
+
+
async clear(): Promise<void> {
+
this.cache.clear();
+
this.currentSize = 0;
+
}
+
+
/**
+
* Evict least-recently-used entries until there's space for new data.
+
*
+
* @param incomingSize - Size of data being added
+
*
+
* @remarks
+
* TinyLRU handles count-based eviction automatically.
+
* This method handles size-based eviction by using TinyLRU's built-in evict() method,
+
* which properly removes the LRU item without updating access order.
+
*/
+
private async evictIfNeeded(incomingSize: number): Promise<void> {
+
// Keep evicting until we have enough space
+
while (this.currentSize + incomingSize > this.config.maxSizeBytes && this.cache.size > 0) {
+
// Get the LRU key (first in the list) without accessing it
+
const keys = this.cache.keys();
+
if (keys.length === 0) break;
+
+
const lruKey = keys[0];
+
if (!lruKey) break;
+
+
// Access the entry directly from internal items without triggering LRU update
+
// TinyLRU exposes items as a public property for this purpose
+
const entry = (this.cache as any).items[lruKey] as CacheEntry | undefined;
+
if (!entry) break;
+
+
// Use TinyLRU's built-in evict() which properly removes the LRU item
+
this.cache.evict();
+
this.currentSize -= entry.size;
+
this.stats.evictions++;
+
}
+
}
+
}
+567
src/tiers/S3StorageTier.ts
···
+
import {
+
S3Client,
+
PutObjectCommand,
+
GetObjectCommand,
+
DeleteObjectCommand,
+
HeadObjectCommand,
+
ListObjectsV2Command,
+
DeleteObjectsCommand,
+
CopyObjectCommand,
+
type S3ClientConfig,
+
} from '@aws-sdk/client-s3';
+
import type { Readable } from 'node:stream';
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
+
+
/**
+
* Configuration for S3StorageTier.
+
*/
+
export interface S3StorageTierConfig {
+
/**
+
* S3 bucket name.
+
*/
+
bucket: string;
+
+
/**
+
* AWS region.
+
*/
+
region: string;
+
+
/**
+
* Optional S3-compatible endpoint (for R2, Minio, etc.).
+
*
+
* @example 'https://s3.us-east-1.amazonaws.com'
+
* @example 'https://account-id.r2.cloudflarestorage.com'
+
*/
+
endpoint?: string;
+
+
/**
+
* Optional AWS credentials.
+
*
+
* @remarks
+
* If not provided, uses the default AWS credential chain
+
* (environment variables, ~/.aws/credentials, IAM roles, etc.)
+
*/
+
credentials?: {
+
accessKeyId: string;
+
secretAccessKey: string;
+
};
+
+
/**
+
* Optional key prefix for namespacing.
+
*
+
* @remarks
+
* All keys will be prefixed with this value.
+
* Useful for multi-tenant scenarios or organizing data.
+
*
+
* @example 'tiered-storage/'
+
*/
+
prefix?: string;
+
+
/**
+
* Force path-style addressing for S3-compatible services.
+
*
+
* @defaultValue true
+
*
+
* @remarks
+
* Most S3-compatible services (MinIO, R2, etc.) require path-style URLs.
+
* AWS S3 uses virtual-hosted-style by default, but path-style also works.
+
*
+
* - true: `https://endpoint.com/bucket/key` (path-style)
+
* - false: `https://bucket.endpoint.com/key` (virtual-hosted-style)
+
*/
+
forcePathStyle?: boolean;
+
+
/**
+
* Optional separate bucket for storing metadata.
+
*
+
* @remarks
+
* **RECOMMENDED for production use!**
+
*
+
* By default, metadata is stored in S3 object metadata fields. However, updating
+
* metadata requires copying the entire object, which is slow and expensive for large files.
+
*
+
* When `metadataBucket` is specified, metadata is stored as separate JSON objects
+
* in this bucket. This allows fast, cheap metadata updates without copying data.
+
*
+
* **Benefits:**
+
* - Fast metadata updates (no object copying)
+
* - Much cheaper for large objects
+
* - No impact on data object performance
+
*
+
* **Trade-offs:**
+
* - Requires managing two buckets
+
* - Metadata and data could become out of sync if not handled carefully
+
* - Additional S3 API calls for metadata operations
+
*
+
* @example
+
* ```typescript
+
* const tier = new S3StorageTier({
+
* bucket: 'my-data-bucket',
+
* metadataBucket: 'my-metadata-bucket', // Separate bucket for metadata
+
* region: 'us-east-1',
+
* });
+
* ```
+
*/
+
metadataBucket?: string;
+
}
+
+
/**
+
* AWS S3 (or compatible) storage tier.
+
*
+
* @remarks
+
* - Supports AWS S3, Cloudflare R2, MinIO, and other S3-compatible services
+
* - Uses object metadata for StorageMetadata
+
* - Requires `@aws-sdk/client-s3` peer dependency
+
* - Typically used as the cold tier (source of truth)
+
*
+
* **Metadata Storage:**
+
* Metadata is stored in S3 object metadata fields:
+
* - Custom metadata fields are prefixed with `x-amz-meta-`
+
* - Built-in fields use standard S3 headers
+
*
+
* @example
+
* ```typescript
+
* const tier = new S3StorageTier({
+
* bucket: 'my-bucket',
+
* region: 'us-east-1',
+
* credentials: {
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
+
* },
+
* prefix: 'cache/',
+
* });
+
* ```
+
*
+
* @example Cloudflare R2
+
* ```typescript
+
* const tier = new S3StorageTier({
+
* bucket: 'my-bucket',
+
* region: 'auto',
+
* endpoint: 'https://account-id.r2.cloudflarestorage.com',
+
* credentials: {
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
+
* },
+
* });
+
* ```
+
*/
+
export class S3StorageTier implements StorageTier {
+
private client: S3Client;
+
private prefix: string;
+
private metadataBucket?: string;
+
+
constructor(private config: S3StorageTierConfig) {
+
const clientConfig: S3ClientConfig = {
+
region: config.region,
+
// Most S3-compatible services need path-style URLs
+
forcePathStyle: config.forcePathStyle ?? true,
+
...(config.endpoint && { endpoint: config.endpoint }),
+
...(config.credentials && { credentials: config.credentials }),
+
};
+
+
this.client = new S3Client(clientConfig);
+
this.prefix = config.prefix ?? '';
+
if (config.metadataBucket) {
+
this.metadataBucket = config.metadataBucket;
+
}
+
}
+
+
async get(key: string): Promise<Uint8Array | null> {
+
try {
+
const command = new GetObjectCommand({
+
Bucket: this.config.bucket,
+
Key: this.getS3Key(key),
+
});
+
+
const response = await this.client.send(command);
+
+
if (!response.Body) {
+
return null;
+
}
+
+
return await this.streamToUint8Array(response.Body as Readable);
+
} catch (error) {
+
if (this.isNoSuchKeyError(error)) {
+
return null;
+
}
+
throw error;
+
}
+
}
+
+
private async streamToUint8Array(stream: Readable): Promise<Uint8Array> {
+
const chunks: Uint8Array[] = [];
+
+
for await (const chunk of stream) {
+
if (Buffer.isBuffer(chunk)) {
+
chunks.push(new Uint8Array(chunk));
+
} else if (chunk instanceof Uint8Array) {
+
chunks.push(chunk);
+
} else {
+
throw new Error('Unexpected chunk type in S3 stream');
+
}
+
}
+
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
+
const result = new Uint8Array(totalLength);
+
let offset = 0;
+
for (const chunk of chunks) {
+
result.set(chunk, offset);
+
offset += chunk.length;
+
}
+
+
return result;
+
}
+
+
private isNoSuchKeyError(error: unknown): boolean {
+
return (
+
typeof error === 'object' &&
+
error !== null &&
+
'name' in error &&
+
(error.name === 'NoSuchKey' || error.name === 'NotFound')
+
);
+
}
+
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
+
const s3Key = this.getS3Key(key);
+
+
if (this.metadataBucket) {
+
const dataCommand = new PutObjectCommand({
+
Bucket: this.config.bucket,
+
Key: s3Key,
+
Body: data,
+
ContentLength: data.byteLength,
+
});
+
+
const metadataJson = JSON.stringify(metadata);
+
const metadataBuffer = new TextEncoder().encode(metadataJson);
+
const metadataCommand = new PutObjectCommand({
+
Bucket: this.metadataBucket,
+
Key: s3Key + '.meta',
+
Body: metadataBuffer,
+
ContentType: 'application/json',
+
});
+
+
await Promise.all([
+
this.client.send(dataCommand),
+
this.client.send(metadataCommand),
+
]);
+
} else {
+
const command = new PutObjectCommand({
+
Bucket: this.config.bucket,
+
Key: s3Key,
+
Body: data,
+
ContentLength: data.byteLength,
+
Metadata: this.metadataToS3(metadata),
+
});
+
+
await this.client.send(command);
+
}
+
}
+
+
async delete(key: string): Promise<void> {
+
const s3Key = this.getS3Key(key);
+
+
try {
+
const dataCommand = new DeleteObjectCommand({
+
Bucket: this.config.bucket,
+
Key: s3Key,
+
});
+
+
if (this.metadataBucket) {
+
const metadataCommand = new DeleteObjectCommand({
+
Bucket: this.metadataBucket,
+
Key: s3Key + '.meta',
+
});
+
+
await Promise.all([
+
this.client.send(dataCommand),
+
this.client.send(metadataCommand).catch((error) => {
+
if (!this.isNoSuchKeyError(error)) throw error;
+
}),
+
]);
+
} else {
+
await this.client.send(dataCommand);
+
}
+
} catch (error) {
+
if (!this.isNoSuchKeyError(error)) {
+
throw error;
+
}
+
}
+
}
+
+
async exists(key: string): Promise<boolean> {
+
try {
+
const command = new HeadObjectCommand({
+
Bucket: this.config.bucket,
+
Key: this.getS3Key(key),
+
});
+
+
await this.client.send(command);
+
return true;
+
} catch (error) {
+
if (this.isNoSuchKeyError(error)) {
+
return false;
+
}
+
throw error;
+
}
+
}
+
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
+
const s3Prefix = prefix ? this.getS3Key(prefix) : this.prefix;
+
let continuationToken: string | undefined;
+
+
do {
+
const command = new ListObjectsV2Command({
+
Bucket: this.config.bucket,
+
Prefix: s3Prefix,
+
ContinuationToken: continuationToken,
+
});
+
+
const response = await this.client.send(command);
+
+
if (response.Contents) {
+
for (const object of response.Contents) {
+
if (object.Key) {
+
// Remove prefix to get original key
+
const key = this.removePrefix(object.Key);
+
yield key;
+
}
+
}
+
}
+
+
continuationToken = response.NextContinuationToken;
+
} while (continuationToken);
+
}
+
+
async deleteMany(keys: string[]): Promise<void> {
+
if (keys.length === 0) return;
+
+
const batchSize = 1000;
+
+
for (let i = 0; i < keys.length; i += batchSize) {
+
const batch = keys.slice(i, i + batchSize);
+
+
const dataCommand = new DeleteObjectsCommand({
+
Bucket: this.config.bucket,
+
Delete: {
+
Objects: batch.map((key) => ({ Key: this.getS3Key(key) })),
+
},
+
});
+
+
if (this.metadataBucket) {
+
const metadataCommand = new DeleteObjectsCommand({
+
Bucket: this.metadataBucket,
+
Delete: {
+
Objects: batch.map((key) => ({ Key: this.getS3Key(key) + '.meta' })),
+
},
+
});
+
+
await Promise.all([
+
this.client.send(dataCommand),
+
this.client.send(metadataCommand).catch(() => {}),
+
]);
+
} else {
+
await this.client.send(dataCommand);
+
}
+
}
+
}
+
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
+
if (this.metadataBucket) {
+
try {
+
const command = new GetObjectCommand({
+
Bucket: this.metadataBucket,
+
Key: this.getS3Key(key) + '.meta',
+
});
+
+
const response = await this.client.send(command);
+
+
if (!response.Body) {
+
return null;
+
}
+
+
const buffer = await this.streamToUint8Array(response.Body as Readable);
+
const json = new TextDecoder().decode(buffer);
+
const metadata = JSON.parse(json) as StorageMetadata;
+
+
metadata.createdAt = new Date(metadata.createdAt);
+
metadata.lastAccessed = new Date(metadata.lastAccessed);
+
if (metadata.ttl) {
+
metadata.ttl = new Date(metadata.ttl);
+
}
+
+
return metadata;
+
} catch (error) {
+
if (this.isNoSuchKeyError(error)) {
+
return null;
+
}
+
throw error;
+
}
+
}
+
+
try {
+
const command = new HeadObjectCommand({
+
Bucket: this.config.bucket,
+
Key: this.getS3Key(key),
+
});
+
+
const response = await this.client.send(command);
+
+
if (!response.Metadata) {
+
return null;
+
}
+
+
return this.s3ToMetadata(response.Metadata);
+
} catch (error) {
+
if (this.isNoSuchKeyError(error)) {
+
return null;
+
}
+
throw error;
+
}
+
}
+
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
+
if (this.metadataBucket) {
+
const metadataJson = JSON.stringify(metadata);
+
const buffer = new TextEncoder().encode(metadataJson);
+
+
const command = new PutObjectCommand({
+
Bucket: this.metadataBucket,
+
Key: this.getS3Key(key) + '.meta',
+
Body: buffer,
+
ContentType: 'application/json',
+
});
+
+
await this.client.send(command);
+
return;
+
}
+
+
const s3Key = this.getS3Key(key);
+
const command = new CopyObjectCommand({
+
Bucket: this.config.bucket,
+
Key: s3Key,
+
CopySource: `${this.config.bucket}/${s3Key}`,
+
Metadata: this.metadataToS3(metadata),
+
MetadataDirective: 'REPLACE',
+
});
+
+
await this.client.send(command);
+
}
+
+
async getStats(): Promise<TierStats> {
+
let bytes = 0;
+
let items = 0;
+
+
// List all objects and sum up sizes
+
let continuationToken: string | undefined;
+
+
do {
+
const command = new ListObjectsV2Command({
+
Bucket: this.config.bucket,
+
Prefix: this.prefix,
+
ContinuationToken: continuationToken,
+
});
+
+
const response = await this.client.send(command);
+
+
if (response.Contents) {
+
for (const object of response.Contents) {
+
items++;
+
bytes += object.Size ?? 0;
+
}
+
}
+
+
continuationToken = response.NextContinuationToken;
+
} while (continuationToken);
+
+
return { bytes, items };
+
}
+
+
async clear(): Promise<void> {
+
// List and delete all objects with the prefix
+
const keys: string[] = [];
+
+
for await (const key of this.listKeys()) {
+
keys.push(key);
+
}
+
+
await this.deleteMany(keys);
+
}
+
+
/**
+
* Get the full S3 key including prefix.
+
*/
+
private getS3Key(key: string): string {
+
return this.prefix + key;
+
}
+
+
/**
+
* Remove the prefix from an S3 key to get the original key.
+
*/
+
private removePrefix(s3Key: string): string {
+
if (this.prefix && s3Key.startsWith(this.prefix)) {
+
return s3Key.slice(this.prefix.length);
+
}
+
return s3Key;
+
}
+
+
/**
+
* Convert StorageMetadata to S3 metadata format.
+
*
+
* @remarks
+
* S3 metadata keys must be lowercase and values must be strings.
+
* We serialize complex values as JSON.
+
*/
+
private metadataToS3(metadata: StorageMetadata): Record<string, string> {
+
return {
+
key: metadata.key,
+
size: metadata.size.toString(),
+
createdat: metadata.createdAt.toISOString(),
+
lastaccessed: metadata.lastAccessed.toISOString(),
+
accesscount: metadata.accessCount.toString(),
+
compressed: metadata.compressed.toString(),
+
checksum: metadata.checksum,
+
...(metadata.ttl && { ttl: metadata.ttl.toISOString() }),
+
...(metadata.mimeType && { mimetype: metadata.mimeType }),
+
...(metadata.encoding && { encoding: metadata.encoding }),
+
...(metadata.customMetadata && { custom: JSON.stringify(metadata.customMetadata) }),
+
};
+
}
+
+
/**
+
* Convert S3 metadata to StorageMetadata format.
+
*/
+
private s3ToMetadata(s3Metadata: Record<string, string>): StorageMetadata {
+
const metadata: StorageMetadata = {
+
key: s3Metadata.key ?? '',
+
size: parseInt(s3Metadata.size ?? '0', 10),
+
createdAt: new Date(s3Metadata.createdat ?? Date.now()),
+
lastAccessed: new Date(s3Metadata.lastaccessed ?? Date.now()),
+
accessCount: parseInt(s3Metadata.accesscount ?? '0', 10),
+
compressed: s3Metadata.compressed === 'true',
+
checksum: s3Metadata.checksum ?? '',
+
};
+
+
if (s3Metadata.ttl) {
+
metadata.ttl = new Date(s3Metadata.ttl);
+
}
+
+
if (s3Metadata.mimetype) {
+
metadata.mimeType = s3Metadata.mimetype;
+
}
+
+
if (s3Metadata.encoding) {
+
metadata.encoding = s3Metadata.encoding;
+
}
+
+
if (s3Metadata.custom) {
+
try {
+
metadata.customMetadata = JSON.parse(s3Metadata.custom);
+
} catch {
+
// Ignore invalid JSON
+
}
+
}
+
+
return metadata;
+
}
+
}
+407
src/types/index.ts
···
+
/**
+
* Metadata associated with stored data in a tier.
+
*
+
* @remarks
+
* This metadata is stored alongside the actual data and is used for:
+
* - TTL management and expiration
+
* - Access tracking for LRU/eviction policies
+
* - Data integrity verification via checksum
+
* - Content type information for HTTP serving
+
*/
+
export interface StorageMetadata {
+
/** Original key used to store the data (human-readable) */
+
key: string;
+
+
/** Size of the data in bytes (uncompressed size) */
+
size: number;
+
+
/** Timestamp when the data was first created */
+
createdAt: Date;
+
+
/** Timestamp when the data was last accessed */
+
lastAccessed: Date;
+
+
/** Number of times this data has been accessed */
+
accessCount: number;
+
+
/** Optional expiration timestamp. Data expires when current time > ttl */
+
ttl?: Date;
+
+
/** Whether the data is compressed (e.g., with gzip) */
+
compressed: boolean;
+
+
/** SHA256 checksum of the data for integrity verification */
+
checksum: string;
+
+
/** Optional MIME type (e.g., 'text/html', 'application/json') */
+
mimeType?: string;
+
+
/** Optional encoding (e.g., 'gzip', 'base64') */
+
encoding?: string;
+
+
/** User-defined metadata fields */
+
customMetadata?: Record<string, string>;
+
}
+
+
/**
+
* Statistics for a single storage tier.
+
*
+
* @remarks
+
* Used for monitoring cache performance and capacity planning.
+
*/
+
export interface TierStats {
+
/** Total bytes stored in this tier */
+
bytes: number;
+
+
/** Total number of items stored in this tier */
+
items: number;
+
+
/** Number of cache hits (only tracked if tier implements hit tracking) */
+
hits?: number;
+
+
/** Number of cache misses (only tracked if tier implements miss tracking) */
+
misses?: number;
+
+
/** Number of evictions due to size/count limits (only tracked if tier implements eviction) */
+
evictions?: number;
+
}
+
+
/**
+
* Aggregated statistics across all configured tiers.
+
*
+
* @remarks
+
* Provides a complete view of cache performance across the entire storage hierarchy.
+
*/
+
export interface AllTierStats {
+
/** Statistics for hot tier (if configured) */
+
hot?: TierStats;
+
+
/** Statistics for warm tier (if configured) */
+
warm?: TierStats;
+
+
/** Statistics for cold tier (always present) */
+
cold: TierStats;
+
+
/** Total hits across all tiers */
+
totalHits: number;
+
+
/** Total misses across all tiers */
+
totalMisses: number;
+
+
/** Hit rate as a percentage (0-1) */
+
hitRate: number;
+
}
+
+
/**
+
* Interface that all storage tier implementations must satisfy.
+
*
+
* @remarks
+
* This is the core abstraction that allows pluggable backends.
+
* Implementations can be memory-based (Map, Redis), disk-based (filesystem, SQLite),
+
* or cloud-based (S3, R2, etc.).
+
*
+
* @example
+
* ```typescript
+
* class RedisStorageTier implements StorageTier {
+
* constructor(private client: RedisClient) {}
+
*
+
* async get(key: string): Promise<Uint8Array | null> {
+
* const buffer = await this.client.getBuffer(key);
+
* return buffer ? new Uint8Array(buffer) : null;
+
* }
+
*
+
* // ... implement other methods
+
* }
+
* ```
+
*/
+
export interface StorageTier {
+
/**
+
* Retrieve data for a key.
+
*
+
* @param key - The key to retrieve
+
* @returns The data as a Uint8Array, or null if not found
+
*/
+
get(key: string): Promise<Uint8Array | null>;
+
+
/**
+
* Store data with associated metadata.
+
*
+
* @param key - The key to store under
+
* @param data - The data to store (as Uint8Array)
+
* @param metadata - Metadata to store alongside the data
+
*
+
* @remarks
+
* If the key already exists, it should be overwritten.
+
*/
+
set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void>;
+
+
/**
+
* Delete data for a key.
+
*
+
* @param key - The key to delete
+
*
+
* @remarks
+
* Should not throw if the key doesn't exist.
+
*/
+
delete(key: string): Promise<void>;
+
+
/**
+
* Check if a key exists in this tier.
+
*
+
* @param key - The key to check
+
* @returns true if the key exists, false otherwise
+
*/
+
exists(key: string): Promise<boolean>;
+
+
/**
+
* List all keys in this tier, optionally filtered by prefix.
+
*
+
* @param prefix - Optional prefix to filter keys (e.g., 'user:' matches 'user:123', 'user:456')
+
* @returns An async iterator of keys
+
*
+
* @remarks
+
* This should be memory-efficient and stream keys rather than loading all into memory.
+
* Useful for prefix-based invalidation and cache warming.
+
*
+
* @example
+
* ```typescript
+
* for await (const key of tier.listKeys('site:')) {
+
* console.log(key); // 'site:abc', 'site:xyz', etc.
+
* }
+
* ```
+
*/
+
listKeys(prefix?: string): AsyncIterableIterator<string>;
+
+
/**
+
* Delete multiple keys in a single operation.
+
*
+
* @param keys - Array of keys to delete
+
*
+
* @remarks
+
* This is more efficient than calling delete() in a loop.
+
* Implementations should batch deletions where possible.
+
*/
+
deleteMany(keys: string[]): Promise<void>;
+
+
/**
+
* Retrieve metadata for a key without fetching the data.
+
*
+
* @param key - The key to get metadata for
+
* @returns The metadata, or null if not found
+
*
+
* @remarks
+
* This is useful for checking TTL, access counts, etc. without loading large data.
+
*/
+
getMetadata(key: string): Promise<StorageMetadata | null>;
+
+
/**
+
* Update metadata for a key without modifying the data.
+
*
+
* @param key - The key to update metadata for
+
* @param metadata - The new metadata
+
*
+
* @remarks
+
* Useful for updating TTL (via touch()) or access counts.
+
*/
+
setMetadata(key: string, metadata: StorageMetadata): Promise<void>;
+
+
/**
+
* Get statistics about this tier.
+
*
+
* @returns Statistics including size, item count, hits, misses, etc.
+
*/
+
getStats(): Promise<TierStats>;
+
+
/**
+
* Clear all data from this tier.
+
*
+
* @remarks
+
* Use with caution! This will delete all data in the tier.
+
*/
+
clear(): Promise<void>;
+
}
+
+
/**
+
* Configuration for the TieredStorage system.
+
*
+
* @typeParam T - The type of data being stored (for serialization)
+
*
+
* @remarks
+
* The tiered storage system uses a cascading containment model:
+
* - Hot tier (optional): Fastest, smallest capacity (memory/Redis)
+
* - Warm tier (optional): Medium speed, medium capacity (disk/database)
+
* - Cold tier (required): Slowest, unlimited capacity (S3/object storage)
+
*
+
* Data flows down on writes (hot → warm → cold) and bubbles up on reads (cold → warm → hot).
+
*/
+
export interface TieredStorageConfig {
+
/** Storage tier configuration */
+
tiers: {
+
/** Optional hot tier - fastest, smallest capacity (e.g., in-memory, Redis) */
+
hot?: StorageTier;
+
+
/** Optional warm tier - medium speed, medium capacity (e.g., disk, SQLite, Postgres) */
+
warm?: StorageTier;
+
+
/** Required cold tier - slowest, largest capacity (e.g., S3, R2, object storage) */
+
cold: StorageTier;
+
};
+
+
/**
+
* Whether to automatically compress data before storing.
+
*
+
* @defaultValue false
+
*
+
* @remarks
+
* Uses gzip compression. Compression is transparent - data is automatically
+
* decompressed on retrieval. The `compressed` flag in metadata indicates compression state.
+
*/
+
compression?: boolean;
+
+
/**
+
* Default TTL (time-to-live) in milliseconds.
+
*
+
* @remarks
+
* Data will expire after this duration. Can be overridden per-key via SetOptions.
+
* If not set, data never expires.
+
*/
+
defaultTTL?: number;
+
+
/**
+
* Strategy for promoting data to upper tiers on cache miss.
+
*
+
* @defaultValue 'lazy'
+
*
+
* @remarks
+
* - 'eager': Immediately promote data to all upper tiers on read
+
* - 'lazy': Don't automatically promote; rely on explicit promotion or next write
+
*
+
* Eager promotion increases hot tier hit rate but adds write overhead.
+
* Lazy promotion reduces writes but may serve from lower tiers more often.
+
*/
+
promotionStrategy?: 'eager' | 'lazy';
+
+
/**
+
* Custom serialization/deserialization functions.
+
*
+
* @remarks
+
* By default, JSON serialization is used. Provide custom functions for:
+
* - Non-JSON types (e.g., Buffer, custom classes)
+
* - Performance optimization (e.g., msgpack, protobuf)
+
* - Encryption (serialize includes encryption, deserialize includes decryption)
+
*/
+
serialization?: {
+
/** Convert data to Uint8Array for storage */
+
serialize: (data: unknown) => Promise<Uint8Array>;
+
+
/** Convert Uint8Array back to original data */
+
deserialize: (data: Uint8Array) => Promise<unknown>;
+
};
+
}
+
+
/**
+
* Options for setting data in the cache.
+
*
+
* @remarks
+
* These options allow fine-grained control over where and how data is stored.
+
*/
+
export interface SetOptions {
+
/**
+
* Custom TTL in milliseconds for this specific key.
+
*
+
* @remarks
+
* Overrides the default TTL from TieredStorageConfig.
+
* Data will expire after this duration from the current time.
+
*/
+
ttl?: number;
+
+
/**
+
* Custom metadata to attach to this key.
+
*
+
* @remarks
+
* Merged with system-generated metadata (size, checksum, timestamps).
+
* Useful for storing application-specific information like content-type, encoding, etc.
+
*/
+
metadata?: Record<string, string>;
+
+
/**
+
* Skip writing to specific tiers.
+
*
+
* @remarks
+
* Useful for controlling which tiers receive data. For example:
+
* - Large files: `skipTiers: ['hot']` to avoid filling memory
+
* - Small critical files: Write to hot only for fastest access
+
*
+
* Note: Cold tier can never be skipped (it's the source of truth).
+
*
+
* @example
+
* ```typescript
+
* // Store large file only in warm and cold (skip memory)
+
* await storage.set('large-video.mp4', videoData, { skipTiers: ['hot'] });
+
*
+
* // Store index.html in all tiers for fast access
+
* await storage.set('index.html', htmlData); // No skipping
+
* ```
+
*/
+
skipTiers?: ('hot' | 'warm')[];
+
}
+
+
/**
+
* Result from retrieving data with metadata.
+
*
+
* @typeParam T - The type of data being retrieved
+
*
+
* @remarks
+
* Includes both the data and information about where it was served from.
+
*/
+
export interface StorageResult<T> {
+
/** The retrieved data */
+
data: T;
+
+
/** Metadata associated with the data */
+
metadata: StorageMetadata;
+
+
/** Which tier the data was served from */
+
source: 'hot' | 'warm' | 'cold';
+
}
+
+
/**
+
* Result from setting data in the cache.
+
*
+
* @remarks
+
* Indicates which tiers successfully received the data.
+
*/
+
export interface SetResult {
+
/** The key that was set */
+
key: string;
+
+
/** Metadata that was stored with the data */
+
metadata: StorageMetadata;
+
+
/** Which tiers received the data */
+
tiersWritten: ('hot' | 'warm' | 'cold')[];
+
}
+
+
/**
+
* Snapshot of the entire storage state.
+
*
+
* @remarks
+
* Used for export/import, backup, and migration scenarios.
+
* The snapshot includes metadata but not the actual data (data remains in tiers).
+
*/
+
export interface StorageSnapshot {
+
/** Snapshot format version (for compatibility) */
+
version: number;
+
+
/** When this snapshot was created */
+
exportedAt: Date;
+
+
/** All keys present in cold tier (source of truth) */
+
keys: string[];
+
+
/** Metadata for each key */
+
metadata: Record<string, StorageMetadata>;
+
+
/** Statistics at time of export */
+
stats: AllTierStats;
+
}
+57
src/utils/checksum.ts
···
+
import { createHash, timingSafeEqual } from 'node:crypto';
+
+
/**
+
* Calculate SHA256 checksum of data.
+
*
+
* @param data - Data to checksum
+
* @returns Hex-encoded SHA256 hash
+
*
+
* @remarks
+
* Used for data integrity verification. The checksum is stored in metadata
+
* and can be used to detect corruption or tampering.
+
*
+
* @example
+
* ```typescript
+
* const data = new TextEncoder().encode('Hello, world!');
+
* const checksum = calculateChecksum(data);
+
* console.log(checksum); // '315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3'
+
* ```
+
*/
+
export function calculateChecksum(data: Uint8Array): string {
+
const hash = createHash('sha256');
+
hash.update(data);
+
return hash.digest('hex');
+
}
+
+
/**
+
* Verify that data matches an expected checksum.
+
*
+
* @param data - Data to verify
+
* @param expectedChecksum - Expected SHA256 checksum (hex-encoded)
+
* @returns true if checksums match, false otherwise
+
*
+
* @remarks
+
* Uses constant-time comparison to prevent timing attacks.
+
*
+
* @example
+
* ```typescript
+
* const isValid = verifyChecksum(data, metadata.checksum);
+
* if (!isValid) {
+
* throw new Error('Data corruption detected');
+
* }
+
* ```
+
*/
+
export function verifyChecksum(data: Uint8Array, expectedChecksum: string): boolean {
+
const actualChecksum = calculateChecksum(data);
+
+
// Use constant-time comparison to prevent timing attacks
+
try {
+
return timingSafeEqual(
+
Buffer.from(actualChecksum, 'hex'),
+
Buffer.from(expectedChecksum, 'hex')
+
);
+
} catch {
+
// If checksums have different lengths, timingSafeEqual throws
+
return false;
+
}
+
}
+78
src/utils/compression.ts
···
+
import { gzip, gunzip } from 'node:zlib';
+
import { promisify } from 'node:util';
+
+
const gzipAsync = promisify(gzip);
+
const gunzipAsync = promisify(gunzip);
+
+
/**
+
* Compress data using gzip.
+
*
+
* @param data - Data to compress
+
* @returns Compressed data as Uint8Array
+
*
+
* @remarks
+
* Uses Node.js zlib with default compression level (6).
+
* Compression is transparent to the user - data is automatically decompressed on retrieval.
+
*
+
* @example
+
* ```typescript
+
* const original = new TextEncoder().encode('Hello, world!');
+
* const compressed = await compress(original);
+
* console.log(`Compressed from ${original.length} to ${compressed.length} bytes`);
+
* ```
+
*/
+
export async function compress(data: Uint8Array): Promise<Uint8Array> {
+
const buffer = Buffer.from(data);
+
const compressed = await gzipAsync(buffer);
+
return new Uint8Array(compressed);
+
}
+
+
/**
+
* Decompress gzip-compressed data.
+
*
+
* @param data - Compressed data
+
* @returns Decompressed data as Uint8Array
+
* @throws Error if data is not valid gzip format
+
*
+
* @remarks
+
* Automatically validates gzip magic bytes (0x1f 0x8b) before decompression.
+
*
+
* @example
+
* ```typescript
+
* const decompressed = await decompress(compressedData);
+
* const text = new TextDecoder().decode(decompressed);
+
* ```
+
*/
+
export async function decompress(data: Uint8Array): Promise<Uint8Array> {
+
// Validate gzip magic bytes
+
if (data.length < 2 || data[0] !== 0x1f || data[1] !== 0x8b) {
+
throw new Error('Invalid gzip data: missing magic bytes');
+
}
+
+
const buffer = Buffer.from(data);
+
const decompressed = await gunzipAsync(buffer);
+
return new Uint8Array(decompressed);
+
}
+
+
/**
+
* Check if data appears to be gzip-compressed by inspecting magic bytes.
+
*
+
* @param data - Data to check
+
* @returns true if data starts with gzip magic bytes (0x1f 0x8b)
+
*
+
* @remarks
+
* This is a quick check that doesn't decompress the data.
+
* Useful for detecting already-compressed data to avoid double compression.
+
*
+
* @example
+
* ```typescript
+
* if (isGzipped(data)) {
+
* console.log('Already compressed, skipping compression');
+
* } else {
+
* data = await compress(data);
+
* }
+
* ```
+
*/
+
export function isGzipped(data: Uint8Array): boolean {
+
return data.length >= 2 && data[0] === 0x1f && data[1] === 0x8b;
+
}
+69
src/utils/path-encoding.ts
···
+
/**
+
* Encode a key to be safe for use as a filesystem path.
+
*
+
* @param key - The key to encode
+
* @returns Filesystem-safe encoded key
+
*
+
* @remarks
+
* Encodes characters that are problematic in filenames:
+
* - Forward slash (/) → %2F
+
* - Backslash (\) → %5C
+
* - Colon (:) → %3A
+
* - Asterisk (*) → %2A
+
* - Question mark (?) → %3F
+
* - Quote (") → %22
+
* - Less than (<) → %3C
+
* - Greater than (>) → %3E
+
* - Pipe (|) → %7C
+
* - Percent (%) → %25
+
* - Null byte → %00
+
*
+
* @example
+
* ```typescript
+
* const key = 'user:123/profile.json';
+
* const encoded = encodeKey(key);
+
* // Result: 'user%3A123%2Fprofile.json'
+
* ```
+
*/
+
export function encodeKey(key: string): string {
+
return key
+
.replace(/%/g, '%25') // Must be first!
+
.replace(/\//g, '%2F')
+
.replace(/\\/g, '%5C')
+
.replace(/:/g, '%3A')
+
.replace(/\*/g, '%2A')
+
.replace(/\?/g, '%3F')
+
.replace(/"/g, '%22')
+
.replace(/</g, '%3C')
+
.replace(/>/g, '%3E')
+
.replace(/\|/g, '%7C')
+
.replace(/\0/g, '%00');
+
}
+
+
/**
+
* Decode a filesystem-safe key back to original form.
+
*
+
* @param encoded - The encoded key
+
* @returns Original key
+
*
+
* @example
+
* ```typescript
+
* const encoded = 'user%3A123%2Fprofile.json';
+
* const key = decodeKey(encoded);
+
* // Result: 'user:123/profile.json'
+
* ```
+
*/
+
export function decodeKey(encoded: string): string {
+
return encoded
+
.replace(/%2F/g, '/')
+
.replace(/%5C/g, '\\')
+
.replace(/%3A/g, ':')
+
.replace(/%2A/g, '*')
+
.replace(/%3F/g, '?')
+
.replace(/%22/g, '"')
+
.replace(/%3C/g, '<')
+
.replace(/%3E/g, '>')
+
.replace(/%7C/g, '|')
+
.replace(/%00/g, '\0')
+
.replace(/%25/g, '%'); // Must be last!
+
}
+46
src/utils/serialization.ts
···
+
/**
+
* Default JSON serializer.
+
*
+
* @param data - Data to serialize (must be JSON-serializable)
+
* @returns Serialized data as Uint8Array (UTF-8 encoded JSON)
+
*
+
* @remarks
+
* This is the default serializer used if no custom serializer is provided.
+
* Handles most JavaScript types but cannot serialize:
+
* - Functions
+
* - Symbols
+
* - undefined values (they become null)
+
* - Circular references
+
*
+
* @example
+
* ```typescript
+
* const data = { name: 'Alice', age: 30 };
+
* const serialized = await defaultSerialize(data);
+
* ```
+
*/
+
export async function defaultSerialize(data: unknown): Promise<Uint8Array> {
+
const json = JSON.stringify(data);
+
return new TextEncoder().encode(json);
+
}
+
+
/**
+
* Default JSON deserializer.
+
*
+
* @param data - Serialized data (UTF-8 encoded JSON)
+
* @returns Deserialized data
+
* @throws SyntaxError if data is not valid JSON
+
*
+
* @remarks
+
* This is the default deserializer used if no custom deserializer is provided.
+
* Parses UTF-8 encoded JSON back to JavaScript objects.
+
*
+
* @example
+
* ```typescript
+
* const data = await defaultDeserialize(serialized);
+
* console.log(data.name); // 'Alice'
+
* ```
+
*/
+
export async function defaultDeserialize(data: Uint8Array): Promise<unknown> {
+
const json = new TextDecoder().decode(data);
+
return JSON.parse(json);
+
}
+404
test/TieredStorage.test.ts
···
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+
import { TieredStorage } from '../src/TieredStorage.js';
+
import { MemoryStorageTier } from '../src/tiers/MemoryStorageTier.js';
+
import { DiskStorageTier } from '../src/tiers/DiskStorageTier.js';
+
import { rm } from 'node:fs/promises';
+
+
describe('TieredStorage', () => {
+
const testDir = './test-cache';
+
+
afterEach(async () => {
+
await rm(testDir, { recursive: true, force: true });
+
});
+
+
describe('Basic Operations', () => {
+
it('should store and retrieve data', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
await storage.set('test-key', { message: 'Hello, world!' });
+
const result = await storage.get('test-key');
+
+
expect(result).toEqual({ message: 'Hello, world!' });
+
});
+
+
it('should return null for non-existent key', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
const result = await storage.get('non-existent');
+
expect(result).toBeNull();
+
});
+
+
it('should delete data from all tiers', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
await storage.set('test-key', { data: 'test' });
+
await storage.delete('test-key');
+
const result = await storage.get('test-key');
+
+
expect(result).toBeNull();
+
});
+
+
it('should check if key exists', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
await storage.set('test-key', { data: 'test' });
+
+
expect(await storage.exists('test-key')).toBe(true);
+
expect(await storage.exists('non-existent')).toBe(false);
+
});
+
});
+
+
describe('Cascading Write', () => {
+
it('should write to all configured tiers', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
await storage.set('test-key', { data: 'test' });
+
+
// Verify data exists in all tiers
+
expect(await hot.exists('test-key')).toBe(true);
+
expect(await warm.exists('test-key')).toBe(true);
+
expect(await cold.exists('test-key')).toBe(true);
+
});
+
+
it('should skip tiers when specified', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
// Skip hot tier
+
await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] });
+
+
expect(await hot.exists('test-key')).toBe(false);
+
expect(await warm.exists('test-key')).toBe(true);
+
expect(await cold.exists('test-key')).toBe(true);
+
});
+
});
+
+
describe('Bubbling Read', () => {
+
it('should read from hot tier first', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
await storage.set('test-key', { data: 'test' });
+
const result = await storage.getWithMetadata('test-key');
+
+
expect(result?.source).toBe('hot');
+
expect(result?.data).toEqual({ data: 'test' });
+
});
+
+
it('should fall back to warm tier on hot miss', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
// Write to warm and cold, skip hot
+
await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] });
+
+
const result = await storage.getWithMetadata('test-key');
+
+
expect(result?.source).toBe('warm');
+
expect(result?.data).toEqual({ data: 'test' });
+
});
+
+
it('should fall back to cold tier on hot and warm miss', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, cold },
+
});
+
+
// Write only to cold
+
await cold.set(
+
'test-key',
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
+
{
+
key: 'test-key',
+
size: 100,
+
createdAt: new Date(),
+
lastAccessed: new Date(),
+
accessCount: 0,
+
compressed: false,
+
checksum: 'abc123',
+
}
+
);
+
+
const result = await storage.getWithMetadata('test-key');
+
+
expect(result?.source).toBe('cold');
+
expect(result?.data).toEqual({ data: 'test' });
+
});
+
});
+
+
describe('Promotion Strategy', () => {
+
it('should eagerly promote data to upper tiers', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
promotionStrategy: 'eager',
+
});
+
+
// Write only to cold
+
await cold.set(
+
'test-key',
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
+
{
+
key: 'test-key',
+
size: 100,
+
createdAt: new Date(),
+
lastAccessed: new Date(),
+
accessCount: 0,
+
compressed: false,
+
checksum: 'abc123',
+
}
+
);
+
+
// Read should promote to hot and warm
+
await storage.get('test-key');
+
+
expect(await hot.exists('test-key')).toBe(true);
+
expect(await warm.exists('test-key')).toBe(true);
+
});
+
+
it('should lazily promote data (not automatic)', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
promotionStrategy: 'lazy',
+
});
+
+
// Write only to cold
+
await cold.set(
+
'test-key',
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
+
{
+
key: 'test-key',
+
size: 100,
+
createdAt: new Date(),
+
lastAccessed: new Date(),
+
accessCount: 0,
+
compressed: false,
+
checksum: 'abc123',
+
}
+
);
+
+
// Read should NOT promote to hot and warm
+
await storage.get('test-key');
+
+
expect(await hot.exists('test-key')).toBe(false);
+
expect(await warm.exists('test-key')).toBe(false);
+
});
+
});
+
+
describe('TTL Management', () => {
+
it('should expire data after TTL', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
// Set with 100ms TTL
+
await storage.set('test-key', { data: 'test' }, { ttl: 100 });
+
+
// Should exist immediately
+
expect(await storage.get('test-key')).toEqual({ data: 'test' });
+
+
// Wait for expiration
+
await new Promise((resolve) => setTimeout(resolve, 150));
+
+
// Should be null after expiration
+
expect(await storage.get('test-key')).toBeNull();
+
});
+
+
it('should renew TTL with touch', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
defaultTTL: 100,
+
});
+
+
await storage.set('test-key', { data: 'test' });
+
+
// Wait 50ms
+
await new Promise((resolve) => setTimeout(resolve, 50));
+
+
// Renew TTL
+
await storage.touch('test-key', 200);
+
+
// Wait another 100ms (would have expired without touch)
+
await new Promise((resolve) => setTimeout(resolve, 100));
+
+
// Should still exist
+
expect(await storage.get('test-key')).toEqual({ data: 'test' });
+
});
+
});
+
+
describe('Prefix Invalidation', () => {
+
it('should invalidate all keys with prefix', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
await storage.set('user:123', { name: 'Alice' });
+
await storage.set('user:456', { name: 'Bob' });
+
await storage.set('post:789', { title: 'Test' });
+
+
const deleted = await storage.invalidate('user:');
+
+
expect(deleted).toBe(2);
+
expect(await storage.exists('user:123')).toBe(false);
+
expect(await storage.exists('user:456')).toBe(false);
+
expect(await storage.exists('post:789')).toBe(true);
+
});
+
});
+
+
describe('Compression', () => {
+
it('should compress data when enabled', async () => {
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { cold },
+
compression: true,
+
});
+
+
const largeData = { data: 'x'.repeat(10000) };
+
const result = await storage.set('test-key', largeData);
+
+
// Check that compressed flag is set
+
expect(result.metadata.compressed).toBe(true);
+
+
// Verify data can be retrieved correctly
+
const retrieved = await storage.get('test-key');
+
expect(retrieved).toEqual(largeData);
+
});
+
});
+
+
describe('Bootstrap', () => {
+
it('should bootstrap hot from warm', async () => {
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { hot, warm, cold },
+
});
+
+
// Write some data
+
await storage.set('key1', { data: '1' });
+
await storage.set('key2', { data: '2' });
+
await storage.set('key3', { data: '3' });
+
+
// Clear hot tier
+
await hot.clear();
+
+
// Bootstrap hot from warm
+
const loaded = await storage.bootstrapHot();
+
+
expect(loaded).toBe(3);
+
expect(await hot.exists('key1')).toBe(true);
+
expect(await hot.exists('key2')).toBe(true);
+
expect(await hot.exists('key3')).toBe(true);
+
});
+
+
it('should bootstrap warm from cold', async () => {
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
+
+
const storage = new TieredStorage({
+
tiers: { warm, cold },
+
});
+
+
// Write directly to cold
+
await cold.set(
+
'key1',
+
new TextEncoder().encode(JSON.stringify({ data: '1' })),
+
{
+
key: 'key1',
+
size: 100,
+
createdAt: new Date(),
+
lastAccessed: new Date(),
+
accessCount: 0,
+
compressed: false,
+
checksum: 'abc',
+
}
+
);
+
+
// Bootstrap warm from cold
+
const loaded = await storage.bootstrapWarm({ limit: 10 });
+
+
expect(loaded).toBe(1);
+
expect(await warm.exists('key1')).toBe(true);
+
});
+
});
+
+
describe('Statistics', () => {
+
it('should return statistics for all tiers', async () => {
+
const storage = new TieredStorage({
+
tiers: {
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
+
},
+
});
+
+
await storage.set('key1', { data: 'test1' });
+
await storage.set('key2', { data: 'test2' });
+
+
const stats = await storage.getStats();
+
+
expect(stats.cold.items).toBe(2);
+
expect(stats.warm?.items).toBe(2);
+
expect(stats.hot?.items).toBe(2);
+
});
+
});
+
});
+33
tsconfig.json
···
+
{
+
"compilerOptions": {
+
"target": "ES2022",
+
"module": "ESNext",
+
"moduleResolution": "node",
+
"allowSyntheticDefaultImports": true,
+
"esModuleInterop": true,
+
"allowJs": true,
+
"strict": true,
+
"noImplicitAny": true,
+
"strictNullChecks": true,
+
"strictFunctionTypes": true,
+
"strictBindCallApply": true,
+
"strictPropertyInitialization": true,
+
"noImplicitReturns": true,
+
"noImplicitThis": true,
+
"noUncheckedIndexedAccess": true,
+
"noImplicitOverride": true,
+
"exactOptionalPropertyTypes": true,
+
"noPropertyAccessFromIndexSignature": false,
+
"declaration": true,
+
"declarationMap": true,
+
"sourceMap": true,
+
"outDir": "./dist",
+
"rootDir": "./src",
+
"removeComments": false,
+
"skipLibCheck": true,
+
"forceConsistentCasingInFileNames": true,
+
"resolveJsonModule": true
+
},
+
"include": ["src/**/*"],
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
+
}