@push.rocks/smartregistry

a module implementing package registries for oci, npm, maven

readme.md for @push.rocks/smartregistry

๐Ÿš€ A composable TypeScript library implementing OCI Distribution Specification v1.1, NPM Registry API, Maven Repository, Cargo/crates.io Registry, Composer/Packagist, PyPI (Python Package Index), and RubyGems Registry โ€” everything you need to build a unified container and package registry in one library.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

โœจ Features

๐Ÿ”„ Multi-Protocol Support

๐Ÿ—๏ธ Unified Architecture

๐Ÿ” Authentication & Authorization

๐Ÿ“ฆ Protocol Feature Matrix

Feature OCI NPM Maven Cargo Composer PyPI RubyGems
Publish/Upload โœ… โœ… โœ… โœ… โœ… โœ… โœ…
Download โœ… โœ… โœ… โœ… โœ… โœ… โœ…
Search โ€” โœ… โ€” โœ… โ€” โ€” โ€”
Version Yank โ€” โ€” โ€” โœ… โ€” โ€” โœ…
Metadata API โœ… โœ… โœ… โœ… โœ… โœ… โœ…
Token Auth โœ… โœ… โœ… โœ… โœ… โœ… โœ…
Checksum Verification โœ… โœ… โœ… โœ… โ€” โœ… โœ…
Upstream Proxy โœ… โœ… โœ… โœ… โœ… โœ… โœ…

๐ŸŒ Upstream Proxy & Caching

๐ŸŒŠ Streaming-First Architecture

๐Ÿ”Œ Enterprise Extensibility

๐Ÿ“ฅ Installation

# Using pnpm (recommended)
pnpm add @push.rocks/smartregistry

# Using npm
npm install @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',
    },
  },
  // Enable only the protocols you need
  oci: { enabled: true, basePath: '/oci' },
  npm: { enabled: true, basePath: '/npm' },
  maven: { enabled: true, basePath: '/maven' },
  cargo: { enabled: true, basePath: '/cargo' },
  composer: { enabled: true, basePath: '/composer' },
  pypi: { enabled: true, basePath: '/pypi' },
  rubygems: { enabled: true, basePath: '/rubygems' },
};

const registry = new SmartRegistry(config);
await registry.init();

// Handle any incoming HTTP request โ€” the router does the rest
const response = await registry.handleRequest({
  method: 'GET',
  path: '/npm/express',
  headers: {},
  query: {},
});

๐Ÿ›๏ธ Architecture

Request Flow

HTTP Request
    โ†“
SmartRegistry (orchestrator)
    โ†“
