@firesystem/s3
Version:
AWS S3 implementation of Virtual File System
161 lines (142 loc) • 5.19 kB
text/typescript
import type {
IReactiveFileSystem,
IFileSystemCapabilities,
} from "@firesystem/core";
// Import type-only para evitar dependência em runtime
import type { SourceProvider } from "@workspace-fs/core";
import { S3FileSystem } from "../S3FileSystem";
import type { S3FileSystemConfig } from "../types";
export class S3WorkspaceProvider implements SourceProvider {
readonly scheme = "s3";
readonly displayName = "AWS S3 Storage";
async createFileSystem(config: any): Promise<IReactiveFileSystem> {
// Validar configuração primeiro
const validation = await this.validateConfiguration(config);
if (!validation.valid) {
throw new Error(
`Invalid S3 configuration: ${validation.errors?.join(", ")}`,
);
}
// Configuração padrão se não fornecida
const fsConfig: S3FileSystemConfig = {
bucket: config.bucket,
region: config.region,
prefix: config.prefix || "/",
mode: config.mode || "strict",
credentials: config.credentials,
clientOptions: config.clientOptions,
};
const fs = new S3FileSystem(fsConfig);
// S3FileSystem tem método initialize se precisar conectar
if ("initialize" in fs && typeof fs.initialize === "function") {
await fs.initialize();
}
return fs;
}
getCapabilities(): IFileSystemCapabilities {
return {
readonly: false,
caseSensitive: true,
atomicRename: false, // S3 doesn't support atomic rename
supportsWatch: false, // S3 doesn't support real-time watch
supportsMetadata: true,
supportsGlob: false, // S3 não tem suporte nativo para glob (é simulado via list + filter)
maxFileSize: 5 * 1024 * 1024 * 1024 * 1024, // 5TB with multipart upload
maxPathLength: 1024,
description:
"AWS S3 cloud storage with eventual consistency. Best for large files, backups, and static content.",
};
}
async validateConfiguration(
config: any,
): Promise<{ valid: boolean; errors?: string[] }> {
const errors: string[] = [];
if (!config) {
errors.push("Configuration is required");
return { valid: false, errors };
}
// Validar bucket (obrigatório)
if (config.bucket === undefined || config.bucket === null) {
errors.push("bucket is required");
} else if (typeof config.bucket !== "string") {
errors.push("bucket must be a string");
} else if (config.bucket.trim() === "") {
errors.push("bucket cannot be empty");
} else if (!/^[a-z0-9.-]+$/.test(config.bucket)) {
errors.push(
"bucket name must contain only lowercase letters, numbers, dots, and hyphens",
);
}
// Validar region (obrigatório)
if (config.region === undefined || config.region === null) {
errors.push("region is required");
} else if (typeof config.region !== "string") {
errors.push("region must be a string");
} else if (config.region.trim() === "") {
errors.push("region cannot be empty");
}
// Validar prefix (opcional)
if (config.prefix !== undefined) {
if (typeof config.prefix !== "string") {
errors.push("prefix must be a string");
} else if (config.prefix.length > 1000) {
errors.push("prefix is too long (max 1000 characters)");
}
}
// Validar mode (opcional)
if (config.mode !== undefined) {
if (config.mode !== "strict" && config.mode !== "lenient") {
errors.push("mode must be 'strict' or 'lenient'");
}
}
// Validar credentials (opcional)
if (config.credentials !== undefined) {
if (typeof config.credentials !== "object") {
errors.push("credentials must be an object");
} else {
if (
!config.credentials.accessKeyId ||
typeof config.credentials.accessKeyId !== "string"
) {
errors.push("credentials.accessKeyId must be a string");
}
if (
!config.credentials.secretAccessKey ||
typeof config.credentials.secretAccessKey !== "string"
) {
errors.push("credentials.secretAccessKey must be a string");
}
}
}
// Validar clientOptions (opcional)
if (config.clientOptions !== undefined) {
if (typeof config.clientOptions !== "object") {
errors.push("clientOptions must be an object");
} else {
if (
config.clientOptions.endpoint !== undefined &&
typeof config.clientOptions.endpoint !== "string"
) {
errors.push("clientOptions.endpoint must be a string");
}
if (
config.clientOptions.forcePathStyle !== undefined &&
typeof config.clientOptions.forcePathStyle !== "boolean"
) {
errors.push("clientOptions.forcePathStyle must be a boolean");
}
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
};
}
async dispose(fs: IReactiveFileSystem): Promise<void> {
if ("dispose" in fs && typeof fs.dispose === "function") {
await fs.dispose();
}
}
}
// Export singleton instance
export const s3Provider = new S3WorkspaceProvider();