import * as plugins from './plugins.js'; import * as paths from './paths.js'; /** * Authentication configuration */ export interface IAuthConfig { enabled: boolean; credentials: Array<{ accessKeyId: string; secretAccessKey: string; }>; } /** * CORS configuration */ export interface ICorsConfig { enabled: boolean; allowedOrigins?: string[]; allowedMethods?: string[]; allowedHeaders?: string[]; exposedHeaders?: string[]; maxAge?: number; allowCredentials?: boolean; } /** * Logging configuration */ export interface ILoggingConfig { level?: 'error' | 'warn' | 'info' | 'debug'; format?: 'text' | 'json'; enabled?: boolean; } /** * Request limits configuration */ export interface ILimitsConfig { maxObjectSize?: number; maxMetadataSize?: number; requestTimeout?: number; } /** * Multipart upload configuration */ export interface IMultipartConfig { expirationDays?: number; cleanupIntervalMinutes?: number; } /** * Server configuration */ export interface IServerConfig { port?: number; address?: string; silent?: boolean; } /** * Storage configuration */ export interface IStorageConfig { directory?: string; cleanSlate?: boolean; } /** * Complete smarts3 configuration */ export interface ISmarts3Config { server?: IServerConfig; storage?: IStorageConfig; auth?: IAuthConfig; cors?: ICorsConfig; logging?: ILoggingConfig; limits?: ILimitsConfig; multipart?: IMultipartConfig; } /** * Default configuration values */ const DEFAULT_CONFIG: ISmarts3Config = { server: { port: 3000, address: '0.0.0.0', silent: false, }, storage: { directory: paths.bucketsDir, cleanSlate: false, }, auth: { enabled: false, credentials: [ { accessKeyId: 'S3RVER', secretAccessKey: 'S3RVER', }, ], }, cors: { enabled: false, allowedOrigins: ['*'], allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], allowedHeaders: ['*'], exposedHeaders: ['ETag', 'x-amz-request-id', 'x-amz-version-id'], maxAge: 86400, allowCredentials: false, }, logging: { level: 'info', format: 'text', enabled: true, }, limits: { maxObjectSize: 5 * 1024 * 1024 * 1024, // 5GB maxMetadataSize: 2048, requestTimeout: 300000, // 5 minutes }, multipart: { expirationDays: 7, cleanupIntervalMinutes: 60, }, }; /** * Merge user config with defaults (deep merge) */ function mergeConfig(userConfig: ISmarts3Config): Required { return { server: { ...DEFAULT_CONFIG.server!, ...(userConfig.server || {}), }, storage: { ...DEFAULT_CONFIG.storage!, ...(userConfig.storage || {}), }, auth: { ...DEFAULT_CONFIG.auth!, ...(userConfig.auth || {}), }, cors: { ...DEFAULT_CONFIG.cors!, ...(userConfig.cors || {}), }, logging: { ...DEFAULT_CONFIG.logging!, ...(userConfig.logging || {}), }, limits: { ...DEFAULT_CONFIG.limits!, ...(userConfig.limits || {}), }, multipart: { ...DEFAULT_CONFIG.multipart!, ...(userConfig.multipart || {}), }, }; } /** * IPC command type map for RustBridge */ type TRustS3Commands = { start: { params: { config: Required }; result: {} }; stop: { params: {}; result: {} }; createBucket: { params: { name: string }; result: {} }; }; /** * Main Smarts3 class - production-ready S3-compatible server */ export class Smarts3 { // STATIC public static async createAndStart(configArg: ISmarts3Config = {}) { const smartS3Instance = new Smarts3(configArg); await smartS3Instance.start(); return smartS3Instance; } // INSTANCE public config: Required; private bridge: InstanceType>; constructor(configArg: ISmarts3Config = {}) { this.config = mergeConfig(configArg); this.bridge = new plugins.RustBridge({ binaryName: 'rusts3', localPaths: [ plugins.path.join(paths.packageDir, 'dist_rust', 'rusts3'), plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'rusts3'), plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'rusts3'), ], readyTimeoutMs: 30000, requestTimeoutMs: 300000, }); } public async start() { const spawned = await this.bridge.spawn(); if (!spawned) { throw new Error('Failed to spawn rusts3 binary. Make sure it is compiled (pnpm build).'); } await this.bridge.sendCommand('start', { config: this.config }); if (!this.config.server.silent) { console.log('s3 server is running'); } } public async getS3Descriptor( optionsArg?: Partial, ): Promise { const cred = this.config.auth.credentials[0] || { accessKeyId: 'S3RVER', secretAccessKey: 'S3RVER', }; const descriptor: plugins.tsclass.storage.IS3Descriptor = { endpoint: this.config.server.address === '0.0.0.0' ? 'localhost' : this.config.server.address!, port: this.config.server.port!, useSsl: false, accessKey: cred.accessKeyId, accessSecret: cred.secretAccessKey, bucketName: '', }; return { ...descriptor, ...(optionsArg ? optionsArg : {}), }; } public async createBucket(bucketNameArg: string) { await this.bridge.sendCommand('createBucket', { name: bucketNameArg }); return { name: bucketNameArg }; } public async stop() { await this.bridge.sendCommand('stop', {}); this.bridge.kill(); } }