Path-based routing
    โ”œโ”€โ†’ /oci/*       โ†’ OciRegistry
    โ”œโ”€โ†’ /npm/*       โ†’ NpmRegistry
    โ”œโ”€โ†’ /maven/*     โ†’ MavenRegistry
    โ”œโ”€โ†’ /cargo/*     โ†’ CargoRegistry
    โ”œโ”€โ†’ /composer/*  โ†’ ComposerRegistry
    โ”œโ”€โ†’ /pypi/*      โ†’ PypiRegistry
    โ””โ”€โ†’ /rubygems/*  โ†’ RubyGemsRegistry
           โ†“
    Shared Storage & Auth
           โ†“
    S3-compatible backend

Directory Structure

ts/
โ”œโ”€โ”€ core/                    # Shared infrastructure
โ”‚   โ”œโ”€โ”€ classes.baseregistry.ts
โ”‚   โ”œโ”€โ”€ classes.registrystorage.ts
โ”‚   โ”œโ”€โ”€ classes.authmanager.ts
โ”‚   โ””โ”€โ”€ interfaces.core.ts
โ”œโ”€โ”€ oci/                     # OCI implementation
โ”œโ”€โ”€ npm/                     # NPM implementation
โ”œโ”€โ”€ maven/                   # Maven implementation
โ”œโ”€โ”€ cargo/                   # Cargo implementation
โ”œโ”€โ”€ composer/                # Composer implementation
โ”œโ”€โ”€ pypi/                    # PyPI implementation
โ”œโ”€โ”€ rubygems/                # RubyGems implementation
โ”œโ”€โ”€ upstream/                # Upstream proxy infrastructure
โ””โ”€โ”€ classes.smartregistry.ts # Main orchestrator

๐Ÿ’ก Usage Examples

๐Ÿณ OCI Registry (Container Images)

// Pull a manifest
const response = await registry.handleRequest({
  method: 'GET',
  path: '/oci/library/nginx/manifests/latest',
  headers: { 'Authorization': 'Bearer <token>' },
  query: {},
});

// Push a blob (two-step upload)
const uploadInit = await registry.handleRequest({
  method: 'POST',
  path: '/oci/myapp/blobs/uploads/',
  headers: { 'Authorization': 'Bearer <token>' },
  query: {},
});

const uploadId = uploadInit.headers['Docker-Upload-UUID'];

await registry.handleRequest({
  method: 'PUT',
  path: `/oci/myapp/blobs/uploads/${uploadId}`,
  headers: { 'Authorization': 'Bearer <token>' },
  query: { digest: 'sha256:abc123...' },
  body: blobData,
});

๐Ÿ“ฆ NPM Registry

// Get package metadata
const metadata = await registry.handleRequest({
  method: 'GET',
  path: '/npm/express',
  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 search = await registry.handleRequest({
  method: 'GET',
  path: '/npm/-/v1/search',
  headers: {},
  query: { text: 'express', size: '20' },
});

๐Ÿฆ€ Cargo Registry (Rust Crates)

// Get registry config (required for Cargo sparse protocol)
const config = await registry.handleRequest({
  method: 'GET',
  path: '/cargo/config.json',
  headers: {},
  query: {},
});

// Publish a crate (binary format: [4 bytes JSON len][JSON][4 bytes crate len][.crate])
const publishResponse = await registry.handleRequest({
  method: 'PUT',
  path: '/cargo/api/v1/crates/new',
  headers: { 'Authorization': '<cargo-token>' },
  query: {},
  body: binaryPublishData,
});

// Yank a version
await registry.handleRequest({
  method: 'DELETE',
  path: '/cargo/api/v1/crates/my-crate/0.1.0/yank',
  headers: { 'Authorization': '<cargo-token>' },
  query: {},
});

Using with Cargo CLI:

# .cargo/config.toml
[registries.myregistry]
index = "sparse+https://registry.example.com/cargo/"
cargo publish --registry=myregistry
cargo install --registry=myregistry my-crate

๐ŸŽผ Composer Registry (PHP Packages)

// Get repository root
const packagesJson = await registry.handleRequest({
  method: 'GET',
  path: '/composer/packages.json',
  headers: {},
  query: {},
});

// Upload a package (ZIP with composer.json inside)
const uploadResponse = await registry.handleRequest({
  method: 'PUT',
  path: '/composer/packages/vendor/package',
  headers: { 'Authorization': 'Bearer <composer-token>' },
  query: {},
  body: zipBuffer,
});

Using with Composer CLI:

{
  "repositories": [
    { "type": "composer", "url": "https://registry.example.com/composer" }
  ]
}
composer require vendor/package

๐Ÿ PyPI Registry (Python Packages)

// Get package index (PEP 503 HTML)
const htmlIndex = await registry.handleRequest({
  method: 'GET',
  path: '/simple/requests/',
  headers: { 'Accept': 'text/html' },
  query: {},
});

// Get package index (PEP 691 JSON)
const jsonIndex = await registry.handleRequest({
  method: 'GET',
  path: '/simple/requests/',
  headers: { 'Accept': 'application/vnd.pypi.simple.v1+json' },
  query: {},
});

// Upload a package
const upload = await registry.handleRequest({
  method: 'POST',
  path: '/pypi/',
  headers: {
    'Authorization': 'Bearer <pypi-token>',
    'Content-Type': 'multipart/form-data',
  },
  query: {},
  body: {
    ':action': 'file_upload',
    protocol_version: '1',
    name: 'my-package',
    version: '1.0.0',
    filetype: 'bdist_wheel',
    content: wheelData,
    filename: 'my_package-1.0.0-py3-none-any.whl',
  },
});

Using with pip:

pip install --index-url https://registry.example.com/simple/ my-package
python -m twine upload --repository-url https://registry.example.com/pypi/ dist/*

๐Ÿ’Ž RubyGems Registry

// Upload a gem
const uploadGem = await registry.handleRequest({
  method: 'POST',
  path: '/rubygems/api/v1/gems',
  headers: { 'Authorization': '<rubygems-api-key>' },
  query: {},
  body: gemBuffer,
});

// Get compact index
const versions = await registry.handleRequest({
  method: 'GET',
  path: '/rubygems/versions',
  headers: {},
  query: {},
});

Using with Bundler:

# Gemfile
source 'https://registry.example.com/rubygems' do
  gem 'my-gem'
end
gem push my-gem-1.0.0.gem --host https://registry.example.com/rubygems
bundle install

๐Ÿ” Authentication

const authManager = registry.getAuthManager();

// Authenticate user
const userId = await authManager.authenticate({ username: 'user', password: 'pass' });

// Create protocol-specific tokens
const npmToken = await authManager.createNpmToken(userId, false);
const ociToken = await authManager.createOciToken(userId, ['oci:repository:myapp:push'], 3600);
const pypiToken = await authManager.createPypiToken(userId, false);
const cargoToken = await authManager.createCargoToken(userId, false);
const composerToken = await authManager.createComposerToken(userId, false);
const rubygemsToken = await authManager.createRubyGemsToken(userId, false);

// Validate and check permissions
const token = await authManager.validateToken(npmToken, 'npm');
const canWrite = await authManager.authorize(token, 'npm:package:my-package', 'write');

๐ŸŒ Upstream Proxy Configuration

import { SmartRegistry, StaticUpstreamProvider } from '@push.rocks/smartregistry';

const upstreamProvider = new StaticUpstreamProvider({
  npm: {
    enabled: true,
    upstreams: [
      {
        id: 'company-private',
        url: 'https://npm.internal.company.com',
        priority: 1,
        enabled: true,
        scopeRules: [{ pattern: '@company/*', action: 'include' }],
        auth: { type: 'bearer', token: process.env.NPM_PRIVATE_TOKEN },
      },
      {
        id: 'npmjs',
        url: 'https://registry.npmjs.org',
        priority: 10,
        enabled: true,
        scopeRules: [{ pattern: '@company/*', action: 'exclude' }],
      },
    ],
    cache: { enabled: true, staleWhileRevalidate: true },
  },
  oci: {
    enabled: true,
    upstreams: [
      { id: 'dockerhub', url: 'https://registry-1.docker.io', priority: 1, enabled: true },
    ],
  },
});

const registry = new SmartRegistry({
  storage: { /* S3 config */ },
  auth: { /* Auth config */ },
  upstreamProvider,
  npm: { enabled: true, basePath: '/npm' },
  oci: { enabled: true, basePath: '/oci' },
});

