@oxyhq/services 5.10.4 → 5.10.6

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 (142) hide show
  1. package/README.md +62 -14
  2. package/lib/commonjs/core/OxyServices.js +797 -5
  3. package/lib/commonjs/core/OxyServices.js.map +1 -1
  4. package/lib/commonjs/core/index.js +8 -83
  5. package/lib/commonjs/core/index.js.map +1 -1
  6. package/lib/commonjs/index.js +31 -1
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/ui/screens/FileManagementScreen.js +12 -12
  9. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  10. package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
  11. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  12. package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
  13. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  14. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  15. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  16. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
  17. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  18. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
  19. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  20. package/lib/commonjs/ui/stores/followStore.js +4 -4
  21. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  22. package/lib/commonjs/utils/s3FileManager.js +243 -0
  23. package/lib/commonjs/utils/s3FileManager.js.map +1 -0
  24. package/lib/commonjs/utils/s3FileManagerExample.js +407 -0
  25. package/lib/commonjs/utils/s3FileManagerExample.js.map +1 -0
  26. package/lib/commonjs/utils/s3FileManagerRN.js +274 -0
  27. package/lib/commonjs/utils/s3FileManagerRN.js.map +1 -0
  28. package/lib/module/core/OxyServices.js +796 -4
  29. package/lib/module/core/OxyServices.js.map +1 -1
  30. package/lib/module/core/index.js +9 -24
  31. package/lib/module/core/index.js.map +1 -1
  32. package/lib/module/index.js +4 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/ui/screens/FileManagementScreen.js +12 -12
  35. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  36. package/lib/module/ui/screens/ProfileScreen.js +2 -2
  37. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  38. package/lib/module/ui/screens/SignInScreen.js +1 -1
  39. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  40. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  41. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  42. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
  43. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  44. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  45. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  46. package/lib/module/ui/stores/followStore.js +4 -4
  47. package/lib/module/ui/stores/followStore.js.map +1 -1
  48. package/lib/module/utils/s3FileManager.js +237 -0
  49. package/lib/module/utils/s3FileManager.js.map +1 -0
  50. package/lib/module/utils/s3FileManagerExample.js +400 -0
  51. package/lib/module/utils/s3FileManagerExample.js.map +1 -0
  52. package/lib/module/utils/s3FileManagerRN.js +268 -0
  53. package/lib/module/utils/s3FileManagerRN.js.map +1 -0
  54. package/lib/typescript/core/OxyServices.d.ts +292 -3
  55. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  56. package/lib/typescript/core/index.d.ts +7 -16
  57. package/lib/typescript/core/index.d.ts.map +1 -1
  58. package/lib/typescript/index.d.ts +4 -0
  59. package/lib/typescript/index.d.ts.map +1 -1
  60. package/lib/typescript/utils/s3FileManager.d.ts +81 -0
  61. package/lib/typescript/utils/s3FileManager.d.ts.map +1 -0
  62. package/lib/typescript/utils/s3FileManagerExample.d.ts +87 -0
  63. package/lib/typescript/utils/s3FileManagerExample.d.ts.map +1 -0
  64. package/lib/typescript/utils/s3FileManagerRN.d.ts +104 -0
  65. package/lib/typescript/utils/s3FileManagerRN.d.ts.map +1 -0
  66. package/package.json +3 -1
  67. package/src/core/OxyServices.ts +881 -46
  68. package/src/core/index.ts +9 -24
  69. package/src/index.ts +17 -1
  70. package/src/ui/screens/FileManagementScreen.tsx +12 -12
  71. package/src/ui/screens/ProfileScreen.tsx +3 -3
  72. package/src/ui/screens/SignInScreen.tsx +1 -1
  73. package/src/ui/screens/karma/KarmaCenterScreen.tsx +2 -2
  74. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +1 -1
  75. package/src/ui/screens/karma/KarmaRulesScreen.tsx +1 -1
  76. package/src/ui/stores/followStore.ts +4 -4
  77. package/src/utils/s3FileManager.ts +281 -0
  78. package/src/utils/s3FileManagerExample.ts +432 -0
  79. package/src/utils/s3FileManagerRN.ts +322 -0
  80. package/lib/commonjs/core/OxyServicesMain.js +0 -51
  81. package/lib/commonjs/core/OxyServicesMain.js.map +0 -1
  82. package/lib/commonjs/core/analytics/AnalyticsService.js +0 -67
  83. package/lib/commonjs/core/analytics/AnalyticsService.js.map +0 -1
  84. package/lib/commonjs/core/auth/AuthService.js +0 -538
  85. package/lib/commonjs/core/auth/AuthService.js.map +0 -1
  86. package/lib/commonjs/core/devices/DeviceService.js +0 -61
  87. package/lib/commonjs/core/devices/DeviceService.js.map +0 -1
  88. package/lib/commonjs/core/files/FileService.js +0 -180
  89. package/lib/commonjs/core/files/FileService.js.map +0 -1
  90. package/lib/commonjs/core/karma/KarmaService.js +0 -100
  91. package/lib/commonjs/core/karma/KarmaService.js.map +0 -1
  92. package/lib/commonjs/core/locations/LocationService.js +0 -131
  93. package/lib/commonjs/core/locations/LocationService.js.map +0 -1
  94. package/lib/commonjs/core/payments/PaymentService.js +0 -124
  95. package/lib/commonjs/core/payments/PaymentService.js.map +0 -1
  96. package/lib/commonjs/core/users/UserService.js +0 -234
  97. package/lib/commonjs/core/users/UserService.js.map +0 -1
  98. package/lib/module/core/OxyServicesMain.js +0 -47
  99. package/lib/module/core/OxyServicesMain.js.map +0 -1
  100. package/lib/module/core/analytics/AnalyticsService.js +0 -62
  101. package/lib/module/core/analytics/AnalyticsService.js.map +0 -1
  102. package/lib/module/core/auth/AuthService.js +0 -533
  103. package/lib/module/core/auth/AuthService.js.map +0 -1
  104. package/lib/module/core/devices/DeviceService.js +0 -57
  105. package/lib/module/core/devices/DeviceService.js.map +0 -1
  106. package/lib/module/core/files/FileService.js +0 -175
  107. package/lib/module/core/files/FileService.js.map +0 -1
  108. package/lib/module/core/karma/KarmaService.js +0 -95
  109. package/lib/module/core/karma/KarmaService.js.map +0 -1
  110. package/lib/module/core/locations/LocationService.js +0 -127
  111. package/lib/module/core/locations/LocationService.js.map +0 -1
  112. package/lib/module/core/payments/PaymentService.js +0 -119
  113. package/lib/module/core/payments/PaymentService.js.map +0 -1
  114. package/lib/module/core/users/UserService.js +0 -230
  115. package/lib/module/core/users/UserService.js.map +0 -1
  116. package/lib/typescript/core/OxyServicesMain.d.ts +0 -33
  117. package/lib/typescript/core/OxyServicesMain.d.ts.map +0 -1
  118. package/lib/typescript/core/analytics/AnalyticsService.d.ts +0 -26
  119. package/lib/typescript/core/analytics/AnalyticsService.d.ts.map +0 -1
  120. package/lib/typescript/core/auth/AuthService.d.ts +0 -165
  121. package/lib/typescript/core/auth/AuthService.d.ts.map +0 -1
  122. package/lib/typescript/core/devices/DeviceService.d.ts +0 -20
  123. package/lib/typescript/core/devices/DeviceService.d.ts.map +0 -1
  124. package/lib/typescript/core/files/FileService.d.ts +0 -59
  125. package/lib/typescript/core/files/FileService.d.ts.map +0 -1
  126. package/lib/typescript/core/karma/KarmaService.d.ts +0 -50
  127. package/lib/typescript/core/karma/KarmaService.d.ts.map +0 -1
  128. package/lib/typescript/core/locations/LocationService.d.ts +0 -39
  129. package/lib/typescript/core/locations/LocationService.d.ts.map +0 -1
  130. package/lib/typescript/core/payments/PaymentService.d.ts +0 -50
  131. package/lib/typescript/core/payments/PaymentService.d.ts.map +0 -1
  132. package/lib/typescript/core/users/UserService.d.ts +0 -111
  133. package/lib/typescript/core/users/UserService.d.ts.map +0 -1
  134. package/src/core/OxyServicesMain.ts +0 -57
  135. package/src/core/analytics/AnalyticsService.ts +0 -64
  136. package/src/core/auth/AuthService.ts +0 -560
  137. package/src/core/devices/DeviceService.ts +0 -55
  138. package/src/core/files/FileService.ts +0 -198
  139. package/src/core/karma/KarmaService.ts +0 -104
  140. package/src/core/locations/LocationService.ts +0 -141
  141. package/src/core/payments/PaymentService.ts +0 -133
  142. package/src/core/users/UserService.ts +0 -241
