@parsrun/storage 0.1.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 ADDED
@@ -0,0 +1,176 @@
1
+ # @parsrun/storage
2
+
3
+ Edge-compatible object storage for Pars with S3 and R2 support.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Adapter**: S3, Cloudflare R2, Memory
8
+ - **Edge-Compatible**: Works on all runtimes
9
+ - **Presigned URLs**: Secure direct uploads
10
+ - **Streaming**: Large file support
11
+ - **Metadata**: File metadata management
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @parsrun/storage
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import { createStorage } from '@parsrun/storage';
23
+
24
+ const storage = createStorage({
25
+ adapter: 'r2', // or 's3', 'memory'
26
+ bucket: 'my-bucket',
27
+ });
28
+
29
+ // Upload file
30
+ await storage.put('uploads/photo.jpg', fileBuffer, {
31
+ contentType: 'image/jpeg',
32
+ });
33
+
34
+ // Get file
35
+ const file = await storage.get('uploads/photo.jpg');
36
+
37
+ // Delete file
38
+ await storage.delete('uploads/photo.jpg');
39
+ ```
40
+
41
+ ## API Overview
42
+
43
+ ### Adapters
44
+
45
+ #### S3
46
+
47
+ ```typescript
48
+ import { createS3Adapter } from '@parsrun/storage/adapters/s3';
49
+
50
+ const storage = createS3Adapter({
51
+ region: 'us-east-1',
52
+ bucket: 'my-bucket',
53
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
54
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
55
+ });
56
+ ```
57
+
58
+ #### Cloudflare R2
59
+
60
+ ```typescript
61
+ import { createR2Adapter } from '@parsrun/storage/adapters/r2';
62
+
63
+ // In Cloudflare Worker
64
+ const storage = createR2Adapter({
65
+ bucket: env.MY_BUCKET,
66
+ });
67
+ ```
68
+
69
+ #### Memory (Development)
70
+
71
+ ```typescript
72
+ import { createMemoryAdapter } from '@parsrun/storage/adapters/memory';
73
+
74
+ const storage = createMemoryAdapter();
75
+ ```
76
+
77
+ ### File Operations
78
+
79
+ ```typescript
80
+ // Upload
81
+ await storage.put('path/to/file.txt', 'Hello World', {
82
+ contentType: 'text/plain',
83
+ metadata: { uploadedBy: 'user:1' },
84
+ });
85
+
86
+ // Upload from stream
87
+ await storage.put('path/to/large-file.zip', readableStream, {
88
+ contentType: 'application/zip',
89
+ });
90
+
91
+ // Get file
92
+ const file = await storage.get('path/to/file.txt');
93
+ console.log(file.body); // Content
94
+ console.log(file.metadata); // Metadata
95
+ console.log(file.size); // Size in bytes
96
+
97
+ // Get as stream
98
+ const stream = await storage.getStream('path/to/large-file.zip');
99
+
100
+ // Check existence
101
+ const exists = await storage.exists('path/to/file.txt');
102
+
103
+ // Delete
104
+ await storage.delete('path/to/file.txt');
105
+
106
+ // Delete multiple
107
+ await storage.deleteMany(['file1.txt', 'file2.txt']);
108
+ ```
109
+
110
+ ### Listing Files
111
+
112
+ ```typescript
113
+ // List files
114
+ const files = await storage.list('uploads/', {
115
+ limit: 100,
116
+ cursor: 'next-page-token',
117
+ });
118
+
119
+ for (const file of files.objects) {
120
+ console.log(file.key, file.size, file.lastModified);
121
+ }
122
+
123
+ // Continue with next page
124
+ if (files.cursor) {
125
+ const nextPage = await storage.list('uploads/', {
126
+ cursor: files.cursor,
127
+ });
128
+ }
129
+ ```
130
+
131
+ ### Presigned URLs
132
+
133
+ ```typescript
134
+ // Generate upload URL (PUT)
135
+ const uploadUrl = await storage.getPresignedUrl('uploads/new-file.jpg', {
136
+ method: 'PUT',
137
+ expiresIn: 3600, // 1 hour
138
+ contentType: 'image/jpeg',
139
+ });
140
+
141
+ // Generate download URL (GET)
142
+ const downloadUrl = await storage.getPresignedUrl('uploads/photo.jpg', {
143
+ method: 'GET',
144
+ expiresIn: 3600,
145
+ });
146
+
147
+ // Client-side upload
148
+ await fetch(uploadUrl, {
149
+ method: 'PUT',
150
+ body: file,
151
+ headers: { 'Content-Type': 'image/jpeg' },
152
+ });
153
+ ```
154
+
155
+ ### Copy & Move
156
+
157
+ ```typescript
158
+ // Copy file
159
+ await storage.copy('source/file.txt', 'dest/file.txt');
160
+
161
+ // Move file
162
+ await storage.move('old/path.txt', 'new/path.txt');
163
+ ```
164
+
165
+ ## Exports
166
+
167
+ ```typescript
168
+ import { ... } from '@parsrun/storage'; // Main exports
169
+ import { ... } from '@parsrun/storage/adapters/s3'; // S3 adapter
170
+ import { ... } from '@parsrun/storage/adapters/r2'; // R2 adapter
171
+ import { ... } from '@parsrun/storage/adapters/memory'; // Memory adapter
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,67 @@
1
+ import { a as StorageAdapter, M as MemoryConfig, U as UploadOptions, F as FileMetadata, D as DownloadOptions, d as DeleteResult, B as BatchDeleteResult, L as ListOptions, c as ListResult, C as CopyOptions, P as PresignedUrlOptions } from '../types-CCTK5LsZ.js';
2
+
3
+ /**
4
+ * @parsrun/storage - Memory Adapter
5
+ * In-memory storage adapter for development and testing
6
+ */
7
+
8
+ /**
9
+ * Memory Storage Adapter
10
+ * Stores files in memory - useful for development and testing
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const storage = new MemoryAdapter({
15
+ * type: 'memory',
16
+ * bucket: 'test-bucket',
17
+ * });
18
+ *
19
+ * await storage.upload('test.txt', 'Hello, World!');
20
+ * const data = await storage.download('test.txt');
21
+ * ```
22
+ */
23
+ declare class MemoryAdapter implements StorageAdapter {
24
+ readonly type: "memory";
25
+ readonly bucket: string;
26
+ private files;
27
+ private maxSize;
28
+ private currentSize;
29
+ private basePath;
30
+ constructor(config: MemoryConfig);
31
+ private getFullKey;
32
+ private validateKey;
33
+ private dataToUint8Array;
34
+ private streamToUint8Array;
35
+ upload(key: string, data: Uint8Array | ReadableStream<Uint8Array> | Blob | string, options?: UploadOptions): Promise<FileMetadata>;
36
+ download(key: string, _options?: DownloadOptions): Promise<Uint8Array>;
37
+ downloadStream(key: string, _options?: DownloadOptions): Promise<ReadableStream<Uint8Array>>;
38
+ head(key: string): Promise<FileMetadata | null>;
39
+ exists(key: string): Promise<boolean>;
40
+ delete(key: string): Promise<DeleteResult>;
41
+ deleteMany(keys: string[]): Promise<BatchDeleteResult>;
42
+ list(options?: ListOptions): Promise<ListResult>;
43
+ copy(sourceKey: string, destKey: string, options?: CopyOptions): Promise<FileMetadata>;
44
+ move(sourceKey: string, destKey: string): Promise<FileMetadata>;
45
+ getPresignedUrl(key: string, options?: PresignedUrlOptions): Promise<string>;
46
+ getUploadUrl(key: string, options?: PresignedUrlOptions): Promise<string>;
47
+ /**
48
+ * Clear all files (useful for testing)
49
+ */
50
+ clear(): void;
51
+ /**
52
+ * Get current storage size
53
+ */
54
+ getSize(): number;
55
+ /**
56
+ * Get file count
57
+ */
58
+ getFileCount(): number;
59
+ private guessContentType;
60
+ private generateEtag;
61
+ }
62
+ /**
63
+ * Create a memory storage adapter
64
+ */
65
+ declare function createMemoryAdapter(config: Omit<MemoryConfig, "type">): MemoryAdapter;
66
+
67
+ export { MemoryAdapter, createMemoryAdapter };
@@ -0,0 +1,311 @@
1
+ // src/types.ts
2
+ import {
3
+ type,
4
+ fileMetadata,
5
+ uploadOptions,
6
+ signedUrlOptions,
7
+ listFilesOptions,
8
+ listFilesResult,
9
+ localStorageConfig,
10
+ s3StorageConfig,
11
+ r2StorageConfig,
12
+ gcsStorageConfig,
13
+ storageProviderConfig
14
+ } from "@parsrun/types";
15
+ var StorageError = class extends Error {
16
+ constructor(message, code, statusCode, cause) {
17
+ super(message);
18
+ this.code = code;
19
+ this.statusCode = statusCode;
20
+ this.cause = cause;
21
+ this.name = "StorageError";
22
+ }
23
+ };
24
+ var StorageErrorCodes = {
25
+ NOT_FOUND: "NOT_FOUND",
26
+ ACCESS_DENIED: "ACCESS_DENIED",
27
+ BUCKET_NOT_FOUND: "BUCKET_NOT_FOUND",
28
+ INVALID_KEY: "INVALID_KEY",
29
+ UPLOAD_FAILED: "UPLOAD_FAILED",
30
+ DOWNLOAD_FAILED: "DOWNLOAD_FAILED",
31
+ DELETE_FAILED: "DELETE_FAILED",
32
+ COPY_FAILED: "COPY_FAILED",
33
+ LIST_FAILED: "LIST_FAILED",
34
+ PRESIGN_FAILED: "PRESIGN_FAILED",
35
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
36
+ INVALID_CONFIG: "INVALID_CONFIG",
37
+ ADAPTER_NOT_AVAILABLE: "ADAPTER_NOT_AVAILABLE"
38
+ };
39
+
40
+ // src/adapters/memory.ts
41
+ var MemoryAdapter = class {
42
+ type = "memory";
43
+ bucket;
44
+ files = /* @__PURE__ */ new Map();
45
+ maxSize;
46
+ currentSize = 0;
47
+ basePath;
48
+ constructor(config) {
49
+ this.bucket = config.bucket;
50
+ this.maxSize = config.maxSize ?? Infinity;
51
+ this.basePath = config.basePath ?? "";
52
+ }
53
+ getFullKey(key) {
54
+ return this.basePath ? `${this.basePath}/${key}` : key;
55
+ }
56
+ validateKey(key) {
57
+ if (!key || key.includes("..") || key.startsWith("/")) {
58
+ throw new StorageError(
59
+ `Invalid key: ${key}`,
60
+ StorageErrorCodes.INVALID_KEY
61
+ );
62
+ }
63
+ }
64
+ dataToUint8Array(data) {
65
+ if (data instanceof Uint8Array) {
66
+ return data;
67
+ }
68
+ if (typeof data === "string") {
69
+ return new TextEncoder().encode(data);
70
+ }
71
+ if (data instanceof Blob) {
72
+ return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
73
+ }
74
+ return this.streamToUint8Array(data);
75
+ }
76
+ async streamToUint8Array(stream) {
77
+ const reader = stream.getReader();
78
+ const chunks = [];
79
+ while (true) {
80
+ const { done, value } = await reader.read();
81
+ if (done) break;
82
+ chunks.push(value);
83
+ }
84
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
85
+ const result = new Uint8Array(totalLength);
86
+ let offset = 0;
87
+ for (const chunk of chunks) {
88
+ result.set(chunk, offset);
89
+ offset += chunk.length;
90
+ }
91
+ return result;
92
+ }
93
+ async upload(key, data, options) {
94
+ this.validateKey(key);
95
+ const fullKey = this.getFullKey(key);
96
+ const uint8Data = await this.dataToUint8Array(data);
97
+ const existingFile = this.files.get(fullKey);
98
+ const sizeDiff = uint8Data.length - (existingFile?.data.length ?? 0);
99
+ if (this.currentSize + sizeDiff > this.maxSize) {
100
+ throw new StorageError(
101
+ "Storage quota exceeded",
102
+ StorageErrorCodes.QUOTA_EXCEEDED
103
+ );
104
+ }
105
+ const metadata = {
106
+ key: fullKey,
107
+ size: uint8Data.length,
108
+ contentType: options?.contentType ?? this.guessContentType(key),
109
+ lastModified: /* @__PURE__ */ new Date(),
110
+ etag: this.generateEtag(uint8Data),
111
+ metadata: options?.metadata ?? void 0
112
+ };
113
+ this.files.set(fullKey, { data: uint8Data, metadata });
114
+ this.currentSize += sizeDiff;
115
+ return metadata;
116
+ }
117
+ async download(key, _options) {
118
+ this.validateKey(key);
119
+ const fullKey = this.getFullKey(key);
120
+ const file = this.files.get(fullKey);
121
+ if (!file) {
122
+ throw new StorageError(
123
+ `File not found: ${key}`,
124
+ StorageErrorCodes.NOT_FOUND,
125
+ 404
126
+ );
127
+ }
128
+ return file.data;
129
+ }
130
+ async downloadStream(key, _options) {
131
+ const data = await this.download(key);
132
+ return new ReadableStream({
133
+ start(controller) {
134
+ controller.enqueue(data);
135
+ controller.close();
136
+ }
137
+ });
138
+ }
139
+ async head(key) {
140
+ this.validateKey(key);
141
+ const fullKey = this.getFullKey(key);
142
+ const file = this.files.get(fullKey);
143
+ return file?.metadata ?? null;
144
+ }
145
+ async exists(key) {
146
+ this.validateKey(key);
147
+ const fullKey = this.getFullKey(key);
148
+ return this.files.has(fullKey);
149
+ }
150
+ async delete(key) {
151
+ this.validateKey(key);
152
+ const fullKey = this.getFullKey(key);
153
+ const file = this.files.get(fullKey);
154
+ if (file) {
155
+ this.currentSize -= file.data.length;
156
+ this.files.delete(fullKey);
157
+ }
158
+ return { success: true, key: fullKey };
159
+ }
160
+ async deleteMany(keys) {
161
+ const deleted = [];
162
+ const errors = [];
163
+ for (const key of keys) {
164
+ try {
165
+ await this.delete(key);
166
+ deleted.push(this.getFullKey(key));
167
+ } catch (err) {
168
+ errors.push({
169
+ key: this.getFullKey(key),
170
+ error: err instanceof Error ? err.message : "Unknown error"
171
+ });
172
+ }
173
+ }
174
+ return { deleted, errors };
175
+ }
176
+ async list(options) {
177
+ const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath;
178
+ const delimiter = options?.delimiter ?? "/";
179
+ const maxKeys = options?.maxKeys ?? 1e3;
180
+ const files = [];
181
+ const prefixSet = /* @__PURE__ */ new Set();
182
+ for (const [key, file] of this.files) {
183
+ if (prefix && !key.startsWith(prefix)) {
184
+ continue;
185
+ }
186
+ const relativePath = prefix ? key.slice(prefix.length) : key;
187
+ if (delimiter) {
188
+ const delimiterIndex = relativePath.indexOf(delimiter);
189
+ if (delimiterIndex !== -1) {
190
+ const commonPrefix = key.slice(0, prefix.length + delimiterIndex + 1);
191
+ prefixSet.add(commonPrefix);
192
+ continue;
193
+ }
194
+ }
195
+ files.push(file.metadata);
196
+ if (files.length >= maxKeys) {
197
+ break;
198
+ }
199
+ }
200
+ return {
201
+ files,
202
+ prefixes: Array.from(prefixSet),
203
+ isTruncated: files.length >= maxKeys,
204
+ continuationToken: void 0
205
+ };
206
+ }
207
+ async copy(sourceKey, destKey, options) {
208
+ this.validateKey(sourceKey);
209
+ this.validateKey(destKey);
210
+ const sourceFullKey = this.getFullKey(sourceKey);
211
+ const destFullKey = this.getFullKey(destKey);
212
+ const sourceFile = this.files.get(sourceFullKey);
213
+ if (!sourceFile) {
214
+ throw new StorageError(
215
+ `Source file not found: ${sourceKey}`,
216
+ StorageErrorCodes.NOT_FOUND,
217
+ 404
218
+ );
219
+ }
220
+ const newMetadata = options?.metadataDirective === "REPLACE" ? {
221
+ key: destFullKey,
222
+ size: sourceFile.data.length,
223
+ contentType: options.contentType ?? sourceFile.metadata.contentType,
224
+ lastModified: /* @__PURE__ */ new Date(),
225
+ etag: sourceFile.metadata.etag,
226
+ metadata: options.metadata ?? void 0
227
+ } : {
228
+ ...sourceFile.metadata,
229
+ key: destFullKey,
230
+ lastModified: /* @__PURE__ */ new Date()
231
+ };
232
+ this.files.set(destFullKey, {
233
+ data: sourceFile.data,
234
+ metadata: newMetadata
235
+ });
236
+ this.currentSize += sourceFile.data.length;
237
+ return newMetadata;
238
+ }
239
+ async move(sourceKey, destKey) {
240
+ const metadata = await this.copy(sourceKey, destKey);
241
+ await this.delete(sourceKey);
242
+ return metadata;
243
+ }
244
+ async getPresignedUrl(key, options) {
245
+ this.validateKey(key);
246
+ const fullKey = this.getFullKey(key);
247
+ const expiresIn = options?.expiresIn ?? 3600;
248
+ const expires = Date.now() + expiresIn * 1e3;
249
+ return `memory://${this.bucket}/${fullKey}?expires=${expires}`;
250
+ }
251
+ async getUploadUrl(key, options) {
252
+ return this.getPresignedUrl(key, options);
253
+ }
254
+ /**
255
+ * Clear all files (useful for testing)
256
+ */
257
+ clear() {
258
+ this.files.clear();
259
+ this.currentSize = 0;
260
+ }
261
+ /**
262
+ * Get current storage size
263
+ */
264
+ getSize() {
265
+ return this.currentSize;
266
+ }
267
+ /**
268
+ * Get file count
269
+ */
270
+ getFileCount() {
271
+ return this.files.size;
272
+ }
273
+ guessContentType(key) {
274
+ const ext = key.split(".").pop()?.toLowerCase();
275
+ const contentTypes = {
276
+ txt: "text/plain",
277
+ html: "text/html",
278
+ css: "text/css",
279
+ js: "application/javascript",
280
+ json: "application/json",
281
+ xml: "application/xml",
282
+ pdf: "application/pdf",
283
+ zip: "application/zip",
284
+ png: "image/png",
285
+ jpg: "image/jpeg",
286
+ jpeg: "image/jpeg",
287
+ gif: "image/gif",
288
+ webp: "image/webp",
289
+ svg: "image/svg+xml",
290
+ mp3: "audio/mpeg",
291
+ mp4: "video/mp4",
292
+ webm: "video/webm"
293
+ };
294
+ return contentTypes[ext ?? ""] ?? "application/octet-stream";
295
+ }
296
+ generateEtag(data) {
297
+ let hash = 0;
298
+ for (const byte of data) {
299
+ hash = (hash << 5) - hash + byte | 0;
300
+ }
301
+ return `"${Math.abs(hash).toString(16)}"`;
302
+ }
303
+ };
304
+ function createMemoryAdapter(config) {
305
+ return new MemoryAdapter({ ...config, type: "memory" });
306
+ }
307
+ export {
308
+ MemoryAdapter,
309
+ createMemoryAdapter
310
+ };
311
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/memory.ts"],"sourcesContent":["/**\n * @parsrun/storage - Type Definitions\n * Storage abstraction types for edge-compatible storage\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n fileMetadata,\n uploadOptions as parsUploadOptions,\n signedUrlOptions,\n listFilesOptions,\n listFilesResult,\n localStorageConfig,\n s3StorageConfig,\n r2StorageConfig,\n gcsStorageConfig,\n storageProviderConfig,\n type FileMetadata as ParsFileMetadata,\n type UploadOptions as ParsUploadOptions,\n type SignedUrlOptions,\n type ListFilesOptions,\n type ListFilesResult,\n type LocalStorageConfig,\n type S3StorageConfig,\n type R2StorageConfig,\n type GcsStorageConfig,\n type StorageProviderConfig,\n} from \"@parsrun/types\";\n\n/**\n * Storage adapter type\n */\nexport type StorageAdapterType = \"s3\" | \"r2\" | \"do-spaces\" | \"memory\" | \"custom\";\n\n/**\n * File metadata\n */\nexport interface FileMetadata {\n /** File key/path */\n key: string;\n /** File size in bytes */\n size: number;\n /** Content type (MIME type) */\n contentType: string | undefined;\n /** Last modified date */\n lastModified: Date | undefined;\n /** ETag (entity tag) */\n etag: string | undefined;\n /** Custom metadata */\n metadata: Record<string, string> | undefined;\n}\n\n/**\n * Upload options\n */\nexport interface UploadOptions {\n /** Content type override */\n contentType?: string | undefined;\n /** Content disposition */\n contentDisposition?: string | undefined;\n /** Cache control header */\n cacheControl?: string | undefined;\n /** Content encoding */\n contentEncoding?: string | undefined;\n /** Custom metadata */\n metadata?: Record<string, string> | undefined;\n /** ACL (access control list) */\n acl?: \"private\" | \"public-read\" | undefined;\n}\n\n/**\n * Download options\n */\nexport interface DownloadOptions {\n /** Range start (for partial downloads) */\n rangeStart?: number | undefined;\n /** Range end (for partial downloads) */\n rangeEnd?: number | undefined;\n /** If-None-Match (ETag) */\n ifNoneMatch?: string | undefined;\n /** If-Modified-Since */\n ifModifiedSince?: Date | undefined;\n}\n\n/**\n * List options\n */\nexport interface ListOptions {\n /** Prefix to filter by */\n prefix?: string | undefined;\n /** Delimiter for hierarchy */\n delimiter?: string | undefined;\n /** Maximum keys to return */\n maxKeys?: number | undefined;\n /** Continuation token for pagination */\n continuationToken?: string | undefined;\n}\n\n/**\n * List result\n */\nexport interface ListResult {\n /** List of files */\n files: FileMetadata[];\n /** Common prefixes (for hierarchical listing) */\n prefixes: string[];\n /** Whether there are more results */\n isTruncated: boolean;\n /** Continuation token for next page */\n continuationToken: string | undefined;\n}\n\n/**\n * Presigned URL options\n */\nexport interface PresignedUrlOptions {\n /** URL expiration in seconds (default: 3600) */\n expiresIn?: number | undefined;\n /** Content type (for upload URLs) */\n contentType?: string | undefined;\n /** Content disposition */\n contentDisposition?: string | undefined;\n /** Response cache control */\n responseCacheControl?: string | undefined;\n /** Response content type */\n responseContentType?: string | undefined;\n}\n\n/**\n * Copy options\n */\nexport interface CopyOptions {\n /** Source bucket (if different from destination) */\n sourceBucket?: string | undefined;\n /** Metadata directive */\n metadataDirective?: \"COPY\" | \"REPLACE\" | undefined;\n /** New metadata (if REPLACE) */\n metadata?: Record<string, string> | undefined;\n /** New content type (if REPLACE) */\n contentType?: string | undefined;\n}\n\n/**\n * Delete result\n */\nexport interface DeleteResult {\n /** Whether deletion was successful */\n success: boolean;\n /** Deleted key */\n key: string;\n}\n\n/**\n * Batch delete result\n */\nexport interface BatchDeleteResult {\n /** Successfully deleted keys */\n deleted: string[];\n /** Failed deletions */\n errors: Array<{ key: string; error: string }>;\n}\n\n/**\n * Storage adapter interface\n * All storage adapters must implement this interface\n */\nexport interface StorageAdapter {\n /** Adapter type */\n readonly type: StorageAdapterType;\n\n /** Bucket name */\n readonly bucket: string;\n\n /**\n * Upload a file\n * @param key - File key/path\n * @param data - File data\n * @param options - Upload options\n */\n upload(\n key: string,\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string,\n options?: UploadOptions\n ): Promise<FileMetadata>;\n\n /**\n * Download a file\n * @param key - File key/path\n * @param options - Download options\n */\n download(key: string, options?: DownloadOptions): Promise<Uint8Array>;\n\n /**\n * Download a file as a stream\n * @param key - File key/path\n * @param options - Download options\n */\n downloadStream(\n key: string,\n options?: DownloadOptions\n ): Promise<ReadableStream<Uint8Array>>;\n\n /**\n * Get file metadata\n * @param key - File key/path\n */\n head(key: string): Promise<FileMetadata | null>;\n\n /**\n * Check if file exists\n * @param key - File key/path\n */\n exists(key: string): Promise<boolean>;\n\n /**\n * Delete a file\n * @param key - File key/path\n */\n delete(key: string): Promise<DeleteResult>;\n\n /**\n * Delete multiple files\n * @param keys - File keys/paths\n */\n deleteMany(keys: string[]): Promise<BatchDeleteResult>;\n\n /**\n * List files\n * @param options - List options\n */\n list(options?: ListOptions): Promise<ListResult>;\n\n /**\n * Copy a file\n * @param sourceKey - Source key\n * @param destKey - Destination key\n * @param options - Copy options\n */\n copy(sourceKey: string, destKey: string, options?: CopyOptions): Promise<FileMetadata>;\n\n /**\n * Move/rename a file\n * @param sourceKey - Source key\n * @param destKey - Destination key\n */\n move(sourceKey: string, destKey: string): Promise<FileMetadata>;\n\n /**\n * Generate a presigned URL for download\n * @param key - File key/path\n * @param options - URL options\n */\n getPresignedUrl(key: string, options?: PresignedUrlOptions): Promise<string>;\n\n /**\n * Generate a presigned URL for upload\n * @param key - File key/path\n * @param options - URL options\n */\n getUploadUrl(key: string, options?: PresignedUrlOptions): Promise<string>;\n}\n\n/**\n * Storage configuration\n */\nexport interface StorageConfig {\n /** Default bucket name */\n bucket: string;\n /** Storage adapter type */\n type: StorageAdapterType;\n /** Base path prefix for all operations */\n basePath?: string | undefined;\n /** Public URL prefix */\n publicUrl?: string | undefined;\n}\n\n/**\n * S3 configuration\n */\nexport interface S3Config extends StorageConfig {\n type: \"s3\" | \"do-spaces\";\n /** AWS region */\n region: string;\n /** Access key ID */\n accessKeyId: string;\n /** Secret access key */\n secretAccessKey: string;\n /** Custom endpoint (for DO Spaces, MinIO, etc.) */\n endpoint?: string | undefined;\n /** Force path style (for MinIO) */\n forcePathStyle?: boolean | undefined;\n}\n\n/**\n * R2 configuration\n */\nexport interface R2Config extends StorageConfig {\n type: \"r2\";\n /** Cloudflare account ID */\n accountId: string;\n /** R2 access key ID */\n accessKeyId: string;\n /** R2 secret access key */\n secretAccessKey: string;\n /** Custom domain for public access */\n customDomain?: string | undefined;\n}\n\n/**\n * Memory storage configuration (for testing)\n */\nexport interface MemoryConfig extends StorageConfig {\n type: \"memory\";\n /** Maximum storage size in bytes */\n maxSize?: number | undefined;\n}\n\n/**\n * Storage error\n */\nexport class StorageError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number | undefined,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"StorageError\";\n }\n}\n\n/**\n * Common storage error codes\n */\nexport const StorageErrorCodes = {\n NOT_FOUND: \"NOT_FOUND\",\n ACCESS_DENIED: \"ACCESS_DENIED\",\n BUCKET_NOT_FOUND: \"BUCKET_NOT_FOUND\",\n INVALID_KEY: \"INVALID_KEY\",\n UPLOAD_FAILED: \"UPLOAD_FAILED\",\n DOWNLOAD_FAILED: \"DOWNLOAD_FAILED\",\n DELETE_FAILED: \"DELETE_FAILED\",\n COPY_FAILED: \"COPY_FAILED\",\n LIST_FAILED: \"LIST_FAILED\",\n PRESIGN_FAILED: \"PRESIGN_FAILED\",\n QUOTA_EXCEEDED: \"QUOTA_EXCEEDED\",\n INVALID_CONFIG: \"INVALID_CONFIG\",\n ADAPTER_NOT_AVAILABLE: \"ADAPTER_NOT_AVAILABLE\",\n} as const;\n","/**\n * @parsrun/storage - Memory Adapter\n * In-memory storage adapter for development and testing\n */\n\nimport {\n type BatchDeleteResult,\n type CopyOptions,\n type DeleteResult,\n type DownloadOptions,\n type FileMetadata,\n type ListOptions,\n type ListResult,\n type MemoryConfig,\n type PresignedUrlOptions,\n type StorageAdapter,\n type UploadOptions,\n StorageError,\n StorageErrorCodes,\n} from \"../types.js\";\n\n/**\n * Internal file storage structure\n */\ninterface StoredFile {\n data: Uint8Array;\n metadata: FileMetadata;\n}\n\n/**\n * Memory Storage Adapter\n * Stores files in memory - useful for development and testing\n *\n * @example\n * ```typescript\n * const storage = new MemoryAdapter({\n * type: 'memory',\n * bucket: 'test-bucket',\n * });\n *\n * await storage.upload('test.txt', 'Hello, World!');\n * const data = await storage.download('test.txt');\n * ```\n */\nexport class MemoryAdapter implements StorageAdapter {\n readonly type = \"memory\" as const;\n readonly bucket: string;\n\n private files: Map<string, StoredFile> = new Map();\n private maxSize: number;\n private currentSize = 0;\n private basePath: string;\n\n constructor(config: MemoryConfig) {\n this.bucket = config.bucket;\n this.maxSize = config.maxSize ?? Infinity;\n this.basePath = config.basePath ?? \"\";\n }\n\n private getFullKey(key: string): string {\n return this.basePath ? `${this.basePath}/${key}` : key;\n }\n\n private validateKey(key: string): void {\n if (!key || key.includes(\"..\") || key.startsWith(\"/\")) {\n throw new StorageError(\n `Invalid key: ${key}`,\n StorageErrorCodes.INVALID_KEY\n );\n }\n }\n\n private dataToUint8Array(\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string\n ): Promise<Uint8Array> | Uint8Array {\n if (data instanceof Uint8Array) {\n return data;\n }\n\n if (typeof data === \"string\") {\n return new TextEncoder().encode(data);\n }\n\n if (data instanceof Blob) {\n return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));\n }\n\n // ReadableStream\n return this.streamToUint8Array(data);\n }\n\n private async streamToUint8Array(\n stream: ReadableStream<Uint8Array>\n ): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n async upload(\n key: string,\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string,\n options?: UploadOptions\n ): Promise<FileMetadata> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const uint8Data = await this.dataToUint8Array(data);\n\n // Check quota\n const existingFile = this.files.get(fullKey);\n const sizeDiff = uint8Data.length - (existingFile?.data.length ?? 0);\n\n if (this.currentSize + sizeDiff > this.maxSize) {\n throw new StorageError(\n \"Storage quota exceeded\",\n StorageErrorCodes.QUOTA_EXCEEDED\n );\n }\n\n const metadata: FileMetadata = {\n key: fullKey,\n size: uint8Data.length,\n contentType: options?.contentType ?? this.guessContentType(key),\n lastModified: new Date(),\n etag: this.generateEtag(uint8Data),\n metadata: options?.metadata ?? undefined,\n };\n\n this.files.set(fullKey, { data: uint8Data, metadata });\n this.currentSize += sizeDiff;\n\n return metadata;\n }\n\n async download(key: string, _options?: DownloadOptions): Promise<Uint8Array> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const file = this.files.get(fullKey);\n if (!file) {\n throw new StorageError(\n `File not found: ${key}`,\n StorageErrorCodes.NOT_FOUND,\n 404\n );\n }\n\n return file.data;\n }\n\n async downloadStream(\n key: string,\n _options?: DownloadOptions\n ): Promise<ReadableStream<Uint8Array>> {\n const data = await this.download(key);\n\n return new ReadableStream({\n start(controller) {\n controller.enqueue(data);\n controller.close();\n },\n });\n }\n\n async head(key: string): Promise<FileMetadata | null> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const file = this.files.get(fullKey);\n return file?.metadata ?? null;\n }\n\n async exists(key: string): Promise<boolean> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n return this.files.has(fullKey);\n }\n\n async delete(key: string): Promise<DeleteResult> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const file = this.files.get(fullKey);\n if (file) {\n this.currentSize -= file.data.length;\n this.files.delete(fullKey);\n }\n\n return { success: true, key: fullKey };\n }\n\n async deleteMany(keys: string[]): Promise<BatchDeleteResult> {\n const deleted: string[] = [];\n const errors: Array<{ key: string; error: string }> = [];\n\n for (const key of keys) {\n try {\n await this.delete(key);\n deleted.push(this.getFullKey(key));\n } catch (err) {\n errors.push({\n key: this.getFullKey(key),\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n }\n }\n\n return { deleted, errors };\n }\n\n async list(options?: ListOptions): Promise<ListResult> {\n const prefix = options?.prefix\n ? this.getFullKey(options.prefix)\n : this.basePath;\n const delimiter = options?.delimiter ?? \"/\";\n const maxKeys = options?.maxKeys ?? 1000;\n\n const files: FileMetadata[] = [];\n const prefixSet = new Set<string>();\n\n for (const [key, file] of this.files) {\n if (prefix && !key.startsWith(prefix)) {\n continue;\n }\n\n const relativePath = prefix ? key.slice(prefix.length) : key;\n\n // Handle delimiter for hierarchical listing\n if (delimiter) {\n const delimiterIndex = relativePath.indexOf(delimiter);\n if (delimiterIndex !== -1) {\n const commonPrefix = key.slice(0, prefix.length + delimiterIndex + 1);\n prefixSet.add(commonPrefix);\n continue;\n }\n }\n\n files.push(file.metadata);\n\n if (files.length >= maxKeys) {\n break;\n }\n }\n\n return {\n files,\n prefixes: Array.from(prefixSet),\n isTruncated: files.length >= maxKeys,\n continuationToken: undefined,\n };\n }\n\n async copy(\n sourceKey: string,\n destKey: string,\n options?: CopyOptions\n ): Promise<FileMetadata> {\n this.validateKey(sourceKey);\n this.validateKey(destKey);\n\n const sourceFullKey = this.getFullKey(sourceKey);\n const destFullKey = this.getFullKey(destKey);\n\n const sourceFile = this.files.get(sourceFullKey);\n if (!sourceFile) {\n throw new StorageError(\n `Source file not found: ${sourceKey}`,\n StorageErrorCodes.NOT_FOUND,\n 404\n );\n }\n\n const newMetadata: FileMetadata =\n options?.metadataDirective === \"REPLACE\"\n ? {\n key: destFullKey,\n size: sourceFile.data.length,\n contentType: options.contentType ?? sourceFile.metadata.contentType,\n lastModified: new Date(),\n etag: sourceFile.metadata.etag,\n metadata: options.metadata ?? undefined,\n }\n : {\n ...sourceFile.metadata,\n key: destFullKey,\n lastModified: new Date(),\n };\n\n this.files.set(destFullKey, {\n data: sourceFile.data,\n metadata: newMetadata,\n });\n this.currentSize += sourceFile.data.length;\n\n return newMetadata;\n }\n\n async move(sourceKey: string, destKey: string): Promise<FileMetadata> {\n const metadata = await this.copy(sourceKey, destKey);\n await this.delete(sourceKey);\n return metadata;\n }\n\n async getPresignedUrl(\n key: string,\n options?: PresignedUrlOptions\n ): Promise<string> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n // Memory adapter doesn't support real presigned URLs\n // Return a fake URL for testing purposes\n const expiresIn = options?.expiresIn ?? 3600;\n const expires = Date.now() + expiresIn * 1000;\n\n return `memory://${this.bucket}/${fullKey}?expires=${expires}`;\n }\n\n async getUploadUrl(\n key: string,\n options?: PresignedUrlOptions\n ): Promise<string> {\n return this.getPresignedUrl(key, options);\n }\n\n /**\n * Clear all files (useful for testing)\n */\n clear(): void {\n this.files.clear();\n this.currentSize = 0;\n }\n\n /**\n * Get current storage size\n */\n getSize(): number {\n return this.currentSize;\n }\n\n /**\n * Get file count\n */\n getFileCount(): number {\n return this.files.size;\n }\n\n private guessContentType(key: string): string {\n const ext = key.split(\".\").pop()?.toLowerCase();\n const contentTypes: Record<string, string> = {\n txt: \"text/plain\",\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n mp3: \"audio/mpeg\",\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n };\n return contentTypes[ext ?? \"\"] ?? \"application/octet-stream\";\n }\n\n private generateEtag(data: Uint8Array): string {\n // Simple hash for testing - not cryptographically secure\n let hash = 0;\n for (const byte of data) {\n hash = ((hash << 5) - hash + byte) | 0;\n }\n return `\"${Math.abs(hash).toString(16)}\"`;\n }\n}\n\n/**\n * Create a memory storage adapter\n */\nexport function createMemoryAdapter(\n config: Omit<MemoryConfig, \"type\">\n): MemoryAdapter {\n return new MemoryAdapter({ ...config, type: \"memory\" });\n}\n"],"mappings":"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","names":[]}