๐Ÿ”Œ Custom Auth Provider

import { SmartRegistry, IAuthProvider, IAuthToken, TRegistryProtocol } from '@push.rocks/smartregistry';

class LdapAuthProvider implements IAuthProvider {
  async init() { /* connect to LDAP */ }

  async authenticate(credentials) {
    const result = await this.ldapClient.bind(credentials.username, credentials.password);
    return result.success ? credentials.username : null;
  }

  async validateToken(token: string, protocol?: TRegistryProtocol): Promise<IAuthToken | null> {
    const session = await this.sessionStore.get(token);
    return session ? { userId: session.userId, scopes: session.scopes } : null;
  }

  async createToken(userId: string, protocol: TRegistryProtocol, options?) {
    const token = crypto.randomUUID();
    await this.sessionStore.set(token, { userId, protocol, ...options });
    return token;
  }

  async revokeToken(token: string) { await this.sessionStore.delete(token); }

  async authorize(token: IAuthToken | null, resource: string, action: string) {
    if (!token) return action === 'read';
    return this.checkPermissions(token.userId, resource, action);
  }
}

const registry = new SmartRegistry({
  ...config,
  authProvider: new LdapAuthProvider(),
});

๐Ÿ“Š Storage Hooks (Quota & Audit)

import { SmartRegistry, IStorageHooks, IStorageHookContext } from '@push.rocks/smartregistry';