@@ -0,0 +1,432 @@
1
+ /**
2
+ * S3 File Management Examples
3
+ *
4
+ * This file demonstrates how to use the S3 file management functionality
5
+ * in both web and React Native environments.
6
+ */
7
+
8
+ import {
9
+ S3FileManager,
10
+ createS3FileManager,
11
+ S3Config,
12
+ UploadOptions
13
+ } from './s3FileManager';
14
+ import {
15
+ S3FileManagerRN,
16
+ createS3FileManagerRN,
17
+ RNFile
18
+ } from './s3FileManagerRN';
19
+
20
+ // Example configuration
21
+ const s3Config: S3Config = {
22
+ region: 'us-east-1',
23
+ accessKeyId: 'your-access-key-id',
24
+ secretAccessKey: 'your-secret-access-key',
25
+ bucketName: 'your-bucket-name',
26
+ };
27
+
28
+ // ============================================================================
29
+ // WEB ENVIRONMENT EXAMPLES
30
+ // ============================================================================
31
+
32
+ export class WebFileManagerExample {
33
+ private s3Manager: S3FileManager;
34
+
35
+ constructor() {
36
+ this.s3Manager = createS3FileManager(s3Config);
37
+ }
38
+
39
+ /**
40
+ * Example: Upload a file from web form
41
+ */
42
+ async uploadFileFromForm(fileInput: HTMLInputElement): Promise<string> {
43
+ const file = fileInput.files?.[0];
44
+ if (!file) {
45
+ throw new Error('No file selected');
46
+ }
47
+
48
+ const key = `uploads/${Date.now()}-${file.name}`;
49
+ const uploadOptions: UploadOptions = {
50
+ contentType: file.type,
51
+ metadata: {
52
+ originalName: file.name,
53
+ uploadedBy: 'web-user',
54
+ uploadedAt: new Date().toISOString(),
55
+ },
56
+ publicRead: false,
57
+ };
58
+
59
+ try {
60
+ const url = await this.s3Manager.uploadFile(key, file, uploadOptions);
61
+ console.log('File uploaded successfully:', url);
62
+ return url;
63
+ } catch (error) {
64
+ console.error('Upload failed:', error);
65
+ throw error;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Example: Upload multiple files
71
+ */
72
+ async uploadMultipleFiles(fileList: FileList): Promise<string[]> {
73
+ const files = Array.from(fileList).map(file => ({
74
+ key: `uploads/${Date.now()}-${file.name}`,
75
+ file,
76
+ options: {
77
+ contentType: file.type,
78
+ metadata: {
79
+ originalName: file.name,
80
+ uploadedBy: 'web-user',
81
+ uploadedAt: new Date().toISOString(),
82
+ },
83
+ publicRead: false,
84
+ } as UploadOptions,
85
+ }));
86
+
87
+ try {
88
+ const urls = await this.s3Manager.uploadMultipleFiles(files);
89
+ console.log('Files uploaded successfully:', urls);
90
+ return urls;
91
+ } catch (error) {
92
+ console.error('Upload failed:', error);
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Example: Download a file
99
+ */
100
+ async downloadFile(key: string): Promise<void> {
101
+ try {
102
+ const buffer = await this.s3Manager.downloadFile(key);
103
+
104
+ // Create a blob and download link
105
+ const blob = new Blob([buffer]);
106
+ const url = URL.createObjectURL(blob);
107
+ const a = document.createElement('a');
108
+ a.href = url;
109
+ a.download = key.split('/').pop() || 'download';
110
+ document.body.appendChild(a);
111
+ a.click();
112
+ document.body.removeChild(a);
113
+ URL.revokeObjectURL(url);
114
+
115
+ console.log('File downloaded successfully');
116
+ } catch (error) {
117
+ console.error('Download failed:', error);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Example: Get presigned URLs
124
+ */
125
+ async getPresignedUrls(key: string): Promise<{ upload: string; download: string }> {
126
+ try {
127
+ const uploadUrl = await this.s3Manager.getPresignedUploadUrl(key, 'application/octet-stream');
128
+ const downloadUrl = await this.s3Manager.getPresignedDownloadUrl(key);
129
+
130
+ return { upload: uploadUrl, download: downloadUrl };
131
+ } catch (error) {
132
+ console.error('Failed to get presigned URLs:', error);
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Example: List files with pagination
139
+ */
140
+ async listFiles(prefix: string = '', maxKeys: number = 50): Promise<void> {
141
+ try {
142
+ const files = await this.s3Manager.listFiles(prefix, maxKeys);
143
+ console.log('Files found:', files);
144
+
145
+ files.forEach(file => {
146
+ console.log(`- ${file.key} (${file.size} bytes, modified: ${file.lastModified})`);
147
+ });
148
+ } catch (error) {
149
+ console.error('Failed to list files:', error);
150
+ throw error;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Example: File operations (copy, move, delete)
156
+ */
157
+ async performFileOperations(): Promise<void> {
158
+ const sourceKey = 'uploads/source-file.txt';
159
+ const destKey = 'uploads/destination-file.txt';
160
+
161
+ try {
162
+ // Copy file
163
+ await this.s3Manager.copyFile(sourceKey, destKey);
164
+ console.log('File copied successfully');
165
+
166
+ // Move file
167
+ await this.s3Manager.moveFile(destKey, 'uploads/moved-file.txt');
168
+ console.log('File moved successfully');
169
+
170
+ // Delete file
171
+ await this.s3Manager.deleteFile('uploads/moved-file.txt');
172
+ console.log('File deleted successfully');
173
+ } catch (error) {
174
+ console.error('File operation failed:', error);
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ // ============================================================================
181
+ // REACT NATIVE ENVIRONMENT EXAMPLES
182
+ // ============================================================================
183
+
184
+ export class ReactNativeFileManagerExample {
185
+ private s3Manager: S3FileManagerRN;
186
+
187
+ constructor() {
188
+ this.s3Manager = createS3FileManagerRN(s3Config);
189
+ }
190
+
191
+ /**
192
+ * Example: Upload a file from React Native
193
+ */
194
+ async uploadFileFromRN(fileInfo: RNFile): Promise<string> {
195
+ const key = `uploads/${Date.now()}-${fileInfo.name}`;
196
+ const uploadOptions: UploadOptions = {
197
+ contentType: fileInfo.type || 'application/octet-stream',
198
+ metadata: {
199
+ originalName: fileInfo.name,
200
+ uploadedBy: 'rn-user',
201
+ uploadedAt: new Date().toISOString(),
202
+ fileSize: fileInfo.size?.toString() || '0',
203
+ },
204
+ publicRead: false,
205
+ };
206
+
207
+ try {
208
+ const url = await this.s3Manager.uploadFile(key, fileInfo, uploadOptions);
209
+ console.log('File uploaded successfully:', url);
210
+ return url;
211
+ } catch (error) {
212
+ console.error('Upload failed:', error);
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Example: Upload image with size validation
219
+ */
220
+ async uploadImageWithValidation(fileInfo: RNFile, maxSize: number = 5 * 1024 * 1024): Promise<string> {
221
+ // Get file size
222
+ const fileSize = await this.s3Manager.getFileSize(fileInfo);
223
+
224
+ if (fileSize > maxSize) {
225
+ throw new Error(`File size (${fileSize} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
226
+ }
227
+
228
+ const key = `images/${Date.now()}-${fileInfo.name}`;
229
+ const uploadOptions: UploadOptions = {
230
+ contentType: fileInfo.type || 'image/jpeg',
231
+ metadata: {
232
+ originalName: fileInfo.name,
233
+ uploadedBy: 'rn-user',
234
+ uploadedAt: new Date().toISOString(),
235
+ fileSize: fileSize.toString(),
236
+ category: 'image',
237
+ },
238
+ publicRead: true, // Images are often public
239
+ };
240
+
241
+ try {
242
+ const url = await this.s3Manager.uploadImage(key, fileInfo, uploadOptions);
243
+ console.log('Image uploaded successfully:', url);
244
+ return url;
245
+ } catch (error) {
246
+ console.error('Image upload failed:', error);
247
+ throw error;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Example: Download file to React Native
253
+ */
254
+ async downloadFileToRN(key: string): Promise<Buffer> {
255
+ try {
256
+ const buffer = await this.s3Manager.downloadFile(key);
257
+ console.log('File downloaded successfully, size:', buffer.length);
258
+ return buffer;
259
+ } catch (error) {
260
+ console.error('Download failed:', error);
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Example: Batch operations
267
+ */
268
+ async performBatchOperations(files: RNFile[]): Promise<void> {
269
+ try {
270
+ // Upload multiple files
271
+ const uploadPromises = files.map((file, index) => {
272
+ const key = `batch-uploads/${Date.now()}-${index}-${file.name}`;
273
+ return this.s3Manager.uploadFile(key, file, {
274
+ metadata: { batchId: Date.now().toString() },
275
+ });
276
+ });
277
+
278
+ const uploadedUrls = await Promise.all(uploadPromises);
279
+ console.log('Batch upload completed:', uploadedUrls);
280
+
281
+ // Delete multiple files (example with keys)
282
+ const keysToDelete = uploadedUrls.map((url: string) => {
283
+ const key = url.split('/').pop();
284
+ return `batch-uploads/${key}`;
285
+ });
286
+
287
+ await this.s3Manager.deleteMultipleFiles(keysToDelete);
288
+ console.log('Batch delete completed');
289
+ } catch (error) {
290
+ console.error('Batch operations failed:', error);
291
+ throw error;
292
+ }
293
+ }
294
+ }
295
+
296
+ // ============================================================================
297
+ // USAGE EXAMPLES
298
+ // ============================================================================
299
+
300
+ /**
301
+ * Example usage in a React component
302
+ */
303
+ export const useS3FileManager = () => {
304
+ const webManager = new WebFileManagerExample();
305
+ const rnManager = new ReactNativeFileManagerExample();
306
+
307
+ const handleFileUpload = async (file: File | RNFile) => {
308
+ try {
309
+ if ('uri' in file) {
310
+ // React Native file
311
+ return await rnManager.uploadFileFromRN(file);
312
+ } else {
313
+ // Web file
314
+ const mockInput = { files: [file] } as unknown as HTMLInputElement;
315
+ return await webManager.uploadFileFromForm(mockInput);
316
+ }
317
+ } catch (error) {
318
+ console.error('Upload failed:', error);
319
+ throw error;
320
+ }
321
+ };
322
+
323
+ const handleFileDownload = async (key: string) => {
324
+ try {
325
+ if (typeof window !== 'undefined') {
326
+ // Web environment
327
+ await webManager.downloadFile(key);
328
+ } else {
329
+ // React Native environment
330
+ const buffer = await rnManager.downloadFileToRN(key);
331
+ // Handle buffer in React Native (save to file system, etc.)
332
+ return buffer;
333
+ }
334
+ } catch (error) {
335
+ console.error('Download failed:', error);
336
+ throw error;
337
+ }
338
+ };
339
+
340
+ return {
341
+ uploadFile: handleFileUpload,
342
+ downloadFile: handleFileDownload,
343
+ listFiles: webManager.listFiles.bind(webManager),
344
+ getPresignedUrls: webManager.getPresignedUrls.bind(webManager),
345
+ };
346
+ };
347
+
348
+ /**
349
+ * Example: Error handling and retry logic
350
+ */
351
+ export const uploadWithRetry = async (
352
+ s3Manager: S3FileManager | S3FileManagerRN,
353
+ key: string,
354
+ file: File | RNFile | Buffer,
355
+ options: UploadOptions = {},
356
+ maxRetries: number = 3
357
+ ): Promise<string> => {
358
+ // Type guard to determine which manager we're using
359
+ const isRNManager = (manager: S3FileManager | S3FileManagerRN): manager is S3FileManagerRN => {
360
+ return 'getFileSize' in manager;
361
+ };
362
+ let lastError: Error | undefined;
363
+
364
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
365
+ try {
366
+ if (isRNManager(s3Manager)) {
367
+ // For React Native manager, ensure file is RNFile
368
+ if ('uri' in file) {
369
+ return await s3Manager.uploadFile(key, file, options);
370
+ } else {
371
+ throw new Error('React Native manager requires RNFile with uri property');
372
+ }
373
+ } else {
374
+ // For web manager, ensure file is not RNFile
375
+ if ('uri' in file) {
376
+ throw new Error('Web manager does not support RNFile');
377
+ } else {
378
+ return await s3Manager.uploadFile(key, file, options);
379
+ }
380
+ }
381
+ } catch (error) {
382
+ lastError = error as Error;
383
+ console.warn(`Upload attempt ${attempt} failed:`, error);
384
+
385
+ if (attempt < maxRetries) {
386
+ // Wait before retrying (exponential backoff)
387
+ const delay = Math.pow(2, attempt) * 1000;
388
+ await new Promise(resolve => setTimeout(resolve, delay));
389
+ }
390
+ }
391
+ }
392
+
393
+ throw new Error(`Upload failed after ${maxRetries} attempts. Last error: ${lastError?.message || 'Unknown error'}`);
394
+ };
395
+
396
+ /**
397
+ * Example: File validation utilities
398
+ */
399
+ export const validateFile = (
400
+ file: File | RNFile,
401
+ options: {
402
+ maxSize?: number;
403
+ allowedTypes?: string[];
404
+ allowedExtensions?: string[];
405
+ } = {}
406
+ ): { isValid: boolean; errors: string[] } => {
407
+ const errors: string[] = [];
408
+ const { maxSize = 10 * 1024 * 1024, allowedTypes = [], allowedExtensions = [] } = options;
409
+
410
+ // Check file size
411
+ if ('size' in file && file.size && file.size > maxSize) {
412
+ errors.push(`File size (${file.size} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
413
+ }
414
+
415
+ // Check file type
416
+ if ('type' in file && file.type && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
417
+ errors.push(`File type (${file.type}) is not allowed`);
418
+ }
419
+
420
+ // Check file extension
421
+ if (allowedExtensions.length > 0) {
422
+ const extension = file.name.split('.').pop()?.toLowerCase();
423
+ if (!extension || !allowedExtensions.includes(`.${extension}`)) {
424
+ errors.push(`File extension (.${extension}) is not allowed`);
425
+ }
426
+ }
427
+
428
+ return {
429
+ isValid: errors.length === 0,
430
+ errors,
431
+ };
432
+ };