@memberjunction/storage 2.43.0 → 2.44.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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/readme.md +315 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/storage",
3
- "version": "2.43.0",
3
+ "version": "2.44.0",
4
4
  "description": "This library provides a set of objects that handle the interface between the server-side API and various cloud storage providers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,9 +25,9 @@
25
25
  "@azure/identity": "^4.0.1",
26
26
  "@azure/storage-blob": "^12.17.0",
27
27
  "@google-cloud/storage": "^7.9.0",
28
- "@memberjunction/core": "2.43.0",
29
- "@memberjunction/core-entities": "2.43.0",
30
- "@memberjunction/global": "2.43.0",
28
+ "@memberjunction/core": "2.44.0",
29
+ "@memberjunction/core-entities": "2.44.0",
30
+ "@memberjunction/global": "2.44.0",
31
31
  "@microsoft/mgt-element": "^3.1.3",
32
32
  "@microsoft/mgt-msal2-provider": "^3.1.3",
33
33
  "@microsoft/mgt-sharepoint-provider": "^3.1.3",
package/readme.md CHANGED
@@ -4,10 +4,18 @@ The `@memberjunction/storage` library provides a unified interface for interacti
4
4
 
5
5
  [![MemberJunction Logo](https://memberjunction.com/images/MJ_Dark_Logo_Transparent_tm.png)](https://memberjunction.com)
6
6
 
7
+ ## Overview
8
+
9
+ This library is a key component of the MemberJunction platform, providing seamless file storage operations across multiple cloud providers. It offers a provider-agnostic approach to file management, allowing applications to switch between storage providers without code changes.
10
+
7
11
  ## Features
8
12
 
9
- - **Unified API**: Consistent methods across all storage providers
13
+ - **Unified API**: Consistent methods across all storage providers via the `FileStorageBase` abstract class
14
+ - **Type-Safe**: Full TypeScript support with comprehensive type definitions
10
15
  - **Flexible Provider Selection**: Use any number of storage providers simultaneously based on your application needs
16
+ - **Pre-authenticated URLs**: Secure upload and download operations using time-limited URLs
17
+ - **Metadata Support**: Store and retrieve custom metadata with your files
18
+ - **Error Handling**: Provider-specific errors are normalized with clear error messages
11
19
  - **Commonly Supported Storage Providers**:
12
20
  - [AWS S3](https://aws.amazon.com/s3/)
13
21
  - [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs)
@@ -21,11 +29,12 @@ The `@memberjunction/storage` library provides a unified interface for interacti
21
29
  - Download files (via pre-authenticated URLs)
22
30
  - Copy and move files
23
31
  - Delete files and directories
24
- - List files and directories
32
+ - List files and directories with metadata
25
33
  - Create and manage directories
26
- - Get file metadata
34
+ - Get detailed file metadata
27
35
  - Check file/directory existence
28
- - **Extensible**: Easy to add new storage providers
36
+ - Direct upload/download via Buffer
37
+ - **Extensible**: Easy to add new storage providers by extending `FileStorageBase`
29
38
 
30
39
  ## Installation
31
40
 
@@ -33,6 +42,14 @@ The `@memberjunction/storage` library provides a unified interface for interacti
33
42
  npm install @memberjunction/storage
34
43
  ```
35
44
 
45
+ ## Dependencies
46
+
47
+ This package depends on:
48
+ - `@memberjunction/core` - Core MemberJunction functionality
49
+ - `@memberjunction/core-entities` - Entity definitions including `FileStorageProviderEntity`
50
+ - `@memberjunction/global` - Global utilities and class registration
51
+ - Provider-specific SDKs (installed as dependencies)
52
+
36
53
  ## Usage
37
54
 
38
55
  ### Basic Setup
@@ -48,83 +65,225 @@ STORAGE_AZURE_ACCOUNT_NAME=your-account-name
48
65
  STORAGE_AZURE_ACCOUNT_KEY=your-account-key
49
66
  ```
50
67
 
51
- ### Code Example
68
+ ### Using Utility Functions (Recommended)
69
+
70
+ The library provides high-level utility functions that work with MemberJunction's entity system:
52
71
 
53
72
  ```typescript
54
- import { AzureFileStorage, createUploadUrl, createDownloadUrl, deleteObject } from '@memberjunction/storage';
73
+ import { createUploadUrl, createDownloadUrl, deleteObject, moveObject } from '@memberjunction/storage';
55
74
  import { FileStorageProviderEntity } from '@memberjunction/core-entities';
75
+ import { Metadata } from '@memberjunction/core';
56
76
 
57
- // Assuming you have a FileStorageProviderEntity loaded from your database
58
- async function fileOperationsExample(provider: FileStorageProviderEntity) {
77
+ // Load a FileStorageProviderEntity from the database
78
+ async function fileOperationsExample() {
79
+ const md = new Metadata();
80
+ const provider = await md.GetEntityObject<FileStorageProviderEntity>('File Storage Providers');
81
+ await provider.Load('your-provider-id');
82
+
59
83
  // Create pre-authenticated upload URL
60
84
  const { updatedInput, UploadUrl } = await createUploadUrl(
61
85
  provider,
62
86
  {
63
87
  ID: '123',
64
- Name: 'document.pdf',
88
+ Name: 'documents/report.pdf',
65
89
  ProviderID: provider.ID
66
90
  }
67
91
  );
68
92
 
69
93
  // The client can use the UploadUrl directly to upload the file
70
94
  console.log(`Upload URL: ${UploadUrl}`);
95
+ console.log(`File status: ${updatedInput.Status}`); // 'Uploading'
96
+ console.log(`Content type: ${updatedInput.ContentType}`); // 'application/pdf'
97
+
98
+ // If a ProviderKey was returned, use it for future operations
99
+ const fileIdentifier = updatedInput.ProviderKey || updatedInput.Name;
71
100
 
72
101
  // Later, create pre-authenticated download URL
73
- const downloadUrl = await createDownloadUrl(provider, updatedInput.Name);
102
+ const downloadUrl = await createDownloadUrl(provider, fileIdentifier);
74
103
  console.log(`Download URL: ${downloadUrl}`);
75
104
 
105
+ // Move the file to a new location
106
+ const moved = await moveObject(
107
+ provider,
108
+ fileIdentifier,
109
+ 'documents/archived/report_2024.pdf'
110
+ );
111
+ console.log(`File moved: ${moved}`);
112
+
76
113
  // Delete the file when no longer needed
77
- const deleted = await deleteObject(provider, updatedInput.Name);
114
+ const deleted = await deleteObject(provider, 'documents/archived/report_2024.pdf');
78
115
  console.log(`File deleted: ${deleted}`);
79
116
  }
80
117
  ```
81
118
 
82
119
  ### Direct Provider Usage
83
120
 
84
- You can also work directly with a storage provider:
121
+ You can also work directly with a storage provider by instantiating it using the MemberJunction class factory:
85
122
 
86
123
  ```typescript
87
- import { AzureFileStorage } from '@memberjunction/storage';
124
+ import { FileStorageBase } from '@memberjunction/storage';
125
+ import { MJGlobal } from '@memberjunction/global';
88
126
 
89
127
  async function directProviderExample() {
90
- const storage = new AzureFileStorage();
128
+ // Create storage instance using the class factory (recommended)
129
+ const storage = MJGlobal.Instance.ClassFactory.CreateInstance<FileStorageBase>(
130
+ FileStorageBase,
131
+ 'Azure Blob Storage'
132
+ );
133
+
134
+ // Initialize the storage provider if needed
135
+ await storage.initialize();
91
136
 
92
137
  // List all files in a directory
93
138
  const result = await storage.ListObjects('documents/');
94
139
  console.log('Files:', result.objects);
95
140
  console.log('Directories:', result.prefixes);
96
141
 
142
+ // Display detailed metadata for each file
143
+ for (const file of result.objects) {
144
+ console.log(`\nFile: ${file.name}`);
145
+ console.log(` Path: ${file.path}`);
146
+ console.log(` Full Path: ${file.fullPath}`);
147
+ console.log(` Size: ${file.size} bytes`);
148
+ console.log(` Type: ${file.contentType}`);
149
+ console.log(` Modified: ${file.lastModified}`);
150
+ console.log(` Is Directory: ${file.isDirectory}`);
151
+ }
152
+
97
153
  // Create a directory
98
- await storage.CreateDirectory('documents/reports/');
154
+ const dirCreated = await storage.CreateDirectory('documents/reports/');
155
+ console.log(`Directory created: ${dirCreated}`);
99
156
 
100
- // Upload a file directly
157
+ // Upload a file directly with metadata
101
158
  const content = Buffer.from('Hello, World!');
102
- await storage.PutObject('documents/reports/hello.txt', content, 'text/plain');
159
+ const uploaded = await storage.PutObject(
160
+ 'documents/reports/hello.txt',
161
+ content,
162
+ 'text/plain',
163
+ {
164
+ author: 'John Doe',
165
+ department: 'Engineering',
166
+ version: '1.0'
167
+ }
168
+ );
169
+ console.log(`File uploaded: ${uploaded}`);
170
+
171
+ // Get file metadata without downloading content
172
+ const metadata = await storage.GetObjectMetadata('documents/reports/hello.txt');
173
+ console.log('File metadata:', metadata);
174
+
175
+ // Download file content
176
+ const fileContent = await storage.GetObject('documents/reports/hello.txt');
177
+ console.log('File content:', fileContent.toString('utf8'));
103
178
 
104
179
  // Copy a file
105
- await storage.CopyObject('documents/reports/hello.txt', 'documents/archive/hello-copy.txt');
180
+ const copied = await storage.CopyObject(
181
+ 'documents/reports/hello.txt',
182
+ 'documents/archive/hello-backup.txt'
183
+ );
184
+ console.log(`File copied: ${copied}`);
106
185
 
107
186
  // Check if a file exists
108
187
  const exists = await storage.ObjectExists('documents/reports/hello.txt');
188
+ console.log(`File exists: ${exists}`);
189
+
190
+ // Check if a directory exists
191
+ const dirExists = await storage.DirectoryExists('documents/reports/');
192
+ console.log(`Directory exists: ${dirExists}`);
193
+
194
+ // Delete a directory and all its contents
195
+ const dirDeleted = await storage.DeleteDirectory('documents/reports/', true);
196
+ console.log(`Directory deleted: ${dirDeleted}`);
109
197
  }
110
198
  ```
111
199
 
200
+ ## API Reference
201
+
202
+ ### Core Types
203
+
204
+ #### `CreatePreAuthUploadUrlPayload`
205
+ ```typescript
206
+ type CreatePreAuthUploadUrlPayload = {
207
+ UploadUrl: string; // Pre-authenticated URL for upload
208
+ ProviderKey?: string; // Optional provider-specific key
209
+ };
210
+ ```
211
+
212
+ #### `StorageObjectMetadata`
213
+ ```typescript
214
+ type StorageObjectMetadata = {
215
+ name: string; // Object name (filename)
216
+ path: string; // Directory path
217
+ fullPath: string; // Complete path including name
218
+ size: number; // Size in bytes
219
+ contentType: string; // MIME type
220
+ lastModified: Date; // Last modification date
221
+ isDirectory: boolean; // Whether this is a directory
222
+ etag?: string; // Entity tag for caching
223
+ cacheControl?: string; // Cache control directives
224
+ customMetadata?: Record<string, string>; // Custom metadata
225
+ };
226
+ ```
227
+
228
+ #### `StorageListResult`
229
+ ```typescript
230
+ type StorageListResult = {
231
+ objects: StorageObjectMetadata[]; // Files found
232
+ prefixes: string[]; // Directories found
233
+ };
234
+ ```
235
+
236
+ ### FileStorageBase Methods
237
+
238
+ All storage providers implement these methods:
239
+
240
+ - `CreatePreAuthUploadUrl(objectName: string): Promise<CreatePreAuthUploadUrlPayload>`
241
+ - `CreatePreAuthDownloadUrl(objectName: string): Promise<string>`
242
+ - `MoveObject(oldObjectName: string, newObjectName: string): Promise<boolean>`
243
+ - `DeleteObject(objectName: string): Promise<boolean>`
244
+ - `ListObjects(prefix: string, delimiter?: string): Promise<StorageListResult>`
245
+ - `CreateDirectory(directoryPath: string): Promise<boolean>`
246
+ - `DeleteDirectory(directoryPath: string, recursive?: boolean): Promise<boolean>`
247
+ - `GetObjectMetadata(objectName: string): Promise<StorageObjectMetadata>`
248
+ - `GetObject(objectName: string): Promise<Buffer>`
249
+ - `PutObject(objectName: string, data: Buffer, contentType?: string, metadata?: Record<string, string>): Promise<boolean>`
250
+ - `CopyObject(sourceObjectName: string, destinationObjectName: string): Promise<boolean>`
251
+ - `ObjectExists(objectName: string): Promise<boolean>`
252
+ - `DirectoryExists(directoryPath: string): Promise<boolean>`
253
+ - `initialize(): Promise<void>` (optional, for async initialization)
254
+
255
+ ### Utility Functions
256
+
257
+ - `createUploadUrl<T>(provider: FileStorageProviderEntity, input: T): Promise<{ updatedInput: T & { Status: string; ContentType: string }, UploadUrl: string }>`
258
+ - `createDownloadUrl(provider: FileStorageProviderEntity, providerKeyOrName: string): Promise<string>`
259
+ - `moveObject(provider: FileStorageProviderEntity, oldProviderKeyOrName: string, newProviderKeyOrName: string): Promise<boolean>`
260
+ - `deleteObject(provider: FileStorageProviderEntity, providerKeyOrName: string): Promise<boolean>`
261
+
112
262
  ## Architecture
113
263
 
114
- The library uses a class hierarchy with `FileStorageBase` as the abstract base class that defines the common interface. Each storage provider implements this interface in its own way:
264
+ The library uses a class hierarchy with `FileStorageBase` as the abstract base class that defines the common interface. Each storage provider implements this interface:
115
265
 
116
266
  ```
117
267
  FileStorageBase (Abstract Base Class)
118
- ├── AWSFileStorage
119
- ├── AzureFileStorage
120
- ├── GoogleFileStorage
121
- ├── GoogleDriveFileStorage
122
- ├── SharePointFileStorage
123
- ├── DropboxFileStorage
124
- └── BoxFileStorage
268
+ ├── AWSFileStorage (@RegisterClass: 'AWS S3')
269
+ ├── AzureFileStorage (@RegisterClass: 'Azure Blob Storage')
270
+ ├── GoogleFileStorage (@RegisterClass: 'Google Cloud Storage')
271
+ ├── GoogleDriveFileStorage (@RegisterClass: 'Google Drive')
272
+ ├── SharePointFileStorage (@RegisterClass: 'SharePoint')
273
+ ├── DropboxFileStorage (@RegisterClass: 'Dropbox')
274
+ └── BoxFileStorage (@RegisterClass: 'Box')
125
275
  ```
126
276
 
127
- Utility functions provide a simplified interface for common operations.
277
+ Classes are registered with the MemberJunction global class factory using the `@RegisterClass` decorator, enabling dynamic instantiation based on provider keys.
278
+
279
+ ### Integration with MemberJunction
280
+
281
+ This library integrates seamlessly with the MemberJunction platform:
282
+
283
+ 1. **Entity System**: Works with `FileStorageProviderEntity` from `@memberjunction/core-entities`
284
+ 2. **Class Factory**: Uses `@memberjunction/global` for dynamic provider instantiation
285
+ 3. **Configuration**: Provider settings are stored in the MemberJunction database
286
+ 4. **Type Safety**: Fully typed interfaces ensure compile-time safety
128
287
 
129
288
  ## Storage Provider Configuration
130
289
 
@@ -187,22 +346,142 @@ For more information, see [Box Platform Documentation](https://developer.box.com
187
346
 
188
347
  ## Implementing Additional Providers
189
348
 
190
- The library is designed to be extensible. You can implement new storage providers by:
349
+ The library is designed to be extensible. To add a new storage provider:
350
+
351
+ ### 1. Create a New Provider Class
352
+
353
+ ```typescript
354
+ import { FileStorageBase, StorageObjectMetadata, StorageListResult } from '@memberjunction/storage';
355
+ import { RegisterClass } from '@memberjunction/global';
356
+
357
+ @RegisterClass(FileStorageBase, 'My Custom Storage')
358
+ export class MyCustomStorage extends FileStorageBase {
359
+ protected readonly providerName = 'My Custom Storage';
360
+
361
+ constructor() {
362
+ super();
363
+ // Initialize your storage client here
364
+ }
365
+
366
+ public async initialize(): Promise<void> {
367
+ // Optional: Perform async initialization
368
+ // e.g., authenticate, verify permissions
369
+ }
370
+
371
+ public async CreatePreAuthUploadUrl(objectName: string): Promise<CreatePreAuthUploadUrlPayload> {
372
+ // Implement upload URL generation
373
+ // Return { UploadUrl: string, ProviderKey?: string }
374
+ }
375
+
376
+ public async CreatePreAuthDownloadUrl(objectName: string): Promise<string> {
377
+ // Implement download URL generation
378
+ }
379
+
380
+ // Implement all other abstract methods...
381
+ }
382
+ ```
383
+
384
+ ### 2. Handle Unsupported Operations
385
+
386
+ If your provider doesn't support certain operations:
387
+
388
+ ```typescript
389
+ public async CreateDirectory(directoryPath: string): Promise<boolean> {
390
+ // If directories aren't supported
391
+ this.throwUnsupportedOperationError('CreateDirectory');
392
+ }
393
+ ```
394
+
395
+ ### 3. Register Environment Variables
396
+
397
+ Document required environment variables:
398
+
399
+ ```typescript
400
+ import * as env from 'env-var';
401
+
402
+ constructor() {
403
+ super();
404
+ const apiKey = env.get('STORAGE_MYCUSTOM_API_KEY').required().asString();
405
+ const endpoint = env.get('STORAGE_MYCUSTOM_ENDPOINT').required().asString();
406
+ // Use these to initialize your client
407
+ }
408
+ ```
409
+
410
+ ### 4. Export from Index
411
+
412
+ Add to `src/index.ts`:
413
+
414
+ ```typescript
415
+ export * from './drivers/MyCustomStorage';
416
+ ```
417
+
418
+ ### 5. Add to Documentation
419
+
420
+ Update this README with configuration requirements and any provider-specific notes.
421
+
422
+ ## Error Handling
423
+
424
+ The library provides consistent error handling across all providers:
425
+
426
+ ### UnsupportedOperationError
427
+
428
+ Thrown when a provider doesn't support a specific operation:
429
+
430
+ ```typescript
431
+ try {
432
+ await storage.CreateDirectory('/some/path/');
433
+ } catch (error) {
434
+ if (error instanceof UnsupportedOperationError) {
435
+ console.log(`Provider doesn't support directories: ${error.message}`);
436
+ }
437
+ }
438
+ ```
439
+
440
+ ### Provider-Specific Errors
441
+
442
+ Each provider may throw errors specific to its underlying SDK. These are not wrapped, allowing you to handle provider-specific error conditions:
443
+
444
+ ```typescript
445
+ try {
446
+ await storage.GetObject('non-existent-file.txt');
447
+ } catch (error) {
448
+ // Handle provider-specific errors
449
+ if (error.code === 'NoSuchKey') { // AWS S3
450
+ console.log('File not found');
451
+ } else if (error.code === 'BlobNotFound') { // Azure
452
+ console.log('Blob not found');
453
+ }
454
+ }
455
+ ```
456
+
457
+ ## Best Practices
458
+
459
+ 1. **Use ProviderKey**: Always check for and use `ProviderKey` if returned by `CreatePreAuthUploadUrl`
460
+ 2. **Error Handling**: Implement proper error handling for both generic and provider-specific errors
461
+ 3. **Environment Variables**: Store sensitive credentials securely and never commit them to version control
462
+ 4. **Content Types**: Always specify content types for better browser handling and security
463
+ 5. **Metadata**: Use custom metadata to store additional information without modifying file content
464
+ 6. **Directory Paths**: Always use trailing slashes for directory paths (e.g., `documents/` not `documents`)
465
+ 7. **Initialize Providers**: Call `initialize()` on providers that require async setup
191
466
 
192
- 1. Creating a new class that extends `FileStorageBase`
193
- 2. Implementing all required abstract methods
194
- 3. Registering the class using the `@RegisterClass` decorator from `@memberjunction/global`
467
+ ## Performance Considerations
195
468
 
196
- Refer to the existing provider implementations for guidance on how to implement a new provider.
469
+ - **Pre-authenticated URLs**: Use these for client uploads/downloads to reduce server load
470
+ - **Buffering**: The `GetObject` and `PutObject` methods load entire files into memory; for large files, consider streaming approaches
471
+ - **List Operations**: Use appropriate prefixes and delimiters to limit results
472
+ - **Caching**: Utilize ETags and cache control headers when available
197
473
 
198
474
  ## Contributing
199
475
 
200
476
  Contributions are welcome! To add a new storage provider:
201
477
 
202
- 1. Create a new class in the `src/drivers` directory
203
- 2. Extend the `FileStorageBase` class
204
- 3. Implement all required methods
205
- 4. Add exports to `src/index.ts`
478
+ 1. Fork the repository
479
+ 2. Create a feature branch (`git checkout -b feature/new-provider`)
480
+ 3. Create your provider class in `src/drivers/`
481
+ 4. Implement all required methods from `FileStorageBase`
482
+ 5. Add comprehensive tests
483
+ 6. Update documentation
484
+ 7. Submit a pull request
206
485
 
207
486
  ## License
208
487