+136
.env.example
+136
.env.example
···
1
+
# Tiered Storage Configuration
2
+
# Copy this file to .env and configure for your environment
3
+
4
+
# ============================================================================
5
+
# S3 Configuration (Cold Tier - Required)
6
+
# ============================================================================
7
+
8
+
# AWS S3 bucket name (or S3-compatible bucket)
9
+
S3_BUCKET=tiered-storage-cache
10
+
11
+
# Optional: Separate bucket for metadata (RECOMMENDED for production!)
12
+
# When set, metadata is stored as separate JSON objects instead of S3 object metadata.
13
+
# This allows fast, cheap metadata updates without copying entire objects.
14
+
# Leave blank to store metadata in S3 object metadata fields (slower, more expensive).
15
+
S3_METADATA_BUCKET=tiered-storage-metadata
16
+
17
+
# AWS region
18
+
S3_REGION=us-east-1
19
+
20
+
# S3 endpoint (optional - for S3-compatible services like R2, Minio)
21
+
# Leave blank for AWS S3
22
+
# For Cloudflare R2: https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com
23
+
# For MinIO: http://localhost:9000
24
+
# For other S3-compatible: https://s3.your-service.com
25
+
# S3_ENDPOINT=
26
+
27
+
# Force path-style URLs (usually needed for S3-compatible services)
28
+
# Default: true (recommended for most S3-compatible services)
29
+
# Set to false only if your service requires virtual-host-style URLs
30
+
# S3_FORCE_PATH_STYLE=true
31
+
32
+
# AWS credentials
33
+
# If not provided, uses default AWS credential chain
34
+
# (environment variables, ~/.aws/credentials, IAM roles, etc.)
35
+
AWS_ACCESS_KEY_ID=your_access_key_id
36
+
AWS_SECRET_ACCESS_KEY=your_secret_access_key
37
+
38
+
# ============================================================================
39
+
# Cloudflare R2 Example Configuration
40
+
# ============================================================================
41
+
# Uncomment these to use Cloudflare R2 instead of AWS S3:
42
+
#
43
+
# S3_BUCKET=my-r2-bucket
44
+
# S3_METADATA_BUCKET=my-r2-metadata-bucket
45
+
# S3_REGION=auto
46
+
# S3_ENDPOINT=https://YOUR-ACCOUNT-ID.r2.cloudflarestorage.com
47
+
# AWS_ACCESS_KEY_ID=your_r2_access_key_id
48
+
# AWS_SECRET_ACCESS_KEY=your_r2_secret_access_key
49
+
50
+
# ============================================================================
51
+
# Memory Tier Configuration (Hot)
52
+
# ============================================================================
53
+
54
+
# Maximum size in bytes for hot (memory) tier
55
+
# Default: 100MB
56
+
MEMORY_MAX_SIZE_BYTES=104857600
57
+
58
+
# Maximum number of items in hot tier
59
+
# Optional - if not set, only size limit applies
60
+
MEMORY_MAX_ITEMS=1000
61
+
62
+
# ============================================================================
63
+
# Disk Tier Configuration (Warm)
64
+
# ============================================================================
65
+
66
+
# Directory for warm tier cache
67
+
# Default: ./cache/warm
68
+
DISK_WARM_DIRECTORY=./cache/warm
69
+
70
+
# Maximum size in bytes for warm tier
71
+
# Optional - if not set, no size limit
72
+
DISK_WARM_MAX_SIZE_BYTES=10737418240
73
+
74
+
# Eviction policy when size limit reached
75
+
# Options: lru, fifo, size
76
+
# Default: lru
77
+
DISK_WARM_EVICTION_POLICY=lru
78
+
79
+
# ============================================================================
80
+
# Storage Options
81
+
# ============================================================================
82
+
83
+
# Enable compression (gzip)
84
+
# Default: false
85
+
COMPRESSION_ENABLED=true
86
+
87
+
# Default TTL in milliseconds
88
+
# Optional - if not set, data never expires
89
+
# Example: 1209600000 = 14 days
90
+
DEFAULT_TTL_MS=1209600000
91
+
92
+
# Promotion strategy: 'eager' or 'lazy'
93
+
# eager: Automatically promote data to upper tiers on read
94
+
# lazy: Only promote on explicit bootstrap or write
95
+
# Default: lazy
96
+
PROMOTION_STRATEGY=lazy
97
+
98
+
# ============================================================================
99
+
# Bootstrap Configuration
100
+
# ============================================================================
101
+
102
+
# Number of items to load into hot tier on bootstrap
103
+
# Optional - if not set, loads all items
104
+
BOOTSTRAP_HOT_LIMIT=1000
105
+
106
+
# Number of days to look back when bootstrapping warm tier
107
+
# Example: 7 = only load items accessed in last 7 days
108
+
BOOTSTRAP_WARM_DAYS=7
109
+
110
+
# Maximum items to load into warm tier on bootstrap
111
+
# Optional - if not set, loads all matching items
112
+
BOOTSTRAP_WARM_LIMIT=10000
113
+
114
+
# ============================================================================
115
+
# Performance Tuning
116
+
# ============================================================================
117
+
118
+
# Maximum concurrent operations for bootstrap
119
+
# Default: 10
120
+
BOOTSTRAP_CONCURRENCY=10
121
+
122
+
# Timeout for tier operations in milliseconds
123
+
# Default: 30000 (30 seconds)
124
+
TIER_OPERATION_TIMEOUT_MS=30000
125
+
126
+
# ============================================================================
127
+
# Monitoring & Observability
128
+
# ============================================================================
129
+
130
+
# Enable statistics tracking
131
+
# Default: true
132
+
STATS_ENABLED=true
133
+
134
+
# Log level: debug, info, warn, error
135
+
# Default: info
136
+
LOG_LEVEL=info
+28
.eslintrc.cjs
+28
.eslintrc.cjs
···
1
+
module.exports = {
2
+
parser: '@typescript-eslint/parser',
3
+
parserOptions: {
4
+
ecmaVersion: 2022,
5
+
sourceType: 'module',
6
+
project: './tsconfig.json',
7
+
},
8
+
plugins: ['@typescript-eslint'],
9
+
extends: [
10
+
'eslint:recommended',
11
+
'@typescript-eslint/recommended',
12
+
'@typescript-eslint/recommended-requiring-type-checking',
13
+
],
14
+
root: true,
15
+
env: {
16
+
node: true,
17
+
es2022: true,
18
+
},
19
+
rules: {
20
+
'@typescript-eslint/no-unused-vars': 'error',
21
+
'@typescript-eslint/explicit-function-return-type': 'warn',
22
+
'@typescript-eslint/no-explicit-any': 'warn',
23
+
'@typescript-eslint/prefer-nullish-coalescing': 'error',
24
+
'@typescript-eslint/prefer-optional-chain': 'error',
25
+
'@typescript-eslint/no-floating-promises': 'error',
26
+
'@typescript-eslint/await-thenable': 'error',
27
+
},
28
+
};
+17
.gitignore
+17
.gitignore
+574
README.md
+574
README.md
···
1
+
# Tiered Storage
2
+
3
+
A lightweight, pluggable tiered storage library that orchestrates caching across hot (memory), warm (disk/database), and cold (S3/object storage) tiers.
4
+
5
+
## Features
6
+
7
+
- **Cascading Containment Model**: Hot ⊆ Warm ⊆ Cold (lower tiers contain all data from upper tiers)
8
+
- **Pluggable Backends**: Bring your own Redis, Postgres, SQLite, or use built-in implementations
9
+
- **Automatic Promotion**: Configurable eager/lazy promotion strategies for cache warming
10
+
- **TTL Management**: Per-key TTL with automatic expiration and renewal
11
+
- **Prefix Invalidation**: Efficiently delete groups of keys by prefix
12
+
- **Bootstrap Support**: Warm up caches from lower tiers on startup
13
+
- **Compression**: Optional transparent gzip compression
14
+
- **TypeScript First**: Full type safety with comprehensive TSDoc comments
15
+
- **Zero Forced Dependencies**: Only require what you use
16
+
17
+
## Installation
18
+
19
+
```bash
20
+
npm install tiered-storage
21
+
# or
22
+
bun add tiered-storage
23
+
```
24
+
25
+
## Quick Start
26
+
27
+
```typescript
28
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage';
29
+
30
+
const storage = new TieredStorage({
31
+
tiers: {
32
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB
33
+
warm: new DiskStorageTier({ directory: './cache' }),
34
+
cold: new S3StorageTier({
35
+
bucket: 'my-bucket',
36
+
region: 'us-east-1',
37
+
credentials: {
38
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
39
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
40
+
},
41
+
}),
42
+
},
43
+
compression: true,
44
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
45
+
promotionStrategy: 'lazy',
46
+
});
47
+
48
+
// Store data (cascades to all tiers)
49
+
await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' });
50
+
51
+
// Retrieve data (bubbles up from cold → warm → hot)
52
+
const user = await storage.get('user:123');
53
+
54
+
// Get data with metadata and source tier
55
+
const result = await storage.getWithMetadata('user:123');
56
+
console.log(`Served from ${result.source}`); // 'hot', 'warm', or 'cold'
57
+
58
+
// Invalidate all keys with prefix
59
+
await storage.invalidate('user:');
60
+
61
+
// Renew TTL
62
+
await storage.touch('user:123');
63
+
```
64
+
65
+
## Core Concepts
66
+
67
+
### Cascading Containment Model
68
+
69
+
```
70
+
┌──────────────────────────────────────────────────────┐
71
+
│ Cold Storage (S3/Object Storage) │
72
+
│ • Contains ALL objects (source of truth) │
73
+
│ • Slowest access, unlimited capacity │
74
+
├──────────────────────────────────────────────────────┤
75
+
│ Warm Storage (Disk/Database) │
76
+
│ • Contains ALL hot objects + additional warm objects │
77
+
│ • Medium access speed, large capacity │
78
+
├──────────────────────────────────────────────────────┤
79
+
│ Hot Storage (Memory) │
80
+
│ • Contains only the hottest objects │
81
+
│ • Fastest access, limited capacity │
82
+
└──────────────────────────────────────────────────────┘
83
+
```
84
+
85
+
**Write Strategy (Cascading Down):**
86
+
- Write to **hot** → also writes to **warm** and **cold**
87
+
- Write to **warm** → also writes to **cold**
88
+
- Write to **cold** → only writes to **cold**
89
+
90
+
**Read Strategy (Bubbling Up):**
91
+
- Check **hot** first → if miss, check **warm** → if miss, check **cold**
92
+
- On cache miss, optionally promote data up through tiers
93
+
94
+
### Selective Tier Placement
95
+
96
+
For use cases like static site hosting, you can control which files go into which tiers:
97
+
98
+
```typescript
99
+
// Small, critical file (index.html) - store in all tiers for instant serving
100
+
await storage.set('site:abc/index.html', htmlContent);
101
+
102
+
// Large file (video) - skip hot tier to avoid memory bloat
103
+
await storage.set('site:abc/video.mp4', videoData, { skipTiers: ['hot'] });
104
+
105
+
// Medium files (images, CSS) - skip hot, use warm + cold
106
+
await storage.set('site:abc/style.css', cssData, { skipTiers: ['hot'] });
107
+
```
108
+
109
+
This pattern ensures:
110
+
- Hot tier stays small and fast (only critical files)
111
+
- Warm tier caches everything (all site files on disk)
112
+
- Cold tier is source of truth (all data)
113
+
114
+
## API Reference
115
+
116
+
### `TieredStorage`
117
+
118
+
Main orchestrator class for tiered storage.
119
+
120
+
#### Constructor
121
+
122
+
```typescript
123
+
new TieredStorage<T>(config: TieredStorageConfig)
124
+
```
125
+
126
+
**Config Options:**
127
+
128
+
```typescript
129
+
interface TieredStorageConfig {
130
+
tiers: {
131
+
hot?: StorageTier; // Optional: fastest tier (memory/Redis)
132
+
warm?: StorageTier; // Optional: medium tier (disk/SQLite/Postgres)
133
+
cold: StorageTier; // Required: slowest tier (S3/object storage)
134
+
};
135
+
compression?: boolean; // Auto-compress before storing (default: false)
136
+
defaultTTL?: number; // Default TTL in milliseconds
137
+
promotionStrategy?: 'eager' | 'lazy'; // When to promote to upper tiers (default: 'lazy')
138
+
serialization?: { // Custom serialization (default: JSON)
139
+
serialize: (data: unknown) => Promise<Uint8Array>;
140
+
deserialize: (data: Uint8Array) => Promise<unknown>;
141
+
};
142
+
}
143
+
```
144
+
145
+
#### Methods
146
+
147
+
**`get(key: string): Promise<T | null>`**
148
+
149
+
Retrieve data for a key. Returns null if not found or expired.
150
+
151
+
**`getWithMetadata(key: string): Promise<StorageResult<T> | null>`**
152
+
153
+
Retrieve data with metadata and source tier information.
154
+
155
+
```typescript
156
+
const result = await storage.getWithMetadata('user:123');
157
+
console.log(result.data); // The actual data
158
+
console.log(result.source); // 'hot' | 'warm' | 'cold'
159
+
console.log(result.metadata); // Metadata (size, timestamps, TTL, etc.)
160
+
```
161
+
162
+
**`set(key: string, data: T, options?: SetOptions): Promise<SetResult>`**
163
+
164
+
Store data with optional configuration.
165
+
166
+
```typescript
167
+
await storage.set('key', data, {
168
+
ttl: 24 * 60 * 60 * 1000, // Custom TTL (24 hours)
169
+
metadata: { contentType: 'application/json' }, // Custom metadata
170
+
skipTiers: ['hot'], // Skip specific tiers
171
+
});
172
+
```
173
+
174
+
**`delete(key: string): Promise<void>`**
175
+
176
+
Delete data from all tiers.
177
+
178
+
**`exists(key: string): Promise<boolean>`**
179
+
180
+
Check if a key exists (and hasn't expired).
181
+
182
+
**`touch(key: string, ttlMs?: number): Promise<void>`**
183
+
184
+
Renew TTL for a key. Useful for "keep alive" behavior.
185
+
186
+
**`invalidate(prefix: string): Promise<number>`**
187
+
188
+
Delete all keys matching a prefix. Returns number of keys deleted.
189
+
190
+
```typescript
191
+
await storage.invalidate('user:'); // Delete all user keys
192
+
await storage.invalidate('site:abc/'); // Delete all files for site 'abc'
193
+
await storage.invalidate(''); // Delete everything
194
+
```
195
+
196
+
**`listKeys(prefix?: string): AsyncIterableIterator<string>`**
197
+
198
+
List all keys, optionally filtered by prefix.
199
+
200
+
```typescript
201
+
for await (const key of storage.listKeys('user:')) {
202
+
console.log(key); // 'user:123', 'user:456', etc.
203
+
}
204
+
```
205
+
206
+
**`getStats(): Promise<AllTierStats>`**
207
+
208
+
Get aggregated statistics across all tiers.
209
+
210
+
```typescript
211
+
const stats = await storage.getStats();
212
+
console.log(stats.hot); // Hot tier stats (size, items, hits, misses)
213
+
console.log(stats.hitRate); // Overall hit rate (0-1)
214
+
```
215
+
216
+
**`bootstrapHot(limit?: number): Promise<number>`**
217
+
218
+
Load most frequently accessed items from warm into hot. Returns number of items loaded.
219
+
220
+
```typescript
221
+
// On server startup: warm up hot tier
222
+
const loaded = await storage.bootstrapHot(1000); // Load top 1000 items
223
+
console.log(`Loaded ${loaded} items into hot tier`);
224
+
```
225
+
226
+
**`bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number>`**
227
+
228
+
Load recent items from cold into warm. Returns number of items loaded.
229
+
230
+
```typescript
231
+
// Load items accessed in last 7 days
232
+
const loaded = await storage.bootstrapWarm({
233
+
sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
234
+
limit: 10000,
235
+
});
236
+
```
237
+
238
+
**`export(): Promise<StorageSnapshot>`**
239
+
240
+
Export metadata snapshot for backup or migration.
241
+
242
+
**`import(snapshot: StorageSnapshot): Promise<void>`**
243
+
244
+
Import metadata snapshot.
245
+
246
+
**`clear(): Promise<void>`**
247
+
248
+
Clear all data from all tiers. ⚠️ Use with extreme caution!
249
+
250
+
**`clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void>`**
251
+
252
+
Clear a specific tier.
253
+
254
+
### Built-in Storage Tiers
255
+
256
+
#### `MemoryStorageTier`
257
+
258
+
In-memory storage using TinyLRU for efficient LRU eviction.
259
+
260
+
```typescript
261
+
import { MemoryStorageTier } from 'tiered-storage';
262
+
263
+
const tier = new MemoryStorageTier({
264
+
maxSizeBytes: 100 * 1024 * 1024, // 100MB
265
+
maxItems: 1000, // Optional: max number of items
266
+
});
267
+
```
268
+
269
+
**Features:**
270
+
- Battle-tested TinyLRU library
271
+
- Automatic LRU eviction
272
+
- Size-based and count-based limits
273
+
- Single process only (not distributed)
274
+
275
+
#### `DiskStorageTier`
276
+
277
+
Filesystem-based storage with `.meta` files.
278
+
279
+
```typescript
280
+
import { DiskStorageTier } from 'tiered-storage';
281
+
282
+
const tier = new DiskStorageTier({
283
+
directory: './cache',
284
+
maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB (optional)
285
+
evictionPolicy: 'lru', // 'lru' | 'fifo' | 'size'
286
+
});
287
+
```
288
+
289
+
**Features:**
290
+
- Human-readable file structure
291
+
- Optional size-based eviction
292
+
- Three eviction policies: LRU, FIFO, size-based
293
+
- Atomic writes with `.meta` files
294
+
- Zero external dependencies
295
+
296
+
**File structure:**
297
+
```
298
+
cache/
299
+
├── user%3A123 # Data file (encoded key)
300
+
├── user%3A123.meta # Metadata JSON
301
+
├── site%3Aabc%2Findex.html
302
+
└── site%3Aabc%2Findex.html.meta
303
+
```
304
+
305
+
#### `S3StorageTier`
306
+
307
+
AWS S3 or S3-compatible object storage.
308
+
309
+
```typescript
310
+
import { S3StorageTier } from 'tiered-storage';
311
+
312
+
// AWS S3 with separate metadata bucket (RECOMMENDED!)
313
+
const tier = new S3StorageTier({
314
+
bucket: 'my-data-bucket',
315
+
metadataBucket: 'my-metadata-bucket', // Stores metadata separately for fast updates
316
+
region: 'us-east-1',
317
+
credentials: {
318
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
319
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
320
+
},
321
+
prefix: 'cache/', // Optional key prefix
322
+
});
323
+
324
+
// Cloudflare R2 with metadata bucket
325
+
const r2Tier = new S3StorageTier({
326
+
bucket: 'my-r2-data-bucket',
327
+
metadataBucket: 'my-r2-metadata-bucket',
328
+
region: 'auto',
329
+
endpoint: 'https://account-id.r2.cloudflarestorage.com',
330
+
credentials: {
331
+
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
332
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
333
+
},
334
+
});
335
+
336
+
// Without metadata bucket (legacy mode - slower, more expensive)
337
+
const legacyTier = new S3StorageTier({
338
+
bucket: 'my-bucket',
339
+
region: 'us-east-1',
340
+
// No metadataBucket - metadata stored in S3 object metadata fields
341
+
});
342
+
```
343
+
344
+
**Features:**
345
+
- Compatible with AWS S3, Cloudflare R2, MinIO, and other S3-compatible services
346
+
- **Separate metadata bucket support (RECOMMENDED)** - stores metadata as JSON objects for fast, cheap updates
347
+
- Legacy mode: metadata in S3 object metadata fields (requires object copying for updates)
348
+
- Efficient batch deletions (up to 1000 keys per request)
349
+
- Optional key prefixing for multi-tenant scenarios
350
+
- Typically used as cold tier (source of truth)
351
+
352
+
**⚠️ Important:** Without `metadataBucket`, updating metadata (e.g., access counts) requires copying the entire object, which is slow and expensive for large files. Use a separate metadata bucket in production!
353
+
354
+
## Usage Patterns
355
+
356
+
### Pattern 1: Simple Single-Server Setup
357
+
358
+
```typescript
359
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage';
360
+
361
+
const storage = new TieredStorage({
362
+
tiers: {
363
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }),
364
+
warm: new DiskStorageTier({ directory: './cache' }),
365
+
cold: new DiskStorageTier({ directory: './storage' }),
366
+
},
367
+
compression: true,
368
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
369
+
});
370
+
371
+
await storage.set('user:123', { name: 'Alice', email: 'alice@example.com' });
372
+
const user = await storage.get('user:123');
373
+
```
374
+
375
+
### Pattern 2: Static Site Hosting (wisp.place-style)
376
+
377
+
```typescript
378
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier } from 'tiered-storage';
379
+
380
+
const storage = new TieredStorage({
381
+
tiers: {
382
+
hot: new MemoryStorageTier({
383
+
maxSizeBytes: 100 * 1024 * 1024, // 100MB
384
+
maxItems: 500,
385
+
}),
386
+
warm: new DiskStorageTier({
387
+
directory: './cache/sites',
388
+
maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB
389
+
}),
390
+
// Cold tier is PDS (fetched on demand via custom tier implementation)
391
+
},
392
+
compression: true,
393
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
394
+
promotionStrategy: 'lazy', // Don't auto-promote large files to hot
395
+
});
396
+
397
+
// Store index.html in all tiers (fast access)
398
+
await storage.set(`${did}/${rkey}/index.html`, htmlBuffer, {
399
+
metadata: { mimeType: 'text/html', encoding: 'gzip' },
400
+
});
401
+
402
+
// Store large files only in warm + cold (skip hot)
403
+
await storage.set(`${did}/${rkey}/video.mp4`, videoBuffer, {
404
+
skipTiers: ['hot'],
405
+
metadata: { mimeType: 'video/mp4' },
406
+
});
407
+
408
+
// Get file with source tracking
409
+
const result = await storage.getWithMetadata(`${did}/${rkey}/index.html`);
410
+
console.log(`Served from ${result.source}`); // Likely 'hot' for index.html
411
+
412
+
// Invalidate entire site
413
+
await storage.invalidate(`${did}/${rkey}/`);
414
+
415
+
// Renew TTL when site is accessed
416
+
await storage.touch(`${did}/${rkey}/index.html`);
417
+
```
418
+
419
+
### Pattern 3: Custom Backend (SQLite)
420
+
421
+
Implement the `StorageTier` interface to use any backend:
422
+
423
+
```typescript
424
+
import { StorageTier, StorageMetadata, TierStats } from 'tiered-storage';
425
+
import Database from 'better-sqlite3';
426
+
427
+
class SQLiteStorageTier implements StorageTier {
428
+
private db: Database.Database;
429
+
430
+
constructor(dbPath: string) {
431
+
this.db = new Database(dbPath);
432
+
this.db.exec(`
433
+
CREATE TABLE IF NOT EXISTS cache (
434
+
key TEXT PRIMARY KEY,
435
+
data BLOB NOT NULL,
436
+
metadata TEXT NOT NULL
437
+
)
438
+
`);
439
+
}
440
+
441
+
async get(key: string): Promise<Uint8Array | null> {
442
+
const row = this.db.prepare('SELECT data FROM cache WHERE key = ?').get(key);
443
+
return row ? new Uint8Array(row.data) : null;
444
+
}
445
+
446
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
447
+
this.db.prepare('INSERT OR REPLACE INTO cache (key, data, metadata) VALUES (?, ?, ?)')
448
+
.run(key, Buffer.from(data), JSON.stringify(metadata));
449
+
}
450
+
451
+
async delete(key: string): Promise<void> {
452
+
this.db.prepare('DELETE FROM cache WHERE key = ?').run(key);
453
+
}
454
+
455
+
async exists(key: string): Promise<boolean> {
456
+
const row = this.db.prepare('SELECT 1 FROM cache WHERE key = ?').get(key);
457
+
return !!row;
458
+
}
459
+
460
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
461
+
const query = prefix
462
+
? this.db.prepare('SELECT key FROM cache WHERE key LIKE ?')
463
+
: this.db.prepare('SELECT key FROM cache');
464
+
465
+
const rows = prefix ? query.all(`${prefix}%`) : query.all();
466
+
467
+
for (const row of rows) {
468
+
yield row.key;
469
+
}
470
+
}
471
+
472
+
async deleteMany(keys: string[]): Promise<void> {
473
+
const placeholders = keys.map(() => '?').join(',');
474
+
this.db.prepare(`DELETE FROM cache WHERE key IN (${placeholders})`).run(...keys);
475
+
}
476
+
477
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
478
+
const row = this.db.prepare('SELECT metadata FROM cache WHERE key = ?').get(key);
479
+
return row ? JSON.parse(row.metadata) : null;
480
+
}
481
+
482
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
483
+
this.db.prepare('UPDATE cache SET metadata = ? WHERE key = ?')
484
+
.run(JSON.stringify(metadata), key);
485
+
}
486
+
487
+
async getStats(): Promise<TierStats> {
488
+
const row = this.db.prepare('SELECT COUNT(*) as count, SUM(LENGTH(data)) as bytes FROM cache').get();
489
+
return { items: row.count, bytes: row.bytes || 0 };
490
+
}
491
+
492
+
async clear(): Promise<void> {
493
+
this.db.prepare('DELETE FROM cache').run();
494
+
}
495
+
}
496
+
497
+
// Use it
498
+
const storage = new TieredStorage({
499
+
tiers: {
500
+
warm: new SQLiteStorageTier('./cache.db'),
501
+
cold: new DiskStorageTier({ directory: './storage' }),
502
+
},
503
+
});
504
+
```
505
+
506
+
## Running Examples
507
+
508
+
### Interactive Demo Server
509
+
510
+
Run a **real HTTP server** that serves the example site using tiered storage:
511
+
512
+
```bash
513
+
# Configure S3 credentials first (copy .env.example to .env and fill in)
514
+
cp .env.example .env
515
+
516
+
# Start the demo server
517
+
bun run serve
518
+
```
519
+
520
+
Then visit:
521
+
- **http://localhost:3000/** - The demo site served from tiered storage
522
+
- **http://localhost:3000/admin/stats** - Live cache statistics dashboard
523
+
524
+
Watch the console to see which tier serves each request:
525
+
- 🔥 **Hot tier (memory)** - index.html served instantly
526
+
- 💾 **Warm tier (disk)** - Other pages served from disk cache
527
+
- ☁️ **Cold tier (S3)** - First access fetches from S3, then cached
528
+
529
+
### Command-Line Examples
530
+
531
+
Or run the non-interactive examples:
532
+
533
+
```bash
534
+
bun run example
535
+
```
536
+
537
+
The examples include:
538
+
- **Basic CRUD operations** with statistics tracking
539
+
- **Static site hosting** using the real site in `example-site/` directory
540
+
- **Bootstrap demonstrations** (warming caches from lower tiers)
541
+
- **Promotion strategy comparisons** (eager vs lazy)
542
+
543
+
The `example-site/` directory contains a complete static website with:
544
+
- `index.html` - Stored in hot + warm + cold (instant serving)
545
+
- `about.html`, `docs.html` - Stored in warm + cold (skips hot)
546
+
- `style.css`, `script.js` - Stored in warm + cold (skips hot)
547
+
548
+
This demonstrates the exact pattern you'd use for wisp.place: critical files in memory, everything else on disk/S3.
549
+
550
+
## Testing
551
+
552
+
```bash
553
+
bun test
554
+
```
555
+
556
+
## Development
557
+
558
+
```bash
559
+
# Install dependencies
560
+
bun install
561
+
562
+
# Type check
563
+
bun run check
564
+
565
+
# Build
566
+
bun run build
567
+
568
+
# Run tests
569
+
bun test
570
+
571
+
```
572
+
## License
573
+
574
+
MIT
+27
agents.md
+27
agents.md
···
1
+
You are working on a project that stores cold objects in s3, warm objects on disk, and hot objects in memory. It serves APIs for library consumers to use.
2
+
3
+
## Package management
4
+
- Use bun for package management (bun install)
5
+
- Use npm run test to test
6
+
- Use npm run lint to lint
7
+
- Use npm run check to typecheck
8
+
9
+
Please test and typecheck always whenever you think you are done.
10
+
11
+
## Code style
12
+
- Use tabs for indentation, spaces allowed for diagrams in comments
13
+
- Use single quotes and add trailing commas
14
+
- Use template literals for user-facing strings and error messages
15
+
16
+
## Commenting
17
+
Add JSDoc comments to all new exported functions, methods, classes, fields, and enums
18
+
JSDoc should include proper annotations:
19
+
- use @param for parameters (no dashes after param names)
20
+
- use @returns for return values
21
+
- use @throws for exceptions when applicable
22
+
- keep descriptions concise but informative
23
+
24
+
## Misc
25
+
the .research/ directory serves as a workspace for temporary experiments, analysis, and planning materials. create it if necessary (it's gitignored). this directory may contain cloned repositories or other reference materials that can help inform implementation decisions
26
+
27
+
**don't make assumptions or speculate about code, plans, or requirements without exploring first; pause and ask for clarification when you're still unsure after looking into it**
+713
bun.lock
+713
bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"configVersion": 1,
4
+
"workspaces": {
5
+
"": {
6
+
"name": "tiered-storage",
7
+
"dependencies": {
8
+
"@aws-sdk/client-s3": "^3.500.0",
9
+
"hono": "^4.10.7",
10
+
"mime-types": "^3.0.2",
11
+
"tiny-lru": "^11.0.0",
12
+
},
13
+
"devDependencies": {
14
+
"@types/node": "^24.10.1",
15
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
16
+
"@typescript-eslint/parser": "^8.48.1",
17
+
"eslint": "^8.0.0",
18
+
"tsx": "^4.0.0",
19
+
"typescript": "^5.3.0",
20
+
"vitest": "^4.0.15",
21
+
},
22
+
},
23
+
},
24
+
"packages": {
25
+
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
26
+
27
+
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
28
+
29
+
"@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
30
+
31
+
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
32
+
33
+
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
34
+
35
+
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
36
+
37
+
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
38
+
39
+
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.946.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/credential-provider-node": "3.946.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", "@aws-sdk/middleware-flexible-checksums": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-sdk-s3": "3.946.0", "@aws-sdk/middleware-ssec": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-Y3ww3yd1wzmS2r3qgH3jg4MxCTdeNrae2J1BmdV+IW/2R2gFWJva5U5GbS6KUSUxanJBRG7gd8uOIi1b0EMOng=="],
40
+
41
+
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.946.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kGAs5iIVyUz4p6TX3pzG5q3cNxXnVpC4pwRC6DCSaSv9ozyPjc2d74FsK4fZ+J+ejtvCdJk72uiuQtWJc86Wuw=="],
42
+
43
+
"@aws-sdk/core": ["@aws-sdk/core@3.946.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg=="],
44
+
45
+
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-P4l+K6wX1tf8LmWUvZofdQ+BgCNyk6Tb9u1H10npvqpuCD+dCM4pXIBq3PQcv/juUBOvLGGREo+Govuh3lfD0Q=="],
46
+
47
+
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-/zeOJ6E7dGZQ/l2k7KytEoPJX0APIhwt0A79hPf/bUpMF4dDs2P6JmchDrotk0a0Y/MIdNF8sBQ/MEOPnBiYoQ=="],
48
+
49
+
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/credential-provider-env": "3.946.0", "@aws-sdk/credential-provider-http": "3.946.0", "@aws-sdk/credential-provider-login": "3.946.0", "@aws-sdk/credential-provider-process": "3.946.0", "@aws-sdk/credential-provider-sso": "3.946.0", "@aws-sdk/credential-provider-web-identity": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Pdgcra3RivWj/TuZmfFaHbqsvvgnSKO0CxlRUMMr0PgBiCnUhyl+zBktdNOeGsOPH2fUzQpYhcUjYUgVSdcSDQ=="],
50
+
51
+
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5iqLNc15u2Zx+7jOdQkIbP62N7n2031tw5hkmIG0DLnozhnk64osOh2CliiOE9x3c4P9Pf4frAwgyy9GzNTk2g=="],
52
+
53
+
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.946.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.946.0", "@aws-sdk/credential-provider-http": "3.946.0", "@aws-sdk/credential-provider-ini": "3.946.0", "@aws-sdk/credential-provider-process": "3.946.0", "@aws-sdk/credential-provider-sso": "3.946.0", "@aws-sdk/credential-provider-web-identity": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-I7URUqnBPng1a5y81OImxrwERysZqMBREG6svhhGeZgxmqcpAZ8z5ywILeQXdEOCuuES8phUp/ojzxFjPXp/eA=="],
54
+
55
+
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GtGHX7OGqIeVQ3DlVm5RRF43Qmf3S1+PLJv9svrdvAhAdy2bUb044FdXXqrtSsIfpzTKlHgQUiRo5MWLd35Ntw=="],
56
+
57
+
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.946.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.946.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/token-providers": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-LeGSSt2V5iwYey1ENGY75RmoDP3bA2iE/py8QBKW8EDA8hn74XBLkprhrK5iccOvU3UGWY8WrEKFAFGNjJOL9g=="],
58
+
59
+
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ocBCvjWfkbjxElBI1QUxOnHldsNhoU0uOICFvuRDAZAoxvypJHN3m5BJkqb7gqorBbcv3LRgmBdEnWXOAvq+7Q=="],
60
+
61
+
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg=="],
62
+
63
+
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA=="],
64
+
65
+
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.946.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-HJA7RIWsnxcChyZ1hNF/3JICkYCqDonxoeG8FkrmLRBknZ8WVdJiPD420/UwrWaa5F2MuTDA92jxk77rI09h1w=="],
66
+
67
+
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="],
68
+
69
+
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw=="],
70
+
71
+
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="],
72
+
73
+
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="],
74
+
75
+
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-0UTFmFd8PX2k/jLu/DBmR+mmLQWAtUGHYps9Rjx3dcXNwaMLaa/39NoV3qn7Dwzfpqc6JZlZzBk+NDOCJIHW9g=="],
76
+
77
+
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA=="],
78
+
79
+
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.7", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw=="],
80
+
81
+
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.946.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.946.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.946.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-rjAtEguukeW8mlyEQMQI56vxFoyWlaNwowmz1p1rav948SUjtrzjHAp4TOQWhibb7AR7BUTHBCgIcyCRjBEf4g=="],
82
+
83
+
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="],
84
+
85
+
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.946.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-61FZ685lKiJuQ06g6U7K3PL9EwKCxNm51wNlxyKV57nnl1GrLD0NC8O3/hDNkCQLNBArT9y3IXl2H7TtIxP8Jg=="],
86
+
87
+
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.946.0", "", { "dependencies": { "@aws-sdk/core": "3.946.0", "@aws-sdk/nested-clients": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-a5c+rM6CUPX2ExmUZ3DlbLlS5rQr4tbdoGcgBsjnAHiYx8MuMNAI+8M7wfjF13i2yvUQj5WEIddvLpayfEZj9g=="],
88
+
89
+
"@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="],
90
+
91
+
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="],
92
+
93
+
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="],
94
+
95
+
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="],
96
+
97
+
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="],
98
+
99
+
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.946.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.946.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q=="],
100
+
101
+
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="],
102
+
103
+
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="],
104
+
105
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="],
106
+
107
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="],
108
+
109
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.1", "", { "os": "android", "cpu": "arm64" }, "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ=="],
110
+
111
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.1", "", { "os": "android", "cpu": "x64" }, "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ=="],
112
+
113
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ=="],
114
+
115
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ=="],
116
+
117
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg=="],
118
+
119
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ=="],
120
+
121
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA=="],
122
+
123
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q=="],
124
+
125
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw=="],
126
+
127
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg=="],
128
+
129
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA=="],
130
+
131
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ=="],
132
+
133
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ=="],
134
+
135
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw=="],
136
+
137
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA=="],
138
+
139
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ=="],
140
+
141
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.1", "", { "os": "none", "cpu": "x64" }, "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg=="],
142
+
143
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g=="],
144
+
145
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg=="],
146
+
147
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg=="],
148
+
149
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA=="],
150
+
151
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg=="],
152
+
153
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ=="],
154
+
155
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
156
+
157
+
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
158
+
159
+
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
160
+
161
+
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
162
+
163
+
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
164
+
165
+
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
166
+
167
+
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
168
+
169
+
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
170
+
171
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
172
+
173
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
174
+
175
+
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
176
+
177
+
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
178
+
179
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="],
180
+
181
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="],
182
+
183
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="],
184
+
185
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="],
186
+
187
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="],
188
+
189
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="],
190
+
191
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="],
192
+
193
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="],
194
+
195
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="],
196
+
197
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="],
198
+
199
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="],
200
+
201
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="],
202
+
203
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="],
204
+
205
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="],
206
+
207
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="],
208
+
209
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="],
210
+
211
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="],
212
+
213
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="],
214
+
215
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="],
216
+
217
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="],
218
+
219
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="],
220
+
221
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="],
222
+
223
+
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="],
224
+
225
+
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="],
226
+
227
+
"@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="],
228
+
229
+
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="],
230
+
231
+
"@smithy/core": ["@smithy/core@3.18.7", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw=="],
232
+
233
+
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
234
+
235
+
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
236
+
237
+
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
238
+
239
+
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ=="],
240
+
241
+
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg=="],
242
+
243
+
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.5", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q=="],
244
+
245
+
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="],
246
+
247
+
"@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.6", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw=="],
248
+
249
+
"@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="],
250
+
251
+
"@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q=="],
252
+
253
+
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="],
254
+
255
+
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
256
+
257
+
"@smithy/md5-js": ["@smithy/md5-js@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg=="],
258
+
259
+
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="],
260
+
261
+
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.14", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg=="],
262
+
263
+
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q=="],
264
+
265
+
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="],
266
+
267
+
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="],
268
+
269
+
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="],
270
+
271
+
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="],
272
+
273
+
"@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="],
274
+
275
+
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="],
276
+
277
+
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="],
278
+
279
+
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="],
280
+
281
+
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="],
282
+
283
+
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="],
284
+
285
+
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="],
286
+
287
+
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.10", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ=="],
288
+
289
+
"@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="],
290
+
291
+
"@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="],
292
+
293
+
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
294
+
295
+
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
296
+
297
+
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
298
+
299
+
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
300
+
301
+
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
302
+
303
+
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.13", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA=="],
304
+
305
+
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.16", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg=="],
306
+
307
+
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="],
308
+
309
+
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
310
+
311
+
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="],
312
+
313
+
"@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="],
314
+
315
+
"@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="],
316
+
317
+
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
318
+
319
+
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
320
+
321
+
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
322
+
323
+
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
324
+
325
+
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
326
+
327
+
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
328
+
329
+
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
330
+
331
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
332
+
333
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
334
+
335
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="],
336
+
337
+
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="],
338
+
339
+
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="],
340
+
341
+
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="],
342
+
343
+
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="],
344
+
345
+
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="],
346
+
347
+
"@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="],
348
+
349
+
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="],
350
+
351
+
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="],
352
+
353
+
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="],
354
+
355
+
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
356
+
357
+
"@vitest/expect": ["@vitest/expect@4.0.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w=="],
358
+
359
+
"@vitest/mocker": ["@vitest/mocker@4.0.15", "", { "dependencies": { "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ=="],
360
+
361
+
"@vitest/pretty-format": ["@vitest/pretty-format@4.0.15", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A=="],
362
+
363
+
"@vitest/runner": ["@vitest/runner@4.0.15", "", { "dependencies": { "@vitest/utils": "4.0.15", "pathe": "^2.0.3" } }, "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw=="],
364
+
365
+
"@vitest/snapshot": ["@vitest/snapshot@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g=="],
366
+
367
+
"@vitest/spy": ["@vitest/spy@4.0.15", "", {}, "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw=="],
368
+
369
+
"@vitest/utils": ["@vitest/utils@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" } }, "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA=="],
370
+
371
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
372
+
373
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
374
+
375
+
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
376
+
377
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
378
+
379
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
380
+
381
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
382
+
383
+
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
384
+
385
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
386
+
387
+
"bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
388
+
389
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
390
+
391
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
392
+
393
+
"chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
394
+
395
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
396
+
397
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
398
+
399
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
400
+
401
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
402
+
403
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
404
+
405
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
406
+
407
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
408
+
409
+
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
410
+
411
+
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
412
+
413
+
"esbuild": ["esbuild@0.27.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.1", "@esbuild/android-arm": "0.27.1", "@esbuild/android-arm64": "0.27.1", "@esbuild/android-x64": "0.27.1", "@esbuild/darwin-arm64": "0.27.1", "@esbuild/darwin-x64": "0.27.1", "@esbuild/freebsd-arm64": "0.27.1", "@esbuild/freebsd-x64": "0.27.1", "@esbuild/linux-arm": "0.27.1", "@esbuild/linux-arm64": "0.27.1", "@esbuild/linux-ia32": "0.27.1", "@esbuild/linux-loong64": "0.27.1", "@esbuild/linux-mips64el": "0.27.1", "@esbuild/linux-ppc64": "0.27.1", "@esbuild/linux-riscv64": "0.27.1", "@esbuild/linux-s390x": "0.27.1", "@esbuild/linux-x64": "0.27.1", "@esbuild/netbsd-arm64": "0.27.1", "@esbuild/netbsd-x64": "0.27.1", "@esbuild/openbsd-arm64": "0.27.1", "@esbuild/openbsd-x64": "0.27.1", "@esbuild/openharmony-arm64": "0.27.1", "@esbuild/sunos-x64": "0.27.1", "@esbuild/win32-arm64": "0.27.1", "@esbuild/win32-ia32": "0.27.1", "@esbuild/win32-x64": "0.27.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA=="],
414
+
415
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
416
+
417
+
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
418
+
419
+
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
420
+
421
+
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
422
+
423
+
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
424
+
425
+
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
426
+
427
+
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
428
+
429
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
430
+
431
+
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
432
+
433
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
434
+
435
+
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
436
+
437
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
438
+
439
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
440
+
441
+
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
442
+
443
+
"fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
444
+
445
+
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
446
+
447
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
448
+
449
+
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
450
+
451
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
452
+
453
+
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
454
+
455
+
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
456
+
457
+
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
458
+
459
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
460
+
461
+
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
462
+
463
+
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
464
+
465
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
466
+
467
+
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
468
+
469
+
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
470
+
471
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
472
+
473
+
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
474
+
475
+
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
476
+
477
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
478
+
479
+
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
480
+
481
+
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
482
+
483
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
484
+
485
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
486
+
487
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
488
+
489
+
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
490
+
491
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
492
+
493
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
494
+
495
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
496
+
497
+
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
498
+
499
+
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
500
+
501
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
502
+
503
+
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
504
+
505
+
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
506
+
507
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
508
+
509
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
510
+
511
+
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
512
+
513
+
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
514
+
515
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
516
+
517
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
518
+
519
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
520
+
521
+
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
522
+
523
+
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
524
+
525
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
526
+
527
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
528
+
529
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
530
+
531
+
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
532
+
533
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
534
+
535
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
536
+
537
+
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
538
+
539
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
540
+
541
+
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
542
+
543
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
544
+
545
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
546
+
547
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
548
+
549
+
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
550
+
551
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
552
+
553
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
554
+
555
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
556
+
557
+
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
558
+
559
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
560
+
561
+
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
562
+
563
+
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
564
+
565
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
566
+
567
+
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
568
+
569
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
570
+
571
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
572
+
573
+
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
574
+
575
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
576
+
577
+
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
578
+
579
+
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
580
+
581
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
582
+
583
+
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
584
+
585
+
"strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="],
586
+
587
+
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
588
+
589
+
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
590
+
591
+
"tiny-lru": ["tiny-lru@11.4.5", "", {}, "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw=="],
592
+
593
+
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
594
+
595
+
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
596
+
597
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
598
+
599
+
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
600
+
601
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
602
+
603
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
604
+
605
+
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
606
+
607
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
608
+
609
+
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
610
+
611
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
612
+
613
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
614
+
615
+
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
616
+
617
+
"vite": ["vite@7.2.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ=="],
618
+
619
+
"vitest": ["vitest@4.0.15", "", { "dependencies": { "@vitest/expect": "4.0.15", "@vitest/mocker": "4.0.15", "@vitest/pretty-format": "4.0.15", "@vitest/runner": "4.0.15", "@vitest/snapshot": "4.0.15", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.15", "@vitest/browser-preview": "4.0.15", "@vitest/browser-webdriverio": "4.0.15", "@vitest/ui": "4.0.15", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA=="],
620
+
621
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
622
+
623
+
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
624
+
625
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
626
+
627
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
628
+
629
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
630
+
631
+
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
632
+
633
+
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
634
+
635
+
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
636
+
637
+
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
638
+
639
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
640
+
641
+
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
642
+
643
+
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
644
+
645
+
"vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
646
+
647
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
648
+
649
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
650
+
651
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
652
+
653
+
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
654
+
655
+
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
656
+
657
+
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
658
+
659
+
"vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
660
+
661
+
"vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
662
+
663
+
"vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
664
+
665
+
"vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
666
+
667
+
"vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
668
+
669
+
"vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
670
+
671
+
"vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
672
+
673
+
"vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
674
+
675
+
"vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
676
+
677
+
"vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
678
+
679
+
"vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
680
+
681
+
"vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
682
+
683
+
"vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
684
+
685
+
"vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
686
+
687
+
"vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
688
+
689
+
"vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
690
+
691
+
"vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
692
+
693
+
"vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
694
+
695
+
"vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
696
+
697
+
"vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
698
+
699
+
"vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
700
+
701
+
"vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
702
+
703
+
"vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
704
+
705
+
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
706
+
707
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
708
+
709
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
710
+
711
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
712
+
}
713
+
}
+47
example-site/README.md
+47
example-site/README.md
···
1
+
# Example Static Site
2
+
3
+
This is a demonstration static website used in the tiered-storage library examples.
4
+
5
+
## Files
6
+
7
+
- **index.html** (3.5 KB) - Homepage, stored in hot + warm + cold tiers
8
+
- **about.html** (4.2 KB) - About page, stored in warm + cold (skips hot)
9
+
- **docs.html** (3.8 KB) - Documentation, stored in warm + cold (skips hot)
10
+
- **style.css** (7.1 KB) - Stylesheet, stored in warm + cold (skips hot)
11
+
- **script.js** (1.9 KB) - JavaScript, stored in warm + cold (skips hot)
12
+
13
+
## Usage in Examples
14
+
15
+
The `example.ts` file demonstrates how this site would be stored using the tiered-storage library:
16
+
17
+
1. **index.html** is stored in all tiers (hot + warm + cold) because it's the entry point and needs instant serving
18
+
2. Other HTML pages skip the hot tier to save memory
19
+
3. CSS and JS files are stored in warm + cold
20
+
4. If there were large media files, they would be stored in cold tier only
21
+
22
+
## Tier Strategy
23
+
24
+
```
25
+
Hot Tier (Memory - 100MB):
26
+
└── index.html (3.5 KB)
27
+
28
+
Warm Tier (Disk - 10GB):
29
+
├── index.html (3.5 KB)
30
+
├── about.html (4.2 KB)
31
+
├── docs.html (3.8 KB)
32
+
├── style.css (7.1 KB)
33
+
└── script.js (1.9 KB)
34
+
35
+
Cold Tier (S3 - Unlimited):
36
+
├── index.html (3.5 KB)
37
+
├── about.html (4.2 KB)
38
+
├── docs.html (3.8 KB)
39
+
├── style.css (7.1 KB)
40
+
└── script.js (1.9 KB)
41
+
```
42
+
43
+
This strategy ensures:
44
+
- Lightning-fast serving of the homepage (from memory)
45
+
- Efficient use of hot tier capacity
46
+
- All files are cached on disk for fast local access
47
+
- S3 acts as the source of truth for disaster recovery
+86
example-site/about.html
+86
example-site/about.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>About - Tiered Storage Demo</title>
7
+
<link rel="stylesheet" href="style.css">
8
+
</head>
9
+
<body>
10
+
<header>
11
+
<nav>
12
+
<div class="logo">🗄️ TieredCache</div>
13
+
<ul>
14
+
<li><a href="index.html">Home</a></li>
15
+
<li><a href="about.html" class="active">About</a></li>
16
+
<li><a href="docs.html">Docs</a></li>
17
+
</ul>
18
+
</nav>
19
+
</header>
20
+
21
+
<main>
22
+
<section class="content">
23
+
<h1>About Tiered Storage</h1>
24
+
25
+
<h2>The Problem</h2>
26
+
<p>Modern applications need to balance three competing concerns:</p>
27
+
<ul>
28
+
<li><strong>Speed:</strong> Users expect instant responses</li>
29
+
<li><strong>Cost:</strong> Keeping everything in memory is expensive</li>
30
+
<li><strong>Reliability:</strong> Data must be durable and recoverable</li>
31
+
</ul>
32
+
33
+
<h2>The Solution</h2>
34
+
<p>Tiered storage provides the best of all worlds by automatically managing data across multiple storage tiers:</p>
35
+
36
+
<div class="solution-grid">
37
+
<div class="solution-item">
38
+
<h3>🚀 Performance</h3>
39
+
<p>Hot tier (memory) serves critical files like index.html in microseconds</p>
40
+
</div>
41
+
<div class="solution-item">
42
+
<h3>💰 Cost-Effective</h3>
43
+
<p>Warm tier (disk) and cold tier (S3) handle bulk storage efficiently</p>
44
+
</div>
45
+
<div class="solution-item">
46
+
<h3>🛡️ Reliability</h3>
47
+
<p>Cold tier acts as source of truth with automatic backups</p>
48
+
</div>
49
+
</div>
50
+
51
+
<h2>Use Cases</h2>
52
+
<div class="use-cases">
53
+
<div class="use-case">
54
+
<h4>Static Site Hosting</h4>
55
+
<p>Store index.html in memory for instant serving, while keeping images and videos on disk/S3. Perfect for CDN-like performance on a single server.</p>
56
+
</div>
57
+
<div class="use-case">
58
+
<h4>Content Delivery</h4>
59
+
<p>Automatically promote popular content to hot tier based on access patterns. Rarely accessed content stays in cold storage.</p>
60
+
</div>
61
+
<div class="use-case">
62
+
<h4>Database Caching</h4>
63
+
<p>Cache query results in memory, with overflow to disk and S3. Automatic TTL management keeps data fresh.</p>
64
+
</div>
65
+
</div>
66
+
67
+
<h2>How This Demo Works</h2>
68
+
<p>This example site is stored using the tiered-storage library:</p>
69
+
<ol>
70
+
<li><code>index.html</code> - Stored in all tiers (hot + warm + cold) for instant access</li>
71
+
<li><code>about.html</code> - Stored in warm + cold (skips hot to save memory)</li>
72
+
<li><code>style.css</code> - Stored in warm + cold</li>
73
+
<li><code>script.js</code> - Stored in warm + cold</li>
74
+
<li><code>hero-image.jpg</code> - Large file, stored in cold tier only</li>
75
+
</ol>
76
+
<p>When you request a page, the library automatically checks hot → warm → cold and serves from the fastest available tier.</p>
77
+
</section>
78
+
</main>
79
+
80
+
<footer>
81
+
<p>© 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
82
+
</footer>
83
+
84
+
<script src="script.js"></script>
85
+
</body>
86
+
</html>
+105
example-site/docs.html
+105
example-site/docs.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>Documentation - Tiered Storage</title>
7
+
<link rel="stylesheet" href="style.css">
8
+
</head>
9
+
<body>
10
+
<header>
11
+
<nav>
12
+
<div class="logo">🗄️ TieredCache</div>
13
+
<ul>
14
+
<li><a href="index.html">Home</a></li>
15
+
<li><a href="about.html">About</a></li>
16
+
<li><a href="docs.html" class="active">Docs</a></li>
17
+
</ul>
18
+
</nav>
19
+
</header>
20
+
21
+
<main>
22
+
<section class="content">
23
+
<h1>Quick Start Guide</h1>
24
+
25
+
<h2>Installation</h2>
26
+
<pre><code>npm install tiered-storage
27
+
# or
28
+
bun add tiered-storage</code></pre>
29
+
30
+
<h2>Basic Usage</h2>
31
+
<pre><code>import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from 'tiered-storage';
32
+
33
+
const storage = new TieredStorage({
34
+
tiers: {
35
+
hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }),
36
+
warm: new DiskStorageTier({ directory: './cache' }),
37
+
cold: new S3StorageTier({
38
+
bucket: 'my-bucket',
39
+
region: 'us-east-1',
40
+
}),
41
+
},
42
+
compression: true,
43
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
44
+
});
45
+
46
+
// Store data
47
+
await storage.set('user:123', { name: 'Alice' });
48
+
49
+
// Retrieve data
50
+
const user = await storage.get('user:123');
51
+
52
+
// Invalidate by prefix
53
+
await storage.invalidate('user:');</code></pre>
54
+
55
+
<h2>Selective Tier Placement</h2>
56
+
<p>Control which tiers receive specific files:</p>
57
+
<pre><code>// Critical file - store in all tiers
58
+
await storage.set('index.html', htmlContent);
59
+
60
+
// Large file - skip hot tier to save memory
61
+
await storage.set('video.mp4', videoData, {
62
+
skipTiers: ['hot']
63
+
});</code></pre>
64
+
65
+
<h2>Bootstrap on Startup</h2>
66
+
<pre><code>// Warm up hot tier from warm tier
67
+
await storage.bootstrapHot(1000); // Load top 1000 items
68
+
69
+
// Warm up warm tier from cold tier
70
+
await storage.bootstrapWarm({
71
+
sinceDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
72
+
limit: 10000,
73
+
});</code></pre>
74
+
75
+
<h2>Statistics & Monitoring</h2>
76
+
<pre><code>const stats = await storage.getStats();
77
+
console.log('Hot tier:', stats.hot);
78
+
console.log('Warm tier:', stats.warm);
79
+
console.log('Cold tier:', stats.cold);
80
+
console.log('Hit rate:', stats.hitRate);</code></pre>
81
+
82
+
<h2>API Reference</h2>
83
+
<ul>
84
+
<li><code>get(key)</code> - Retrieve data</li>
85
+
<li><code>getWithMetadata(key)</code> - Retrieve with metadata and source tier</li>
86
+
<li><code>set(key, data, options)</code> - Store data</li>
87
+
<li><code>delete(key)</code> - Delete from all tiers</li>
88
+
<li><code>exists(key)</code> - Check if key exists</li>
89
+
<li><code>touch(key, ttlMs)</code> - Renew TTL</li>
90
+
<li><code>invalidate(prefix)</code> - Delete by prefix</li>
91
+
<li><code>listKeys(prefix)</code> - List keys</li>
92
+
<li><code>getStats()</code> - Get statistics</li>
93
+
<li><code>bootstrapHot(limit)</code> - Warm up hot tier</li>
94
+
<li><code>bootstrapWarm(options)</code> - Warm up warm tier</li>
95
+
</ul>
96
+
</section>
97
+
</main>
98
+
99
+
<footer>
100
+
<p>© 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
101
+
</footer>
102
+
103
+
<script src="script.js"></script>
104
+
</body>
105
+
</html>
+103
example-site/index.html
+103
example-site/index.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>Tiered Storage Demo Site</title>
7
+
<link rel="stylesheet" href="style.css">
8
+
</head>
9
+
<body>
10
+
<header>
11
+
<nav>
12
+
<div class="logo">🗄️ TieredCache</div>
13
+
<ul>
14
+
<li><a href="index.html" class="active">Home</a></li>
15
+
<li><a href="about.html">About</a></li>
16
+
<li><a href="docs.html">Docs</a></li>
17
+
</ul>
18
+
</nav>
19
+
</header>
20
+
21
+
<main>
22
+
<section class="hero">
23
+
<h1>Lightning-Fast Multi-Tier Caching</h1>
24
+
<p>Store your data across memory, disk, and cloud storage with automatic promotion and intelligent eviction.</p>
25
+
<div class="cta-buttons">
26
+
<a href="#features" class="btn btn-primary">Learn More</a>
27
+
<a href="docs.html" class="btn btn-secondary">Documentation</a>
28
+
</div>
29
+
</section>
30
+
31
+
<section id="features" class="features">
32
+
<h2>Features</h2>
33
+
<div class="feature-grid">
34
+
<div class="feature-card">
35
+
<div class="feature-icon">⚡</div>
36
+
<h3>Hot Tier (Memory)</h3>
37
+
<p>Lightning-fast access with LRU eviction. Perfect for frequently accessed data like index.html.</p>
38
+
</div>
39
+
<div class="feature-card">
40
+
<div class="feature-icon">💾</div>
41
+
<h3>Warm Tier (Disk)</h3>
42
+
<p>Fast local storage with configurable eviction policies. Ideal for site assets and media.</p>
43
+
</div>
44
+
<div class="feature-card">
45
+
<div class="feature-icon">☁️</div>
46
+
<h3>Cold Tier (S3)</h3>
47
+
<p>Unlimited cloud storage as your source of truth. Supports S3, R2, MinIO, and more.</p>
48
+
</div>
49
+
</div>
50
+
</section>
51
+
52
+
<section class="architecture">
53
+
<h2>How It Works</h2>
54
+
<div class="tier-diagram">
55
+
<div class="tier hot">
56
+
<div class="tier-label">Hot (Memory)</div>
57
+
<div class="tier-content">index.html ✓</div>
58
+
</div>
59
+
<div class="arrow">↓</div>
60
+
<div class="tier warm">
61
+
<div class="tier-label">Warm (Disk)</div>
62
+
<div class="tier-content">index.html, style.css, images ✓</div>
63
+
</div>
64
+
<div class="arrow">↓</div>
65
+
<div class="tier cold">
66
+
<div class="tier-label">Cold (S3)</div>
67
+
<div class="tier-content">All files (source of truth) ✓</div>
68
+
</div>
69
+
</div>
70
+
<p class="diagram-note">Data cascades down on writes, bubbles up on reads</p>
71
+
</section>
72
+
73
+
<section class="stats" id="cache-stats">
74
+
<h2>Live Cache Statistics</h2>
75
+
<div class="stats-grid">
76
+
<div class="stat-card">
77
+
<div class="stat-value" id="hot-items">-</div>
78
+
<div class="stat-label">Hot Tier Items</div>
79
+
</div>
80
+
<div class="stat-card">
81
+
<div class="stat-value" id="warm-items">-</div>
82
+
<div class="stat-label">Warm Tier Items</div>
83
+
</div>
84
+
<div class="stat-card">
85
+
<div class="stat-value" id="cold-items">-</div>
86
+
<div class="stat-label">Cold Tier Items</div>
87
+
</div>
88
+
<div class="stat-card">
89
+
<div class="stat-value" id="hit-rate">-</div>
90
+
<div class="stat-label">Cache Hit Rate</div>
91
+
</div>
92
+
</div>
93
+
</section>
94
+
</main>
95
+
96
+
<footer>
97
+
<p>© 2024 Tiered Storage Library. Built with ❤️ for performance.</p>
98
+
<p><small>This is a demo site to showcase the tiered-storage library capabilities.</small></p>
99
+
</footer>
100
+
101
+
<script src="script.js"></script>
102
+
</body>
103
+
</html>
+84
example-site/script.js
+84
example-site/script.js
···
1
+
/**
2
+
* Tiered Storage Demo Site - Client-side JavaScript
3
+
*
4
+
* This script demonstrates how a static site can interact with
5
+
* the tiered storage system (in a real scenario, stats would be
6
+
* fetched from a backend API that uses the storage library)
7
+
*/
8
+
9
+
// Simulated cache statistics
10
+
// In a real implementation, this would fetch from your backend
11
+
function simulateCacheStats() {
12
+
return {
13
+
hot: {
14
+
items: 1,
15
+
bytes: 3547,
16
+
hits: 42,
17
+
misses: 3,
18
+
},
19
+
warm: {
20
+
items: 5,
21
+
bytes: 127438,
22
+
hits: 15,
23
+
misses: 2,
24
+
},
25
+
cold: {
26
+
items: 5,
27
+
bytes: 127438,
28
+
},
29
+
totalHits: 57,
30
+
totalMisses: 5,
31
+
hitRate: 0.919,
32
+
};
33
+
}
34
+
35
+
// Update stats display
36
+
function updateStatsDisplay() {
37
+
const stats = simulateCacheStats();
38
+
39
+
const hotItems = document.getElementById('hot-items');
40
+
const warmItems = document.getElementById('warm-items');
41
+
const coldItems = document.getElementById('cold-items');
42
+
const hitRate = document.getElementById('hit-rate');
43
+
44
+
if (hotItems) hotItems.textContent = stats.hot.items;
45
+
if (warmItems) warmItems.textContent = stats.warm.items;
46
+
if (coldItems) coldItems.textContent = stats.cold.items;
47
+
if (hitRate) hitRate.textContent = `${(stats.hitRate * 100).toFixed(1)}%`;
48
+
}
49
+
50
+
// Smooth scrolling for anchor links
51
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
52
+
anchor.addEventListener('click', function (e) {
53
+
e.preventDefault();
54
+
const target = document.querySelector(this.getAttribute('href'));
55
+
if (target) {
56
+
target.scrollIntoView({
57
+
behavior: 'smooth',
58
+
block: 'start'
59
+
});
60
+
}
61
+
});
62
+
});
63
+
64
+
// Initialize stats when page loads
65
+
if (document.readyState === 'loading') {
66
+
document.addEventListener('DOMContentLoaded', updateStatsDisplay);
67
+
} else {
68
+
updateStatsDisplay();
69
+
}
70
+
71
+
// Update stats periodically (simulate real-time updates)
72
+
setInterval(updateStatsDisplay, 5000);
73
+
74
+
// Add active class to navigation based on current page
75
+
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
76
+
document.querySelectorAll('nav a').forEach(link => {
77
+
if (link.getAttribute('href') === currentPage) {
78
+
link.classList.add('active');
79
+
}
80
+
});
81
+
82
+
// Log page view (in real app, would send to analytics)
83
+
console.log(`[TieredCache] Page viewed: ${currentPage}`);
84
+
console.log(`[TieredCache] This page was likely served from ${currentPage === 'index.html' ? 'hot tier (memory)' : 'warm tier (disk)'}`);
+439
example-site/style.css
+439
example-site/style.css
···
1
+
/* Tiered Storage Demo Site - Stylesheet */
2
+
3
+
:root {
4
+
--primary-color: #3b82f6;
5
+
--secondary-color: #8b5cf6;
6
+
--success-color: #10b981;
7
+
--background: #0f172a;
8
+
--surface: #1e293b;
9
+
--text: #f1f5f9;
10
+
--text-muted: #94a3b8;
11
+
--border: #334155;
12
+
}
13
+
14
+
* {
15
+
margin: 0;
16
+
padding: 0;
17
+
box-sizing: border-box;
18
+
}
19
+
20
+
body {
21
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
22
+
background: var(--background);
23
+
color: var(--text);
24
+
line-height: 1.6;
25
+
min-height: 100vh;
26
+
display: flex;
27
+
flex-direction: column;
28
+
}
29
+
30
+
/* Header & Navigation */
31
+
header {
32
+
background: var(--surface);
33
+
border-bottom: 1px solid var(--border);
34
+
position: sticky;
35
+
top: 0;
36
+
z-index: 100;
37
+
}
38
+
39
+
nav {
40
+
max-width: 1200px;
41
+
margin: 0 auto;
42
+
padding: 1rem 2rem;
43
+
display: flex;
44
+
justify-content: space-between;
45
+
align-items: center;
46
+
}
47
+
48
+
.logo {
49
+
font-size: 1.5rem;
50
+
font-weight: 700;
51
+
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
52
+
-webkit-background-clip: text;
53
+
-webkit-text-fill-color: transparent;
54
+
background-clip: text;
55
+
}
56
+
57
+
nav ul {
58
+
display: flex;
59
+
gap: 2rem;
60
+
list-style: none;
61
+
}
62
+
63
+
nav a {
64
+
color: var(--text-muted);
65
+
text-decoration: none;
66
+
transition: color 0.2s;
67
+
padding: 0.5rem 1rem;
68
+
border-radius: 0.5rem;
69
+
}
70
+
71
+
nav a:hover,
72
+
nav a.active {
73
+
color: var(--text);
74
+
background: rgba(59, 130, 246, 0.1);
75
+
}
76
+
77
+
/* Main Content */
78
+
main {
79
+
flex: 1;
80
+
max-width: 1200px;
81
+
margin: 0 auto;
82
+
padding: 2rem;
83
+
width: 100%;
84
+
}
85
+
86
+
/* Hero Section */
87
+
.hero {
88
+
text-align: center;
89
+
padding: 4rem 2rem;
90
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1));
91
+
border-radius: 1rem;
92
+
margin-bottom: 3rem;
93
+
}
94
+
95
+
.hero h1 {
96
+
font-size: 3rem;
97
+
margin-bottom: 1rem;
98
+
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
99
+
-webkit-background-clip: text;
100
+
-webkit-text-fill-color: transparent;
101
+
background-clip: text;
102
+
}
103
+
104
+
.hero p {
105
+
font-size: 1.25rem;
106
+
color: var(--text-muted);
107
+
max-width: 600px;
108
+
margin: 0 auto 2rem;
109
+
}
110
+
111
+
.cta-buttons {
112
+
display: flex;
113
+
gap: 1rem;
114
+
justify-content: center;
115
+
}
116
+
117
+
.btn {
118
+
padding: 0.75rem 2rem;
119
+
border-radius: 0.5rem;
120
+
text-decoration: none;
121
+
font-weight: 600;
122
+
transition: all 0.2s;
123
+
display: inline-block;
124
+
}
125
+
126
+
.btn-primary {
127
+
background: var(--primary-color);
128
+
color: white;
129
+
}
130
+
131
+
.btn-primary:hover {
132
+
background: #2563eb;
133
+
transform: translateY(-2px);
134
+
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
135
+
}
136
+
137
+
.btn-secondary {
138
+
background: var(--surface);
139
+
color: var(--text);
140
+
border: 1px solid var(--border);
141
+
}
142
+
143
+
.btn-secondary:hover {
144
+
background: var(--border);
145
+
}
146
+
147
+
/* Features */
148
+
.features {
149
+
margin-bottom: 3rem;
150
+
}
151
+
152
+
.features h2 {
153
+
text-align: center;
154
+
font-size: 2rem;
155
+
margin-bottom: 2rem;
156
+
}
157
+
158
+
.feature-grid {
159
+
display: grid;
160
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
161
+
gap: 2rem;
162
+
}
163
+
164
+
.feature-card {
165
+
background: var(--surface);
166
+
padding: 2rem;
167
+
border-radius: 1rem;
168
+
border: 1px solid var(--border);
169
+
transition: transform 0.2s, box-shadow 0.2s;
170
+
}
171
+
172
+
.feature-card:hover {
173
+
transform: translateY(-4px);
174
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
175
+
}
176
+
177
+
.feature-icon {
178
+
font-size: 3rem;
179
+
margin-bottom: 1rem;
180
+
}
181
+
182
+
.feature-card h3 {
183
+
color: var(--primary-color);
184
+
margin-bottom: 0.5rem;
185
+
}
186
+
187
+
.feature-card p {
188
+
color: var(--text-muted);
189
+
}
190
+
191
+
/* Architecture Diagram */
192
+
.architecture {
193
+
margin-bottom: 3rem;
194
+
}
195
+
196
+
.architecture h2 {
197
+
text-align: center;
198
+
font-size: 2rem;
199
+
margin-bottom: 2rem;
200
+
}
201
+
202
+
.tier-diagram {
203
+
max-width: 600px;
204
+
margin: 0 auto;
205
+
}
206
+
207
+
.tier {
208
+
background: var(--surface);
209
+
border: 2px solid var(--border);
210
+
border-radius: 0.5rem;
211
+
padding: 1.5rem;
212
+
margin-bottom: 1rem;
213
+
}
214
+
215
+
.tier.hot {
216
+
border-color: #ef4444;
217
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), var(--surface));
218
+
}
219
+
220
+
.tier.warm {
221
+
border-color: #f59e0b;
222
+
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), var(--surface));
223
+
}
224
+
225
+
.tier.cold {
226
+
border-color: var(--primary-color);
227
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), var(--surface));
228
+
}
229
+
230
+
.tier-label {
231
+
font-weight: 700;
232
+
margin-bottom: 0.5rem;
233
+
font-size: 1.1rem;
234
+
}
235
+
236
+
.tier-content {
237
+
color: var(--text-muted);
238
+
font-size: 0.9rem;
239
+
}
240
+
241
+
.arrow {
242
+
text-align: center;
243
+
font-size: 2rem;
244
+
color: var(--text-muted);
245
+
margin: -0.5rem 0;
246
+
}
247
+
248
+
.diagram-note {
249
+
text-align: center;
250
+
color: var(--text-muted);
251
+
font-style: italic;
252
+
margin-top: 1rem;
253
+
}
254
+
255
+
/* Stats */
256
+
.stats {
257
+
margin-bottom: 3rem;
258
+
}
259
+
260
+
.stats h2 {
261
+
text-align: center;
262
+
font-size: 2rem;
263
+
margin-bottom: 2rem;
264
+
}
265
+
266
+
.stats-grid {
267
+
display: grid;
268
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
269
+
gap: 1.5rem;
270
+
}
271
+
272
+
.stat-card {
273
+
background: var(--surface);
274
+
padding: 2rem;
275
+
border-radius: 1rem;
276
+
border: 1px solid var(--border);
277
+
text-align: center;
278
+
}
279
+
280
+
.stat-value {
281
+
font-size: 2.5rem;
282
+
font-weight: 700;
283
+
color: var(--primary-color);
284
+
margin-bottom: 0.5rem;
285
+
}
286
+
287
+
.stat-label {
288
+
color: var(--text-muted);
289
+
font-size: 0.9rem;
290
+
}
291
+
292
+
/* Content Pages */
293
+
.content {
294
+
max-width: 800px;
295
+
margin: 0 auto;
296
+
}
297
+
298
+
.content h1 {
299
+
font-size: 2.5rem;
300
+
margin-bottom: 1.5rem;
301
+
color: var(--primary-color);
302
+
}
303
+
304
+
.content h2 {
305
+
font-size: 1.8rem;
306
+
margin-top: 2rem;
307
+
margin-bottom: 1rem;
308
+
}
309
+
310
+
.content h3 {
311
+
font-size: 1.3rem;
312
+
margin-top: 1.5rem;
313
+
margin-bottom: 0.5rem;
314
+
}
315
+
316
+
.content h4 {
317
+
font-size: 1.1rem;
318
+
margin-top: 1rem;
319
+
margin-bottom: 0.5rem;
320
+
color: var(--primary-color);
321
+
}
322
+
323
+
.content p {
324
+
margin-bottom: 1rem;
325
+
color: var(--text-muted);
326
+
}
327
+
328
+
.content ul, .content ol {
329
+
margin-bottom: 1rem;
330
+
margin-left: 2rem;
331
+
color: var(--text-muted);
332
+
}
333
+
334
+
.content li {
335
+
margin-bottom: 0.5rem;
336
+
}
337
+
338
+
.content code {
339
+
background: var(--surface);
340
+
padding: 0.2rem 0.5rem;
341
+
border-radius: 0.25rem;
342
+
font-family: 'Monaco', 'Courier New', monospace;
343
+
font-size: 0.9em;
344
+
color: var(--success-color);
345
+
}
346
+
347
+
.content pre {
348
+
background: var(--surface);
349
+
padding: 1.5rem;
350
+
border-radius: 0.5rem;
351
+
overflow-x: auto;
352
+
margin-bottom: 1rem;
353
+
border: 1px solid var(--border);
354
+
}
355
+
356
+
.content pre code {
357
+
background: none;
358
+
padding: 0;
359
+
color: var(--text);
360
+
}
361
+
362
+
.solution-grid {
363
+
display: grid;
364
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
365
+
gap: 1.5rem;
366
+
margin: 2rem 0;
367
+
}
368
+
369
+
.solution-item {
370
+
background: var(--surface);
371
+
padding: 1.5rem;
372
+
border-radius: 0.5rem;
373
+
border: 1px solid var(--border);
374
+
}
375
+
376
+
.solution-item h3 {
377
+
margin-top: 0;
378
+
}
379
+
380
+
.use-cases {
381
+
display: flex;
382
+
flex-direction: column;
383
+
gap: 1.5rem;
384
+
margin-top: 2rem;
385
+
}
386
+
387
+
.use-case {
388
+
background: var(--surface);
389
+
padding: 1.5rem;
390
+
border-radius: 0.5rem;
391
+
border-left: 4px solid var(--primary-color);
392
+
}
393
+
394
+
.use-case h4 {
395
+
margin-top: 0;
396
+
}
397
+
398
+
/* Footer */
399
+
footer {
400
+
background: var(--surface);
401
+
border-top: 1px solid var(--border);
402
+
padding: 2rem;
403
+
text-align: center;
404
+
color: var(--text-muted);
405
+
margin-top: auto;
406
+
}
407
+
408
+
footer p {
409
+
margin: 0.5rem 0;
410
+
}
411
+
412
+
/* Responsive */
413
+
@media (max-width: 768px) {
414
+
.hero h1 {
415
+
font-size: 2rem;
416
+
}
417
+
418
+
.hero p {
419
+
font-size: 1rem;
420
+
}
421
+
422
+
nav ul {
423
+
gap: 1rem;
424
+
}
425
+
426
+
.feature-grid,
427
+
.stats-grid {
428
+
grid-template-columns: 1fr;
429
+
}
430
+
431
+
.solution-grid {
432
+
grid-template-columns: 1fr;
433
+
}
434
+
435
+
.cta-buttons {
436
+
flex-direction: column;
437
+
align-items: stretch;
438
+
}
439
+
}
+434
example.ts
+434
example.ts
···
1
+
/**
2
+
* Example usage of the tiered-storage library
3
+
*
4
+
* Run with: bun run example
5
+
*
6
+
* Note: This example uses S3 for cold storage. You'll need to configure
7
+
* AWS credentials and an S3 bucket in .env (see .env.example)
8
+
*/
9
+
10
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js';
11
+
import { rm } from 'node:fs/promises';
12
+
13
+
// Configuration from environment variables
14
+
const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example';
15
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
16
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
17
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false'; // Default true
18
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
19
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
20
+
21
+
async function basicExample() {
22
+
console.log('\n=== Basic Example ===\n');
23
+
24
+
const storage = new TieredStorage({
25
+
tiers: {
26
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }), // 10MB
27
+
warm: new DiskStorageTier({ directory: './example-cache/basic/warm' }),
28
+
cold: new S3StorageTier({
29
+
bucket: S3_BUCKET,
30
+
region: S3_REGION,
31
+
endpoint: S3_ENDPOINT,
32
+
forcePathStyle: S3_FORCE_PATH_STYLE,
33
+
credentials:
34
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
35
+
? {
36
+
accessKeyId: AWS_ACCESS_KEY_ID,
37
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
38
+
}
39
+
: undefined,
40
+
prefix: 'example/basic/',
41
+
}),
42
+
},
43
+
compression: true,
44
+
defaultTTL: 60 * 60 * 1000, // 1 hour
45
+
});
46
+
47
+
// Store some data
48
+
console.log('Storing user data...');
49
+
await storage.set('user:alice', {
50
+
name: 'Alice',
51
+
email: 'alice@example.com',
52
+
role: 'admin',
53
+
});
54
+
55
+
await storage.set('user:bob', {
56
+
name: 'Bob',
57
+
email: 'bob@example.com',
58
+
role: 'user',
59
+
});
60
+
61
+
// Retrieve with metadata
62
+
const result = await storage.getWithMetadata('user:alice');
63
+
if (result) {
64
+
console.log(`Retrieved user:alice from ${result.source} tier:`);
65
+
console.log(result.data);
66
+
console.log('Metadata:', {
67
+
size: result.metadata.size,
68
+
compressed: result.metadata.compressed,
69
+
accessCount: result.metadata.accessCount,
70
+
});
71
+
}
72
+
73
+
// Get statistics
74
+
const stats = await storage.getStats();
75
+
console.log('\nStorage Statistics:');
76
+
console.log(`Hot tier: ${stats.hot?.items} items, ${stats.hot?.bytes} bytes`);
77
+
console.log(`Warm tier: ${stats.warm?.items} items, ${stats.warm?.bytes} bytes`);
78
+
console.log(`Cold tier (S3): ${stats.cold.items} items, ${stats.cold.bytes} bytes`);
79
+
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(2)}%`);
80
+
81
+
// List all keys with prefix
82
+
console.log('\nAll user keys:');
83
+
for await (const key of storage.listKeys('user:')) {
84
+
console.log(` - ${key}`);
85
+
}
86
+
87
+
// Invalidate by prefix
88
+
console.log('\nInvalidating all user keys...');
89
+
const deleted = await storage.invalidate('user:');
90
+
console.log(`Deleted ${deleted} keys`);
91
+
}
92
+
93
+
async function staticSiteHostingExample() {
94
+
console.log('\n=== Static Site Hosting Example (wisp.place pattern) ===\n');
95
+
96
+
const storage = new TieredStorage({
97
+
tiers: {
98
+
hot: new MemoryStorageTier({
99
+
maxSizeBytes: 50 * 1024 * 1024, // 50MB
100
+
maxItems: 500,
101
+
}),
102
+
warm: new DiskStorageTier({
103
+
directory: './example-cache/sites/warm',
104
+
maxSizeBytes: 1024 * 1024 * 1024, // 1GB
105
+
}),
106
+
cold: new S3StorageTier({
107
+
bucket: S3_BUCKET,
108
+
region: S3_REGION,
109
+
endpoint: S3_ENDPOINT,
110
+
forcePathStyle: S3_FORCE_PATH_STYLE,
111
+
credentials:
112
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
113
+
? {
114
+
accessKeyId: AWS_ACCESS_KEY_ID,
115
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
116
+
}
117
+
: undefined,
118
+
prefix: 'example/sites/',
119
+
}),
120
+
},
121
+
compression: true,
122
+
defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
123
+
promotionStrategy: 'lazy', // Don't auto-promote large files
124
+
});
125
+
126
+
const siteId = 'did:plc:abc123';
127
+
const siteName = 'tiered-cache-demo';
128
+
129
+
console.log('Loading real static site from example-site/...\n');
130
+
131
+
// Load actual site files
132
+
const { readFile } = await import('node:fs/promises');
133
+
134
+
const files = [
135
+
{ name: 'index.html', skipTiers: [], mimeType: 'text/html' },
136
+
{ name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' },
137
+
{ name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' },
138
+
{ name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' },
139
+
{ name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' },
140
+
];
141
+
142
+
console.log('Storing site files with selective tier placement:\n');
143
+
144
+
for (const file of files) {
145
+
const content = await readFile(`./example-site/${file.name}`, 'utf-8');
146
+
const key = `${siteId}/${siteName}/${file.name}`;
147
+
148
+
await storage.set(key, content, {
149
+
skipTiers: file.skipTiers as ('hot' | 'warm')[],
150
+
metadata: { mimeType: file.mimeType },
151
+
});
152
+
153
+
const tierInfo =
154
+
file.skipTiers.length === 0
155
+
? 'hot + warm + cold (S3)'
156
+
: `warm + cold (S3) - skipped ${file.skipTiers.join(', ')}`;
157
+
const sizeKB = (content.length / 1024).toFixed(2);
158
+
console.log(`✓ ${file.name} (${sizeKB} KB) → ${tierInfo}`);
159
+
}
160
+
161
+
// Check where each file is served from
162
+
console.log('\nServing files (checking which tier):');
163
+
for (const file of files) {
164
+
const result = await storage.getWithMetadata(`${siteId}/${siteName}/${file.name}`);
165
+
if (result) {
166
+
const sizeKB = (result.metadata.size / 1024).toFixed(2);
167
+
console.log(` ${file.name}: served from ${result.source} (${sizeKB} KB)`);
168
+
}
169
+
}
170
+
171
+
// Show hot tier only has index.html
172
+
console.log('\nHot tier contents (should only contain index.html):');
173
+
const stats = await storage.getStats();
174
+
console.log(` Items: ${stats.hot?.items}`);
175
+
console.log(` Size: ${((stats.hot?.bytes ?? 0) / 1024).toFixed(2)} KB`);
176
+
console.log(` Files: index.html only`);
177
+
178
+
console.log('\nWarm tier contents (all site files):');
179
+
console.log(` Items: ${stats.warm?.items}`);
180
+
console.log(` Size: ${((stats.warm?.bytes ?? 0) / 1024).toFixed(2)} KB`);
181
+
console.log(` Files: all ${files.length} files`);
182
+
183
+
// Demonstrate accessing a page
184
+
console.log('\nSimulating page request for about.html:');
185
+
const aboutPage = await storage.getWithMetadata(`${siteId}/${siteName}/about.html`);
186
+
if (aboutPage) {
187
+
console.log(` Source: ${aboutPage.source} tier`);
188
+
console.log(` Access count: ${aboutPage.metadata.accessCount}`);
189
+
console.log(` Preview: ${aboutPage.data.toString().slice(0, 100)}...`);
190
+
}
191
+
192
+
// Invalidate entire site
193
+
console.log(`\nInvalidating entire site: ${siteId}/${siteName}/`);
194
+
const deleted = await storage.invalidate(`${siteId}/${siteName}/`);
195
+
console.log(`Deleted ${deleted} files from all tiers`);
196
+
}
197
+
198
+
async function bootstrapExample() {
199
+
console.log('\n=== Bootstrap Example ===\n');
200
+
201
+
const hot = new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 });
202
+
const warm = new DiskStorageTier({ directory: './example-cache/bootstrap/warm' });
203
+
const cold = new S3StorageTier({
204
+
bucket: S3_BUCKET,
205
+
region: S3_REGION,
206
+
endpoint: S3_ENDPOINT,
207
+
forcePathStyle: S3_FORCE_PATH_STYLE,
208
+
credentials:
209
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
210
+
? {
211
+
accessKeyId: AWS_ACCESS_KEY_ID,
212
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
213
+
}
214
+
: undefined,
215
+
prefix: 'example/bootstrap/',
216
+
});
217
+
218
+
const storage = new TieredStorage({
219
+
tiers: { hot, warm, cold },
220
+
});
221
+
222
+
// Populate with some data
223
+
console.log('Populating storage with test data...');
224
+
for (let i = 0; i < 10; i++) {
225
+
await storage.set(`item:${i}`, {
226
+
id: i,
227
+
name: `Item ${i}`,
228
+
description: `This is item number ${i}`,
229
+
});
230
+
}
231
+
232
+
// Access some items to build up access counts
233
+
console.log('Accessing some items to simulate usage patterns...');
234
+
await storage.get('item:0'); // Most accessed
235
+
await storage.get('item:0');
236
+
await storage.get('item:0');
237
+
await storage.get('item:1'); // Second most accessed
238
+
await storage.get('item:1');
239
+
await storage.get('item:2'); // Third most accessed
240
+
241
+
// Clear hot tier to simulate server restart
242
+
console.log('\nSimulating server restart (clearing hot tier)...');
243
+
await hot.clear();
244
+
245
+
let hotStats = await hot.getStats();
246
+
console.log(`Hot tier after clear: ${hotStats.items} items`);
247
+
248
+
// Bootstrap hot from warm (loads most accessed items)
249
+
console.log('\nBootstrapping hot tier from warm (loading top 3 items)...');
250
+
const loaded = await storage.bootstrapHot(3);
251
+
console.log(`Loaded ${loaded} items into hot tier`);
252
+
253
+
hotStats = await hot.getStats();
254
+
console.log(`Hot tier after bootstrap: ${hotStats.items} items`);
255
+
256
+
// Verify the right items were loaded
257
+
console.log('\nVerifying loaded items are served from hot:');
258
+
for (let i = 0; i < 3; i++) {
259
+
const result = await storage.getWithMetadata(`item:${i}`);
260
+
console.log(` item:${i}: ${result?.source}`);
261
+
}
262
+
263
+
// Cleanup this example's data
264
+
console.log('\nCleaning up bootstrap example data...');
265
+
await storage.invalidate('item:');
266
+
}
267
+
268
+
async function promotionStrategyExample() {
269
+
console.log('\n=== Promotion Strategy Example ===\n');
270
+
271
+
// Lazy promotion (default)
272
+
console.log('Testing LAZY promotion:');
273
+
const lazyStorage = new TieredStorage({
274
+
tiers: {
275
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }),
276
+
warm: new DiskStorageTier({ directory: './example-cache/promo-lazy/warm' }),
277
+
cold: new S3StorageTier({
278
+
bucket: S3_BUCKET,
279
+
region: S3_REGION,
280
+
endpoint: S3_ENDPOINT,
281
+
forcePathStyle: S3_FORCE_PATH_STYLE,
282
+
credentials:
283
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
284
+
? {
285
+
accessKeyId: AWS_ACCESS_KEY_ID,
286
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
287
+
}
288
+
: undefined,
289
+
prefix: 'example/promo-lazy/',
290
+
}),
291
+
},
292
+
promotionStrategy: 'lazy',
293
+
});
294
+
295
+
// Write data and clear hot
296
+
await lazyStorage.set('test:lazy', { value: 'lazy test' });
297
+
await lazyStorage.clearTier('hot');
298
+
299
+
// Read from cold (should NOT auto-promote to hot)
300
+
const lazyResult = await lazyStorage.getWithMetadata('test:lazy');
301
+
console.log(` First read served from: ${lazyResult?.source}`);
302
+
303
+
const lazyResult2 = await lazyStorage.getWithMetadata('test:lazy');
304
+
console.log(` Second read served from: ${lazyResult2?.source} (lazy = no auto-promotion)`);
305
+
306
+
// Eager promotion
307
+
console.log('\nTesting EAGER promotion:');
308
+
const eagerStorage = new TieredStorage({
309
+
tiers: {
310
+
hot: new MemoryStorageTier({ maxSizeBytes: 10 * 1024 * 1024 }),
311
+
warm: new DiskStorageTier({ directory: './example-cache/promo-eager/warm' }),
312
+
cold: new S3StorageTier({
313
+
bucket: S3_BUCKET,
314
+
region: S3_REGION,
315
+
endpoint: S3_ENDPOINT,
316
+
forcePathStyle: S3_FORCE_PATH_STYLE,
317
+
credentials:
318
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
319
+
? {
320
+
accessKeyId: AWS_ACCESS_KEY_ID,
321
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
322
+
}
323
+
: undefined,
324
+
prefix: 'example/promo-eager/',
325
+
}),
326
+
},
327
+
promotionStrategy: 'eager',
328
+
});
329
+
330
+
// Write data and clear hot
331
+
await eagerStorage.set('test:eager', { value: 'eager test' });
332
+
await eagerStorage.clearTier('hot');
333
+
334
+
// Read from cold (SHOULD auto-promote to hot)
335
+
const eagerResult = await eagerStorage.getWithMetadata('test:eager');
336
+
console.log(` First read served from: ${eagerResult?.source}`);
337
+
338
+
const eagerResult2 = await eagerStorage.getWithMetadata('test:eager');
339
+
console.log(` Second read served from: ${eagerResult2?.source} (eager = promoted to hot)`);
340
+
341
+
// Cleanup
342
+
await lazyStorage.invalidate('test:');
343
+
await eagerStorage.invalidate('test:');
344
+
}
345
+
346
+
async function cleanup() {
347
+
console.log('\n=== Cleanup ===\n');
348
+
console.log('Removing example cache directories...');
349
+
await rm('./example-cache', { recursive: true, force: true });
350
+
console.log('✓ Local cache directories removed');
351
+
console.log('\nNote: S3 objects with prefix "example/" remain in bucket');
352
+
console.log(' (remove manually if needed)');
353
+
}
354
+
355
+
async function main() {
356
+
console.log('╔════════════════════════════════════════════════╗');
357
+
console.log('║ Tiered Storage Library - Usage Examples ║');
358
+
console.log('║ Cold Tier: S3 (or S3-compatible storage) ║');
359
+
console.log('╚════════════════════════════════════════════════╝');
360
+
361
+
// Check for S3 configuration
362
+
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
363
+
console.log('\n⚠️ Warning: AWS credentials not configured');
364
+
console.log(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env');
365
+
console.log(' (See .env.example for configuration options)\n');
366
+
}
367
+
368
+
console.log('\nConfiguration:');
369
+
console.log(` S3 Bucket: ${S3_BUCKET}`);
370
+
console.log(` S3 Region: ${S3_REGION}`);
371
+
console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
372
+
console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
373
+
console.log(` Credentials: ${AWS_ACCESS_KEY_ID ? '✓ Configured' : '✗ Not configured (using IAM role)'}`);
374
+
375
+
try {
376
+
// Test S3 connection first
377
+
console.log('\nTesting S3 connection...');
378
+
const testStorage = new S3StorageTier({
379
+
bucket: S3_BUCKET,
380
+
region: S3_REGION,
381
+
endpoint: S3_ENDPOINT,
382
+
forcePathStyle: S3_FORCE_PATH_STYLE,
383
+
credentials:
384
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
385
+
? {
386
+
accessKeyId: AWS_ACCESS_KEY_ID,
387
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
388
+
}
389
+
: undefined,
390
+
prefix: 'test/',
391
+
});
392
+
393
+
try {
394
+
await testStorage.set('connection-test', new TextEncoder().encode('test'), {
395
+
key: 'connection-test',
396
+
size: 4,
397
+
createdAt: new Date(),
398
+
lastAccessed: new Date(),
399
+
accessCount: 0,
400
+
compressed: false,
401
+
checksum: 'test',
402
+
});
403
+
console.log('✓ S3 connection successful!\n');
404
+
await testStorage.delete('connection-test');
405
+
} catch (error: any) {
406
+
console.error('✗ S3 connection failed:', error.message);
407
+
console.error('\nPossible issues:');
408
+
console.error(' 1. Check that the bucket exists on your S3 service');
409
+
console.error(' 2. Verify credentials have read/write permissions');
410
+
console.error(' 3. Confirm the endpoint URL is correct');
411
+
console.error(' 4. Try setting S3_REGION to a different value (e.g., "us-east-1" or "auto")');
412
+
console.error('\nSkipping examples due to S3 connection error.\n');
413
+
return;
414
+
}
415
+
416
+
await basicExample();
417
+
await staticSiteHostingExample();
418
+
await bootstrapExample();
419
+
await promotionStrategyExample();
420
+
} catch (error: any) {
421
+
console.error('\n❌ Error:', error.message);
422
+
if (error.name === 'NoSuchBucket') {
423
+
console.error(`\n The S3 bucket "${S3_BUCKET}" does not exist.`);
424
+
console.error(' Create it first or set S3_BUCKET in .env to an existing bucket.\n');
425
+
}
426
+
} finally {
427
+
await cleanup();
428
+
}
429
+
430
+
console.log('\n✅ All examples completed successfully!');
431
+
console.log('\nTry modifying this file to experiment with different patterns.');
432
+
}
433
+
434
+
main().catch(console.error);
+4668
package-lock.json
+4668
package-lock.json
···
1
+
{
2
+
"name": "tiered-storage",
3
+
"version": "1.0.0",
4
+
"lockfileVersion": 3,
5
+
"requires": true,
6
+
"packages": {
7
+
"": {
8
+
"name": "tiered-storage",
9
+
"version": "1.0.0",
10
+
"dependencies": {
11
+
"@aws-sdk/client-s3": "^3.500.0",
12
+
"hono": "^4.10.7",
13
+
"mime-types": "^3.0.2",
14
+
"tiny-lru": "^11.0.0"
15
+
},
16
+
"devDependencies": {
17
+
"@types/bun": "^1.3.4",
18
+
"@types/mime-types": "^3.0.1",
19
+
"@types/node": "^24.10.1",
20
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
21
+
"@typescript-eslint/parser": "^8.48.1",
22
+
"eslint": "^9.39.1",
23
+
"tsx": "^4.0.0",
24
+
"typescript": "^5.3.0",
25
+
"vitest": "^4.0.15"
26
+
},
27
+
"engines": {
28
+
"node": ">=18.0.0"
29
+
}
30
+
},
31
+
"node_modules/@aws-crypto/crc32": {
32
+
"version": "5.2.0",
33
+
"license": "Apache-2.0",
34
+
"dependencies": {
35
+
"@aws-crypto/util": "^5.2.0",
36
+
"@aws-sdk/types": "^3.222.0",
37
+
"tslib": "^2.6.2"
38
+
},
39
+
"engines": {
40
+
"node": ">=16.0.0"
41
+
}
42
+
},
43
+
"node_modules/@aws-crypto/crc32c": {
44
+
"version": "5.2.0",
45
+
"license": "Apache-2.0",
46
+
"dependencies": {
47
+
"@aws-crypto/util": "^5.2.0",
48
+
"@aws-sdk/types": "^3.222.0",
49
+
"tslib": "^2.6.2"
50
+
}
51
+
},
52
+
"node_modules/@aws-crypto/sha1-browser": {
53
+
"version": "5.2.0",
54
+
"license": "Apache-2.0",
55
+
"dependencies": {
56
+
"@aws-crypto/supports-web-crypto": "^5.2.0",
57
+
"@aws-crypto/util": "^5.2.0",
58
+
"@aws-sdk/types": "^3.222.0",
59
+
"@aws-sdk/util-locate-window": "^3.0.0",
60
+
"@smithy/util-utf8": "^2.0.0",
61
+
"tslib": "^2.6.2"
62
+
}
63
+
},
64
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": {
65
+
"version": "2.3.0",
66
+
"license": "Apache-2.0",
67
+
"dependencies": {
68
+
"@smithy/util-buffer-from": "^2.2.0",
69
+
"tslib": "^2.6.2"
70
+
},
71
+
"engines": {
72
+
"node": ">=14.0.0"
73
+
}
74
+
},
75
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
76
+
"version": "2.2.0",
77
+
"license": "Apache-2.0",
78
+
"dependencies": {
79
+
"@smithy/is-array-buffer": "^2.2.0",
80
+
"tslib": "^2.6.2"
81
+
},
82
+
"engines": {
83
+
"node": ">=14.0.0"
84
+
}
85
+
},
86
+
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
87
+
"version": "2.2.0",
88
+
"license": "Apache-2.0",
89
+
"dependencies": {
90
+
"tslib": "^2.6.2"
91
+
},
92
+
"engines": {
93
+
"node": ">=14.0.0"
94
+
}
95
+
},
96
+
"node_modules/@aws-crypto/sha256-browser": {
97
+
"version": "5.2.0",
98
+
"license": "Apache-2.0",
99
+
"dependencies": {
100
+
"@aws-crypto/sha256-js": "^5.2.0",
101
+
"@aws-crypto/supports-web-crypto": "^5.2.0",
102
+
"@aws-crypto/util": "^5.2.0",
103
+
"@aws-sdk/types": "^3.222.0",
104
+
"@aws-sdk/util-locate-window": "^3.0.0",
105
+
"@smithy/util-utf8": "^2.0.0",
106
+
"tslib": "^2.6.2"
107
+
}
108
+
},
109
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
110
+
"version": "2.3.0",
111
+
"license": "Apache-2.0",
112
+
"dependencies": {
113
+
"@smithy/util-buffer-from": "^2.2.0",
114
+
"tslib": "^2.6.2"
115
+
},
116
+
"engines": {
117
+
"node": ">=14.0.0"
118
+
}
119
+
},
120
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
121
+
"version": "2.2.0",
122
+
"license": "Apache-2.0",
123
+
"dependencies": {
124
+
"@smithy/is-array-buffer": "^2.2.0",
125
+
"tslib": "^2.6.2"
126
+
},
127
+
"engines": {
128
+
"node": ">=14.0.0"
129
+
}
130
+
},
131
+
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
132
+
"version": "2.2.0",
133
+
"license": "Apache-2.0",
134
+
"dependencies": {
135
+
"tslib": "^2.6.2"
136
+
},
137
+
"engines": {
138
+
"node": ">=14.0.0"
139
+
}
140
+
},
141
+
"node_modules/@aws-crypto/sha256-js": {
142
+
"version": "5.2.0",
143
+
"license": "Apache-2.0",
144
+
"dependencies": {
145
+
"@aws-crypto/util": "^5.2.0",
146
+
"@aws-sdk/types": "^3.222.0",
147
+
"tslib": "^2.6.2"
148
+
},
149
+
"engines": {
150
+
"node": ">=16.0.0"
151
+
}
152
+
},
153
+
"node_modules/@aws-crypto/supports-web-crypto": {
154
+
"version": "5.2.0",
155
+
"license": "Apache-2.0",
156
+
"dependencies": {
157
+
"tslib": "^2.6.2"
158
+
}
159
+
},
160
+
"node_modules/@aws-crypto/util": {
161
+
"version": "5.2.0",
162
+
"license": "Apache-2.0",
163
+
"dependencies": {
164
+
"@aws-sdk/types": "^3.222.0",
165
+
"@smithy/util-utf8": "^2.0.0",
166
+
"tslib": "^2.6.2"
167
+
}
168
+
},
169
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
170
+
"version": "2.3.0",
171
+
"license": "Apache-2.0",
172
+
"dependencies": {
173
+
"@smithy/util-buffer-from": "^2.2.0",
174
+
"tslib": "^2.6.2"
175
+
},
176
+
"engines": {
177
+
"node": ">=14.0.0"
178
+
}
179
+
},
180
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": {
181
+
"version": "2.2.0",
182
+
"license": "Apache-2.0",
183
+
"dependencies": {
184
+
"@smithy/is-array-buffer": "^2.2.0",
185
+
"tslib": "^2.6.2"
186
+
},
187
+
"engines": {
188
+
"node": ">=14.0.0"
189
+
}
190
+
},
191
+
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": {
192
+
"version": "2.2.0",
193
+
"license": "Apache-2.0",
194
+
"dependencies": {
195
+
"tslib": "^2.6.2"
196
+
},
197
+
"engines": {
198
+
"node": ">=14.0.0"
199
+
}
200
+
},
201
+
"node_modules/@aws-sdk/client-s3": {
202
+
"version": "3.946.0",
203
+
"license": "Apache-2.0",
204
+
"dependencies": {
205
+
"@aws-crypto/sha1-browser": "5.2.0",
206
+
"@aws-crypto/sha256-browser": "5.2.0",
207
+
"@aws-crypto/sha256-js": "5.2.0",
208
+
"@aws-sdk/core": "3.946.0",
209
+
"@aws-sdk/credential-provider-node": "3.946.0",
210
+
"@aws-sdk/middleware-bucket-endpoint": "3.936.0",
211
+
"@aws-sdk/middleware-expect-continue": "3.936.0",
212
+
"@aws-sdk/middleware-flexible-checksums": "3.946.0",
213
+
"@aws-sdk/middleware-host-header": "3.936.0",
214
+
"@aws-sdk/middleware-location-constraint": "3.936.0",
215
+
"@aws-sdk/middleware-logger": "3.936.0",
216
+
"@aws-sdk/middleware-recursion-detection": "3.936.0",
217
+
"@aws-sdk/middleware-sdk-s3": "3.946.0",
218
+
"@aws-sdk/middleware-ssec": "3.936.0",
219
+
"@aws-sdk/middleware-user-agent": "3.946.0",
220
+
"@aws-sdk/region-config-resolver": "3.936.0",
221
+
"@aws-sdk/signature-v4-multi-region": "3.946.0",
222
+
"@aws-sdk/types": "3.936.0",
223
+
"@aws-sdk/util-endpoints": "3.936.0",
224
+
"@aws-sdk/util-user-agent-browser": "3.936.0",
225
+
"@aws-sdk/util-user-agent-node": "3.946.0",
226
+
"@smithy/config-resolver": "^4.4.3",
227
+
"@smithy/core": "^3.18.7",
228
+
"@smithy/eventstream-serde-browser": "^4.2.5",
229
+
"@smithy/eventstream-serde-config-resolver": "^4.3.5",
230
+
"@smithy/eventstream-serde-node": "^4.2.5",
231
+
"@smithy/fetch-http-handler": "^5.3.6",
232
+
"@smithy/hash-blob-browser": "^4.2.6",
233
+
"@smithy/hash-node": "^4.2.5",
234
+
"@smithy/hash-stream-node": "^4.2.5",
235
+
"@smithy/invalid-dependency": "^4.2.5",
236
+
"@smithy/md5-js": "^4.2.5",
237
+
"@smithy/middleware-content-length": "^4.2.5",
238
+
"@smithy/middleware-endpoint": "^4.3.14",
239
+
"@smithy/middleware-retry": "^4.4.14",
240
+
"@smithy/middleware-serde": "^4.2.6",
241
+
"@smithy/middleware-stack": "^4.2.5",
242
+
"@smithy/node-config-provider": "^4.3.5",
243
+
"@smithy/node-http-handler": "^4.4.5",
244
+
"@smithy/protocol-http": "^5.3.5",
245
+
"@smithy/smithy-client": "^4.9.10",
246
+
"@smithy/types": "^4.9.0",
247
+
"@smithy/url-parser": "^4.2.5",
248
+
"@smithy/util-base64": "^4.3.0",
249
+
"@smithy/util-body-length-browser": "^4.2.0",
250
+
"@smithy/util-body-length-node": "^4.2.1",
251
+
"@smithy/util-defaults-mode-browser": "^4.3.13",
252
+
"@smithy/util-defaults-mode-node": "^4.2.16",
253
+
"@smithy/util-endpoints": "^3.2.5",
254
+
"@smithy/util-middleware": "^4.2.5",
255
+
"@smithy/util-retry": "^4.2.5",
256
+
"@smithy/util-stream": "^4.5.6",
257
+
"@smithy/util-utf8": "^4.2.0",
258
+
"@smithy/util-waiter": "^4.2.5",
259
+
"tslib": "^2.6.2"
260
+
},
261
+
"engines": {
262
+
"node": ">=18.0.0"
263
+
}
264
+
},
265
+
"node_modules/@aws-sdk/client-sso": {
266
+
"version": "3.946.0",
267
+
"license": "Apache-2.0",
268
+
"dependencies": {
269
+
"@aws-crypto/sha256-browser": "5.2.0",
270
+
"@aws-crypto/sha256-js": "5.2.0",
271
+
"@aws-sdk/core": "3.946.0",
272
+
"@aws-sdk/middleware-host-header": "3.936.0",
273
+
"@aws-sdk/middleware-logger": "3.936.0",
274
+
"@aws-sdk/middleware-recursion-detection": "3.936.0",
275
+
"@aws-sdk/middleware-user-agent": "3.946.0",
276
+
"@aws-sdk/region-config-resolver": "3.936.0",
277
+
"@aws-sdk/types": "3.936.0",
278
+
"@aws-sdk/util-endpoints": "3.936.0",
279
+
"@aws-sdk/util-user-agent-browser": "3.936.0",
280
+
"@aws-sdk/util-user-agent-node": "3.946.0",
281
+
"@smithy/config-resolver": "^4.4.3",
282
+
"@smithy/core": "^3.18.7",
283
+
"@smithy/fetch-http-handler": "^5.3.6",
284
+
"@smithy/hash-node": "^4.2.5",
285
+
"@smithy/invalid-dependency": "^4.2.5",
286
+
"@smithy/middleware-content-length": "^4.2.5",
287
+
"@smithy/middleware-endpoint": "^4.3.14",
288
+
"@smithy/middleware-retry": "^4.4.14",
289
+
"@smithy/middleware-serde": "^4.2.6",
290
+
"@smithy/middleware-stack": "^4.2.5",
291
+
"@smithy/node-config-provider": "^4.3.5",
292
+
"@smithy/node-http-handler": "^4.4.5",
293
+
"@smithy/protocol-http": "^5.3.5",
294
+
"@smithy/smithy-client": "^4.9.10",
295
+
"@smithy/types": "^4.9.0",
296
+
"@smithy/url-parser": "^4.2.5",
297
+
"@smithy/util-base64": "^4.3.0",
298
+
"@smithy/util-body-length-browser": "^4.2.0",
299
+
"@smithy/util-body-length-node": "^4.2.1",
300
+
"@smithy/util-defaults-mode-browser": "^4.3.13",
301
+
"@smithy/util-defaults-mode-node": "^4.2.16",
302
+
"@smithy/util-endpoints": "^3.2.5",
303
+
"@smithy/util-middleware": "^4.2.5",
304
+
"@smithy/util-retry": "^4.2.5",
305
+
"@smithy/util-utf8": "^4.2.0",
306
+
"tslib": "^2.6.2"
307
+
},
308
+
"engines": {
309
+
"node": ">=18.0.0"
310
+
}
311
+
},
312
+
"node_modules/@aws-sdk/core": {
313
+
"version": "3.946.0",
314
+
"license": "Apache-2.0",
315
+
"dependencies": {
316
+
"@aws-sdk/types": "3.936.0",
317
+
"@aws-sdk/xml-builder": "3.930.0",
318
+
"@smithy/core": "^3.18.7",
319
+
"@smithy/node-config-provider": "^4.3.5",
320
+
"@smithy/property-provider": "^4.2.5",
321
+
"@smithy/protocol-http": "^5.3.5",
322
+
"@smithy/signature-v4": "^5.3.5",
323
+
"@smithy/smithy-client": "^4.9.10",
324
+
"@smithy/types": "^4.9.0",
325
+
"@smithy/util-base64": "^4.3.0",
326
+
"@smithy/util-middleware": "^4.2.5",
327
+
"@smithy/util-utf8": "^4.2.0",
328
+
"tslib": "^2.6.2"
329
+
},
330
+
"engines": {
331
+
"node": ">=18.0.0"
332
+
}
333
+
},
334
+
"node_modules/@aws-sdk/credential-provider-env": {
335
+
"version": "3.946.0",
336
+
"license": "Apache-2.0",
337
+
"dependencies": {
338
+
"@aws-sdk/core": "3.946.0",
339
+
"@aws-sdk/types": "3.936.0",
340
+
"@smithy/property-provider": "^4.2.5",
341
+
"@smithy/types": "^4.9.0",
342
+
"tslib": "^2.6.2"
343
+
},
344
+
"engines": {
345
+
"node": ">=18.0.0"
346
+
}
347
+
},
348
+
"node_modules/@aws-sdk/credential-provider-http": {
349
+
"version": "3.946.0",
350
+
"license": "Apache-2.0",
351
+
"dependencies": {
352
+
"@aws-sdk/core": "3.946.0",
353
+
"@aws-sdk/types": "3.936.0",
354
+
"@smithy/fetch-http-handler": "^5.3.6",
355
+
"@smithy/node-http-handler": "^4.4.5",
356
+
"@smithy/property-provider": "^4.2.5",
357
+
"@smithy/protocol-http": "^5.3.5",
358
+
"@smithy/smithy-client": "^4.9.10",
359
+
"@smithy/types": "^4.9.0",
360
+
"@smithy/util-stream": "^4.5.6",
361
+
"tslib": "^2.6.2"
362
+
},
363
+
"engines": {
364
+
"node": ">=18.0.0"
365
+
}
366
+
},
367
+
"node_modules/@aws-sdk/credential-provider-ini": {
368
+
"version": "3.946.0",
369
+
"license": "Apache-2.0",
370
+
"dependencies": {
371
+
"@aws-sdk/core": "3.946.0",
372
+
"@aws-sdk/credential-provider-env": "3.946.0",
373
+
"@aws-sdk/credential-provider-http": "3.946.0",
374
+
"@aws-sdk/credential-provider-login": "3.946.0",
375
+
"@aws-sdk/credential-provider-process": "3.946.0",
376
+
"@aws-sdk/credential-provider-sso": "3.946.0",
377
+
"@aws-sdk/credential-provider-web-identity": "3.946.0",
378
+
"@aws-sdk/nested-clients": "3.946.0",
379
+
"@aws-sdk/types": "3.936.0",
380
+
"@smithy/credential-provider-imds": "^4.2.5",
381
+
"@smithy/property-provider": "^4.2.5",
382
+
"@smithy/shared-ini-file-loader": "^4.4.0",
383
+
"@smithy/types": "^4.9.0",
384
+
"tslib": "^2.6.2"
385
+
},
386
+
"engines": {
387
+
"node": ">=18.0.0"
388
+
}
389
+
},
390
+
"node_modules/@aws-sdk/credential-provider-login": {
391
+
"version": "3.946.0",
392
+
"license": "Apache-2.0",
393
+
"dependencies": {
394
+
"@aws-sdk/core": "3.946.0",
395
+
"@aws-sdk/nested-clients": "3.946.0",
396
+
"@aws-sdk/types": "3.936.0",
397
+
"@smithy/property-provider": "^4.2.5",
398
+
"@smithy/protocol-http": "^5.3.5",
399
+
"@smithy/shared-ini-file-loader": "^4.4.0",
400
+
"@smithy/types": "^4.9.0",
401
+
"tslib": "^2.6.2"
402
+
},
403
+
"engines": {
404
+
"node": ">=18.0.0"
405
+
}
406
+
},
407
+
"node_modules/@aws-sdk/credential-provider-node": {
408
+
"version": "3.946.0",
409
+
"license": "Apache-2.0",
410
+
"dependencies": {
411
+
"@aws-sdk/credential-provider-env": "3.946.0",
412
+
"@aws-sdk/credential-provider-http": "3.946.0",
413
+
"@aws-sdk/credential-provider-ini": "3.946.0",
414
+
"@aws-sdk/credential-provider-process": "3.946.0",
415
+
"@aws-sdk/credential-provider-sso": "3.946.0",
416
+
"@aws-sdk/credential-provider-web-identity": "3.946.0",
417
+
"@aws-sdk/types": "3.936.0",
418
+
"@smithy/credential-provider-imds": "^4.2.5",
419
+
"@smithy/property-provider": "^4.2.5",
420
+
"@smithy/shared-ini-file-loader": "^4.4.0",
421
+
"@smithy/types": "^4.9.0",
422
+
"tslib": "^2.6.2"
423
+
},
424
+
"engines": {
425
+
"node": ">=18.0.0"
426
+
}
427
+
},
428
+
"node_modules/@aws-sdk/credential-provider-process": {
429
+
"version": "3.946.0",
430
+
"license": "Apache-2.0",
431
+
"dependencies": {
432
+
"@aws-sdk/core": "3.946.0",
433
+
"@aws-sdk/types": "3.936.0",
434
+
"@smithy/property-provider": "^4.2.5",
435
+
"@smithy/shared-ini-file-loader": "^4.4.0",
436
+
"@smithy/types": "^4.9.0",
437
+
"tslib": "^2.6.2"
438
+
},
439
+
"engines": {
440
+
"node": ">=18.0.0"
441
+
}
442
+
},
443
+
"node_modules/@aws-sdk/credential-provider-sso": {
444
+
"version": "3.946.0",
445
+
"license": "Apache-2.0",
446
+
"dependencies": {
447
+
"@aws-sdk/client-sso": "3.946.0",
448
+
"@aws-sdk/core": "3.946.0",
449
+
"@aws-sdk/token-providers": "3.946.0",
450
+
"@aws-sdk/types": "3.936.0",
451
+
"@smithy/property-provider": "^4.2.5",
452
+
"@smithy/shared-ini-file-loader": "^4.4.0",
453
+
"@smithy/types": "^4.9.0",
454
+
"tslib": "^2.6.2"
455
+
},
456
+
"engines": {
457
+
"node": ">=18.0.0"
458
+
}
459
+
},
460
+
"node_modules/@aws-sdk/credential-provider-web-identity": {
461
+
"version": "3.946.0",
462
+
"license": "Apache-2.0",
463
+
"dependencies": {
464
+
"@aws-sdk/core": "3.946.0",
465
+
"@aws-sdk/nested-clients": "3.946.0",
466
+
"@aws-sdk/types": "3.936.0",
467
+
"@smithy/property-provider": "^4.2.5",
468
+
"@smithy/shared-ini-file-loader": "^4.4.0",
469
+
"@smithy/types": "^4.9.0",
470
+
"tslib": "^2.6.2"
471
+
},
472
+
"engines": {
473
+
"node": ">=18.0.0"
474
+
}
475
+
},
476
+
"node_modules/@aws-sdk/middleware-bucket-endpoint": {
477
+
"version": "3.936.0",
478
+
"license": "Apache-2.0",
479
+
"dependencies": {
480
+
"@aws-sdk/types": "3.936.0",
481
+
"@aws-sdk/util-arn-parser": "3.893.0",
482
+
"@smithy/node-config-provider": "^4.3.5",
483
+
"@smithy/protocol-http": "^5.3.5",
484
+
"@smithy/types": "^4.9.0",
485
+
"@smithy/util-config-provider": "^4.2.0",
486
+
"tslib": "^2.6.2"
487
+
},
488
+
"engines": {
489
+
"node": ">=18.0.0"
490
+
}
491
+
},
492
+
"node_modules/@aws-sdk/middleware-expect-continue": {
493
+
"version": "3.936.0",
494
+
"license": "Apache-2.0",
495
+
"dependencies": {
496
+
"@aws-sdk/types": "3.936.0",
497
+
"@smithy/protocol-http": "^5.3.5",
498
+
"@smithy/types": "^4.9.0",
499
+
"tslib": "^2.6.2"
500
+
},
501
+
"engines": {
502
+
"node": ">=18.0.0"
503
+
}
504
+
},
505
+
"node_modules/@aws-sdk/middleware-flexible-checksums": {
506
+
"version": "3.946.0",
507
+
"license": "Apache-2.0",
508
+
"dependencies": {
509
+
"@aws-crypto/crc32": "5.2.0",
510
+
"@aws-crypto/crc32c": "5.2.0",
511
+
"@aws-crypto/util": "5.2.0",
512
+
"@aws-sdk/core": "3.946.0",
513
+
"@aws-sdk/types": "3.936.0",
514
+
"@smithy/is-array-buffer": "^4.2.0",
515
+
"@smithy/node-config-provider": "^4.3.5",
516
+
"@smithy/protocol-http": "^5.3.5",
517
+
"@smithy/types": "^4.9.0",
518
+
"@smithy/util-middleware": "^4.2.5",
519
+
"@smithy/util-stream": "^4.5.6",
520
+
"@smithy/util-utf8": "^4.2.0",
521
+
"tslib": "^2.6.2"
522
+
},
523
+
"engines": {
524
+
"node": ">=18.0.0"
525
+
}
526
+
},
527
+
"node_modules/@aws-sdk/middleware-host-header": {
528
+
"version": "3.936.0",
529
+
"license": "Apache-2.0",
530
+
"dependencies": {
531
+
"@aws-sdk/types": "3.936.0",
532
+
"@smithy/protocol-http": "^5.3.5",
533
+
"@smithy/types": "^4.9.0",
534
+
"tslib": "^2.6.2"
535
+
},
536
+
"engines": {
537
+
"node": ">=18.0.0"
538
+
}
539
+
},
540
+
"node_modules/@aws-sdk/middleware-location-constraint": {
541
+
"version": "3.936.0",
542
+
"license": "Apache-2.0",
543
+
"dependencies": {
544
+
"@aws-sdk/types": "3.936.0",
545
+
"@smithy/types": "^4.9.0",
546
+
"tslib": "^2.6.2"
547
+
},
548
+
"engines": {
549
+
"node": ">=18.0.0"
550
+
}
551
+
},
552
+
"node_modules/@aws-sdk/middleware-logger": {
553
+
"version": "3.936.0",
554
+
"license": "Apache-2.0",
555
+
"dependencies": {
556
+
"@aws-sdk/types": "3.936.0",
557
+
"@smithy/types": "^4.9.0",
558
+
"tslib": "^2.6.2"
559
+
},
560
+
"engines": {
561
+
"node": ">=18.0.0"
562
+
}
563
+
},
564
+
"node_modules/@aws-sdk/middleware-recursion-detection": {
565
+
"version": "3.936.0",
566
+
"license": "Apache-2.0",
567
+
"dependencies": {
568
+
"@aws-sdk/types": "3.936.0",
569
+
"@aws/lambda-invoke-store": "^0.2.0",
570
+
"@smithy/protocol-http": "^5.3.5",
571
+
"@smithy/types": "^4.9.0",
572
+
"tslib": "^2.6.2"
573
+
},
574
+
"engines": {
575
+
"node": ">=18.0.0"
576
+
}
577
+
},
578
+
"node_modules/@aws-sdk/middleware-sdk-s3": {
579
+
"version": "3.946.0",
580
+
"license": "Apache-2.0",
581
+
"dependencies": {
582
+
"@aws-sdk/core": "3.946.0",
583
+
"@aws-sdk/types": "3.936.0",
584
+
"@aws-sdk/util-arn-parser": "3.893.0",
585
+
"@smithy/core": "^3.18.7",
586
+
"@smithy/node-config-provider": "^4.3.5",
587
+
"@smithy/protocol-http": "^5.3.5",
588
+
"@smithy/signature-v4": "^5.3.5",
589
+
"@smithy/smithy-client": "^4.9.10",
590
+
"@smithy/types": "^4.9.0",
591
+
"@smithy/util-config-provider": "^4.2.0",
592
+
"@smithy/util-middleware": "^4.2.5",
593
+
"@smithy/util-stream": "^4.5.6",
594
+
"@smithy/util-utf8": "^4.2.0",
595
+
"tslib": "^2.6.2"
596
+
},
597
+
"engines": {
598
+
"node": ">=18.0.0"
599
+
}
600
+
},
601
+
"node_modules/@aws-sdk/middleware-ssec": {
602
+
"version": "3.936.0",
603
+
"license": "Apache-2.0",
604
+
"dependencies": {
605
+
"@aws-sdk/types": "3.936.0",
606
+
"@smithy/types": "^4.9.0",
607
+
"tslib": "^2.6.2"
608
+
},
609
+
"engines": {
610
+
"node": ">=18.0.0"
611
+
}
612
+
},
613
+
"node_modules/@aws-sdk/middleware-user-agent": {
614
+
"version": "3.946.0",
615
+
"license": "Apache-2.0",
616
+
"dependencies": {
617
+
"@aws-sdk/core": "3.946.0",
618
+
"@aws-sdk/types": "3.936.0",
619
+
"@aws-sdk/util-endpoints": "3.936.0",
620
+
"@smithy/core": "^3.18.7",
621
+
"@smithy/protocol-http": "^5.3.5",
622
+
"@smithy/types": "^4.9.0",
623
+
"tslib": "^2.6.2"
624
+
},
625
+
"engines": {
626
+
"node": ">=18.0.0"
627
+
}
628
+
},
629
+
"node_modules/@aws-sdk/nested-clients": {
630
+
"version": "3.946.0",
631
+
"license": "Apache-2.0",
632
+
"dependencies": {
633
+
"@aws-crypto/sha256-browser": "5.2.0",
634
+
"@aws-crypto/sha256-js": "5.2.0",
635
+
"@aws-sdk/core": "3.946.0",
636
+
"@aws-sdk/middleware-host-header": "3.936.0",
637
+
"@aws-sdk/middleware-logger": "3.936.0",
638
+
"@aws-sdk/middleware-recursion-detection": "3.936.0",
639
+
"@aws-sdk/middleware-user-agent": "3.946.0",
640
+
"@aws-sdk/region-config-resolver": "3.936.0",
641
+
"@aws-sdk/types": "3.936.0",
642
+
"@aws-sdk/util-endpoints": "3.936.0",
643
+
"@aws-sdk/util-user-agent-browser": "3.936.0",
644
+
"@aws-sdk/util-user-agent-node": "3.946.0",
645
+
"@smithy/config-resolver": "^4.4.3",
646
+
"@smithy/core": "^3.18.7",
647
+
"@smithy/fetch-http-handler": "^5.3.6",
648
+
"@smithy/hash-node": "^4.2.5",
649
+
"@smithy/invalid-dependency": "^4.2.5",
650
+
"@smithy/middleware-content-length": "^4.2.5",
651
+
"@smithy/middleware-endpoint": "^4.3.14",
652
+
"@smithy/middleware-retry": "^4.4.14",
653
+
"@smithy/middleware-serde": "^4.2.6",
654
+
"@smithy/middleware-stack": "^4.2.5",
655
+
"@smithy/node-config-provider": "^4.3.5",
656
+
"@smithy/node-http-handler": "^4.4.5",
657
+
"@smithy/protocol-http": "^5.3.5",
658
+
"@smithy/smithy-client": "^4.9.10",
659
+
"@smithy/types": "^4.9.0",
660
+
"@smithy/url-parser": "^4.2.5",
661
+
"@smithy/util-base64": "^4.3.0",
662
+
"@smithy/util-body-length-browser": "^4.2.0",
663
+
"@smithy/util-body-length-node": "^4.2.1",
664
+
"@smithy/util-defaults-mode-browser": "^4.3.13",
665
+
"@smithy/util-defaults-mode-node": "^4.2.16",
666
+
"@smithy/util-endpoints": "^3.2.5",
667
+
"@smithy/util-middleware": "^4.2.5",
668
+
"@smithy/util-retry": "^4.2.5",
669
+
"@smithy/util-utf8": "^4.2.0",
670
+
"tslib": "^2.6.2"
671
+
},
672
+
"engines": {
673
+
"node": ">=18.0.0"
674
+
}
675
+
},
676
+
"node_modules/@aws-sdk/region-config-resolver": {
677
+
"version": "3.936.0",
678
+
"license": "Apache-2.0",
679
+
"dependencies": {
680
+
"@aws-sdk/types": "3.936.0",
681
+
"@smithy/config-resolver": "^4.4.3",
682
+
"@smithy/node-config-provider": "^4.3.5",
683
+
"@smithy/types": "^4.9.0",
684
+
"tslib": "^2.6.2"
685
+
},
686
+
"engines": {
687
+
"node": ">=18.0.0"
688
+
}
689
+
},
690
+
"node_modules/@aws-sdk/signature-v4-multi-region": {
691
+
"version": "3.946.0",
692
+
"license": "Apache-2.0",
693
+
"dependencies": {
694
+
"@aws-sdk/middleware-sdk-s3": "3.946.0",
695
+
"@aws-sdk/types": "3.936.0",
696
+
"@smithy/protocol-http": "^5.3.5",
697
+
"@smithy/signature-v4": "^5.3.5",
698
+
"@smithy/types": "^4.9.0",
699
+
"tslib": "^2.6.2"
700
+
},
701
+
"engines": {
702
+
"node": ">=18.0.0"
703
+
}
704
+
},
705
+
"node_modules/@aws-sdk/token-providers": {
706
+
"version": "3.946.0",
707
+
"license": "Apache-2.0",
708
+
"dependencies": {
709
+
"@aws-sdk/core": "3.946.0",
710
+
"@aws-sdk/nested-clients": "3.946.0",
711
+
"@aws-sdk/types": "3.936.0",
712
+
"@smithy/property-provider": "^4.2.5",
713
+
"@smithy/shared-ini-file-loader": "^4.4.0",
714
+
"@smithy/types": "^4.9.0",
715
+
"tslib": "^2.6.2"
716
+
},
717
+
"engines": {
718
+
"node": ">=18.0.0"
719
+
}
720
+
},
721
+
"node_modules/@aws-sdk/types": {
722
+
"version": "3.936.0",
723
+
"license": "Apache-2.0",
724
+
"dependencies": {
725
+
"@smithy/types": "^4.9.0",
726
+
"tslib": "^2.6.2"
727
+
},
728
+
"engines": {
729
+
"node": ">=18.0.0"
730
+
}
731
+
},
732
+
"node_modules/@aws-sdk/util-arn-parser": {
733
+
"version": "3.893.0",
734
+
"license": "Apache-2.0",
735
+
"dependencies": {
736
+
"tslib": "^2.6.2"
737
+
},
738
+
"engines": {
739
+
"node": ">=18.0.0"
740
+
}
741
+
},
742
+
"node_modules/@aws-sdk/util-endpoints": {
743
+
"version": "3.936.0",
744
+
"license": "Apache-2.0",
745
+
"dependencies": {
746
+
"@aws-sdk/types": "3.936.0",
747
+
"@smithy/types": "^4.9.0",
748
+
"@smithy/url-parser": "^4.2.5",
749
+
"@smithy/util-endpoints": "^3.2.5",
750
+
"tslib": "^2.6.2"
751
+
},
752
+
"engines": {
753
+
"node": ">=18.0.0"
754
+
}
755
+
},
756
+
"node_modules/@aws-sdk/util-locate-window": {
757
+
"version": "3.893.0",
758
+
"license": "Apache-2.0",
759
+
"dependencies": {
760
+
"tslib": "^2.6.2"
761
+
},
762
+
"engines": {
763
+
"node": ">=18.0.0"
764
+
}
765
+
},
766
+
"node_modules/@aws-sdk/util-user-agent-browser": {
767
+
"version": "3.936.0",
768
+
"license": "Apache-2.0",
769
+
"dependencies": {
770
+
"@aws-sdk/types": "3.936.0",
771
+
"@smithy/types": "^4.9.0",
772
+
"bowser": "^2.11.0",
773
+
"tslib": "^2.6.2"
774
+
}
775
+
},
776
+
"node_modules/@aws-sdk/util-user-agent-node": {
777
+
"version": "3.946.0",
778
+
"license": "Apache-2.0",
779
+
"dependencies": {
780
+
"@aws-sdk/middleware-user-agent": "3.946.0",
781
+
"@aws-sdk/types": "3.936.0",
782
+
"@smithy/node-config-provider": "^4.3.5",
783
+
"@smithy/types": "^4.9.0",
784
+
"tslib": "^2.6.2"
785
+
},
786
+
"engines": {
787
+
"node": ">=18.0.0"
788
+
},
789
+
"peerDependencies": {
790
+
"aws-crt": ">=1.0.0"
791
+
},
792
+
"peerDependenciesMeta": {
793
+
"aws-crt": {
794
+
"optional": true
795
+
}
796
+
}
797
+
},
798
+
"node_modules/@aws-sdk/xml-builder": {
799
+
"version": "3.930.0",
800
+
"license": "Apache-2.0",
801
+
"dependencies": {
802
+
"@smithy/types": "^4.9.0",
803
+
"fast-xml-parser": "5.2.5",
804
+
"tslib": "^2.6.2"
805
+
},
806
+
"engines": {
807
+
"node": ">=18.0.0"
808
+
}
809
+
},
810
+
"node_modules/@aws/lambda-invoke-store": {
811
+
"version": "0.2.2",
812
+
"license": "Apache-2.0",
813
+
"engines": {
814
+
"node": ">=18.0.0"
815
+
}
816
+
},
817
+
"node_modules/@esbuild/aix-ppc64": {
818
+
"version": "0.27.1",
819
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
820
+
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
821
+
"cpu": [
822
+
"ppc64"
823
+
],
824
+
"dev": true,
825
+
"license": "MIT",
826
+
"optional": true,
827
+
"os": [
828
+
"aix"
829
+
],
830
+
"engines": {
831
+
"node": ">=18"
832
+
}
833
+
},
834
+
"node_modules/@esbuild/android-arm": {
835
+
"version": "0.27.1",
836
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
837
+
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
838
+
"cpu": [
839
+
"arm"
840
+
],
841
+
"dev": true,
842
+
"license": "MIT",
843
+
"optional": true,
844
+
"os": [
845
+
"android"
846
+
],
847
+
"engines": {
848
+
"node": ">=18"
849
+
}
850
+
},
851
+
"node_modules/@esbuild/android-arm64": {
852
+
"version": "0.27.1",
853
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
854
+
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
855
+
"cpu": [
856
+
"arm64"
857
+
],
858
+
"dev": true,
859
+
"license": "MIT",
860
+
"optional": true,
861
+
"os": [
862
+
"android"
863
+
],
864
+
"engines": {
865
+
"node": ">=18"
866
+
}
867
+
},
868
+
"node_modules/@esbuild/android-x64": {
869
+
"version": "0.27.1",
870
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
871
+
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
872
+
"cpu": [
873
+
"x64"
874
+
],
875
+
"dev": true,
876
+
"license": "MIT",
877
+
"optional": true,
878
+
"os": [
879
+
"android"
880
+
],
881
+
"engines": {
882
+
"node": ">=18"
883
+
}
884
+
},
885
+
"node_modules/@esbuild/darwin-arm64": {
886
+
"version": "0.27.1",
887
+
"cpu": [
888
+
"arm64"
889
+
],
890
+
"dev": true,
891
+
"license": "MIT",
892
+
"optional": true,
893
+
"os": [
894
+
"darwin"
895
+
],
896
+
"engines": {
897
+
"node": ">=18"
898
+
}
899
+
},
900
+
"node_modules/@esbuild/darwin-x64": {
901
+
"version": "0.27.1",
902
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
903
+
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
904
+
"cpu": [
905
+
"x64"
906
+
],
907
+
"dev": true,
908
+
"license": "MIT",
909
+
"optional": true,
910
+
"os": [
911
+
"darwin"
912
+
],
913
+
"engines": {
914
+
"node": ">=18"
915
+
}
916
+
},
917
+
"node_modules/@esbuild/freebsd-arm64": {
918
+
"version": "0.27.1",
919
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
920
+
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
921
+
"cpu": [
922
+
"arm64"
923
+
],
924
+
"dev": true,
925
+
"license": "MIT",
926
+
"optional": true,
927
+
"os": [
928
+
"freebsd"
929
+
],
930
+
"engines": {
931
+
"node": ">=18"
932
+
}
933
+
},
934
+
"node_modules/@esbuild/freebsd-x64": {
935
+
"version": "0.27.1",
936
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
937
+
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
938
+
"cpu": [
939
+
"x64"
940
+
],
941
+
"dev": true,
942
+
"license": "MIT",
943
+
"optional": true,
944
+
"os": [
945
+
"freebsd"
946
+
],
947
+
"engines": {
948
+
"node": ">=18"
949
+
}
950
+
},
951
+
"node_modules/@esbuild/linux-arm": {
952
+
"version": "0.27.1",
953
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
954
+
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
955
+
"cpu": [
956
+
"arm"
957
+
],
958
+
"dev": true,
959
+
"license": "MIT",
960
+
"optional": true,
961
+
"os": [
962
+
"linux"
963
+
],
964
+
"engines": {
965
+
"node": ">=18"
966
+
}
967
+
},
968
+
"node_modules/@esbuild/linux-arm64": {
969
+
"version": "0.27.1",
970
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
971
+
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
972
+
"cpu": [
973
+
"arm64"
974
+
],
975
+
"dev": true,
976
+
"license": "MIT",
977
+
"optional": true,
978
+
"os": [
979
+
"linux"
980
+
],
981
+
"engines": {
982
+
"node": ">=18"
983
+
}
984
+
},
985
+
"node_modules/@esbuild/linux-ia32": {
986
+
"version": "0.27.1",
987
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
988
+
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
989
+
"cpu": [
990
+
"ia32"
991
+
],
992
+
"dev": true,
993
+
"license": "MIT",
994
+
"optional": true,
995
+
"os": [
996
+
"linux"
997
+
],
998
+
"engines": {
999
+
"node": ">=18"
1000
+
}
1001
+
},
1002
+
"node_modules/@esbuild/linux-loong64": {
1003
+
"version": "0.27.1",
1004
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
1005
+
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
1006
+
"cpu": [
1007
+
"loong64"
1008
+
],
1009
+
"dev": true,
1010
+
"license": "MIT",
1011
+
"optional": true,
1012
+
"os": [
1013
+
"linux"
1014
+
],
1015
+
"engines": {
1016
+
"node": ">=18"
1017
+
}
1018
+
},
1019
+
"node_modules/@esbuild/linux-mips64el": {
1020
+
"version": "0.27.1",
1021
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
1022
+
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
1023
+
"cpu": [
1024
+
"mips64el"
1025
+
],
1026
+
"dev": true,
1027
+
"license": "MIT",
1028
+
"optional": true,
1029
+
"os": [
1030
+
"linux"
1031
+
],
1032
+
"engines": {
1033
+
"node": ">=18"
1034
+
}
1035
+
},
1036
+
"node_modules/@esbuild/linux-ppc64": {
1037
+
"version": "0.27.1",
1038
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
1039
+
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
1040
+
"cpu": [
1041
+
"ppc64"
1042
+
],
1043
+
"dev": true,
1044
+
"license": "MIT",
1045
+
"optional": true,
1046
+
"os": [
1047
+
"linux"
1048
+
],
1049
+
"engines": {
1050
+
"node": ">=18"
1051
+
}
1052
+
},
1053
+
"node_modules/@esbuild/linux-riscv64": {
1054
+
"version": "0.27.1",
1055
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
1056
+
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
1057
+
"cpu": [
1058
+
"riscv64"
1059
+
],
1060
+
"dev": true,
1061
+
"license": "MIT",
1062
+
"optional": true,
1063
+
"os": [
1064
+
"linux"
1065
+
],
1066
+
"engines": {
1067
+
"node": ">=18"
1068
+
}
1069
+
},
1070
+
"node_modules/@esbuild/linux-s390x": {
1071
+
"version": "0.27.1",
1072
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
1073
+
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
1074
+
"cpu": [
1075
+
"s390x"
1076
+
],
1077
+
"dev": true,
1078
+
"license": "MIT",
1079
+
"optional": true,
1080
+
"os": [
1081
+
"linux"
1082
+
],
1083
+
"engines": {
1084
+
"node": ">=18"
1085
+
}
1086
+
},
1087
+
"node_modules/@esbuild/linux-x64": {
1088
+
"version": "0.27.1",
1089
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
1090
+
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
1091
+
"cpu": [
1092
+
"x64"
1093
+
],
1094
+
"dev": true,
1095
+
"license": "MIT",
1096
+
"optional": true,
1097
+
"os": [
1098
+
"linux"
1099
+
],
1100
+
"engines": {
1101
+
"node": ">=18"
1102
+
}
1103
+
},
1104
+
"node_modules/@esbuild/netbsd-arm64": {
1105
+
"version": "0.27.1",
1106
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
1107
+
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
1108
+
"cpu": [
1109
+
"arm64"
1110
+
],
1111
+
"dev": true,
1112
+
"license": "MIT",
1113
+
"optional": true,
1114
+
"os": [
1115
+
"netbsd"
1116
+
],
1117
+
"engines": {
1118
+
"node": ">=18"
1119
+
}
1120
+
},
1121
+
"node_modules/@esbuild/netbsd-x64": {
1122
+
"version": "0.27.1",
1123
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
1124
+
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
1125
+
"cpu": [
1126
+
"x64"
1127
+
],
1128
+
"dev": true,
1129
+
"license": "MIT",
1130
+
"optional": true,
1131
+
"os": [
1132
+
"netbsd"
1133
+
],
1134
+
"engines": {
1135
+
"node": ">=18"
1136
+
}
1137
+
},
1138
+
"node_modules/@esbuild/openbsd-arm64": {
1139
+
"version": "0.27.1",
1140
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
1141
+
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
1142
+
"cpu": [
1143
+
"arm64"
1144
+
],
1145
+
"dev": true,
1146
+
"license": "MIT",
1147
+
"optional": true,
1148
+
"os": [
1149
+
"openbsd"
1150
+
],
1151
+
"engines": {
1152
+
"node": ">=18"
1153
+
}
1154
+
},
1155
+
"node_modules/@esbuild/openbsd-x64": {
1156
+
"version": "0.27.1",
1157
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
1158
+
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
1159
+
"cpu": [
1160
+
"x64"
1161
+
],
1162
+
"dev": true,
1163
+
"license": "MIT",
1164
+
"optional": true,
1165
+
"os": [
1166
+
"openbsd"
1167
+
],
1168
+
"engines": {
1169
+
"node": ">=18"
1170
+
}
1171
+
},
1172
+
"node_modules/@esbuild/openharmony-arm64": {
1173
+
"version": "0.27.1",
1174
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
1175
+
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
1176
+
"cpu": [
1177
+
"arm64"
1178
+
],
1179
+
"dev": true,
1180
+
"license": "MIT",
1181
+
"optional": true,
1182
+
"os": [
1183
+
"openharmony"
1184
+
],
1185
+
"engines": {
1186
+
"node": ">=18"
1187
+
}
1188
+
},
1189
+
"node_modules/@esbuild/sunos-x64": {
1190
+
"version": "0.27.1",
1191
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
1192
+
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
1193
+
"cpu": [
1194
+
"x64"
1195
+
],
1196
+
"dev": true,
1197
+
"license": "MIT",
1198
+
"optional": true,
1199
+
"os": [
1200
+
"sunos"
1201
+
],
1202
+
"engines": {
1203
+
"node": ">=18"
1204
+
}
1205
+
},
1206
+
"node_modules/@esbuild/win32-arm64": {
1207
+
"version": "0.27.1",
1208
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
1209
+
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
1210
+
"cpu": [
1211
+
"arm64"
1212
+
],
1213
+
"dev": true,
1214
+
"license": "MIT",
1215
+
"optional": true,
1216
+
"os": [
1217
+
"win32"
1218
+
],
1219
+
"engines": {
1220
+
"node": ">=18"
1221
+
}
1222
+
},
1223
+
"node_modules/@esbuild/win32-ia32": {
1224
+
"version": "0.27.1",
1225
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
1226
+
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
1227
+
"cpu": [
1228
+
"ia32"
1229
+
],
1230
+
"dev": true,
1231
+
"license": "MIT",
1232
+
"optional": true,
1233
+
"os": [
1234
+
"win32"
1235
+
],
1236
+
"engines": {
1237
+
"node": ">=18"
1238
+
}
1239
+
},
1240
+
"node_modules/@esbuild/win32-x64": {
1241
+
"version": "0.27.1",
1242
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
1243
+
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
1244
+
"cpu": [
1245
+
"x64"
1246
+
],
1247
+
"dev": true,
1248
+
"license": "MIT",
1249
+
"optional": true,
1250
+
"os": [
1251
+
"win32"
1252
+
],
1253
+
"engines": {
1254
+
"node": ">=18"
1255
+
}
1256
+
},
1257
+
"node_modules/@eslint-community/eslint-utils": {
1258
+
"version": "4.9.0",
1259
+
"dev": true,
1260
+
"license": "MIT",
1261
+
"dependencies": {
1262
+
"eslint-visitor-keys": "^3.4.3"
1263
+
},
1264
+
"engines": {
1265
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1266
+
},
1267
+
"funding": {
1268
+
"url": "https://opencollective.com/eslint"
1269
+
},
1270
+
"peerDependencies": {
1271
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
1272
+
}
1273
+
},
1274
+
"node_modules/@eslint-community/regexpp": {
1275
+
"version": "4.12.2",
1276
+
"dev": true,
1277
+
"license": "MIT",
1278
+
"engines": {
1279
+
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
1280
+
}
1281
+
},
1282
+
"node_modules/@eslint/config-array": {
1283
+
"version": "0.21.1",
1284
+
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
1285
+
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
1286
+
"dev": true,
1287
+
"license": "Apache-2.0",
1288
+
"dependencies": {
1289
+
"@eslint/object-schema": "^2.1.7",
1290
+
"debug": "^4.3.1",
1291
+
"minimatch": "^3.1.2"
1292
+
},
1293
+
"engines": {
1294
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1295
+
}
1296
+
},
1297
+
"node_modules/@eslint/config-helpers": {
1298
+
"version": "0.4.2",
1299
+
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
1300
+
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
1301
+
"dev": true,
1302
+
"license": "Apache-2.0",
1303
+
"dependencies": {
1304
+
"@eslint/core": "^0.17.0"
1305
+
},
1306
+
"engines": {
1307
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1308
+
}
1309
+
},
1310
+
"node_modules/@eslint/core": {
1311
+
"version": "0.17.0",
1312
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
1313
+
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
1314
+
"dev": true,
1315
+
"license": "Apache-2.0",
1316
+
"dependencies": {
1317
+
"@types/json-schema": "^7.0.15"
1318
+
},
1319
+
"engines": {
1320
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1321
+
}
1322
+
},
1323
+
"node_modules/@eslint/eslintrc": {
1324
+
"version": "3.3.3",
1325
+
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
1326
+
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
1327
+
"dev": true,
1328
+
"license": "MIT",
1329
+
"dependencies": {
1330
+
"ajv": "^6.12.4",
1331
+
"debug": "^4.3.2",
1332
+
"espree": "^10.0.1",
1333
+
"globals": "^14.0.0",
1334
+
"ignore": "^5.2.0",
1335
+
"import-fresh": "^3.2.1",
1336
+
"js-yaml": "^4.1.1",
1337
+
"minimatch": "^3.1.2",
1338
+
"strip-json-comments": "^3.1.1"
1339
+
},
1340
+
"engines": {
1341
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1342
+
},
1343
+
"funding": {
1344
+
"url": "https://opencollective.com/eslint"
1345
+
}
1346
+
},
1347
+
"node_modules/@eslint/eslintrc/node_modules/ignore": {
1348
+
"version": "5.3.2",
1349
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
1350
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
1351
+
"dev": true,
1352
+
"license": "MIT",
1353
+
"engines": {
1354
+
"node": ">= 4"
1355
+
}
1356
+
},
1357
+
"node_modules/@eslint/js": {
1358
+
"version": "9.39.1",
1359
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
1360
+
"integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
1361
+
"dev": true,
1362
+
"license": "MIT",
1363
+
"engines": {
1364
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1365
+
},
1366
+
"funding": {
1367
+
"url": "https://eslint.org/donate"
1368
+
}
1369
+
},
1370
+
"node_modules/@eslint/object-schema": {
1371
+
"version": "2.1.7",
1372
+
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
1373
+
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
1374
+
"dev": true,
1375
+
"license": "Apache-2.0",
1376
+
"engines": {
1377
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1378
+
}
1379
+
},
1380
+
"node_modules/@eslint/plugin-kit": {
1381
+
"version": "0.4.1",
1382
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
1383
+
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
1384
+
"dev": true,
1385
+
"license": "Apache-2.0",
1386
+
"dependencies": {
1387
+
"@eslint/core": "^0.17.0",
1388
+
"levn": "^0.4.1"
1389
+
},
1390
+
"engines": {
1391
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1392
+
}
1393
+
},
1394
+
"node_modules/@humanfs/core": {
1395
+
"version": "0.19.1",
1396
+
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
1397
+
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
1398
+
"dev": true,
1399
+
"license": "Apache-2.0",
1400
+
"engines": {
1401
+
"node": ">=18.18.0"
1402
+
}
1403
+
},
1404
+
"node_modules/@humanfs/node": {
1405
+
"version": "0.16.7",
1406
+
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
1407
+
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
1408
+
"dev": true,
1409
+
"license": "Apache-2.0",
1410
+
"dependencies": {
1411
+
"@humanfs/core": "^0.19.1",
1412
+
"@humanwhocodes/retry": "^0.4.0"
1413
+
},
1414
+
"engines": {
1415
+
"node": ">=18.18.0"
1416
+
}
1417
+
},
1418
+
"node_modules/@humanwhocodes/module-importer": {
1419
+
"version": "1.0.1",
1420
+
"dev": true,
1421
+
"license": "Apache-2.0",
1422
+
"engines": {
1423
+
"node": ">=12.22"
1424
+
},
1425
+
"funding": {
1426
+
"type": "github",
1427
+
"url": "https://github.com/sponsors/nzakas"
1428
+
}
1429
+
},
1430
+
"node_modules/@humanwhocodes/retry": {
1431
+
"version": "0.4.3",
1432
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
1433
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
1434
+
"dev": true,
1435
+
"license": "Apache-2.0",
1436
+
"engines": {
1437
+
"node": ">=18.18"
1438
+
},
1439
+
"funding": {
1440
+
"type": "github",
1441
+
"url": "https://github.com/sponsors/nzakas"
1442
+
}
1443
+
},
1444
+
"node_modules/@jridgewell/sourcemap-codec": {
1445
+
"version": "1.5.5",
1446
+
"dev": true,
1447
+
"license": "MIT"
1448
+
},
1449
+
"node_modules/@rollup/rollup-android-arm-eabi": {
1450
+
"version": "4.53.3",
1451
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
1452
+
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
1453
+
"cpu": [
1454
+
"arm"
1455
+
],
1456
+
"dev": true,
1457
+
"license": "MIT",
1458
+
"optional": true,
1459
+
"os": [
1460
+
"android"
1461
+
]
1462
+
},
1463
+
"node_modules/@rollup/rollup-android-arm64": {
1464
+
"version": "4.53.3",
1465
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
1466
+
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
1467
+
"cpu": [
1468
+
"arm64"
1469
+
],
1470
+
"dev": true,
1471
+
"license": "MIT",
1472
+
"optional": true,
1473
+
"os": [
1474
+
"android"
1475
+
]
1476
+
},
1477
+
"node_modules/@rollup/rollup-darwin-arm64": {
1478
+
"version": "4.53.3",
1479
+
"cpu": [
1480
+
"arm64"
1481
+
],
1482
+
"dev": true,
1483
+
"license": "MIT",
1484
+
"optional": true,
1485
+
"os": [
1486
+
"darwin"
1487
+
]
1488
+
},
1489
+
"node_modules/@rollup/rollup-darwin-x64": {
1490
+
"version": "4.53.3",
1491
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
1492
+
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
1493
+
"cpu": [
1494
+
"x64"
1495
+
],
1496
+
"dev": true,
1497
+
"license": "MIT",
1498
+
"optional": true,
1499
+
"os": [
1500
+
"darwin"
1501
+
]
1502
+
},
1503
+
"node_modules/@rollup/rollup-freebsd-arm64": {
1504
+
"version": "4.53.3",
1505
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
1506
+
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
1507
+
"cpu": [
1508
+
"arm64"
1509
+
],
1510
+
"dev": true,
1511
+
"license": "MIT",
1512
+
"optional": true,
1513
+
"os": [
1514
+
"freebsd"
1515
+
]
1516
+
},
1517
+
"node_modules/@rollup/rollup-freebsd-x64": {
1518
+
"version": "4.53.3",
1519
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
1520
+
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
1521
+
"cpu": [
1522
+
"x64"
1523
+
],
1524
+
"dev": true,
1525
+
"license": "MIT",
1526
+
"optional": true,
1527
+
"os": [
1528
+
"freebsd"
1529
+
]
1530
+
},
1531
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1532
+
"version": "4.53.3",
1533
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
1534
+
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
1535
+
"cpu": [
1536
+
"arm"
1537
+
],
1538
+
"dev": true,
1539
+
"license": "MIT",
1540
+
"optional": true,
1541
+
"os": [
1542
+
"linux"
1543
+
]
1544
+
},
1545
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
1546
+
"version": "4.53.3",
1547
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
1548
+
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
1549
+
"cpu": [
1550
+
"arm"
1551
+
],
1552
+
"dev": true,
1553
+
"license": "MIT",
1554
+
"optional": true,
1555
+
"os": [
1556
+
"linux"
1557
+
]
1558
+
},
1559
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
1560
+
"version": "4.53.3",
1561
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
1562
+
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
1563
+
"cpu": [
1564
+
"arm64"
1565
+
],
1566
+
"dev": true,
1567
+
"license": "MIT",
1568
+
"optional": true,
1569
+
"os": [
1570
+
"linux"
1571
+
]
1572
+
},
1573
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
1574
+
"version": "4.53.3",
1575
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
1576
+
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
1577
+
"cpu": [
1578
+
"arm64"
1579
+
],
1580
+
"dev": true,
1581
+
"license": "MIT",
1582
+
"optional": true,
1583
+
"os": [
1584
+
"linux"
1585
+
]
1586
+
},
1587
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
1588
+
"version": "4.53.3",
1589
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
1590
+
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
1591
+
"cpu": [
1592
+
"loong64"
1593
+
],
1594
+
"dev": true,
1595
+
"license": "MIT",
1596
+
"optional": true,
1597
+
"os": [
1598
+
"linux"
1599
+
]
1600
+
},
1601
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
1602
+
"version": "4.53.3",
1603
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
1604
+
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
1605
+
"cpu": [
1606
+
"ppc64"
1607
+
],
1608
+
"dev": true,
1609
+
"license": "MIT",
1610
+
"optional": true,
1611
+
"os": [
1612
+
"linux"
1613
+
]
1614
+
},
1615
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
1616
+
"version": "4.53.3",
1617
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
1618
+
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
1619
+
"cpu": [
1620
+
"riscv64"
1621
+
],
1622
+
"dev": true,
1623
+
"license": "MIT",
1624
+
"optional": true,
1625
+
"os": [
1626
+
"linux"
1627
+
]
1628
+
},
1629
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
1630
+
"version": "4.53.3",
1631
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
1632
+
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
1633
+
"cpu": [
1634
+
"riscv64"
1635
+
],
1636
+
"dev": true,
1637
+
"license": "MIT",
1638
+
"optional": true,
1639
+
"os": [
1640
+
"linux"
1641
+
]
1642
+
},
1643
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
1644
+
"version": "4.53.3",
1645
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
1646
+
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
1647
+
"cpu": [
1648
+
"s390x"
1649
+
],
1650
+
"dev": true,
1651
+
"license": "MIT",
1652
+
"optional": true,
1653
+
"os": [
1654
+
"linux"
1655
+
]
1656
+
},
1657
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
1658
+
"version": "4.53.3",
1659
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
1660
+
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
1661
+
"cpu": [
1662
+
"x64"
1663
+
],
1664
+
"dev": true,
1665
+
"license": "MIT",
1666
+
"optional": true,
1667
+
"os": [
1668
+
"linux"
1669
+
]
1670
+
},
1671
+
"node_modules/@rollup/rollup-linux-x64-musl": {
1672
+
"version": "4.53.3",
1673
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
1674
+
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
1675
+
"cpu": [
1676
+
"x64"
1677
+
],
1678
+
"dev": true,
1679
+
"license": "MIT",
1680
+
"optional": true,
1681
+
"os": [
1682
+
"linux"
1683
+
]
1684
+
},
1685
+
"node_modules/@rollup/rollup-openharmony-arm64": {
1686
+
"version": "4.53.3",
1687
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
1688
+
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
1689
+
"cpu": [
1690
+
"arm64"
1691
+
],
1692
+
"dev": true,
1693
+
"license": "MIT",
1694
+
"optional": true,
1695
+
"os": [
1696
+
"openharmony"
1697
+
]
1698
+
},
1699
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
1700
+
"version": "4.53.3",
1701
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
1702
+
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
1703
+
"cpu": [
1704
+
"arm64"
1705
+
],
1706
+
"dev": true,
1707
+
"license": "MIT",
1708
+
"optional": true,
1709
+
"os": [
1710
+
"win32"
1711
+
]
1712
+
},
1713
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
1714
+
"version": "4.53.3",
1715
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
1716
+
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
1717
+
"cpu": [
1718
+
"ia32"
1719
+
],
1720
+
"dev": true,
1721
+
"license": "MIT",
1722
+
"optional": true,
1723
+
"os": [
1724
+
"win32"
1725
+
]
1726
+
},
1727
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
1728
+
"version": "4.53.3",
1729
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
1730
+
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
1731
+
"cpu": [
1732
+
"x64"
1733
+
],
1734
+
"dev": true,
1735
+
"license": "MIT",
1736
+
"optional": true,
1737
+
"os": [
1738
+
"win32"
1739
+
]
1740
+
},
1741
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
1742
+
"version": "4.53.3",
1743
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
1744
+
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
1745
+
"cpu": [
1746
+
"x64"
1747
+
],
1748
+
"dev": true,
1749
+
"license": "MIT",
1750
+
"optional": true,
1751
+
"os": [
1752
+
"win32"
1753
+
]
1754
+
},
1755
+
"node_modules/@smithy/abort-controller": {
1756
+
"version": "4.2.5",
1757
+
"license": "Apache-2.0",
1758
+
"dependencies": {
1759
+
"@smithy/types": "^4.9.0",
1760
+
"tslib": "^2.6.2"
1761
+
},
1762
+
"engines": {
1763
+
"node": ">=18.0.0"
1764
+
}
1765
+
},
1766
+
"node_modules/@smithy/chunked-blob-reader": {
1767
+
"version": "5.2.0",
1768
+
"license": "Apache-2.0",
1769
+
"dependencies": {
1770
+
"tslib": "^2.6.2"
1771
+
},
1772
+
"engines": {
1773
+
"node": ">=18.0.0"
1774
+
}
1775
+
},
1776
+
"node_modules/@smithy/chunked-blob-reader-native": {
1777
+
"version": "4.2.1",
1778
+
"license": "Apache-2.0",
1779
+
"dependencies": {
1780
+
"@smithy/util-base64": "^4.3.0",
1781
+
"tslib": "^2.6.2"
1782
+
},
1783
+
"engines": {
1784
+
"node": ">=18.0.0"
1785
+
}
1786
+
},
1787
+
"node_modules/@smithy/config-resolver": {
1788
+
"version": "4.4.3",
1789
+
"license": "Apache-2.0",
1790
+
"dependencies": {
1791
+
"@smithy/node-config-provider": "^4.3.5",
1792
+
"@smithy/types": "^4.9.0",
1793
+
"@smithy/util-config-provider": "^4.2.0",
1794
+
"@smithy/util-endpoints": "^3.2.5",
1795
+
"@smithy/util-middleware": "^4.2.5",
1796
+
"tslib": "^2.6.2"
1797
+
},
1798
+
"engines": {
1799
+
"node": ">=18.0.0"
1800
+
}
1801
+
},
1802
+
"node_modules/@smithy/core": {
1803
+
"version": "3.18.7",
1804
+
"license": "Apache-2.0",
1805
+
"dependencies": {
1806
+
"@smithy/middleware-serde": "^4.2.6",
1807
+
"@smithy/protocol-http": "^5.3.5",
1808
+
"@smithy/types": "^4.9.0",
1809
+
"@smithy/util-base64": "^4.3.0",
1810
+
"@smithy/util-body-length-browser": "^4.2.0",
1811
+
"@smithy/util-middleware": "^4.2.5",
1812
+
"@smithy/util-stream": "^4.5.6",
1813
+
"@smithy/util-utf8": "^4.2.0",
1814
+
"@smithy/uuid": "^1.1.0",
1815
+
"tslib": "^2.6.2"
1816
+
},
1817
+
"engines": {
1818
+
"node": ">=18.0.0"
1819
+
}
1820
+
},
1821
+
"node_modules/@smithy/credential-provider-imds": {
1822
+
"version": "4.2.5",
1823
+
"license": "Apache-2.0",
1824
+
"dependencies": {
1825
+
"@smithy/node-config-provider": "^4.3.5",
1826
+
"@smithy/property-provider": "^4.2.5",
1827
+
"@smithy/types": "^4.9.0",
1828
+
"@smithy/url-parser": "^4.2.5",
1829
+
"tslib": "^2.6.2"
1830
+
},
1831
+
"engines": {
1832
+
"node": ">=18.0.0"
1833
+
}
1834
+
},
1835
+
"node_modules/@smithy/eventstream-codec": {
1836
+
"version": "4.2.5",
1837
+
"license": "Apache-2.0",
1838
+
"dependencies": {
1839
+
"@aws-crypto/crc32": "5.2.0",
1840
+
"@smithy/types": "^4.9.0",
1841
+
"@smithy/util-hex-encoding": "^4.2.0",
1842
+
"tslib": "^2.6.2"
1843
+
},
1844
+
"engines": {
1845
+
"node": ">=18.0.0"
1846
+
}
1847
+
},
1848
+
"node_modules/@smithy/eventstream-serde-browser": {
1849
+
"version": "4.2.5",
1850
+
"license": "Apache-2.0",
1851
+
"dependencies": {
1852
+
"@smithy/eventstream-serde-universal": "^4.2.5",
1853
+
"@smithy/types": "^4.9.0",
1854
+
"tslib": "^2.6.2"
1855
+
},
1856
+
"engines": {
1857
+
"node": ">=18.0.0"
1858
+
}
1859
+
},
1860
+
"node_modules/@smithy/eventstream-serde-config-resolver": {
1861
+
"version": "4.3.5",
1862
+
"license": "Apache-2.0",
1863
+
"dependencies": {
1864
+
"@smithy/types": "^4.9.0",
1865
+
"tslib": "^2.6.2"
1866
+
},
1867
+
"engines": {
1868
+
"node": ">=18.0.0"
1869
+
}
1870
+
},
1871
+
"node_modules/@smithy/eventstream-serde-node": {
1872
+
"version": "4.2.5",
1873
+
"license": "Apache-2.0",
1874
+
"dependencies": {
1875
+
"@smithy/eventstream-serde-universal": "^4.2.5",
1876
+
"@smithy/types": "^4.9.0",
1877
+
"tslib": "^2.6.2"
1878
+
},
1879
+
"engines": {
1880
+
"node": ">=18.0.0"
1881
+
}
1882
+
},
1883
+
"node_modules/@smithy/eventstream-serde-universal": {
1884
+
"version": "4.2.5",
1885
+
"license": "Apache-2.0",
1886
+
"dependencies": {
1887
+
"@smithy/eventstream-codec": "^4.2.5",
1888
+
"@smithy/types": "^4.9.0",
1889
+
"tslib": "^2.6.2"
1890
+
},
1891
+
"engines": {
1892
+
"node": ">=18.0.0"
1893
+
}
1894
+
},
1895
+
"node_modules/@smithy/fetch-http-handler": {
1896
+
"version": "5.3.6",
1897
+
"license": "Apache-2.0",
1898
+
"dependencies": {
1899
+
"@smithy/protocol-http": "^5.3.5",
1900
+
"@smithy/querystring-builder": "^4.2.5",
1901
+
"@smithy/types": "^4.9.0",
1902
+
"@smithy/util-base64": "^4.3.0",
1903
+
"tslib": "^2.6.2"
1904
+
},
1905
+
"engines": {
1906
+
"node": ">=18.0.0"
1907
+
}
1908
+
},
1909
+
"node_modules/@smithy/hash-blob-browser": {
1910
+
"version": "4.2.6",
1911
+
"license": "Apache-2.0",
1912
+
"dependencies": {
1913
+
"@smithy/chunked-blob-reader": "^5.2.0",
1914
+
"@smithy/chunked-blob-reader-native": "^4.2.1",
1915
+
"@smithy/types": "^4.9.0",
1916
+
"tslib": "^2.6.2"
1917
+
},
1918
+
"engines": {
1919
+
"node": ">=18.0.0"
1920
+
}
1921
+
},
1922
+
"node_modules/@smithy/hash-node": {
1923
+
"version": "4.2.5",
1924
+
"license": "Apache-2.0",
1925
+
"dependencies": {
1926
+
"@smithy/types": "^4.9.0",
1927
+
"@smithy/util-buffer-from": "^4.2.0",
1928
+
"@smithy/util-utf8": "^4.2.0",
1929
+
"tslib": "^2.6.2"
1930
+
},
1931
+
"engines": {
1932
+
"node": ">=18.0.0"
1933
+
}
1934
+
},
1935
+
"node_modules/@smithy/hash-stream-node": {
1936
+
"version": "4.2.5",
1937
+
"license": "Apache-2.0",
1938
+
"dependencies": {
1939
+
"@smithy/types": "^4.9.0",
1940
+
"@smithy/util-utf8": "^4.2.0",
1941
+
"tslib": "^2.6.2"
1942
+
},
1943
+
"engines": {
1944
+
"node": ">=18.0.0"
1945
+
}
1946
+
},
1947
+
"node_modules/@smithy/invalid-dependency": {
1948
+
"version": "4.2.5",
1949
+
"license": "Apache-2.0",
1950
+
"dependencies": {
1951
+
"@smithy/types": "^4.9.0",
1952
+
"tslib": "^2.6.2"
1953
+
},
1954
+
"engines": {
1955
+
"node": ">=18.0.0"
1956
+
}
1957
+
},
1958
+
"node_modules/@smithy/is-array-buffer": {
1959
+
"version": "4.2.0",
1960
+
"license": "Apache-2.0",
1961
+
"dependencies": {
1962
+
"tslib": "^2.6.2"
1963
+
},
1964
+
"engines": {
1965
+
"node": ">=18.0.0"
1966
+
}
1967
+
},
1968
+
"node_modules/@smithy/md5-js": {
1969
+
"version": "4.2.5",
1970
+
"license": "Apache-2.0",
1971
+
"dependencies": {
1972
+
"@smithy/types": "^4.9.0",
1973
+
"@smithy/util-utf8": "^4.2.0",
1974
+
"tslib": "^2.6.2"
1975
+
},
1976
+
"engines": {
1977
+
"node": ">=18.0.0"
1978
+
}
1979
+
},
1980
+
"node_modules/@smithy/middleware-content-length": {
1981
+
"version": "4.2.5",
1982
+
"license": "Apache-2.0",
1983
+
"dependencies": {
1984
+
"@smithy/protocol-http": "^5.3.5",
1985
+
"@smithy/types": "^4.9.0",
1986
+
"tslib": "^2.6.2"
1987
+
},
1988
+
"engines": {
1989
+
"node": ">=18.0.0"
1990
+
}
1991
+
},
1992
+
"node_modules/@smithy/middleware-endpoint": {
1993
+
"version": "4.3.14",
1994
+
"license": "Apache-2.0",
1995
+
"dependencies": {
1996
+
"@smithy/core": "^3.18.7",
1997
+
"@smithy/middleware-serde": "^4.2.6",
1998
+
"@smithy/node-config-provider": "^4.3.5",
1999
+
"@smithy/shared-ini-file-loader": "^4.4.0",
2000
+
"@smithy/types": "^4.9.0",
2001
+
"@smithy/url-parser": "^4.2.5",
2002
+
"@smithy/util-middleware": "^4.2.5",
2003
+
"tslib": "^2.6.2"
2004
+
},
2005
+
"engines": {
2006
+
"node": ">=18.0.0"
2007
+
}
2008
+
},
2009
+
"node_modules/@smithy/middleware-retry": {
2010
+
"version": "4.4.14",
2011
+
"license": "Apache-2.0",
2012
+
"dependencies": {
2013
+
"@smithy/node-config-provider": "^4.3.5",
2014
+
"@smithy/protocol-http": "^5.3.5",
2015
+
"@smithy/service-error-classification": "^4.2.5",
2016
+
"@smithy/smithy-client": "^4.9.10",
2017
+
"@smithy/types": "^4.9.0",
2018
+
"@smithy/util-middleware": "^4.2.5",
2019
+
"@smithy/util-retry": "^4.2.5",
2020
+
"@smithy/uuid": "^1.1.0",
2021
+
"tslib": "^2.6.2"
2022
+
},
2023
+
"engines": {
2024
+
"node": ">=18.0.0"
2025
+
}
2026
+
},
2027
+
"node_modules/@smithy/middleware-serde": {
2028
+
"version": "4.2.6",
2029
+
"license": "Apache-2.0",
2030
+
"dependencies": {
2031
+
"@smithy/protocol-http": "^5.3.5",
2032
+
"@smithy/types": "^4.9.0",
2033
+
"tslib": "^2.6.2"
2034
+
},
2035
+
"engines": {
2036
+
"node": ">=18.0.0"
2037
+
}
2038
+
},
2039
+
"node_modules/@smithy/middleware-stack": {
2040
+
"version": "4.2.5",
2041
+
"license": "Apache-2.0",
2042
+
"dependencies": {
2043
+
"@smithy/types": "^4.9.0",
2044
+
"tslib": "^2.6.2"
2045
+
},
2046
+
"engines": {
2047
+
"node": ">=18.0.0"
2048
+
}
2049
+
},
2050
+
"node_modules/@smithy/node-config-provider": {
2051
+
"version": "4.3.5",
2052
+
"license": "Apache-2.0",
2053
+
"dependencies": {
2054
+
"@smithy/property-provider": "^4.2.5",
2055
+
"@smithy/shared-ini-file-loader": "^4.4.0",
2056
+
"@smithy/types": "^4.9.0",
2057
+
"tslib": "^2.6.2"
2058
+
},
2059
+
"engines": {
2060
+
"node": ">=18.0.0"
2061
+
}
2062
+
},
2063
+
"node_modules/@smithy/node-http-handler": {
2064
+
"version": "4.4.5",
2065
+
"license": "Apache-2.0",
2066
+
"dependencies": {
2067
+
"@smithy/abort-controller": "^4.2.5",
2068
+
"@smithy/protocol-http": "^5.3.5",
2069
+
"@smithy/querystring-builder": "^4.2.5",
2070
+
"@smithy/types": "^4.9.0",
2071
+
"tslib": "^2.6.2"
2072
+
},
2073
+
"engines": {
2074
+
"node": ">=18.0.0"
2075
+
}
2076
+
},
2077
+
"node_modules/@smithy/property-provider": {
2078
+
"version": "4.2.5",
2079
+
"license": "Apache-2.0",
2080
+
"dependencies": {
2081
+
"@smithy/types": "^4.9.0",
2082
+
"tslib": "^2.6.2"
2083
+
},
2084
+
"engines": {
2085
+
"node": ">=18.0.0"
2086
+
}
2087
+
},
2088
+
"node_modules/@smithy/protocol-http": {
2089
+
"version": "5.3.5",
2090
+
"license": "Apache-2.0",
2091
+
"dependencies": {
2092
+
"@smithy/types": "^4.9.0",
2093
+
"tslib": "^2.6.2"
2094
+
},
2095
+
"engines": {
2096
+
"node": ">=18.0.0"
2097
+
}
2098
+
},
2099
+
"node_modules/@smithy/querystring-builder": {
2100
+
"version": "4.2.5",
2101
+
"license": "Apache-2.0",
2102
+
"dependencies": {
2103
+
"@smithy/types": "^4.9.0",
2104
+
"@smithy/util-uri-escape": "^4.2.0",
2105
+
"tslib": "^2.6.2"
2106
+
},
2107
+
"engines": {
2108
+
"node": ">=18.0.0"
2109
+
}
2110
+
},
2111
+
"node_modules/@smithy/querystring-parser": {
2112
+
"version": "4.2.5",
2113
+
"license": "Apache-2.0",
2114
+
"dependencies": {
2115
+
"@smithy/types": "^4.9.0",
2116
+
"tslib": "^2.6.2"
2117
+
},
2118
+
"engines": {
2119
+
"node": ">=18.0.0"
2120
+
}
2121
+
},
2122
+
"node_modules/@smithy/service-error-classification": {
2123
+
"version": "4.2.5",
2124
+
"license": "Apache-2.0",
2125
+
"dependencies": {
2126
+
"@smithy/types": "^4.9.0"
2127
+
},
2128
+
"engines": {
2129
+
"node": ">=18.0.0"
2130
+
}
2131
+
},
2132
+
"node_modules/@smithy/shared-ini-file-loader": {
2133
+
"version": "4.4.0",
2134
+
"license": "Apache-2.0",
2135
+
"dependencies": {
2136
+
"@smithy/types": "^4.9.0",
2137
+
"tslib": "^2.6.2"
2138
+
},
2139
+
"engines": {
2140
+
"node": ">=18.0.0"
2141
+
}
2142
+
},
2143
+
"node_modules/@smithy/signature-v4": {
2144
+
"version": "5.3.5",
2145
+
"license": "Apache-2.0",
2146
+
"dependencies": {
2147
+
"@smithy/is-array-buffer": "^4.2.0",
2148
+
"@smithy/protocol-http": "^5.3.5",
2149
+
"@smithy/types": "^4.9.0",
2150
+
"@smithy/util-hex-encoding": "^4.2.0",
2151
+
"@smithy/util-middleware": "^4.2.5",
2152
+
"@smithy/util-uri-escape": "^4.2.0",
2153
+
"@smithy/util-utf8": "^4.2.0",
2154
+
"tslib": "^2.6.2"
2155
+
},
2156
+
"engines": {
2157
+
"node": ">=18.0.0"
2158
+
}
2159
+
},
2160
+
"node_modules/@smithy/smithy-client": {
2161
+
"version": "4.9.10",
2162
+
"license": "Apache-2.0",
2163
+
"dependencies": {
2164
+
"@smithy/core": "^3.18.7",
2165
+
"@smithy/middleware-endpoint": "^4.3.14",
2166
+
"@smithy/middleware-stack": "^4.2.5",
2167
+
"@smithy/protocol-http": "^5.3.5",
2168
+
"@smithy/types": "^4.9.0",
2169
+
"@smithy/util-stream": "^4.5.6",
2170
+
"tslib": "^2.6.2"
2171
+
},
2172
+
"engines": {
2173
+
"node": ">=18.0.0"
2174
+
}
2175
+
},
2176
+
"node_modules/@smithy/types": {
2177
+
"version": "4.9.0",
2178
+
"license": "Apache-2.0",
2179
+
"dependencies": {
2180
+
"tslib": "^2.6.2"
2181
+
},
2182
+
"engines": {
2183
+
"node": ">=18.0.0"
2184
+
}
2185
+
},
2186
+
"node_modules/@smithy/url-parser": {
2187
+
"version": "4.2.5",
2188
+
"license": "Apache-2.0",
2189
+
"dependencies": {
2190
+
"@smithy/querystring-parser": "^4.2.5",
2191
+
"@smithy/types": "^4.9.0",
2192
+
"tslib": "^2.6.2"
2193
+
},
2194
+
"engines": {
2195
+
"node": ">=18.0.0"
2196
+
}
2197
+
},
2198
+
"node_modules/@smithy/util-base64": {
2199
+
"version": "4.3.0",
2200
+
"license": "Apache-2.0",
2201
+
"dependencies": {
2202
+
"@smithy/util-buffer-from": "^4.2.0",
2203
+
"@smithy/util-utf8": "^4.2.0",
2204
+
"tslib": "^2.6.2"
2205
+
},
2206
+
"engines": {
2207
+
"node": ">=18.0.0"
2208
+
}
2209
+
},
2210
+
"node_modules/@smithy/util-body-length-browser": {
2211
+
"version": "4.2.0",
2212
+
"license": "Apache-2.0",
2213
+
"dependencies": {
2214
+
"tslib": "^2.6.2"
2215
+
},
2216
+
"engines": {
2217
+
"node": ">=18.0.0"
2218
+
}
2219
+
},
2220
+
"node_modules/@smithy/util-body-length-node": {
2221
+
"version": "4.2.1",
2222
+
"license": "Apache-2.0",
2223
+
"dependencies": {
2224
+
"tslib": "^2.6.2"
2225
+
},
2226
+
"engines": {
2227
+
"node": ">=18.0.0"
2228
+
}
2229
+
},
2230
+
"node_modules/@smithy/util-buffer-from": {
2231
+
"version": "4.2.0",
2232
+
"license": "Apache-2.0",
2233
+
"dependencies": {
2234
+
"@smithy/is-array-buffer": "^4.2.0",
2235
+
"tslib": "^2.6.2"
2236
+
},
2237
+
"engines": {
2238
+
"node": ">=18.0.0"
2239
+
}
2240
+
},
2241
+
"node_modules/@smithy/util-config-provider": {
2242
+
"version": "4.2.0",
2243
+
"license": "Apache-2.0",
2244
+
"dependencies": {
2245
+
"tslib": "^2.6.2"
2246
+
},
2247
+
"engines": {
2248
+
"node": ">=18.0.0"
2249
+
}
2250
+
},
2251
+
"node_modules/@smithy/util-defaults-mode-browser": {
2252
+
"version": "4.3.13",
2253
+
"license": "Apache-2.0",
2254
+
"dependencies": {
2255
+
"@smithy/property-provider": "^4.2.5",
2256
+
"@smithy/smithy-client": "^4.9.10",
2257
+
"@smithy/types": "^4.9.0",
2258
+
"tslib": "^2.6.2"
2259
+
},
2260
+
"engines": {
2261
+
"node": ">=18.0.0"
2262
+
}
2263
+
},
2264
+
"node_modules/@smithy/util-defaults-mode-node": {
2265
+
"version": "4.2.16",
2266
+
"license": "Apache-2.0",
2267
+
"dependencies": {
2268
+
"@smithy/config-resolver": "^4.4.3",
2269
+
"@smithy/credential-provider-imds": "^4.2.5",
2270
+
"@smithy/node-config-provider": "^4.3.5",
2271
+
"@smithy/property-provider": "^4.2.5",
2272
+
"@smithy/smithy-client": "^4.9.10",
2273
+
"@smithy/types": "^4.9.0",
2274
+
"tslib": "^2.6.2"
2275
+
},
2276
+
"engines": {
2277
+
"node": ">=18.0.0"
2278
+
}
2279
+
},
2280
+
"node_modules/@smithy/util-endpoints": {
2281
+
"version": "3.2.5",
2282
+
"license": "Apache-2.0",
2283
+
"dependencies": {
2284
+
"@smithy/node-config-provider": "^4.3.5",
2285
+
"@smithy/types": "^4.9.0",
2286
+
"tslib": "^2.6.2"
2287
+
},
2288
+
"engines": {
2289
+
"node": ">=18.0.0"
2290
+
}
2291
+
},
2292
+
"node_modules/@smithy/util-hex-encoding": {
2293
+
"version": "4.2.0",
2294
+
"license": "Apache-2.0",
2295
+
"dependencies": {
2296
+
"tslib": "^2.6.2"
2297
+
},
2298
+
"engines": {
2299
+
"node": ">=18.0.0"
2300
+
}
2301
+
},
2302
+
"node_modules/@smithy/util-middleware": {
2303
+
"version": "4.2.5",
2304
+
"license": "Apache-2.0",
2305
+
"dependencies": {
2306
+
"@smithy/types": "^4.9.0",
2307
+
"tslib": "^2.6.2"
2308
+
},
2309
+
"engines": {
2310
+
"node": ">=18.0.0"
2311
+
}
2312
+
},
2313
+
"node_modules/@smithy/util-retry": {
2314
+
"version": "4.2.5",
2315
+
"license": "Apache-2.0",
2316
+
"dependencies": {
2317
+
"@smithy/service-error-classification": "^4.2.5",
2318
+
"@smithy/types": "^4.9.0",
2319
+
"tslib": "^2.6.2"
2320
+
},
2321
+
"engines": {
2322
+
"node": ">=18.0.0"
2323
+
}
2324
+
},
2325
+
"node_modules/@smithy/util-stream": {
2326
+
"version": "4.5.6",
2327
+
"license": "Apache-2.0",
2328
+
"dependencies": {
2329
+
"@smithy/fetch-http-handler": "^5.3.6",
2330
+
"@smithy/node-http-handler": "^4.4.5",
2331
+
"@smithy/types": "^4.9.0",
2332
+
"@smithy/util-base64": "^4.3.0",
2333
+
"@smithy/util-buffer-from": "^4.2.0",
2334
+
"@smithy/util-hex-encoding": "^4.2.0",
2335
+
"@smithy/util-utf8": "^4.2.0",
2336
+
"tslib": "^2.6.2"
2337
+
},
2338
+
"engines": {
2339
+
"node": ">=18.0.0"
2340
+
}
2341
+
},
2342
+
"node_modules/@smithy/util-uri-escape": {
2343
+
"version": "4.2.0",
2344
+
"license": "Apache-2.0",
2345
+
"dependencies": {
2346
+
"tslib": "^2.6.2"
2347
+
},
2348
+
"engines": {
2349
+
"node": ">=18.0.0"
2350
+
}
2351
+
},
2352
+
"node_modules/@smithy/util-utf8": {
2353
+
"version": "4.2.0",
2354
+
"license": "Apache-2.0",
2355
+
"dependencies": {
2356
+
"@smithy/util-buffer-from": "^4.2.0",
2357
+
"tslib": "^2.6.2"
2358
+
},
2359
+
"engines": {
2360
+
"node": ">=18.0.0"
2361
+
}
2362
+
},
2363
+
"node_modules/@smithy/util-waiter": {
2364
+
"version": "4.2.5",
2365
+
"license": "Apache-2.0",
2366
+
"dependencies": {
2367
+
"@smithy/abort-controller": "^4.2.5",
2368
+
"@smithy/types": "^4.9.0",
2369
+
"tslib": "^2.6.2"
2370
+
},
2371
+
"engines": {
2372
+
"node": ">=18.0.0"
2373
+
}
2374
+
},
2375
+
"node_modules/@smithy/uuid": {
2376
+
"version": "1.1.0",
2377
+
"license": "Apache-2.0",
2378
+
"dependencies": {
2379
+
"tslib": "^2.6.2"
2380
+
},
2381
+
"engines": {
2382
+
"node": ">=18.0.0"
2383
+
}
2384
+
},
2385
+
"node_modules/@standard-schema/spec": {
2386
+
"version": "1.0.0",
2387
+
"dev": true,
2388
+
"license": "MIT"
2389
+
},
2390
+
"node_modules/@types/bun": {
2391
+
"version": "1.3.4",
2392
+
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.4.tgz",
2393
+
"integrity": "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA==",
2394
+
"dev": true,
2395
+
"license": "MIT",
2396
+
"dependencies": {
2397
+
"bun-types": "1.3.4"
2398
+
}
2399
+
},
2400
+
"node_modules/@types/chai": {
2401
+
"version": "5.2.3",
2402
+
"dev": true,
2403
+
"license": "MIT",
2404
+
"dependencies": {
2405
+
"@types/deep-eql": "*",
2406
+
"assertion-error": "^2.0.1"
2407
+
}
2408
+
},
2409
+
"node_modules/@types/deep-eql": {
2410
+
"version": "4.0.2",
2411
+
"dev": true,
2412
+
"license": "MIT"
2413
+
},
2414
+
"node_modules/@types/estree": {
2415
+
"version": "1.0.8",
2416
+
"dev": true,
2417
+
"license": "MIT"
2418
+
},
2419
+
"node_modules/@types/json-schema": {
2420
+
"version": "7.0.15",
2421
+
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
2422
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
2423
+
"dev": true,
2424
+
"license": "MIT"
2425
+
},
2426
+
"node_modules/@types/mime-types": {
2427
+
"version": "3.0.1",
2428
+
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz",
2429
+
"integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==",
2430
+
"dev": true,
2431
+
"license": "MIT"
2432
+
},
2433
+
"node_modules/@types/node": {
2434
+
"version": "24.10.1",
2435
+
"dev": true,
2436
+
"license": "MIT",
2437
+
"peer": true,
2438
+
"dependencies": {
2439
+
"undici-types": "~7.16.0"
2440
+
}
2441
+
},
2442
+
"node_modules/@typescript-eslint/eslint-plugin": {
2443
+
"version": "8.49.0",
2444
+
"dev": true,
2445
+
"license": "MIT",
2446
+
"dependencies": {
2447
+
"@eslint-community/regexpp": "^4.10.0",
2448
+
"@typescript-eslint/scope-manager": "8.49.0",
2449
+
"@typescript-eslint/type-utils": "8.49.0",
2450
+
"@typescript-eslint/utils": "8.49.0",
2451
+
"@typescript-eslint/visitor-keys": "8.49.0",
2452
+
"ignore": "^7.0.0",
2453
+
"natural-compare": "^1.4.0",
2454
+
"ts-api-utils": "^2.1.0"
2455
+
},
2456
+
"engines": {
2457
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2458
+
},
2459
+
"funding": {
2460
+
"type": "opencollective",
2461
+
"url": "https://opencollective.com/typescript-eslint"
2462
+
},
2463
+
"peerDependencies": {
2464
+
"@typescript-eslint/parser": "^8.49.0",
2465
+
"eslint": "^8.57.0 || ^9.0.0",
2466
+
"typescript": ">=4.8.4 <6.0.0"
2467
+
}
2468
+
},
2469
+
"node_modules/@typescript-eslint/parser": {
2470
+
"version": "8.49.0",
2471
+
"dev": true,
2472
+
"license": "MIT",
2473
+
"peer": true,
2474
+
"dependencies": {
2475
+
"@typescript-eslint/scope-manager": "8.49.0",
2476
+
"@typescript-eslint/types": "8.49.0",
2477
+
"@typescript-eslint/typescript-estree": "8.49.0",
2478
+
"@typescript-eslint/visitor-keys": "8.49.0",
2479
+
"debug": "^4.3.4"
2480
+
},
2481
+
"engines": {
2482
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2483
+
},
2484
+
"funding": {
2485
+
"type": "opencollective",
2486
+
"url": "https://opencollective.com/typescript-eslint"
2487
+
},
2488
+
"peerDependencies": {
2489
+
"eslint": "^8.57.0 || ^9.0.0",
2490
+
"typescript": ">=4.8.4 <6.0.0"
2491
+
}
2492
+
},
2493
+
"node_modules/@typescript-eslint/project-service": {
2494
+
"version": "8.49.0",
2495
+
"dev": true,
2496
+
"license": "MIT",
2497
+
"dependencies": {
2498
+
"@typescript-eslint/tsconfig-utils": "^8.49.0",
2499
+
"@typescript-eslint/types": "^8.49.0",
2500
+
"debug": "^4.3.4"
2501
+
},
2502
+
"engines": {
2503
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2504
+
},
2505
+
"funding": {
2506
+
"type": "opencollective",
2507
+
"url": "https://opencollective.com/typescript-eslint"
2508
+
},
2509
+
"peerDependencies": {
2510
+
"typescript": ">=4.8.4 <6.0.0"
2511
+
}
2512
+
},
2513
+
"node_modules/@typescript-eslint/scope-manager": {
2514
+
"version": "8.49.0",
2515
+
"dev": true,
2516
+
"license": "MIT",
2517
+
"dependencies": {
2518
+
"@typescript-eslint/types": "8.49.0",
2519
+
"@typescript-eslint/visitor-keys": "8.49.0"
2520
+
},
2521
+
"engines": {
2522
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2523
+
},
2524
+
"funding": {
2525
+
"type": "opencollective",
2526
+
"url": "https://opencollective.com/typescript-eslint"
2527
+
}
2528
+
},
2529
+
"node_modules/@typescript-eslint/tsconfig-utils": {
2530
+
"version": "8.49.0",
2531
+
"dev": true,
2532
+
"license": "MIT",
2533
+
"engines": {
2534
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2535
+
},
2536
+
"funding": {
2537
+
"type": "opencollective",
2538
+
"url": "https://opencollective.com/typescript-eslint"
2539
+
},
2540
+
"peerDependencies": {
2541
+
"typescript": ">=4.8.4 <6.0.0"
2542
+
}
2543
+
},
2544
+
"node_modules/@typescript-eslint/type-utils": {
2545
+
"version": "8.49.0",
2546
+
"dev": true,
2547
+
"license": "MIT",
2548
+
"dependencies": {
2549
+
"@typescript-eslint/types": "8.49.0",
2550
+
"@typescript-eslint/typescript-estree": "8.49.0",
2551
+
"@typescript-eslint/utils": "8.49.0",
2552
+
"debug": "^4.3.4",
2553
+
"ts-api-utils": "^2.1.0"
2554
+
},
2555
+
"engines": {
2556
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2557
+
},
2558
+
"funding": {
2559
+
"type": "opencollective",
2560
+
"url": "https://opencollective.com/typescript-eslint"
2561
+
},
2562
+
"peerDependencies": {
2563
+
"eslint": "^8.57.0 || ^9.0.0",
2564
+
"typescript": ">=4.8.4 <6.0.0"
2565
+
}
2566
+
},
2567
+
"node_modules/@typescript-eslint/types": {
2568
+
"version": "8.49.0",
2569
+
"dev": true,
2570
+
"license": "MIT",
2571
+
"engines": {
2572
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2573
+
},
2574
+
"funding": {
2575
+
"type": "opencollective",
2576
+
"url": "https://opencollective.com/typescript-eslint"
2577
+
}
2578
+
},
2579
+
"node_modules/@typescript-eslint/typescript-estree": {
2580
+
"version": "8.49.0",
2581
+
"dev": true,
2582
+
"license": "MIT",
2583
+
"dependencies": {
2584
+
"@typescript-eslint/project-service": "8.49.0",
2585
+
"@typescript-eslint/tsconfig-utils": "8.49.0",
2586
+
"@typescript-eslint/types": "8.49.0",
2587
+
"@typescript-eslint/visitor-keys": "8.49.0",
2588
+
"debug": "^4.3.4",
2589
+
"minimatch": "^9.0.4",
2590
+
"semver": "^7.6.0",
2591
+
"tinyglobby": "^0.2.15",
2592
+
"ts-api-utils": "^2.1.0"
2593
+
},
2594
+
"engines": {
2595
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2596
+
},
2597
+
"funding": {
2598
+
"type": "opencollective",
2599
+
"url": "https://opencollective.com/typescript-eslint"
2600
+
},
2601
+
"peerDependencies": {
2602
+
"typescript": ">=4.8.4 <6.0.0"
2603
+
}
2604
+
},
2605
+
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
2606
+
"version": "9.0.5",
2607
+
"dev": true,
2608
+
"license": "ISC",
2609
+
"dependencies": {
2610
+
"brace-expansion": "^2.0.1"
2611
+
},
2612
+
"engines": {
2613
+
"node": ">=16 || 14 >=14.17"
2614
+
},
2615
+
"funding": {
2616
+
"url": "https://github.com/sponsors/isaacs"
2617
+
}
2618
+
},
2619
+
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": {
2620
+
"version": "2.0.2",
2621
+
"dev": true,
2622
+
"license": "MIT",
2623
+
"dependencies": {
2624
+
"balanced-match": "^1.0.0"
2625
+
}
2626
+
},
2627
+
"node_modules/@typescript-eslint/utils": {
2628
+
"version": "8.49.0",
2629
+
"dev": true,
2630
+
"license": "MIT",
2631
+
"dependencies": {
2632
+
"@eslint-community/eslint-utils": "^4.7.0",
2633
+
"@typescript-eslint/scope-manager": "8.49.0",
2634
+
"@typescript-eslint/types": "8.49.0",
2635
+
"@typescript-eslint/typescript-estree": "8.49.0"
2636
+
},
2637
+
"engines": {
2638
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2639
+
},
2640
+
"funding": {
2641
+
"type": "opencollective",
2642
+
"url": "https://opencollective.com/typescript-eslint"
2643
+
},
2644
+
"peerDependencies": {
2645
+
"eslint": "^8.57.0 || ^9.0.0",
2646
+
"typescript": ">=4.8.4 <6.0.0"
2647
+
}
2648
+
},
2649
+
"node_modules/@typescript-eslint/visitor-keys": {
2650
+
"version": "8.49.0",
2651
+
"dev": true,
2652
+
"license": "MIT",
2653
+
"dependencies": {
2654
+
"@typescript-eslint/types": "8.49.0",
2655
+
"eslint-visitor-keys": "^4.2.1"
2656
+
},
2657
+
"engines": {
2658
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2659
+
},
2660
+
"funding": {
2661
+
"type": "opencollective",
2662
+
"url": "https://opencollective.com/typescript-eslint"
2663
+
}
2664
+
},
2665
+
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
2666
+
"version": "4.2.1",
2667
+
"dev": true,
2668
+
"license": "Apache-2.0",
2669
+
"engines": {
2670
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2671
+
},
2672
+
"funding": {
2673
+
"url": "https://opencollective.com/eslint"
2674
+
}
2675
+
},
2676
+
"node_modules/@vitest/expect": {
2677
+
"version": "4.0.15",
2678
+
"dev": true,
2679
+
"license": "MIT",
2680
+
"dependencies": {
2681
+
"@standard-schema/spec": "^1.0.0",
2682
+
"@types/chai": "^5.2.2",
2683
+
"@vitest/spy": "4.0.15",
2684
+
"@vitest/utils": "4.0.15",
2685
+
"chai": "^6.2.1",
2686
+
"tinyrainbow": "^3.0.3"
2687
+
},
2688
+
"funding": {
2689
+
"url": "https://opencollective.com/vitest"
2690
+
}
2691
+
},
2692
+
"node_modules/@vitest/mocker": {
2693
+
"version": "4.0.15",
2694
+
"dev": true,
2695
+
"license": "MIT",
2696
+
"dependencies": {
2697
+
"@vitest/spy": "4.0.15",
2698
+
"estree-walker": "^3.0.3",
2699
+
"magic-string": "^0.30.21"
2700
+
},
2701
+
"funding": {
2702
+
"url": "https://opencollective.com/vitest"
2703
+
},
2704
+
"peerDependencies": {
2705
+
"msw": "^2.4.9",
2706
+
"vite": "^6.0.0 || ^7.0.0-0"
2707
+
},
2708
+
"peerDependenciesMeta": {
2709
+
"msw": {
2710
+
"optional": true
2711
+
},
2712
+
"vite": {
2713
+
"optional": true
2714
+
}
2715
+
}
2716
+
},
2717
+
"node_modules/@vitest/pretty-format": {
2718
+
"version": "4.0.15",
2719
+
"dev": true,
2720
+
"license": "MIT",
2721
+
"dependencies": {
2722
+
"tinyrainbow": "^3.0.3"
2723
+
},
2724
+
"funding": {
2725
+
"url": "https://opencollective.com/vitest"
2726
+
}
2727
+
},
2728
+
"node_modules/@vitest/runner": {
2729
+
"version": "4.0.15",
2730
+
"dev": true,
2731
+
"license": "MIT",
2732
+
"dependencies": {
2733
+
"@vitest/utils": "4.0.15",
2734
+
"pathe": "^2.0.3"
2735
+
},
2736
+
"funding": {
2737
+
"url": "https://opencollective.com/vitest"
2738
+
}
2739
+
},
2740
+
"node_modules/@vitest/snapshot": {
2741
+
"version": "4.0.15",
2742
+
"dev": true,
2743
+
"license": "MIT",
2744
+
"dependencies": {
2745
+
"@vitest/pretty-format": "4.0.15",
2746
+
"magic-string": "^0.30.21",
2747
+
"pathe": "^2.0.3"
2748
+
},
2749
+
"funding": {
2750
+
"url": "https://opencollective.com/vitest"
2751
+
}
2752
+
},
2753
+
"node_modules/@vitest/spy": {
2754
+
"version": "4.0.15",
2755
+
"dev": true,
2756
+
"license": "MIT",
2757
+
"funding": {
2758
+
"url": "https://opencollective.com/vitest"
2759
+
}
2760
+
},
2761
+
"node_modules/@vitest/utils": {
2762
+
"version": "4.0.15",
2763
+
"dev": true,
2764
+
"license": "MIT",
2765
+
"dependencies": {
2766
+
"@vitest/pretty-format": "4.0.15",
2767
+
"tinyrainbow": "^3.0.3"
2768
+
},
2769
+
"funding": {
2770
+
"url": "https://opencollective.com/vitest"
2771
+
}
2772
+
},
2773
+
"node_modules/acorn": {
2774
+
"version": "8.15.0",
2775
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
2776
+
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
2777
+
"dev": true,
2778
+
"license": "MIT",
2779
+
"peer": true,
2780
+
"bin": {
2781
+
"acorn": "bin/acorn"
2782
+
},
2783
+
"engines": {
2784
+
"node": ">=0.4.0"
2785
+
}
2786
+
},
2787
+
"node_modules/acorn-jsx": {
2788
+
"version": "5.3.2",
2789
+
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
2790
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
2791
+
"dev": true,
2792
+
"license": "MIT",
2793
+
"peerDependencies": {
2794
+
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
2795
+
}
2796
+
},
2797
+
"node_modules/ajv": {
2798
+
"version": "6.12.6",
2799
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
2800
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
2801
+
"dev": true,
2802
+
"license": "MIT",
2803
+
"dependencies": {
2804
+
"fast-deep-equal": "^3.1.1",
2805
+
"fast-json-stable-stringify": "^2.0.0",
2806
+
"json-schema-traverse": "^0.4.1",
2807
+
"uri-js": "^4.2.2"
2808
+
},
2809
+
"funding": {
2810
+
"type": "github",
2811
+
"url": "https://github.com/sponsors/epoberezkin"
2812
+
}
2813
+
},
2814
+
"node_modules/ansi-styles": {
2815
+
"version": "4.3.0",
2816
+
"dev": true,
2817
+
"license": "MIT",
2818
+
"dependencies": {
2819
+
"color-convert": "^2.0.1"
2820
+
},
2821
+
"engines": {
2822
+
"node": ">=8"
2823
+
},
2824
+
"funding": {
2825
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
2826
+
}
2827
+
},
2828
+
"node_modules/argparse": {
2829
+
"version": "2.0.1",
2830
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
2831
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
2832
+
"dev": true,
2833
+
"license": "Python-2.0"
2834
+
},
2835
+
"node_modules/assertion-error": {
2836
+
"version": "2.0.1",
2837
+
"dev": true,
2838
+
"license": "MIT",
2839
+
"engines": {
2840
+
"node": ">=12"
2841
+
}
2842
+
},
2843
+
"node_modules/balanced-match": {
2844
+
"version": "1.0.2",
2845
+
"dev": true,
2846
+
"license": "MIT"
2847
+
},
2848
+
"node_modules/bowser": {
2849
+
"version": "2.13.1",
2850
+
"license": "MIT"
2851
+
},
2852
+
"node_modules/brace-expansion": {
2853
+
"version": "1.1.12",
2854
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
2855
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
2856
+
"dev": true,
2857
+
"license": "MIT",
2858
+
"dependencies": {
2859
+
"balanced-match": "^1.0.0",
2860
+
"concat-map": "0.0.1"
2861
+
}
2862
+
},
2863
+
"node_modules/bun-types": {
2864
+
"version": "1.3.4",
2865
+
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.4.tgz",
2866
+
"integrity": "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ==",
2867
+
"dev": true,
2868
+
"license": "MIT",
2869
+
"dependencies": {
2870
+
"@types/node": "*"
2871
+
}
2872
+
},
2873
+
"node_modules/callsites": {
2874
+
"version": "3.1.0",
2875
+
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
2876
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
2877
+
"dev": true,
2878
+
"license": "MIT",
2879
+
"engines": {
2880
+
"node": ">=6"
2881
+
}
2882
+
},
2883
+
"node_modules/chai": {
2884
+
"version": "6.2.1",
2885
+
"dev": true,
2886
+
"license": "MIT",
2887
+
"engines": {
2888
+
"node": ">=18"
2889
+
}
2890
+
},
2891
+
"node_modules/chalk": {
2892
+
"version": "4.1.2",
2893
+
"dev": true,
2894
+
"license": "MIT",
2895
+
"dependencies": {
2896
+
"ansi-styles": "^4.1.0",
2897
+
"supports-color": "^7.1.0"
2898
+
},
2899
+
"engines": {
2900
+
"node": ">=10"
2901
+
},
2902
+
"funding": {
2903
+
"url": "https://github.com/chalk/chalk?sponsor=1"
2904
+
}
2905
+
},
2906
+
"node_modules/color-convert": {
2907
+
"version": "2.0.1",
2908
+
"dev": true,
2909
+
"license": "MIT",
2910
+
"dependencies": {
2911
+
"color-name": "~1.1.4"
2912
+
},
2913
+
"engines": {
2914
+
"node": ">=7.0.0"
2915
+
}
2916
+
},
2917
+
"node_modules/color-name": {
2918
+
"version": "1.1.4",
2919
+
"dev": true,
2920
+
"license": "MIT"
2921
+
},
2922
+
"node_modules/concat-map": {
2923
+
"version": "0.0.1",
2924
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
2925
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
2926
+
"dev": true,
2927
+
"license": "MIT"
2928
+
},
2929
+
"node_modules/cross-spawn": {
2930
+
"version": "7.0.6",
2931
+
"dev": true,
2932
+
"license": "MIT",
2933
+
"dependencies": {
2934
+
"path-key": "^3.1.0",
2935
+
"shebang-command": "^2.0.0",
2936
+
"which": "^2.0.1"
2937
+
},
2938
+
"engines": {
2939
+
"node": ">= 8"
2940
+
}
2941
+
},
2942
+
"node_modules/debug": {
2943
+
"version": "4.4.3",
2944
+
"dev": true,
2945
+
"license": "MIT",
2946
+
"dependencies": {
2947
+
"ms": "^2.1.3"
2948
+
},
2949
+
"engines": {
2950
+
"node": ">=6.0"
2951
+
},
2952
+
"peerDependenciesMeta": {
2953
+
"supports-color": {
2954
+
"optional": true
2955
+
}
2956
+
}
2957
+
},
2958
+
"node_modules/deep-is": {
2959
+
"version": "0.1.4",
2960
+
"dev": true,
2961
+
"license": "MIT"
2962
+
},
2963
+
"node_modules/es-module-lexer": {
2964
+
"version": "1.7.0",
2965
+
"dev": true,
2966
+
"license": "MIT"
2967
+
},
2968
+
"node_modules/esbuild": {
2969
+
"version": "0.27.1",
2970
+
"dev": true,
2971
+
"hasInstallScript": true,
2972
+
"license": "MIT",
2973
+
"bin": {
2974
+
"esbuild": "bin/esbuild"
2975
+
},
2976
+
"engines": {
2977
+
"node": ">=18"
2978
+
},
2979
+
"optionalDependencies": {
2980
+
"@esbuild/aix-ppc64": "0.27.1",
2981
+
"@esbuild/android-arm": "0.27.1",
2982
+
"@esbuild/android-arm64": "0.27.1",
2983
+
"@esbuild/android-x64": "0.27.1",
2984
+
"@esbuild/darwin-arm64": "0.27.1",
2985
+
"@esbuild/darwin-x64": "0.27.1",
2986
+
"@esbuild/freebsd-arm64": "0.27.1",
2987
+
"@esbuild/freebsd-x64": "0.27.1",
2988
+
"@esbuild/linux-arm": "0.27.1",
2989
+
"@esbuild/linux-arm64": "0.27.1",
2990
+
"@esbuild/linux-ia32": "0.27.1",
2991
+
"@esbuild/linux-loong64": "0.27.1",
2992
+
"@esbuild/linux-mips64el": "0.27.1",
2993
+
"@esbuild/linux-ppc64": "0.27.1",
2994
+
"@esbuild/linux-riscv64": "0.27.1",
2995
+
"@esbuild/linux-s390x": "0.27.1",
2996
+
"@esbuild/linux-x64": "0.27.1",
2997
+
"@esbuild/netbsd-arm64": "0.27.1",
2998
+
"@esbuild/netbsd-x64": "0.27.1",
2999
+
"@esbuild/openbsd-arm64": "0.27.1",
3000
+
"@esbuild/openbsd-x64": "0.27.1",
3001
+
"@esbuild/openharmony-arm64": "0.27.1",
3002
+
"@esbuild/sunos-x64": "0.27.1",
3003
+
"@esbuild/win32-arm64": "0.27.1",
3004
+
"@esbuild/win32-ia32": "0.27.1",
3005
+
"@esbuild/win32-x64": "0.27.1"
3006
+
}
3007
+
},
3008
+
"node_modules/escape-string-regexp": {
3009
+
"version": "4.0.0",
3010
+
"dev": true,
3011
+
"license": "MIT",
3012
+
"engines": {
3013
+
"node": ">=10"
3014
+
},
3015
+
"funding": {
3016
+
"url": "https://github.com/sponsors/sindresorhus"
3017
+
}
3018
+
},
3019
+
"node_modules/eslint": {
3020
+
"version": "9.39.1",
3021
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
3022
+
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
3023
+
"dev": true,
3024
+
"license": "MIT",
3025
+
"peer": true,
3026
+
"dependencies": {
3027
+
"@eslint-community/eslint-utils": "^4.8.0",
3028
+
"@eslint-community/regexpp": "^4.12.1",
3029
+
"@eslint/config-array": "^0.21.1",
3030
+
"@eslint/config-helpers": "^0.4.2",
3031
+
"@eslint/core": "^0.17.0",
3032
+
"@eslint/eslintrc": "^3.3.1",
3033
+
"@eslint/js": "9.39.1",
3034
+
"@eslint/plugin-kit": "^0.4.1",
3035
+
"@humanfs/node": "^0.16.6",
3036
+
"@humanwhocodes/module-importer": "^1.0.1",
3037
+
"@humanwhocodes/retry": "^0.4.2",
3038
+
"@types/estree": "^1.0.6",
3039
+
"ajv": "^6.12.4",
3040
+
"chalk": "^4.0.0",
3041
+
"cross-spawn": "^7.0.6",
3042
+
"debug": "^4.3.2",
3043
+
"escape-string-regexp": "^4.0.0",
3044
+
"eslint-scope": "^8.4.0",
3045
+
"eslint-visitor-keys": "^4.2.1",
3046
+
"espree": "^10.4.0",
3047
+
"esquery": "^1.5.0",
3048
+
"esutils": "^2.0.2",
3049
+
"fast-deep-equal": "^3.1.3",
3050
+
"file-entry-cache": "^8.0.0",
3051
+
"find-up": "^5.0.0",
3052
+
"glob-parent": "^6.0.2",
3053
+
"ignore": "^5.2.0",
3054
+
"imurmurhash": "^0.1.4",
3055
+
"is-glob": "^4.0.0",
3056
+
"json-stable-stringify-without-jsonify": "^1.0.1",
3057
+
"lodash.merge": "^4.6.2",
3058
+
"minimatch": "^3.1.2",
3059
+
"natural-compare": "^1.4.0",
3060
+
"optionator": "^0.9.3"
3061
+
},
3062
+
"bin": {
3063
+
"eslint": "bin/eslint.js"
3064
+
},
3065
+
"engines": {
3066
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3067
+
},
3068
+
"funding": {
3069
+
"url": "https://eslint.org/donate"
3070
+
},
3071
+
"peerDependencies": {
3072
+
"jiti": "*"
3073
+
},
3074
+
"peerDependenciesMeta": {
3075
+
"jiti": {
3076
+
"optional": true
3077
+
}
3078
+
}
3079
+
},
3080
+
"node_modules/eslint-scope": {
3081
+
"version": "8.4.0",
3082
+
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
3083
+
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
3084
+
"dev": true,
3085
+
"license": "BSD-2-Clause",
3086
+
"dependencies": {
3087
+
"esrecurse": "^4.3.0",
3088
+
"estraverse": "^5.2.0"
3089
+
},
3090
+
"engines": {
3091
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3092
+
},
3093
+
"funding": {
3094
+
"url": "https://opencollective.com/eslint"
3095
+
}
3096
+
},
3097
+
"node_modules/eslint-visitor-keys": {
3098
+
"version": "3.4.3",
3099
+
"dev": true,
3100
+
"license": "Apache-2.0",
3101
+
"engines": {
3102
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
3103
+
},
3104
+
"funding": {
3105
+
"url": "https://opencollective.com/eslint"
3106
+
}
3107
+
},
3108
+
"node_modules/eslint/node_modules/eslint-visitor-keys": {
3109
+
"version": "4.2.1",
3110
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
3111
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
3112
+
"dev": true,
3113
+
"license": "Apache-2.0",
3114
+
"engines": {
3115
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3116
+
},
3117
+
"funding": {
3118
+
"url": "https://opencollective.com/eslint"
3119
+
}
3120
+
},
3121
+
"node_modules/eslint/node_modules/ignore": {
3122
+
"version": "5.3.2",
3123
+
"dev": true,
3124
+
"license": "MIT",
3125
+
"engines": {
3126
+
"node": ">= 4"
3127
+
}
3128
+
},
3129
+
"node_modules/espree": {
3130
+
"version": "10.4.0",
3131
+
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
3132
+
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
3133
+
"dev": true,
3134
+
"license": "BSD-2-Clause",
3135
+
"dependencies": {
3136
+
"acorn": "^8.15.0",
3137
+
"acorn-jsx": "^5.3.2",
3138
+
"eslint-visitor-keys": "^4.2.1"
3139
+
},
3140
+
"engines": {
3141
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3142
+
},
3143
+
"funding": {
3144
+
"url": "https://opencollective.com/eslint"
3145
+
}
3146
+
},
3147
+
"node_modules/espree/node_modules/eslint-visitor-keys": {
3148
+
"version": "4.2.1",
3149
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
3150
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
3151
+
"dev": true,
3152
+
"license": "Apache-2.0",
3153
+
"engines": {
3154
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3155
+
},
3156
+
"funding": {
3157
+
"url": "https://opencollective.com/eslint"
3158
+
}
3159
+
},
3160
+
"node_modules/esquery": {
3161
+
"version": "1.6.0",
3162
+
"dev": true,
3163
+
"license": "BSD-3-Clause",
3164
+
"dependencies": {
3165
+
"estraverse": "^5.1.0"
3166
+
},
3167
+
"engines": {
3168
+
"node": ">=0.10"
3169
+
}
3170
+
},
3171
+
"node_modules/esrecurse": {
3172
+
"version": "4.3.0",
3173
+
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
3174
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
3175
+
"dev": true,
3176
+
"license": "BSD-2-Clause",
3177
+
"dependencies": {
3178
+
"estraverse": "^5.2.0"
3179
+
},
3180
+
"engines": {
3181
+
"node": ">=4.0"
3182
+
}
3183
+
},
3184
+
"node_modules/estraverse": {
3185
+
"version": "5.3.0",
3186
+
"dev": true,
3187
+
"license": "BSD-2-Clause",
3188
+
"engines": {
3189
+
"node": ">=4.0"
3190
+
}
3191
+
},
3192
+
"node_modules/estree-walker": {
3193
+
"version": "3.0.3",
3194
+
"dev": true,
3195
+
"license": "MIT",
3196
+
"dependencies": {
3197
+
"@types/estree": "^1.0.0"
3198
+
}
3199
+
},
3200
+
"node_modules/esutils": {
3201
+
"version": "2.0.3",
3202
+
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
3203
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
3204
+
"dev": true,
3205
+
"license": "BSD-2-Clause",
3206
+
"engines": {
3207
+
"node": ">=0.10.0"
3208
+
}
3209
+
},
3210
+
"node_modules/expect-type": {
3211
+
"version": "1.3.0",
3212
+
"dev": true,
3213
+
"license": "Apache-2.0",
3214
+
"engines": {
3215
+
"node": ">=12.0.0"
3216
+
}
3217
+
},
3218
+
"node_modules/fast-deep-equal": {
3219
+
"version": "3.1.3",
3220
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
3221
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
3222
+
"dev": true,
3223
+
"license": "MIT"
3224
+
},
3225
+
"node_modules/fast-json-stable-stringify": {
3226
+
"version": "2.1.0",
3227
+
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
3228
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
3229
+
"dev": true,
3230
+
"license": "MIT"
3231
+
},
3232
+
"node_modules/fast-levenshtein": {
3233
+
"version": "2.0.6",
3234
+
"dev": true,
3235
+
"license": "MIT"
3236
+
},
3237
+
"node_modules/fast-xml-parser": {
3238
+
"version": "5.2.5",
3239
+
"funding": [
3240
+
{
3241
+
"type": "github",
3242
+
"url": "https://github.com/sponsors/NaturalIntelligence"
3243
+
}
3244
+
],
3245
+
"license": "MIT",
3246
+
"dependencies": {
3247
+
"strnum": "^2.1.0"
3248
+
},
3249
+
"bin": {
3250
+
"fxparser": "src/cli/cli.js"
3251
+
}
3252
+
},
3253
+
"node_modules/fdir": {
3254
+
"version": "6.5.0",
3255
+
"dev": true,
3256
+
"license": "MIT",
3257
+
"engines": {
3258
+
"node": ">=12.0.0"
3259
+
},
3260
+
"peerDependencies": {
3261
+
"picomatch": "^3 || ^4"
3262
+
},
3263
+
"peerDependenciesMeta": {
3264
+
"picomatch": {
3265
+
"optional": true
3266
+
}
3267
+
}
3268
+
},
3269
+
"node_modules/file-entry-cache": {
3270
+
"version": "8.0.0",
3271
+
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
3272
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
3273
+
"dev": true,
3274
+
"license": "MIT",
3275
+
"dependencies": {
3276
+
"flat-cache": "^4.0.0"
3277
+
},
3278
+
"engines": {
3279
+
"node": ">=16.0.0"
3280
+
}
3281
+
},
3282
+
"node_modules/find-up": {
3283
+
"version": "5.0.0",
3284
+
"dev": true,
3285
+
"license": "MIT",
3286
+
"dependencies": {
3287
+
"locate-path": "^6.0.0",
3288
+
"path-exists": "^4.0.0"
3289
+
},
3290
+
"engines": {
3291
+
"node": ">=10"
3292
+
},
3293
+
"funding": {
3294
+
"url": "https://github.com/sponsors/sindresorhus"
3295
+
}
3296
+
},
3297
+
"node_modules/flat-cache": {
3298
+
"version": "4.0.1",
3299
+
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
3300
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
3301
+
"dev": true,
3302
+
"license": "MIT",
3303
+
"dependencies": {
3304
+
"flatted": "^3.2.9",
3305
+
"keyv": "^4.5.4"
3306
+
},
3307
+
"engines": {
3308
+
"node": ">=16"
3309
+
}
3310
+
},
3311
+
"node_modules/flatted": {
3312
+
"version": "3.3.3",
3313
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
3314
+
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
3315
+
"dev": true,
3316
+
"license": "ISC"
3317
+
},
3318
+
"node_modules/fsevents": {
3319
+
"version": "2.3.3",
3320
+
"dev": true,
3321
+
"license": "MIT",
3322
+
"optional": true,
3323
+
"os": [
3324
+
"darwin"
3325
+
],
3326
+
"engines": {
3327
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
3328
+
}
3329
+
},
3330
+
"node_modules/get-tsconfig": {
3331
+
"version": "4.13.0",
3332
+
"dev": true,
3333
+
"license": "MIT",
3334
+
"dependencies": {
3335
+
"resolve-pkg-maps": "^1.0.0"
3336
+
},
3337
+
"funding": {
3338
+
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
3339
+
}
3340
+
},
3341
+
"node_modules/glob-parent": {
3342
+
"version": "6.0.2",
3343
+
"dev": true,
3344
+
"license": "ISC",
3345
+
"dependencies": {
3346
+
"is-glob": "^4.0.3"
3347
+
},
3348
+
"engines": {
3349
+
"node": ">=10.13.0"
3350
+
}
3351
+
},
3352
+
"node_modules/globals": {
3353
+
"version": "14.0.0",
3354
+
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
3355
+
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
3356
+
"dev": true,
3357
+
"license": "MIT",
3358
+
"engines": {
3359
+
"node": ">=18"
3360
+
},
3361
+
"funding": {
3362
+
"url": "https://github.com/sponsors/sindresorhus"
3363
+
}
3364
+
},
3365
+
"node_modules/has-flag": {
3366
+
"version": "4.0.0",
3367
+
"dev": true,
3368
+
"license": "MIT",
3369
+
"engines": {
3370
+
"node": ">=8"
3371
+
}
3372
+
},
3373
+
"node_modules/hono": {
3374
+
"version": "4.10.7",
3375
+
"license": "MIT",
3376
+
"engines": {
3377
+
"node": ">=16.9.0"
3378
+
}
3379
+
},
3380
+
"node_modules/ignore": {
3381
+
"version": "7.0.5",
3382
+
"dev": true,
3383
+
"license": "MIT",
3384
+
"engines": {
3385
+
"node": ">= 4"
3386
+
}
3387
+
},
3388
+
"node_modules/import-fresh": {
3389
+
"version": "3.3.1",
3390
+
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
3391
+
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
3392
+
"dev": true,
3393
+
"license": "MIT",
3394
+
"dependencies": {
3395
+
"parent-module": "^1.0.0",
3396
+
"resolve-from": "^4.0.0"
3397
+
},
3398
+
"engines": {
3399
+
"node": ">=6"
3400
+
},
3401
+
"funding": {
3402
+
"url": "https://github.com/sponsors/sindresorhus"
3403
+
}
3404
+
},
3405
+
"node_modules/imurmurhash": {
3406
+
"version": "0.1.4",
3407
+
"dev": true,
3408
+
"license": "MIT",
3409
+
"engines": {
3410
+
"node": ">=0.8.19"
3411
+
}
3412
+
},
3413
+
"node_modules/is-extglob": {
3414
+
"version": "2.1.1",
3415
+
"dev": true,
3416
+
"license": "MIT",
3417
+
"engines": {
3418
+
"node": ">=0.10.0"
3419
+
}
3420
+
},
3421
+
"node_modules/is-glob": {
3422
+
"version": "4.0.3",
3423
+
"dev": true,
3424
+
"license": "MIT",
3425
+
"dependencies": {
3426
+
"is-extglob": "^2.1.1"
3427
+
},
3428
+
"engines": {
3429
+
"node": ">=0.10.0"
3430
+
}
3431
+
},
3432
+
"node_modules/isexe": {
3433
+
"version": "2.0.0",
3434
+
"dev": true,
3435
+
"license": "ISC"
3436
+
},
3437
+
"node_modules/js-yaml": {
3438
+
"version": "4.1.1",
3439
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
3440
+
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
3441
+
"dev": true,
3442
+
"license": "MIT",
3443
+
"dependencies": {
3444
+
"argparse": "^2.0.1"
3445
+
},
3446
+
"bin": {
3447
+
"js-yaml": "bin/js-yaml.js"
3448
+
}
3449
+
},
3450
+
"node_modules/json-buffer": {
3451
+
"version": "3.0.1",
3452
+
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
3453
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
3454
+
"dev": true,
3455
+
"license": "MIT"
3456
+
},
3457
+
"node_modules/json-schema-traverse": {
3458
+
"version": "0.4.1",
3459
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
3460
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
3461
+
"dev": true,
3462
+
"license": "MIT"
3463
+
},
3464
+
"node_modules/json-stable-stringify-without-jsonify": {
3465
+
"version": "1.0.1",
3466
+
"dev": true,
3467
+
"license": "MIT"
3468
+
},
3469
+
"node_modules/keyv": {
3470
+
"version": "4.5.4",
3471
+
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
3472
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
3473
+
"dev": true,
3474
+
"license": "MIT",
3475
+
"dependencies": {
3476
+
"json-buffer": "3.0.1"
3477
+
}
3478
+
},
3479
+
"node_modules/levn": {
3480
+
"version": "0.4.1",
3481
+
"dev": true,
3482
+
"license": "MIT",
3483
+
"dependencies": {
3484
+
"prelude-ls": "^1.2.1",
3485
+
"type-check": "~0.4.0"
3486
+
},
3487
+
"engines": {
3488
+
"node": ">= 0.8.0"
3489
+
}
3490
+
},
3491
+
"node_modules/locate-path": {
3492
+
"version": "6.0.0",
3493
+
"dev": true,
3494
+
"license": "MIT",
3495
+
"dependencies": {
3496
+
"p-locate": "^5.0.0"
3497
+
},
3498
+
"engines": {
3499
+
"node": ">=10"
3500
+
},
3501
+
"funding": {
3502
+
"url": "https://github.com/sponsors/sindresorhus"
3503
+
}
3504
+
},
3505
+
"node_modules/lodash.merge": {
3506
+
"version": "4.6.2",
3507
+
"dev": true,
3508
+
"license": "MIT"
3509
+
},
3510
+
"node_modules/magic-string": {
3511
+
"version": "0.30.21",
3512
+
"dev": true,
3513
+
"license": "MIT",
3514
+
"dependencies": {
3515
+
"@jridgewell/sourcemap-codec": "^1.5.5"
3516
+
}
3517
+
},
3518
+
"node_modules/mime-db": {
3519
+
"version": "1.54.0",
3520
+
"license": "MIT",
3521
+
"engines": {
3522
+
"node": ">= 0.6"
3523
+
}
3524
+
},
3525
+
"node_modules/mime-types": {
3526
+
"version": "3.0.2",
3527
+
"license": "MIT",
3528
+
"dependencies": {
3529
+
"mime-db": "^1.54.0"
3530
+
},
3531
+
"engines": {
3532
+
"node": ">=18"
3533
+
},
3534
+
"funding": {
3535
+
"type": "opencollective",
3536
+
"url": "https://opencollective.com/express"
3537
+
}
3538
+
},
3539
+
"node_modules/minimatch": {
3540
+
"version": "3.1.2",
3541
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
3542
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
3543
+
"dev": true,
3544
+
"license": "ISC",
3545
+
"dependencies": {
3546
+
"brace-expansion": "^1.1.7"
3547
+
},
3548
+
"engines": {
3549
+
"node": "*"
3550
+
}
3551
+
},
3552
+
"node_modules/ms": {
3553
+
"version": "2.1.3",
3554
+
"dev": true,
3555
+
"license": "MIT"
3556
+
},
3557
+
"node_modules/nanoid": {
3558
+
"version": "3.3.11",
3559
+
"dev": true,
3560
+
"funding": [
3561
+
{
3562
+
"type": "github",
3563
+
"url": "https://github.com/sponsors/ai"
3564
+
}
3565
+
],
3566
+
"license": "MIT",
3567
+
"bin": {
3568
+
"nanoid": "bin/nanoid.cjs"
3569
+
},
3570
+
"engines": {
3571
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
3572
+
}
3573
+
},
3574
+
"node_modules/natural-compare": {
3575
+
"version": "1.4.0",
3576
+
"dev": true,
3577
+
"license": "MIT"
3578
+
},
3579
+
"node_modules/obug": {
3580
+
"version": "2.1.1",
3581
+
"dev": true,
3582
+
"funding": [
3583
+
"https://github.com/sponsors/sxzz",
3584
+
"https://opencollective.com/debug"
3585
+
],
3586
+
"license": "MIT"
3587
+
},
3588
+
"node_modules/optionator": {
3589
+
"version": "0.9.4",
3590
+
"dev": true,
3591
+
"license": "MIT",
3592
+
"dependencies": {
3593
+
"deep-is": "^0.1.3",
3594
+
"fast-levenshtein": "^2.0.6",
3595
+
"levn": "^0.4.1",
3596
+
"prelude-ls": "^1.2.1",
3597
+
"type-check": "^0.4.0",
3598
+
"word-wrap": "^1.2.5"
3599
+
},
3600
+
"engines": {
3601
+
"node": ">= 0.8.0"
3602
+
}
3603
+
},
3604
+
"node_modules/p-limit": {
3605
+
"version": "3.1.0",
3606
+
"dev": true,
3607
+
"license": "MIT",
3608
+
"dependencies": {
3609
+
"yocto-queue": "^0.1.0"
3610
+
},
3611
+
"engines": {
3612
+
"node": ">=10"
3613
+
},
3614
+
"funding": {
3615
+
"url": "https://github.com/sponsors/sindresorhus"
3616
+
}
3617
+
},
3618
+
"node_modules/p-locate": {
3619
+
"version": "5.0.0",
3620
+
"dev": true,
3621
+
"license": "MIT",
3622
+
"dependencies": {
3623
+
"p-limit": "^3.0.2"
3624
+
},
3625
+
"engines": {
3626
+
"node": ">=10"
3627
+
},
3628
+
"funding": {
3629
+
"url": "https://github.com/sponsors/sindresorhus"
3630
+
}
3631
+
},
3632
+
"node_modules/parent-module": {
3633
+
"version": "1.0.1",
3634
+
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
3635
+
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
3636
+
"dev": true,
3637
+
"license": "MIT",
3638
+
"dependencies": {
3639
+
"callsites": "^3.0.0"
3640
+
},
3641
+
"engines": {
3642
+
"node": ">=6"
3643
+
}
3644
+
},
3645
+
"node_modules/path-exists": {
3646
+
"version": "4.0.0",
3647
+
"dev": true,
3648
+
"license": "MIT",
3649
+
"engines": {
3650
+
"node": ">=8"
3651
+
}
3652
+
},
3653
+
"node_modules/path-key": {
3654
+
"version": "3.1.1",
3655
+
"dev": true,
3656
+
"license": "MIT",
3657
+
"engines": {
3658
+
"node": ">=8"
3659
+
}
3660
+
},
3661
+
"node_modules/pathe": {
3662
+
"version": "2.0.3",
3663
+
"dev": true,
3664
+
"license": "MIT"
3665
+
},
3666
+
"node_modules/picocolors": {
3667
+
"version": "1.1.1",
3668
+
"dev": true,
3669
+
"license": "ISC"
3670
+
},
3671
+
"node_modules/picomatch": {
3672
+
"version": "4.0.3",
3673
+
"dev": true,
3674
+
"license": "MIT",
3675
+
"peer": true,
3676
+
"engines": {
3677
+
"node": ">=12"
3678
+
},
3679
+
"funding": {
3680
+
"url": "https://github.com/sponsors/jonschlinkert"
3681
+
}
3682
+
},
3683
+
"node_modules/postcss": {
3684
+
"version": "8.5.6",
3685
+
"dev": true,
3686
+
"funding": [
3687
+
{
3688
+
"type": "opencollective",
3689
+
"url": "https://opencollective.com/postcss/"
3690
+
},
3691
+
{
3692
+
"type": "tidelift",
3693
+
"url": "https://tidelift.com/funding/github/npm/postcss"
3694
+
},
3695
+
{
3696
+
"type": "github",
3697
+
"url": "https://github.com/sponsors/ai"
3698
+
}
3699
+
],
3700
+
"license": "MIT",
3701
+
"dependencies": {
3702
+
"nanoid": "^3.3.11",
3703
+
"picocolors": "^1.1.1",
3704
+
"source-map-js": "^1.2.1"
3705
+
},
3706
+
"engines": {
3707
+
"node": "^10 || ^12 || >=14"
3708
+
}
3709
+
},
3710
+
"node_modules/prelude-ls": {
3711
+
"version": "1.2.1",
3712
+
"dev": true,
3713
+
"license": "MIT",
3714
+
"engines": {
3715
+
"node": ">= 0.8.0"
3716
+
}
3717
+
},
3718
+
"node_modules/punycode": {
3719
+
"version": "2.3.1",
3720
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
3721
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
3722
+
"dev": true,
3723
+
"license": "MIT",
3724
+
"engines": {
3725
+
"node": ">=6"
3726
+
}
3727
+
},
3728
+
"node_modules/resolve-from": {
3729
+
"version": "4.0.0",
3730
+
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
3731
+
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
3732
+
"dev": true,
3733
+
"license": "MIT",
3734
+
"engines": {
3735
+
"node": ">=4"
3736
+
}
3737
+
},
3738
+
"node_modules/resolve-pkg-maps": {
3739
+
"version": "1.0.0",
3740
+
"dev": true,
3741
+
"license": "MIT",
3742
+
"funding": {
3743
+
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
3744
+
}
3745
+
},
3746
+
"node_modules/rollup": {
3747
+
"version": "4.53.3",
3748
+
"dev": true,
3749
+
"license": "MIT",
3750
+
"dependencies": {
3751
+
"@types/estree": "1.0.8"
3752
+
},
3753
+
"bin": {
3754
+
"rollup": "dist/bin/rollup"
3755
+
},
3756
+
"engines": {
3757
+
"node": ">=18.0.0",
3758
+
"npm": ">=8.0.0"
3759
+
},
3760
+
"optionalDependencies": {
3761
+
"@rollup/rollup-android-arm-eabi": "4.53.3",
3762
+
"@rollup/rollup-android-arm64": "4.53.3",
3763
+
"@rollup/rollup-darwin-arm64": "4.53.3",
3764
+
"@rollup/rollup-darwin-x64": "4.53.3",
3765
+
"@rollup/rollup-freebsd-arm64": "4.53.3",
3766
+
"@rollup/rollup-freebsd-x64": "4.53.3",
3767
+
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
3768
+
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
3769
+
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
3770
+
"@rollup/rollup-linux-arm64-musl": "4.53.3",
3771
+
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
3772
+
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
3773
+
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
3774
+
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
3775
+
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
3776
+
"@rollup/rollup-linux-x64-gnu": "4.53.3",
3777
+
"@rollup/rollup-linux-x64-musl": "4.53.3",
3778
+
"@rollup/rollup-openharmony-arm64": "4.53.3",
3779
+
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
3780
+
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
3781
+
"@rollup/rollup-win32-x64-gnu": "4.53.3",
3782
+
"@rollup/rollup-win32-x64-msvc": "4.53.3",
3783
+
"fsevents": "~2.3.2"
3784
+
}
3785
+
},
3786
+
"node_modules/semver": {
3787
+
"version": "7.7.3",
3788
+
"dev": true,
3789
+
"license": "ISC",
3790
+
"bin": {
3791
+
"semver": "bin/semver.js"
3792
+
},
3793
+
"engines": {
3794
+
"node": ">=10"
3795
+
}
3796
+
},
3797
+
"node_modules/shebang-command": {
3798
+
"version": "2.0.0",
3799
+
"dev": true,
3800
+
"license": "MIT",
3801
+
"dependencies": {
3802
+
"shebang-regex": "^3.0.0"
3803
+
},
3804
+
"engines": {
3805
+
"node": ">=8"
3806
+
}
3807
+
},
3808
+
"node_modules/shebang-regex": {
3809
+
"version": "3.0.0",
3810
+
"dev": true,
3811
+
"license": "MIT",
3812
+
"engines": {
3813
+
"node": ">=8"
3814
+
}
3815
+
},
3816
+
"node_modules/siginfo": {
3817
+
"version": "2.0.0",
3818
+
"dev": true,
3819
+
"license": "ISC"
3820
+
},
3821
+
"node_modules/source-map-js": {
3822
+
"version": "1.2.1",
3823
+
"dev": true,
3824
+
"license": "BSD-3-Clause",
3825
+
"engines": {
3826
+
"node": ">=0.10.0"
3827
+
}
3828
+
},
3829
+
"node_modules/stackback": {
3830
+
"version": "0.0.2",
3831
+
"dev": true,
3832
+
"license": "MIT"
3833
+
},
3834
+
"node_modules/std-env": {
3835
+
"version": "3.10.0",
3836
+
"dev": true,
3837
+
"license": "MIT"
3838
+
},
3839
+
"node_modules/strip-json-comments": {
3840
+
"version": "3.1.1",
3841
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
3842
+
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
3843
+
"dev": true,
3844
+
"license": "MIT",
3845
+
"engines": {
3846
+
"node": ">=8"
3847
+
},
3848
+
"funding": {
3849
+
"url": "https://github.com/sponsors/sindresorhus"
3850
+
}
3851
+
},
3852
+
"node_modules/strnum": {
3853
+
"version": "2.1.1",
3854
+
"funding": [
3855
+
{
3856
+
"type": "github",
3857
+
"url": "https://github.com/sponsors/NaturalIntelligence"
3858
+
}
3859
+
],
3860
+
"license": "MIT"
3861
+
},
3862
+
"node_modules/supports-color": {
3863
+
"version": "7.2.0",
3864
+
"dev": true,
3865
+
"license": "MIT",
3866
+
"dependencies": {
3867
+
"has-flag": "^4.0.0"
3868
+
},
3869
+
"engines": {
3870
+
"node": ">=8"
3871
+
}
3872
+
},
3873
+
"node_modules/tiny-lru": {
3874
+
"version": "11.4.5",
3875
+
"license": "BSD-3-Clause",
3876
+
"engines": {
3877
+
"node": ">=12"
3878
+
}
3879
+
},
3880
+
"node_modules/tinybench": {
3881
+
"version": "2.9.0",
3882
+
"dev": true,
3883
+
"license": "MIT"
3884
+
},
3885
+
"node_modules/tinyexec": {
3886
+
"version": "1.0.2",
3887
+
"dev": true,
3888
+
"license": "MIT",
3889
+
"engines": {
3890
+
"node": ">=18"
3891
+
}
3892
+
},
3893
+
"node_modules/tinyglobby": {
3894
+
"version": "0.2.15",
3895
+
"dev": true,
3896
+
"license": "MIT",
3897
+
"dependencies": {
3898
+
"fdir": "^6.5.0",
3899
+
"picomatch": "^4.0.3"
3900
+
},
3901
+
"engines": {
3902
+
"node": ">=12.0.0"
3903
+
},
3904
+
"funding": {
3905
+
"url": "https://github.com/sponsors/SuperchupuDev"
3906
+
}
3907
+
},
3908
+
"node_modules/tinyrainbow": {
3909
+
"version": "3.0.3",
3910
+
"dev": true,
3911
+
"license": "MIT",
3912
+
"engines": {
3913
+
"node": ">=14.0.0"
3914
+
}
3915
+
},
3916
+
"node_modules/ts-api-utils": {
3917
+
"version": "2.1.0",
3918
+
"dev": true,
3919
+
"license": "MIT",
3920
+
"engines": {
3921
+
"node": ">=18.12"
3922
+
},
3923
+
"peerDependencies": {
3924
+
"typescript": ">=4.8.4"
3925
+
}
3926
+
},
3927
+
"node_modules/tslib": {
3928
+
"version": "2.8.1",
3929
+
"license": "0BSD"
3930
+
},
3931
+
"node_modules/tsx": {
3932
+
"version": "4.21.0",
3933
+
"dev": true,
3934
+
"license": "MIT",
3935
+
"peer": true,
3936
+
"dependencies": {
3937
+
"esbuild": "~0.27.0",
3938
+
"get-tsconfig": "^4.7.5"
3939
+
},
3940
+
"bin": {
3941
+
"tsx": "dist/cli.mjs"
3942
+
},
3943
+
"engines": {
3944
+
"node": ">=18.0.0"
3945
+
},
3946
+
"optionalDependencies": {
3947
+
"fsevents": "~2.3.3"
3948
+
}
3949
+
},
3950
+
"node_modules/type-check": {
3951
+
"version": "0.4.0",
3952
+
"dev": true,
3953
+
"license": "MIT",
3954
+
"dependencies": {
3955
+
"prelude-ls": "^1.2.1"
3956
+
},
3957
+
"engines": {
3958
+
"node": ">= 0.8.0"
3959
+
}
3960
+
},
3961
+
"node_modules/typescript": {
3962
+
"version": "5.9.3",
3963
+
"dev": true,
3964
+
"license": "Apache-2.0",
3965
+
"peer": true,
3966
+
"bin": {
3967
+
"tsc": "bin/tsc",
3968
+
"tsserver": "bin/tsserver"
3969
+
},
3970
+
"engines": {
3971
+
"node": ">=14.17"
3972
+
}
3973
+
},
3974
+
"node_modules/undici-types": {
3975
+
"version": "7.16.0",
3976
+
"dev": true,
3977
+
"license": "MIT"
3978
+
},
3979
+
"node_modules/uri-js": {
3980
+
"version": "4.4.1",
3981
+
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
3982
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
3983
+
"dev": true,
3984
+
"license": "BSD-2-Clause",
3985
+
"dependencies": {
3986
+
"punycode": "^2.1.0"
3987
+
}
3988
+
},
3989
+
"node_modules/vite": {
3990
+
"version": "7.2.7",
3991
+
"dev": true,
3992
+
"license": "MIT",
3993
+
"peer": true,
3994
+
"dependencies": {
3995
+
"esbuild": "^0.25.0",
3996
+
"fdir": "^6.5.0",
3997
+
"picomatch": "^4.0.3",
3998
+
"postcss": "^8.5.6",
3999
+
"rollup": "^4.43.0",
4000
+
"tinyglobby": "^0.2.15"
4001
+
},
4002
+
"bin": {
4003
+
"vite": "bin/vite.js"
4004
+
},
4005
+
"engines": {
4006
+
"node": "^20.19.0 || >=22.12.0"
4007
+
},
4008
+
"funding": {
4009
+
"url": "https://github.com/vitejs/vite?sponsor=1"
4010
+
},
4011
+
"optionalDependencies": {
4012
+
"fsevents": "~2.3.3"
4013
+
},
4014
+
"peerDependencies": {
4015
+
"@types/node": "^20.19.0 || >=22.12.0",
4016
+
"jiti": ">=1.21.0",
4017
+
"less": "^4.0.0",
4018
+
"lightningcss": "^1.21.0",
4019
+
"sass": "^1.70.0",
4020
+
"sass-embedded": "^1.70.0",
4021
+
"stylus": ">=0.54.8",
4022
+
"sugarss": "^5.0.0",
4023
+
"terser": "^5.16.0",
4024
+
"tsx": "^4.8.1",
4025
+
"yaml": "^2.4.2"
4026
+
},
4027
+
"peerDependenciesMeta": {
4028
+
"@types/node": {
4029
+
"optional": true
4030
+
},
4031
+
"jiti": {
4032
+
"optional": true
4033
+
},
4034
+
"less": {
4035
+
"optional": true
4036
+
},
4037
+
"lightningcss": {
4038
+
"optional": true
4039
+
},
4040
+
"sass": {
4041
+
"optional": true
4042
+
},
4043
+
"sass-embedded": {
4044
+
"optional": true
4045
+
},
4046
+
"stylus": {
4047
+
"optional": true
4048
+
},
4049
+
"sugarss": {
4050
+
"optional": true
4051
+
},
4052
+
"terser": {
4053
+
"optional": true
4054
+
},
4055
+
"tsx": {
4056
+
"optional": true
4057
+
},
4058
+
"yaml": {
4059
+
"optional": true
4060
+
}
4061
+
}
4062
+
},
4063
+
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
4064
+
"version": "0.25.12",
4065
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
4066
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
4067
+
"cpu": [
4068
+
"ppc64"
4069
+
],
4070
+
"dev": true,
4071
+
"license": "MIT",
4072
+
"optional": true,
4073
+
"os": [
4074
+
"aix"
4075
+
],
4076
+
"engines": {
4077
+
"node": ">=18"
4078
+
}
4079
+
},
4080
+
"node_modules/vite/node_modules/@esbuild/android-arm": {
4081
+
"version": "0.25.12",
4082
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
4083
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
4084
+
"cpu": [
4085
+
"arm"
4086
+
],
4087
+
"dev": true,
4088
+
"license": "MIT",
4089
+
"optional": true,
4090
+
"os": [
4091
+
"android"
4092
+
],
4093
+
"engines": {
4094
+
"node": ">=18"
4095
+
}
4096
+
},
4097
+
"node_modules/vite/node_modules/@esbuild/android-arm64": {
4098
+
"version": "0.25.12",
4099
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
4100
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
4101
+
"cpu": [
4102
+
"arm64"
4103
+
],
4104
+
"dev": true,
4105
+
"license": "MIT",
4106
+
"optional": true,
4107
+
"os": [
4108
+
"android"
4109
+
],
4110
+
"engines": {
4111
+
"node": ">=18"
4112
+
}
4113
+
},
4114
+
"node_modules/vite/node_modules/@esbuild/android-x64": {
4115
+
"version": "0.25.12",
4116
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
4117
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
4118
+
"cpu": [
4119
+
"x64"
4120
+
],
4121
+
"dev": true,
4122
+
"license": "MIT",
4123
+
"optional": true,
4124
+
"os": [
4125
+
"android"
4126
+
],
4127
+
"engines": {
4128
+
"node": ">=18"
4129
+
}
4130
+
},
4131
+
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
4132
+
"version": "0.25.12",
4133
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
4134
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
4135
+
"cpu": [
4136
+
"x64"
4137
+
],
4138
+
"dev": true,
4139
+
"license": "MIT",
4140
+
"optional": true,
4141
+
"os": [
4142
+
"darwin"
4143
+
],
4144
+
"engines": {
4145
+
"node": ">=18"
4146
+
}
4147
+
},
4148
+
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
4149
+
"version": "0.25.12",
4150
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
4151
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
4152
+
"cpu": [
4153
+
"arm64"
4154
+
],
4155
+
"dev": true,
4156
+
"license": "MIT",
4157
+
"optional": true,
4158
+
"os": [
4159
+
"freebsd"
4160
+
],
4161
+
"engines": {
4162
+
"node": ">=18"
4163
+
}
4164
+
},
4165
+
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
4166
+
"version": "0.25.12",
4167
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
4168
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
4169
+
"cpu": [
4170
+
"x64"
4171
+
],
4172
+
"dev": true,
4173
+
"license": "MIT",
4174
+
"optional": true,
4175
+
"os": [
4176
+
"freebsd"
4177
+
],
4178
+
"engines": {
4179
+
"node": ">=18"
4180
+
}
4181
+
},
4182
+
"node_modules/vite/node_modules/@esbuild/linux-arm": {
4183
+
"version": "0.25.12",
4184
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
4185
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
4186
+
"cpu": [
4187
+
"arm"
4188
+
],
4189
+
"dev": true,
4190
+
"license": "MIT",
4191
+
"optional": true,
4192
+
"os": [
4193
+
"linux"
4194
+
],
4195
+
"engines": {
4196
+
"node": ">=18"
4197
+
}
4198
+
},
4199
+
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
4200
+
"version": "0.25.12",
4201
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
4202
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
4203
+
"cpu": [
4204
+
"arm64"
4205
+
],
4206
+
"dev": true,
4207
+
"license": "MIT",
4208
+
"optional": true,
4209
+
"os": [
4210
+
"linux"
4211
+
],
4212
+
"engines": {
4213
+
"node": ">=18"
4214
+
}
4215
+
},
4216
+
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
4217
+
"version": "0.25.12",
4218
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
4219
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
4220
+
"cpu": [
4221
+
"ia32"
4222
+
],
4223
+
"dev": true,
4224
+
"license": "MIT",
4225
+
"optional": true,
4226
+
"os": [
4227
+
"linux"
4228
+
],
4229
+
"engines": {
4230
+
"node": ">=18"
4231
+
}
4232
+
},
4233
+
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
4234
+
"version": "0.25.12",
4235
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
4236
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
4237
+
"cpu": [
4238
+
"loong64"
4239
+
],
4240
+
"dev": true,
4241
+
"license": "MIT",
4242
+
"optional": true,
4243
+
"os": [
4244
+
"linux"
4245
+
],
4246
+
"engines": {
4247
+
"node": ">=18"
4248
+
}
4249
+
},
4250
+
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
4251
+
"version": "0.25.12",
4252
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
4253
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
4254
+
"cpu": [
4255
+
"mips64el"
4256
+
],
4257
+
"dev": true,
4258
+
"license": "MIT",
4259
+
"optional": true,
4260
+
"os": [
4261
+
"linux"
4262
+
],
4263
+
"engines": {
4264
+
"node": ">=18"
4265
+
}
4266
+
},
4267
+
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
4268
+
"version": "0.25.12",
4269
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
4270
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
4271
+
"cpu": [
4272
+
"ppc64"
4273
+
],
4274
+
"dev": true,
4275
+
"license": "MIT",
4276
+
"optional": true,
4277
+
"os": [
4278
+
"linux"
4279
+
],
4280
+
"engines": {
4281
+
"node": ">=18"
4282
+
}
4283
+
},
4284
+
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
4285
+
"version": "0.25.12",
4286
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
4287
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
4288
+
"cpu": [
4289
+
"riscv64"
4290
+
],
4291
+
"dev": true,
4292
+
"license": "MIT",
4293
+
"optional": true,
4294
+
"os": [
4295
+
"linux"
4296
+
],
4297
+
"engines": {
4298
+
"node": ">=18"
4299
+
}
4300
+
},
4301
+
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
4302
+
"version": "0.25.12",
4303
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
4304
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
4305
+
"cpu": [
4306
+
"s390x"
4307
+
],
4308
+
"dev": true,
4309
+
"license": "MIT",
4310
+
"optional": true,
4311
+
"os": [
4312
+
"linux"
4313
+
],
4314
+
"engines": {
4315
+
"node": ">=18"
4316
+
}
4317
+
},
4318
+
"node_modules/vite/node_modules/@esbuild/linux-x64": {
4319
+
"version": "0.25.12",
4320
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
4321
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
4322
+
"cpu": [
4323
+
"x64"
4324
+
],
4325
+
"dev": true,
4326
+
"license": "MIT",
4327
+
"optional": true,
4328
+
"os": [
4329
+
"linux"
4330
+
],
4331
+
"engines": {
4332
+
"node": ">=18"
4333
+
}
4334
+
},
4335
+
"node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
4336
+
"version": "0.25.12",
4337
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
4338
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
4339
+
"cpu": [
4340
+
"arm64"
4341
+
],
4342
+
"dev": true,
4343
+
"license": "MIT",
4344
+
"optional": true,
4345
+
"os": [
4346
+
"netbsd"
4347
+
],
4348
+
"engines": {
4349
+
"node": ">=18"
4350
+
}
4351
+
},
4352
+
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
4353
+
"version": "0.25.12",
4354
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
4355
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
4356
+
"cpu": [
4357
+
"x64"
4358
+
],
4359
+
"dev": true,
4360
+
"license": "MIT",
4361
+
"optional": true,
4362
+
"os": [
4363
+
"netbsd"
4364
+
],
4365
+
"engines": {
4366
+
"node": ">=18"
4367
+
}
4368
+
},
4369
+
"node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
4370
+
"version": "0.25.12",
4371
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
4372
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
4373
+
"cpu": [
4374
+
"arm64"
4375
+
],
4376
+
"dev": true,
4377
+
"license": "MIT",
4378
+
"optional": true,
4379
+
"os": [
4380
+
"openbsd"
4381
+
],
4382
+
"engines": {
4383
+
"node": ">=18"
4384
+
}
4385
+
},
4386
+
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
4387
+
"version": "0.25.12",
4388
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
4389
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
4390
+
"cpu": [
4391
+
"x64"
4392
+
],
4393
+
"dev": true,
4394
+
"license": "MIT",
4395
+
"optional": true,
4396
+
"os": [
4397
+
"openbsd"
4398
+
],
4399
+
"engines": {
4400
+
"node": ">=18"
4401
+
}
4402
+
},
4403
+
"node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
4404
+
"version": "0.25.12",
4405
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
4406
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
4407
+
"cpu": [
4408
+
"arm64"
4409
+
],
4410
+
"dev": true,
4411
+
"license": "MIT",
4412
+
"optional": true,
4413
+
"os": [
4414
+
"openharmony"
4415
+
],
4416
+
"engines": {
4417
+
"node": ">=18"
4418
+
}
4419
+
},
4420
+
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
4421
+
"version": "0.25.12",
4422
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
4423
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
4424
+
"cpu": [
4425
+
"x64"
4426
+
],
4427
+
"dev": true,
4428
+
"license": "MIT",
4429
+
"optional": true,
4430
+
"os": [
4431
+
"sunos"
4432
+
],
4433
+
"engines": {
4434
+
"node": ">=18"
4435
+
}
4436
+
},
4437
+
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
4438
+
"version": "0.25.12",
4439
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
4440
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
4441
+
"cpu": [
4442
+
"arm64"
4443
+
],
4444
+
"dev": true,
4445
+
"license": "MIT",
4446
+
"optional": true,
4447
+
"os": [
4448
+
"win32"
4449
+
],
4450
+
"engines": {
4451
+
"node": ">=18"
4452
+
}
4453
+
},
4454
+
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
4455
+
"version": "0.25.12",
4456
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
4457
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
4458
+
"cpu": [
4459
+
"ia32"
4460
+
],
4461
+
"dev": true,
4462
+
"license": "MIT",
4463
+
"optional": true,
4464
+
"os": [
4465
+
"win32"
4466
+
],
4467
+
"engines": {
4468
+
"node": ">=18"
4469
+
}
4470
+
},
4471
+
"node_modules/vite/node_modules/@esbuild/win32-x64": {
4472
+
"version": "0.25.12",
4473
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
4474
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
4475
+
"cpu": [
4476
+
"x64"
4477
+
],
4478
+
"dev": true,
4479
+
"license": "MIT",
4480
+
"optional": true,
4481
+
"os": [
4482
+
"win32"
4483
+
],
4484
+
"engines": {
4485
+
"node": ">=18"
4486
+
}
4487
+
},
4488
+
"node_modules/vite/node_modules/esbuild": {
4489
+
"version": "0.25.12",
4490
+
"dev": true,
4491
+
"hasInstallScript": true,
4492
+
"license": "MIT",
4493
+
"bin": {
4494
+
"esbuild": "bin/esbuild"
4495
+
},
4496
+
"engines": {
4497
+
"node": ">=18"
4498
+
},
4499
+
"optionalDependencies": {
4500
+
"@esbuild/aix-ppc64": "0.25.12",
4501
+
"@esbuild/android-arm": "0.25.12",
4502
+
"@esbuild/android-arm64": "0.25.12",
4503
+
"@esbuild/android-x64": "0.25.12",
4504
+
"@esbuild/darwin-arm64": "0.25.12",
4505
+
"@esbuild/darwin-x64": "0.25.12",
4506
+
"@esbuild/freebsd-arm64": "0.25.12",
4507
+
"@esbuild/freebsd-x64": "0.25.12",
4508
+
"@esbuild/linux-arm": "0.25.12",
4509
+
"@esbuild/linux-arm64": "0.25.12",
4510
+
"@esbuild/linux-ia32": "0.25.12",
4511
+
"@esbuild/linux-loong64": "0.25.12",
4512
+
"@esbuild/linux-mips64el": "0.25.12",
4513
+
"@esbuild/linux-ppc64": "0.25.12",
4514
+
"@esbuild/linux-riscv64": "0.25.12",
4515
+
"@esbuild/linux-s390x": "0.25.12",
4516
+
"@esbuild/linux-x64": "0.25.12",
4517
+
"@esbuild/netbsd-arm64": "0.25.12",
4518
+
"@esbuild/netbsd-x64": "0.25.12",
4519
+
"@esbuild/openbsd-arm64": "0.25.12",
4520
+
"@esbuild/openbsd-x64": "0.25.12",
4521
+
"@esbuild/openharmony-arm64": "0.25.12",
4522
+
"@esbuild/sunos-x64": "0.25.12",
4523
+
"@esbuild/win32-arm64": "0.25.12",
4524
+
"@esbuild/win32-ia32": "0.25.12",
4525
+
"@esbuild/win32-x64": "0.25.12"
4526
+
}
4527
+
},
4528
+
"node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
4529
+
"version": "0.25.12",
4530
+
"cpu": [
4531
+
"arm64"
4532
+
],
4533
+
"dev": true,
4534
+
"license": "MIT",
4535
+
"optional": true,
4536
+
"os": [
4537
+
"darwin"
4538
+
],
4539
+
"engines": {
4540
+
"node": ">=18"
4541
+
}
4542
+
},
4543
+
"node_modules/vitest": {
4544
+
"version": "4.0.15",
4545
+
"dev": true,
4546
+
"license": "MIT",
4547
+
"dependencies": {
4548
+
"@vitest/expect": "4.0.15",
4549
+
"@vitest/mocker": "4.0.15",
4550
+
"@vitest/pretty-format": "4.0.15",
4551
+
"@vitest/runner": "4.0.15",
4552
+
"@vitest/snapshot": "4.0.15",
4553
+
"@vitest/spy": "4.0.15",
4554
+
"@vitest/utils": "4.0.15",
4555
+
"es-module-lexer": "^1.7.0",
4556
+
"expect-type": "^1.2.2",
4557
+
"magic-string": "^0.30.21",
4558
+
"obug": "^2.1.1",
4559
+
"pathe": "^2.0.3",
4560
+
"picomatch": "^4.0.3",
4561
+
"std-env": "^3.10.0",
4562
+
"tinybench": "^2.9.0",
4563
+
"tinyexec": "^1.0.2",
4564
+
"tinyglobby": "^0.2.15",
4565
+
"tinyrainbow": "^3.0.3",
4566
+
"vite": "^6.0.0 || ^7.0.0",
4567
+
"why-is-node-running": "^2.3.0"
4568
+
},
4569
+
"bin": {
4570
+
"vitest": "vitest.mjs"
4571
+
},
4572
+
"engines": {
4573
+
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
4574
+
},
4575
+
"funding": {
4576
+
"url": "https://opencollective.com/vitest"
4577
+
},
4578
+
"peerDependencies": {
4579
+
"@edge-runtime/vm": "*",
4580
+
"@opentelemetry/api": "^1.9.0",
4581
+
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
4582
+
"@vitest/browser-playwright": "4.0.15",
4583
+
"@vitest/browser-preview": "4.0.15",
4584
+
"@vitest/browser-webdriverio": "4.0.15",
4585
+
"@vitest/ui": "4.0.15",
4586
+
"happy-dom": "*",
4587
+
"jsdom": "*"
4588
+
},
4589
+
"peerDependenciesMeta": {
4590
+
"@edge-runtime/vm": {
4591
+
"optional": true
4592
+
},
4593
+
"@opentelemetry/api": {
4594
+
"optional": true
4595
+
},
4596
+
"@types/node": {
4597
+
"optional": true
4598
+
},
4599
+
"@vitest/browser-playwright": {
4600
+
"optional": true
4601
+
},
4602
+
"@vitest/browser-preview": {
4603
+
"optional": true
4604
+
},
4605
+
"@vitest/browser-webdriverio": {
4606
+
"optional": true
4607
+
},
4608
+
"@vitest/ui": {
4609
+
"optional": true
4610
+
},
4611
+
"happy-dom": {
4612
+
"optional": true
4613
+
},
4614
+
"jsdom": {
4615
+
"optional": true
4616
+
}
4617
+
}
4618
+
},
4619
+
"node_modules/which": {
4620
+
"version": "2.0.2",
4621
+
"dev": true,
4622
+
"license": "ISC",
4623
+
"dependencies": {
4624
+
"isexe": "^2.0.0"
4625
+
},
4626
+
"bin": {
4627
+
"node-which": "bin/node-which"
4628
+
},
4629
+
"engines": {
4630
+
"node": ">= 8"
4631
+
}
4632
+
},
4633
+
"node_modules/why-is-node-running": {
4634
+
"version": "2.3.0",
4635
+
"dev": true,
4636
+
"license": "MIT",
4637
+
"dependencies": {
4638
+
"siginfo": "^2.0.0",
4639
+
"stackback": "0.0.2"
4640
+
},
4641
+
"bin": {
4642
+
"why-is-node-running": "cli.js"
4643
+
},
4644
+
"engines": {
4645
+
"node": ">=8"
4646
+
}
4647
+
},
4648
+
"node_modules/word-wrap": {
4649
+
"version": "1.2.5",
4650
+
"dev": true,
4651
+
"license": "MIT",
4652
+
"engines": {
4653
+
"node": ">=0.10.0"
4654
+
}
4655
+
},
4656
+
"node_modules/yocto-queue": {
4657
+
"version": "0.1.0",
4658
+
"dev": true,
4659
+
"license": "MIT",
4660
+
"engines": {
4661
+
"node": ">=10"
4662
+
},
4663
+
"funding": {
4664
+
"url": "https://github.com/sponsors/sindresorhus"
4665
+
}
4666
+
}
4667
+
}
4668
+
}
+40
package.json
+40
package.json
···
1
+
{
2
+
"name": "tiered-storage",
3
+
"version": "1.0.0",
4
+
"description": "Tiered storage library with S3, disk, and memory caching",
5
+
"main": "dist/index.js",
6
+
"types": "dist/index.d.ts",
7
+
"type": "module",
8
+
"scripts": {
9
+
"check": "tsc --noEmit",
10
+
"build": "tsc",
11
+
"dev": "tsx --watch src/index.ts",
12
+
"example": "tsx example.ts",
13
+
"serve": "tsx serve-example.ts",
14
+
"test": "vitest",
15
+
"test:watch": "vitest --watch",
16
+
"lint": "eslint src --ext .ts",
17
+
"lint:fix": "eslint src --ext .ts --fix",
18
+
"typecheck": "tsc --noEmit"
19
+
},
20
+
"dependencies": {
21
+
"@aws-sdk/client-s3": "^3.500.0",
22
+
"hono": "^4.10.7",
23
+
"mime-types": "^3.0.2",
24
+
"tiny-lru": "^11.0.0"
25
+
},
26
+
"devDependencies": {
27
+
"@types/bun": "^1.3.4",
28
+
"@types/mime-types": "^3.0.1",
29
+
"@types/node": "^24.10.1",
30
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
31
+
"@typescript-eslint/parser": "^8.48.1",
32
+
"eslint": "^9.39.1",
33
+
"tsx": "^4.0.0",
34
+
"typescript": "^5.3.0",
35
+
"vitest": "^4.0.15"
36
+
},
37
+
"engines": {
38
+
"node": ">=18.0.0"
39
+
}
40
+
}
+469
serve-example.ts
+469
serve-example.ts
···
1
+
/**
2
+
* Example HTTP server serving static sites from tiered storage
3
+
*
4
+
* This demonstrates a real-world use case: serving static websites
5
+
* with automatic caching across hot (memory), warm (disk), and cold (S3) tiers.
6
+
*
7
+
* Run with: bun run serve
8
+
*/
9
+
10
+
import { Hono } from 'hono';
11
+
import { TieredStorage, MemoryStorageTier, DiskStorageTier, S3StorageTier } from './src/index.js';
12
+
import { readFile, readdir } from 'node:fs/promises';
13
+
import { lookup } from 'mime-types';
14
+
15
+
const S3_BUCKET = process.env.S3_BUCKET || 'tiered-storage-example';
16
+
const S3_METADATA_BUCKET = process.env.S3_METADATA_BUCKET;
17
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
18
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
19
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false';
20
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
21
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
22
+
const PORT = parseInt(process.env.PORT || '3000', 10);
23
+
24
+
const storage = new TieredStorage({
25
+
tiers: {
26
+
hot: new MemoryStorageTier({
27
+
maxSizeBytes: 50 * 1024 * 1024,
28
+
maxItems: 500,
29
+
}),
30
+
warm: new DiskStorageTier({
31
+
directory: './cache/sites',
32
+
maxSizeBytes: 1024 * 1024 * 1024,
33
+
}),
34
+
cold: new S3StorageTier({
35
+
bucket: S3_BUCKET,
36
+
metadataBucket: S3_METADATA_BUCKET,
37
+
region: S3_REGION,
38
+
endpoint: S3_ENDPOINT,
39
+
forcePathStyle: S3_FORCE_PATH_STYLE,
40
+
credentials:
41
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
42
+
? { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY }
43
+
: undefined,
44
+
prefix: 'demo-sites/',
45
+
}),
46
+
},
47
+
compression: true,
48
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
49
+
promotionStrategy: 'lazy',
50
+
});
51
+
52
+
const app = new Hono();
53
+
54
+
// Site metadata
55
+
const siteId = 'did:plc:example123';
56
+
const siteName = 'tiered-cache-demo';
57
+
58
+
/**
59
+
* Load the example site into storage
60
+
*/
61
+
async function loadExampleSite() {
62
+
console.log('\n📦 Loading example site into tiered storage...\n');
63
+
64
+
const files = [
65
+
{ name: 'index.html', skipTiers: [], mimeType: 'text/html' },
66
+
{ name: 'about.html', skipTiers: ['hot'], mimeType: 'text/html' },
67
+
{ name: 'docs.html', skipTiers: ['hot'], mimeType: 'text/html' },
68
+
{ name: 'style.css', skipTiers: ['hot'], mimeType: 'text/css' },
69
+
{ name: 'script.js', skipTiers: ['hot'], mimeType: 'application/javascript' },
70
+
];
71
+
72
+
for (const file of files) {
73
+
const content = await readFile(`./example-site/${file.name}`, 'utf-8');
74
+
const key = `${siteId}/${siteName}/${file.name}`;
75
+
76
+
await storage.set(key, content, {
77
+
skipTiers: file.skipTiers as ('hot' | 'warm')[],
78
+
metadata: { mimeType: file.mimeType },
79
+
});
80
+
81
+
const tierInfo =
82
+
file.skipTiers.length === 0
83
+
? '🔥 hot + 💾 warm + ☁️ cold'
84
+
: `💾 warm + ☁️ cold (skipped hot)`;
85
+
const sizeKB = (content.length / 1024).toFixed(2);
86
+
console.log(` ✓ ${file.name.padEnd(15)} ${sizeKB.padStart(6)} KB → ${tierInfo}`);
87
+
}
88
+
89
+
console.log('\n✅ Site loaded successfully!\n');
90
+
}
91
+
92
+
/**
93
+
* Serve a file from tiered storage
94
+
*/
95
+
app.get('/sites/:did/:siteName/:path{.*}', async (c) => {
96
+
const { did, siteName, path } = c.req.param();
97
+
let filePath = path || 'index.html';
98
+
99
+
if (filePath === '' || filePath.endsWith('/')) {
100
+
filePath += 'index.html';
101
+
}
102
+
103
+
const key = `${did}/${siteName}/${filePath}`;
104
+
105
+
try {
106
+
const result = await storage.getWithMetadata(key);
107
+
108
+
if (!result) {
109
+
return c.text('404 Not Found', 404);
110
+
}
111
+
112
+
const mimeType = result.metadata.customMetadata?.mimeType || lookup(filePath) || 'application/octet-stream';
113
+
114
+
const headers: Record<string, string> = {
115
+
'Content-Type': mimeType,
116
+
'X-Cache-Tier': result.source, // Which tier served this
117
+
'X-Cache-Size': result.metadata.size.toString(),
118
+
'X-Cache-Compressed': result.metadata.compressed.toString(),
119
+
'X-Cache-Access-Count': result.metadata.accessCount.toString(),
120
+
};
121
+
122
+
// Add cache control based on tier
123
+
if (result.source === 'hot') {
124
+
headers['X-Cache-Status'] = 'HIT-MEMORY';
125
+
} else if (result.source === 'warm') {
126
+
headers['X-Cache-Status'] = 'HIT-DISK';
127
+
} else {
128
+
headers['X-Cache-Status'] = 'HIT-S3';
129
+
}
130
+
131
+
const emoji = result.source === 'hot' ? '🔥' : result.source === 'warm' ? '💾' : '☁️';
132
+
console.log(`${emoji} ${filePath.padEnd(20)} served from ${result.source.padEnd(4)} (${(result.metadata.size / 1024).toFixed(2)} KB, access #${result.metadata.accessCount})`);
133
+
134
+
return c.body(result.data as any, 200, headers);
135
+
} catch (error: any) {
136
+
console.error(`❌ Error serving ${filePath}:`, error.message);
137
+
return c.text('500 Internal Server Error', 500);
138
+
}
139
+
});
140
+
141
+
/**
142
+
* Admin endpoint: Cache statistics
143
+
*/
144
+
app.get('/admin/stats', async (c) => {
145
+
const stats = await storage.getStats();
146
+
147
+
const html = `
148
+
<!DOCTYPE html>
149
+
<html>
150
+
<head>
151
+
<title>Tiered Storage Statistics</title>
152
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
153
+
<style>
154
+
* { margin: 0; padding: 0; box-sizing: border-box; }
155
+
body {
156
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
157
+
background: #0f172a;
158
+
color: #f1f5f9;
159
+
padding: 2rem;
160
+
line-height: 1.6;
161
+
}
162
+
.container { max-width: 1200px; margin: 0 auto; }
163
+
h1 {
164
+
font-size: 2rem;
165
+
margin-bottom: 0.5rem;
166
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
167
+
-webkit-background-clip: text;
168
+
-webkit-text-fill-color: transparent;
169
+
}
170
+
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
171
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
172
+
.card {
173
+
background: #1e293b;
174
+
border: 1px solid #334155;
175
+
border-radius: 0.5rem;
176
+
padding: 1.5rem;
177
+
}
178
+
.tier-hot { border-left: 4px solid #ef4444; }
179
+
.tier-warm { border-left: 4px solid #f59e0b; }
180
+
.tier-cold { border-left: 4px solid #3b82f6; }
181
+
.card-title {
182
+
font-size: 1.2rem;
183
+
font-weight: 600;
184
+
margin-bottom: 1rem;
185
+
display: flex;
186
+
align-items: center;
187
+
gap: 0.5rem;
188
+
}
189
+
.stat { margin-bottom: 0.75rem; }
190
+
.stat-label { color: #94a3b8; font-size: 0.9rem; }
191
+
.stat-value { color: #f1f5f9; font-size: 1.5rem; font-weight: 700; }
192
+
.overall { background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1)); }
193
+
.refresh {
194
+
display: inline-block;
195
+
background: #3b82f6;
196
+
color: white;
197
+
padding: 0.75rem 1.5rem;
198
+
border-radius: 0.5rem;
199
+
text-decoration: none;
200
+
font-weight: 600;
201
+
margin-top: 1rem;
202
+
}
203
+
.refresh:hover { background: #2563eb; }
204
+
code {
205
+
background: #334155;
206
+
padding: 0.2rem 0.5rem;
207
+
border-radius: 0.25rem;
208
+
font-size: 0.9em;
209
+
}
210
+
</style>
211
+
</head>
212
+
<body>
213
+
<div class="container">
214
+
<h1>📊 Tiered Storage Statistics</h1>
215
+
<p class="subtitle">Real-time cache performance metrics • Auto-refresh every 5 seconds</p>
216
+
217
+
<div class="grid">
218
+
<div class="card tier-hot">
219
+
<div class="card-title">🔥 Hot Tier (Memory)</div>
220
+
<div class="stat">
221
+
<div class="stat-label">Items</div>
222
+
<div class="stat-value">${stats.hot?.items || 0}</div>
223
+
</div>
224
+
<div class="stat">
225
+
<div class="stat-label">Size</div>
226
+
<div class="stat-value">${((stats.hot?.bytes || 0) / 1024).toFixed(2)} KB</div>
227
+
</div>
228
+
<div class="stat">
229
+
<div class="stat-label">Hits / Misses</div>
230
+
<div class="stat-value">${stats.hot?.hits || 0} / ${stats.hot?.misses || 0}</div>
231
+
</div>
232
+
<div class="stat">
233
+
<div class="stat-label">Evictions</div>
234
+
<div class="stat-value">${stats.hot?.evictions || 0}</div>
235
+
</div>
236
+
</div>
237
+
238
+
<div class="card tier-warm">
239
+
<div class="card-title">💾 Warm Tier (Disk)</div>
240
+
<div class="stat">
241
+
<div class="stat-label">Items</div>
242
+
<div class="stat-value">${stats.warm?.items || 0}</div>
243
+
</div>
244
+
<div class="stat">
245
+
<div class="stat-label">Size</div>
246
+
<div class="stat-value">${((stats.warm?.bytes || 0) / 1024).toFixed(2)} KB</div>
247
+
</div>
248
+
<div class="stat">
249
+
<div class="stat-label">Hits / Misses</div>
250
+
<div class="stat-value">${stats.warm?.hits || 0} / ${stats.warm?.misses || 0}</div>
251
+
</div>
252
+
</div>
253
+
254
+
<div class="card tier-cold">
255
+
<div class="card-title">☁️ Cold Tier (S3)</div>
256
+
<div class="stat">
257
+
<div class="stat-label">Items</div>
258
+
<div class="stat-value">${stats.cold.items}</div>
259
+
</div>
260
+
<div class="stat">
261
+
<div class="stat-label">Size</div>
262
+
<div class="stat-value">${(stats.cold.bytes / 1024).toFixed(2)} KB</div>
263
+
</div>
264
+
</div>
265
+
</div>
266
+
267
+
<div class="card overall">
268
+
<div class="card-title">📈 Overall Performance</div>
269
+
<div class="grid" style="grid-template-columns: repeat(3, 1fr);">
270
+
<div class="stat">
271
+
<div class="stat-label">Total Hits</div>
272
+
<div class="stat-value">${stats.totalHits}</div>
273
+
</div>
274
+
<div class="stat">
275
+
<div class="stat-label">Total Misses</div>
276
+
<div class="stat-value">${stats.totalMisses}</div>
277
+
</div>
278
+
<div class="stat">
279
+
<div class="stat-label">Hit Rate</div>
280
+
<div class="stat-value">${(stats.hitRate * 100).toFixed(1)}%</div>
281
+
</div>
282
+
</div>
283
+
</div>
284
+
285
+
<div style="margin-top: 2rem; padding: 1rem; background: #1e293b; border-radius: 0.5rem; border: 1px solid #334155;">
286
+
<p style="margin-bottom: 0.5rem;"><strong>Try it out:</strong></p>
287
+
<p>Visit <code>http://localhost:${PORT}/sites/${siteId}/${siteName}/</code> to see the site</p>
288
+
<p>Watch the stats update as you browse different pages!</p>
289
+
</div>
290
+
</div>
291
+
292
+
<script>
293
+
// Auto-refresh stats every 5 seconds
294
+
setTimeout(() => window.location.reload(), 5000);
295
+
</script>
296
+
</body>
297
+
</html>
298
+
`;
299
+
300
+
return c.html(html);
301
+
});
302
+
303
+
/**
304
+
* Admin endpoint: Invalidate cache
305
+
*/
306
+
app.post('/admin/invalidate/:did/:siteName', async (c) => {
307
+
const { did, siteName } = c.req.param();
308
+
const prefix = `${did}/${siteName}/`;
309
+
const deleted = await storage.invalidate(prefix);
310
+
311
+
console.log(`🗑️ Invalidated ${deleted} files for ${did}/${siteName}`);
312
+
313
+
return c.json({ success: true, deleted, prefix });
314
+
});
315
+
316
+
/**
317
+
* Admin endpoint: Bootstrap hot cache
318
+
*/
319
+
app.post('/admin/bootstrap/hot', async (c) => {
320
+
const limit = parseInt(c.req.query('limit') || '100', 10);
321
+
const loaded = await storage.bootstrapHot(limit);
322
+
323
+
console.log(`🔥 Bootstrapped ${loaded} items into hot tier`);
324
+
325
+
return c.json({ success: true, loaded, limit });
326
+
});
327
+
328
+
/**
329
+
* Root redirect
330
+
*/
331
+
app.get('/', (c) => {
332
+
return c.redirect(`/sites/${siteId}/${siteName}/`);
333
+
});
334
+
335
+
/**
336
+
* Health check
337
+
*/
338
+
app.get('/health', (c) => c.json({ status: 'ok' }));
339
+
340
+
/**
341
+
* Test S3 connection
342
+
*/
343
+
async function testS3Connection() {
344
+
console.log('\n🔍 Testing S3 connection...\n');
345
+
346
+
try {
347
+
// Try to get stats (which lists objects)
348
+
const stats = await storage.getStats();
349
+
console.log(`✅ S3 connection successful!`);
350
+
console.log(` Found ${stats.cold.items} items (${(stats.cold.bytes / 1024).toFixed(2)} KB)\n`);
351
+
return true;
352
+
} catch (error: any) {
353
+
console.error('❌ S3 connection failed:', error.message);
354
+
console.error('\nDebug Info:');
355
+
console.error(` Bucket: ${S3_BUCKET}`);
356
+
console.error(` Region: ${S3_REGION}`);
357
+
console.error(` Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
358
+
console.error(` Access Key: ${AWS_ACCESS_KEY_ID?.substring(0, 8)}...`);
359
+
console.error(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
360
+
console.error('\nCommon issues:');
361
+
console.error(' • Check that bucket exists');
362
+
console.error(' • Verify credentials are correct');
363
+
console.error(' • Ensure endpoint URL is correct');
364
+
console.error(' • Check firewall/network access');
365
+
console.error(' • For S3-compatible services, verify region name\n');
366
+
return false;
367
+
}
368
+
}
369
+
370
+
/**
371
+
* Periodic cache clearing - demonstrates tier bootstrapping
372
+
*/
373
+
function startCacheClearInterval() {
374
+
const CLEAR_INTERVAL_MS = 60 * 1000; // 1 minute
375
+
376
+
setInterval(async () => {
377
+
console.log('\n' + '═'.repeat(60));
378
+
console.log('🧹 CACHE CLEAR - Clearing hot and warm tiers...');
379
+
console.log(' (Cold tier on S3 remains intact)');
380
+
console.log('═'.repeat(60) + '\n');
381
+
382
+
try {
383
+
// Clear hot tier (memory)
384
+
if (storage['config'].tiers.hot) {
385
+
await storage['config'].tiers.hot.clear();
386
+
console.log('✓ Hot tier (memory) cleared');
387
+
}
388
+
389
+
// Clear warm tier (disk)
390
+
if (storage['config'].tiers.warm) {
391
+
await storage['config'].tiers.warm.clear();
392
+
console.log('✓ Warm tier (disk) cleared');
393
+
}
394
+
395
+
console.log('\n💡 Next request will bootstrap from S3 (cold tier)\n');
396
+
console.log('─'.repeat(60) + '\n');
397
+
} catch (error: any) {
398
+
console.error('❌ Error clearing cache:', error.message);
399
+
}
400
+
}, CLEAR_INTERVAL_MS);
401
+
402
+
console.log(`⏰ Cache clear interval started (every ${CLEAR_INTERVAL_MS / 1000}s)\n`);
403
+
}
404
+
405
+
/**
406
+
* Main startup
407
+
*/
408
+
async function main() {
409
+
console.log('╔════════════════════════════════════════════════╗');
410
+
console.log('║ Tiered Storage Demo Server ║');
411
+
console.log('╚════════════════════════════════════════════════╝\n');
412
+
413
+
console.log('Configuration:');
414
+
console.log(` S3 Bucket: ${S3_BUCKET}`);
415
+
console.log(` S3 Region: ${S3_REGION}`);
416
+
console.log(` S3 Endpoint: ${S3_ENDPOINT || '(default AWS S3)'}`);
417
+
console.log(` Force Path Style: ${S3_FORCE_PATH_STYLE}`);
418
+
console.log(` Port: ${PORT}`);
419
+
420
+
try {
421
+
// Test S3 connection first
422
+
const s3Connected = await testS3Connection();
423
+
if (!s3Connected) {
424
+
process.exit(1);
425
+
}
426
+
427
+
// Load the example site
428
+
await loadExampleSite();
429
+
430
+
// Start periodic cache clearing
431
+
startCacheClearInterval();
432
+
433
+
// Start the server
434
+
console.log('🚀 Starting server...\n');
435
+
436
+
const server = Bun.serve({
437
+
port: PORT,
438
+
fetch: app.fetch,
439
+
});
440
+
441
+
console.log('╔════════════════════════════════════════════════╗');
442
+
console.log('║ Server Running! ║');
443
+
console.log('╚════════════════════════════════════════════════╝\n');
444
+
console.log(`📍 Demo Site: http://localhost:${PORT}/sites/${siteId}/${siteName}/`);
445
+
console.log(`📊 Statistics: http://localhost:${PORT}/admin/stats`);
446
+
console.log(`💚 Health: http://localhost:${PORT}/health`);
447
+
console.log('\n🎯 Try browsing the site and watch which tier serves each file!\n');
448
+
console.log('💡 Caches clear every 60 seconds - watch files get re-fetched from S3!\n');
449
+
if (S3_METADATA_BUCKET) {
450
+
console.log(`✨ Metadata bucket: ${S3_METADATA_BUCKET} (fast updates enabled!)\n`);
451
+
} else {
452
+
console.log('⚠️ No metadata bucket - using legacy mode (slower updates)\n');
453
+
}
454
+
console.log('Press Ctrl+C to stop\n');
455
+
console.log('─'.repeat(60));
456
+
console.log('Request Log:\n');
457
+
} catch (error: any) {
458
+
console.error('\n❌ Failed to start server:', error.message);
459
+
if (error.message.includes('Forbidden')) {
460
+
console.error('\nS3 connection issue. Check:');
461
+
console.error(' 1. Bucket exists on S3 service');
462
+
console.error(' 2. Credentials are correct');
463
+
console.error(' 3. Permissions allow read/write');
464
+
}
465
+
process.exit(1);
466
+
}
467
+
}
468
+
469
+
main().catch(console.error);
+682
src/TieredStorage.ts
+682
src/TieredStorage.ts
···
1
+
import type {
2
+
TieredStorageConfig,
3
+
SetOptions,
4
+
StorageResult,
5
+
SetResult,
6
+
StorageMetadata,
7
+
AllTierStats,
8
+
StorageSnapshot,
9
+
} from './types/index.js';
10
+
import { compress, decompress } from './utils/compression.js';
11
+
import { defaultSerialize, defaultDeserialize } from './utils/serialization.js';
12
+
import { calculateChecksum } from './utils/checksum.js';
13
+
14
+
/**
15
+
* Main orchestrator for tiered storage system.
16
+
*
17
+
* @typeParam T - The type of data being stored
18
+
*
19
+
* @remarks
20
+
* Implements a cascading containment model:
21
+
* - **Write Strategy (Cascading Down):** Write to hot → also writes to warm and cold
22
+
* - **Read Strategy (Bubbling Up):** Check hot first → if miss, check warm → if miss, check cold
23
+
* - **Bootstrap Strategy:** Hot can bootstrap from warm, warm can bootstrap from cold
24
+
*
25
+
* The cold tier is the source of truth and is required.
26
+
* Hot and warm tiers are optional performance optimizations.
27
+
*
28
+
* @example
29
+
* ```typescript
30
+
* const storage = new TieredStorage({
31
+
* tiers: {
32
+
* hot: new MemoryStorageTier({ maxSizeBytes: 100 * 1024 * 1024 }), // 100MB
33
+
* warm: new DiskStorageTier({ directory: './cache' }),
34
+
* cold: new S3StorageTier({ bucket: 'my-bucket', region: 'us-east-1' }),
35
+
* },
36
+
* compression: true,
37
+
* defaultTTL: 14 * 24 * 60 * 60 * 1000, // 14 days
38
+
* promotionStrategy: 'lazy',
39
+
* });
40
+
*
41
+
* // Store data (cascades to all tiers)
42
+
* await storage.set('user:123', { name: 'Alice' });
43
+
*
44
+
* // Retrieve data (bubbles up from cold → warm → hot)
45
+
* const user = await storage.get('user:123');
46
+
*
47
+
* // Invalidate all keys with prefix
48
+
* await storage.invalidate('user:');
49
+
* ```
50
+
*/
51
+
export class TieredStorage<T = unknown> {
52
+
private serialize: (data: unknown) => Promise<Uint8Array>;
53
+
private deserialize: (data: Uint8Array) => Promise<unknown>;
54
+
55
+
constructor(private config: TieredStorageConfig) {
56
+
if (!config.tiers.cold) {
57
+
throw new Error('Cold tier is required');
58
+
}
59
+
60
+
this.serialize = config.serialization?.serialize ?? defaultSerialize;
61
+
this.deserialize = config.serialization?.deserialize ?? defaultDeserialize;
62
+
}
63
+
64
+
/**
65
+
* Retrieve data for a key.
66
+
*
67
+
* @param key - The key to retrieve
68
+
* @returns The data, or null if not found or expired
69
+
*
70
+
* @remarks
71
+
* Checks tiers in order: hot → warm → cold.
72
+
* On cache miss, promotes data to upper tiers based on promotionStrategy.
73
+
* Automatically handles decompression and deserialization.
74
+
* Returns null if key doesn't exist or has expired (TTL).
75
+
*/
76
+
async get(key: string): Promise<T | null> {
77
+
const result = await this.getWithMetadata(key);
78
+
return result ? result.data : null;
79
+
}
80
+
81
+
/**
82
+
* Retrieve data with metadata and source tier information.
83
+
*
84
+
* @param key - The key to retrieve
85
+
* @returns The data, metadata, and source tier, or null if not found
86
+
*
87
+
* @remarks
88
+
* Use this when you need to know:
89
+
* - Which tier served the data (for observability)
90
+
* - Metadata like access count, TTL, checksum
91
+
* - When the data was created/last accessed
92
+
*/
93
+
async getWithMetadata(key: string): Promise<StorageResult<T> | null> {
94
+
// 1. Check hot tier first
95
+
if (this.config.tiers.hot) {
96
+
const data = await this.config.tiers.hot.get(key);
97
+
if (data) {
98
+
const metadata = await this.config.tiers.hot.getMetadata(key);
99
+
if (!metadata) {
100
+
await this.delete(key);
101
+
} else if (this.isExpired(metadata)) {
102
+
await this.delete(key);
103
+
return null;
104
+
} else {
105
+
await this.updateAccessStats(key, 'hot');
106
+
return {
107
+
data: (await this.deserializeData(data)) as T,
108
+
metadata,
109
+
source: 'hot',
110
+
};
111
+
}
112
+
}
113
+
}
114
+
115
+
// 2. Check warm tier
116
+
if (this.config.tiers.warm) {
117
+
const data = await this.config.tiers.warm.get(key);
118
+
if (data) {
119
+
const metadata = await this.config.tiers.warm.getMetadata(key);
120
+
if (!metadata) {
121
+
await this.delete(key);
122
+
} else if (this.isExpired(metadata)) {
123
+
await this.delete(key);
124
+
return null;
125
+
} else {
126
+
if (this.config.tiers.hot && this.config.promotionStrategy === 'eager') {
127
+
await this.config.tiers.hot.set(key, data, metadata);
128
+
}
129
+
130
+
await this.updateAccessStats(key, 'warm');
131
+
return {
132
+
data: (await this.deserializeData(data)) as T,
133
+
metadata,
134
+
source: 'warm',
135
+
};
136
+
}
137
+
}
138
+
}
139
+
140
+
// 3. Check cold tier (source of truth)
141
+
const data = await this.config.tiers.cold.get(key);
142
+
if (data) {
143
+
const metadata = await this.config.tiers.cold.getMetadata(key);
144
+
if (!metadata) {
145
+
await this.config.tiers.cold.delete(key);
146
+
return null;
147
+
}
148
+
149
+
if (this.isExpired(metadata)) {
150
+
await this.delete(key);
151
+
return null;
152
+
}
153
+
154
+
// Promote to warm and hot (if configured)
155
+
if (this.config.promotionStrategy === 'eager') {
156
+
if (this.config.tiers.warm) {
157
+
await this.config.tiers.warm.set(key, data, metadata);
158
+
}
159
+
if (this.config.tiers.hot) {
160
+
await this.config.tiers.hot.set(key, data, metadata);
161
+
}
162
+
}
163
+
164
+
await this.updateAccessStats(key, 'cold');
165
+
return {
166
+
data: (await this.deserializeData(data)) as T,
167
+
metadata,
168
+
source: 'cold',
169
+
};
170
+
}
171
+
172
+
return null;
173
+
}
174
+
175
+
/**
176
+
* Store data with optional configuration.
177
+
*
178
+
* @param key - The key to store under
179
+
* @param data - The data to store
180
+
* @param options - Optional configuration (TTL, metadata, tier skipping)
181
+
* @returns Information about what was stored and where
182
+
*
183
+
* @remarks
184
+
* Data cascades down through tiers:
185
+
* - If written to hot, also written to warm and cold
186
+
* - If written to warm (hot skipped), also written to cold
187
+
* - Cold is always written (source of truth)
188
+
*
189
+
* Use `skipTiers` to control placement. For example:
190
+
* - Large files: `skipTiers: ['hot']` to avoid memory bloat
191
+
* - Critical small files: Write to all tiers for fastest access
192
+
*
193
+
* Automatically handles serialization and optional compression.
194
+
*/
195
+
async set(key: string, data: T, options?: SetOptions): Promise<SetResult> {
196
+
// 1. Serialize data
197
+
const serialized = await this.serialize(data);
198
+
199
+
// 2. Optionally compress
200
+
const finalData = this.config.compression ? await compress(serialized) : serialized;
201
+
202
+
// 3. Create metadata
203
+
const metadata = this.createMetadata(key, finalData, options);
204
+
205
+
// 4. Write to all tiers (cascading down)
206
+
const tiersWritten: ('hot' | 'warm' | 'cold')[] = [];
207
+
208
+
// Write to hot (if configured and not skipped)
209
+
if (this.config.tiers.hot && !options?.skipTiers?.includes('hot')) {
210
+
await this.config.tiers.hot.set(key, finalData, metadata);
211
+
tiersWritten.push('hot');
212
+
213
+
// Hot writes cascade to warm
214
+
if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) {
215
+
await this.config.tiers.warm.set(key, finalData, metadata);
216
+
tiersWritten.push('warm');
217
+
}
218
+
} else if (this.config.tiers.warm && !options?.skipTiers?.includes('warm')) {
219
+
// Write to warm (if hot skipped)
220
+
await this.config.tiers.warm.set(key, finalData, metadata);
221
+
tiersWritten.push('warm');
222
+
}
223
+
224
+
// Always write to cold (source of truth)
225
+
await this.config.tiers.cold.set(key, finalData, metadata);
226
+
tiersWritten.push('cold');
227
+
228
+
return { key, metadata, tiersWritten };
229
+
}
230
+
231
+
/**
232
+
* Delete data from all tiers.
233
+
*
234
+
* @param key - The key to delete
235
+
*
236
+
* @remarks
237
+
* Deletes from all configured tiers in parallel.
238
+
* Does not throw if the key doesn't exist.
239
+
*/
240
+
async delete(key: string): Promise<void> {
241
+
await Promise.all([
242
+
this.config.tiers.hot?.delete(key),
243
+
this.config.tiers.warm?.delete(key),
244
+
this.config.tiers.cold.delete(key),
245
+
]);
246
+
}
247
+
248
+
/**
249
+
* Check if a key exists in any tier.
250
+
*
251
+
* @param key - The key to check
252
+
* @returns true if the key exists and hasn't expired
253
+
*
254
+
* @remarks
255
+
* Checks tiers in order: hot → warm → cold.
256
+
* Returns false if key exists but has expired.
257
+
*/
258
+
async exists(key: string): Promise<boolean> {
259
+
// Check hot first (fastest)
260
+
if (this.config.tiers.hot && (await this.config.tiers.hot.exists(key))) {
261
+
const metadata = await this.config.tiers.hot.getMetadata(key);
262
+
if (metadata && !this.isExpired(metadata)) {
263
+
return true;
264
+
}
265
+
}
266
+
267
+
// Check warm
268
+
if (this.config.tiers.warm && (await this.config.tiers.warm.exists(key))) {
269
+
const metadata = await this.config.tiers.warm.getMetadata(key);
270
+
if (metadata && !this.isExpired(metadata)) {
271
+
return true;
272
+
}
273
+
}
274
+
275
+
// Check cold (source of truth)
276
+
if (await this.config.tiers.cold.exists(key)) {
277
+
const metadata = await this.config.tiers.cold.getMetadata(key);
278
+
if (metadata && !this.isExpired(metadata)) {
279
+
return true;
280
+
}
281
+
}
282
+
283
+
return false;
284
+
}
285
+
286
+
/**
287
+
* Renew TTL for a key.
288
+
*
289
+
* @param key - The key to touch
290
+
* @param ttlMs - Optional new TTL in milliseconds (uses default if not provided)
291
+
*
292
+
* @remarks
293
+
* Updates the TTL and lastAccessed timestamp in all tiers.
294
+
* Useful for implementing "keep alive" behavior for actively used keys.
295
+
* Does nothing if no TTL is configured.
296
+
*/
297
+
async touch(key: string, ttlMs?: number): Promise<void> {
298
+
const ttl = ttlMs ?? this.config.defaultTTL;
299
+
if (!ttl) return;
300
+
301
+
const newTTL = new Date(Date.now() + ttl);
302
+
303
+
for (const tier of [this.config.tiers.hot, this.config.tiers.warm, this.config.tiers.cold]) {
304
+
if (!tier) continue;
305
+
306
+
const metadata = await tier.getMetadata(key);
307
+
if (metadata) {
308
+
metadata.ttl = newTTL;
309
+
metadata.lastAccessed = new Date();
310
+
await tier.setMetadata(key, metadata);
311
+
}
312
+
}
313
+
}
314
+
315
+
/**
316
+
* Invalidate all keys matching a prefix.
317
+
*
318
+
* @param prefix - The prefix to match (e.g., 'user:' matches 'user:123', 'user:456')
319
+
* @returns Number of keys deleted
320
+
*
321
+
* @remarks
322
+
* Useful for bulk invalidation:
323
+
* - Site invalidation: `invalidate('site:abc:')`
324
+
* - User invalidation: `invalidate('user:123:')`
325
+
* - Global invalidation: `invalidate('')` (deletes everything)
326
+
*
327
+
* Deletes from all tiers in parallel for efficiency.
328
+
*/
329
+
async invalidate(prefix: string): Promise<number> {
330
+
const keysToDelete = new Set<string>();
331
+
332
+
// Collect all keys matching prefix from all tiers
333
+
if (this.config.tiers.hot) {
334
+
for await (const key of this.config.tiers.hot.listKeys(prefix)) {
335
+
keysToDelete.add(key);
336
+
}
337
+
}
338
+
339
+
if (this.config.tiers.warm) {
340
+
for await (const key of this.config.tiers.warm.listKeys(prefix)) {
341
+
keysToDelete.add(key);
342
+
}
343
+
}
344
+
345
+
for await (const key of this.config.tiers.cold.listKeys(prefix)) {
346
+
keysToDelete.add(key);
347
+
}
348
+
349
+
// Delete from all tiers in parallel
350
+
const keys = Array.from(keysToDelete);
351
+
352
+
await Promise.all([
353
+
this.config.tiers.hot?.deleteMany(keys),
354
+
this.config.tiers.warm?.deleteMany(keys),
355
+
this.config.tiers.cold.deleteMany(keys),
356
+
]);
357
+
358
+
return keys.length;
359
+
}
360
+
361
+
/**
362
+
* List all keys, optionally filtered by prefix.
363
+
*
364
+
* @param prefix - Optional prefix to filter keys
365
+
* @returns Async iterator of keys
366
+
*
367
+
* @remarks
368
+
* Returns keys from the cold tier (source of truth).
369
+
* Memory-efficient - streams keys rather than loading all into memory.
370
+
*
371
+
* @example
372
+
* ```typescript
373
+
* for await (const key of storage.listKeys('user:')) {
374
+
* console.log(key);
375
+
* }
376
+
* ```
377
+
*/
378
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
379
+
// List from cold tier (source of truth)
380
+
for await (const key of this.config.tiers.cold.listKeys(prefix)) {
381
+
yield key;
382
+
}
383
+
}
384
+
385
+
/**
386
+
* Get aggregated statistics across all tiers.
387
+
*
388
+
* @returns Statistics including size, item count, hits, misses, hit rate
389
+
*
390
+
* @remarks
391
+
* Useful for monitoring and capacity planning.
392
+
* Hit rate is calculated as: hits / (hits + misses).
393
+
*/
394
+
async getStats(): Promise<AllTierStats> {
395
+
const [hot, warm, cold] = await Promise.all([
396
+
this.config.tiers.hot?.getStats(),
397
+
this.config.tiers.warm?.getStats(),
398
+
this.config.tiers.cold.getStats(),
399
+
]);
400
+
401
+
const totalHits = (hot?.hits ?? 0) + (warm?.hits ?? 0) + (cold?.hits ?? 0);
402
+
const totalMisses = (hot?.misses ?? 0) + (warm?.misses ?? 0) + (cold?.misses ?? 0);
403
+
const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
404
+
405
+
return {
406
+
...(hot && { hot }),
407
+
...(warm && { warm }),
408
+
cold,
409
+
totalHits,
410
+
totalMisses,
411
+
hitRate,
412
+
};
413
+
}
414
+
415
+
/**
416
+
* Clear all data from all tiers.
417
+
*
418
+
* @remarks
419
+
* Use with extreme caution! This will delete all data in the entire storage system.
420
+
* Cannot be undone.
421
+
*/
422
+
async clear(): Promise<void> {
423
+
await Promise.all([
424
+
this.config.tiers.hot?.clear(),
425
+
this.config.tiers.warm?.clear(),
426
+
this.config.tiers.cold.clear(),
427
+
]);
428
+
}
429
+
430
+
/**
431
+
* Clear a specific tier.
432
+
*
433
+
* @param tier - Which tier to clear
434
+
*
435
+
* @remarks
436
+
* Useful for:
437
+
* - Clearing hot tier to test warm/cold performance
438
+
* - Clearing warm tier to force rebuilding from cold
439
+
* - Clearing cold tier to start fresh (⚠️ loses source of truth!)
440
+
*/
441
+
async clearTier(tier: 'hot' | 'warm' | 'cold'): Promise<void> {
442
+
switch (tier) {
443
+
case 'hot':
444
+
await this.config.tiers.hot?.clear();
445
+
break;
446
+
case 'warm':
447
+
await this.config.tiers.warm?.clear();
448
+
break;
449
+
case 'cold':
450
+
await this.config.tiers.cold.clear();
451
+
break;
452
+
}
453
+
}
454
+
455
+
/**
456
+
* Export metadata snapshot for backup or migration.
457
+
*
458
+
* @returns Snapshot containing all keys, metadata, and statistics
459
+
*
460
+
* @remarks
461
+
* The snapshot includes metadata but not the actual data (data remains in tiers).
462
+
* Useful for:
463
+
* - Backup and restore
464
+
* - Migration between storage systems
465
+
* - Auditing and compliance
466
+
*/
467
+
async export(): Promise<StorageSnapshot> {
468
+
const keys: string[] = [];
469
+
const metadata: Record<string, StorageMetadata> = {};
470
+
471
+
// Export from cold tier (source of truth)
472
+
for await (const key of this.config.tiers.cold.listKeys()) {
473
+
keys.push(key);
474
+
const meta = await this.config.tiers.cold.getMetadata(key);
475
+
if (meta) {
476
+
metadata[key] = meta;
477
+
}
478
+
}
479
+
480
+
const stats = await this.getStats();
481
+
482
+
return {
483
+
version: 1,
484
+
exportedAt: new Date(),
485
+
keys,
486
+
metadata,
487
+
stats,
488
+
};
489
+
}
490
+
491
+
/**
492
+
* Import metadata snapshot.
493
+
*
494
+
* @param snapshot - Snapshot to import
495
+
*
496
+
* @remarks
497
+
* Validates version compatibility before importing.
498
+
* Only imports metadata - assumes data already exists in cold tier.
499
+
*/
500
+
async import(snapshot: StorageSnapshot): Promise<void> {
501
+
if (snapshot.version !== 1) {
502
+
throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
503
+
}
504
+
505
+
// Import metadata into all configured tiers
506
+
for (const key of snapshot.keys) {
507
+
const metadata = snapshot.metadata[key];
508
+
if (!metadata) continue;
509
+
510
+
if (this.config.tiers.hot) {
511
+
await this.config.tiers.hot.setMetadata(key, metadata);
512
+
}
513
+
514
+
if (this.config.tiers.warm) {
515
+
await this.config.tiers.warm.setMetadata(key, metadata);
516
+
}
517
+
518
+
await this.config.tiers.cold.setMetadata(key, metadata);
519
+
}
520
+
}
521
+
522
+
/**
523
+
* Bootstrap hot tier from warm tier.
524
+
*
525
+
* @param limit - Optional limit on number of items to load
526
+
* @returns Number of items loaded
527
+
*
528
+
* @remarks
529
+
* Loads the most frequently accessed items from warm into hot.
530
+
* Useful for warming up the cache after a restart.
531
+
* Items are sorted by: accessCount * lastAccessed timestamp (higher is better).
532
+
*/
533
+
async bootstrapHot(limit?: number): Promise<number> {
534
+
if (!this.config.tiers.hot || !this.config.tiers.warm) {
535
+
return 0;
536
+
}
537
+
538
+
let loaded = 0;
539
+
const keyMetadata: Array<[string, StorageMetadata]> = [];
540
+
541
+
// Load metadata for all keys
542
+
for await (const key of this.config.tiers.warm.listKeys()) {
543
+
const metadata = await this.config.tiers.warm.getMetadata(key);
544
+
if (metadata) {
545
+
keyMetadata.push([key, metadata]);
546
+
}
547
+
}
548
+
549
+
// Sort by access count * recency (simple scoring)
550
+
keyMetadata.sort((a, b) => {
551
+
const scoreA = a[1].accessCount * a[1].lastAccessed.getTime();
552
+
const scoreB = b[1].accessCount * b[1].lastAccessed.getTime();
553
+
return scoreB - scoreA;
554
+
});
555
+
556
+
// Load top N keys into hot tier
557
+
const keysToLoad = limit ? keyMetadata.slice(0, limit) : keyMetadata;
558
+
559
+
for (const [key, metadata] of keysToLoad) {
560
+
const data = await this.config.tiers.warm.get(key);
561
+
if (data) {
562
+
await this.config.tiers.hot.set(key, data, metadata);
563
+
loaded++;
564
+
}
565
+
}
566
+
567
+
return loaded;
568
+
}
569
+
570
+
/**
571
+
* Bootstrap warm tier from cold tier.
572
+
*
573
+
* @param options - Optional limit and date filter
574
+
* @returns Number of items loaded
575
+
*
576
+
* @remarks
577
+
* Loads recent items from cold into warm.
578
+
* Useful for:
579
+
* - Initial cache population
580
+
* - Recovering from warm tier failure
581
+
* - Migrating to a new warm tier implementation
582
+
*/
583
+
async bootstrapWarm(options?: { limit?: number; sinceDate?: Date }): Promise<number> {
584
+
if (!this.config.tiers.warm) {
585
+
return 0;
586
+
}
587
+
588
+
let loaded = 0;
589
+
590
+
for await (const key of this.config.tiers.cold.listKeys()) {
591
+
const metadata = await this.config.tiers.cold.getMetadata(key);
592
+
if (!metadata) continue;
593
+
594
+
// Skip if too old
595
+
if (options?.sinceDate && metadata.lastAccessed < options.sinceDate) {
596
+
continue;
597
+
}
598
+
599
+
const data = await this.config.tiers.cold.get(key);
600
+
if (data) {
601
+
await this.config.tiers.warm.set(key, data, metadata);
602
+
loaded++;
603
+
604
+
if (options?.limit && loaded >= options.limit) {
605
+
break;
606
+
}
607
+
}
608
+
}
609
+
610
+
return loaded;
611
+
}
612
+
613
+
/**
614
+
* Check if data has expired based on TTL.
615
+
*/
616
+
private isExpired(metadata: StorageMetadata): boolean {
617
+
if (!metadata.ttl) return false;
618
+
return Date.now() > metadata.ttl.getTime();
619
+
}
620
+
621
+
/**
622
+
* Update access statistics for a key.
623
+
*/
624
+
private async updateAccessStats(key: string, tier: 'hot' | 'warm' | 'cold'): Promise<void> {
625
+
const tierObj =
626
+
tier === 'hot'
627
+
? this.config.tiers.hot
628
+
: tier === 'warm'
629
+
? this.config.tiers.warm
630
+
: this.config.tiers.cold;
631
+
632
+
if (!tierObj) return;
633
+
634
+
const metadata = await tierObj.getMetadata(key);
635
+
if (metadata) {
636
+
metadata.lastAccessed = new Date();
637
+
metadata.accessCount++;
638
+
await tierObj.setMetadata(key, metadata);
639
+
}
640
+
}
641
+
642
+
/**
643
+
* Create metadata for new data.
644
+
*/
645
+
private createMetadata(key: string, data: Uint8Array, options?: SetOptions): StorageMetadata {
646
+
const now = new Date();
647
+
const ttl = options?.ttl ?? this.config.defaultTTL;
648
+
649
+
const metadata: StorageMetadata = {
650
+
key,
651
+
size: data.byteLength,
652
+
createdAt: now,
653
+
lastAccessed: now,
654
+
accessCount: 0,
655
+
compressed: this.config.compression ?? false,
656
+
checksum: calculateChecksum(data),
657
+
};
658
+
659
+
if (ttl) {
660
+
metadata.ttl = new Date(now.getTime() + ttl);
661
+
}
662
+
663
+
if (options?.metadata) {
664
+
metadata.customMetadata = options.metadata;
665
+
}
666
+
667
+
return metadata;
668
+
}
669
+
670
+
/**
671
+
* Deserialize data, handling compression automatically.
672
+
*/
673
+
private async deserializeData(data: Uint8Array): Promise<unknown> {
674
+
// Decompress if needed (check for gzip magic bytes)
675
+
const finalData =
676
+
this.config.compression && data[0] === 0x1f && data[1] === 0x8b
677
+
? await decompress(data)
678
+
: data;
679
+
680
+
return this.deserialize(finalData);
681
+
}
682
+
}
+35
src/index.ts
+35
src/index.ts
···
1
+
/**
2
+
* Tiered Storage Library
3
+
*
4
+
* A lightweight, pluggable tiered storage library that orchestrates caching across
5
+
* hot (memory), warm (disk/database), and cold (S3/object storage) tiers.
6
+
*
7
+
* @packageDocumentation
8
+
*/
9
+
10
+
// Main class
11
+
export { TieredStorage } from './TieredStorage.js';
12
+
13
+
// Built-in tier implementations
14
+
export { MemoryStorageTier, type MemoryStorageTierConfig } from './tiers/MemoryStorageTier.js';
15
+
export { DiskStorageTier, type DiskStorageTierConfig, type EvictionPolicy } from './tiers/DiskStorageTier.js';
16
+
export { S3StorageTier, type S3StorageTierConfig } from './tiers/S3StorageTier.js';
17
+
18
+
// Types
19
+
export type {
20
+
StorageTier,
21
+
StorageMetadata,
22
+
TierStats,
23
+
AllTierStats,
24
+
TieredStorageConfig,
25
+
SetOptions,
26
+
StorageResult,
27
+
SetResult,
28
+
StorageSnapshot,
29
+
} from './types/index.js';
30
+
31
+
// Utilities
32
+
export { compress, decompress, isGzipped } from './utils/compression.js';
33
+
export { defaultSerialize, defaultDeserialize } from './utils/serialization.js';
34
+
export { calculateChecksum, verifyChecksum } from './utils/checksum.js';
35
+
export { encodeKey, decodeKey } from './utils/path-encoding.js';
+367
src/tiers/DiskStorageTier.ts
+367
src/tiers/DiskStorageTier.ts
···
1
+
import { readFile, writeFile, unlink, readdir, stat, mkdir, rm, rename } from 'node:fs/promises';
2
+
import { existsSync } from 'node:fs';
3
+
import { join, dirname } from 'node:path';
4
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
5
+
import { encodeKey } from '../utils/path-encoding.js';
6
+
7
+
/**
8
+
* Eviction policy for disk tier when size limit is reached.
9
+
*/
10
+
export type EvictionPolicy = 'lru' | 'fifo' | 'size';
11
+
12
+
/**
13
+
* Configuration for DiskStorageTier.
14
+
*/
15
+
export interface DiskStorageTierConfig {
16
+
/**
17
+
* Directory path where files will be stored.
18
+
*
19
+
* @remarks
20
+
* Created automatically if it doesn't exist.
21
+
* Files are stored as: `{directory}/{encoded-key}`
22
+
* Metadata is stored as: `{directory}/{encoded-key}.meta`
23
+
*/
24
+
directory: string;
25
+
26
+
/**
27
+
* Optional maximum size in bytes.
28
+
*
29
+
* @remarks
30
+
* When this limit is reached, files are evicted according to the eviction policy.
31
+
* If not set, no size limit is enforced (grows unbounded).
32
+
*/
33
+
maxSizeBytes?: number;
34
+
35
+
/**
36
+
* Eviction policy when maxSizeBytes is reached.
37
+
*
38
+
* @defaultValue 'lru'
39
+
*
40
+
* @remarks
41
+
* - 'lru': Evict least-recently-accessed files (based on metadata.lastAccessed)
42
+
* - 'fifo': Evict oldest files (based on metadata.createdAt)
43
+
* - 'size': Evict largest files first
44
+
*/
45
+
evictionPolicy?: EvictionPolicy;
46
+
}
47
+
48
+
/**
49
+
* Filesystem-based storage tier.
50
+
*
51
+
* @remarks
52
+
* - Stores data files and `.meta` JSON files side-by-side
53
+
* - Keys are encoded to be filesystem-safe
54
+
* - Human-readable file structure for debugging
55
+
* - Optional size-based eviction with configurable policy
56
+
* - Zero external dependencies (uses Node.js fs APIs)
57
+
*
58
+
* File structure:
59
+
* ```
60
+
* cache/
61
+
* ├── user%3A123 # Data file (encoded key)
62
+
* ├── user%3A123.meta # Metadata JSON
63
+
* ├── site%3Aabc%2Findex.html
64
+
* └── site%3Aabc%2Findex.html.meta
65
+
* ```
66
+
*
67
+
* @example
68
+
* ```typescript
69
+
* const tier = new DiskStorageTier({
70
+
* directory: './cache',
71
+
* maxSizeBytes: 10 * 1024 * 1024 * 1024, // 10GB
72
+
* evictionPolicy: 'lru',
73
+
* });
74
+
*
75
+
* await tier.set('key', data, metadata);
76
+
* const retrieved = await tier.get('key');
77
+
* ```
78
+
*/
79
+
export class DiskStorageTier implements StorageTier {
80
+
private metadataIndex = new Map<
81
+
string,
82
+
{ size: number; createdAt: Date; lastAccessed: Date }
83
+
>();
84
+
private currentSize = 0;
85
+
86
+
constructor(private config: DiskStorageTierConfig) {
87
+
if (!config.directory) {
88
+
throw new Error('directory is required');
89
+
}
90
+
if (config.maxSizeBytes !== undefined && config.maxSizeBytes <= 0) {
91
+
throw new Error('maxSizeBytes must be positive');
92
+
}
93
+
94
+
void this.ensureDirectory();
95
+
void this.rebuildIndex();
96
+
}
97
+
98
+
private async rebuildIndex(): Promise<void> {
99
+
if (!existsSync(this.config.directory)) {
100
+
return;
101
+
}
102
+
103
+
const files = await readdir(this.config.directory);
104
+
105
+
for (const file of files) {
106
+
if (file.endsWith('.meta')) {
107
+
continue;
108
+
}
109
+
110
+
try {
111
+
const metaPath = join(this.config.directory, `${file}.meta`);
112
+
const metaContent = await readFile(metaPath, 'utf-8');
113
+
const metadata = JSON.parse(metaContent) as StorageMetadata;
114
+
const filePath = join(this.config.directory, file);
115
+
const fileStats = await stat(filePath);
116
+
117
+
this.metadataIndex.set(metadata.key, {
118
+
size: fileStats.size,
119
+
createdAt: new Date(metadata.createdAt),
120
+
lastAccessed: new Date(metadata.lastAccessed),
121
+
});
122
+
123
+
this.currentSize += fileStats.size;
124
+
} catch {
125
+
continue;
126
+
}
127
+
}
128
+
}
129
+
130
+
async get(key: string): Promise<Uint8Array | null> {
131
+
const filePath = this.getFilePath(key);
132
+
133
+
try {
134
+
const data = await readFile(filePath);
135
+
136
+
const metadata = await this.getMetadata(key);
137
+
if (metadata) {
138
+
metadata.lastAccessed = new Date();
139
+
metadata.accessCount++;
140
+
await this.setMetadata(key, metadata);
141
+
142
+
const entry = this.metadataIndex.get(key);
143
+
if (entry) {
144
+
entry.lastAccessed = metadata.lastAccessed;
145
+
}
146
+
}
147
+
148
+
return new Uint8Array(data);
149
+
} catch (error) {
150
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
151
+
return null;
152
+
}
153
+
throw error;
154
+
}
155
+
}
156
+
157
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
158
+
const filePath = this.getFilePath(key);
159
+
const metaPath = this.getMetaPath(key);
160
+
161
+
const dir = dirname(filePath);
162
+
if (!existsSync(dir)) {
163
+
await mkdir(dir, { recursive: true });
164
+
}
165
+
166
+
const existingEntry = this.metadataIndex.get(key);
167
+
if (existingEntry) {
168
+
this.currentSize -= existingEntry.size;
169
+
}
170
+
171
+
if (this.config.maxSizeBytes) {
172
+
await this.evictIfNeeded(data.byteLength);
173
+
}
174
+
175
+
const tempMetaPath = `${metaPath}.tmp`;
176
+
await writeFile(tempMetaPath, JSON.stringify(metadata, null, 2));
177
+
await writeFile(filePath, data);
178
+
await rename(tempMetaPath, metaPath);
179
+
180
+
this.metadataIndex.set(key, {
181
+
size: data.byteLength,
182
+
createdAt: metadata.createdAt,
183
+
lastAccessed: metadata.lastAccessed,
184
+
});
185
+
this.currentSize += data.byteLength;
186
+
}
187
+
188
+
async delete(key: string): Promise<void> {
189
+
const filePath = this.getFilePath(key);
190
+
const metaPath = this.getMetaPath(key);
191
+
192
+
const entry = this.metadataIndex.get(key);
193
+
if (entry) {
194
+
this.currentSize -= entry.size;
195
+
this.metadataIndex.delete(key);
196
+
}
197
+
198
+
await Promise.all([
199
+
unlink(filePath).catch(() => {}),
200
+
unlink(metaPath).catch(() => {}),
201
+
]);
202
+
}
203
+
204
+
async exists(key: string): Promise<boolean> {
205
+
const filePath = this.getFilePath(key);
206
+
return existsSync(filePath);
207
+
}
208
+
209
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
210
+
if (!existsSync(this.config.directory)) {
211
+
return;
212
+
}
213
+
214
+
const files = await readdir(this.config.directory);
215
+
216
+
for (const file of files) {
217
+
// Skip metadata files
218
+
if (file.endsWith('.meta')) {
219
+
continue;
220
+
}
221
+
222
+
// The file name is the encoded key
223
+
// We need to read metadata to get the original key for prefix matching
224
+
const metaPath = join(this.config.directory, `${file}.meta`);
225
+
try {
226
+
const metaContent = await readFile(metaPath, 'utf-8');
227
+
const metadata = JSON.parse(metaContent) as StorageMetadata;
228
+
const originalKey = metadata.key;
229
+
230
+
if (!prefix || originalKey.startsWith(prefix)) {
231
+
yield originalKey;
232
+
}
233
+
} catch {
234
+
// If metadata is missing or invalid, skip this file
235
+
continue;
236
+
}
237
+
}
238
+
}
239
+
240
+
async deleteMany(keys: string[]): Promise<void> {
241
+
await Promise.all(keys.map((key) => this.delete(key)));
242
+
}
243
+
244
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
245
+
const metaPath = this.getMetaPath(key);
246
+
247
+
try {
248
+
const content = await readFile(metaPath, 'utf-8');
249
+
const metadata = JSON.parse(content) as StorageMetadata;
250
+
251
+
// Convert date strings back to Date objects
252
+
metadata.createdAt = new Date(metadata.createdAt);
253
+
metadata.lastAccessed = new Date(metadata.lastAccessed);
254
+
if (metadata.ttl) {
255
+
metadata.ttl = new Date(metadata.ttl);
256
+
}
257
+
258
+
return metadata;
259
+
} catch (error) {
260
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
261
+
return null;
262
+
}
263
+
throw error;
264
+
}
265
+
}
266
+
267
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
268
+
const metaPath = this.getMetaPath(key);
269
+
270
+
// Ensure parent directory exists
271
+
const dir = dirname(metaPath);
272
+
if (!existsSync(dir)) {
273
+
await mkdir(dir, { recursive: true });
274
+
}
275
+
276
+
await writeFile(metaPath, JSON.stringify(metadata, null, 2));
277
+
}
278
+
279
+
async getStats(): Promise<TierStats> {
280
+
let bytes = 0;
281
+
let items = 0;
282
+
283
+
if (!existsSync(this.config.directory)) {
284
+
return { bytes: 0, items: 0 };
285
+
}
286
+
287
+
const files = await readdir(this.config.directory);
288
+
289
+
for (const file of files) {
290
+
if (file.endsWith('.meta')) {
291
+
continue;
292
+
}
293
+
294
+
const filePath = join(this.config.directory, file);
295
+
const stats = await stat(filePath);
296
+
bytes += stats.size;
297
+
items++;
298
+
}
299
+
300
+
return { bytes, items };
301
+
}
302
+
303
+
async clear(): Promise<void> {
304
+
if (existsSync(this.config.directory)) {
305
+
await rm(this.config.directory, { recursive: true, force: true });
306
+
await this.ensureDirectory();
307
+
this.metadataIndex.clear();
308
+
this.currentSize = 0;
309
+
}
310
+
}
311
+
312
+
/**
313
+
* Get the filesystem path for a key's data file.
314
+
*/
315
+
private getFilePath(key: string): string {
316
+
const encoded = encodeKey(key);
317
+
return join(this.config.directory, encoded);
318
+
}
319
+
320
+
/**
321
+
* Get the filesystem path for a key's metadata file.
322
+
*/
323
+
private getMetaPath(key: string): string {
324
+
return `${this.getFilePath(key)}.meta`;
325
+
}
326
+
327
+
private async ensureDirectory(): Promise<void> {
328
+
await mkdir(this.config.directory, { recursive: true }).catch(() => {});
329
+
}
330
+
331
+
private async evictIfNeeded(incomingSize: number): Promise<void> {
332
+
if (!this.config.maxSizeBytes) {
333
+
return;
334
+
}
335
+
336
+
if (this.currentSize + incomingSize <= this.config.maxSizeBytes) {
337
+
return;
338
+
}
339
+
340
+
const entries = Array.from(this.metadataIndex.entries()).map(([key, info]) => ({
341
+
key,
342
+
...info,
343
+
}));
344
+
345
+
const policy = this.config.evictionPolicy ?? 'lru';
346
+
entries.sort((a, b) => {
347
+
switch (policy) {
348
+
case 'lru':
349
+
return a.lastAccessed.getTime() - b.lastAccessed.getTime();
350
+
case 'fifo':
351
+
return a.createdAt.getTime() - b.createdAt.getTime();
352
+
case 'size':
353
+
return b.size - a.size;
354
+
default:
355
+
return 0;
356
+
}
357
+
});
358
+
359
+
for (const entry of entries) {
360
+
if (this.currentSize + incomingSize <= this.config.maxSizeBytes) {
361
+
break;
362
+
}
363
+
364
+
await this.delete(entry.key);
365
+
}
366
+
}
367
+
}
+195
src/tiers/MemoryStorageTier.ts
+195
src/tiers/MemoryStorageTier.ts
···
1
+
import { lru } from 'tiny-lru';
2
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
3
+
4
+
interface CacheEntry {
5
+
data: Uint8Array;
6
+
metadata: StorageMetadata;
7
+
size: number;
8
+
}
9
+
10
+
/**
11
+
* Configuration for MemoryStorageTier.
12
+
*/
13
+
export interface MemoryStorageTierConfig {
14
+
/**
15
+
* Maximum total size in bytes.
16
+
*
17
+
* @remarks
18
+
* When this limit is reached, least-recently-used entries are evicted.
19
+
*/
20
+
maxSizeBytes: number;
21
+
22
+
/**
23
+
* Maximum number of items.
24
+
*
25
+
* @remarks
26
+
* When this limit is reached, least-recently-used entries are evicted.
27
+
* Useful for limiting memory usage when items have variable sizes.
28
+
*/
29
+
maxItems?: number;
30
+
}
31
+
32
+
/**
33
+
* In-memory storage tier using TinyLRU for LRU eviction.
34
+
*
35
+
* @remarks
36
+
* - Uses the battle-tested TinyLRU library for efficient LRU caching
37
+
* - Automatically evicts least-recently-used entries when limits are reached
38
+
* - Not distributed - single process only
39
+
* - Data is lost on process restart (use warm/cold tiers for persistence)
40
+
* - Implements both size-based and count-based eviction
41
+
*
42
+
* @example
43
+
* ```typescript
44
+
* const tier = new MemoryStorageTier({
45
+
* maxSizeBytes: 100 * 1024 * 1024, // 100MB
46
+
* maxItems: 1000,
47
+
* });
48
+
*
49
+
* await tier.set('key', data, metadata);
50
+
* const retrieved = await tier.get('key');
51
+
* ```
52
+
*/
53
+
export class MemoryStorageTier implements StorageTier {
54
+
private cache: ReturnType<typeof lru<CacheEntry>>;
55
+
private currentSize = 0;
56
+
private stats = {
57
+
hits: 0,
58
+
misses: 0,
59
+
evictions: 0,
60
+
};
61
+
62
+
constructor(private config: MemoryStorageTierConfig) {
63
+
if (config.maxSizeBytes <= 0) {
64
+
throw new Error('maxSizeBytes must be positive');
65
+
}
66
+
if (config.maxItems !== undefined && config.maxItems <= 0) {
67
+
throw new Error('maxItems must be positive');
68
+
}
69
+
70
+
// Initialize TinyLRU with max items (we'll handle size limits separately)
71
+
const maxItems = config.maxItems ?? 10000; // Default to 10k items if not specified
72
+
this.cache = lru<CacheEntry>(maxItems);
73
+
}
74
+
75
+
async get(key: string): Promise<Uint8Array | null> {
76
+
const entry = this.cache.get(key);
77
+
78
+
if (!entry) {
79
+
this.stats.misses++;
80
+
return null;
81
+
}
82
+
83
+
this.stats.hits++;
84
+
return entry.data;
85
+
}
86
+
87
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
88
+
const size = data.byteLength;
89
+
90
+
// Check existing entry for size accounting
91
+
const existing = this.cache.get(key);
92
+
if (existing) {
93
+
this.currentSize -= existing.size;
94
+
}
95
+
96
+
// Evict entries until we have space for the new entry
97
+
await this.evictIfNeeded(size);
98
+
99
+
// Add new entry
100
+
const entry: CacheEntry = { data, metadata, size };
101
+
this.cache.set(key, entry);
102
+
this.currentSize += size;
103
+
}
104
+
105
+
async delete(key: string): Promise<void> {
106
+
const entry = this.cache.get(key);
107
+
if (entry) {
108
+
this.cache.delete(key);
109
+
this.currentSize -= entry.size;
110
+
}
111
+
}
112
+
113
+
async exists(key: string): Promise<boolean> {
114
+
return this.cache.has(key);
115
+
}
116
+
117
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
118
+
// TinyLRU doesn't expose keys(), so we need to track them separately
119
+
// For now, we'll use the cache's internal structure
120
+
const keys = this.cache.keys();
121
+
for (const key of keys) {
122
+
if (!prefix || key.startsWith(prefix)) {
123
+
yield key;
124
+
}
125
+
}
126
+
}
127
+
128
+
async deleteMany(keys: string[]): Promise<void> {
129
+
for (const key of keys) {
130
+
await this.delete(key);
131
+
}
132
+
}
133
+
134
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
135
+
const entry = this.cache.get(key);
136
+
return entry ? entry.metadata : null;
137
+
}
138
+
139
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
140
+
const entry = this.cache.get(key);
141
+
if (entry) {
142
+
// Update metadata in place
143
+
entry.metadata = metadata;
144
+
// Re-set to mark as recently used
145
+
this.cache.set(key, entry);
146
+
}
147
+
}
148
+
149
+
async getStats(): Promise<TierStats> {
150
+
return {
151
+
bytes: this.currentSize,
152
+
items: this.cache.size,
153
+
hits: this.stats.hits,
154
+
misses: this.stats.misses,
155
+
evictions: this.stats.evictions,
156
+
};
157
+
}
158
+
159
+
async clear(): Promise<void> {
160
+
this.cache.clear();
161
+
this.currentSize = 0;
162
+
}
163
+
164
+
/**
165
+
* Evict least-recently-used entries until there's space for new data.
166
+
*
167
+
* @param incomingSize - Size of data being added
168
+
*
169
+
* @remarks
170
+
* TinyLRU handles count-based eviction automatically.
171
+
* This method handles size-based eviction by using TinyLRU's built-in evict() method,
172
+
* which properly removes the LRU item without updating access order.
173
+
*/
174
+
private async evictIfNeeded(incomingSize: number): Promise<void> {
175
+
// Keep evicting until we have enough space
176
+
while (this.currentSize + incomingSize > this.config.maxSizeBytes && this.cache.size > 0) {
177
+
// Get the LRU key (first in the list) without accessing it
178
+
const keys = this.cache.keys();
179
+
if (keys.length === 0) break;
180
+
181
+
const lruKey = keys[0];
182
+
if (!lruKey) break;
183
+
184
+
// Access the entry directly from internal items without triggering LRU update
185
+
// TinyLRU exposes items as a public property for this purpose
186
+
const entry = (this.cache as any).items[lruKey] as CacheEntry | undefined;
187
+
if (!entry) break;
188
+
189
+
// Use TinyLRU's built-in evict() which properly removes the LRU item
190
+
this.cache.evict();
191
+
this.currentSize -= entry.size;
192
+
this.stats.evictions++;
193
+
}
194
+
}
195
+
}
+567
src/tiers/S3StorageTier.ts
+567
src/tiers/S3StorageTier.ts
···
1
+
import {
2
+
S3Client,
3
+
PutObjectCommand,
4
+
GetObjectCommand,
5
+
DeleteObjectCommand,
6
+
HeadObjectCommand,
7
+
ListObjectsV2Command,
8
+
DeleteObjectsCommand,
9
+
CopyObjectCommand,
10
+
type S3ClientConfig,
11
+
} from '@aws-sdk/client-s3';
12
+
import type { Readable } from 'node:stream';
13
+
import type { StorageTier, StorageMetadata, TierStats } from '../types/index.js';
14
+
15
+
/**
16
+
* Configuration for S3StorageTier.
17
+
*/
18
+
export interface S3StorageTierConfig {
19
+
/**
20
+
* S3 bucket name.
21
+
*/
22
+
bucket: string;
23
+
24
+
/**
25
+
* AWS region.
26
+
*/
27
+
region: string;
28
+
29
+
/**
30
+
* Optional S3-compatible endpoint (for R2, Minio, etc.).
31
+
*
32
+
* @example 'https://s3.us-east-1.amazonaws.com'
33
+
* @example 'https://account-id.r2.cloudflarestorage.com'
34
+
*/
35
+
endpoint?: string;
36
+
37
+
/**
38
+
* Optional AWS credentials.
39
+
*
40
+
* @remarks
41
+
* If not provided, uses the default AWS credential chain
42
+
* (environment variables, ~/.aws/credentials, IAM roles, etc.)
43
+
*/
44
+
credentials?: {
45
+
accessKeyId: string;
46
+
secretAccessKey: string;
47
+
};
48
+
49
+
/**
50
+
* Optional key prefix for namespacing.
51
+
*
52
+
* @remarks
53
+
* All keys will be prefixed with this value.
54
+
* Useful for multi-tenant scenarios or organizing data.
55
+
*
56
+
* @example 'tiered-storage/'
57
+
*/
58
+
prefix?: string;
59
+
60
+
/**
61
+
* Force path-style addressing for S3-compatible services.
62
+
*
63
+
* @defaultValue true
64
+
*
65
+
* @remarks
66
+
* Most S3-compatible services (MinIO, R2, etc.) require path-style URLs.
67
+
* AWS S3 uses virtual-hosted-style by default, but path-style also works.
68
+
*
69
+
* - true: `https://endpoint.com/bucket/key` (path-style)
70
+
* - false: `https://bucket.endpoint.com/key` (virtual-hosted-style)
71
+
*/
72
+
forcePathStyle?: boolean;
73
+
74
+
/**
75
+
* Optional separate bucket for storing metadata.
76
+
*
77
+
* @remarks
78
+
* **RECOMMENDED for production use!**
79
+
*
80
+
* By default, metadata is stored in S3 object metadata fields. However, updating
81
+
* metadata requires copying the entire object, which is slow and expensive for large files.
82
+
*
83
+
* When `metadataBucket` is specified, metadata is stored as separate JSON objects
84
+
* in this bucket. This allows fast, cheap metadata updates without copying data.
85
+
*
86
+
* **Benefits:**
87
+
* - Fast metadata updates (no object copying)
88
+
* - Much cheaper for large objects
89
+
* - No impact on data object performance
90
+
*
91
+
* **Trade-offs:**
92
+
* - Requires managing two buckets
93
+
* - Metadata and data could become out of sync if not handled carefully
94
+
* - Additional S3 API calls for metadata operations
95
+
*
96
+
* @example
97
+
* ```typescript
98
+
* const tier = new S3StorageTier({
99
+
* bucket: 'my-data-bucket',
100
+
* metadataBucket: 'my-metadata-bucket', // Separate bucket for metadata
101
+
* region: 'us-east-1',
102
+
* });
103
+
* ```
104
+
*/
105
+
metadataBucket?: string;
106
+
}
107
+
108
+
/**
109
+
* AWS S3 (or compatible) storage tier.
110
+
*
111
+
* @remarks
112
+
* - Supports AWS S3, Cloudflare R2, MinIO, and other S3-compatible services
113
+
* - Uses object metadata for StorageMetadata
114
+
* - Requires `@aws-sdk/client-s3` peer dependency
115
+
* - Typically used as the cold tier (source of truth)
116
+
*
117
+
* **Metadata Storage:**
118
+
* Metadata is stored in S3 object metadata fields:
119
+
* - Custom metadata fields are prefixed with `x-amz-meta-`
120
+
* - Built-in fields use standard S3 headers
121
+
*
122
+
* @example
123
+
* ```typescript
124
+
* const tier = new S3StorageTier({
125
+
* bucket: 'my-bucket',
126
+
* region: 'us-east-1',
127
+
* credentials: {
128
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
129
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
130
+
* },
131
+
* prefix: 'cache/',
132
+
* });
133
+
* ```
134
+
*
135
+
* @example Cloudflare R2
136
+
* ```typescript
137
+
* const tier = new S3StorageTier({
138
+
* bucket: 'my-bucket',
139
+
* region: 'auto',
140
+
* endpoint: 'https://account-id.r2.cloudflarestorage.com',
141
+
* credentials: {
142
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
143
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
144
+
* },
145
+
* });
146
+
* ```
147
+
*/
148
+
export class S3StorageTier implements StorageTier {
149
+
private client: S3Client;
150
+
private prefix: string;
151
+
private metadataBucket?: string;
152
+
153
+
constructor(private config: S3StorageTierConfig) {
154
+
const clientConfig: S3ClientConfig = {
155
+
region: config.region,
156
+
// Most S3-compatible services need path-style URLs
157
+
forcePathStyle: config.forcePathStyle ?? true,
158
+
...(config.endpoint && { endpoint: config.endpoint }),
159
+
...(config.credentials && { credentials: config.credentials }),
160
+
};
161
+
162
+
this.client = new S3Client(clientConfig);
163
+
this.prefix = config.prefix ?? '';
164
+
if (config.metadataBucket) {
165
+
this.metadataBucket = config.metadataBucket;
166
+
}
167
+
}
168
+
169
+
async get(key: string): Promise<Uint8Array | null> {
170
+
try {
171
+
const command = new GetObjectCommand({
172
+
Bucket: this.config.bucket,
173
+
Key: this.getS3Key(key),
174
+
});
175
+
176
+
const response = await this.client.send(command);
177
+
178
+
if (!response.Body) {
179
+
return null;
180
+
}
181
+
182
+
return await this.streamToUint8Array(response.Body as Readable);
183
+
} catch (error) {
184
+
if (this.isNoSuchKeyError(error)) {
185
+
return null;
186
+
}
187
+
throw error;
188
+
}
189
+
}
190
+
191
+
private async streamToUint8Array(stream: Readable): Promise<Uint8Array> {
192
+
const chunks: Uint8Array[] = [];
193
+
194
+
for await (const chunk of stream) {
195
+
if (Buffer.isBuffer(chunk)) {
196
+
chunks.push(new Uint8Array(chunk));
197
+
} else if (chunk instanceof Uint8Array) {
198
+
chunks.push(chunk);
199
+
} else {
200
+
throw new Error('Unexpected chunk type in S3 stream');
201
+
}
202
+
}
203
+
204
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
205
+
const result = new Uint8Array(totalLength);
206
+
let offset = 0;
207
+
for (const chunk of chunks) {
208
+
result.set(chunk, offset);
209
+
offset += chunk.length;
210
+
}
211
+
212
+
return result;
213
+
}
214
+
215
+
private isNoSuchKeyError(error: unknown): boolean {
216
+
return (
217
+
typeof error === 'object' &&
218
+
error !== null &&
219
+
'name' in error &&
220
+
(error.name === 'NoSuchKey' || error.name === 'NotFound')
221
+
);
222
+
}
223
+
224
+
async set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void> {
225
+
const s3Key = this.getS3Key(key);
226
+
227
+
if (this.metadataBucket) {
228
+
const dataCommand = new PutObjectCommand({
229
+
Bucket: this.config.bucket,
230
+
Key: s3Key,
231
+
Body: data,
232
+
ContentLength: data.byteLength,
233
+
});
234
+
235
+
const metadataJson = JSON.stringify(metadata);
236
+
const metadataBuffer = new TextEncoder().encode(metadataJson);
237
+
const metadataCommand = new PutObjectCommand({
238
+
Bucket: this.metadataBucket,
239
+
Key: s3Key + '.meta',
240
+
Body: metadataBuffer,
241
+
ContentType: 'application/json',
242
+
});
243
+
244
+
await Promise.all([
245
+
this.client.send(dataCommand),
246
+
this.client.send(metadataCommand),
247
+
]);
248
+
} else {
249
+
const command = new PutObjectCommand({
250
+
Bucket: this.config.bucket,
251
+
Key: s3Key,
252
+
Body: data,
253
+
ContentLength: data.byteLength,
254
+
Metadata: this.metadataToS3(metadata),
255
+
});
256
+
257
+
await this.client.send(command);
258
+
}
259
+
}
260
+
261
+
async delete(key: string): Promise<void> {
262
+
const s3Key = this.getS3Key(key);
263
+
264
+
try {
265
+
const dataCommand = new DeleteObjectCommand({
266
+
Bucket: this.config.bucket,
267
+
Key: s3Key,
268
+
});
269
+
270
+
if (this.metadataBucket) {
271
+
const metadataCommand = new DeleteObjectCommand({
272
+
Bucket: this.metadataBucket,
273
+
Key: s3Key + '.meta',
274
+
});
275
+
276
+
await Promise.all([
277
+
this.client.send(dataCommand),
278
+
this.client.send(metadataCommand).catch((error) => {
279
+
if (!this.isNoSuchKeyError(error)) throw error;
280
+
}),
281
+
]);
282
+
} else {
283
+
await this.client.send(dataCommand);
284
+
}
285
+
} catch (error) {
286
+
if (!this.isNoSuchKeyError(error)) {
287
+
throw error;
288
+
}
289
+
}
290
+
}
291
+
292
+
async exists(key: string): Promise<boolean> {
293
+
try {
294
+
const command = new HeadObjectCommand({
295
+
Bucket: this.config.bucket,
296
+
Key: this.getS3Key(key),
297
+
});
298
+
299
+
await this.client.send(command);
300
+
return true;
301
+
} catch (error) {
302
+
if (this.isNoSuchKeyError(error)) {
303
+
return false;
304
+
}
305
+
throw error;
306
+
}
307
+
}
308
+
309
+
async *listKeys(prefix?: string): AsyncIterableIterator<string> {
310
+
const s3Prefix = prefix ? this.getS3Key(prefix) : this.prefix;
311
+
let continuationToken: string | undefined;
312
+
313
+
do {
314
+
const command = new ListObjectsV2Command({
315
+
Bucket: this.config.bucket,
316
+
Prefix: s3Prefix,
317
+
ContinuationToken: continuationToken,
318
+
});
319
+
320
+
const response = await this.client.send(command);
321
+
322
+
if (response.Contents) {
323
+
for (const object of response.Contents) {
324
+
if (object.Key) {
325
+
// Remove prefix to get original key
326
+
const key = this.removePrefix(object.Key);
327
+
yield key;
328
+
}
329
+
}
330
+
}
331
+
332
+
continuationToken = response.NextContinuationToken;
333
+
} while (continuationToken);
334
+
}
335
+
336
+
async deleteMany(keys: string[]): Promise<void> {
337
+
if (keys.length === 0) return;
338
+
339
+
const batchSize = 1000;
340
+
341
+
for (let i = 0; i < keys.length; i += batchSize) {
342
+
const batch = keys.slice(i, i + batchSize);
343
+
344
+
const dataCommand = new DeleteObjectsCommand({
345
+
Bucket: this.config.bucket,
346
+
Delete: {
347
+
Objects: batch.map((key) => ({ Key: this.getS3Key(key) })),
348
+
},
349
+
});
350
+
351
+
if (this.metadataBucket) {
352
+
const metadataCommand = new DeleteObjectsCommand({
353
+
Bucket: this.metadataBucket,
354
+
Delete: {
355
+
Objects: batch.map((key) => ({ Key: this.getS3Key(key) + '.meta' })),
356
+
},
357
+
});
358
+
359
+
await Promise.all([
360
+
this.client.send(dataCommand),
361
+
this.client.send(metadataCommand).catch(() => {}),
362
+
]);
363
+
} else {
364
+
await this.client.send(dataCommand);
365
+
}
366
+
}
367
+
}
368
+
369
+
async getMetadata(key: string): Promise<StorageMetadata | null> {
370
+
if (this.metadataBucket) {
371
+
try {
372
+
const command = new GetObjectCommand({
373
+
Bucket: this.metadataBucket,
374
+
Key: this.getS3Key(key) + '.meta',
375
+
});
376
+
377
+
const response = await this.client.send(command);
378
+
379
+
if (!response.Body) {
380
+
return null;
381
+
}
382
+
383
+
const buffer = await this.streamToUint8Array(response.Body as Readable);
384
+
const json = new TextDecoder().decode(buffer);
385
+
const metadata = JSON.parse(json) as StorageMetadata;
386
+
387
+
metadata.createdAt = new Date(metadata.createdAt);
388
+
metadata.lastAccessed = new Date(metadata.lastAccessed);
389
+
if (metadata.ttl) {
390
+
metadata.ttl = new Date(metadata.ttl);
391
+
}
392
+
393
+
return metadata;
394
+
} catch (error) {
395
+
if (this.isNoSuchKeyError(error)) {
396
+
return null;
397
+
}
398
+
throw error;
399
+
}
400
+
}
401
+
402
+
try {
403
+
const command = new HeadObjectCommand({
404
+
Bucket: this.config.bucket,
405
+
Key: this.getS3Key(key),
406
+
});
407
+
408
+
const response = await this.client.send(command);
409
+
410
+
if (!response.Metadata) {
411
+
return null;
412
+
}
413
+
414
+
return this.s3ToMetadata(response.Metadata);
415
+
} catch (error) {
416
+
if (this.isNoSuchKeyError(error)) {
417
+
return null;
418
+
}
419
+
throw error;
420
+
}
421
+
}
422
+
423
+
async setMetadata(key: string, metadata: StorageMetadata): Promise<void> {
424
+
if (this.metadataBucket) {
425
+
const metadataJson = JSON.stringify(metadata);
426
+
const buffer = new TextEncoder().encode(metadataJson);
427
+
428
+
const command = new PutObjectCommand({
429
+
Bucket: this.metadataBucket,
430
+
Key: this.getS3Key(key) + '.meta',
431
+
Body: buffer,
432
+
ContentType: 'application/json',
433
+
});
434
+
435
+
await this.client.send(command);
436
+
return;
437
+
}
438
+
439
+
const s3Key = this.getS3Key(key);
440
+
const command = new CopyObjectCommand({
441
+
Bucket: this.config.bucket,
442
+
Key: s3Key,
443
+
CopySource: `${this.config.bucket}/${s3Key}`,
444
+
Metadata: this.metadataToS3(metadata),
445
+
MetadataDirective: 'REPLACE',
446
+
});
447
+
448
+
await this.client.send(command);
449
+
}
450
+
451
+
async getStats(): Promise<TierStats> {
452
+
let bytes = 0;
453
+
let items = 0;
454
+
455
+
// List all objects and sum up sizes
456
+
let continuationToken: string | undefined;
457
+
458
+
do {
459
+
const command = new ListObjectsV2Command({
460
+
Bucket: this.config.bucket,
461
+
Prefix: this.prefix,
462
+
ContinuationToken: continuationToken,
463
+
});
464
+
465
+
const response = await this.client.send(command);
466
+
467
+
if (response.Contents) {
468
+
for (const object of response.Contents) {
469
+
items++;
470
+
bytes += object.Size ?? 0;
471
+
}
472
+
}
473
+
474
+
continuationToken = response.NextContinuationToken;
475
+
} while (continuationToken);
476
+
477
+
return { bytes, items };
478
+
}
479
+
480
+
async clear(): Promise<void> {
481
+
// List and delete all objects with the prefix
482
+
const keys: string[] = [];
483
+
484
+
for await (const key of this.listKeys()) {
485
+
keys.push(key);
486
+
}
487
+
488
+
await this.deleteMany(keys);
489
+
}
490
+
491
+
/**
492
+
* Get the full S3 key including prefix.
493
+
*/
494
+
private getS3Key(key: string): string {
495
+
return this.prefix + key;
496
+
}
497
+
498
+
/**
499
+
* Remove the prefix from an S3 key to get the original key.
500
+
*/
501
+
private removePrefix(s3Key: string): string {
502
+
if (this.prefix && s3Key.startsWith(this.prefix)) {
503
+
return s3Key.slice(this.prefix.length);
504
+
}
505
+
return s3Key;
506
+
}
507
+
508
+
/**
509
+
* Convert StorageMetadata to S3 metadata format.
510
+
*
511
+
* @remarks
512
+
* S3 metadata keys must be lowercase and values must be strings.
513
+
* We serialize complex values as JSON.
514
+
*/
515
+
private metadataToS3(metadata: StorageMetadata): Record<string, string> {
516
+
return {
517
+
key: metadata.key,
518
+
size: metadata.size.toString(),
519
+
createdat: metadata.createdAt.toISOString(),
520
+
lastaccessed: metadata.lastAccessed.toISOString(),
521
+
accesscount: metadata.accessCount.toString(),
522
+
compressed: metadata.compressed.toString(),
523
+
checksum: metadata.checksum,
524
+
...(metadata.ttl && { ttl: metadata.ttl.toISOString() }),
525
+
...(metadata.mimeType && { mimetype: metadata.mimeType }),
526
+
...(metadata.encoding && { encoding: metadata.encoding }),
527
+
...(metadata.customMetadata && { custom: JSON.stringify(metadata.customMetadata) }),
528
+
};
529
+
}
530
+
531
+
/**
532
+
* Convert S3 metadata to StorageMetadata format.
533
+
*/
534
+
private s3ToMetadata(s3Metadata: Record<string, string>): StorageMetadata {
535
+
const metadata: StorageMetadata = {
536
+
key: s3Metadata.key ?? '',
537
+
size: parseInt(s3Metadata.size ?? '0', 10),
538
+
createdAt: new Date(s3Metadata.createdat ?? Date.now()),
539
+
lastAccessed: new Date(s3Metadata.lastaccessed ?? Date.now()),
540
+
accessCount: parseInt(s3Metadata.accesscount ?? '0', 10),
541
+
compressed: s3Metadata.compressed === 'true',
542
+
checksum: s3Metadata.checksum ?? '',
543
+
};
544
+
545
+
if (s3Metadata.ttl) {
546
+
metadata.ttl = new Date(s3Metadata.ttl);
547
+
}
548
+
549
+
if (s3Metadata.mimetype) {
550
+
metadata.mimeType = s3Metadata.mimetype;
551
+
}
552
+
553
+
if (s3Metadata.encoding) {
554
+
metadata.encoding = s3Metadata.encoding;
555
+
}
556
+
557
+
if (s3Metadata.custom) {
558
+
try {
559
+
metadata.customMetadata = JSON.parse(s3Metadata.custom);
560
+
} catch {
561
+
// Ignore invalid JSON
562
+
}
563
+
}
564
+
565
+
return metadata;
566
+
}
567
+
}
+407
src/types/index.ts
+407
src/types/index.ts
···
1
+
/**
2
+
* Metadata associated with stored data in a tier.
3
+
*
4
+
* @remarks
5
+
* This metadata is stored alongside the actual data and is used for:
6
+
* - TTL management and expiration
7
+
* - Access tracking for LRU/eviction policies
8
+
* - Data integrity verification via checksum
9
+
* - Content type information for HTTP serving
10
+
*/
11
+
export interface StorageMetadata {
12
+
/** Original key used to store the data (human-readable) */
13
+
key: string;
14
+
15
+
/** Size of the data in bytes (uncompressed size) */
16
+
size: number;
17
+
18
+
/** Timestamp when the data was first created */
19
+
createdAt: Date;
20
+
21
+
/** Timestamp when the data was last accessed */
22
+
lastAccessed: Date;
23
+
24
+
/** Number of times this data has been accessed */
25
+
accessCount: number;
26
+
27
+
/** Optional expiration timestamp. Data expires when current time > ttl */
28
+
ttl?: Date;
29
+
30
+
/** Whether the data is compressed (e.g., with gzip) */
31
+
compressed: boolean;
32
+
33
+
/** SHA256 checksum of the data for integrity verification */
34
+
checksum: string;
35
+
36
+
/** Optional MIME type (e.g., 'text/html', 'application/json') */
37
+
mimeType?: string;
38
+
39
+
/** Optional encoding (e.g., 'gzip', 'base64') */
40
+
encoding?: string;
41
+
42
+
/** User-defined metadata fields */
43
+
customMetadata?: Record<string, string>;
44
+
}
45
+
46
+
/**
47
+
* Statistics for a single storage tier.
48
+
*
49
+
* @remarks
50
+
* Used for monitoring cache performance and capacity planning.
51
+
*/
52
+
export interface TierStats {
53
+
/** Total bytes stored in this tier */
54
+
bytes: number;
55
+
56
+
/** Total number of items stored in this tier */
57
+
items: number;
58
+
59
+
/** Number of cache hits (only tracked if tier implements hit tracking) */
60
+
hits?: number;
61
+
62
+
/** Number of cache misses (only tracked if tier implements miss tracking) */
63
+
misses?: number;
64
+
65
+
/** Number of evictions due to size/count limits (only tracked if tier implements eviction) */
66
+
evictions?: number;
67
+
}
68
+
69
+
/**
70
+
* Aggregated statistics across all configured tiers.
71
+
*
72
+
* @remarks
73
+
* Provides a complete view of cache performance across the entire storage hierarchy.
74
+
*/
75
+
export interface AllTierStats {
76
+
/** Statistics for hot tier (if configured) */
77
+
hot?: TierStats;
78
+
79
+
/** Statistics for warm tier (if configured) */
80
+
warm?: TierStats;
81
+
82
+
/** Statistics for cold tier (always present) */
83
+
cold: TierStats;
84
+
85
+
/** Total hits across all tiers */
86
+
totalHits: number;
87
+
88
+
/** Total misses across all tiers */
89
+
totalMisses: number;
90
+
91
+
/** Hit rate as a percentage (0-1) */
92
+
hitRate: number;
93
+
}
94
+
95
+
/**
96
+
* Interface that all storage tier implementations must satisfy.
97
+
*
98
+
* @remarks
99
+
* This is the core abstraction that allows pluggable backends.
100
+
* Implementations can be memory-based (Map, Redis), disk-based (filesystem, SQLite),
101
+
* or cloud-based (S3, R2, etc.).
102
+
*
103
+
* @example
104
+
* ```typescript
105
+
* class RedisStorageTier implements StorageTier {
106
+
* constructor(private client: RedisClient) {}
107
+
*
108
+
* async get(key: string): Promise<Uint8Array | null> {
109
+
* const buffer = await this.client.getBuffer(key);
110
+
* return buffer ? new Uint8Array(buffer) : null;
111
+
* }
112
+
*
113
+
* // ... implement other methods
114
+
* }
115
+
* ```
116
+
*/
117
+
export interface StorageTier {
118
+
/**
119
+
* Retrieve data for a key.
120
+
*
121
+
* @param key - The key to retrieve
122
+
* @returns The data as a Uint8Array, or null if not found
123
+
*/
124
+
get(key: string): Promise<Uint8Array | null>;
125
+
126
+
/**
127
+
* Store data with associated metadata.
128
+
*
129
+
* @param key - The key to store under
130
+
* @param data - The data to store (as Uint8Array)
131
+
* @param metadata - Metadata to store alongside the data
132
+
*
133
+
* @remarks
134
+
* If the key already exists, it should be overwritten.
135
+
*/
136
+
set(key: string, data: Uint8Array, metadata: StorageMetadata): Promise<void>;
137
+
138
+
/**
139
+
* Delete data for a key.
140
+
*
141
+
* @param key - The key to delete
142
+
*
143
+
* @remarks
144
+
* Should not throw if the key doesn't exist.
145
+
*/
146
+
delete(key: string): Promise<void>;
147
+
148
+
/**
149
+
* Check if a key exists in this tier.
150
+
*
151
+
* @param key - The key to check
152
+
* @returns true if the key exists, false otherwise
153
+
*/
154
+
exists(key: string): Promise<boolean>;
155
+
156
+
/**
157
+
* List all keys in this tier, optionally filtered by prefix.
158
+
*
159
+
* @param prefix - Optional prefix to filter keys (e.g., 'user:' matches 'user:123', 'user:456')
160
+
* @returns An async iterator of keys
161
+
*
162
+
* @remarks
163
+
* This should be memory-efficient and stream keys rather than loading all into memory.
164
+
* Useful for prefix-based invalidation and cache warming.
165
+
*
166
+
* @example
167
+
* ```typescript
168
+
* for await (const key of tier.listKeys('site:')) {
169
+
* console.log(key); // 'site:abc', 'site:xyz', etc.
170
+
* }
171
+
* ```
172
+
*/
173
+
listKeys(prefix?: string): AsyncIterableIterator<string>;
174
+
175
+
/**
176
+
* Delete multiple keys in a single operation.
177
+
*
178
+
* @param keys - Array of keys to delete
179
+
*
180
+
* @remarks
181
+
* This is more efficient than calling delete() in a loop.
182
+
* Implementations should batch deletions where possible.
183
+
*/
184
+
deleteMany(keys: string[]): Promise<void>;
185
+
186
+
/**
187
+
* Retrieve metadata for a key without fetching the data.
188
+
*
189
+
* @param key - The key to get metadata for
190
+
* @returns The metadata, or null if not found
191
+
*
192
+
* @remarks
193
+
* This is useful for checking TTL, access counts, etc. without loading large data.
194
+
*/
195
+
getMetadata(key: string): Promise<StorageMetadata | null>;
196
+
197
+
/**
198
+
* Update metadata for a key without modifying the data.
199
+
*
200
+
* @param key - The key to update metadata for
201
+
* @param metadata - The new metadata
202
+
*
203
+
* @remarks
204
+
* Useful for updating TTL (via touch()) or access counts.
205
+
*/
206
+
setMetadata(key: string, metadata: StorageMetadata): Promise<void>;
207
+
208
+
/**
209
+
* Get statistics about this tier.
210
+
*
211
+
* @returns Statistics including size, item count, hits, misses, etc.
212
+
*/
213
+
getStats(): Promise<TierStats>;
214
+
215
+
/**
216
+
* Clear all data from this tier.
217
+
*
218
+
* @remarks
219
+
* Use with caution! This will delete all data in the tier.
220
+
*/
221
+
clear(): Promise<void>;
222
+
}
223
+
224
+
/**
225
+
* Configuration for the TieredStorage system.
226
+
*
227
+
* @typeParam T - The type of data being stored (for serialization)
228
+
*
229
+
* @remarks
230
+
* The tiered storage system uses a cascading containment model:
231
+
* - Hot tier (optional): Fastest, smallest capacity (memory/Redis)
232
+
* - Warm tier (optional): Medium speed, medium capacity (disk/database)
233
+
* - Cold tier (required): Slowest, unlimited capacity (S3/object storage)
234
+
*
235
+
* Data flows down on writes (hot → warm → cold) and bubbles up on reads (cold → warm → hot).
236
+
*/
237
+
export interface TieredStorageConfig {
238
+
/** Storage tier configuration */
239
+
tiers: {
240
+
/** Optional hot tier - fastest, smallest capacity (e.g., in-memory, Redis) */
241
+
hot?: StorageTier;
242
+
243
+
/** Optional warm tier - medium speed, medium capacity (e.g., disk, SQLite, Postgres) */
244
+
warm?: StorageTier;
245
+
246
+
/** Required cold tier - slowest, largest capacity (e.g., S3, R2, object storage) */
247
+
cold: StorageTier;
248
+
};
249
+
250
+
/**
251
+
* Whether to automatically compress data before storing.
252
+
*
253
+
* @defaultValue false
254
+
*
255
+
* @remarks
256
+
* Uses gzip compression. Compression is transparent - data is automatically
257
+
* decompressed on retrieval. The `compressed` flag in metadata indicates compression state.
258
+
*/
259
+
compression?: boolean;
260
+
261
+
/**
262
+
* Default TTL (time-to-live) in milliseconds.
263
+
*
264
+
* @remarks
265
+
* Data will expire after this duration. Can be overridden per-key via SetOptions.
266
+
* If not set, data never expires.
267
+
*/
268
+
defaultTTL?: number;
269
+
270
+
/**
271
+
* Strategy for promoting data to upper tiers on cache miss.
272
+
*
273
+
* @defaultValue 'lazy'
274
+
*
275
+
* @remarks
276
+
* - 'eager': Immediately promote data to all upper tiers on read
277
+
* - 'lazy': Don't automatically promote; rely on explicit promotion or next write
278
+
*
279
+
* Eager promotion increases hot tier hit rate but adds write overhead.
280
+
* Lazy promotion reduces writes but may serve from lower tiers more often.
281
+
*/
282
+
promotionStrategy?: 'eager' | 'lazy';
283
+
284
+
/**
285
+
* Custom serialization/deserialization functions.
286
+
*
287
+
* @remarks
288
+
* By default, JSON serialization is used. Provide custom functions for:
289
+
* - Non-JSON types (e.g., Buffer, custom classes)
290
+
* - Performance optimization (e.g., msgpack, protobuf)
291
+
* - Encryption (serialize includes encryption, deserialize includes decryption)
292
+
*/
293
+
serialization?: {
294
+
/** Convert data to Uint8Array for storage */
295
+
serialize: (data: unknown) => Promise<Uint8Array>;
296
+
297
+
/** Convert Uint8Array back to original data */
298
+
deserialize: (data: Uint8Array) => Promise<unknown>;
299
+
};
300
+
}
301
+
302
+
/**
303
+
* Options for setting data in the cache.
304
+
*
305
+
* @remarks
306
+
* These options allow fine-grained control over where and how data is stored.
307
+
*/
308
+
export interface SetOptions {
309
+
/**
310
+
* Custom TTL in milliseconds for this specific key.
311
+
*
312
+
* @remarks
313
+
* Overrides the default TTL from TieredStorageConfig.
314
+
* Data will expire after this duration from the current time.
315
+
*/
316
+
ttl?: number;
317
+
318
+
/**
319
+
* Custom metadata to attach to this key.
320
+
*
321
+
* @remarks
322
+
* Merged with system-generated metadata (size, checksum, timestamps).
323
+
* Useful for storing application-specific information like content-type, encoding, etc.
324
+
*/
325
+
metadata?: Record<string, string>;
326
+
327
+
/**
328
+
* Skip writing to specific tiers.
329
+
*
330
+
* @remarks
331
+
* Useful for controlling which tiers receive data. For example:
332
+
* - Large files: `skipTiers: ['hot']` to avoid filling memory
333
+
* - Small critical files: Write to hot only for fastest access
334
+
*
335
+
* Note: Cold tier can never be skipped (it's the source of truth).
336
+
*
337
+
* @example
338
+
* ```typescript
339
+
* // Store large file only in warm and cold (skip memory)
340
+
* await storage.set('large-video.mp4', videoData, { skipTiers: ['hot'] });
341
+
*
342
+
* // Store index.html in all tiers for fast access
343
+
* await storage.set('index.html', htmlData); // No skipping
344
+
* ```
345
+
*/
346
+
skipTiers?: ('hot' | 'warm')[];
347
+
}
348
+
349
+
/**
350
+
* Result from retrieving data with metadata.
351
+
*
352
+
* @typeParam T - The type of data being retrieved
353
+
*
354
+
* @remarks
355
+
* Includes both the data and information about where it was served from.
356
+
*/
357
+
export interface StorageResult<T> {
358
+
/** The retrieved data */
359
+
data: T;
360
+
361
+
/** Metadata associated with the data */
362
+
metadata: StorageMetadata;
363
+
364
+
/** Which tier the data was served from */
365
+
source: 'hot' | 'warm' | 'cold';
366
+
}
367
+
368
+
/**
369
+
* Result from setting data in the cache.
370
+
*
371
+
* @remarks
372
+
* Indicates which tiers successfully received the data.
373
+
*/
374
+
export interface SetResult {
375
+
/** The key that was set */
376
+
key: string;
377
+
378
+
/** Metadata that was stored with the data */
379
+
metadata: StorageMetadata;
380
+
381
+
/** Which tiers received the data */
382
+
tiersWritten: ('hot' | 'warm' | 'cold')[];
383
+
}
384
+
385
+
/**
386
+
* Snapshot of the entire storage state.
387
+
*
388
+
* @remarks
389
+
* Used for export/import, backup, and migration scenarios.
390
+
* The snapshot includes metadata but not the actual data (data remains in tiers).
391
+
*/
392
+
export interface StorageSnapshot {
393
+
/** Snapshot format version (for compatibility) */
394
+
version: number;
395
+
396
+
/** When this snapshot was created */
397
+
exportedAt: Date;
398
+
399
+
/** All keys present in cold tier (source of truth) */
400
+
keys: string[];
401
+
402
+
/** Metadata for each key */
403
+
metadata: Record<string, StorageMetadata>;
404
+
405
+
/** Statistics at time of export */
406
+
stats: AllTierStats;
407
+
}
+57
src/utils/checksum.ts
+57
src/utils/checksum.ts
···
1
+
import { createHash, timingSafeEqual } from 'node:crypto';
2
+
3
+
/**
4
+
* Calculate SHA256 checksum of data.
5
+
*
6
+
* @param data - Data to checksum
7
+
* @returns Hex-encoded SHA256 hash
8
+
*
9
+
* @remarks
10
+
* Used for data integrity verification. The checksum is stored in metadata
11
+
* and can be used to detect corruption or tampering.
12
+
*
13
+
* @example
14
+
* ```typescript
15
+
* const data = new TextEncoder().encode('Hello, world!');
16
+
* const checksum = calculateChecksum(data);
17
+
* console.log(checksum); // '315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3'
18
+
* ```
19
+
*/
20
+
export function calculateChecksum(data: Uint8Array): string {
21
+
const hash = createHash('sha256');
22
+
hash.update(data);
23
+
return hash.digest('hex');
24
+
}
25
+
26
+
/**
27
+
* Verify that data matches an expected checksum.
28
+
*
29
+
* @param data - Data to verify
30
+
* @param expectedChecksum - Expected SHA256 checksum (hex-encoded)
31
+
* @returns true if checksums match, false otherwise
32
+
*
33
+
* @remarks
34
+
* Uses constant-time comparison to prevent timing attacks.
35
+
*
36
+
* @example
37
+
* ```typescript
38
+
* const isValid = verifyChecksum(data, metadata.checksum);
39
+
* if (!isValid) {
40
+
* throw new Error('Data corruption detected');
41
+
* }
42
+
* ```
43
+
*/
44
+
export function verifyChecksum(data: Uint8Array, expectedChecksum: string): boolean {
45
+
const actualChecksum = calculateChecksum(data);
46
+
47
+
// Use constant-time comparison to prevent timing attacks
48
+
try {
49
+
return timingSafeEqual(
50
+
Buffer.from(actualChecksum, 'hex'),
51
+
Buffer.from(expectedChecksum, 'hex')
52
+
);
53
+
} catch {
54
+
// If checksums have different lengths, timingSafeEqual throws
55
+
return false;
56
+
}
57
+
}
+78
src/utils/compression.ts
+78
src/utils/compression.ts
···
1
+
import { gzip, gunzip } from 'node:zlib';
2
+
import { promisify } from 'node:util';
3
+
4
+
const gzipAsync = promisify(gzip);
5
+
const gunzipAsync = promisify(gunzip);
6
+
7
+
/**
8
+
* Compress data using gzip.
9
+
*
10
+
* @param data - Data to compress
11
+
* @returns Compressed data as Uint8Array
12
+
*
13
+
* @remarks
14
+
* Uses Node.js zlib with default compression level (6).
15
+
* Compression is transparent to the user - data is automatically decompressed on retrieval.
16
+
*
17
+
* @example
18
+
* ```typescript
19
+
* const original = new TextEncoder().encode('Hello, world!');
20
+
* const compressed = await compress(original);
21
+
* console.log(`Compressed from ${original.length} to ${compressed.length} bytes`);
22
+
* ```
23
+
*/
24
+
export async function compress(data: Uint8Array): Promise<Uint8Array> {
25
+
const buffer = Buffer.from(data);
26
+
const compressed = await gzipAsync(buffer);
27
+
return new Uint8Array(compressed);
28
+
}
29
+
30
+
/**
31
+
* Decompress gzip-compressed data.
32
+
*
33
+
* @param data - Compressed data
34
+
* @returns Decompressed data as Uint8Array
35
+
* @throws Error if data is not valid gzip format
36
+
*
37
+
* @remarks
38
+
* Automatically validates gzip magic bytes (0x1f 0x8b) before decompression.
39
+
*
40
+
* @example
41
+
* ```typescript
42
+
* const decompressed = await decompress(compressedData);
43
+
* const text = new TextDecoder().decode(decompressed);
44
+
* ```
45
+
*/
46
+
export async function decompress(data: Uint8Array): Promise<Uint8Array> {
47
+
// Validate gzip magic bytes
48
+
if (data.length < 2 || data[0] !== 0x1f || data[1] !== 0x8b) {
49
+
throw new Error('Invalid gzip data: missing magic bytes');
50
+
}
51
+
52
+
const buffer = Buffer.from(data);
53
+
const decompressed = await gunzipAsync(buffer);
54
+
return new Uint8Array(decompressed);
55
+
}
56
+
57
+
/**
58
+
* Check if data appears to be gzip-compressed by inspecting magic bytes.
59
+
*
60
+
* @param data - Data to check
61
+
* @returns true if data starts with gzip magic bytes (0x1f 0x8b)
62
+
*
63
+
* @remarks
64
+
* This is a quick check that doesn't decompress the data.
65
+
* Useful for detecting already-compressed data to avoid double compression.
66
+
*
67
+
* @example
68
+
* ```typescript
69
+
* if (isGzipped(data)) {
70
+
* console.log('Already compressed, skipping compression');
71
+
* } else {
72
+
* data = await compress(data);
73
+
* }
74
+
* ```
75
+
*/
76
+
export function isGzipped(data: Uint8Array): boolean {
77
+
return data.length >= 2 && data[0] === 0x1f && data[1] === 0x8b;
78
+
}
+69
src/utils/path-encoding.ts
+69
src/utils/path-encoding.ts
···
1
+
/**
2
+
* Encode a key to be safe for use as a filesystem path.
3
+
*
4
+
* @param key - The key to encode
5
+
* @returns Filesystem-safe encoded key
6
+
*
7
+
* @remarks
8
+
* Encodes characters that are problematic in filenames:
9
+
* - Forward slash (/) → %2F
10
+
* - Backslash (\) → %5C
11
+
* - Colon (:) → %3A
12
+
* - Asterisk (*) → %2A
13
+
* - Question mark (?) → %3F
14
+
* - Quote (") → %22
15
+
* - Less than (<) → %3C
16
+
* - Greater than (>) → %3E
17
+
* - Pipe (|) → %7C
18
+
* - Percent (%) → %25
19
+
* - Null byte → %00
20
+
*
21
+
* @example
22
+
* ```typescript
23
+
* const key = 'user:123/profile.json';
24
+
* const encoded = encodeKey(key);
25
+
* // Result: 'user%3A123%2Fprofile.json'
26
+
* ```
27
+
*/
28
+
export function encodeKey(key: string): string {
29
+
return key
30
+
.replace(/%/g, '%25') // Must be first!
31
+
.replace(/\//g, '%2F')
32
+
.replace(/\\/g, '%5C')
33
+
.replace(/:/g, '%3A')
34
+
.replace(/\*/g, '%2A')
35
+
.replace(/\?/g, '%3F')
36
+
.replace(/"/g, '%22')
37
+
.replace(/</g, '%3C')
38
+
.replace(/>/g, '%3E')
39
+
.replace(/\|/g, '%7C')
40
+
.replace(/\0/g, '%00');
41
+
}
42
+
43
+
/**
44
+
* Decode a filesystem-safe key back to original form.
45
+
*
46
+
* @param encoded - The encoded key
47
+
* @returns Original key
48
+
*
49
+
* @example
50
+
* ```typescript
51
+
* const encoded = 'user%3A123%2Fprofile.json';
52
+
* const key = decodeKey(encoded);
53
+
* // Result: 'user:123/profile.json'
54
+
* ```
55
+
*/
56
+
export function decodeKey(encoded: string): string {
57
+
return encoded
58
+
.replace(/%2F/g, '/')
59
+
.replace(/%5C/g, '\\')
60
+
.replace(/%3A/g, ':')
61
+
.replace(/%2A/g, '*')
62
+
.replace(/%3F/g, '?')
63
+
.replace(/%22/g, '"')
64
+
.replace(/%3C/g, '<')
65
+
.replace(/%3E/g, '>')
66
+
.replace(/%7C/g, '|')
67
+
.replace(/%00/g, '\0')
68
+
.replace(/%25/g, '%'); // Must be last!
69
+
}
+46
src/utils/serialization.ts
+46
src/utils/serialization.ts
···
1
+
/**
2
+
* Default JSON serializer.
3
+
*
4
+
* @param data - Data to serialize (must be JSON-serializable)
5
+
* @returns Serialized data as Uint8Array (UTF-8 encoded JSON)
6
+
*
7
+
* @remarks
8
+
* This is the default serializer used if no custom serializer is provided.
9
+
* Handles most JavaScript types but cannot serialize:
10
+
* - Functions
11
+
* - Symbols
12
+
* - undefined values (they become null)
13
+
* - Circular references
14
+
*
15
+
* @example
16
+
* ```typescript
17
+
* const data = { name: 'Alice', age: 30 };
18
+
* const serialized = await defaultSerialize(data);
19
+
* ```
20
+
*/
21
+
export async function defaultSerialize(data: unknown): Promise<Uint8Array> {
22
+
const json = JSON.stringify(data);
23
+
return new TextEncoder().encode(json);
24
+
}
25
+
26
+
/**
27
+
* Default JSON deserializer.
28
+
*
29
+
* @param data - Serialized data (UTF-8 encoded JSON)
30
+
* @returns Deserialized data
31
+
* @throws SyntaxError if data is not valid JSON
32
+
*
33
+
* @remarks
34
+
* This is the default deserializer used if no custom deserializer is provided.
35
+
* Parses UTF-8 encoded JSON back to JavaScript objects.
36
+
*
37
+
* @example
38
+
* ```typescript
39
+
* const data = await defaultDeserialize(serialized);
40
+
* console.log(data.name); // 'Alice'
41
+
* ```
42
+
*/
43
+
export async function defaultDeserialize(data: Uint8Array): Promise<unknown> {
44
+
const json = new TextDecoder().decode(data);
45
+
return JSON.parse(json);
46
+
}
+404
test/TieredStorage.test.ts
+404
test/TieredStorage.test.ts
···
1
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+
import { TieredStorage } from '../src/TieredStorage.js';
3
+
import { MemoryStorageTier } from '../src/tiers/MemoryStorageTier.js';
4
+
import { DiskStorageTier } from '../src/tiers/DiskStorageTier.js';
5
+
import { rm } from 'node:fs/promises';
6
+
7
+
describe('TieredStorage', () => {
8
+
const testDir = './test-cache';
9
+
10
+
afterEach(async () => {
11
+
await rm(testDir, { recursive: true, force: true });
12
+
});
13
+
14
+
describe('Basic Operations', () => {
15
+
it('should store and retrieve data', async () => {
16
+
const storage = new TieredStorage({
17
+
tiers: {
18
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
19
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
20
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
21
+
},
22
+
});
23
+
24
+
await storage.set('test-key', { message: 'Hello, world!' });
25
+
const result = await storage.get('test-key');
26
+
27
+
expect(result).toEqual({ message: 'Hello, world!' });
28
+
});
29
+
30
+
it('should return null for non-existent key', async () => {
31
+
const storage = new TieredStorage({
32
+
tiers: {
33
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
34
+
},
35
+
});
36
+
37
+
const result = await storage.get('non-existent');
38
+
expect(result).toBeNull();
39
+
});
40
+
41
+
it('should delete data from all tiers', async () => {
42
+
const storage = new TieredStorage({
43
+
tiers: {
44
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
45
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
46
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
47
+
},
48
+
});
49
+
50
+
await storage.set('test-key', { data: 'test' });
51
+
await storage.delete('test-key');
52
+
const result = await storage.get('test-key');
53
+
54
+
expect(result).toBeNull();
55
+
});
56
+
57
+
it('should check if key exists', async () => {
58
+
const storage = new TieredStorage({
59
+
tiers: {
60
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
61
+
},
62
+
});
63
+
64
+
await storage.set('test-key', { data: 'test' });
65
+
66
+
expect(await storage.exists('test-key')).toBe(true);
67
+
expect(await storage.exists('non-existent')).toBe(false);
68
+
});
69
+
});
70
+
71
+
describe('Cascading Write', () => {
72
+
it('should write to all configured tiers', async () => {
73
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
74
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
75
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
76
+
77
+
const storage = new TieredStorage({
78
+
tiers: { hot, warm, cold },
79
+
});
80
+
81
+
await storage.set('test-key', { data: 'test' });
82
+
83
+
// Verify data exists in all tiers
84
+
expect(await hot.exists('test-key')).toBe(true);
85
+
expect(await warm.exists('test-key')).toBe(true);
86
+
expect(await cold.exists('test-key')).toBe(true);
87
+
});
88
+
89
+
it('should skip tiers when specified', async () => {
90
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
91
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
92
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
93
+
94
+
const storage = new TieredStorage({
95
+
tiers: { hot, warm, cold },
96
+
});
97
+
98
+
// Skip hot tier
99
+
await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] });
100
+
101
+
expect(await hot.exists('test-key')).toBe(false);
102
+
expect(await warm.exists('test-key')).toBe(true);
103
+
expect(await cold.exists('test-key')).toBe(true);
104
+
});
105
+
});
106
+
107
+
describe('Bubbling Read', () => {
108
+
it('should read from hot tier first', async () => {
109
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
110
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
111
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
112
+
113
+
const storage = new TieredStorage({
114
+
tiers: { hot, warm, cold },
115
+
});
116
+
117
+
await storage.set('test-key', { data: 'test' });
118
+
const result = await storage.getWithMetadata('test-key');
119
+
120
+
expect(result?.source).toBe('hot');
121
+
expect(result?.data).toEqual({ data: 'test' });
122
+
});
123
+
124
+
it('should fall back to warm tier on hot miss', async () => {
125
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
126
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
127
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
128
+
129
+
const storage = new TieredStorage({
130
+
tiers: { hot, warm, cold },
131
+
});
132
+
133
+
// Write to warm and cold, skip hot
134
+
await storage.set('test-key', { data: 'test' }, { skipTiers: ['hot'] });
135
+
136
+
const result = await storage.getWithMetadata('test-key');
137
+
138
+
expect(result?.source).toBe('warm');
139
+
expect(result?.data).toEqual({ data: 'test' });
140
+
});
141
+
142
+
it('should fall back to cold tier on hot and warm miss', async () => {
143
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
144
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
145
+
146
+
const storage = new TieredStorage({
147
+
tiers: { hot, cold },
148
+
});
149
+
150
+
// Write only to cold
151
+
await cold.set(
152
+
'test-key',
153
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
154
+
{
155
+
key: 'test-key',
156
+
size: 100,
157
+
createdAt: new Date(),
158
+
lastAccessed: new Date(),
159
+
accessCount: 0,
160
+
compressed: false,
161
+
checksum: 'abc123',
162
+
}
163
+
);
164
+
165
+
const result = await storage.getWithMetadata('test-key');
166
+
167
+
expect(result?.source).toBe('cold');
168
+
expect(result?.data).toEqual({ data: 'test' });
169
+
});
170
+
});
171
+
172
+
describe('Promotion Strategy', () => {
173
+
it('should eagerly promote data to upper tiers', async () => {
174
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
175
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
176
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
177
+
178
+
const storage = new TieredStorage({
179
+
tiers: { hot, warm, cold },
180
+
promotionStrategy: 'eager',
181
+
});
182
+
183
+
// Write only to cold
184
+
await cold.set(
185
+
'test-key',
186
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
187
+
{
188
+
key: 'test-key',
189
+
size: 100,
190
+
createdAt: new Date(),
191
+
lastAccessed: new Date(),
192
+
accessCount: 0,
193
+
compressed: false,
194
+
checksum: 'abc123',
195
+
}
196
+
);
197
+
198
+
// Read should promote to hot and warm
199
+
await storage.get('test-key');
200
+
201
+
expect(await hot.exists('test-key')).toBe(true);
202
+
expect(await warm.exists('test-key')).toBe(true);
203
+
});
204
+
205
+
it('should lazily promote data (not automatic)', async () => {
206
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
207
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
208
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
209
+
210
+
const storage = new TieredStorage({
211
+
tiers: { hot, warm, cold },
212
+
promotionStrategy: 'lazy',
213
+
});
214
+
215
+
// Write only to cold
216
+
await cold.set(
217
+
'test-key',
218
+
new TextEncoder().encode(JSON.stringify({ data: 'test' })),
219
+
{
220
+
key: 'test-key',
221
+
size: 100,
222
+
createdAt: new Date(),
223
+
lastAccessed: new Date(),
224
+
accessCount: 0,
225
+
compressed: false,
226
+
checksum: 'abc123',
227
+
}
228
+
);
229
+
230
+
// Read should NOT promote to hot and warm
231
+
await storage.get('test-key');
232
+
233
+
expect(await hot.exists('test-key')).toBe(false);
234
+
expect(await warm.exists('test-key')).toBe(false);
235
+
});
236
+
});
237
+
238
+
describe('TTL Management', () => {
239
+
it('should expire data after TTL', async () => {
240
+
const storage = new TieredStorage({
241
+
tiers: {
242
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
243
+
},
244
+
});
245
+
246
+
// Set with 100ms TTL
247
+
await storage.set('test-key', { data: 'test' }, { ttl: 100 });
248
+
249
+
// Should exist immediately
250
+
expect(await storage.get('test-key')).toEqual({ data: 'test' });
251
+
252
+
// Wait for expiration
253
+
await new Promise((resolve) => setTimeout(resolve, 150));
254
+
255
+
// Should be null after expiration
256
+
expect(await storage.get('test-key')).toBeNull();
257
+
});
258
+
259
+
it('should renew TTL with touch', async () => {
260
+
const storage = new TieredStorage({
261
+
tiers: {
262
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
263
+
},
264
+
defaultTTL: 100,
265
+
});
266
+
267
+
await storage.set('test-key', { data: 'test' });
268
+
269
+
// Wait 50ms
270
+
await new Promise((resolve) => setTimeout(resolve, 50));
271
+
272
+
// Renew TTL
273
+
await storage.touch('test-key', 200);
274
+
275
+
// Wait another 100ms (would have expired without touch)
276
+
await new Promise((resolve) => setTimeout(resolve, 100));
277
+
278
+
// Should still exist
279
+
expect(await storage.get('test-key')).toEqual({ data: 'test' });
280
+
});
281
+
});
282
+
283
+
describe('Prefix Invalidation', () => {
284
+
it('should invalidate all keys with prefix', async () => {
285
+
const storage = new TieredStorage({
286
+
tiers: {
287
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
288
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
289
+
},
290
+
});
291
+
292
+
await storage.set('user:123', { name: 'Alice' });
293
+
await storage.set('user:456', { name: 'Bob' });
294
+
await storage.set('post:789', { title: 'Test' });
295
+
296
+
const deleted = await storage.invalidate('user:');
297
+
298
+
expect(deleted).toBe(2);
299
+
expect(await storage.exists('user:123')).toBe(false);
300
+
expect(await storage.exists('user:456')).toBe(false);
301
+
expect(await storage.exists('post:789')).toBe(true);
302
+
});
303
+
});
304
+
305
+
describe('Compression', () => {
306
+
it('should compress data when enabled', async () => {
307
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
308
+
309
+
const storage = new TieredStorage({
310
+
tiers: { cold },
311
+
compression: true,
312
+
});
313
+
314
+
const largeData = { data: 'x'.repeat(10000) };
315
+
const result = await storage.set('test-key', largeData);
316
+
317
+
// Check that compressed flag is set
318
+
expect(result.metadata.compressed).toBe(true);
319
+
320
+
// Verify data can be retrieved correctly
321
+
const retrieved = await storage.get('test-key');
322
+
expect(retrieved).toEqual(largeData);
323
+
});
324
+
});
325
+
326
+
describe('Bootstrap', () => {
327
+
it('should bootstrap hot from warm', async () => {
328
+
const hot = new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 });
329
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
330
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
331
+
332
+
const storage = new TieredStorage({
333
+
tiers: { hot, warm, cold },
334
+
});
335
+
336
+
// Write some data
337
+
await storage.set('key1', { data: '1' });
338
+
await storage.set('key2', { data: '2' });
339
+
await storage.set('key3', { data: '3' });
340
+
341
+
// Clear hot tier
342
+
await hot.clear();
343
+
344
+
// Bootstrap hot from warm
345
+
const loaded = await storage.bootstrapHot();
346
+
347
+
expect(loaded).toBe(3);
348
+
expect(await hot.exists('key1')).toBe(true);
349
+
expect(await hot.exists('key2')).toBe(true);
350
+
expect(await hot.exists('key3')).toBe(true);
351
+
});
352
+
353
+
it('should bootstrap warm from cold', async () => {
354
+
const warm = new DiskStorageTier({ directory: `${testDir}/warm` });
355
+
const cold = new DiskStorageTier({ directory: `${testDir}/cold` });
356
+
357
+
const storage = new TieredStorage({
358
+
tiers: { warm, cold },
359
+
});
360
+
361
+
// Write directly to cold
362
+
await cold.set(
363
+
'key1',
364
+
new TextEncoder().encode(JSON.stringify({ data: '1' })),
365
+
{
366
+
key: 'key1',
367
+
size: 100,
368
+
createdAt: new Date(),
369
+
lastAccessed: new Date(),
370
+
accessCount: 0,
371
+
compressed: false,
372
+
checksum: 'abc',
373
+
}
374
+
);
375
+
376
+
// Bootstrap warm from cold
377
+
const loaded = await storage.bootstrapWarm({ limit: 10 });
378
+
379
+
expect(loaded).toBe(1);
380
+
expect(await warm.exists('key1')).toBe(true);
381
+
});
382
+
});
383
+
384
+
describe('Statistics', () => {
385
+
it('should return statistics for all tiers', async () => {
386
+
const storage = new TieredStorage({
387
+
tiers: {
388
+
hot: new MemoryStorageTier({ maxSizeBytes: 1024 * 1024 }),
389
+
warm: new DiskStorageTier({ directory: `${testDir}/warm` }),
390
+
cold: new DiskStorageTier({ directory: `${testDir}/cold` }),
391
+
},
392
+
});
393
+
394
+
await storage.set('key1', { data: 'test1' });
395
+
await storage.set('key2', { data: 'test2' });
396
+
397
+
const stats = await storage.getStats();
398
+
399
+
expect(stats.cold.items).toBe(2);
400
+
expect(stats.warm?.items).toBe(2);
401
+
expect(stats.hot?.items).toBe(2);
402
+
});
403
+
});
404
+
});
+33
tsconfig.json
+33
tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"target": "ES2022",
4
+
"module": "ESNext",
5
+
"moduleResolution": "node",
6
+
"allowSyntheticDefaultImports": true,
7
+
"esModuleInterop": true,
8
+
"allowJs": true,
9
+
"strict": true,
10
+
"noImplicitAny": true,
11
+
"strictNullChecks": true,
12
+
"strictFunctionTypes": true,
13
+
"strictBindCallApply": true,
14
+
"strictPropertyInitialization": true,
15
+
"noImplicitReturns": true,
16
+
"noImplicitThis": true,
17
+
"noUncheckedIndexedAccess": true,
18
+
"noImplicitOverride": true,
19
+
"exactOptionalPropertyTypes": true,
20
+
"noPropertyAccessFromIndexSignature": false,
21
+
"declaration": true,
22
+
"declarationMap": true,
23
+
"sourceMap": true,
24
+
"outDir": "./dist",
25
+
"rootDir": "./src",
26
+
"removeComments": false,
27
+
"skipLibCheck": true,
28
+
"forceConsistentCasingInFileNames": true,
29
+
"resolveJsonModule": true
30
+
},
31
+
"include": ["src/**/*"],
32
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
33
+
}