const storageHooks: IStorageHooks = {
  async beforePut(ctx: IStorageHookContext) {
    if (ctx.actor?.orgId) {
      const usage = await getStorageUsage(ctx.actor.orgId);
      const quota = await getQuota(ctx.actor.orgId);
      if (usage + (ctx.metadata?.size || 0) > quota) {
        return { allowed: false, reason: 'Storage quota exceeded' };
      }
    }
    return { allowed: true };
  },

  async afterPut(ctx: IStorageHookContext) {
    await auditLog.write({
      action: 'storage.put',
      key: ctx.key,
      protocol: ctx.protocol,
      actor: ctx.actor,
      timestamp: ctx.timestamp,
    });
  },

  async beforeDelete(ctx: IStorageHookContext) {
    if (await isProtectedPackage(ctx.key)) {
      return { allowed: false, reason: 'Cannot delete protected package' };
    }
    return { allowed: true };
  },
};

const registry = new SmartRegistry({ ...config, storageHooks });

๐Ÿ‘ค Request Actor Context

// Pass actor information for audit/quota tracking
const response = await registry.handleRequest({
  method: 'PUT',
  path: '/npm/my-package',
  headers: { 'Authorization': 'Bearer <token>' },
  query: {},
  body: packageData,
  actor: {
    userId: 'user123',
    tokenId: 'token-abc',
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    orgId: 'org-456',
  },
});

โš™๏ธ Configuration

Storage Configuration

Extends IS3Descriptor from @tsclass/tsclass:

storage: {
  accessKey: string;        // S3 access key
  accessSecret: string;     // S3 secret key
  endpoint: string;         // S3 endpoint (e.g., 's3.amazonaws.com')
  port?: number;            // Default: 443
  useSsl?: boolean;         // Default: true
  region?: string;          // AWS region
  bucketName: string;       // Bucket name for registry storage
}

Authentication Configuration

auth: {
  jwtSecret: string;
  tokenStore: 'memory' | 'redis' | 'database';
  npmTokens: { enabled: boolean; defaultReadonly?: boolean };
  ociTokens: { enabled: boolean; realm: string; service: string };
  pypiTokens: { enabled: boolean };
  rubygemsTokens: { enabled: boolean };
}

Protocol Configuration

Each protocol accepts:

{
  enabled: boolean;
  basePath: string;         // URL prefix, e.g. '/npm'
  registryUrl?: string;     // Public-facing base URL (used in generated metadata links)
  features?: Record<string, boolean>;
}

The registryUrl is important when the registry is served behind a reverse proxy or on a non-default port. For example, if your server is at https://registry.example.com, set registryUrl: 'https://registry.example.com/npm' for the NPM protocol so that generated metadata URLs point to the correct host.

๐Ÿ“š API Reference

Core Classes

SmartRegistry

Main orchestrator โ€” routes requests to the appropriate protocol handler.

Method Description
init() Initialize the registry and all enabled protocols
handleRequest(context) Route and handle an HTTP request
getStorage() Get the shared RegistryStorage instance
getAuthManager() Get the shared AuthManager instance
getRegistry(protocol) Get a specific protocol handler by name
isInitialized() Check if the registry has been initialized
destroy() Clean up resources

Protocol Endpoints

OCI Registry

Method Path Description
GET /{name}/manifests/{ref} Get manifest by tag or digest
PUT /{name}/manifests/{ref} Push manifest
GET /{name}/blobs/{digest} Get blob
POST /{name}/blobs/uploads/ Initiate blob upload
PUT /{name}/blobs/uploads/{uuid} Complete blob upload
GET /{name}/tags/list List tags
GET /{name}/referrers/{digest} Get referrers (OCI 1.1)

