Skip to content

Glowing-Pixels-UG/just-storage-node-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JustStorage Node SDK

CI npm version

Modern TypeScript SDK for the JustStorage object storage service.

Features

  • âś… Full API Coverage: Upload, download, list, delete, and search operations
  • âś… Streaming Support: Efficient streaming for large files with automatic chunking
  • âś… Type-Safe: Full TypeScript support with comprehensive type definitions
  • âś… Multiple Auth Methods: JWT Bearer tokens, API keys, or no-auth for development
  • âś… Error Handling: Structured error hierarchy with error codes and retry logic
  • âś… Checksum Verification: Optional integrity checking for uploads and downloads
  • âś… Production Ready: Configurable timeouts, connection pooling, and retry strategies
  • âś… API Key Management: Built-in API key CRUD operations

Installation

npm install @just-storage/node-sdk

Requirements:

  • Node.js >= 24
  • Optional: Zod >= 4.3.6 (for runtime validation)

Quick Start

import { StorageClient, StorageClass } from '@just-storage/node-sdk';
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';

// Initialize client with Bearer token
const client = new StorageClient({
  endpoint: 'http://localhost:8080',
  token: process.env.JUST_STORAGE_TOKEN,
});

const tenantId = 'acme-corp';
const namespace = 'models';

// Upload a file
const fileStream = createReadStream('model.bin');
const obj = await client.objects.upload(fileStream, {
  namespace,
  tenantId,
  key: 'llama-3.1-8b',
  storageClass: StorageClass.Hot,
});

console.log(`Uploaded: ${obj.id}`);

// Download the object
const { stream, metadata } = await client.objects.download({
  objectId: obj.id,
  tenantId,
});

const output = createWriteStream('downloaded.bin');
await pipeline(Readable.fromWeb(stream), output);
console.log(`Downloaded ${metadata.size_bytes} bytes`);

// List objects
const response = await client.objects.list({
  namespace,
  tenantId,
  limit: 50,
});

console.log(`Found ${response.total} objects`);

// Delete object
await client.objects.delete({ objectId: obj.id, tenantId });

await client.close();

Authentication

The SDK supports three authentication methods:

JWT Bearer Token

import { StorageClient, AuthType } from '@just-storage/node-sdk';

const client = new StorageClient({
  endpoint: 'http://localhost:8080',
  token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  authType: AuthType.Bearer, // default
});

API Key

const client = new StorageClient({
  endpoint: 'http://localhost:8080',
  token: 'your-api-key',
  authType: AuthType.ApiKey,
});

No Authentication (Development)

const client = new StorageClient({
  endpoint: 'http://localhost:8080',
  authType: AuthType.None,
});

Operations

Upload

Upload objects with streaming support:

import { createReadStream } from 'node:fs';
import { StorageClass } from '@just-storage/node-sdk';

// Upload from file stream
const fileStream = createReadStream('data.bin');
const obj = await client.objects.upload(fileStream, {
  namespace: 'models',
  tenantId: 'acme-corp',
  key: 'my-model', // Optional
  storageClass: StorageClass.Hot, // Optional, defaults to Hot
  contentType: 'application/octet-stream', // Optional
  metadata: { version: '1.0' }, // Optional custom metadata
  calculateChecksum: true, // Optional integrity verification
});

console.log(`Uploaded: ${obj.id}`);
console.log(`Content hash: ${obj.content_hash}`);

Supported input types:

  • Buffer
  • Node.js Readable stream
  • Web ReadableStream<Uint8Array>

Download

Download by object ID:

import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';

const { stream, metadata } = await client.objects.download({
  objectId: 'obj-123',
  tenantId: 'acme-corp',
});

console.log(`Downloading ${metadata.size_bytes} bytes`);

// Save to file
const output = createWriteStream('output.bin');
await pipeline(Readable.fromWeb(stream), output);

Download by key:

const { stream, metadata } = await client.objects.downloadByKey({
  namespace: 'models',
  tenantId: 'acme-corp',
  key: 'my-model',
});

List

List objects with pagination:

const response = await client.objects.list({
  namespace: 'models',
  tenantId: 'acme-corp',
  limit: 50,
  offset: 0,
});

console.log(`Found ${response.total} objects`);
for (const obj of response.objects) {
  console.log(`${obj.id}: ${obj.key ?? 'no key'}`);
}

List all objects using async iterator:

for await (const obj of client.objects.listAll({
  namespace: 'models',
  tenantId: 'acme-corp',
  limit: 100, // Page size
})) {
  console.log(`${obj.id}: ${obj.key}`);
}

Search

Search objects by metadata:

