import * as plugins from './plugins.js'; import * as paths from './paths.js'; /** * Authentication configuration */ export interface IStorageCredential { accessKeyId: string; secretAccessKey: string; } /** * Authentication configuration */ export interface IAuthConfig { enabled: boolean; credentials: IStorageCredential[]; } /** * 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; region?: string; } /** * Storage configuration */ export interface IStorageConfig { directory?: string; cleanSlate?: boolean; } /** * Erasure coding configuration */ export interface IErasureConfig { dataShards?: number; parityShards?: number; chunkSizeBytes?: number; } /** * Drive configuration for multi-drive support */ export interface IDriveConfig { paths: string[]; } /** * Cluster configuration for distributed mode */ export interface IClusterConfig { enabled: boolean; nodeId?: string; quicPort?: number; seedNodes?: string[]; erasure?: IErasureConfig; drives?: IDriveConfig; heartbeatIntervalMs?: number; heartbeatTimeoutMs?: number; } /** * Complete smartstorage configuration */ export interface ISmartStorageConfig { server?: IServerConfig; storage?: IStorageConfig; auth?: IAuthConfig; cors?: ICorsConfig; logging?: ILoggingConfig; limits?: ILimitsConfig; multipart?: IMultipartConfig; cluster?: IClusterConfig; } /** * Logical bucket stats maintained by the Rust runtime. * Values are initialized from native storage on startup and updated on smartstorage mutations. */ export interface IBucketSummary { name: string; objectCount: number; totalSizeBytes: number; creationDate?: number; } /** * Filesystem-level capacity snapshot for the storage directory or configured drive path. */ export interface IStorageLocationSummary { path: string; totalBytes?: number; availableBytes?: number; usedBytes?: number; } /** * Runtime storage stats served by the Rust core without issuing S3 list calls. */ export interface IStorageStats { bucketCount: number; totalObjectCount: number; totalStorageBytes: number; buckets: IBucketSummary[]; storageDirectory: string; storageLocations?: IStorageLocationSummary[]; } /** * Known peer status from the local node's current cluster view. */ export interface IClusterPeerHealth { nodeId: string; status: 'online' | 'suspect' | 'offline'; quicAddress?: string; s3Address?: string; driveCount?: number; lastHeartbeat?: number; missedHeartbeats?: number; } /** * Local drive health as measured by smartstorage's runtime probes. */ export interface IClusterDriveHealth { index: number; path: string; status: 'online' | 'degraded' | 'offline' | 'healing'; totalBytes?: number; usedBytes?: number; availableBytes?: number; errorCount?: number; lastError?: string; lastCheck?: number; erasureSetId?: number; } export interface IClusterErasureHealth { dataShards: number; parityShards: number; chunkSizeBytes: number; totalShards: number; readQuorum: number; writeQuorum: number; erasureSetCount: number; } export interface IClusterRepairHealth { active: boolean; scanIntervalMs?: number; lastRunStartedAt?: number; lastRunCompletedAt?: number; lastDurationMs?: number; shardsChecked?: number; shardsHealed?: number; failed?: number; lastError?: string; } /** * Cluster runtime health from the Rust core. * When clustering is disabled, the response is `{ enabled: false }`. */ export interface IClusterHealth { enabled: boolean; nodeId?: string; quorumHealthy?: boolean; majorityHealthy?: boolean; peers?: IClusterPeerHealth[]; drives?: IClusterDriveHealth[]; erasure?: IClusterErasureHealth; repairs?: IClusterRepairHealth; } /** * Default configuration values */ const DEFAULT_CONFIG: ISmartStorageConfig = { server: { port: 3000, address: '0.0.0.0', silent: false, region: 'us-east-1', }, storage: { directory: paths.bucketsDir, cleanSlate: false, }, auth: { enabled: false, credentials: [ { accessKeyId: 'STORAGE', secretAccessKey: 'STORAGE', }, ], }, 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: ISmartStorageConfig): 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 || {}), }, ...(userConfig.cluster ? { cluster: userConfig.cluster } : {}), } as Required; } /** * IPC command type map for RustBridge */ type TRustStorageCommands = { start: { params: { config: Required }; result: {} }; stop: { params: {}; result: {} }; createBucket: { params: { name: string }; result: {} }; getStorageStats: { params: {}; result: IStorageStats }; listBucketSummaries: { params: {}; result: IBucketSummary[] }; listCredentials: { params: {}; result: IStorageCredential[] }; replaceCredentials: { params: { credentials: IStorageCredential[] }; result: {} }; getClusterHealth: { params: {}; result: IClusterHealth }; }; /** * Main SmartStorage class - production-ready S3-compatible storage server */ export class SmartStorage { // STATIC public static async createAndStart(configArg: ISmartStorageConfig = {}) { const smartStorageInstance = new SmartStorage(configArg); await smartStorageInstance.start(); return smartStorageInstance; } // INSTANCE public config: Required; private bridge: InstanceType>; constructor(configArg: ISmartStorageConfig = {}) { this.config = mergeConfig(configArg); this.bridge = new plugins.RustBridge({ binaryName: 'ruststorage', localPaths: [ plugins.path.join(paths.packageDir, 'dist_rust', 'ruststorage'), ], readyTimeoutMs: 30000, requestTimeoutMs: 300000, }); } public async start() { const spawned = await this.bridge.spawn(); if (!spawned) { throw new Error('Failed to spawn ruststorage binary. Make sure it is compiled (pnpm build).'); } await this.bridge.sendCommand('start', { config: this.config }); if (!this.config.server.silent) { console.log('storage server is running'); } } public async getStorageDescriptor( optionsArg?: Partial, ): Promise { const cred = this.config.auth.credentials[0] || { accessKeyId: 'STORAGE', secretAccessKey: 'STORAGE', }; 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 getStorageStats(): Promise { return this.bridge.sendCommand('getStorageStats', {}); } public async listBucketSummaries(): Promise { return this.bridge.sendCommand('listBucketSummaries', {}); } public async listCredentials(): Promise { return this.bridge.sendCommand('listCredentials', {}); } public async replaceCredentials(credentials: IStorageCredential[]): Promise { await this.bridge.sendCommand('replaceCredentials', { credentials }); this.config.auth.credentials = credentials.map((credential) => ({ ...credential })); } public async getClusterHealth(): Promise { return this.bridge.sendCommand('getClusterHealth', {}); } public async stop() { await this.bridge.sendCommand('stop', {}); this.bridge.kill(); } }