@od-oneapp/storage 2026.1.1301

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.
Files changed (69) hide show
  1. package/README.md +854 -0
  2. package/dist/client-next.d.mts +61 -0
  3. package/dist/client-next.d.mts.map +1 -0
  4. package/dist/client-next.mjs +111 -0
  5. package/dist/client-next.mjs.map +1 -0
  6. package/dist/client-utils-Dx6W25iz.d.mts +43 -0
  7. package/dist/client-utils-Dx6W25iz.d.mts.map +1 -0
  8. package/dist/client.d.mts +28 -0
  9. package/dist/client.d.mts.map +1 -0
  10. package/dist/client.mjs +183 -0
  11. package/dist/client.mjs.map +1 -0
  12. package/dist/env-BVHLmQdh.mjs +128 -0
  13. package/dist/env-BVHLmQdh.mjs.map +1 -0
  14. package/dist/env.mjs +3 -0
  15. package/dist/health-check-D7LnnDec.mjs +746 -0
  16. package/dist/health-check-D7LnnDec.mjs.map +1 -0
  17. package/dist/health-check-im_huJ59.d.mts +116 -0
  18. package/dist/health-check-im_huJ59.d.mts.map +1 -0
  19. package/dist/index.d.mts +60 -0
  20. package/dist/index.d.mts.map +1 -0
  21. package/dist/index.mjs +3 -0
  22. package/dist/keys.d.mts +37 -0
  23. package/dist/keys.d.mts.map +1 -0
  24. package/dist/keys.mjs +253 -0
  25. package/dist/keys.mjs.map +1 -0
  26. package/dist/server-edge.d.mts +28 -0
  27. package/dist/server-edge.d.mts.map +1 -0
  28. package/dist/server-edge.mjs +88 -0
  29. package/dist/server-edge.mjs.map +1 -0
  30. package/dist/server-next.d.mts +183 -0
  31. package/dist/server-next.d.mts.map +1 -0
  32. package/dist/server-next.mjs +1353 -0
  33. package/dist/server-next.mjs.map +1 -0
  34. package/dist/server.d.mts +70 -0
  35. package/dist/server.d.mts.map +1 -0
  36. package/dist/server.mjs +384 -0
  37. package/dist/server.mjs.map +1 -0
  38. package/dist/types.d.mts +321 -0
  39. package/dist/types.d.mts.map +1 -0
  40. package/dist/types.mjs +3 -0
  41. package/dist/validation.d.mts +101 -0
  42. package/dist/validation.d.mts.map +1 -0
  43. package/dist/validation.mjs +590 -0
  44. package/dist/validation.mjs.map +1 -0
  45. package/dist/vercel-blob-07Sx0Akn.d.mts +31 -0
  46. package/dist/vercel-blob-07Sx0Akn.d.mts.map +1 -0
  47. package/dist/vercel-blob-DA8HaYuw.mjs +158 -0
  48. package/dist/vercel-blob-DA8HaYuw.mjs.map +1 -0
  49. package/package.json +111 -0
  50. package/src/actions/blob-upload.ts +171 -0
  51. package/src/actions/index.ts +23 -0
  52. package/src/actions/mediaActions.ts +1071 -0
  53. package/src/actions/productMediaActions.ts +538 -0
  54. package/src/auth-helpers.ts +386 -0
  55. package/src/capabilities.ts +225 -0
  56. package/src/client-next.ts +184 -0
  57. package/src/client-utils.ts +292 -0
  58. package/src/client.ts +102 -0
  59. package/src/constants.ts +88 -0
  60. package/src/health-check.ts +81 -0
  61. package/src/multi-storage.ts +230 -0
  62. package/src/multipart.ts +497 -0
  63. package/src/retry-utils.test.ts +118 -0
  64. package/src/retry-utils.ts +59 -0
  65. package/src/server-edge.ts +129 -0
  66. package/src/server-next.ts +14 -0
  67. package/src/server.ts +666 -0
  68. package/src/validation.test.ts +312 -0
  69. package/src/validation.ts +827 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @fileoverview Client-side storage exports for Next.js