NPM Registry

Method Path Description
GET /{package} Get package metadata (packument)
PUT /{package} Publish package
GET /{package}/-/{tarball} Download tarball
GET /-/v1/search?text=... Search packages
PUT /-/user/org.couchdb.user:{user} Login
GET/POST/DELETE /-/npm/v1/tokens Token management
PUT /-/package/{pkg}/dist-tags/{tag} Manage dist-tags

Maven Repository

Method Path Description
PUT /{group}/{artifact}/{version}/{file} Upload artifact
GET /{group}/{artifact}/{version}/{file} Download artifact
GET /{group}/{artifact}/maven-metadata.xml Get metadata

Cargo Registry

Method Path Description
GET /config.json Registry configuration
GET /{p1}/{p2}/{name} Sparse index entry
PUT /api/v1/crates/new Publish crate (binary format)
GET /api/v1/crates/{crate}/{version}/download Download .crate
DELETE /api/v1/crates/{crate}/{version}/yank Yank version
PUT /api/v1/crates/{crate}/{version}/unyank Unyank version
GET /api/v1/crates?q=... Search crates

Composer Registry

Method Path Description
GET /packages.json Repository metadata
GET /p2/{vendor}/{package}.json Package version metadata
GET /packages/list.json List all packages
GET /dists/{vendor}/{package}/{ref}.zip Download package ZIP
PUT /packages/{vendor}/{package} Upload package
DELETE /packages/{vendor}/{package}[/{version}] Delete package/version

PyPI Registry

Method Path Description
GET /simple/ List all packages (PEP 503/691)
GET /simple/{package}/ List package files
POST / Upload package (multipart)
GET /pypi/{package}/json Package metadata API
GET /pypi/{package}/{version}/json Version metadata
GET /packages/{package}/{filename} Download file

RubyGems Registry

Method Path Description
GET /versions Master versions file (compact index)
GET /info/{gem} Gem info file
GET /names List all gem names
POST /api/v1/gems Upload .gem file
DELETE /api/v1/gems/yank Yank version
PUT /api/v1/gems/unyank Unyank version
GET /api/v1/versions/{gem}.json Version metadata
GET /gems/{gem}-{version}.gem Download .gem file

๐ŸŽฏ Scope Format

Unified scope format across all protocols:

{protocol}:{type}:{name}:{action}

Examples:
  npm:package:express:read          # Read express package
  npm:package:*:write               # Write any package
  oci:repository:nginx:pull         # Pull nginx image
  oci:repository:*:push             # Push any image
  cargo:crate:serde:write           # Write serde crate
  composer:package:vendor/pkg:read  # Read Composer package
  pypi:package:requests:read        # Read PyPI package
  rubygems:gem:rails:write          # Write RubyGems gem
  {protocol}:*:*:*                  # Full access for a protocol

๐Ÿ—„๏ธ Storage Structure