const results = await client.objects.search({
  namespace: 'models',
  tenantId: 'acme-corp',
  filters: {
    'metadata.version': '1.0',
    'metadata.type': 'llm',
  },
  limit: 50,
});

Full-text search:

const results = await client.objects.textSearch({
  namespace: 'documents',
  tenantId: 'acme-corp',
  query: 'machine learning',
  limit: 20,
});

Delete

Delete a single object:

await client.objects.delete({
  objectId: 'obj-123',
  tenantId: 'acme-corp',
});

Batch delete:

const result = await client.objects.batchDelete(
  ['obj-1', 'obj-2', 'obj-3'],
  'acme-corp',
);

console.log(`Deleted: ${result.deleted_count} objects`);

Health Checks

// Basic health check (no authentication required)
const health = await client.health();
console.log(`Status: ${health.status}`);

// Readiness check (includes database connectivity)
const readiness = await client.readiness();
console.log(`Database: ${readiness.database}`);

API Key Management

Manage API keys programmatically:

// Create API key
const apiKey = await client.apiKeys.create({
  name: 'production-key',
  expires_at: new Date('2026-12-31'),
  permissions: ['read', 'write'],
});

console.log(`API Key: ${apiKey.key}`);

// List API keys
const keys = await client.apiKeys.list({ limit: 50 });

// Get specific key
const key = await client.apiKeys.get(apiKey.id);

// Update key
await client.apiKeys.update(apiKey.id, {
  name: 'production-key-updated',
});

// Delete key
await client.apiKeys.delete(apiKey.id);

Configuration

Client Configuration

import { StorageClient, AuthType } from '@just-storage/node-sdk';

const client = new StorageClient({
  // Required
  endpoint: 'http://localhost:8080',

  // Authentication
  token: 'your-token', // Required unless authType is 'none'
  authType: AuthType.Bearer, // 'bearer' | 'apikey' | 'none'

  // Transport configuration
  transport: {
    timeout: 30000, // Request timeout in ms
    connectTimeout: 5000, // Connection timeout in ms
    keepAlive: true, // Enable HTTP keep-alive
    maxConnections: 100, // Max concurrent connections
    headers: { // Custom headers
      'User-Agent': 'my-app/1.0',
    },
  },

  // Retry configuration
  retry: {
    maxRetries: 3, // Max retry attempts
    initialDelay: 1000, // Initial delay in ms
    maxDelay: 10000, // Max delay between retries
    backoffMultiplier: 2, // Exponential backoff multiplier
    jitter: true, // Add random jitter to delays
    shouldRetry: (error) => { // Custom retry logic
      return error.retryable;
    },
  },
});

Transport Options

ClientConfig:

  • endpoint (required): Base URL of the JustStorage service
  • token: JWT token or API key (required unless authType is none)
  • authType: Authentication type (bearer, apikey, or none)
  • transport: Transport layer configuration
  • retry: Retry behavior configuration

TransportConfig:

  • timeout: Request timeout in milliseconds (default: 30000)
  • connectTimeout: Connection timeout in milliseconds (default: 5000)
  • keepAlive: Enable HTTP keep-alive (default: true)
  • maxConnections: Maximum concurrent connections (default: 100)
  • headers: Custom HTTP headers

RetryConfig:

  • maxRetries: Maximum number of retry attempts (default: 3)
  • initialDelay: Initial delay between retries in ms (default: 1000)
  • maxDelay: Maximum delay between retries in ms (default: 10000)
  • backoffMultiplier: Exponential backoff multiplier (default: 2)
  • jitter: Add random jitter to retry delays (default: true)
  • shouldRetry: Custom function to determine if request should be retried

Error Handling

All SDK errors extend JustStorageError with structured error information:

import {
  JustStorageError,
  NotFoundError,
  UnauthorizedError,
  ChecksumMismatchError,
  ErrorCode,
} from '@just-storage/node-sdk';

try {
  const obj = await client.objects.download({
    objectId: 'obj-123',
    tenantId: 'acme-corp',
  });
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Object not found');
  } else if (error instanceof UnauthorizedError) {
    console.error('Authentication failed');
  } else if (error instanceof ChecksumMismatchError) {
    console.error('Data integrity check failed');
  } else if (error instanceof JustStorageError) {
    console.error(`Error ${error.code}: ${error.message}`);
    console.error(`Status: ${error.status}`);
    console.error(`Request ID: ${error.requestId}`);
    console.error(`Retryable: ${error.retryable}`);
  } else {
    throw error;
  }
}