3
+ *
4
+ * This file provides client-side storage functionality specifically for Next.js applications.
5
+ * NO REACT HOOKS - keep in consuming apps to avoid React peer dependency.
6
+ *
7
+ * Features:
8
+ * - File upload with progress tracking
9
+ * - Next.js-specific upload helpers
10
+ * - Client-side validation
11
+ *
12
+ * @module @repo/storage/client/next
13
+ */
14
+
15
+ 'use client';
16
+
17
+ // Import for internal use
18
+ import { uploadFile } from './client';
19
+ import { validateMimeType } from './validation';
20
+
21
+ // Re-export all client functionality
22
+ export * from './client';
23
+
24
+ // Next.js-specific upload helpers (no hooks)
25
+ export async function uploadFileWithProgress(
26
+ pathname: string,
27
+ file: File | Blob,
28
+ options?: {
29
+ access?: 'public' | 'private';
30
+ addRandomSuffix?: boolean;
31
+ allowOverwrite?: boolean;
32
+ cacheControlMaxAge?: number;
33
+ clientPayload?: string;
34
+ contentType?: string;
35
+ multipart?: boolean;
36
+ onUploadProgress?: (event: { loaded: number; total?: number; percentage?: number }) => void;
37
+ handleUploadUrl?: string;
38
+ },
39
+ ): Promise<{ url: string; pathname: string }> {
40
+ // Use the base uploadFile function
41
+ return await uploadFile(pathname, file, options);
42
+ }
43
+
44
+ /**
45
+ * Upload multiple files with progress tracking
46
+ *
47
+ * @param files - Array of files to upload
48
+ * @param getPathname - Function to generate pathname for each file
49
+ * @param options - Upload options
50
+ * @returns Array of upload results
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const results = await uploadMultipleFiles(
55
+ * files,
56
+ * (file, index) => `uploads/${index}-${file.name}`,
57
+ * {
58
+ * onUploadProgress: (event) => {
59
+ * logInfo(`Uploaded ${event.percentage}%`);
60
+ * }
61
+ * }
62
+ * );
63
+ * ```
64
+ */
65
+ export async function uploadMultipleFiles(
66
+ files: File[],
67
+ getPathname: (file: File, index: number) => string,
68
+ options?: {
69
+ access?: 'public' | 'private';
70
+ addRandomSuffix?: boolean;
71
+ allowOverwrite?: boolean;
72
+ cacheControlMaxAge?: number;
73
+ clientPayload?: string;
74
+ contentType?: string;
75
+ multipart?: boolean;
76
+ onUploadProgress?: (event: { loaded: number; total?: number; percentage?: number }) => void;
77
+ handleUploadUrl?: string;
78
+ onFileProgress?: (
79
+ fileIndex: number,
80
+ progress: { loaded: number; total?: number; percentage?: number },
81
+ ) => void;
82
+ },
83
+ ): Promise<Array<{ url: string; pathname: string; success: boolean; error?: string }>> {
84
+ const results: Array<{ url: string; pathname: string; success: boolean; error?: string }> = [];
85
+
86
+ for (let i = 0; i < files.length; i++) {
87
+ const file = files[i];
88
+ if (!file) continue;
89
+ const pathname = getPathname(file, i);
90
+
91
+ try {
92
+ const result = await uploadFile(pathname, file, {
93
+ ...options,
94
+ onUploadProgress: options?.onFileProgress
95
+ ? (event: { loaded: number; total?: number; percentage?: number }) =>
96
+ options.onFileProgress?.(i, event)
97
+ : options?.onUploadProgress,
98
+ });
99
+
100
+ results.push({
101
+ ...result,
102
+ success: true,
103
+ });
104
+ } catch (error) {
105
+ results.push({
106
+ url: '',
107
+ pathname,
108
+ success: false,
109
+ error: error instanceof Error ? error.message : String(error),
110
+ });
111
+ }
112
+ }
113
+
114
+ return results;
115
+ }
116
+
117
+ /**
118
+ * Validate file before upload
119
+ *
120
+ * @param file - File to validate
121
+ * @param options - Validation options
122
+ * @returns Validation result
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const validation = await validateFileForUpload(file, {
127
+ * maxFileSize: 10 * 1024 * 1024, // 10MB
128
+ * allowedMimeTypes: ['image/jpeg', 'image/png'],
129
+ * });
130
+ *
131
+ * if (!validation.valid) {
132
+ * logError('Validation failed:', validation.errors);
133
+ * }
134
+ * ```
135
+ */
136
+ export async function validateFileForUpload(
137
+ file: File,
138
+ options: {
139
+ maxFileSize?: number;
140
+ allowedMimeTypes?: string[];
141
+ allowedExtensions?: string[];
142
+ } = {},
143
+ ): Promise<{ valid: boolean; errors: string[] }> {
144
+ const errors: string[] = [];
145
+
146
+ // Validate file size
147
+ if (options.maxFileSize && file.size > options.maxFileSize) {
148
+ errors.push(`File size ${file.size} bytes exceeds maximum ${options.maxFileSize} bytes`);
149
+ }
150
+
151
+ // Validate MIME type
152
+ if (options.allowedMimeTypes && options.allowedMimeTypes.length > 0) {
153
+ const mimeValidation = validateMimeType(file.type, options.allowedMimeTypes);
154
+ if (!mimeValidation.valid) {
155
+ if (mimeValidation.error) {
156
+ errors.push(mimeValidation.error);
157
+ }
158
+ }
159
+ }
160
+
161
+ // Validate file extension
162
+ if (options.allowedExtensions && options.allowedExtensions.length > 0) {
163
+ const extension = file.name.match(/\.[^/.]+$/)?.[0]?.toLowerCase();
164
+ if (extension && !options.allowedExtensions.includes(extension)) {
165
+ errors.push(
166
+ `File extension ${extension} is not allowed. Allowed: ${options.allowedExtensions.join(', ')}`,
167
+ );
168
+ }
169
+ }
170
+
171
+ return {
172
+ valid: errors.length === 0,
173
+ errors,
174
+ };
175
+ }
176
+
177
+ // Re-export validation utilities for convenience
178
+ export {
179
+ formatFileSize,
180
+ parseFileSize,
181
+ validateFileSize,
182
+ validateMimeType,
183
+ validateStorageKey,
184
+ } from './validation';
@@ -0,0 +1,292 @@
1
+ /**
2
+ * @fileoverview Client-side utilities for storage operations
3
+ *
4
+ * These work with server-side APIs to enable client uploads without exposing credentials.
5
+ * Provides presigned URL uploads and progress tracking.
6
+ *
7
+ * Features:
8
+ * - Presigned URL uploads
9
+ * - Upload progress tracking
10
+ * - Client-side file handling
11
+ *
12
+ * @module @repo/storage/client-utils
13
+ */
14
+
15
+ import type { PresignedUploadUrl as BasePresignedUploadUrl } from '../types';
16
+
17
+ /**
18
+ * Extended presigned upload URL configuration for client-side use
19
+ * Includes the storage key for tracking uploads
20
+ */
21
+ export interface ClientPresignedUploadUrl extends BasePresignedUploadUrl {
22
+ key: string;
23
+ }
24
+
25
+ /**
26
+ * Upload progress information for client-side tracking
27
+ * Uses 'percentage' to align with types.ts UploadProgress
28
+ */
29
+ export interface ClientUploadProgress {
30
+ loaded: number;
31
+ total: number;
32
+ percentage: number;
33
+ }
34
+
35
+ /**
36
+ * Upload a file using a presigned URL (works with R2, S3, etc.)
37
+ */
38
+ export async function uploadWithPresignedUrl(
39
+ presignedData: ClientPresignedUploadUrl,
40
+ file: File | Blob,
41
+ options?: {
42
+ onProgress?: (progress: ClientUploadProgress) => void;
43
+ },
44
+ ): Promise<void> {
45
+ const formData = new FormData();
46
+
47
+ // Add any required fields first (for POST uploads)
48
+ if (presignedData.fields) {
49
+ Object.entries(presignedData.fields).forEach(([key, value]) => {
50
+ formData.append(key, value);
51
+ });
52
+ }
53
+
54
+ // Add the file last
55
+ formData.append('file', file);
56
+
57
+ // For progress tracking, we need XMLHttpRequest
58
+ if (options?.onProgress) {
59
+ return new Promise((resolve, reject) => {
60
+ const xhr = new XMLHttpRequest();
61
+
62
+ xhr.upload.addEventListener('progress', event => {
63
+ if (event.lengthComputable && options.onProgress) {
64
+ options.onProgress({
65
+ loaded: event.loaded,
66
+ total: event.total,
67
+ percentage: (event.loaded / event.total) * 100,
68
+ });
69
+ }
70
+ });
71
+
72
+ xhr.addEventListener('load', () => {
73
+ if (xhr.status >= 200 && xhr.status < 300) {
74
+ resolve();
75
+ } else {
76
+ reject(new Error(`Upload failed with status ${xhr.status}`));
77
+ }
78
+ });
79
+
80
+ xhr.addEventListener('error', () => {
81
+ reject(new Error('Upload failed'));
82
+ });
83
+
84
+ xhr.open('POST', presignedData.url);
85
+ xhr.send(formData);
86
+ });
87
+ }
88
+
89
+ // Simple fetch for no progress tracking
90
+ const response = await fetch(presignedData.url, {
91
+ method: 'POST',
92
+ body: formData,
93
+ });
94
+
95
+ if (!response.ok) {
96
+ throw new Error(`Upload failed with status ${response.status}`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Upload directly to a URL (PUT method, typically for presigned PUT URLs)
102
+ */
103
+ export async function uploadDirectToUrl(
104
+ url: string,
105
+ file: File | Blob,
106
+ options?: {
107
+ headers?: Record<string, string>;
108
+ onProgress?: (progress: ClientUploadProgress) => void;
109
+ },
110
+ ): Promise<void> {
111
+ // For progress tracking, we need XMLHttpRequest
112
+ if (options?.onProgress) {
113
+ return new Promise((resolve, reject) => {
114
+ const xhr = new XMLHttpRequest();
115
+
116
+ xhr.upload.addEventListener('progress', event => {
117
+ if (event.lengthComputable && options.onProgress) {
118
+ options.onProgress({
119
+ loaded: event.loaded,
120
+ total: event.total,
121
+ percentage: (event.loaded / event.total) * 100,
122
+ });
123
+ }
124
+ });
125
+
126
+ xhr.addEventListener('load', () => {
127
+ if (xhr.status >= 200 && xhr.status < 300) {
128
+ resolve();
129
+ } else {
130
+ reject(new Error(`Upload failed with status ${xhr.status}`));
131
+ }
132
+ });
133
+
134
+ xhr.addEventListener('error', () => {
135
+ reject(new Error('Upload failed'));
136
+ });
137
+
138
+ xhr.open('PUT', url);
139
+
140
+ // Set headers
141
+ if (options?.headers) {
142
+ Object.entries(options.headers).forEach(([key, value]) => {
143
+ xhr.setRequestHeader(key, value);
144
+ });
145
+ }
146
+
147
+ xhr.send(file);
148
+ });
149
+ }
150
+
151
+ // Simple fetch for no progress tracking
152
+ const response = await fetch(url, {
153
+ method: 'PUT',
154
+ body: file,
155
+ headers: options?.headers,
156
+ });
157
+
158
+ if (!response.ok) {
159
+ throw new Error(`Upload failed with status ${response.status}`);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Client-side multipart upload coordinator
165
+ * Works with server-side APIs to manage multipart uploads
166
+ */
167
+ export class ClientMultipartUpload {
168
+ private uploadId: string;
169
+ private key: string;
170
+ private parts: Array<{ PartNumber: number; ETag: string }> = [];
171
+
172
+ constructor(uploadId: string, key: string) {
173
+ this.uploadId = uploadId;
174
+ this.key = key;
175
+ }
176
+
177
+ async uploadPart(
178
+ partNumber: number,
179
+ presignedUrl: string,
180
+ data: Blob,
181
+ _onProgress?: (progress: ClientUploadProgress) => void,
182
+ ): Promise<void> {
183
+ // Note: onProgress tracking for presigned URL uploads requires XMLHttpRequest
184
+ // This is a simplified implementation using fetch
185
+ const response = await fetch(presignedUrl, {
186
+ method: 'PUT',
187
+ body: data,
188
+ });
189
+
190
+ if (!response.ok) {
191
+ throw new Error(`Part upload failed with status ${response.status}`);
192
+ }
193
+
194
+ const etag = response.headers.get('ETag') ?? '';
195
+ this.parts.push({ PartNumber: partNumber, ETag: etag });
196
+ }
197
+
198
+ getParts() {
199
+ return this.parts.sort((a, b) => a.PartNumber - b.PartNumber);
200
+ }
201
+
202
+ getUploadId() {
203
+ return this.uploadId;
204
+ }
205
+
206
+ getKey() {
207
+ return this.key;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Split a file into chunks for multipart upload
213
+ */
214
+ export async function* splitFileIntoChunks(
215
+ file: File | Blob,
216
+ chunkSize: number = 5 * 1024 * 1024, // 5MB default
217
+ ): AsyncGenerator<{ chunk: Blob; partNumber: number; start: number; end: number }> {
218
+ const totalSize = file.size;
219
+ let partNumber = 1;
220
+
221
+ for (let start = 0; start < totalSize; start += chunkSize) {
222
+ const end = Math.min(start + chunkSize, totalSize);
223
+ const chunk = file.slice(start, end);
224
+
225
+ yield {
226
+ chunk,
227
+ partNumber,
228
+ start,
229
+ end,
230
+ };
231
+
232
+ partNumber++;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Download a file from a URL with progress tracking
238
+ */
239
+ export async function downloadFromUrl(
240
+ url: string,
241
+ options?: {
242
+ onProgress?: (progress: ClientUploadProgress) => void;
243
+ },
244
+ ): Promise<Blob> {
245
+ const response = await fetch(url);
246
+
247
+ if (!response.ok) {
248
+ throw new Error(`Download failed with status ${response.status}`);
249
+ }
250
+
251
+ // If no progress tracking needed, just return blob
252
+ if (!options?.onProgress || !response.body) {
253
+ return response.blob();
254
+ }
255
+
256
+ // Stream with progress tracking
257
+ const contentLength = response.headers.get('content-length');
258
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
259
+
260
+ const reader = response.body.getReader();
261
+ const chunks: Uint8Array[] = [];
262
+ let loaded = 0;
263
+
264
+ while (true) {
265
+ const { done, value } = await reader.read();
266
+
267
+ if (done) break;
268
+
269
+ chunks.push(value);
270
+ loaded += value.length;
271
+
272
+ if (total > 0) {
273
+ options.onProgress({
274
+ loaded,
275
+ total,
276
+ percentage: (loaded / total) * 100,
277
+ });
278
+ }
279
+ }
280
+
281
+ const blob = new Blob(
282
+ chunks.map(
283
+ chunk =>
284
+ chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength) as ArrayBuffer,
285
+ ),
286
+ {
287
+ type: response.headers.get('content-type') ?? 'application/octet-stream',
288
+ },
289
+ );
290
+
291
+ return blob;
292
+ }
package/src/client.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview Client-side storage exports (non-Next.js)
3
+ *
4
+ * This file provides client-side storage functionality for non-Next.js environments.
5
+ * For Next.js applications, use '@od-oneapp/storage/client/next' instead.
6
+ *
7
+ * CLEAN BREAK: Removed legacy wrappers, direct Vercel SDK re-exports only.
8
+ *
9
+ * Features:
10
+ * - File upload utilities
11
+ * - Client-side validation
12
+ * - Key generation utilities
13
+ *
14
+ * @module @od-oneapp/storage/client
15
+ */
16
+
17
+ // Re-export core Vercel Blob client utilities
18
+ // Import for internal use
19
+ import { upload as vercelUpload } from '@vercel/blob/client';
20
+
21
+ export {
22
+ generateClientTokenFromReadWriteToken,
23
+ handleUpload,
24
+ upload,
25
+ } from '@vercel/blob/client';
26
+
27
+ // Export client-side validation utilities
28
+ export {
29
+ formatFileSize,
30
+ parseFileSize,
31
+ validateFileSize,
32
+ validateMimeType,
33
+ validateStorageKey,
34
+ type ValidationOptions,
35
+ } from './validation';
36
+
37
+ // Export key generation utilities (validateStorageKey is exported from './validation')
38
+ export {
39
+ generateMultipleKeys,
40
+ generateStorageKey,
41
+ isKeyPattern,
42
+ normalizeStorageKey,
43
+ parseStorageKey,
44
+ sanitizeStorageKey,
45
+ type KeyGenerationOptions,
46
+ type KeyPattern,
47
+ } from '../keys';
48
+
49
+ // Export client-side utilities for presigned URLs (R2, S3, etc.)
50
+ export * from './client-utils';
51
+
52
+ // Type-safe wrapper for Vercel's upload function
53
+ export async function uploadFile(
54
+ pathname: string,
55
+ file: File | Blob,
56
+ options?: {
57
+ access?: 'public' | 'private';
58
+ addRandomSuffix?: boolean;
59
+ allowOverwrite?: boolean;
60
+ cacheControlMaxAge?: number;
61
+ clientPayload?: string;
62
+ contentType?: string;
63
+ multipart?: boolean;
64
+ onUploadProgress?: (event: { loaded: number; total?: number; percentage?: number }) => void;
65
+ handleUploadUrl?: string;
66
+ },
67
+ ): Promise<{ url: string; pathname: string }> {
68
+ const { handleUploadUrl, ...vercelOptions } = options ?? {};
69
+
70
+ if (handleUploadUrl) {
71
+ // Use handleUpload pattern for server-side processing
72
+ return await vercelUpload(pathname, file, {
73
+ ...vercelOptions,
74
+ access: 'public' as const,
75
+ handleUploadUrl,
76
+ });
77
+ } else {
78
+ // Use direct upload with client token
79
+ return await vercelUpload(pathname, file, {
80
+ ...vercelOptions,
81
+ access: 'public' as const,
82
+ handleUploadUrl: '/api/upload', // Default upload URL
83
+ });
84
+ }
85
+ }
86
+
87
+ // Export types that are safe for client-side use
88
+ export type {
89
+ BlobListResponse,
90
+ ClientUploadOptions,
91
+ CloudflareImagesBatchToken,
92
+ CloudflareImagesListOptions,
93
+ CloudflareImagesTransformOptions,
94
+ CloudflareImagesVariant,
95
+ DirectUploadResponse,
96
+ ListOptions,
97
+ PresignedUploadUrl,
98
+ StorageObject,
99
+ UploadOptions,
100
+ UploadProgress,
101
+ VercelBlobOptions,
102
+ } from '../types';
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @fileoverview Storage Package Constants
3
+ *
4
+ * Centralized constants for storage operations to avoid magic numbers
5
+ * and improve maintainability.
6
+ *
7
+ * Includes:
8
+ * - URL expiration times
9
+ * - File size limits
10
+ * - Multipart upload thresholds
11
+ * - Retry configuration
12
+ * - Rate limiting settings
13
+ *
14
+ * @module @repo/storage/constants
15
+ */
16
+
17
+ export const STORAGE_CONSTANTS = {
18
+ // URL Expiration Times (seconds)
19
+ DEFAULT_URL_EXPIRY_SECONDS: 3600, // 1 hour
20
+ PRODUCT_URL_EXPIRY_SECONDS: 3600, // 1 hour for product photos
21
+ UPLOAD_URL_EXPIRY_SECONDS: 1800, // 30 minutes for uploads
22
+ ADMIN_URL_EXPIRY_SECONDS: 7200, // 2 hours for admin operations
23
+
24
+ // File Size Limits (bytes)
25
+ MULTIPART_THRESHOLD_BYTES: 100 * 1024 * 1024, // 100MB - use multipart above this
26
+ DEFAULT_PART_SIZE_BYTES: 5 * 1024 * 1024, // 5MB default part size
27
+ DEFAULT_MAX_PART_SIZE_BYTES: 25 * 1024 * 1024, // 25MB max part size
28
+ DEFAULT_QUEUE_SIZE: 4, // Concurrent upload parts
29
+
30
+ // Batch Operations
31
+ DEFAULT_BATCH_SIZE: 5, // Process 5 items concurrently
32
+ MAX_BATCH_SIZE: 50, // Maximum batch size
33
+
34
+ // Timeouts (milliseconds)
35
+ DEFAULT_REQUEST_TIMEOUT_MS: 30000, // 30 seconds
36
+ DEFAULT_UPLOAD_TIMEOUT_MS: 300000, // 5 minutes for uploads
37
+ DEFAULT_DOWNLOAD_TIMEOUT_MS: 60000, // 1 minute for downloads
38
+
39
+ // Retry Configuration
40
+ DEFAULT_MAX_RETRIES: 3,
41
+ RETRY_BASE_DELAY_MS: 1000, // 1 second base delay
42
+
43
+ // Key Validation
44
+ MAX_KEY_LENGTH: 1024,
45
+ MAX_FILENAME_LENGTH: 255,
46
+
47
+ // Rate Limiting (requests per window)
48
+ DEFAULT_RATE_LIMIT_REQUESTS: 100,
49
+ DEFAULT_RATE_LIMIT_WINDOW_MS: 60 * 1000, // 1 minute
50
+
51
+ // Health Check
52
+ HEALTH_CHECK_KEY: '__health_check__',
53
+ } as const;
54
+
55
+ /**
56
+ * File size thresholds for multipart upload decisions
57
+ */
58
+ export const MULTIPART_THRESHOLDS = {
59
+ SMALL_FILE: 100 * 1024 * 1024, // < 100MB - use simple upload
60
+ MEDIUM_FILE: 1024 * 1024 * 1024, // < 1GB - use 10MB parts
61
+ LARGE_FILE: Infinity, // >= 1GB - use 25MB parts
62
+ } as const;
63
+
64
+ /**
65
+ * Part sizes based on file size
66
+ */
67
+ export const PART_SIZES = {
68
+ SMALL: 5 * 1024 * 1024, // 5MB for files < 100MB
69
+ MEDIUM: 10 * 1024 * 1024, // 10MB for files < 1GB
70
+ LARGE: 25 * 1024 * 1024, // 25MB for files >= 1GB
71
+ } as const;
72
+
73
+ /**
74
+ * Default storage capabilities for providers that don't implement getCapabilities()
75
+ * Single source of truth for capability defaults
76
+ */
77
+ export const DEFAULT_STORAGE_CAPABILITIES = {
78
+ multipart: false,
79
+ presignedUrls: false,
80
+ progressTracking: false,
81
+ abortSignal: false,
82
+ metadata: false,
83
+ customDomains: false,
84
+ edgeCompatible: false,
85
+ versioning: false,
86
+ encryption: false,
87
+ directoryListing: false,
88
+ } as const;