@memberjunction/storage 5.24.0 → 5.25.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.
@@ -0,0 +1,187 @@
1
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ import { BaseSingleton } from '@memberjunction/global';
3
+ import { FileStorageEngineBase, StorageAccountWithProvider, MJFileStorageAccountEntity, MJFileStorageProviderEntity } from '@memberjunction/core-entities';
4
+ import { FileStorageBase } from './generic/FileStorageBase.js';
5
+ /**
6
+ * Options for uploading a file to MJ Storage.
7
+ */
8
+ export interface UploadFileOptions {
9
+ /** Raw file content as a Buffer */
10
+ content: Buffer;
11
+ /** File name (used for the MJ: Files record and the storage path) */
12
+ fileName: string;
13
+ /** MIME type of the file (e.g., 'application/pdf') */
14
+ mimeType: string;
15
+ /** User context for DB operations and credential access */
16
+ contextUser: UserInfo;
17
+ /**
18
+ * Optional pre-resolved FileStorageAccount ID.
19
+ * When provided, the file is uploaded to this specific account.
20
+ * Otherwise, the first active account is used.
21
+ */
22
+ storageAccountId?: string;
23
+ /**
24
+ * Optional metadata provider. Defaults to `Metadata.Provider`.
25
+ */
26
+ provider?: IMetadataProvider;
27
+ /**
28
+ * Optional path prefix within the storage bucket.
29
+ * Defaults to `'artifacts/<date>/<uuid>'`.
30
+ */
31
+ pathPrefix?: string;
32
+ }
33
+ /**
34
+ * Result returned by {@link FileStorageEngine.UploadFile}.
35
+ */
36
+ export interface UploadFileResult {
37
+ /** The newly created MJ: Files record ID */
38
+ FileID: string;
39
+ /** The storage path (ProviderKey) where the file was stored */
40
+ StoragePath: string;
41
+ /** The storage account that was used */
42
+ Account: MJFileStorageAccountEntity;
43
+ /** The storage provider that was used */
44
+ Provider: MJFileStorageProviderEntity;
45
+ }
46
+ /**
47
+ * Server-side file storage engine providing high-level operations for uploading,
48
+ * downloading, and managing files in MJ Storage.
49
+ *
50
+ * Follows the containment pattern (like AIEngine wraps AIEngineBase):
51
+ * - Delegates all metadata access to {@link FileStorageEngineBase}
52
+ * - Adds server-side methods: {@link UploadFile}, {@link GetDriver}, {@link ResolveStorageAccount}
53
+ *
54
+ * **Client-side code** should use `FileStorageEngineBase` from `@memberjunction/core-entities`
55
+ * for metadata-only access (accounts, providers, lookups).
56
+ *
57
+ * Usage:
58
+ * ```typescript
59
+ * import { FileStorageEngine } from '@memberjunction/storage';
60
+ *
61
+ * const engine = FileStorageEngine.Instance;
62
+ * await engine.Config(false, contextUser);
63
+ *
64
+ * // Upload a file
65
+ * const result = await engine.UploadFile({
66
+ * content: Buffer.from(base64Data, 'base64'),
67
+ * fileName: 'report.pdf',
68
+ * mimeType: 'application/pdf',
69
+ * contextUser
70
+ * });
71
+ *
72
+ * // Get a driver for direct operations
73
+ * const driver = await engine.GetDriver(accountId, contextUser);
74
+ * const objects = await driver.ListObjects('/');
75
+ * ```
76
+ */
77
+ export declare class FileStorageEngine extends BaseSingleton<FileStorageEngine> {
78
+ private _loaded;
79
+ private _loading;
80
+ private _loadingPromise;
81
+ private _contextUser;
82
+ private _driverCache;
83
+ /**
84
+ * Returns the global singleton instance.
85
+ */
86
+ static get Instance(): FileStorageEngine;
87
+ /** Access to the underlying metadata-only engine. */
88
+ protected get Base(): FileStorageEngineBase;
89
+ /** Returns true if the engine has been configured. */
90
+ get Loaded(): boolean;
91
+ /** Gets all file storage accounts (cached). */
92
+ get Accounts(): MJFileStorageAccountEntity[];
93
+ /** Gets all file storage providers (cached). */
94
+ get Providers(): MJFileStorageProviderEntity[];
95
+ /** Gets all storage accounts combined with their provider details (cached). */
96
+ get AccountsWithProviders(): StorageAccountWithProvider[];
97
+ /** Whether any storage accounts are configured. */
98
+ get HasStorageAccounts(): boolean;
99
+ /** Gets a file storage account by its ID. */
100
+ GetAccountById(accountId: string): MJFileStorageAccountEntity | undefined;
101
+ /** Gets a file storage provider by its ID. */
102
+ GetProviderById(providerId: string): MJFileStorageProviderEntity | undefined;
103
+ /** Gets a file storage account by its name (case-insensitive). */
104
+ GetAccountByName(name: string): MJFileStorageAccountEntity | undefined;
105
+ /** Gets file storage accounts linked to a given provider ID. */
106
+ GetAccountsByProviderID(providerId: string): MJFileStorageAccountEntity[];
107
+ /** Gets a storage account with its provider details by account ID. */
108
+ GetAccountWithProvider(accountId: string): StorageAccountWithProvider | null;
109
+ /**
110
+ * Configures the engine by loading the underlying metadata cache and any
111
+ * server-specific state. Safe to call multiple times — uses cached data
112
+ * unless `forceRefresh` is true. Concurrent callers share a single loading
113
+ * promise to avoid redundant work.
114
+ */
115
+ Config(forceRefresh?: boolean, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<void>;
116
+ /**
117
+ * Internal loading logic — separated for clean promise management.
118
+ * First ensures the base metadata cache is loaded, then loads any
119
+ * server-specific state (extensible for future needs).
120
+ */
121
+ private innerLoad;
122
+ /**
123
+ * Initializes storage drivers for all active accounts and caches them.
124
+ * Called automatically during Config(). Can also be called independently to
125
+ * re-initialize drivers without reloading metadata (e.g., after credential rotation).
126
+ * Accounts that fail to initialize are logged and skipped — they will fall back to
127
+ * on-demand initialization when GetDriver() is called.
128
+ */
129
+ RefreshDriverCache(): Promise<void>;
130
+ /**
131
+ * Resolves a storage account to use for file operations.
132
+ *
133
+ * Resolution logic:
134
+ * 1. If `accountId` is provided, returns that specific account
135
+ * 2. Otherwise, returns the first active account
136
+ * 3. If no active accounts exist, returns the first account regardless of active status
137
+ *
138
+ * @param accountId - Optional explicit account ID
139
+ * @returns The resolved account with provider, or null if no accounts are configured
140
+ */
141
+ ResolveStorageAccount(accountId?: string): StorageAccountWithProvider | null;
142
+ /**
143
+ * Returns an authenticated storage driver for a given account.
144
+ *
145
+ * Checks the pre-initialized driver cache first (populated during Config()).
146
+ * If the account wasn't cached (e.g., it failed during Config or was added after),
147
+ * falls back to on-demand initialization.
148
+ *
149
+ * This handles:
150
+ * - Looking up the account and provider from cached metadata
151
+ * - Decrypting credentials via the Credential Engine
152
+ * - Setting up OAuth token refresh callbacks for providers like Box
153
+ *
154
+ * @param accountId - The FileStorageAccount ID to get a driver for
155
+ * @param contextUser - User context for credential decryption (used for on-demand init)
156
+ * @returns An initialized, ready-to-use FileStorageBase driver
157
+ * @throws Error if the account is not found or driver initialization fails
158
+ */
159
+ GetDriver(accountId: string, contextUser: UserInfo): Promise<FileStorageBase>;
160
+ /**
161
+ * Uploads a file to MJ Storage and creates an `MJ: Files` entity record.
162
+ *
163
+ * This is the primary high-level method for storing files. It handles:
164
+ * 1. Resolving which storage account to use
165
+ * 2. Initializing an authenticated driver
166
+ * 3. Uploading the file content
167
+ * 4. Creating the `MJ: Files` database record
168
+ *
169
+ * @param options - Upload options (content, fileName, mimeType, contextUser, etc.)
170
+ * @returns Upload result containing the file ID, storage path, and account/provider used
171
+ * @throws Error if no storage accounts are configured or the upload/save fails
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const result = await FileStorageEngine.Instance.UploadFile({
176
+ * content: Buffer.from(base64Data, 'base64'),
177
+ * fileName: 'report.pdf',
178
+ * mimeType: 'application/pdf',
179
+ * contextUser,
180
+ * storageAccountId: resolvedAccountId // optional
181
+ * });
182
+ * console.log('Created file:', result.FileID);
183
+ * ```
184
+ */
185
+ UploadFile(options: UploadFileOptions): Promise<UploadFileResult>;
186
+ }
187
+ //# sourceMappingURL=FileStorageEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileStorageEngine.d.ts","sourceRoot":"","sources":["../src/FileStorageEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,301 @@
1
+ import { LogError, Metadata } from '@memberjunction/core';
2
+ import { BaseSingleton } from '@memberjunction/global';
3
+ import { FileStorageEngineBase } from '@memberjunction/core-entities';
4
+ import { initializeDriverWithAccountCredentials } from './util.js';
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Engine
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ /**
9
+ * Server-side file storage engine providing high-level operations for uploading,
10
+ * downloading, and managing files in MJ Storage.
11
+ *
12
+ * Follows the containment pattern (like AIEngine wraps AIEngineBase):
13
+ * - Delegates all metadata access to {@link FileStorageEngineBase}
14
+ * - Adds server-side methods: {@link UploadFile}, {@link GetDriver}, {@link ResolveStorageAccount}
15
+ *
16
+ * **Client-side code** should use `FileStorageEngineBase` from `@memberjunction/core-entities`
17
+ * for metadata-only access (accounts, providers, lookups).
18
+ *
19
+ * Usage:
20
+ * ```typescript
21
+ * import { FileStorageEngine } from '@memberjunction/storage';
22
+ *
23
+ * const engine = FileStorageEngine.Instance;
24
+ * await engine.Config(false, contextUser);
25
+ *
26
+ * // Upload a file
27
+ * const result = await engine.UploadFile({
28
+ * content: Buffer.from(base64Data, 'base64'),
29
+ * fileName: 'report.pdf',
30
+ * mimeType: 'application/pdf',
31
+ * contextUser
32
+ * });
33
+ *
34
+ * // Get a driver for direct operations
35
+ * const driver = await engine.GetDriver(accountId, contextUser);
36
+ * const objects = await driver.ListObjects('/');
37
+ * ```
38
+ */
39
+ export class FileStorageEngine extends BaseSingleton {
40
+ constructor() {
41
+ super(...arguments);
42
+ // Loading state management (mirrors AIEngine pattern)
43
+ this._loaded = false;
44
+ this._loading = false;
45
+ this._loadingPromise = null;
46
+ // Server-specific state: cached, initialized drivers keyed by account ID
47
+ this._driverCache = new Map();
48
+ }
49
+ /**
50
+ * Returns the global singleton instance.
51
+ */
52
+ static get Instance() {
53
+ return super.getInstance();
54
+ }
55
+ // ─────────────────────────────────────────────────────────────────────
56
+ // Containment — delegate to FileStorageEngineBase
57
+ // ─────────────────────────────────────────────────────────────────────
58
+ /** Access to the underlying metadata-only engine. */
59
+ get Base() {
60
+ return FileStorageEngineBase.Instance;
61
+ }
62
+ /** Returns true if the engine has been configured. */
63
+ get Loaded() {
64
+ return this._loaded && this.Base.Loaded;
65
+ }
66
+ // --- Delegated metadata getters ---
67
+ /** Gets all file storage accounts (cached). */
68
+ get Accounts() {
69
+ return this.Base.Accounts;
70
+ }
71
+ /** Gets all file storage providers (cached). */
72
+ get Providers() {
73
+ return this.Base.Providers;
74
+ }
75
+ /** Gets all storage accounts combined with their provider details (cached). */
76
+ get AccountsWithProviders() {
77
+ return this.Base.AccountsWithProviders;
78
+ }
79
+ /** Whether any storage accounts are configured. */
80
+ get HasStorageAccounts() {
81
+ return this.Base.AccountsWithProviders.length > 0;
82
+ }
83
+ // --- Delegated lookup methods ---
84
+ /** Gets a file storage account by its ID. */
85
+ GetAccountById(accountId) {
86
+ return this.Base.GetAccountById(accountId);
87
+ }
88
+ /** Gets a file storage provider by its ID. */
89
+ GetProviderById(providerId) {
90
+ return this.Base.GetProviderById(providerId);
91
+ }
92
+ /** Gets a file storage account by its name (case-insensitive). */
93
+ GetAccountByName(name) {
94
+ return this.Base.GetAccountByName(name);
95
+ }
96
+ /** Gets file storage accounts linked to a given provider ID. */
97
+ GetAccountsByProviderID(providerId) {
98
+ return this.Base.GetAccountsByProviderID(providerId);
99
+ }
100
+ /** Gets a storage account with its provider details by account ID. */
101
+ GetAccountWithProvider(accountId) {
102
+ return this.Base.GetAccountWithProvider(accountId);
103
+ }
104
+ // ─────────────────────────────────────────────────────────────────────
105
+ // Configuration
106
+ // ─────────────────────────────────────────────────────────────────────
107
+ /**
108
+ * Configures the engine by loading the underlying metadata cache and any
109
+ * server-specific state. Safe to call multiple times — uses cached data
110
+ * unless `forceRefresh` is true. Concurrent callers share a single loading
111
+ * promise to avoid redundant work.
112
+ */
113
+ async Config(forceRefresh, contextUser, provider) {
114
+ if (this._loaded && !forceRefresh) {
115
+ return;
116
+ }
117
+ // If currently loading, return the existing promise so all callers wait together
118
+ if (this._loading && this._loadingPromise) {
119
+ return this._loadingPromise;
120
+ }
121
+ this._loading = true;
122
+ this._loadingPromise = this.innerLoad(forceRefresh, contextUser, provider);
123
+ try {
124
+ await this._loadingPromise;
125
+ }
126
+ finally {
127
+ this._loading = false;
128
+ this._loadingPromise = null;
129
+ }
130
+ }
131
+ /**
132
+ * Internal loading logic — separated for clean promise management.
133
+ * First ensures the base metadata cache is loaded, then loads any
134
+ * server-specific state (extensible for future needs).
135
+ */
136
+ async innerLoad(forceRefresh, contextUser, provider) {
137
+ try {
138
+ this._contextUser = contextUser;
139
+ // Load base metadata (accounts, providers)
140
+ await this.Base.Config(forceRefresh ?? false, contextUser, provider);
141
+ // Initialize drivers for all active accounts and cache them
142
+ await this.RefreshDriverCache();
143
+ this._loaded = true;
144
+ }
145
+ catch (error) {
146
+ LogError(error);
147
+ throw error;
148
+ }
149
+ }
150
+ /**
151
+ * Initializes storage drivers for all active accounts and caches them.
152
+ * Called automatically during Config(). Can also be called independently to
153
+ * re-initialize drivers without reloading metadata (e.g., after credential rotation).
154
+ * Accounts that fail to initialize are logged and skipped — they will fall back to
155
+ * on-demand initialization when GetDriver() is called.
156
+ */
157
+ async RefreshDriverCache() {
158
+ this._driverCache.clear();
159
+ const activeAccounts = this.Base.AccountsWithProviders.filter(a => a.provider.IsActive !== false);
160
+ if (activeAccounts.length === 0 || !this._contextUser) {
161
+ return;
162
+ }
163
+ const contextUser = this._contextUser;
164
+ const results = await Promise.allSettled(activeAccounts.map(async ({ account, provider: storageProvider }) => {
165
+ const driver = await initializeDriverWithAccountCredentials({
166
+ accountEntity: account,
167
+ providerEntity: storageProvider,
168
+ contextUser
169
+ });
170
+ this._driverCache.set(account.ID, driver);
171
+ }));
172
+ // Log failures but don't throw — failed accounts fall back to on-demand init
173
+ for (let i = 0; i < results.length; i++) {
174
+ if (results[i].status === 'rejected') {
175
+ const accountName = activeAccounts[i].account.Name;
176
+ const reason = results[i].reason;
177
+ LogError(`FileStorageEngine: failed to pre-initialize driver for account "${accountName}": ${reason}`);
178
+ }
179
+ }
180
+ }
181
+ // ─────────────────────────────────────────────────────────────────────
182
+ // Server-side operations
183
+ // ─────────────────────────────────────────────────────────────────────
184
+ /**
185
+ * Resolves a storage account to use for file operations.
186
+ *
187
+ * Resolution logic:
188
+ * 1. If `accountId` is provided, returns that specific account
189
+ * 2. Otherwise, returns the first active account
190
+ * 3. If no active accounts exist, returns the first account regardless of active status
191
+ *
192
+ * @param accountId - Optional explicit account ID
193
+ * @returns The resolved account with provider, or null if no accounts are configured
194
+ */
195
+ ResolveStorageAccount(accountId) {
196
+ if (accountId) {
197
+ return this.Base.GetAccountWithProvider(accountId);
198
+ }
199
+ const accounts = this.Base.AccountsWithProviders;
200
+ if (accounts.length === 0)
201
+ return null;
202
+ return accounts.find(a => a.provider.IsActive !== false) ?? accounts[0];
203
+ }
204
+ /**
205
+ * Returns an authenticated storage driver for a given account.
206
+ *
207
+ * Checks the pre-initialized driver cache first (populated during Config()).
208
+ * If the account wasn't cached (e.g., it failed during Config or was added after),
209
+ * falls back to on-demand initialization.
210
+ *
211
+ * This handles:
212
+ * - Looking up the account and provider from cached metadata
213
+ * - Decrypting credentials via the Credential Engine
214
+ * - Setting up OAuth token refresh callbacks for providers like Box
215
+ *
216
+ * @param accountId - The FileStorageAccount ID to get a driver for
217
+ * @param contextUser - User context for credential decryption (used for on-demand init)
218
+ * @returns An initialized, ready-to-use FileStorageBase driver
219
+ * @throws Error if the account is not found or driver initialization fails
220
+ */
221
+ async GetDriver(accountId, contextUser) {
222
+ // Check pre-initialized cache first
223
+ const cached = this._driverCache.get(accountId);
224
+ if (cached) {
225
+ return cached;
226
+ }
227
+ // On-demand fallback: account wasn't in cache (failed init, added late, etc.)
228
+ const resolved = this.Base.GetAccountWithProvider(accountId);
229
+ if (!resolved) {
230
+ throw new Error(`FileStorageEngine.GetDriver: account '${accountId}' not found in cached metadata. Did you call Config() first?`);
231
+ }
232
+ const driver = await initializeDriverWithAccountCredentials({
233
+ accountEntity: resolved.account,
234
+ providerEntity: resolved.provider,
235
+ contextUser
236
+ });
237
+ // Cache it for future calls
238
+ this._driverCache.set(accountId, driver);
239
+ return driver;
240
+ }
241
+ /**
242
+ * Uploads a file to MJ Storage and creates an `MJ: Files` entity record.
243
+ *
244
+ * This is the primary high-level method for storing files. It handles:
245
+ * 1. Resolving which storage account to use
246
+ * 2. Initializing an authenticated driver
247
+ * 3. Uploading the file content
248
+ * 4. Creating the `MJ: Files` database record
249
+ *
250
+ * @param options - Upload options (content, fileName, mimeType, contextUser, etc.)
251
+ * @returns Upload result containing the file ID, storage path, and account/provider used
252
+ * @throws Error if no storage accounts are configured or the upload/save fails
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const result = await FileStorageEngine.Instance.UploadFile({
257
+ * content: Buffer.from(base64Data, 'base64'),
258
+ * fileName: 'report.pdf',
259
+ * mimeType: 'application/pdf',
260
+ * contextUser,
261
+ * storageAccountId: resolvedAccountId // optional
262
+ * });
263
+ * console.log('Created file:', result.FileID);
264
+ * ```
265
+ */
266
+ async UploadFile(options) {
267
+ const { content, fileName, mimeType, contextUser, storageAccountId, provider } = options;
268
+ const md = provider ?? Metadata.Provider;
269
+ // 1. Resolve storage account
270
+ const resolved = this.ResolveStorageAccount(storageAccountId);
271
+ if (!resolved) {
272
+ throw new Error('FileStorageEngine.UploadFile: no file storage accounts configured. Cannot upload.');
273
+ }
274
+ // 2. Initialize driver
275
+ const driver = await this.GetDriver(resolved.account.ID, contextUser);
276
+ // 3. Upload
277
+ const pathPrefix = options.pathPrefix ?? `artifacts/${new Date().toISOString().slice(0, 10)}/${crypto.randomUUID()}`;
278
+ const storagePath = `${pathPrefix}/${fileName}`;
279
+ const uploaded = await driver.PutObject(storagePath, content, mimeType);
280
+ if (!uploaded) {
281
+ throw new Error(`FileStorageEngine.UploadFile: PutObject returned false for path '${storagePath}'`);
282
+ }
283
+ // 4. Create MJ: Files record
284
+ const fileEntity = await md.GetEntityObject('MJ: Files', contextUser);
285
+ fileEntity.Name = fileName;
286
+ fileEntity.ContentType = mimeType;
287
+ fileEntity.ProviderID = resolved.provider.ID;
288
+ fileEntity.ProviderKey = storagePath;
289
+ fileEntity.Status = 'Uploaded';
290
+ if (!(await fileEntity.Save())) {
291
+ throw new Error(`FileStorageEngine.UploadFile: failed to save MJ: Files record for '${fileName}'`);
292
+ }
293
+ return {
294
+ FileID: fileEntity.ID,
295
+ StoragePath: storagePath,
296
+ Account: resolved.account,
297
+ Provider: resolved.provider
298
+ };
299
+ }
300
+ }
301
+ //# sourceMappingURL=FileStorageEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileStorageEngine.js","sourceRoot":"","sources":["../src/FileStorageEngine.ts"],"names":[],"mappings":"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"}