bucket/
โ”œโ”€โ”€ oci/
โ”‚   โ”œโ”€โ”€ blobs/sha256/{hash}
โ”‚   โ”œโ”€โ”€ manifests/{repository}/{digest}
โ”‚   โ””โ”€โ”€ tags/{repository}/tags.json
โ”œโ”€โ”€ npm/
โ”‚   โ””โ”€โ”€ packages/{name}/
โ”‚       โ”œโ”€โ”€ index.json              # Packument
โ”‚       โ””โ”€โ”€ {name}-{ver}.tgz       # Tarball
โ”œโ”€โ”€ maven/
โ”‚   โ”œโ”€โ”€ artifacts/{group}/{artifact}/{version}/
โ”‚   โ””โ”€โ”€ metadata/{group}/{artifact}/maven-metadata.xml
โ”œโ”€โ”€ cargo/
โ”‚   โ”œโ”€โ”€ config.json
โ”‚   โ”œโ”€โ”€ index/{p1}/{p2}/{name}     # Sparse index
โ”‚   โ””โ”€โ”€ crates/{name}/{name}-{ver}.crate
โ”œโ”€โ”€ composer/
โ”‚   โ””โ”€โ”€ packages/{vendor}/{package}/
โ”‚       โ”œโ”€โ”€ metadata.json
โ”‚       โ””โ”€โ”€ {reference}.zip
โ”œโ”€โ”€ pypi/
โ”‚   โ”œโ”€โ”€ simple/index.html
โ”‚   โ”œโ”€โ”€ simple/{package}/index.html
โ”‚   โ”œโ”€โ”€ packages/{package}/{filename}
โ”‚   โ””โ”€โ”€ metadata/{package}/metadata.json
โ””โ”€โ”€ rubygems/
    โ”œโ”€โ”€ versions
    โ”œโ”€โ”€ info/{gemname}
    โ”œโ”€โ”€ names
    โ””โ”€โ”€ gems/{gemname}-{version}.gem

๐ŸŒŠ Streaming Architecture

All responses from SmartRegistry.handleRequest() use the Web Streams API. The body field on IResponse is always a ReadableStream<Uint8Array> โ€” whether the content is a 2GB container image layer or a tiny JSON metadata response.

How It Works

Stream Helpers

import { streamToBuffer, streamToJson, toReadableStream } from '@push.rocks/smartregistry';

// Consume a stream into a Buffer
const buffer = await streamToBuffer(response.body);

// Consume a stream into parsed JSON
const data = await streamToJson(response.body);

// Create a ReadableStream from any data type
const stream = toReadableStream({ hello: 'world' });

Consuming in Node.js HTTP Servers

Since Node.js http.ServerResponse uses Node streams, bridge with Readable.fromWeb():

import { Readable } from 'stream';

if (response.body) {
  Readable.fromWeb(response.body).pipe(res);
} else {
  res.end();
}

๐Ÿ”Œ Integration with Express

import express from 'express';
import { Readable } from 'stream';
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);
  for (const [key, value] of Object.entries(response.headers)) {
    res.setHeader(key, value);
  }

  if (response.body) {
    // All response bodies are ReadableStream<Uint8Array> โ€” pipe to HTTP response
    Readable.fromWeb(response.body).pipe(res);
  } else {
    res.end();
  }
});

app.listen(5000);

๐Ÿงช Testing with smartstorage

smartregistry works seamlessly with @push.rocks/smartstorage, a local S3-compatible server for testing โ€” no cloud credentials needed.

import { SmartStorage } from '@push.rocks/smartstorage';
import { SmartRegistry } from '@push.rocks/smartregistry';

// Start local S3 server
const s3Server = await SmartStorage.createAndStart({
  server: { port: 3456, silent: true },
  storage: { cleanSlate: true },
});

// Get S3 descriptor from the running server
const s3Descriptor = await s3Server.getStorageDescriptor();

const registry = new SmartRegistry({
  storage: { ...s3Descriptor, bucketName: 'my-test-registry' },
  auth: { jwtSecret: 'test', tokenStore: 'memory', npmTokens: { enabled: true } },
  npm: { enabled: true, basePath: '/npm' },
  oci: { enabled: true, basePath: '/oci' },
});
await registry.init();

// ... run your tests ...
await s3Server.stop();

๐Ÿ› ๏ธ Development

pnpm install       # Install dependencies
pnpm run build     # Build
pnpm test          # Run all tests

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the LICENSE file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

changelog.md for @push.rocks/smartregistry

2026-03-27 - 2.8.2 - fix(maven,tests)

handle Maven Basic auth and accept deploy-plugin metadata/checksum uploads while stabilizing npm CLI test cleanup

2026-03-24 - 2.8.1 - fix(registry)

align OCI and RubyGems API behavior and improve npm search result ordering

2026-03-24 - 2.8.0 - feat(core,storage,oci,registry-config)

add streaming response support and configurable registry URLs across protocols

2025-12-03 - 2.7.0 - feat(upstream)

