@oxyhq/services 5.10.6 → 5.10.8

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.
@@ -1,281 +0,0 @@
1
- import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
2
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3
-
4
- export interface S3Config {
5
- region: string;
6
- accessKeyId: string;
7
- secretAccessKey: string;
8
- bucketName: string;
9
- }
10
-
11
- export interface UploadOptions {
12
- contentType?: string;
13
- metadata?: Record<string, string>;
14
- publicRead?: boolean;
15
- }
16
-
17
- export interface FileInfo {
18
- key: string;
19
- size: number;
20
- lastModified: Date;
21
- contentType?: string;
22
- metadata?: Record<string, string>;
23
- }
24
-
25
- export class S3FileManager {
26
- private s3Client: S3Client;
27
- private bucketName: string;
28
-
29
- constructor(config: S3Config) {
30
- this.s3Client = new S3Client({
31
- region: config.region,
32
- credentials: {
33
- accessKeyId: config.accessKeyId,
34
- secretAccessKey: config.secretAccessKey,
35
- },
36
- });
37
- this.bucketName = config.bucketName;
38
- }
39
-
40
- /**
41
- * Upload a file to S3
42
- */
43
- async uploadFile(
44
- key: string,
45
- file: File | Buffer | string,
46
- options: UploadOptions = {}
47
- ): Promise<string> {
48
- let body: Buffer | string;
49
- let contentType = options.contentType;
50
-
51
- if (file instanceof File) {
52
- body = await this.fileToBuffer(file);
53
- contentType = contentType || file.type;
54
- } else if (typeof file === 'string') {
55
- body = file;
56
- contentType = contentType || 'text/plain';
57
- } else {
58
- body = file;
59
- contentType = contentType || 'application/octet-stream';
60
- }
61
-
62
- const command = new PutObjectCommand({
63
- Bucket: this.bucketName,
64
- Key: key,
65
- Body: body,
66
- ContentType: contentType,
67
- Metadata: options.metadata,
68
- ACL: options.publicRead ? 'public-read' : 'private',
69
- });
70
-
71
- await this.s3Client.send(command);
72
- return `https://${this.bucketName}.s3.amazonaws.com/${key}`;
73
- }
74
-
75
- /**
76
- * Download a file from S3
77
- */
78
- async downloadFile(key: string): Promise<Buffer> {
79
- const command = new GetObjectCommand({
80
- Bucket: this.bucketName,
81
- Key: key,
82
- });
83
-
84
- const response = await this.s3Client.send(command);
85
-
86
- if (!response.Body) {
87
- throw new Error('File not found or empty');
88
- }
89
-
90
- // Convert stream to buffer
91
- const chunks: Uint8Array[] = [];
92
- const reader = response.Body.transformToWebStream().getReader();
93
-
94
- while (true) {
95
- const { done, value } = await reader.read();
96
- if (done) break;
97
- chunks.push(value);
98
- }
99
-
100
- return Buffer.concat(chunks);
101
- }
102
-
103
- /**
104
- * Delete a file from S3
105
- */
106
- async deleteFile(key: string): Promise<void> {
107
- const command = new DeleteObjectCommand({
108
- Bucket: this.bucketName,
109
- Key: key,
110
- });
111
-
112
- await this.s3Client.send(command);
113
- }
114
-
115
- /**
116
- * Generate a presigned URL for file upload
117
- */
118
- async getPresignedUploadUrl(
119
- key: string,
120
- contentType: string,
121
- expiresIn: number = 3600
122
- ): Promise<string> {
123
- const command = new PutObjectCommand({
124
- Bucket: this.bucketName,
125
- Key: key,
126
- ContentType: contentType,
127
- });
128
-
129
- return getSignedUrl(this.s3Client, command, { expiresIn });
130
- }
131
-
132
- /**
133
- * Generate a presigned URL for file download
134
- */
135
- async getPresignedDownloadUrl(
136
- key: string,
137
- expiresIn: number = 3600
138
- ): Promise<string> {
139
- const command = new GetObjectCommand({
140
- Bucket: this.bucketName,
141
- Key: key,
142
- });
143
-
144
- return getSignedUrl(this.s3Client, command, { expiresIn });
145
- }
146
-
147
- /**
148
- * List files in a directory
149
- */
150
- async listFiles(prefix: string = '', maxKeys: number = 1000): Promise<FileInfo[]> {
151
- const command = new ListObjectsV2Command({
152
- Bucket: this.bucketName,
153
- Prefix: prefix,
154
- MaxKeys: maxKeys,
155
- });
156
-
157
- const response = await this.s3Client.send(command);
158
-
159
- if (!response.Contents) {
160
- return [];
161
- }
162
-
163
- return response.Contents.map((item) => ({
164
- key: item.Key!,
165
- size: item.Size || 0,
166
- lastModified: item.LastModified!,
167
- }));
168
- }
169
-
170
- /**
171
- * Check if a file exists
172
- */
173
- async fileExists(key: string): Promise<boolean> {
174
- try {
175
- const command = new GetObjectCommand({
176
- Bucket: this.bucketName,
177
- Key: key,
178
- });
179
-
180
- await this.s3Client.send(command);
181
- return true;
182
- } catch (error: any) {
183
- if (error.name === 'NoSuchKey') {
184
- return false;
185
- }
186
- throw error;
187
- }
188
- }
189
-
190
- /**
191
- * Get file metadata
192
- */
193
- async getFileMetadata(key: string): Promise<FileInfo | null> {
194
- try {
195
- const command = new GetObjectCommand({
196
- Bucket: this.bucketName,
197
- Key: key,
198
- });
199
-
200
- const response = await this.s3Client.send(command);
201
-
202
- return {
203
- key,
204
- size: parseInt(response.ContentLength?.toString() || '0'),
205
- lastModified: response.LastModified!,
206
- contentType: response.ContentType,
207
- metadata: response.Metadata,
208
- };
209
- } catch (error: any) {
210
- if (error.name === 'NoSuchKey') {
211
- return null;
212
- }
213
- throw error;
214
- }
215
- }
216
-
217
- /**
218
- * Convert File object to Buffer (for React Native compatibility)
219
- */
220
- private async fileToBuffer(file: File): Promise<Buffer> {
221
- return new Promise((resolve, reject) => {
222
- const reader = new FileReader();
223
- reader.onload = () => {
224
- if (reader.result instanceof ArrayBuffer) {
225
- resolve(Buffer.from(reader.result));
226
- } else {
227
- reject(new Error('Failed to read file'));
228
- }
229
- };
230
- reader.onerror = () => reject(reader.error);
231
- reader.readAsArrayBuffer(file);
232
- });
233
- }
234
-
235
- /**
236
- * Upload multiple files
237
- */
238
- async uploadMultipleFiles(
239
- files: Array<{ key: string; file: File | Buffer | string; options?: UploadOptions }>
240
- ): Promise<string[]> {
241
- const uploadPromises = files.map(({ key, file, options }) =>
242
- this.uploadFile(key, file, options)
243
- );
244
-
245
- return Promise.all(uploadPromises);
246
- }
247
-
248
- /**
249
- * Delete multiple files
250
- */
251
- async deleteMultipleFiles(keys: string[]): Promise<void> {
252
- const deletePromises = keys.map(key => this.deleteFile(key));
253
- await Promise.all(deletePromises);
254
- }
255
-
256
- /**
257
- * Copy file from one key to another
258
- */
259
- async copyFile(sourceKey: string, destinationKey: string): Promise<void> {
260
- const command = new PutObjectCommand({
261
- Bucket: this.bucketName,
262
- Key: destinationKey,
263
- Body: await this.downloadFile(sourceKey),
264
- });
265
-
266
- await this.s3Client.send(command);
267
- }
268
-
269
- /**
270
- * Move file (copy + delete)
271
- */
272
- async moveFile(sourceKey: string, destinationKey: string): Promise<void> {
273
- await this.copyFile(sourceKey, destinationKey);
274
- await this.deleteFile(sourceKey);
275
- }
276
- }
277
-
278
- // Export a factory function for easier configuration
279
- export function createS3FileManager(config: S3Config): S3FileManager {
280
- return new S3FileManager(config);
281
- }
@@ -1,432 +0,0 @@
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
- };