**feat: improve environment detection and storage logic in opfsStorage.ts**

### Changes:
- Enhanced environment detection logic with a structured `environment` object (`isBrowser`, `isNode`, `isServerless`).
- Implemented fallback options for various environments:
  - Added priority handling for storage types (e.g., MemoryStorage, OPFSStorage, FileSystemStorage).
  - Improved dynamic imports for `s3CompatibleStorage.js` and `fileSystemStorage.js`.
- Unified and expanded cloud storage support by merging environment variables with provided options:
  - Refined logic for R2, S3, and GCS storage configurations.
  - Added detailed logging during storage type selection (`console.log` for selected storage).
- Introduced better error handling with warnings and defaults for unsupported environments.
- Removed redundant checks and reorganized storage selection logic for clarity.

### Purpose:
Streamlined environment detection and adaptive storage logic to ensure seamless operation across different platforms (browser, Node.js, serverless). These changes improve maintainability, enhance debugging, and simplify storage configuration while providing robust fallback mechanisms.
This commit is contained in:
David Snelling 2025-06-20 10:57:18 -07:00
parent 2433acb1f1
commit 830322fef9

View file

@ -1660,13 +1660,27 @@ export async function createStorage(
forceMemoryStorage?: boolean forceMemoryStorage?: boolean
} = {} } = {}
): Promise<StorageAdapter> { ): Promise<StorageAdapter> {
// Check for environment variables for cloud storage (only in Node.js environment) // Detect environment
const isNode = const environment = {
typeof process !== 'undefined' && isBrowser: typeof window !== 'undefined',
process.versions != null && isNode:
process.versions.node != null typeof process !== 'undefined' &&
process.versions != null &&
process.versions.node != null,
isServerless:
typeof window === 'undefined' &&
(typeof process === 'undefined' ||
!process.versions ||
!process.versions.node)
}
// Default empty values // If force memory storage is specified, use MemoryStorage regardless of environment
if (options.forceMemoryStorage) {
console.log('Using in-memory storage (forced by configuration)')
return new MemoryStorage()
}
// Default empty values for environment variables
const defaultEnvStorage = { const defaultEnvStorage = {
bucketName: undefined, bucketName: undefined,
accountId: undefined, accountId: undefined,
@ -1676,82 +1690,87 @@ export async function createStorage(
endpoint: undefined endpoint: undefined
} }
// Only try to access process.env in Node.js environment // Try to use cloud storage if configured
const envR2Storage = isNode
? {
bucketName: process.env.R2_BUCKET_NAME,
accountId: process.env.R2_ACCOUNT_ID,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
}
: defaultEnvStorage
const envS3Storage = isNode
? {
bucketName: process.env.S3_BUCKET_NAME,
accessKeyId:
process.env.S3_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID,
secretAccessKey:
process.env.S3_SECRET_ACCESS_KEY || process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.S3_REGION || process.env.AWS_REGION
}
: defaultEnvStorage
const envGCSStorage = isNode
? {
bucketName: process.env.GCS_BUCKET_NAME,
accessKeyId: process.env.GCS_ACCESS_KEY_ID,
secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY,
endpoint: process.env.GCS_ENDPOINT
}
: defaultEnvStorage
// Merge environment variables with provided options
const mergedOptions = {
...options,
r2Storage: options.r2Storage
? {
...envR2Storage,
...options.r2Storage
}
: // Only use environment variables if they have the required fields
envR2Storage.bucketName &&
envR2Storage.accessKeyId &&
envR2Storage.secretAccessKey
? envR2Storage
: undefined,
s3Storage: options.s3Storage
? {
...envS3Storage,
...options.s3Storage
}
: // Only use environment variables if they have the required fields
envS3Storage.bucketName &&
envS3Storage.accessKeyId &&
envS3Storage.secretAccessKey
? envS3Storage
: undefined,
gcsStorage: options.gcsStorage
? {
...envGCSStorage,
...options.gcsStorage
}
: // Only use environment variables if they have the required fields
envGCSStorage.bucketName &&
envGCSStorage.accessKeyId &&
envGCSStorage.secretAccessKey
? envGCSStorage
: undefined
}
// If any S3-compatible storage options are provided, use S3CompatibleStorage
if ( if (
mergedOptions.r2Storage || options.r2Storage ||
mergedOptions.s3Storage || options.s3Storage ||
mergedOptions.gcsStorage || options.gcsStorage ||
mergedOptions.customS3Storage options.customS3Storage ||
(environment.isNode &&
(process.env.R2_BUCKET_NAME ||
process.env.S3_BUCKET_NAME ||
process.env.GCS_BUCKET_NAME))
) { ) {
try { try {
// Only try to access process.env in Node.js environment
const envR2Storage = environment.isNode
? {
bucketName: process.env.R2_BUCKET_NAME,
accountId: process.env.R2_ACCOUNT_ID,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
}
: defaultEnvStorage
const envS3Storage = environment.isNode
? {
bucketName: process.env.S3_BUCKET_NAME,
accessKeyId:
process.env.S3_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID,
secretAccessKey:
process.env.S3_SECRET_ACCESS_KEY ||
process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.S3_REGION || process.env.AWS_REGION
}
: defaultEnvStorage
const envGCSStorage = environment.isNode
? {
bucketName: process.env.GCS_BUCKET_NAME,
accessKeyId: process.env.GCS_ACCESS_KEY_ID,
secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY,
endpoint: process.env.GCS_ENDPOINT
}
: defaultEnvStorage
// Merge environment variables with provided options
const mergedOptions = {
...options,
r2Storage: options.r2Storage
? {
...envR2Storage,
...options.r2Storage
}
: // Only use environment variables if they have the required fields
envR2Storage.bucketName &&
envR2Storage.accessKeyId &&
envR2Storage.secretAccessKey
? envR2Storage
: undefined,
s3Storage: options.s3Storage
? {
...envS3Storage,
...options.s3Storage
}
: // Only use environment variables if they have the required fields
envS3Storage.bucketName &&
envS3Storage.accessKeyId &&
envS3Storage.secretAccessKey
? envS3Storage
: undefined,
gcsStorage: options.gcsStorage
? {
...envGCSStorage,
...options.gcsStorage
}
: // Only use environment variables if they have the required fields
envGCSStorage.bucketName &&
envGCSStorage.accessKeyId &&
envGCSStorage.secretAccessKey
? envGCSStorage
: undefined
}
const s3Module = await import('./s3CompatibleStorage.js') const s3Module = await import('./s3CompatibleStorage.js')
if ( if (
@ -1761,6 +1780,7 @@ export async function createStorage(
mergedOptions.r2Storage.accessKeyId && mergedOptions.r2Storage.accessKeyId &&
mergedOptions.r2Storage.secretAccessKey mergedOptions.r2Storage.secretAccessKey
) { ) {
console.log('Using Cloudflare R2 storage')
return new s3Module.R2Storage({ return new s3Module.R2Storage({
bucketName: mergedOptions.r2Storage.bucketName, bucketName: mergedOptions.r2Storage.bucketName,
accountId: mergedOptions.r2Storage.accountId, accountId: mergedOptions.r2Storage.accountId,
@ -1773,6 +1793,7 @@ export async function createStorage(
mergedOptions.s3Storage.accessKeyId && mergedOptions.s3Storage.accessKeyId &&
mergedOptions.s3Storage.secretAccessKey mergedOptions.s3Storage.secretAccessKey
) { ) {
console.log('Using Amazon S3 storage')
return new s3Module.S3CompatibleStorage({ return new s3Module.S3CompatibleStorage({
bucketName: mergedOptions.s3Storage.bucketName, bucketName: mergedOptions.s3Storage.bucketName,
accessKeyId: mergedOptions.s3Storage.accessKeyId, accessKeyId: mergedOptions.s3Storage.accessKeyId,
@ -1786,6 +1807,7 @@ export async function createStorage(
mergedOptions.gcsStorage.accessKeyId && mergedOptions.gcsStorage.accessKeyId &&
mergedOptions.gcsStorage.secretAccessKey mergedOptions.gcsStorage.secretAccessKey
) { ) {
console.log('Using Google Cloud Storage')
return new s3Module.S3CompatibleStorage({ return new s3Module.S3CompatibleStorage({
bucketName: mergedOptions.gcsStorage.bucketName, bucketName: mergedOptions.gcsStorage.bucketName,
accessKeyId: mergedOptions.gcsStorage.accessKeyId, accessKeyId: mergedOptions.gcsStorage.accessKeyId,
@ -1799,6 +1821,7 @@ export async function createStorage(
mergedOptions.customS3Storage.accessKeyId && mergedOptions.customS3Storage.accessKeyId &&
mergedOptions.customS3Storage.secretAccessKey mergedOptions.customS3Storage.secretAccessKey
) { ) {
console.log('Using custom S3-compatible storage')
return new s3Module.S3CompatibleStorage({ return new s3Module.S3CompatibleStorage({
bucketName: mergedOptions.customS3Storage.bucketName, bucketName: mergedOptions.customS3Storage.bucketName,
accessKeyId: mergedOptions.customS3Storage.accessKeyId, accessKeyId: mergedOptions.customS3Storage.accessKeyId,
@ -1810,63 +1833,57 @@ export async function createStorage(
} }
} catch (error) { } catch (error) {
console.warn( console.warn(
'Failed to load S3CompatibleStorage, falling back to default storage:', 'Failed to load S3CompatibleStorage, falling back to environment-specific storage:',
error error
) )
// Continue to default storage selection // Continue to environment-specific storage selection
} }
} }
// If force memory storage is specified, use MemoryStorage // Environment-specific storage selection
if (options.forceMemoryStorage) { if (environment.isBrowser) {
return new MemoryStorage() // In browser environments
}
// Reuse the isNode variable from above
if (isNode) {
// In Node.js, use FileSystemStorage first, then fall back to memory
try {
const fileSystemModule = await import('./fileSystemStorage.js')
return new fileSystemModule.FileSystemStorage()
} catch (error) {
console.warn(
'Failed to load FileSystemStorage, falling back to in-memory storage:',
error
)
return new MemoryStorage()
}
} else {
// In browser, try OPFS first (unless force FileSystem is specified)
if (!options.forceFileSystemStorage) { if (!options.forceFileSystemStorage) {
const opfsStorage = new OPFSStorage() try {
// Try OPFS first (Origin Private File System - browser-specific)
const opfsStorage = new OPFSStorage()
if (opfsStorage.isOPFSAvailable()) { if (opfsStorage.isOPFSAvailable()) {
// Request persistent storage if specified // Request persistent storage if specified
if (options.requestPersistentStorage) { if (options.requestPersistentStorage) {
const isPersistentGranted = const isPersistentGranted =
await opfsStorage.requestPersistentStorage() await opfsStorage.requestPersistentStorage()
if (isPersistentGranted) { if (isPersistentGranted) {
console.log('Persistent storage permission granted') console.log('Persistent storage permission granted')
} else { } else {
console.warn('Persistent storage permission denied') console.warn('Persistent storage permission denied')
}
} }
console.log('Using Origin Private File System (OPFS) storage')
return opfsStorage
} }
return opfsStorage } catch (error) {
console.warn('OPFS storage initialization failed:', error)
} }
} }
// OPFS is not available or force FileSystem is specified, try to use FileSystem API if available // Fall back to memory storage for browser environments
console.log('Using in-memory storage for browser environment')
return new MemoryStorage()
} else if (environment.isNode) {
// In Node.js environments
try { try {
// Try to load FileSystemStorage for browser environments
// Note: This will likely fail as FileSystemStorage is designed for Node.js
const fileSystemModule = await import('./fileSystemStorage.js') const fileSystemModule = await import('./fileSystemStorage.js')
console.log('Using file system storage for Node.js environment')
return new fileSystemModule.FileSystemStorage() return new fileSystemModule.FileSystemStorage()
} catch (error) { } catch (error) {
console.warn( console.warn('Failed to load FileSystemStorage:', error)
'FileSystem storage is not available, falling back to in-memory storage' console.log('Using in-memory storage for Node.js environment')
)
return new MemoryStorage() return new MemoryStorage()
} }
} else {
// In serverless or other environments
console.log('Using in-memory storage for serverless/unknown environment')
return new MemoryStorage()
} }
} }