v1.0.2
@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
npm install @push.rocks/smartregistry
# or
pnpm add @push.rocks/smartregistry
Quick Start
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)
// Pull an image
const response = await registry.handleRequest({
method: 'GET',
path: '/oci/v2/library/nginx/manifests/latest',
headers: {
'Authorization': 'Bearer <token>',
},
query: {},
});
// Push a blob
const uploadInit = await registry.handleRequest({
method: 'POST',
path: '/oci/v2/myapp/blobs/uploads/',
headers: { 'Authorization': 'Bearer <token>' },
query: {},
});
const uploadId = uploadInit.headers['Docker-Upload-UUID'];
await registry.handleRequest({
method: 'PUT',
path: `/oci/v2/myapp/blobs/uploads/${uploadId}`,
headers: { 'Authorization': 'Bearer <token>' },
query: { digest: 'sha256:abc123...' },
body: blobData,
});
NPM Registry (Packages)
// 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 <npm-token>' },
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: '<base64-tarball>',
length: 12345,
},
},
},
});
// Search packages
const searchResults = await registry.handleRequest({
method: 'GET',
path: '/npm/-/v1/search',
headers: {},
query: { text: 'express', size: '20' },
});
Authentication
// 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
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
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
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 registryhandleRequest(context)- Handle HTTP requestgetStorage()- Get storage instancegetAuthManager()- Get auth managergetRegistry(protocol)- Get protocol handler
RegistryStorage
Unified storage abstraction.
OCI Methods:
getOciBlob(digest)- Get blobputOciBlob(digest, data)- Store blobgetOciManifest(repo, digest)- Get manifestputOciManifest(repo, digest, data, type)- Store manifest
NPM Methods:
getNpmPackument(name)- Get package metadataputNpmPackument(name, data)- Store package metadatagetNpmTarball(name, version)- Get tarballputNpmTarball(name, version, data)- Store tarball
AuthManager
Unified authentication manager.
Methods:
authenticate(credentials)- Validate user credentialscreateNpmToken(userId, readonly)- Create NPM tokencreateOciToken(userId, scopes, expiresIn)- Create OCI JWTvalidateToken(token, protocol)- Validate any tokenauthorize(token, resource, action)- Check permissions
Protocol Handlers
OciRegistry
Endpoints:
GET /v2/- Version checkGET /v2/{name}/manifests/{ref}- Get manifestPUT /v2/{name}/manifests/{ref}- Push manifestGET /v2/{name}/blobs/{digest}- Get blobPOST /v2/{name}/blobs/uploads/- Initiate uploadGET /v2/{name}/tags/list- List tagsGET /v2/{name}/referrers/{digest}- Get referrers
NpmRegistry
Endpoints:
GET /{package}- Get package metadataPUT /{package}- Publish packageGET /{package}/-/{tarball}- Download tarballGET /-/v1/search- Search packagesPUT /-/user/org.couchdb.user:{user}- LoginGET /-/npm/v1/tokens- List tokensPOST /-/npm/v1/tokens- Create tokenPUT /-/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
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<string, string>,
query: req.query as Record<string, string>,
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
# Install dependencies
pnpm install
# Build
pnpm run build
# Test
pnpm test
License
MIT
Contributing
Contributions welcome! Please see the repository for guidelines.
Description
Languages
TypeScript
100%