Error Types

  • JustStorageError - Base error class with code, status, requestId, retryable
  • NetworkError - Network connectivity issues
  • TimeoutError - Request timeout exceeded
  • UnauthorizedError - Authentication failure (401)
  • ForbiddenError - Insufficient permissions (403)
  • NotFoundError - Resource not found (404)
  • ConflictError - Resource conflict (409)
  • TooManyRequestsError - Rate limit exceeded (429)
  • ServerError - Server-side error (500)
  • ServiceUnavailableError - Service temporarily unavailable (503)
  • ChecksumMismatchError - Data integrity verification failed
  • ConfigurationError - Invalid client configuration
  • AbortError - Request aborted

Error Codes

All errors include a stable ErrorCode enum value for programmatic handling:

import { ErrorCode } from '@just-storage/node-sdk';

if (error.code === ErrorCode.NotFound) {
  // Handle not found
} else if (error.code === ErrorCode.RateLimitExceeded) {
  // Handle rate limiting
}

Advanced Usage

Streaming Large Files

The SDK efficiently handles large files through streaming:

import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';

// Upload large file (automatic chunking)
const uploadStream = createReadStream('large-model.bin');
const obj = await client.objects.upload(uploadStream, {
  namespace: 'models',
  tenantId: 'acme-corp',
  calculateChecksum: true, // Verify integrity
});

// Download large file
const { stream, metadata } = await client.objects.download({
  objectId: obj.id,
  tenantId: 'acme-corp',
});

const output = createWriteStream('downloaded-model.bin');
await pipeline(Readable.fromWeb(stream), output);

Content Hash Verification

// Upload with checksum calculation
const obj = await client.objects.upload(fileStream, {
  namespace: 'models',
  tenantId: 'acme-corp',
  calculateChecksum: true,
});

console.log(`Content hash: ${obj.content_hash}`);

// SDK automatically verifies hash during download when available

Custom Metadata

const obj = await client.objects.upload(fileStream, {
  namespace: 'models',
  tenantId: 'acme-corp',
  metadata: {
    version: '2.1',
    author: 'data-team',
    environment: 'production',
    tags: ['llm', 'fine-tuned'],
  },
});

Pagination

let offset = 0;
const limit = 50;

while (true) {
  const response = await client.objects.list({
    namespace: 'models',
    tenantId: 'acme-corp',
    limit,
    offset,
  });

  for (const obj of response.objects) {
    console.log(`${obj.id}: ${obj.key}`);
  }

  if (offset + limit >= response.total) {
    break;
  }

  offset += limit;
}

Or use the async iterator for simpler pagination:

for await (const obj of client.objects.listAll({
  namespace: 'models',
  tenantId: 'acme-corp',
})) {
  console.log(obj.id);
}

Storage Classes

  • StorageClass.Hot - NVMe-backed storage for frequently accessed data (default)
  • StorageClass.Cold - HDD-backed storage for archival data with lower cost

Object Status

Objects transition through lifecycle states:

  • ObjectStatus.Writing - Upload in progress
  • ObjectStatus.Committed - Available for download (only visible in list operations)
  • ObjectStatus.Deleting - Deletion requested
  • ObjectStatus.Deleted - Physically removed

Data Models

StoredObject

interface StoredObject {
  id: string;
  namespace: string;
  tenant_id: string;
  key?: string;
  status: ObjectStatus;
  storage_class: StorageClass;
  content_hash?: string;
  size_bytes?: number;
  content_type?: string;
  metadata: Record<string, unknown>;
  created_at: string; // ISO 8601 timestamp
  updated_at: string; // ISO 8601 timestamp
}

ListResponse

interface ListResponse {
  objects: StoredObject[];
  total: number;
  limit: number;
  offset: number;
}

DownloadMetadata

interface DownloadMetadata {
  id: string;
  size_bytes: number;
  content_type?: string;
  content_hash?: string;
  metadata: Record<string, unknown>;
}

Best Practices

  1. Always close the client: Call client.close() when done to release resources
  2. Handle errors appropriately: Use specific error types for better error handling
  3. Stream large files: Use streaming for files > 10MB to reduce memory usage
  4. Use checksums: Enable calculateChecksum for critical data
  5. Set storage class wisely: Use Cold storage for archival data to reduce costs
  6. Provide meaningful keys: Use human-readable keys for easier object retrieval
  7. Add custom metadata: Include context like version, author, and tags
  8. Configure timeouts: Adjust timeouts based on your use case
  9. Use async iterators: Simplify pagination with listAll() method

Examples

See the examples/ directory for complete working examples:

Testing & Development

# Install dependencies
npm install

# Build the package
npm run build

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

# Lint code
npm run lint

# Format code
npm run format

# Type check
npm run typecheck

# Clean build artifacts
npm run clean

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Security

For security issues, please see SECURITY.md for reporting instructions.


Related projects


License

MIT