@memberjunction/storage 4.0.0 → 4.2.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/dist/drivers/AWSFileStorage.js +2 -2
- package/dist/drivers/AWSFileStorage.js.map +1 -1
- package/dist/drivers/AzureFileStorage.js +2 -2
- package/dist/drivers/AzureFileStorage.js.map +1 -1
- package/dist/drivers/BoxFileStorage.js +2 -2
- package/dist/drivers/BoxFileStorage.js.map +1 -1
- package/dist/drivers/DropboxFileStorage.js +2 -2
- package/dist/drivers/DropboxFileStorage.js.map +1 -1
- package/dist/drivers/GoogleDriveFileStorage.js +2 -2
- package/dist/drivers/GoogleDriveFileStorage.js.map +1 -1
- package/dist/drivers/GoogleFileStorage.js +2 -2
- package/dist/drivers/GoogleFileStorage.js.map +1 -1
- package/dist/drivers/SharePointFileStorage.js +2 -2
- package/dist/drivers/SharePointFileStorage.js.map +1 -1
- package/package.json +5 -5
- package/readme.md +688 -371
package/readme.md
CHANGED
|
@@ -2,11 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
The `@memberjunction/storage` library provides a unified interface for interacting with various cloud storage providers. It abstracts the complexities of different storage services behind a consistent API, making it easy to work with files stored across different cloud platforms.
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
Part of the [MemberJunction](https://github.com/MemberJunction/MJ) framework.
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
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.
|
|
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. The package supports both simple single-tenant deployments using environment variables and enterprise multi-tenant deployments using the MemberJunction Credential Engine for secure credential management.
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
The library is organized around an abstract base class (`FileStorageBase`) with concrete driver implementations for each supported cloud storage provider. A set of high-level utility functions in `util.ts` bridge the gap between MemberJunction entities and the underlying drivers. Configuration is handled through Zod-validated schemas loaded from `mj.config.cjs` or environment variables.
|
|
14
|
+
|
|
15
|
+
```mermaid
|
|
16
|
+
graph TB
|
|
17
|
+
subgraph Application["Application Layer"]
|
|
18
|
+
style Application fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
19
|
+
UtilFunctions["Utility Functions<br/>createUploadUrl, createDownloadUrl,<br/>moveObject, deleteObject, listObjects,<br/>copyObjectBetweenProviders,<br/>searchAcrossProviders"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subgraph Core["Core Abstractions"]
|
|
23
|
+
style Core fill:#7c5295,stroke:#563a6b,color:#fff
|
|
24
|
+
FSBase["FileStorageBase<br/>(Abstract Base Class)"]
|
|
25
|
+
Config["StorageConfig<br/>(Zod Schema)"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
subgraph Drivers["Storage Provider Drivers"]
|
|
29
|
+
style Drivers fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
30
|
+
AWS["AWSFileStorage<br/>'AWS S3'"]
|
|
31
|
+
Azure["AzureFileStorage<br/>'Azure Blob Storage'"]
|
|
32
|
+
GCS["GoogleFileStorage<br/>'Google Cloud Storage'"]
|
|
33
|
+
GDrive["GoogleDriveFileStorage<br/>'Google Drive Storage'"]
|
|
34
|
+
SP["SharePointFileStorage<br/>'SharePoint'"]
|
|
35
|
+
Dropbox["DropboxFileStorage<br/>'Dropbox'"]
|
|
36
|
+
Box["BoxFileStorage<br/>'Box'"]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph MJ["MemberJunction Integration"]
|
|
40
|
+
style MJ fill:#b8762f,stroke:#8a5722,color:#fff
|
|
41
|
+
ClassFactory["MJGlobal ClassFactory<br/>@RegisterClass"]
|
|
42
|
+
Entities["FileStorageProviderEntity<br/>FileStorageAccountEntity"]
|
|
43
|
+
CredEngine["CredentialEngine<br/>Secure credential decryption"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
UtilFunctions --> FSBase
|
|
47
|
+
UtilFunctions --> ClassFactory
|
|
48
|
+
UtilFunctions --> Entities
|
|
49
|
+
UtilFunctions --> CredEngine
|
|
50
|
+
FSBase --> Config
|
|
51
|
+
AWS --> FSBase
|
|
52
|
+
Azure --> FSBase
|
|
53
|
+
GCS --> FSBase
|
|
54
|
+
GDrive --> FSBase
|
|
55
|
+
SP --> FSBase
|
|
56
|
+
Dropbox --> FSBase
|
|
57
|
+
Box --> FSBase
|
|
58
|
+
ClassFactory --> AWS
|
|
59
|
+
ClassFactory --> Azure
|
|
60
|
+
ClassFactory --> GCS
|
|
61
|
+
ClassFactory --> GDrive
|
|
62
|
+
ClassFactory --> SP
|
|
63
|
+
ClassFactory --> Dropbox
|
|
64
|
+
ClassFactory --> Box
|
|
65
|
+
```
|
|
10
66
|
|
|
11
67
|
## Features
|
|
12
68
|
|
|
@@ -16,27 +72,37 @@ This library is a key component of the MemberJunction platform, providing seamle
|
|
|
16
72
|
- **Pre-authenticated URLs**: Secure upload and download operations using time-limited URLs
|
|
17
73
|
- **Metadata Support**: Store and retrieve custom metadata with your files
|
|
18
74
|
- **Error Handling**: Provider-specific errors are normalized with clear error messages
|
|
19
|
-
- **
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- [Microsoft SharePoint](https://learn.microsoft.com/en-us/sharepoint/dev/)
|
|
25
|
-
- [Dropbox](https://www.dropbox.com/developers/documentation)
|
|
26
|
-
- [Box](https://developer.box.com/guides/)
|
|
27
|
-
- **Common File Operations**:
|
|
28
|
-
- Upload files (via pre-authenticated URLs)
|
|
29
|
-
- Download files (via pre-authenticated URLs)
|
|
30
|
-
- Copy and move files
|
|
31
|
-
- Delete files and directories
|
|
32
|
-
- List files and directories with metadata
|
|
33
|
-
- Create and manage directories
|
|
34
|
-
- Get detailed file metadata
|
|
35
|
-
- Check file/directory existence
|
|
36
|
-
- Direct upload/download via Buffer
|
|
37
|
-
- **Search files** using native provider search APIs
|
|
75
|
+
- **Zod-Validated Configuration**: All configuration schemas are validated at load time via Zod
|
|
76
|
+
- **Enterprise Credential Management**: Integrates with `@memberjunction/credentials` for secure, multi-tenant credential storage and decryption
|
|
77
|
+
- **Cross-Provider Copy**: Copy files between different storage providers server-side
|
|
78
|
+
- **Multi-Provider Search**: Search for files across multiple providers or accounts in parallel
|
|
79
|
+
- **Token Refresh Persistence**: Automatic callback system to persist new OAuth tokens (critical for providers like Box that rotate refresh tokens)
|
|
38
80
|
- **Extensible**: Easy to add new storage providers by extending `FileStorageBase`
|
|
39
81
|
|
|
82
|
+
### Supported Storage Providers
|
|
83
|
+
|
|
84
|
+
| Provider | Driver Key | Search | Pre-auth URLs | Native Directories |
|
|
85
|
+
|---|---|---|---|---|
|
|
86
|
+
| [AWS S3](https://aws.amazon.com/s3/) | `AWS S3` | No | Yes | Simulated |
|
|
87
|
+
| [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) | `Azure Blob Storage` | No | Yes | Simulated |
|
|
88
|
+
| [Google Cloud Storage](https://cloud.google.com/storage) | `Google Cloud Storage` | No | Yes | Simulated |
|
|
89
|
+
| [Google Drive](https://developers.google.com/drive/api/guides/about-sdk) | `Google Drive Storage` | Yes (content) | Yes | Native |
|
|
90
|
+
| [Microsoft SharePoint](https://learn.microsoft.com/en-us/sharepoint/dev/) | `SharePoint` | Yes (content) | Yes | Native |
|
|
91
|
+
| [Dropbox](https://www.dropbox.com/developers/documentation) | `Dropbox` | Yes (content) | Yes | Native |
|
|
92
|
+
| [Box](https://developer.box.com/guides/) | `Box` | Yes (metadata) | Yes | Native |
|
|
93
|
+
|
|
94
|
+
### File Operations
|
|
95
|
+
|
|
96
|
+
- Upload files (via pre-authenticated URLs or direct Buffer upload)
|
|
97
|
+
- Download files (via pre-authenticated URLs or direct Buffer download)
|
|
98
|
+
- Copy and move files (within and across providers)
|
|
99
|
+
- Delete files and directories (with optional recursive deletion)
|
|
100
|
+
- List files and directories with full metadata
|
|
101
|
+
- Create and manage directories
|
|
102
|
+
- Get detailed file metadata without downloading content
|
|
103
|
+
- Check file/directory existence
|
|
104
|
+
- Search files using native provider search APIs
|
|
105
|
+
|
|
40
106
|
## Installation
|
|
41
107
|
|
|
42
108
|
```bash
|
|
@@ -46,24 +112,56 @@ npm install @memberjunction/storage
|
|
|
46
112
|
## Dependencies
|
|
47
113
|
|
|
48
114
|
This package depends on:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
115
|
+
|
|
116
|
+
| Package | Purpose |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `@memberjunction/core` | Core MemberJunction functionality (logging, UserInfo) |
|
|
119
|
+
| `@memberjunction/core-entities` | Entity definitions (FileStorageProviderEntity, FileStorageAccountEntity) |
|
|
120
|
+
| `@memberjunction/global` | Global class factory and `@RegisterClass` decorator |
|
|
121
|
+
| `@memberjunction/credentials` | Credential Engine for secure credential decryption |
|
|
122
|
+
| `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` | AWS S3 SDK |
|
|
123
|
+
| `@azure/storage-blob`, `@azure/identity` | Azure Blob Storage SDK |
|
|
124
|
+
| `@google-cloud/storage` | Google Cloud Storage SDK |
|
|
125
|
+
| `googleapis` | Google Drive API |
|
|
126
|
+
| `@microsoft/microsoft-graph-client` | SharePoint via Microsoft Graph |
|
|
127
|
+
| `dropbox` | Dropbox SDK |
|
|
128
|
+
| `box-node-sdk` | Box SDK |
|
|
129
|
+
| `cosmiconfig` | Configuration file loading |
|
|
130
|
+
| `zod` | Configuration schema validation |
|
|
131
|
+
| `env-var` | Environment variable parsing |
|
|
132
|
+
| `mime-types` | MIME type detection |
|
|
53
133
|
|
|
54
134
|
## Usage
|
|
55
135
|
|
|
56
|
-
###
|
|
136
|
+
### Initialization Flow
|
|
137
|
+
|
|
138
|
+
Every storage driver follows a two-step initialization pattern. The `initialize()` method is smart enough to handle both simple deployments (environment variables) and multi-tenant deployments (database credentials).
|
|
139
|
+
|
|
140
|
+
```mermaid
|
|
141
|
+
flowchart TD
|
|
142
|
+
Start(["Create Driver Instance"]) --> Constructor["Constructor<br/>Loads env vars / config defaults"]
|
|
143
|
+
Constructor --> Init{"Call initialize()"}
|
|
57
144
|
|
|
58
|
-
|
|
145
|
+
Init -->|"No config"| EnvPath["Use environment variables<br/>already loaded by constructor"]
|
|
146
|
+
Init -->|"With config"| ConfigPath["Override credentials<br/>with provided config"]
|
|
59
147
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
3. Use provider
|
|
148
|
+
EnvPath --> SetAccount["Set accountId / accountName<br/>if provided"]
|
|
149
|
+
ConfigPath --> SetAccount
|
|
63
150
|
|
|
64
|
-
|
|
151
|
+
SetAccount --> Reinit["Reinitialize SDK client<br/>with final credentials"]
|
|
152
|
+
Reinit --> Ready(["Driver Ready"])
|
|
153
|
+
|
|
154
|
+
style Start fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
155
|
+
style Ready fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
156
|
+
style Init fill:#b8762f,stroke:#8a5722,color:#fff
|
|
157
|
+
style Constructor fill:#7c5295,stroke:#563a6b,color:#fff
|
|
158
|
+
style EnvPath fill:#7c5295,stroke:#563a6b,color:#fff
|
|
159
|
+
style ConfigPath fill:#7c5295,stroke:#563a6b,color:#fff
|
|
160
|
+
style SetAccount fill:#7c5295,stroke:#563a6b,color:#fff
|
|
161
|
+
style Reinit fill:#7c5295,stroke:#563a6b,color:#fff
|
|
162
|
+
```
|
|
65
163
|
|
|
66
|
-
###
|
|
164
|
+
### Simple Deployment (Environment Variables)
|
|
67
165
|
|
|
68
166
|
For single-tenant applications, development, testing, or simple production deployments:
|
|
69
167
|
|
|
@@ -89,11 +187,11 @@ await storage.ListObjects('/');
|
|
|
89
187
|
|
|
90
188
|
### Multi-Tenant Enterprise (Database Credentials)
|
|
91
189
|
|
|
92
|
-
For enterprise applications managing multiple storage accounts:
|
|
190
|
+
For enterprise applications managing multiple storage accounts via the MemberJunction entity system and Credential Engine:
|
|
93
191
|
|
|
94
192
|
```typescript
|
|
95
193
|
import { FileStorageEngine } from '@memberjunction/core-entities';
|
|
96
|
-
import { initializeDriverWithAccountCredentials } from '@memberjunction/storage
|
|
194
|
+
import { initializeDriverWithAccountCredentials } from '@memberjunction/storage';
|
|
97
195
|
|
|
98
196
|
// Load account from database
|
|
99
197
|
const engine = FileStorageEngine.Instance;
|
|
@@ -112,34 +210,74 @@ const storage = await initializeDriverWithAccountCredentials({
|
|
|
112
210
|
await storage.ListObjects('/');
|
|
113
211
|
```
|
|
114
212
|
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
213
|
+
The `initializeDriverWithAccountCredentials()` utility:
|
|
214
|
+
- Creates the driver instance via the MJGlobal ClassFactory
|
|
215
|
+
- Retrieves the credential by ID from the Credential Engine
|
|
216
|
+
- Decrypts the credential values
|
|
217
|
+
- Configures a token refresh callback to persist rotated tokens back to the database
|
|
218
|
+
- Calls `initialize()` with decrypted values and account information
|
|
219
|
+
|
|
220
|
+
### Enterprise Credential Flow
|
|
221
|
+
|
|
222
|
+
```mermaid
|
|
223
|
+
sequenceDiagram
|
|
224
|
+
participant App as Application
|
|
225
|
+
participant Util as initializeDriverWithAccountCredentials
|
|
226
|
+
participant CF as ClassFactory
|
|
227
|
+
participant CE as CredentialEngine
|
|
228
|
+
participant Driver as Storage Driver
|
|
229
|
+
|
|
230
|
+
App->>Util: { accountEntity, providerEntity, contextUser }
|
|
231
|
+
Util->>CF: CreateInstance(FileStorageBase, driverKey)
|
|
232
|
+
CF-->>Util: driver instance
|
|
233
|
+
|
|
234
|
+
alt Account has CredentialID
|
|
235
|
+
Util->>CE: Config(false, contextUser)
|
|
236
|
+
Util->>CE: getCredentialById(credentialID)
|
|
237
|
+
CE-->>Util: credentialEntity
|
|
238
|
+
Util->>CE: getCredential(name, options)
|
|
239
|
+
CE-->>Util: { values: decrypted credentials }
|
|
240
|
+
Util->>Util: Create onTokenRefresh callback
|
|
241
|
+
Util->>Driver: initialize({ accountId, ...decryptedValues, onTokenRefresh })
|
|
242
|
+
else No CredentialID
|
|
243
|
+
alt Provider has Configuration JSON
|
|
244
|
+
Util->>Driver: initialize({ accountId, ...providerConfig })
|
|
245
|
+
else No configuration
|
|
246
|
+
Util->>Driver: initialize({ accountId })
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
Driver-->>App: initialized driver
|
|
251
|
+
```
|
|
120
252
|
|
|
121
|
-
### Using Utility Functions
|
|
253
|
+
### Using Utility Functions
|
|
122
254
|
|
|
123
255
|
The library provides high-level utility functions that work with MemberJunction's entity system:
|
|
124
256
|
|
|
125
257
|
```typescript
|
|
126
|
-
import {
|
|
258
|
+
import {
|
|
259
|
+
createUploadUrl,
|
|
260
|
+
createDownloadUrl,
|
|
261
|
+
moveObject,
|
|
262
|
+
deleteObject,
|
|
263
|
+
listObjects,
|
|
264
|
+
copyObject
|
|
265
|
+
} from '@memberjunction/storage';
|
|
127
266
|
import { FileStorageProviderEntity } from '@memberjunction/core-entities';
|
|
128
267
|
import { Metadata } from '@memberjunction/core';
|
|
129
268
|
|
|
130
|
-
// Load a FileStorageProviderEntity from the database
|
|
131
269
|
async function fileOperationsExample() {
|
|
132
270
|
const md = new Metadata();
|
|
133
271
|
const provider = await md.GetEntityObject<FileStorageProviderEntity>('File Storage Providers');
|
|
134
272
|
await provider.Load('your-provider-id');
|
|
135
|
-
|
|
273
|
+
|
|
136
274
|
// Create pre-authenticated upload URL
|
|
137
275
|
const { updatedInput, UploadUrl } = await createUploadUrl(
|
|
138
|
-
provider,
|
|
139
|
-
{
|
|
140
|
-
ID: '123',
|
|
141
|
-
Name: 'documents/report.pdf',
|
|
142
|
-
ProviderID: provider.ID
|
|
276
|
+
provider,
|
|
277
|
+
{
|
|
278
|
+
ID: '123',
|
|
279
|
+
Name: 'documents/report.pdf',
|
|
280
|
+
ProviderID: provider.ID
|
|
143
281
|
}
|
|
144
282
|
);
|
|
145
283
|
|
|
@@ -147,25 +285,27 @@ async function fileOperationsExample() {
|
|
|
147
285
|
console.log(`Upload URL: ${UploadUrl}`);
|
|
148
286
|
console.log(`File status: ${updatedInput.Status}`); // 'Uploading'
|
|
149
287
|
console.log(`Content type: ${updatedInput.ContentType}`); // 'application/pdf'
|
|
150
|
-
|
|
288
|
+
|
|
151
289
|
// If a ProviderKey was returned, use it for future operations
|
|
152
290
|
const fileIdentifier = updatedInput.ProviderKey || updatedInput.Name;
|
|
153
|
-
|
|
154
|
-
//
|
|
291
|
+
|
|
292
|
+
// Create pre-authenticated download URL
|
|
155
293
|
const downloadUrl = await createDownloadUrl(provider, fileIdentifier);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
294
|
+
|
|
295
|
+
// List directory contents
|
|
296
|
+
const contents = await listObjects(provider, 'documents/');
|
|
297
|
+
for (const file of contents.objects) {
|
|
298
|
+
console.log(`${file.name} (${file.size} bytes)`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Copy a file
|
|
302
|
+
await copyObject(provider, fileIdentifier, 'documents/report-backup.pdf');
|
|
303
|
+
|
|
304
|
+
// Move a file
|
|
305
|
+
await moveObject(provider, fileIdentifier, 'archive/report.pdf');
|
|
306
|
+
|
|
307
|
+
// Delete a file
|
|
308
|
+
await deleteObject(provider, 'archive/report.pdf');
|
|
169
309
|
}
|
|
170
310
|
```
|
|
171
311
|
|
|
@@ -174,184 +314,344 @@ async function fileOperationsExample() {
|
|
|
174
314
|
You can work directly with a storage provider by instantiating it:
|
|
175
315
|
|
|
176
316
|
```typescript
|
|
177
|
-
import { FileStorageBase } from '@memberjunction/storage';
|
|
317
|
+
import { AzureFileStorage, FileStorageBase } from '@memberjunction/storage';
|
|
178
318
|
import { MJGlobal } from '@memberjunction/global';
|
|
179
319
|
|
|
180
320
|
async function directProviderExample() {
|
|
181
321
|
// Method 1: Direct instantiation (simple deployment with env vars)
|
|
182
|
-
const storage = new AzureFileStorage();
|
|
183
|
-
await storage.initialize();
|
|
322
|
+
const storage = new AzureFileStorage();
|
|
323
|
+
await storage.initialize();
|
|
184
324
|
|
|
185
325
|
// Method 2: Using class factory (dynamic provider selection)
|
|
186
326
|
const storage2 = MJGlobal.Instance.ClassFactory.CreateInstance<FileStorageBase>(
|
|
187
327
|
FileStorageBase,
|
|
188
328
|
'Azure Blob Storage'
|
|
189
329
|
);
|
|
190
|
-
await storage2.initialize();
|
|
191
|
-
|
|
192
|
-
// Method 3: Multi-tenant with manual initialization
|
|
193
|
-
const storage3 = new AzureFileStorage();
|
|
194
|
-
await storage3.initialize({
|
|
195
|
-
accountId: '12345',
|
|
196
|
-
accountName: 'Azure Account',
|
|
197
|
-
accountName: 'myaccount',
|
|
198
|
-
accountKey: '...',
|
|
199
|
-
defaultContainer: 'my-container'
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Now you can use any of the storage methods:
|
|
330
|
+
await storage2.initialize();
|
|
203
331
|
|
|
204
332
|
// List all files in a directory
|
|
205
333
|
const result = await storage.ListObjects('documents/');
|
|
206
334
|
console.log('Files:', result.objects);
|
|
207
335
|
console.log('Directories:', result.prefixes);
|
|
208
336
|
|
|
209
|
-
// Display detailed metadata for each file
|
|
210
|
-
for (const file of result.objects) {
|
|
211
|
-
console.log(`\nFile: ${file.name}`);
|
|
212
|
-
console.log(` Path: ${file.path}`);
|
|
213
|
-
console.log(` Full Path: ${file.fullPath}`);
|
|
214
|
-
console.log(` Size: ${file.size} bytes`);
|
|
215
|
-
console.log(` Type: ${file.contentType}`);
|
|
216
|
-
console.log(` Modified: ${file.lastModified}`);
|
|
217
|
-
console.log(` Is Directory: ${file.isDirectory}`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Create a directory
|
|
221
|
-
const dirCreated = await storage.CreateDirectory('documents/reports/');
|
|
222
|
-
console.log(`Directory created: ${dirCreated}`);
|
|
223
|
-
|
|
224
337
|
// Upload a file directly with metadata
|
|
225
338
|
const content = Buffer.from('Hello, World!');
|
|
226
|
-
|
|
339
|
+
await storage.PutObject(
|
|
227
340
|
'documents/reports/hello.txt',
|
|
228
341
|
content,
|
|
229
342
|
'text/plain',
|
|
230
|
-
{
|
|
231
|
-
author: 'John Doe',
|
|
232
|
-
department: 'Engineering',
|
|
233
|
-
version: '1.0'
|
|
234
|
-
}
|
|
343
|
+
{ author: 'John Doe', department: 'Engineering' }
|
|
235
344
|
);
|
|
236
|
-
console.log(`File uploaded: ${uploaded}`);
|
|
237
345
|
|
|
238
346
|
// Get file metadata without downloading content
|
|
239
|
-
const metadata = await storage.GetObjectMetadata(
|
|
347
|
+
const metadata = await storage.GetObjectMetadata({
|
|
348
|
+
fullPath: 'documents/reports/hello.txt'
|
|
349
|
+
});
|
|
240
350
|
console.log('File metadata:', metadata);
|
|
241
351
|
|
|
242
352
|
// Download file content
|
|
243
|
-
const fileContent = await storage.GetObject(
|
|
353
|
+
const fileContent = await storage.GetObject({
|
|
354
|
+
fullPath: 'documents/reports/hello.txt'
|
|
355
|
+
});
|
|
244
356
|
console.log('File content:', fileContent.toString('utf8'));
|
|
245
357
|
|
|
246
358
|
// Copy a file
|
|
247
|
-
|
|
359
|
+
await storage.CopyObject(
|
|
248
360
|
'documents/reports/hello.txt',
|
|
249
361
|
'documents/archive/hello-backup.txt'
|
|
250
362
|
);
|
|
251
|
-
console.log(`File copied: ${copied}`);
|
|
252
363
|
|
|
253
364
|
// Check if a file exists
|
|
254
365
|
const exists = await storage.ObjectExists('documents/reports/hello.txt');
|
|
255
|
-
console.log(`File exists: ${exists}`);
|
|
256
366
|
|
|
257
367
|
// Check if a directory exists
|
|
258
368
|
const dirExists = await storage.DirectoryExists('documents/reports/');
|
|
259
|
-
console.log(`Directory exists: ${dirExists}`);
|
|
260
369
|
|
|
261
370
|
// Delete a directory and all its contents
|
|
262
|
-
|
|
263
|
-
|
|
371
|
+
await storage.DeleteDirectory('documents/reports/', true);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Cross-Provider File Copy
|
|
376
|
+
|
|
377
|
+
Transfer files between different storage providers server-side:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { copyObjectBetweenProviders } from '@memberjunction/storage';
|
|
381
|
+
|
|
382
|
+
const result = await copyObjectBetweenProviders(
|
|
383
|
+
sourceProviderEntity,
|
|
384
|
+
destProviderEntity,
|
|
385
|
+
'documents/report.pdf',
|
|
386
|
+
'imported/report.pdf',
|
|
387
|
+
{
|
|
388
|
+
sourceUserContext: { userID: currentUser.ID, contextUser },
|
|
389
|
+
destinationUserContext: { userID: currentUser.ID, contextUser }
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (result.success) {
|
|
394
|
+
console.log(`Transferred ${result.bytesTransferred} bytes`);
|
|
264
395
|
}
|
|
265
396
|
```
|
|
266
397
|
|
|
267
|
-
|
|
398
|
+
```mermaid
|
|
399
|
+
flowchart LR
|
|
400
|
+
Source["Source Provider<br/>(e.g. Dropbox)"] -->|"GetObject()"| Server["MJ Server<br/>(in-memory Buffer)"]
|
|
401
|
+
Server -->|"PutObject()"| Dest["Destination Provider<br/>(e.g. Google Drive)"]
|
|
268
402
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
403
|
+
style Source fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
404
|
+
style Server fill:#b8762f,stroke:#8a5722,color:#fff
|
|
405
|
+
style Dest fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
406
|
+
```
|
|
273
407
|
|
|
274
408
|
### Searching Files
|
|
275
409
|
|
|
276
|
-
Providers with native search capabilities support the `SearchFiles` method
|
|
410
|
+
Providers with native search capabilities support the `SearchFiles` method:
|
|
277
411
|
|
|
278
412
|
```typescript
|
|
279
|
-
import { FileStorageBase,
|
|
280
|
-
import { MJGlobal } from '@memberjunction/global';
|
|
413
|
+
import { FileStorageBase, UnsupportedOperationError } from '@memberjunction/storage';
|
|
281
414
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
'Google Drive Storage'
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
// Simple search for files matching a query
|
|
290
|
-
const results = await storage.SearchFiles('quarterly report');
|
|
415
|
+
try {
|
|
416
|
+
// Simple search
|
|
417
|
+
const results = await storage.SearchFiles('quarterly report');
|
|
291
418
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
console.log(` Excerpt: ${file.excerpt}`);
|
|
297
|
-
}
|
|
419
|
+
for (const file of results.results) {
|
|
420
|
+
console.log(` ${file.path} (${file.size} bytes)`);
|
|
421
|
+
if (file.excerpt) {
|
|
422
|
+
console.log(` Excerpt: ${file.excerpt}`);
|
|
298
423
|
}
|
|
424
|
+
}
|
|
299
425
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// Content search (searches inside files)
|
|
309
|
-
const contentResults = await storage.SearchFiles('machine learning', {
|
|
310
|
-
searchContent: true,
|
|
311
|
-
fileTypes: ['pdf', 'docx', 'txt']
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// Check for more results
|
|
315
|
-
if (contentResults.hasMore) {
|
|
316
|
-
console.log(`Total matches: ${contentResults.totalMatches}`);
|
|
317
|
-
console.log(`Next page token: ${contentResults.nextPageToken}`);
|
|
318
|
-
}
|
|
426
|
+
// Advanced search with filters
|
|
427
|
+
const filtered = await storage.SearchFiles('budget 2024', {
|
|
428
|
+
fileTypes: ['pdf', 'docx'],
|
|
429
|
+
modifiedAfter: new Date('2024-01-01'),
|
|
430
|
+
pathPrefix: 'documents/finance/',
|
|
431
|
+
maxResults: 50,
|
|
432
|
+
searchContent: true
|
|
433
|
+
});
|
|
319
434
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
435
|
+
if (filtered.hasMore) {
|
|
436
|
+
console.log(`Total matches: ${filtered.totalMatches}`);
|
|
437
|
+
}
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error instanceof UnsupportedOperationError) {
|
|
440
|
+
console.log('This provider does not support file search');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Multi-Provider and Multi-Account Search
|
|
446
|
+
|
|
447
|
+
Search across multiple providers or accounts in parallel:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { searchAcrossProviders, searchAcrossAccounts } from '@memberjunction/storage';
|
|
451
|
+
|
|
452
|
+
// Search across multiple providers
|
|
453
|
+
const providerResults = await searchAcrossProviders(
|
|
454
|
+
[googleDriveProvider, dropboxProvider, boxProvider],
|
|
455
|
+
'quarterly report',
|
|
456
|
+
{
|
|
457
|
+
maxResultsPerProvider: 25,
|
|
458
|
+
fileTypes: ['pdf', 'docx'],
|
|
459
|
+
providerUserContexts: new Map([
|
|
460
|
+
[googleDriveProvider.ID, { userID: currentUser.ID, contextUser }],
|
|
461
|
+
[dropboxProvider.ID, { userID: currentUser.ID, contextUser }]
|
|
462
|
+
])
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
for (const pr of providerResults.providerResults) {
|
|
467
|
+
if (pr.success) {
|
|
468
|
+
console.log(`${pr.providerName}: ${pr.results.length} results`);
|
|
469
|
+
} else {
|
|
470
|
+
console.log(`${pr.providerName}: ${pr.errorMessage}`);
|
|
327
471
|
}
|
|
328
472
|
}
|
|
473
|
+
|
|
474
|
+
// Enterprise: Search across multiple accounts (including multiple accounts of same type)
|
|
475
|
+
const accountResults = await searchAcrossAccounts(
|
|
476
|
+
[
|
|
477
|
+
{ accountEntity: researchDropbox, providerEntity: dropboxProvider },
|
|
478
|
+
{ accountEntity: marketingDropbox, providerEntity: dropboxProvider },
|
|
479
|
+
{ accountEntity: engineeringGDrive, providerEntity: gdriveProvider }
|
|
480
|
+
],
|
|
481
|
+
'quarterly report',
|
|
482
|
+
{
|
|
483
|
+
maxResultsPerAccount: 25,
|
|
484
|
+
fileTypes: ['pdf', 'docx'],
|
|
485
|
+
contextUser: currentUser
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
console.log(`Total results: ${accountResults.totalResultsReturned}`);
|
|
490
|
+
console.log(`Successful: ${accountResults.successfulAccounts}`);
|
|
491
|
+
console.log(`Failed: ${accountResults.failedAccounts}`);
|
|
329
492
|
```
|
|
330
493
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
494
|
+
## Configuration
|
|
495
|
+
|
|
496
|
+
### Configuration File (`mj.config.cjs`)
|
|
497
|
+
|
|
498
|
+
Storage providers can be configured via the `mj.config.cjs` file at the repository root. The configuration is validated using Zod schemas at load time.
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
module.exports = {
|
|
502
|
+
storageProviders: {
|
|
503
|
+
aws: {
|
|
504
|
+
accessKeyID: 'your-key',
|
|
505
|
+
secretAccessKey: 'your-secret',
|
|
506
|
+
region: 'us-east-1',
|
|
507
|
+
defaultBucket: 'my-bucket',
|
|
508
|
+
keyPrefix: '/'
|
|
509
|
+
},
|
|
510
|
+
azure: {
|
|
511
|
+
accountName: 'your-account',
|
|
512
|
+
accountKey: 'your-key',
|
|
513
|
+
connectionString: 'optional-conn-string',
|
|
514
|
+
defaultContainer: 'my-container'
|
|
515
|
+
},
|
|
516
|
+
googleCloud: {
|
|
517
|
+
projectID: 'your-project',
|
|
518
|
+
keyFilename: '/path/to/keyfile.json',
|
|
519
|
+
keyJSON: '{"type":"service_account",...}',
|
|
520
|
+
defaultBucket: 'my-bucket'
|
|
521
|
+
},
|
|
522
|
+
googleDrive: {
|
|
523
|
+
clientID: 'your-client-id',
|
|
524
|
+
clientSecret: 'your-client-secret',
|
|
525
|
+
refreshToken: 'your-refresh-token',
|
|
526
|
+
rootFolderID: 'optional-root-folder'
|
|
527
|
+
},
|
|
528
|
+
dropbox: {
|
|
529
|
+
accessToken: 'your-access-token',
|
|
530
|
+
refreshToken: 'your-refresh-token',
|
|
531
|
+
clientID: 'your-app-key',
|
|
532
|
+
clientSecret: 'your-app-secret',
|
|
533
|
+
rootPath: '/optional/root'
|
|
534
|
+
},
|
|
535
|
+
box: {
|
|
536
|
+
clientID: 'your-client-id',
|
|
537
|
+
clientSecret: 'your-client-secret',
|
|
538
|
+
accessToken: 'your-access-token',
|
|
539
|
+
refreshToken: 'your-refresh-token',
|
|
540
|
+
enterpriseID: 'your-enterprise-id',
|
|
541
|
+
rootFolderID: '0'
|
|
542
|
+
},
|
|
543
|
+
sharePoint: {
|
|
544
|
+
clientID: 'your-client-id',
|
|
545
|
+
clientSecret: 'your-client-secret',
|
|
546
|
+
tenantID: 'your-tenant-id',
|
|
547
|
+
siteID: 'your-site-id',
|
|
548
|
+
driveID: 'your-drive-id',
|
|
549
|
+
rootFolderID: 'optional-root-folder'
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
```
|
|
339
554
|
|
|
340
|
-
|
|
555
|
+
### Environment Variables
|
|
556
|
+
|
|
557
|
+
Each provider also supports environment variable configuration. Environment variables take lower priority than config file values (config file wins if both are present).
|
|
558
|
+
|
|
559
|
+
#### AWS S3
|
|
560
|
+
|
|
561
|
+
| Variable | Description |
|
|
562
|
+
|---|---|
|
|
563
|
+
| `STORAGE_AWS_ACCESS_KEY_ID` | AWS access key ID |
|
|
564
|
+
| `STORAGE_AWS_SECRET_ACCESS_KEY` | AWS secret access key |
|
|
565
|
+
| `STORAGE_AWS_REGION` | AWS region (e.g., `us-east-1`) |
|
|
566
|
+
| `STORAGE_AWS_BUCKET_NAME` | S3 bucket name |
|
|
567
|
+
| `STORAGE_AWS_KEY_PREFIX` | Key prefix (defaults to `/`) |
|
|
568
|
+
|
|
569
|
+
#### Azure Blob Storage
|
|
570
|
+
|
|
571
|
+
| Variable | Description |
|
|
572
|
+
|---|---|
|
|
573
|
+
| `STORAGE_AZURE_ACCOUNT_NAME` | Storage account name |
|
|
574
|
+
| `STORAGE_AZURE_ACCOUNT_KEY` | Storage account key |
|
|
575
|
+
| `STORAGE_AZURE_CONNECTION_STRING` | Connection string (alternative to name/key) |
|
|
576
|
+
| `STORAGE_AZURE_CONTAINER` or `STORAGE_AZURE_DEFAULT_CONTAINER` | Container name |
|
|
577
|
+
|
|
578
|
+
#### Google Cloud Storage
|
|
579
|
+
|
|
580
|
+
| Variable | Description |
|
|
581
|
+
|---|---|
|
|
582
|
+
| `STORAGE_GOOGLE_KEY_JSON` | JSON string of service account credentials |
|
|
583
|
+
| `STORAGE_GOOGLE_CLOUD_KEY_FILENAME` | Path to service account key file |
|
|
584
|
+
| `STORAGE_GOOGLE_BUCKET_NAME` or `STORAGE_GOOGLE_CLOUD_DEFAULT_BUCKET` | GCS bucket name |
|
|
585
|
+
| `STORAGE_GOOGLE_CLOUD_PROJECT_ID` | Google Cloud project ID |
|
|
586
|
+
|
|
587
|
+
#### Google Drive
|
|
588
|
+
|
|
589
|
+
| Variable | Description |
|
|
590
|
+
|---|---|
|
|
591
|
+
| `STORAGE_GOOGLE_DRIVE_CLIENT_ID` | OAuth client ID |
|
|
592
|
+
| `STORAGE_GOOGLE_DRIVE_CLIENT_SECRET` | OAuth client secret |
|
|
593
|
+
| `STORAGE_GOOGLE_DRIVE_REFRESH_TOKEN` | OAuth refresh token |
|
|
594
|
+
| `STORAGE_GOOGLE_DRIVE_REDIRECT_URI` | OAuth redirect URI |
|
|
595
|
+
| `STORAGE_GDRIVE_ROOT_FOLDER_ID` | Root folder ID (optional) |
|
|
596
|
+
| `STORAGE_GDRIVE_KEY_FILE` | Service account key file (legacy) |
|
|
597
|
+
| `STORAGE_GDRIVE_CREDENTIALS_JSON` | Service account credentials JSON (legacy) |
|
|
598
|
+
|
|
599
|
+
#### SharePoint
|
|
600
|
+
|
|
601
|
+
| Variable | Description |
|
|
602
|
+
|---|---|
|
|
603
|
+
| `STORAGE_SHAREPOINT_CLIENT_ID` | Azure AD client ID |
|
|
604
|
+
| `STORAGE_SHAREPOINT_CLIENT_SECRET` | Azure AD client secret |
|
|
605
|
+
| `STORAGE_SHAREPOINT_TENANT_ID` | Azure AD tenant ID |
|
|
606
|
+
| `STORAGE_SHAREPOINT_SITE_ID` | SharePoint site ID |
|
|
607
|
+
| `STORAGE_SHAREPOINT_DRIVE_ID` | Document library drive ID |
|
|
608
|
+
| `STORAGE_SHAREPOINT_ROOT_FOLDER_ID` | Root folder ID (optional) |
|
|
609
|
+
|
|
610
|
+
#### Dropbox
|
|
611
|
+
|
|
612
|
+
| Variable | Description |
|
|
613
|
+
|---|---|
|
|
614
|
+
| `STORAGE_DROPBOX_ACCESS_TOKEN` | Dropbox access token |
|
|
615
|
+
| `STORAGE_DROPBOX_REFRESH_TOKEN` | Dropbox refresh token |
|
|
616
|
+
| `STORAGE_DROPBOX_CLIENT_ID` or `STORAGE_DROPBOX_APP_KEY` | App key |
|
|
617
|
+
| `STORAGE_DROPBOX_CLIENT_SECRET` or `STORAGE_DROPBOX_APP_SECRET` | App secret |
|
|
618
|
+
| `STORAGE_DROPBOX_ROOT_PATH` | Root path (optional) |
|
|
619
|
+
|
|
620
|
+
#### Box
|
|
621
|
+
|
|
622
|
+
| Variable | Description |
|
|
623
|
+
|---|---|
|
|
624
|
+
| `STORAGE_BOX_CLIENT_ID` | Box client ID |
|
|
625
|
+
| `STORAGE_BOX_CLIENT_SECRET` | Box client secret |
|
|
626
|
+
| `STORAGE_BOX_ACCESS_TOKEN` | Box access token |
|
|
627
|
+
| `STORAGE_BOX_REFRESH_TOKEN` | Box refresh token |
|
|
628
|
+
| `STORAGE_BOX_ENTERPRISE_ID` | Box enterprise ID (for JWT auth) |
|
|
629
|
+
| `STORAGE_BOX_ROOT_FOLDER_ID` | Root folder ID (optional) |
|
|
341
630
|
|
|
342
631
|
## API Reference
|
|
343
632
|
|
|
344
633
|
### Core Types
|
|
345
634
|
|
|
346
635
|
#### `CreatePreAuthUploadUrlPayload`
|
|
636
|
+
|
|
347
637
|
```typescript
|
|
348
638
|
type CreatePreAuthUploadUrlPayload = {
|
|
349
|
-
UploadUrl: string;
|
|
350
|
-
ProviderKey?: string;
|
|
639
|
+
UploadUrl: string; // Pre-authenticated URL for upload
|
|
640
|
+
ProviderKey?: string; // Optional provider-specific key for future reference
|
|
641
|
+
};
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
#### `GetObjectParams` / `GetObjectMetadataParams`
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
type GetObjectParams = {
|
|
648
|
+
objectId?: string; // Provider-specific ID (preferred for performance)
|
|
649
|
+
fullPath?: string; // Full path to the object (fallback)
|
|
351
650
|
};
|
|
352
651
|
```
|
|
353
652
|
|
|
354
653
|
#### `StorageObjectMetadata`
|
|
654
|
+
|
|
355
655
|
```typescript
|
|
356
656
|
type StorageObjectMetadata = {
|
|
357
657
|
name: string; // Object name (filename)
|
|
@@ -363,11 +663,12 @@ type StorageObjectMetadata = {
|
|
|
363
663
|
isDirectory: boolean; // Whether this is a directory
|
|
364
664
|
etag?: string; // Entity tag for caching
|
|
365
665
|
cacheControl?: string; // Cache control directives
|
|
366
|
-
customMetadata?: Record<string, string>;
|
|
666
|
+
customMetadata?: Record<string, string>;
|
|
367
667
|
};
|
|
368
668
|
```
|
|
369
669
|
|
|
370
670
|
#### `StorageListResult`
|
|
671
|
+
|
|
371
672
|
```typescript
|
|
372
673
|
type StorageListResult = {
|
|
373
674
|
objects: StorageObjectMetadata[]; // Files found
|
|
@@ -376,155 +677,113 @@ type StorageListResult = {
|
|
|
376
677
|
```
|
|
377
678
|
|
|
378
679
|
#### `FileSearchOptions`
|
|
680
|
+
|
|
379
681
|
```typescript
|
|
380
682
|
type FileSearchOptions = {
|
|
381
|
-
maxResults?: number;
|
|
382
|
-
fileTypes?: string[];
|
|
383
|
-
modifiedAfter?: Date;
|
|
384
|
-
modifiedBefore?: Date;
|
|
385
|
-
pathPrefix?: string;
|
|
386
|
-
searchContent?: boolean;
|
|
387
|
-
providerSpecific?: Record<string,
|
|
683
|
+
maxResults?: number; // Maximum results (default: 100)
|
|
684
|
+
fileTypes?: string[]; // Filter by MIME types or extensions
|
|
685
|
+
modifiedAfter?: Date; // Only files modified after this date
|
|
686
|
+
modifiedBefore?: Date; // Only files modified before this date
|
|
687
|
+
pathPrefix?: string; // Search within specific directory
|
|
688
|
+
searchContent?: boolean; // Search file contents (default: false)
|
|
689
|
+
providerSpecific?: Record<string, unknown>;// Provider-specific options
|
|
388
690
|
};
|
|
389
691
|
```
|
|
390
692
|
|
|
391
693
|
#### `FileSearchResult`
|
|
694
|
+
|
|
392
695
|
```typescript
|
|
393
696
|
type FileSearchResult = {
|
|
394
|
-
path: string;
|
|
395
|
-
name: string;
|
|
396
|
-
size: number;
|
|
397
|
-
contentType: string;
|
|
398
|
-
lastModified: Date;
|
|
399
|
-
relevance?: number;
|
|
400
|
-
excerpt?: string;
|
|
401
|
-
matchInFilename?: boolean;
|
|
402
|
-
|
|
403
|
-
|
|
697
|
+
path: string; // Full path to file
|
|
698
|
+
name: string; // Filename only
|
|
699
|
+
size: number; // Size in bytes
|
|
700
|
+
contentType: string; // MIME type
|
|
701
|
+
lastModified: Date; // Last modification date
|
|
702
|
+
relevance?: number; // Relevance score (0.0-1.0)
|
|
703
|
+
excerpt?: string; // Text excerpt with match context
|
|
704
|
+
matchInFilename?: boolean; // Whether match is in filename
|
|
705
|
+
objectId?: string; // Provider-specific ID for direct access
|
|
706
|
+
customMetadata?: Record<string, string>;
|
|
707
|
+
providerData?: Record<string, unknown>;
|
|
404
708
|
};
|
|
405
709
|
```
|
|
406
710
|
|
|
407
711
|
#### `FileSearchResultSet`
|
|
712
|
+
|
|
408
713
|
```typescript
|
|
409
714
|
type FileSearchResultSet = {
|
|
410
|
-
results: FileSearchResult[];
|
|
411
|
-
totalMatches?: number;
|
|
412
|
-
hasMore: boolean;
|
|
413
|
-
nextPageToken?: string;
|
|
715
|
+
results: FileSearchResult[]; // Array of matching files
|
|
716
|
+
totalMatches?: number; // Total matches (if available)
|
|
717
|
+
hasMore: boolean; // More results available?
|
|
718
|
+
nextPageToken?: string; // Token for next page
|
|
414
719
|
};
|
|
415
720
|
```
|
|
416
721
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
All storage providers implement these methods:
|
|
722
|
+
#### `StorageProviderConfig`
|
|
420
723
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
- `CreateDirectory(directoryPath: string): Promise<boolean>`
|
|
428
|
-
- `DeleteDirectory(directoryPath: string, recursive?: boolean): Promise<boolean>`
|
|
429
|
-
- `GetObjectMetadata(objectName: string): Promise<StorageObjectMetadata>`
|
|
430
|
-
- `GetObject(objectName: string): Promise<Buffer>`
|
|
431
|
-
- `PutObject(objectName: string, data: Buffer, contentType?: string, metadata?: Record<string, string>): Promise<boolean>`
|
|
432
|
-
- `CopyObject(sourceObjectName: string, destinationObjectName: string): Promise<boolean>`
|
|
433
|
-
- `ObjectExists(objectName: string): Promise<boolean>`
|
|
434
|
-
- `DirectoryExists(directoryPath: string): Promise<boolean>`
|
|
435
|
-
- `SearchFiles(query: string, options?: FileSearchOptions): Promise<FileSearchResultSet>` (throws `UnsupportedOperationError` for providers without native search)
|
|
436
|
-
|
|
437
|
-
### Utility Functions
|
|
438
|
-
|
|
439
|
-
- `createUploadUrl<T>(provider: FileStorageProviderEntity, input: T): Promise<{ updatedInput: T & { Status: string; ContentType: string }, UploadUrl: string }>`
|
|
440
|
-
- `createDownloadUrl(provider: FileStorageProviderEntity, providerKeyOrName: string): Promise<string>`
|
|
441
|
-
- `moveObject(provider: FileStorageProviderEntity, oldProviderKeyOrName: string, newProviderKeyOrName: string): Promise<boolean>`
|
|
442
|
-
- `deleteObject(provider: FileStorageProviderEntity, providerKeyOrName: string): Promise<boolean>`
|
|
443
|
-
|
|
444
|
-
## Architecture
|
|
445
|
-
|
|
446
|
-
The library uses a class hierarchy with `FileStorageBase` as the abstract base class that defines the common interface. Each storage provider implements this interface:
|
|
447
|
-
|
|
448
|
-
```
|
|
449
|
-
FileStorageBase (Abstract Base Class)
|
|
450
|
-
├── AWSFileStorage (@RegisterClass: 'AWS S3')
|
|
451
|
-
├── AzureFileStorage (@RegisterClass: 'Azure Blob Storage')
|
|
452
|
-
├── GoogleFileStorage (@RegisterClass: 'Google Cloud Storage')
|
|
453
|
-
├── GoogleDriveFileStorage (@RegisterClass: 'Google Drive')
|
|
454
|
-
├── SharePointFileStorage (@RegisterClass: 'SharePoint')
|
|
455
|
-
├── DropboxFileStorage (@RegisterClass: 'Dropbox')
|
|
456
|
-
└── BoxFileStorage (@RegisterClass: 'Box')
|
|
724
|
+
```typescript
|
|
725
|
+
interface StorageProviderConfig {
|
|
726
|
+
accountId?: string; // FileStorageAccount ID (multi-tenant tracking)
|
|
727
|
+
accountName?: string; // Account display name (logging)
|
|
728
|
+
[key: string]: unknown; // Provider-specific configuration values
|
|
729
|
+
}
|
|
457
730
|
```
|
|
458
731
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
### Integration with MemberJunction
|
|
732
|
+
#### `UnsupportedOperationError`
|
|
462
733
|
|
|
463
|
-
|
|
734
|
+
Custom error class thrown when a provider does not support a specific operation (e.g., `SearchFiles` on AWS S3).
|
|
464
735
|
|
|
465
|
-
|
|
466
|
-
2. **Class Factory**: Uses `@memberjunction/global` for dynamic provider instantiation
|
|
467
|
-
3. **Configuration**: Provider settings are stored in the MemberJunction database
|
|
468
|
-
4. **Type Safety**: Fully typed interfaces ensure compile-time safety
|
|
469
|
-
|
|
470
|
-
## Storage Provider Configuration
|
|
471
|
-
|
|
472
|
-
Each storage provider requires specific environment variables. Please refer to the official documentation for each provider for detailed information on authentication and additional configuration options.
|
|
473
|
-
|
|
474
|
-
### AWS S3
|
|
475
|
-
- `STORAGE_AWS_BUCKET`: S3 bucket name
|
|
476
|
-
- `STORAGE_AWS_REGION`: AWS region (e.g., 'us-east-1')
|
|
477
|
-
- `STORAGE_AWS_ACCESS_KEY_ID`: AWS access key ID
|
|
478
|
-
- `STORAGE_AWS_SECRET_ACCESS_KEY`: AWS secret access key
|
|
479
|
-
|
|
480
|
-
For more information, see [AWS S3 Documentation](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started-nodejs.html).
|
|
481
|
-
|
|
482
|
-
### Azure Blob Storage
|
|
483
|
-
- `STORAGE_AZURE_CONTAINER`: Container name
|
|
484
|
-
- `STORAGE_AZURE_ACCOUNT_NAME`: Account name
|
|
485
|
-
- `STORAGE_AZURE_ACCOUNT_KEY`: Account key
|
|
486
|
-
|
|
487
|
-
For more information, see [Azure Blob Storage Documentation](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs).
|
|
488
|
-
|
|
489
|
-
### Google Cloud Storage
|
|
490
|
-
- `STORAGE_GOOGLE_BUCKET`: GCS bucket name
|
|
491
|
-
- `STORAGE_GOOGLE_KEY_FILE_PATH`: Path to service account key file (JSON)
|
|
492
|
-
|
|
493
|
-
For more information, see [Google Cloud Storage Documentation](https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-nodejs).
|
|
494
|
-
|
|
495
|
-
### Google Drive
|
|
496
|
-
- `STORAGE_GOOGLE_DRIVE_CLIENT_ID`: OAuth client ID
|
|
497
|
-
- `STORAGE_GOOGLE_DRIVE_CLIENT_SECRET`: OAuth client secret
|
|
498
|
-
- `STORAGE_GOOGLE_DRIVE_REDIRECT_URI`: OAuth redirect URI
|
|
499
|
-
- `STORAGE_GOOGLE_DRIVE_REFRESH_TOKEN`: OAuth refresh token
|
|
500
|
-
|
|
501
|
-
For more information, see [Google Drive API Documentation](https://developers.google.com/drive/api/guides/about-sdk).
|
|
502
|
-
|
|
503
|
-
### SharePoint
|
|
504
|
-
- `STORAGE_SHAREPOINT_SITE_URL`: SharePoint site URL
|
|
505
|
-
- `STORAGE_SHAREPOINT_CLIENT_ID`: Azure AD client ID
|
|
506
|
-
- `STORAGE_SHAREPOINT_CLIENT_SECRET`: Azure AD client secret
|
|
507
|
-
- `STORAGE_SHAREPOINT_TENANT_ID`: Azure AD tenant ID
|
|
508
|
-
|
|
509
|
-
For more information, see [Microsoft Graph API Documentation](https://learn.microsoft.com/en-us/graph/api/resources/sharepoint).
|
|
736
|
+
### FileStorageBase Methods
|
|
510
737
|
|
|
511
|
-
|
|
512
|
-
- `STORAGE_DROPBOX_ACCESS_TOKEN`: Dropbox access token
|
|
513
|
-
- `STORAGE_DROPBOX_REFRESH_TOKEN`: Dropbox refresh token (optional)
|
|
514
|
-
- `STORAGE_DROPBOX_APP_KEY`: Dropbox app key
|
|
515
|
-
- `STORAGE_DROPBOX_APP_SECRET`: Dropbox app secret
|
|
738
|
+
All storage providers implement these methods:
|
|
516
739
|
|
|
517
|
-
|
|
740
|
+
| Method | Returns | Description |
|
|
741
|
+
|---|---|---|
|
|
742
|
+
| `initialize(config?)` | `Promise<void>` | Initialize the driver. Always call after construction. |
|
|
743
|
+
| `CreatePreAuthUploadUrl(objectName)` | `Promise<CreatePreAuthUploadUrlPayload>` | Generate pre-authenticated upload URL |
|
|
744
|
+
| `CreatePreAuthDownloadUrl(objectName)` | `Promise<string>` | Generate pre-authenticated download URL |
|
|
745
|
+
| `MoveObject(oldName, newName)` | `Promise<boolean>` | Move/rename a file |
|
|
746
|
+
| `DeleteObject(objectName)` | `Promise<boolean>` | Delete a file |
|
|
747
|
+
| `ListObjects(prefix, delimiter?)` | `Promise<StorageListResult>` | List files and directories |
|
|
748
|
+
| `CreateDirectory(directoryPath)` | `Promise<boolean>` | Create a directory |
|
|
749
|
+
| `DeleteDirectory(path, recursive?)` | `Promise<boolean>` | Delete a directory |
|
|
750
|
+
| `GetObjectMetadata(params)` | `Promise<StorageObjectMetadata>` | Get file metadata without downloading |
|
|
751
|
+
| `GetObject(params)` | `Promise<Buffer>` | Download file content |
|
|
752
|
+
| `PutObject(name, data, contentType?, metadata?)` | `Promise<boolean>` | Upload file content directly |
|
|
753
|
+
| `CopyObject(source, destination)` | `Promise<boolean>` | Copy a file |
|
|
754
|
+
| `ObjectExists(objectName)` | `Promise<boolean>` | Check if a file exists |
|
|
755
|
+
| `DirectoryExists(directoryPath)` | `Promise<boolean>` | Check if a directory exists |
|
|
756
|
+
| `SearchFiles(query, options?)` | `Promise<FileSearchResultSet>` | Search files (throws `UnsupportedOperationError` if not supported) |
|
|
757
|
+
| `get IsConfigured` | `boolean` | Check if driver is properly configured |
|
|
758
|
+
| `get AccountId` | `string \| undefined` | Get the associated account ID |
|
|
759
|
+
| `get AccountName` | `string \| undefined` | Get the associated account name |
|
|
518
760
|
|
|
519
|
-
###
|
|
520
|
-
- `STORAGE_BOX_CLIENT_ID`: Box client ID
|
|
521
|
-
- `STORAGE_BOX_CLIENT_SECRET`: Box client secret
|
|
522
|
-
- `STORAGE_BOX_ENTERPRISE_ID`: Box enterprise ID
|
|
523
|
-
- `STORAGE_BOX_JWT_KEY_ID`: Box JWT key ID
|
|
524
|
-
- `STORAGE_BOX_PRIVATE_KEY`: Box private key (base64 encoded)
|
|
525
|
-
- `STORAGE_BOX_PRIVATE_KEY_PASSPHRASE`: Box private key passphrase (optional)
|
|
761
|
+
### Utility Functions
|
|
526
762
|
|
|
527
|
-
|
|
763
|
+
High-level functions that integrate with MemberJunction's entity system:
|
|
764
|
+
|
|
765
|
+
| Function | Description |
|
|
766
|
+
|---|---|
|
|
767
|
+
| `createUploadUrl(provider, input, userContext?)` | Create pre-authenticated upload URL with automatic MIME type detection |
|
|
768
|
+
| `createDownloadUrl(provider, keyOrName, userContext?)` | Create pre-authenticated download URL |
|
|
769
|
+
| `moveObject(provider, oldKey, newKey, userContext?)` | Move a file within a provider |
|
|
770
|
+
| `copyObject(provider, source, destination, userContext?)` | Copy a file within a provider |
|
|
771
|
+
| `deleteObject(provider, keyOrName, userContext?)` | Delete a file |
|
|
772
|
+
| `listObjects(provider, prefix, delimiter?, userContext?)` | List files and directories |
|
|
773
|
+
| `copyObjectBetweenProviders(source, dest, sourcePath, destPath, options?)` | Transfer file between providers |
|
|
774
|
+
| `searchAcrossProviders(providers, query, options?)` | Search multiple providers in parallel |
|
|
775
|
+
| `searchAcrossAccounts(accounts, query, options)` | Search multiple accounts in parallel (enterprise) |
|
|
776
|
+
| `initializeDriverWithAccountCredentials(options)` | Initialize driver using enterprise credential model |
|
|
777
|
+
| `initializeDriverWithUserCredentials(options)` | Initialize driver with user context (deprecated) |
|
|
778
|
+
|
|
779
|
+
### Configuration Functions
|
|
780
|
+
|
|
781
|
+
| Function | Description |
|
|
782
|
+
|---|---|
|
|
783
|
+
| `getStorageConfig()` | Get full storage configuration (loads from `mj.config.cjs` on first call) |
|
|
784
|
+
| `getStorageProvidersConfig()` | Get just the storage providers configuration |
|
|
785
|
+
| `getProviderConfig(provider)` | Get configuration for a specific provider by key |
|
|
786
|
+
| `clearStorageConfig()` | Clear cached configuration (useful for testing) |
|
|
528
787
|
|
|
529
788
|
## Implementing Additional Providers
|
|
530
789
|
|
|
@@ -533,63 +792,55 @@ The library is designed to be extensible. To add a new storage provider:
|
|
|
533
792
|
### 1. Create a New Provider Class
|
|
534
793
|
|
|
535
794
|
```typescript
|
|
536
|
-
import {
|
|
795
|
+
import {
|
|
796
|
+
FileStorageBase,
|
|
797
|
+
StorageProviderConfig,
|
|
798
|
+
StorageObjectMetadata,
|
|
799
|
+
StorageListResult,
|
|
800
|
+
CreatePreAuthUploadUrlPayload,
|
|
801
|
+
GetObjectParams,
|
|
802
|
+
GetObjectMetadataParams,
|
|
803
|
+
FileSearchOptions,
|
|
804
|
+
FileSearchResultSet,
|
|
805
|
+
} from '@memberjunction/storage';
|
|
537
806
|
import { RegisterClass } from '@memberjunction/global';
|
|
538
807
|
|
|
539
808
|
@RegisterClass(FileStorageBase, 'My Custom Storage')
|
|
540
809
|
export class MyCustomStorage extends FileStorageBase {
|
|
541
810
|
protected readonly providerName = 'My Custom Storage';
|
|
542
|
-
|
|
811
|
+
|
|
812
|
+
private _apiKey: string | undefined;
|
|
813
|
+
private _isConfigured = false;
|
|
814
|
+
|
|
543
815
|
constructor() {
|
|
544
816
|
super();
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
public async initialize(): Promise<void> {
|
|
549
|
-
// Optional: Perform async initialization
|
|
550
|
-
// e.g., authenticate, verify permissions
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
public async CreatePreAuthUploadUrl(objectName: string): Promise<CreatePreAuthUploadUrlPayload> {
|
|
554
|
-
// Implement upload URL generation
|
|
555
|
-
// Return { UploadUrl: string, ProviderKey?: string }
|
|
817
|
+
// Load from environment variables
|
|
818
|
+
this._apiKey = process.env.STORAGE_MYCUSTOM_API_KEY;
|
|
556
819
|
}
|
|
557
|
-
|
|
558
|
-
public async CreatePreAuthDownloadUrl(objectName: string): Promise<string> {
|
|
559
|
-
// Implement download URL generation
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Implement all other abstract methods...
|
|
563
|
-
}
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
### 2. Handle Unsupported Operations
|
|
567
|
-
|
|
568
|
-
If your provider doesn't support certain operations:
|
|
569
820
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
821
|
+
public async initialize(config?: StorageProviderConfig): Promise<void> {
|
|
822
|
+
await super.initialize(config); // Sets accountId and accountName
|
|
823
|
+
if (config) {
|
|
824
|
+
// Override env var defaults with config values if present
|
|
825
|
+
if (config.apiKey) this._apiKey = config.apiKey as string;
|
|
826
|
+
}
|
|
827
|
+
this._isConfigured = !!this._apiKey;
|
|
828
|
+
}
|
|
578
829
|
|
|
579
|
-
|
|
830
|
+
public get IsConfigured(): boolean {
|
|
831
|
+
return this._isConfigured;
|
|
832
|
+
}
|
|
580
833
|
|
|
581
|
-
|
|
582
|
-
import * as env from 'env-var';
|
|
834
|
+
// Implement all abstract methods...
|
|
583
835
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
// Use these to initialize your client
|
|
836
|
+
public async SearchFiles(query: string, options?: FileSearchOptions): Promise<FileSearchResultSet> {
|
|
837
|
+
// If not supported:
|
|
838
|
+
this.throwUnsupportedOperationError('SearchFiles');
|
|
839
|
+
}
|
|
589
840
|
}
|
|
590
841
|
```
|
|
591
842
|
|
|
592
|
-
###
|
|
843
|
+
### 2. Export from Index
|
|
593
844
|
|
|
594
845
|
Add to `src/index.ts`:
|
|
595
846
|
|
|
@@ -597,24 +848,80 @@ Add to `src/index.ts`:
|
|
|
597
848
|
export * from './drivers/MyCustomStorage';
|
|
598
849
|
```
|
|
599
850
|
|
|
600
|
-
|
|
851
|
+
## Class Hierarchy
|
|
852
|
+
|
|
853
|
+
```mermaid
|
|
854
|
+
classDiagram
|
|
855
|
+
class FileStorageBase {
|
|
856
|
+
<<abstract>>
|
|
857
|
+
#providerName: string
|
|
858
|
+
#_accountId: string
|
|
859
|
+
#_accountName: string
|
|
860
|
+
+initialize(config?) Promise~void~
|
|
861
|
+
+get IsConfigured() boolean
|
|
862
|
+
+get AccountId() string
|
|
863
|
+
+get AccountName() string
|
|
864
|
+
+CreatePreAuthUploadUrl(name) Promise
|
|
865
|
+
+CreatePreAuthDownloadUrl(name) Promise
|
|
866
|
+
+MoveObject(old, new) Promise
|
|
867
|
+
+DeleteObject(name) Promise
|
|
868
|
+
+ListObjects(prefix, delimiter?) Promise
|
|
869
|
+
+CreateDirectory(path) Promise
|
|
870
|
+
+DeleteDirectory(path, recursive?) Promise
|
|
871
|
+
+GetObjectMetadata(params) Promise
|
|
872
|
+
+GetObject(params) Promise
|
|
873
|
+
+PutObject(name, data, type?, meta?) Promise
|
|
874
|
+
+CopyObject(source, dest) Promise
|
|
875
|
+
+ObjectExists(name) Promise
|
|
876
|
+
+DirectoryExists(path) Promise
|
|
877
|
+
+SearchFiles(query, options?) Promise
|
|
878
|
+
#throwUnsupportedOperationError(method) never
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
class AWSFileStorage {
|
|
882
|
+
@RegisterClass 'AWS S3'
|
|
883
|
+
}
|
|
884
|
+
class AzureFileStorage {
|
|
885
|
+
@RegisterClass 'Azure Blob Storage'
|
|
886
|
+
}
|
|
887
|
+
class GoogleFileStorage {
|
|
888
|
+
@RegisterClass 'Google Cloud Storage'
|
|
889
|
+
}
|
|
890
|
+
class GoogleDriveFileStorage {
|
|
891
|
+
@RegisterClass 'Google Drive Storage'
|
|
892
|
+
}
|
|
893
|
+
class SharePointFileStorage {
|
|
894
|
+
@RegisterClass 'SharePoint'
|
|
895
|
+
}
|
|
896
|
+
class DropboxFileStorage {
|
|
897
|
+
@RegisterClass 'Dropbox'
|
|
898
|
+
}
|
|
899
|
+
class BoxFileStorage {
|
|
900
|
+
@RegisterClass 'Box'
|
|
901
|
+
}
|
|
601
902
|
|
|
602
|
-
|
|
903
|
+
FileStorageBase <|-- AWSFileStorage
|
|
904
|
+
FileStorageBase <|-- AzureFileStorage
|
|
905
|
+
FileStorageBase <|-- GoogleFileStorage
|
|
906
|
+
FileStorageBase <|-- GoogleDriveFileStorage
|
|
907
|
+
FileStorageBase <|-- SharePointFileStorage
|
|
908
|
+
FileStorageBase <|-- DropboxFileStorage
|
|
909
|
+
FileStorageBase <|-- BoxFileStorage
|
|
910
|
+
```
|
|
603
911
|
|
|
604
912
|
## Error Handling
|
|
605
913
|
|
|
606
|
-
The library provides consistent error handling across all providers:
|
|
607
|
-
|
|
608
914
|
### UnsupportedOperationError
|
|
609
915
|
|
|
610
|
-
Thrown when a provider
|
|
916
|
+
Thrown when a provider does not support a specific operation:
|
|
611
917
|
|
|
612
918
|
```typescript
|
|
613
919
|
try {
|
|
614
|
-
await storage.
|
|
920
|
+
await storage.SearchFiles('quarterly report');
|
|
615
921
|
} catch (error) {
|
|
616
922
|
if (error instanceof UnsupportedOperationError) {
|
|
617
|
-
console.log(`Provider doesn't support
|
|
923
|
+
console.log(`Provider doesn't support search: ${error.message}`);
|
|
924
|
+
// Fall back to ListObjects with client-side filtering
|
|
618
925
|
}
|
|
619
926
|
}
|
|
620
927
|
```
|
|
@@ -625,10 +932,10 @@ Each provider may throw errors specific to its underlying SDK. These are not wra
|
|
|
625
932
|
|
|
626
933
|
```typescript
|
|
627
934
|
try {
|
|
628
|
-
await storage.GetObject('non-existent-file.txt');
|
|
935
|
+
await storage.GetObject({ fullPath: 'non-existent-file.txt' });
|
|
629
936
|
} catch (error) {
|
|
630
937
|
// Handle provider-specific errors
|
|
631
|
-
if (error.code === 'NoSuchKey') {
|
|
938
|
+
if (error.code === 'NoSuchKey') { // AWS S3
|
|
632
939
|
console.log('File not found');
|
|
633
940
|
} else if (error.code === 'BlobNotFound') { // Azure
|
|
634
941
|
console.log('Blob not found');
|
|
@@ -636,39 +943,49 @@ try {
|
|
|
636
943
|
}
|
|
637
944
|
```
|
|
638
945
|
|
|
639
|
-
##
|
|
946
|
+
## Testing
|
|
640
947
|
|
|
641
|
-
|
|
642
|
-
2. **Error Handling**: Implement proper error handling for both generic and provider-specific errors
|
|
643
|
-
3. **Environment Variables**: Store sensitive credentials securely and never commit them to version control
|
|
644
|
-
4. **Content Types**: Always specify content types for better browser handling and security
|
|
645
|
-
5. **Metadata**: Use custom metadata to store additional information without modifying file content
|
|
646
|
-
6. **Directory Paths**: Always use trailing slashes for directory paths (e.g., `documents/` not `documents`)
|
|
647
|
-
7. **Initialize Providers**: Call `initialize()` on providers that require async setup
|
|
948
|
+
The package includes Jest-based unit tests for the base class behavior and the enterprise credential initialization flow.
|
|
648
949
|
|
|
649
|
-
|
|
950
|
+
```bash
|
|
951
|
+
# Run tests
|
|
952
|
+
npm test
|
|
650
953
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
- **List Operations**: Use appropriate prefixes and delimiters to limit results
|
|
654
|
-
- **Caching**: Utilize ETags and cache control headers when available
|
|
954
|
+
# Run tests in watch mode
|
|
955
|
+
npm run test:watch
|
|
655
956
|
|
|
656
|
-
|
|
957
|
+
# Run tests with coverage
|
|
958
|
+
npm run test:coverage
|
|
959
|
+
```
|
|
657
960
|
|
|
658
|
-
|
|
961
|
+
Test files are located at:
|
|
962
|
+
- `src/__tests__/FileStorageBase.test.ts` -- Tests for the `FileStorageBase` abstract class, `initialize()` method, and account information handling
|
|
963
|
+
- `src/__tests__/util.test.ts` -- Tests for `initializeDriverWithAccountCredentials` and the enterprise credential model
|
|
659
964
|
|
|
660
|
-
|
|
661
|
-
2. Create a feature branch (`git checkout -b feature/new-provider`)
|
|
662
|
-
3. Create your provider class in `src/drivers/`
|
|
663
|
-
4. Implement all required methods from `FileStorageBase`
|
|
664
|
-
5. Add comprehensive tests
|
|
665
|
-
6. Update documentation
|
|
666
|
-
7. Submit a pull request
|
|
965
|
+
## Build
|
|
667
966
|
|
|
668
|
-
|
|
967
|
+
```bash
|
|
968
|
+
# Build the package
|
|
969
|
+
npm run build
|
|
669
970
|
|
|
670
|
-
|
|
971
|
+
# Watch mode for development
|
|
972
|
+
npm run watch
|
|
973
|
+
```
|
|
671
974
|
|
|
672
|
-
|
|
975
|
+
The package uses TypeScript with `tsc` and `tsc-alias` for path alias resolution. Output is emitted to the `dist/` directory.
|
|
976
|
+
|
|
977
|
+
## Best Practices
|
|
673
978
|
|
|
674
|
-
|
|
979
|
+
1. **Always call `initialize()`**: After creating a provider instance, always call `initialize()` before using the driver, even if no configuration is needed.
|
|
980
|
+
2. **Use ProviderKey**: Always check for and use `ProviderKey` if returned by `CreatePreAuthUploadUrl` for subsequent operations.
|
|
981
|
+
3. **Use `objectId` when available**: The `GetObject` and `GetObjectMetadata` methods accept either `objectId` or `fullPath`. Using `objectId` bypasses path resolution and is significantly faster.
|
|
982
|
+
4. **Use enterprise credential model**: Prefer `initializeDriverWithAccountCredentials` over direct instantiation for multi-tenant applications.
|
|
983
|
+
5. **Handle unsupported operations**: Wrap `SearchFiles` calls in try/catch for `UnsupportedOperationError` since not all providers support search.
|
|
984
|
+
6. **Directory paths**: Always use trailing slashes for directory paths (e.g., `documents/` not `documents`).
|
|
985
|
+
7. **Content types**: Always specify content types for better browser handling and security.
|
|
986
|
+
8. **Buffer operations**: `GetObject` and `PutObject` load entire files into memory; consider pre-authenticated URLs for large files.
|
|
987
|
+
9. **Batch searches**: Use `searchAcrossProviders` or `searchAcrossAccounts` for parallel multi-source search instead of sequential calls.
|
|
988
|
+
|
|
989
|
+
## License
|
|
990
|
+
|
|
991
|
+
ISC
|