JustStorage Node SDK
Modern TypeScript SDK for the JustStorage object storage service.
- âś… 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
npm install @just-storage/node-sdkRequirements:
- Node.js >= 24
- Optional: Zod >= 4.3.6 (for runtime validation)
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();The SDK supports three authentication methods:
import { StorageClient, AuthType } from '@just-storage/node-sdk';
const client = new StorageClient({
endpoint: 'http://localhost:8080',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
authType: AuthType.Bearer, // default
});const client = new StorageClient({
endpoint: 'http://localhost:8080',
token: 'your-api-key',
authType: AuthType.ApiKey,
});const client = new StorageClient({
endpoint: 'http://localhost:8080',
authType: AuthType.None,
});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
Readablestream - Web
ReadableStream<Uint8Array>
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 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 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 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`);// 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}`);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);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;
},
},
});ClientConfig:
endpoint(required): Base URL of the JustStorage servicetoken: JWT token or API key (required unlessauthTypeisnone)authType: Authentication type (bearer,apikey, ornone)transport: Transport layer configurationretry: 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
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;
}
}JustStorageError- Base error class withcode,status,requestId,retryableNetworkError- Network connectivity issuesTimeoutError- Request timeout exceededUnauthorizedError- 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 failedConfigurationError- Invalid client configurationAbortError- Request aborted
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
}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);// 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 availableconst obj = await client.objects.upload(fileStream, {
namespace: 'models',
tenantId: 'acme-corp',
metadata: {
version: '2.1',
author: 'data-team',
environment: 'production',
tags: ['llm', 'fine-tuned'],
},
});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);
}StorageClass.Hot- NVMe-backed storage for frequently accessed data (default)StorageClass.Cold- HDD-backed storage for archival data with lower cost
Objects transition through lifecycle states:
ObjectStatus.Writing- Upload in progressObjectStatus.Committed- Available for download (only visible in list operations)ObjectStatus.Deleting- Deletion requestedObjectStatus.Deleted- Physically removed
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
}interface ListResponse {
objects: StoredObject[];
total: number;
limit: number;
offset: number;
}interface DownloadMetadata {
id: string;
size_bytes: number;
content_type?: string;
content_hash?: string;
metadata: Record<string, unknown>;
}- Always close the client: Call
client.close()when done to release resources - Handle errors appropriately: Use specific error types for better error handling
- Stream large files: Use streaming for files > 10MB to reduce memory usage
- Use checksums: Enable
calculateChecksumfor critical data - Set storage class wisely: Use
Coldstorage for archival data to reduce costs - Provide meaningful keys: Use human-readable keys for easier object retrieval
- Add custom metadata: Include context like version, author, and tags
- Configure timeouts: Adjust timeouts based on your use case
- Use async iterators: Simplify pagination with
listAll()method
See the examples/ directory for complete working examples:
basic-usage.ts- Upload, download, list, deletestreaming.ts- Streaming large fileserror-handling.ts- Error handling patterns
# 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 cleanContributions are welcome! Please see CONTRIBUTING.md for guidelines.
For security issues, please see SECURITY.md for reporting instructions.
- Main repository: Glowing-Pixels-UG/just-storage
- SDKs:
- Node: just-storage-node-sdk
- Python: just-storage-python-sdk
- Go: just-storage-go-sdk
MIT