Files
smartregistry/readme.md

469 lines
10 KiB
Markdown
Raw Normal View History

# @push.rocks/smartregistry
2025-11-19 15:32:00 +00:00
A composable TypeScript library implementing both OCI Distribution Specification v1.1 and NPM Registry API for building unified container and package registries.
2025-11-19 15:16:20 +00:00
## Features
2025-11-19 15:32:00 +00:00
### 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
2025-11-19 15:16:20 +00:00
## Installation
```bash
npm install @push.rocks/smartregistry
# or
pnpm add @push.rocks/smartregistry
```
2025-11-19 15:32:00 +00:00
## Quick Start
2025-11-19 15:16:20 +00:00
```typescript
2025-11-19 15:32:00 +00:00
import { SmartRegistry, IRegistryConfig } from '@push.rocks/smartregistry';
2025-11-19 15:16:20 +00:00
const config: IRegistryConfig = {
storage: {
2025-11-19 15:32:00 +00:00
accessKey: 'your-s3-key',
2025-11-19 15:16:20 +00:00
accessSecret: 'your-s3-secret',
endpoint: 's3.amazonaws.com',
port: 443,
useSsl: true,
region: 'us-east-1',
bucketName: 'my-registry',
},
2025-11-19 15:32:00 +00:00
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',
},
2025-11-19 15:16:20 +00:00
};
const registry = new SmartRegistry(config);
await registry.init();
2025-11-19 15:32:00 +00:00
// Handle requests
const response = await registry.handleRequest({
method: 'GET',
path: '/npm/express',
headers: {},
query: {},
});
2025-11-19 15:16:20 +00:00
```
2025-11-19 15:32:00 +00:00
## 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)
2025-11-19 15:16:20 +00:00
```typescript
2025-11-19 15:32:00 +00:00
// Pull an image
const response = await registry.handleRequest({
method: 'GET',
path: '/oci/v2/library/nginx/manifests/latest',
headers: {
'Authorization': 'Bearer <token>',
},
query: {},
});
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
// Push a blob
const uploadInit = await registry.handleRequest({
method: 'POST',
path: '/oci/v2/myapp/blobs/uploads/',
headers: { 'Authorization': 'Bearer <token>' },
query: {},
});
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
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,
2025-11-19 15:16:20 +00:00
});
2025-11-19 15:32:00 +00:00
```
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
### NPM Registry (Packages)
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
```typescript
// Install a package (get metadata)
const metadata = await registry.handleRequest({
method: 'GET',
path: '/npm/express',
headers: {},
query: {},
});
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
// Download tarball
const tarball = await registry.handleRequest({
method: 'GET',
path: '/npm/express/-/express-4.18.0.tgz',
headers: {},
query: {},
});
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
// 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,
},
},
},
2025-11-19 15:16:20 +00:00
});
2025-11-19 15:32:00 +00:00
// Search packages
const searchResults = await registry.handleRequest({
method: 'GET',
path: '/npm/-/v1/search',
headers: {},
query: { text: 'express', size: '20' },
});
```
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
### Authentication
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
```typescript
// NPM Login
const authManager = registry.getAuthManager();
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
// Authenticate user
const userId = await authManager.authenticate({
username: 'user',
password: 'pass',
2025-11-19 15:16:20 +00:00
});
2025-11-19 15:32:00 +00:00
// 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'
);
```
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
## 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
}
2025-11-19 15:16:20 +00:00
```
2025-11-19 15:32:00 +00:00
### Authentication Configuration
2025-11-19 15:16:20 +00:00
```typescript
2025-11-19 15:32:00 +00:00
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
};
}
```
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
### Protocol Configuration
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
```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;
};
}
2025-11-19 15:16:20 +00:00
```
## API Reference
2025-11-19 15:32:00 +00:00
### Core Classes
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
#### SmartRegistry
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
Main orchestrator class.
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
**Methods:**
- `init()` - Initialize the registry
- `handleRequest(context)` - Handle HTTP request
- `getStorage()` - Get storage instance
- `getAuthManager()` - Get auth manager
- `getRegistry(protocol)` - Get protocol handler
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
#### RegistryStorage
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
Unified storage abstraction.
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
**OCI Methods:**
- `getOciBlob(digest)` - Get blob
- `putOciBlob(digest, data)` - Store blob
- `getOciManifest(repo, digest)` - Get manifest
- `putOciManifest(repo, digest, data, type)` - Store manifest
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
**NPM Methods:**
- `getNpmPackument(name)` - Get package metadata
- `putNpmPackument(name, data)` - Store package metadata
- `getNpmTarball(name, version)` - Get tarball
- `putNpmTarball(name, version, data)` - Store tarball
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
#### 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
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
**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
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
#### 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
```
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
## Integration Examples
2025-11-19 15:16:20 +00:00
2025-11-19 15:32:00 +00:00
### 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<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
```bash
# Install dependencies
pnpm install
# Build
pnpm run build
# Test
pnpm test
```
2025-11-19 15:16:20 +00:00
## License
MIT
## Contributing
2025-11-19 15:32:00 +00:00
Contributions welcome! Please see the repository for guidelines.