# @push.rocks/smartregistry A composable TypeScript library implementing both OCI Distribution Specification v1.1 and NPM Registry API for building unified container and package registries. ## Features ### Dual Protocol Support - **OCI Distribution Spec v1.1**: Full container registry with manifest/blob operations - **NPM Registry API**: Complete package registry with publish/install/search ### Unified Architecture - **Composable Design**: Core infrastructure with protocol plugins - **Shared Storage**: Cloud-agnostic S3-compatible backend (@push.rocks/smartbucket) - **Unified Authentication**: Scope-based permissions across both protocols - **Path-based Routing**: `/oci/*` for containers, `/npm/*` for packages ### Authentication & Authorization - NPM UUID tokens for package operations - OCI JWT tokens for container operations - Unified scope system: `npm:package:foo:write`, `oci:repository:bar:push` - Pluggable via async callbacks ### Comprehensive Feature Set **OCI Features:** - ✅ Pull operations (manifests, blobs) - ✅ Push operations (chunked uploads) - ✅ Content discovery (tags, referrers API) - ✅ Content management (deletion) **NPM Features:** - ✅ Package publish/unpublish - ✅ Package download (tarballs) - ✅ Metadata & search - ✅ Dist-tag management - ✅ Token management ## Installation ```bash npm install @push.rocks/smartregistry # or pnpm add @push.rocks/smartregistry ``` ## Quick Start ```typescript import { SmartRegistry, IRegistryConfig } from '@push.rocks/smartregistry'; const config: IRegistryConfig = { storage: { accessKey: 'your-s3-key', accessSecret: 'your-s3-secret', endpoint: 's3.amazonaws.com', port: 443, useSsl: true, region: 'us-east-1', bucketName: 'my-registry', }, auth: { jwtSecret: 'your-secret-key', tokenStore: 'memory', npmTokens: { enabled: true }, ociTokens: { enabled: true, realm: 'https://auth.example.com/token', service: 'my-registry', }, }, oci: { enabled: true, basePath: '/oci', }, npm: { enabled: true, basePath: '/npm', }, }; const registry = new SmartRegistry(config); await registry.init(); // Handle requests const response = await registry.handleRequest({ method: 'GET', path: '/npm/express', headers: {}, query: {}, }); ``` ## Architecture ### Directory Structure ``` ts/ ├── core/ # Shared infrastructure │ ├── classes.baseregistry.ts │ ├── classes.registrystorage.ts │ ├── classes.authmanager.ts │ └── interfaces.core.ts ├── oci/ # OCI implementation │ ├── classes.ociregistry.ts │ └── interfaces.oci.ts ├── npm/ # NPM implementation │ ├── classes.npmregistry.ts │ └── interfaces.npm.ts └── classes.smartregistry.ts # Main orchestrator ``` ### Request Flow ``` HTTP Request ↓ SmartRegistry (orchestrator) ↓ Path-based routing ├─→ /oci/* → OciRegistry └─→ /npm/* → NpmRegistry ↓ Shared Storage & Auth ↓ S3-compatible backend ``` ## Usage Examples ### OCI Registry (Container Images) ```typescript // Pull an image const response = await registry.handleRequest({ method: 'GET', path: '/oci/v2/library/nginx/manifests/latest', headers: { 'Authorization': 'Bearer ', }, query: {}, }); // Push a blob const uploadInit = await registry.handleRequest({ method: 'POST', path: '/oci/v2/myapp/blobs/uploads/', headers: { 'Authorization': 'Bearer ' }, query: {}, }); const uploadId = uploadInit.headers['Docker-Upload-UUID']; await registry.handleRequest({ method: 'PUT', path: `/oci/v2/myapp/blobs/uploads/${uploadId}`, headers: { 'Authorization': 'Bearer ' }, query: { digest: 'sha256:abc123...' }, body: blobData, }); ``` ### NPM Registry (Packages) ```typescript // Install a package (get metadata) const metadata = await registry.handleRequest({ method: 'GET', path: '/npm/express', headers: {}, query: {}, }); // Download tarball const tarball = await registry.handleRequest({ method: 'GET', path: '/npm/express/-/express-4.18.0.tgz', headers: {}, query: {}, }); // Publish a package const publishResponse = await registry.handleRequest({ method: 'PUT', path: '/npm/my-package', headers: { 'Authorization': 'Bearer ' }, query: {}, body: { name: 'my-package', versions: { '1.0.0': { /* version metadata */ }, }, 'dist-tags': { latest: '1.0.0' }, _attachments: { 'my-package-1.0.0.tgz': { content_type: 'application/octet-stream', data: '', length: 12345, }, }, }, }); // Search packages const searchResults = await registry.handleRequest({ method: 'GET', path: '/npm/-/v1/search', headers: {}, query: { text: 'express', size: '20' }, }); ``` ### Authentication ```typescript // NPM Login const authManager = registry.getAuthManager(); // Authenticate user const userId = await authManager.authenticate({ username: 'user', password: 'pass', }); // Create NPM token const npmToken = await authManager.createNpmToken(userId, false); // Create OCI token with scopes const ociToken = await authManager.createOciToken( userId, ['oci:repository:myapp:push', 'oci:repository:myapp:pull'], 3600 ); // Validate any token const token = await authManager.validateToken(npmToken, 'npm'); // Check permissions const canWrite = await authManager.authorize( token, 'npm:package:my-package', 'write' ); ``` ## Configuration ### Storage Configuration ```typescript storage: { accessKey: string; // S3 access key accessSecret: string; // S3 secret key endpoint: string; // S3 endpoint port?: number; // Default: 443 useSsl?: boolean; // Default: true region?: string; // Default: 'us-east-1' bucketName: string; // Bucket name } ``` ### Authentication Configuration ```typescript auth: { jwtSecret: string; // Secret for signing JWTs tokenStore: 'memory' | 'redis' | 'database'; npmTokens: { enabled: boolean; defaultReadonly?: boolean; }; ociTokens: { enabled: boolean; realm: string; // Auth server URL service: string; // Service name }; } ``` ### Protocol Configuration ```typescript oci?: { enabled: boolean; basePath: string; // Default: '/oci' features?: { referrers?: boolean; deletion?: boolean; }; } npm?: { enabled: boolean; basePath: string; // Default: '/npm' features?: { publish?: boolean; unpublish?: boolean; search?: boolean; }; } ``` ## API Reference ### Core Classes #### SmartRegistry Main orchestrator class. **Methods:** - `init()` - Initialize the registry - `handleRequest(context)` - Handle HTTP request - `getStorage()` - Get storage instance - `getAuthManager()` - Get auth manager - `getRegistry(protocol)` - Get protocol handler #### RegistryStorage Unified storage abstraction. **OCI Methods:** - `getOciBlob(digest)` - Get blob - `putOciBlob(digest, data)` - Store blob - `getOciManifest(repo, digest)` - Get manifest - `putOciManifest(repo, digest, data, type)` - Store manifest **NPM Methods:** - `getNpmPackument(name)` - Get package metadata - `putNpmPackument(name, data)` - Store package metadata - `getNpmTarball(name, version)` - Get tarball - `putNpmTarball(name, version, data)` - Store tarball #### AuthManager Unified authentication manager. **Methods:** - `authenticate(credentials)` - Validate user credentials - `createNpmToken(userId, readonly)` - Create NPM token - `createOciToken(userId, scopes, expiresIn)` - Create OCI JWT - `validateToken(token, protocol)` - Validate any token - `authorize(token, resource, action)` - Check permissions ### Protocol Handlers #### OciRegistry **Endpoints:** - `GET /v2/` - Version check - `GET /v2/{name}/manifests/{ref}` - Get manifest - `PUT /v2/{name}/manifests/{ref}` - Push manifest - `GET /v2/{name}/blobs/{digest}` - Get blob - `POST /v2/{name}/blobs/uploads/` - Initiate upload - `GET /v2/{name}/tags/list` - List tags - `GET /v2/{name}/referrers/{digest}` - Get referrers #### NpmRegistry **Endpoints:** - `GET /{package}` - Get package metadata - `PUT /{package}` - Publish package - `GET /{package}/-/{tarball}` - Download tarball - `GET /-/v1/search` - Search packages - `PUT /-/user/org.couchdb.user:{user}` - Login - `GET /-/npm/v1/tokens` - List tokens - `POST /-/npm/v1/tokens` - Create token - `PUT /-/package/{pkg}/dist-tags/{tag}` - Update tag ## Storage Structure ``` bucket/ ├── oci/ │ ├── blobs/ │ │ └── sha256/{hash} │ ├── manifests/ │ │ └── {repository}/{digest} │ └── tags/ │ └── {repository}/tags.json └── npm/ ├── packages/ │ ├── {name}/ │ │ ├── index.json # Packument │ │ └── {name}-{ver}.tgz # Tarball │ └── @{scope}/{name}/ │ ├── index.json │ └── {name}-{ver}.tgz └── users/ └── {username}.json ``` ## Scope Format Unified scope format across protocols: ``` {protocol}:{type}:{name}:{action} Examples: npm:package:express:read # Read express package npm:package:*:write # Write any package npm:*:* # Full NPM access oci:repository:nginx:pull # Pull nginx image oci:repository:*:push # Push any image oci:*:* # Full OCI access ``` ## Integration Examples ### Express Server ```typescript import express from 'express'; import { SmartRegistry } from '@push.rocks/smartregistry'; const app = express(); const registry = new SmartRegistry(config); await registry.init(); app.all('*', async (req, res) => { const response = await registry.handleRequest({ method: req.method, path: req.path, headers: req.headers as Record, query: req.query as Record, body: req.body, }); res.status(response.status); Object.entries(response.headers).forEach(([key, value]) => { res.setHeader(key, value); }); if (response.body) { if (Buffer.isBuffer(response.body)) { res.send(response.body); } else { res.json(response.body); } } else { res.end(); } }); app.listen(5000); ``` ## Development ```bash # Install dependencies pnpm install # Build pnpm run build # Test pnpm test ``` ## License MIT ## Contributing Contributions welcome! Please see the repository for guidelines.