express-storage 2.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +366 -34
- package/dist/cjs/config/index.d.ts +10 -0
- package/dist/cjs/config/index.d.ts.map +1 -0
- package/dist/cjs/config/index.js +19 -0
- package/dist/cjs/config/index.js.map +1 -0
- package/dist/cjs/drivers/azure.driver.d.ts +73 -0
- package/dist/cjs/drivers/azure.driver.d.ts.map +1 -0
- package/dist/cjs/drivers/azure.driver.js +390 -0
- package/dist/cjs/drivers/azure.driver.js.map +1 -0
- package/dist/cjs/drivers/base.driver.d.ts +136 -0
- package/dist/cjs/drivers/base.driver.d.ts.map +1 -0
- package/dist/cjs/drivers/base.driver.js +357 -0
- package/dist/cjs/drivers/base.driver.js.map +1 -0
- package/dist/{drivers → cjs/drivers}/gcs.driver.d.ts +20 -38
- package/dist/cjs/drivers/gcs.driver.d.ts.map +1 -0
- package/dist/cjs/drivers/gcs.driver.js +343 -0
- package/dist/cjs/drivers/gcs.driver.js.map +1 -0
- package/dist/cjs/drivers/index.d.ts +15 -0
- package/dist/cjs/drivers/index.d.ts.map +1 -0
- package/dist/cjs/drivers/index.js +26 -0
- package/dist/cjs/drivers/index.js.map +1 -0
- package/dist/cjs/drivers/local.driver.d.ts +86 -0
- package/dist/cjs/drivers/local.driver.d.ts.map +1 -0
- package/dist/cjs/drivers/local.driver.js +556 -0
- package/dist/cjs/drivers/local.driver.js.map +1 -0
- package/dist/{drivers → cjs/drivers}/s3.driver.d.ts +19 -39
- package/dist/cjs/drivers/s3.driver.d.ts.map +1 -0
- package/dist/cjs/drivers/s3.driver.js +400 -0
- package/dist/cjs/drivers/s3.driver.js.map +1 -0
- package/dist/cjs/factory/driver.factory.d.ts +43 -0
- package/dist/cjs/factory/driver.factory.d.ts.map +1 -0
- package/dist/cjs/factory/driver.factory.js +101 -0
- package/dist/cjs/factory/driver.factory.js.map +1 -0
- package/dist/cjs/index.d.ts +26 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/storage-manager.d.ts +210 -0
- package/dist/cjs/storage-manager.d.ts.map +1 -0
- package/dist/cjs/storage-manager.js +649 -0
- package/dist/cjs/storage-manager.js.map +1 -0
- package/dist/cjs/types/storage.types.d.ts +438 -0
- package/dist/cjs/types/storage.types.d.ts.map +1 -0
- package/dist/cjs/types/storage.types.js +3 -0
- package/dist/cjs/types/storage.types.js.map +1 -0
- package/dist/cjs/utils/config.utils.d.ts.map +1 -0
- package/dist/cjs/utils/config.utils.js +213 -0
- package/dist/cjs/utils/config.utils.js.map +1 -0
- package/dist/{utils → cjs/utils}/file.utils.d.ts +62 -8
- package/dist/cjs/utils/file.utils.d.ts.map +1 -0
- package/dist/cjs/utils/file.utils.js +464 -0
- package/dist/cjs/utils/file.utils.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +12 -0
- package/dist/cjs/utils/index.d.ts.map +1 -0
- package/dist/cjs/utils/index.js +36 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/rate-limiter.d.ts +40 -0
- package/dist/cjs/utils/rate-limiter.d.ts.map +1 -0
- package/dist/cjs/utils/rate-limiter.js +87 -0
- package/dist/cjs/utils/rate-limiter.js.map +1 -0
- package/dist/esm/config/index.d.ts +10 -0
- package/dist/esm/config/index.d.ts.map +1 -0
- package/dist/esm/config/index.js +10 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/drivers/azure.driver.d.ts +73 -0
- package/dist/esm/drivers/azure.driver.d.ts.map +1 -0
- package/dist/esm/drivers/azure.driver.js +353 -0
- package/dist/esm/drivers/azure.driver.js.map +1 -0
- package/dist/esm/drivers/base.driver.d.ts +136 -0
- package/dist/esm/drivers/base.driver.d.ts.map +1 -0
- package/dist/esm/drivers/base.driver.js +350 -0
- package/dist/esm/drivers/base.driver.js.map +1 -0
- package/dist/esm/drivers/gcs.driver.d.ts +68 -0
- package/dist/esm/drivers/gcs.driver.d.ts.map +1 -0
- package/dist/esm/drivers/gcs.driver.js +306 -0
- package/dist/esm/drivers/gcs.driver.js.map +1 -0
- package/dist/esm/drivers/index.d.ts +15 -0
- package/dist/esm/drivers/index.d.ts.map +1 -0
- package/dist/esm/drivers/index.js +15 -0
- package/dist/esm/drivers/index.js.map +1 -0
- package/dist/esm/drivers/local.driver.d.ts +86 -0
- package/dist/esm/drivers/local.driver.d.ts.map +1 -0
- package/dist/esm/drivers/local.driver.js +549 -0
- package/dist/esm/drivers/local.driver.js.map +1 -0
- package/dist/esm/drivers/s3.driver.d.ts +69 -0
- package/dist/esm/drivers/s3.driver.d.ts.map +1 -0
- package/dist/esm/drivers/s3.driver.js +363 -0
- package/dist/esm/drivers/s3.driver.js.map +1 -0
- package/dist/esm/factory/driver.factory.d.ts +43 -0
- package/dist/esm/factory/driver.factory.d.ts.map +1 -0
- package/dist/esm/factory/driver.factory.js +92 -0
- package/dist/esm/factory/driver.factory.js.map +1 -0
- package/dist/esm/index.d.ts +26 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/storage-manager.d.ts +210 -0
- package/dist/esm/storage-manager.d.ts.map +1 -0
- package/dist/esm/storage-manager.js +645 -0
- package/dist/esm/storage-manager.js.map +1 -0
- package/dist/esm/types/storage.types.d.ts +438 -0
- package/dist/esm/types/storage.types.d.ts.map +1 -0
- package/dist/esm/types/storage.types.js.map +1 -0
- package/dist/esm/utils/config.utils.d.ts +45 -0
- package/dist/esm/utils/config.utils.d.ts.map +1 -0
- package/dist/esm/utils/config.utils.js.map +1 -0
- package/dist/esm/utils/file.utils.d.ts +196 -0
- package/dist/esm/utils/file.utils.d.ts.map +1 -0
- package/dist/esm/utils/file.utils.js +439 -0
- package/dist/esm/utils/file.utils.js.map +1 -0
- package/dist/esm/utils/index.d.ts +12 -0
- package/dist/esm/utils/index.d.ts.map +1 -0
- package/dist/esm/utils/index.js +11 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/rate-limiter.d.ts +40 -0
- package/dist/esm/utils/rate-limiter.d.ts.map +1 -0
- package/dist/esm/utils/rate-limiter.js +82 -0
- package/dist/esm/utils/rate-limiter.js.map +1 -0
- package/package.json +90 -52
- package/src/config/index.ts +17 -0
- package/src/drivers/azure.driver.ts +434 -0
- package/src/drivers/base.driver.ts +436 -0
- package/src/drivers/gcs.driver.ts +366 -0
- package/src/drivers/index.ts +15 -0
- package/src/drivers/local.driver.ts +626 -0
- package/src/drivers/s3.driver.ts +459 -0
- package/src/factory/driver.factory.ts +101 -0
- package/src/index.ts +72 -0
- package/src/storage-manager.ts +801 -0
- package/src/types/storage.types.ts +561 -0
- package/src/utils/config.utils.ts +229 -0
- package/src/utils/file.utils.ts +536 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/rate-limiter.ts +94 -0
- package/dist/drivers/azure.driver.d.ts +0 -88
- package/dist/drivers/azure.driver.d.ts.map +0 -1
- package/dist/drivers/azure.driver.js +0 -391
- package/dist/drivers/azure.driver.js.map +0 -1
- package/dist/drivers/base.driver.d.ts +0 -170
- package/dist/drivers/base.driver.d.ts.map +0 -1
- package/dist/drivers/base.driver.js +0 -347
- package/dist/drivers/base.driver.js.map +0 -1
- package/dist/drivers/gcs.driver.d.ts.map +0 -1
- package/dist/drivers/gcs.driver.js +0 -354
- package/dist/drivers/gcs.driver.js.map +0 -1
- package/dist/drivers/local.driver.d.ts +0 -107
- package/dist/drivers/local.driver.d.ts.map +0 -1
- package/dist/drivers/local.driver.js +0 -621
- package/dist/drivers/local.driver.js.map +0 -1
- package/dist/drivers/s3.driver.d.ts.map +0 -1
- package/dist/drivers/s3.driver.js +0 -387
- package/dist/drivers/s3.driver.js.map +0 -1
- package/dist/factory/driver.factory.d.ts +0 -62
- package/dist/factory/driver.factory.d.ts.map +0 -1
- package/dist/factory/driver.factory.js +0 -177
- package/dist/factory/driver.factory.js.map +0 -1
- package/dist/index.d.ts +0 -30
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -33
- package/dist/index.js.map +0 -1
- package/dist/storage-manager.d.ts +0 -228
- package/dist/storage-manager.d.ts.map +0 -1
- package/dist/storage-manager.js +0 -715
- package/dist/storage-manager.js.map +0 -1
- package/dist/types/storage.types.d.ts +0 -295
- package/dist/types/storage.types.d.ts.map +0 -1
- package/dist/types/storage.types.js.map +0 -1
- package/dist/utils/config.utils.d.ts.map +0 -1
- package/dist/utils/config.utils.js.map +0 -1
- package/dist/utils/file.utils.d.ts.map +0 -1
- package/dist/utils/file.utils.js +0 -278
- package/dist/utils/file.utils.js.map +0 -1
- /package/dist/{utils → cjs/utils}/config.utils.d.ts +0 -0
- /package/dist/{types → esm/types}/storage.types.js +0 -0
- /package/dist/{utils → esm/utils}/config.utils.js +0 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import fsPromises from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { BaseStorageDriver } from './base.driver.js';
|
|
5
|
+
import { createMonthBasedPath, ensureDirectoryExists } from '../utils/file.utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Magic byte signatures for common file types.
|
|
8
|
+
* Used to detect actual file content type regardless of extension.
|
|
9
|
+
* This provides security against extension spoofing attacks.
|
|
10
|
+
*/
|
|
11
|
+
const MAGIC_BYTES = [
|
|
12
|
+
// Images
|
|
13
|
+
{ bytes: [0xFF, 0xD8, 0xFF], mimeType: 'image/jpeg' },
|
|
14
|
+
{ bytes: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], mimeType: 'image/png' },
|
|
15
|
+
{ bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], mimeType: 'image/gif' },
|
|
16
|
+
{ bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], mimeType: 'image/gif' },
|
|
17
|
+
{ bytes: [0x42, 0x4D], mimeType: 'image/bmp' },
|
|
18
|
+
// Documents
|
|
19
|
+
{ bytes: [0x25, 0x50, 0x44, 0x46], mimeType: 'application/pdf' },
|
|
20
|
+
{ bytes: [0x50, 0x4B, 0x03, 0x04], mimeType: 'application/zip' },
|
|
21
|
+
{ bytes: [0x50, 0x4B, 0x05, 0x06], mimeType: 'application/zip' },
|
|
22
|
+
{ bytes: [0x50, 0x4B, 0x07, 0x08], mimeType: 'application/zip' },
|
|
23
|
+
// Archives
|
|
24
|
+
{ bytes: [0x1F, 0x8B], mimeType: 'application/gzip' },
|
|
25
|
+
{ bytes: [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], mimeType: 'application/vnd.rar' },
|
|
26
|
+
{ bytes: [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], mimeType: 'application/x-7z-compressed' },
|
|
27
|
+
// Audio/Video
|
|
28
|
+
{ bytes: [0x49, 0x44, 0x33], mimeType: 'audio/mpeg' },
|
|
29
|
+
{ bytes: [0xFF, 0xFB], mimeType: 'audio/mpeg' },
|
|
30
|
+
{ bytes: [0xFF, 0xFA], mimeType: 'audio/mpeg' },
|
|
31
|
+
{ bytes: [0x4F, 0x67, 0x67, 0x53], mimeType: 'audio/ogg' },
|
|
32
|
+
{ bytes: [0x66, 0x74, 0x79, 0x70], mimeType: 'video/mp4', offset: 4 },
|
|
33
|
+
// Executables (for security detection)
|
|
34
|
+
{ bytes: [0x4D, 0x5A], mimeType: 'application/x-msdownload' },
|
|
35
|
+
{ bytes: [0x7F, 0x45, 0x4C, 0x46], mimeType: 'application/x-executable' },
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Detects MIME type from file content using magic bytes.
|
|
39
|
+
* Returns undefined if no match is found (falls back to extension-based detection).
|
|
40
|
+
*/
|
|
41
|
+
async function detectMimeTypeFromMagicBytes(filePath) {
|
|
42
|
+
try {
|
|
43
|
+
const fd = await fsPromises.open(filePath, 'r');
|
|
44
|
+
const buffer = Buffer.alloc(16);
|
|
45
|
+
const { bytesRead } = await fd.read(buffer, 0, 16, 0);
|
|
46
|
+
await fd.close();
|
|
47
|
+
if (bytesRead === 0) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
for (const signature of MAGIC_BYTES) {
|
|
51
|
+
const offset = signature.offset || 0;
|
|
52
|
+
if (offset + signature.bytes.length > bytesRead) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
let matches = true;
|
|
56
|
+
for (let i = 0; i < signature.bytes.length; i++) {
|
|
57
|
+
if (buffer[offset + i] !== signature.bytes[i]) {
|
|
58
|
+
matches = false;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (matches) {
|
|
63
|
+
return signature.mimeType;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// RIFF container: bytes 0-3 = 'RIFF', bytes 8-11 = sub-format identifier
|
|
67
|
+
if (bytesRead >= 12 &&
|
|
68
|
+
buffer[0] === 0x52 && buffer[1] === 0x49 &&
|
|
69
|
+
buffer[2] === 0x46 && buffer[3] === 0x46) {
|
|
70
|
+
const subFormat = buffer.subarray(8, 12).toString('ascii');
|
|
71
|
+
switch (subFormat) {
|
|
72
|
+
case 'WEBP': return 'image/webp';
|
|
73
|
+
case 'WAVE': return 'audio/wav';
|
|
74
|
+
case 'AVI ': return 'video/x-msvideo';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Detects content type using magic bytes first, then extension as fallback.
|
|
85
|
+
*/
|
|
86
|
+
async function detectContentType(filePath, reference) {
|
|
87
|
+
const magicMime = await detectMimeTypeFromMagicBytes(filePath);
|
|
88
|
+
if (magicMime)
|
|
89
|
+
return magicMime;
|
|
90
|
+
const ext = path.extname(reference).toLowerCase();
|
|
91
|
+
return ext && EXTENSION_MIME_MAP[ext] ? EXTENSION_MIME_MAP[ext] : undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Maps file extensions to MIME types.
|
|
95
|
+
* Used as fallback when magic byte detection doesn't match.
|
|
96
|
+
*/
|
|
97
|
+
const EXTENSION_MIME_MAP = {
|
|
98
|
+
// Images
|
|
99
|
+
'.jpg': 'image/jpeg',
|
|
100
|
+
'.jpeg': 'image/jpeg',
|
|
101
|
+
'.png': 'image/png',
|
|
102
|
+
'.gif': 'image/gif',
|
|
103
|
+
'.webp': 'image/webp',
|
|
104
|
+
'.svg': 'image/svg+xml',
|
|
105
|
+
'.ico': 'image/x-icon',
|
|
106
|
+
'.bmp': 'image/bmp',
|
|
107
|
+
'.tiff': 'image/tiff',
|
|
108
|
+
'.tif': 'image/tiff',
|
|
109
|
+
// Documents
|
|
110
|
+
'.pdf': 'application/pdf',
|
|
111
|
+
'.doc': 'application/msword',
|
|
112
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
113
|
+
'.xls': 'application/vnd.ms-excel',
|
|
114
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
115
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
116
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
117
|
+
'.txt': 'text/plain',
|
|
118
|
+
'.csv': 'text/csv',
|
|
119
|
+
'.json': 'application/json',
|
|
120
|
+
'.xml': 'application/xml',
|
|
121
|
+
// Audio
|
|
122
|
+
'.mp3': 'audio/mpeg',
|
|
123
|
+
'.wav': 'audio/wav',
|
|
124
|
+
'.ogg': 'audio/ogg',
|
|
125
|
+
'.m4a': 'audio/mp4',
|
|
126
|
+
// Video
|
|
127
|
+
'.mp4': 'video/mp4',
|
|
128
|
+
'.webm': 'video/webm',
|
|
129
|
+
'.avi': 'video/x-msvideo',
|
|
130
|
+
'.mov': 'video/quicktime',
|
|
131
|
+
'.mkv': 'video/x-matroska',
|
|
132
|
+
// Archives
|
|
133
|
+
'.zip': 'application/zip',
|
|
134
|
+
'.tar': 'application/x-tar',
|
|
135
|
+
'.gz': 'application/gzip',
|
|
136
|
+
'.rar': 'application/vnd.rar',
|
|
137
|
+
'.7z': 'application/x-7z-compressed',
|
|
138
|
+
// Web
|
|
139
|
+
'.html': 'text/html',
|
|
140
|
+
'.htm': 'text/html',
|
|
141
|
+
'.css': 'text/css',
|
|
142
|
+
'.js': 'application/javascript',
|
|
143
|
+
'.ts': 'application/typescript',
|
|
144
|
+
// Fonts
|
|
145
|
+
'.woff': 'font/woff',
|
|
146
|
+
'.woff2': 'font/woff2',
|
|
147
|
+
'.ttf': 'font/ttf',
|
|
148
|
+
'.otf': 'font/otf',
|
|
149
|
+
};
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// listFiles helpers — extracted for testability and clarity
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
const MAX_RECURSION_DEPTH = 100;
|
|
154
|
+
const MAX_ENTRIES_SCANNED = 50_000;
|
|
155
|
+
function couldContainPrefix(dirRelativePath, targetPrefix) {
|
|
156
|
+
if (!targetPrefix)
|
|
157
|
+
return true;
|
|
158
|
+
return targetPrefix.startsWith(dirRelativePath) ||
|
|
159
|
+
dirRelativePath.startsWith(targetPrefix) ||
|
|
160
|
+
dirRelativePath === '';
|
|
161
|
+
}
|
|
162
|
+
function isAfterToken(filePath, token) {
|
|
163
|
+
if (!token)
|
|
164
|
+
return true;
|
|
165
|
+
return filePath.localeCompare(token) > 0;
|
|
166
|
+
}
|
|
167
|
+
async function buildFileInfo(absolutePath, relativePath) {
|
|
168
|
+
try {
|
|
169
|
+
const stat = await fsPromises.stat(absolutePath);
|
|
170
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
171
|
+
return {
|
|
172
|
+
name: relativePath,
|
|
173
|
+
size: stat.size,
|
|
174
|
+
lastModified: stat.mtime,
|
|
175
|
+
contentType: (ext && EXTENSION_MIME_MAP[ext]) ? EXTENSION_MIME_MAP[ext] : 'application/octet-stream',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function walkDirectory(baseDir, options) {
|
|
183
|
+
const files = [];
|
|
184
|
+
let hasMore = false;
|
|
185
|
+
let entriesScanned = 0;
|
|
186
|
+
const walk = async (dir, dirRelativePath, depth) => {
|
|
187
|
+
if (depth > MAX_RECURSION_DEPTH || files.length >= options.maxCollect || entriesScanned >= MAX_ENTRIES_SCANNED) {
|
|
188
|
+
if (files.length >= options.maxCollect || entriesScanned >= MAX_ENTRIES_SCANNED)
|
|
189
|
+
hasMore = true;
|
|
190
|
+
return files.length < options.maxCollect && entriesScanned < MAX_ENTRIES_SCANNED;
|
|
191
|
+
}
|
|
192
|
+
if (options.prefix && !couldContainPrefix(dirRelativePath, options.prefix)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
let entries;
|
|
196
|
+
try {
|
|
197
|
+
entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
entriesScanned++;
|
|
205
|
+
if (files.length >= options.maxCollect || entriesScanned >= MAX_ENTRIES_SCANNED) {
|
|
206
|
+
hasMore = true;
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (entry.isSymbolicLink())
|
|
210
|
+
continue;
|
|
211
|
+
const itemPath = path.join(dir, entry.name);
|
|
212
|
+
const relativePath = dirRelativePath ? `${dirRelativePath}/${entry.name}` : entry.name;
|
|
213
|
+
if (entry.isDirectory()) {
|
|
214
|
+
if (options.continuationToken && !couldContainPrefix(relativePath, options.continuationToken.split('/')[0] || '')) {
|
|
215
|
+
if (relativePath.localeCompare(options.continuationToken) < 0 && !options.continuationToken.startsWith(relativePath + '/')) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!await walk(itemPath, relativePath, depth + 1))
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
else if (entry.isFile()) {
|
|
223
|
+
if (options.prefix && !relativePath.startsWith(options.prefix))
|
|
224
|
+
continue;
|
|
225
|
+
if (!isAfterToken(relativePath, options.continuationToken))
|
|
226
|
+
continue;
|
|
227
|
+
const info = await buildFileInfo(itemPath, relativePath);
|
|
228
|
+
if (info)
|
|
229
|
+
files.push(info);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
};
|
|
234
|
+
await walk(baseDir, '', 0);
|
|
235
|
+
return { files, hasMore };
|
|
236
|
+
}
|
|
237
|
+
function paginateFiles(files, maxResults, hasMore) {
|
|
238
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
239
|
+
const page = files.slice(0, maxResults);
|
|
240
|
+
const result = { success: true, files: page };
|
|
241
|
+
if (files.length > maxResults || hasMore) {
|
|
242
|
+
const lastFile = page[page.length - 1];
|
|
243
|
+
if (lastFile)
|
|
244
|
+
result.nextToken = lastFile.name;
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// LocalStorageDriver
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
/**
|
|
252
|
+
* LocalStorageDriver - Saves files to your local filesystem.
|
|
253
|
+
*
|
|
254
|
+
* Great for development and small-scale applications.
|
|
255
|
+
* Files are organized by year/month folders automatically.
|
|
256
|
+
*
|
|
257
|
+
* **Security features:**
|
|
258
|
+
* - Path traversal prevention (blocks ../ and null bytes)
|
|
259
|
+
* - Symlinks are NOT followed or deleted (prevents directory escape attacks)
|
|
260
|
+
* - Magic byte detection for content-type validation (prevents extension spoofing)
|
|
261
|
+
* - Files stay within the configured base directory
|
|
262
|
+
*
|
|
263
|
+
* Note: Local storage doesn't support presigned URLs since
|
|
264
|
+
* there's no external service to sign requests against.
|
|
265
|
+
*/
|
|
266
|
+
export class LocalStorageDriver extends BaseStorageDriver {
|
|
267
|
+
basePath;
|
|
268
|
+
originalLocalPath;
|
|
269
|
+
constructor(config) {
|
|
270
|
+
super(config);
|
|
271
|
+
this.originalLocalPath = config.localPath || 'public/express-storage';
|
|
272
|
+
this.basePath = path.resolve(this.originalLocalPath);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Saves a file to the local filesystem.
|
|
276
|
+
*
|
|
277
|
+
* Files are automatically organized into YYYY/MM folders.
|
|
278
|
+
* For large files (>100MB), uses streaming to reduce memory usage.
|
|
279
|
+
*/
|
|
280
|
+
async upload(file, options) {
|
|
281
|
+
try {
|
|
282
|
+
const { errors: validationErrors, resolvedSize } = await this.validateFile(file);
|
|
283
|
+
if (validationErrors.length > 0) {
|
|
284
|
+
return this.createErrorResult(validationErrors.join(', '), 'VALIDATION_FAILED');
|
|
285
|
+
}
|
|
286
|
+
const fileName = this.generateFileName(file.originalname);
|
|
287
|
+
const monthPath = createMonthBasedPath(this.basePath);
|
|
288
|
+
const fullDirPath = path.resolve(monthPath);
|
|
289
|
+
await ensureDirectoryExists(fullDirPath);
|
|
290
|
+
const filePath = path.join(fullDirPath, fileName);
|
|
291
|
+
options?.signal?.throwIfAborted();
|
|
292
|
+
if (this.shouldUseStreaming(resolvedSize)) {
|
|
293
|
+
await this.uploadWithStream(file, filePath);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const fileContent = await this.getFileContent(file);
|
|
297
|
+
await fsPromises.writeFile(filePath, fileContent);
|
|
298
|
+
}
|
|
299
|
+
if (options?.metadata && Object.keys(options.metadata).length > 0) {
|
|
300
|
+
const meta = { metadata: options.metadata };
|
|
301
|
+
if (options.contentType)
|
|
302
|
+
meta['contentType'] = options.contentType;
|
|
303
|
+
if (options.cacheControl)
|
|
304
|
+
meta['cacheControl'] = options.cacheControl;
|
|
305
|
+
if (options.contentDisposition)
|
|
306
|
+
meta['contentDisposition'] = options.contentDisposition;
|
|
307
|
+
const metaPath = filePath + '.meta.json';
|
|
308
|
+
await fsPromises.writeFile(metaPath, JSON.stringify(meta));
|
|
309
|
+
}
|
|
310
|
+
const fileUrl = this.generateFileUrl(filePath);
|
|
311
|
+
const relativePath = this.normalizePathSeparators(path.relative(this.basePath, path.resolve(filePath)));
|
|
312
|
+
return this.createSuccessResult(relativePath, fileUrl);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
await this.cleanupTempFile(file);
|
|
316
|
+
return this.createErrorResult(error instanceof Error ? error.message : 'Failed to upload file');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Uploads a large file using streaming.
|
|
321
|
+
* Pipes the file stream directly to disk for memory efficiency.
|
|
322
|
+
*/
|
|
323
|
+
async uploadWithStream(file, filePath) {
|
|
324
|
+
return new Promise((resolve, reject) => {
|
|
325
|
+
const readStream = this.getFileStream(file);
|
|
326
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
327
|
+
readStream
|
|
328
|
+
.pipe(writeStream)
|
|
329
|
+
.on('finish', resolve)
|
|
330
|
+
.on('error', (err) => {
|
|
331
|
+
void fsPromises.unlink(filePath).catch(() => { });
|
|
332
|
+
reject(err);
|
|
333
|
+
});
|
|
334
|
+
readStream.on('error', (err) => {
|
|
335
|
+
writeStream.destroy();
|
|
336
|
+
void fsPromises.unlink(filePath).catch(() => { });
|
|
337
|
+
reject(err);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Builds a URL for accessing the file.
|
|
343
|
+
*
|
|
344
|
+
* If basePath starts with 'public/', strips that prefix since
|
|
345
|
+
* Express.static('public') serves files from /
|
|
346
|
+
*/
|
|
347
|
+
generateFileUrl(filePath) {
|
|
348
|
+
const absoluteFilePath = path.resolve(filePath);
|
|
349
|
+
const relativeFromBase = this.normalizePathSeparators(path.relative(this.basePath, absoluteFilePath));
|
|
350
|
+
const normalizedLocalPath = this.normalizePathSeparators(this.originalLocalPath);
|
|
351
|
+
if (normalizedLocalPath.startsWith('public/')) {
|
|
352
|
+
const webBasePath = normalizedLocalPath.replace(/^public\//, '');
|
|
353
|
+
return this.normalizeUrl(`/${webBasePath}/${relativeFromBase}`);
|
|
354
|
+
}
|
|
355
|
+
return this.normalizeUrl(`/${normalizedLocalPath}/${relativeFromBase}`);
|
|
356
|
+
}
|
|
357
|
+
normalizePathSeparators(pathStr) {
|
|
358
|
+
return pathStr.replace(/\\/g, '/');
|
|
359
|
+
}
|
|
360
|
+
normalizeUrl(url) {
|
|
361
|
+
return url.replace(/\/+/g, '/');
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Local storage doesn't support presigned upload URLs.
|
|
365
|
+
*/
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
367
|
+
async generateUploadUrl(_fileName, _contentType, _maxSize) {
|
|
368
|
+
return this.createPresignedErrorResult('Presigned URLs are not supported for local storage', 'PRESIGNED_NOT_SUPPORTED');
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Local storage doesn't support presigned view URLs.
|
|
372
|
+
*/
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
374
|
+
async generateViewUrl(_fileName) {
|
|
375
|
+
return this.createPresignedErrorResult('Presigned URLs are not supported for local storage', 'PRESIGNED_NOT_SUPPORTED');
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Validates a local file exists and matches expected values.
|
|
379
|
+
*
|
|
380
|
+
* Content type detection uses a two-tier approach:
|
|
381
|
+
* 1. Magic byte detection (examines actual file content for security)
|
|
382
|
+
* 2. Extension-based fallback (when magic bytes don't match)
|
|
383
|
+
*/
|
|
384
|
+
async validateAndConfirmUpload(reference, options) {
|
|
385
|
+
try {
|
|
386
|
+
const filePath = await this.resolveFilePath(reference);
|
|
387
|
+
if (!filePath) {
|
|
388
|
+
return { success: false, error: 'File not found', code: 'FILE_NOT_FOUND' };
|
|
389
|
+
}
|
|
390
|
+
const stats = await fsPromises.stat(filePath);
|
|
391
|
+
const actual = {
|
|
392
|
+
contentType: await detectContentType(filePath, reference),
|
|
393
|
+
fileSize: stats.size,
|
|
394
|
+
};
|
|
395
|
+
const validationError = await this.checkUploadedFileMetadata(reference, actual, options);
|
|
396
|
+
if (validationError)
|
|
397
|
+
return validationError;
|
|
398
|
+
const result = {
|
|
399
|
+
success: true,
|
|
400
|
+
reference,
|
|
401
|
+
viewUrl: this.generateFileUrl(filePath),
|
|
402
|
+
actualFileSize: actual.fileSize,
|
|
403
|
+
};
|
|
404
|
+
if (actual.contentType)
|
|
405
|
+
result.actualContentType = actual.contentType;
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
error: error instanceof Error ? error.message : 'Failed to validate upload',
|
|
412
|
+
code: 'PROVIDER_ERROR',
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Deletes a file from local storage.
|
|
418
|
+
*
|
|
419
|
+
* Security: decodeFileName() rejects traversal/encoding attacks.
|
|
420
|
+
* Containment check ensures resolved path stays within basePath.
|
|
421
|
+
* Symlinks and non-files are rejected.
|
|
422
|
+
*/
|
|
423
|
+
async delete(reference) {
|
|
424
|
+
try {
|
|
425
|
+
const decoded = this.decodeFileName(reference);
|
|
426
|
+
const baseDir = this.basePath;
|
|
427
|
+
const resolvedPath = path.resolve(path.join(baseDir, decoded));
|
|
428
|
+
if (!resolvedPath.startsWith(baseDir + path.sep) && resolvedPath !== baseDir) {
|
|
429
|
+
return { success: false, reference, error: 'Invalid reference: path is outside storage directory', code: 'PATH_TRAVERSAL' };
|
|
430
|
+
}
|
|
431
|
+
let stat;
|
|
432
|
+
try {
|
|
433
|
+
stat = await fsPromises.lstat(resolvedPath);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return { success: false, reference, error: 'File not found', code: 'FILE_NOT_FOUND' };
|
|
437
|
+
}
|
|
438
|
+
if (stat.isSymbolicLink()) {
|
|
439
|
+
return { success: false, reference, error: 'Symbolic links cannot be deleted', code: 'VALIDATION_FAILED' };
|
|
440
|
+
}
|
|
441
|
+
if (!stat.isFile()) {
|
|
442
|
+
return { success: false, reference, error: 'Path is not a regular file', code: 'VALIDATION_FAILED' };
|
|
443
|
+
}
|
|
444
|
+
await fsPromises.unlink(resolvedPath);
|
|
445
|
+
return { success: true, reference };
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
return { success: false, reference, error: error instanceof Error ? error.message : 'Failed to delete file', code: 'PROVIDER_ERROR' };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Resolves a decoded reference to a verified file path within basePath.
|
|
453
|
+
* Checks containment (path stays inside basePath), rejects symlinks,
|
|
454
|
+
* and verifies the target is a regular file.
|
|
455
|
+
*
|
|
456
|
+
* Callers are responsible for decoding/validating the reference first
|
|
457
|
+
* (via decodeFileName or StorageManager's hasPathTraversal check).
|
|
458
|
+
*/
|
|
459
|
+
async resolveFilePath(reference) {
|
|
460
|
+
const baseDir = this.basePath;
|
|
461
|
+
let decoded;
|
|
462
|
+
try {
|
|
463
|
+
decoded = this.decodeFileName(reference);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
const directPath = path.join(baseDir, decoded);
|
|
469
|
+
const resolvedPath = path.resolve(directPath);
|
|
470
|
+
if (!resolvedPath.startsWith(baseDir + path.sep) && resolvedPath !== baseDir) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const stat = await fsPromises.lstat(directPath);
|
|
475
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
476
|
+
return null;
|
|
477
|
+
return directPath;
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Returns metadata about a file without downloading it.
|
|
485
|
+
* Uses magic byte detection for accurate content type identification.
|
|
486
|
+
*/
|
|
487
|
+
async getMetadata(reference) {
|
|
488
|
+
const filePath = await this.resolveFilePath(reference);
|
|
489
|
+
if (!filePath)
|
|
490
|
+
return null;
|
|
491
|
+
try {
|
|
492
|
+
const stats = await fsPromises.stat(filePath);
|
|
493
|
+
const contentType = await detectContentType(filePath, reference);
|
|
494
|
+
const info = {
|
|
495
|
+
name: reference,
|
|
496
|
+
size: stats.size,
|
|
497
|
+
lastModified: stats.mtime,
|
|
498
|
+
};
|
|
499
|
+
if (contentType) {
|
|
500
|
+
info.contentType = contentType;
|
|
501
|
+
}
|
|
502
|
+
return info;
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Lists files in local storage with optional prefix filtering and pagination.
|
|
510
|
+
*/
|
|
511
|
+
async listFiles(prefix, maxResults = 1000, continuationToken) {
|
|
512
|
+
try {
|
|
513
|
+
let decodedPrefix;
|
|
514
|
+
if (prefix) {
|
|
515
|
+
try {
|
|
516
|
+
decodedPrefix = decodeURIComponent(prefix);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return { success: false, error: 'Invalid prefix: malformed URL encoding', code: 'INVALID_INPUT' };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (decodedPrefix && (decodedPrefix.includes('..') || decodedPrefix.includes('\0'))) {
|
|
523
|
+
return { success: false, error: 'Invalid prefix: path traversal sequences are not allowed', code: 'PATH_TRAVERSAL' };
|
|
524
|
+
}
|
|
525
|
+
const validatedMaxResults = this.validateMaxResults(maxResults);
|
|
526
|
+
const baseDir = this.basePath;
|
|
527
|
+
try {
|
|
528
|
+
await fsPromises.access(baseDir);
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return { success: true, files: [] };
|
|
532
|
+
}
|
|
533
|
+
const { files, hasMore } = await walkDirectory(baseDir, {
|
|
534
|
+
prefix: decodedPrefix,
|
|
535
|
+
continuationToken,
|
|
536
|
+
maxCollect: validatedMaxResults + 1,
|
|
537
|
+
});
|
|
538
|
+
return paginateFiles(files, validatedMaxResults, hasMore);
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
return {
|
|
542
|
+
success: false,
|
|
543
|
+
error: error instanceof Error ? error.message : 'Failed to list files',
|
|
544
|
+
code: 'PROVIDER_ERROR',
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
//# sourceMappingURL=local.driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.driver.js","sourceRoot":"","sources":["../../../src/drivers/local.driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { BaseStorageDriver } from './base.driver.js';
|
|
2
|
+
import { FileUploadResult, PresignedUrlResult, StorageConfig, BlobValidationOptions, BlobValidationResult, ListFilesResult, UploadOptions, FileInfo, DeleteResult } from '../types/storage.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* S3StorageDriver - Handles file operations with Amazon S3.
|
|
5
|
+
*
|
|
6
|
+
* Supports two authentication methods:
|
|
7
|
+
* 1. Explicit credentials (AWS_ACCESS_KEY + AWS_SECRET_KEY)
|
|
8
|
+
* 2. IAM roles (when running on AWS infrastructure)
|
|
9
|
+
*
|
|
10
|
+
* If you don't provide credentials, the AWS SDK automatically uses
|
|
11
|
+
* IAM roles, environment variables, or the shared credentials file.
|
|
12
|
+
*
|
|
13
|
+
* When driver is 's3-presigned', upload() returns presigned URLs
|
|
14
|
+
* instead of uploading directly.
|
|
15
|
+
*
|
|
16
|
+
* Required packages: @aws-sdk/client-s3, @aws-sdk/lib-storage, @aws-sdk/s3-request-presigner
|
|
17
|
+
*/
|
|
18
|
+
export declare class S3StorageDriver extends BaseStorageDriver {
|
|
19
|
+
private _client?;
|
|
20
|
+
private readonly bucketName;
|
|
21
|
+
private readonly region;
|
|
22
|
+
constructor(config: StorageConfig);
|
|
23
|
+
private ensureClient;
|
|
24
|
+
destroy(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Uploads a file to S3, or returns a presigned URL when in presigned mode.
|
|
27
|
+
*
|
|
28
|
+
* For large files (>100MB), uses streaming multipart upload to reduce
|
|
29
|
+
* memory usage and improve reliability.
|
|
30
|
+
*/
|
|
31
|
+
upload(file: Express.Multer.File, options?: UploadOptions): Promise<FileUploadResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Uploads a large file using streaming multipart upload.
|
|
34
|
+
* Uses @aws-sdk/lib-storage which handles chunking and concurrency automatically.
|
|
35
|
+
*/
|
|
36
|
+
private uploadWithStream;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a presigned URL for uploading directly to S3.
|
|
39
|
+
*
|
|
40
|
+
* The URL enforces:
|
|
41
|
+
* - Exact content type (baked into the signature)
|
|
42
|
+
* - Exact file size (if provided)
|
|
43
|
+
*
|
|
44
|
+
* Clients that try to upload different content will get a 403.
|
|
45
|
+
*/
|
|
46
|
+
generateUploadUrl(fileName: string, contentType?: string, fileSize?: number): Promise<PresignedUrlResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a presigned URL for downloading/viewing a file.
|
|
49
|
+
*/
|
|
50
|
+
generateViewUrl(fileName: string): Promise<PresignedUrlResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Deletes a file from S3.
|
|
53
|
+
*/
|
|
54
|
+
delete(fileName: string): Promise<DeleteResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Confirms an upload and optionally validates the file.
|
|
57
|
+
* Uses shared validation logic from BaseStorageDriver.
|
|
58
|
+
*/
|
|
59
|
+
validateAndConfirmUpload(reference: string, options?: BlobValidationOptions): Promise<BlobValidationResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Returns metadata about a file from S3 without downloading it.
|
|
62
|
+
*/
|
|
63
|
+
getMetadata(reference: string): Promise<FileInfo | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Lists files in the bucket with optional prefix filtering and pagination.
|
|
66
|
+
*/
|
|
67
|
+
listFiles(prefix?: string, maxResults?: number, continuationToken?: string): Promise<ListFilesResult>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=s3.driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.driver.d.ts","sourceRoot":"","sources":["../../../src/drivers/s3.driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAgDrM;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAgB,SAAQ,iBAAiB;IACpD,OAAO,CAAC,OAAO,CAAC,CAA2B;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,aAAa;YAcnB,YAAY;IAmBjB,OAAO,IAAI,IAAI;IAKxB;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+D3F;;;OAGG;YACW,gBAAgB;IAyD9B;;;;;;;;OAQG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsC/G;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwBpE;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmCrD;;;OAGG;IACY,wBAAwB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IA6BhC;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoB9D;;OAEG;IACG,SAAS,CACb,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,GAAE,MAAa,EACzB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,eAAe,CAAC;CAyC5B"}
|