Add dynamic per-request upstream provider and integrate into registries

2025-11-27 - 2.6.0 - feat(core)

Add core registry infrastructure: storage, auth, upstream cache, and protocol handlers

2025-11-27 - 2.5.0 - feat(pypi,rubygems)

Add PyPI and RubyGems protocol implementations, upstream caching, and auth/storage improvements

2025-11-27 - 2.4.0 - feat(core)

Add pluggable auth providers, storage hooks, multi-upstream cache awareness, and PyPI/RubyGems protocol implementations

2025-11-27 - 2.3.0 - feat(upstream)

Add upstream proxy/cache subsystem and integrate per-protocol upstreams

2025-11-27 - 2.2.3 - fix(tests)

Use unique test run IDs and add S3 cleanup in test helpers to avoid cross-run conflicts

2025-11-25 - 2.2.2 - fix(npm)

Replace console logging with structured Smartlog in NPM registry and silence RubyGems helper error logging

2025-11-25 - 2.2.1 - fix(core)

Normalize binary data handling across registries and add buffer helpers

2025-11-25 - 2.2.0 - feat(core/registrystorage)

Persist OCI manifest content-type in sidecar and normalize manifest body handling

2025-11-25 - 2.1.2 - fix(oci)

Prefer raw request body for content-addressable OCI operations and expose rawBody on request context

2025-11-25 - 2.1.1 - fix(oci)

Preserve raw manifest bytes for digest calculation and handle string/JSON manifest bodies in OCI registry

2025-11-25 - 2.1.0 - feat(oci)

Support configurable OCI token realm/service and centralize unauthorized responses

2025-11-25 - 2.0.0 - BREAKING CHANGE(pypi,rubygems)

Revise PyPI and RubyGems handling: normalize error payloads, fix .gem parsing/packing, adjust PyPI JSON API and tests, and export smartarchive plugin

2025-11-25 - 1.9.0 - feat(auth)

Implement HMAC-SHA256 OCI JWTs; enhance PyPI & RubyGems uploads and normalize responses

2025-11-24 - 1.8.0 - feat(smarts3)

Add local smarts3 testing support and documentation

2025-11-23 - 1.7.0 - feat(core)

Standardize S3 storage config using @tsclass/tsclass IS3Descriptor and wire it into RegistryStorage and plugins exports; update README and package dependencies.

2025-11-21 - 1.6.0 - feat(core)

Add PyPI and RubyGems registries, integrate into SmartRegistry, extend storage and auth

2025-11-21 - 1.5.0 - feat(core)

Add PyPI and RubyGems protocol support, Cargo token management, and storage helpers

2025-11-21 - 1.4.1 - fix(devcontainer)

Simplify devcontainer configuration and rename container image

2025-11-21 - 1.4.0 - feat(registrystorage)

Add deleteMavenMetadata to RegistryStorage and update Maven DELETE test to expect 204 No Content

2025-11-21 - 1.3.1 - fix(maven)

Pass request path to Maven checksum handler so checksum files are resolved correctly

2025-11-21 - 1.3.0 - feat(core)

Add Cargo and Composer registries with storage, auth and helpers

2025-11-21 - 1.2.0 - feat(maven)

Add Maven registry protocol support (storage, auth, routing, interfaces, and exports)

2025-11-20 - 1.1.1 - fix(oci)

Improve OCI manifest permission response and tag handling: include WWW-Authenticate header on unauthorized manifest GETs, accept optional headers in manifest lookup, and persist tags as a unified tags.json mapping when pushing manifests.

2025-11-20 - 1.1.0 - feat(oci)

Support monolithic OCI blob uploads; add registry cleanup/destroy hooks; update tests and docs

2025-11-20 - 1.0.2 - fix(scripts)

Increase tstest timeout from 30s to 240s in package.json test script

2025-11-20 - 1.0.1 - registry

Release 1.0.1 brings core registry features, multi-registry support, logging integration, and performance improvements to object listing. Also includes the initial project scaffold and CI/CD setup.