import * as plugins from '../plugins.js'; import { IStorageConfig, IStorageBackend } from './interfaces.core.js'; /** * Storage abstraction layer for registry * Provides a unified interface over SmartBucket */ export class RegistryStorage implements IStorageBackend { private smartBucket: plugins.smartbucket.SmartBucket; private bucket: plugins.smartbucket.Bucket; private bucketName: string; constructor(private config: IStorageConfig) { this.bucketName = config.bucketName; } /** * Initialize the storage backend */ public async init(): Promise { this.smartBucket = new plugins.smartbucket.SmartBucket({ accessKey: this.config.accessKey, accessSecret: this.config.accessSecret, endpoint: this.config.endpoint, port: this.config.port || 443, useSsl: this.config.useSsl !== false, region: this.config.region || 'us-east-1', }); // Ensure bucket exists await this.smartBucket.createBucket(this.bucketName).catch(() => { // Bucket may already exist }); this.bucket = await this.smartBucket.getBucketByName(this.bucketName); } /** * Get an object from storage */ public async getObject(key: string): Promise { try { return await this.bucket.fastGet({ path: key }); } catch (error) { return null; } } /** * Store an object */ public async putObject( key: string, data: Buffer, metadata?: Record ): Promise { await this.bucket.fastPut({ path: key, contents: data, meta: metadata, }); } /** * Delete an object */ public async deleteObject(key: string): Promise { await this.bucket.fastRemove({ path: key }); } /** * List objects with a prefix */ public async listObjects(prefix: string): Promise { const baseDir = await this.bucket.getBaseDirectory(); const dir = prefix ? await baseDir.getSubDirectoryByName(prefix) : baseDir; if (!dir) return []; const files = await dir.listFiles(); return files.map(f => f.path); } /** * Check if an object exists */ public async objectExists(key: string): Promise { return await this.bucket.fastExists({ path: key }); } /** * Get object metadata * Note: SmartBucket may not support metadata retrieval, returning empty object */ public async getMetadata(key: string): Promise | null> { // SmartBucket doesn't expose metadata retrieval directly // This is a limitation we'll document const exists = await this.objectExists(key); return exists ? {} : null; } // ======================================================================== // OCI-SPECIFIC HELPERS // ======================================================================== /** * Get OCI blob by digest */ public async getOciBlob(digest: string): Promise { const path = this.getOciBlobPath(digest); return this.getObject(path); } /** * Store OCI blob */ public async putOciBlob(digest: string, data: Buffer): Promise { const path = this.getOciBlobPath(digest); return this.putObject(path, data); } /** * Check if OCI blob exists */ public async ociBlobExists(digest: string): Promise { const path = this.getOciBlobPath(digest); return this.objectExists(path); } /** * Delete OCI blob */ public async deleteOciBlob(digest: string): Promise { const path = this.getOciBlobPath(digest); return this.deleteObject(path); } /** * Get OCI manifest */ public async getOciManifest(repository: string, digest: string): Promise { const path = this.getOciManifestPath(repository, digest); return this.getObject(path); } /** * Store OCI manifest */ public async putOciManifest( repository: string, digest: string, data: Buffer, contentType: string ): Promise { const path = this.getOciManifestPath(repository, digest); return this.putObject(path, data, { 'Content-Type': contentType }); } /** * Check if OCI manifest exists */ public async ociManifestExists(repository: string, digest: string): Promise { const path = this.getOciManifestPath(repository, digest); return this.objectExists(path); } /** * Delete OCI manifest */ public async deleteOciManifest(repository: string, digest: string): Promise { const path = this.getOciManifestPath(repository, digest); return this.deleteObject(path); } // ======================================================================== // NPM-SPECIFIC HELPERS // ======================================================================== /** * Get NPM packument (package document) */ public async getNpmPackument(packageName: string): Promise { const path = this.getNpmPackumentPath(packageName); const data = await this.getObject(path); return data ? JSON.parse(data.toString('utf-8')) : null; } /** * Store NPM packument */ public async putNpmPackument(packageName: string, packument: any): Promise { const path = this.getNpmPackumentPath(packageName); const data = Buffer.from(JSON.stringify(packument, null, 2), 'utf-8'); return this.putObject(path, data, { 'Content-Type': 'application/json' }); } /** * Check if NPM packument exists */ public async npmPackumentExists(packageName: string): Promise { const path = this.getNpmPackumentPath(packageName); return this.objectExists(path); } /** * Delete NPM packument */ public async deleteNpmPackument(packageName: string): Promise { const path = this.getNpmPackumentPath(packageName); return this.deleteObject(path); } /** * Get NPM tarball */ public async getNpmTarball(packageName: string, version: string): Promise { const path = this.getNpmTarballPath(packageName, version); return this.getObject(path); } /** * Store NPM tarball */ public async putNpmTarball( packageName: string, version: string, tarball: Buffer ): Promise { const path = this.getNpmTarballPath(packageName, version); return this.putObject(path, tarball, { 'Content-Type': 'application/octet-stream' }); } /** * Check if NPM tarball exists */ public async npmTarballExists(packageName: string, version: string): Promise { const path = this.getNpmTarballPath(packageName, version); return this.objectExists(path); } /** * Delete NPM tarball */ public async deleteNpmTarball(packageName: string, version: string): Promise { const path = this.getNpmTarballPath(packageName, version); return this.deleteObject(path); } // ======================================================================== // PATH HELPERS // ======================================================================== private getOciBlobPath(digest: string): string { const hash = digest.split(':')[1]; return `oci/blobs/sha256/${hash}`; } private getOciManifestPath(repository: string, digest: string): string { const hash = digest.split(':')[1]; return `oci/manifests/${repository}/${hash}`; } private getNpmPackumentPath(packageName: string): string { return `npm/packages/${packageName}/index.json`; } private getNpmTarballPath(packageName: string, version: string): string { const safeName = packageName.replace('@', '').replace('/', '-'); return `npm/packages/${packageName}/${safeName}-${version}.tgz`; } }