@smarthivelabs-devs/storage-sdk
v0.2.1
Published
SmartHive Storage TypeScript SDK for browser and server applications.
Readme
@smarthivelabs-devs/storage-sdk
Core TypeScript SDK for SmartHive Storage. Works in Node.js 18+, browsers, Cloudflare Workers, and any runtime with a fetch implementation.
Install
npm install @smarthivelabs-devs/storage-sdkSetup
import { SmartHiveStorageClient } from '@smarthivelabs-devs/storage-sdk';
const client = new SmartHiveStorageClient({
baseUrl: 'https://storage.yourapp.com', // must be http:// or https://
appCode: 'my-app',
apiKey: 'sk_live_...',
timeoutMs: 30_000, // optional global timeout
});Config options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| baseUrl | string | Yes | Base URL of your SmartHive Storage server |
| appCode | string | Yes | App code from admin console |
| apiKey | string | Yes | API key credential |
| fetch | typeof fetch | No | Custom fetch (e.g. node-fetch on older Node) |
| timeoutMs | number | No | Request timeout in ms |
| onRequest | (info) => void | No | Hook called before every request |
| onResponse | (info) => void | No | Hook called after every response |
RequestOptions
Every method accepts an optional RequestOptions as its last argument:
type RequestOptions = {
signal?: AbortSignal; // cancel the request
timeoutMs?: number; // per-request timeout (overrides global)
};SmartHiveStorageClient — API Reference
upload(input, options?)
Direct multipart upload. Returns a complete UploadResult with an absolute url.
const result = await client.upload({
bucket: 'avatars', // required — bucket code
file: blob, // Blob | File | ArrayBuffer | Uint8Array
filename: 'avatar.png', // optional — defaults to file.name or 'upload.bin'
contentType: 'image/png', // optional
visibility: 'PUBLIC', // 'PUBLIC' | 'PRIVATE', default depends on bucket
tenantId: 'tenant-123', // optional
ownerUserId: 'user-456', // optional
});
// result: { fileId, versionId, uploadSessionId, status, url }
console.log(result.url); // https://storage.yourapp.com/delivery/files/...initUpload(input, options?)
Start a two-step upload (useful for large files or resumable uploads).
const session = await client.initUpload({
bucket: 'videos',
filename: 'intro.mp4',
mimeType: 'video/mp4',
sizeBytes: file.size,
checksumSha256: '...', // optional
visibility: 'PRIVATE',
});
// session: { fileId, versionId, uploadSessionId, uploadMode, directUploadUrl, ... }
// Upload directly to the provider URL returned in session.directUploadUrl
await fetch(session.directUploadUrl, { method: 'PUT', body: file });
// Confirm completion
await client.completeUpload(session.uploadSessionId);completeUpload(uploadSessionId, options?)
Mark an upload session as complete after the file has been sent to the provider.
const completed = await client.completeUpload(uploadSessionId);
// { fileId, uploadSessionId, status }abortUpload(uploadSessionId, options?)
Cancel an in-progress upload session.
await client.abortUpload(uploadSessionId);replace(input, options?)
Replace a file's content, creating a new version. Returns the same shape as upload.
const result = await client.replace({
fileId: 'file-uuid',
file: newBlob,
filename: 'updated-avatar.png',
contentType: 'image/png',
});delete(fileId, options?)
Soft-delete a file. The storage server enqueues physical object deletion in the background.
await client.delete(fileId);
// { fileId, status: 'DELETED' }getMetadata(fileId, options?)
Fetch full file metadata. The url field is always absolute.
const meta = await client.getMetadata(fileId);
// { id, bucketId, originalFilename, mimeType, sizeBytes, visibility, status, url, ... }getUrl(fileId, options?)
Get the current delivery URL for a file (respects visibility rules).
const { url } = await client.getUrl(fileId);downloadUrl(fileId) (synchronous)
Build a direct download URL without a network request. Forces a download response header.
const url = client.downloadUrl(fileId);
// https://storage.yourapp.com/files/{fileId}/downloadlistFiles(input?, options?)
List files with optional filters. Supports cursor-based pagination.
const page = await client.listFiles({
bucket: 'avatars',
visibility: 'PRIVATE',
mimeType: 'image/png',
limit: 20,
cursor: previousResult.nextCursor ?? undefined,
});
// { items: FileMetadata[], nextCursor: string | null }listByEntity(input, options?)
List files attached to an entity.
const links = await client.listByEntity({
entityType: 'post',
entityId: 'post-abc',
});
// EntityFileLink[]: { id, entityType, entityId, fieldName, sortOrder, file }attachToEntity(input, options?)
Attach an existing file to an entity.
const link = await client.attachToEntity({
fileId: 'file-uuid',
entityType: 'post',
entityId: 'post-abc',
fieldName: 'cover_image',
sortOrder: 0,
});detachFromEntity(input, options?)
Remove a file attachment from an entity.
await client.detachFromEntity({
entityType: 'post',
entityId: 'post-abc',
linkId: link.id,
});getSignedUrl(fileId, input?, options?)
Issues a time-limited signed delivery token for a PRIVATE file, returning a URL the client can fetch directly — no auth headers needed on the delivery request.
How it works end-to-end:
Your server SmartHive Storage Client browser / app
─────────────────────────────────────────────────────────────────────────────────────────
POST /files/:id/signed-url ─────────► creates token in DB
◄──────────────── { url, expiresAt } ─ (raw token embedded in url)
return url to client ──────────────────────────────────────────────► GET url
validate token (expiry, ◄────── file bytes
revocation, use-count,
app ownership)
increment usedCount atomically// Server-side: create the signed URL
const signed = await client.getSignedUrl(fileId, {
expiresInSeconds: 300, // 5 minutes (default 15 min, max 7 days)
maxUses: 1, // single-use; null = unlimited
});
// signed.url is absolute and already has the token embedded:
// https://storage.yourapp.com/delivery/files/{fileId}?token=shat_...
// Return it directly to your client. The client fetches it with no extra headers.
console.log(signed.url);
console.log(signed.expiresAt); // ISO-8601 expiry timestamp
console.log(signed.maxUses); // 1Token validation on delivery (what the backend checks):
- Token exists in DB and is not revoked
- Token has not expired (
expiresAt > now()) - File is ACTIVE (not deleted)
- Token was issued by the same app that owns the file (tenant isolation)
usedCount < maxUses(if maxUses is set) — checked atomically to prevent race conditions
Any failed check returns HTTP 403 — the file bytes are never sent.
Once a token reaches its maxUses limit it is permanently exhausted — issue a new one.
moveFile(fileId, input, options?)
Move a file to a different bucket.
const moved = await client.moveFile(fileId, {
targetBucketId: 'archive-bucket-id',
});
// { fileId, bucketId }createVariant(input, options?)
Request a processed variant of a file (resize, format conversion).
const variant = await client.createVariant({
fileId: 'file-uuid',
variantCode: 'thumbnail',
preset: 'thumbnail', // 'thumbnail' | 'small' | 'medium' | 'large' | 'webp'
width: 200,
height: 200,
});listVariants(fileId, options?)
List all variants for a file.
const variants = await client.listVariants(fileId);getVariantUrl(fileId, variantCode, options?)
Get the delivery URL for a specific variant.
const { url } = await client.getVariantUrl(fileId, 'thumbnail');deleteVariant(fileId, variantCode, options?)
Delete a specific variant.
await client.deleteVariant(fileId, 'thumbnail');SmartHiveAdminClient — API Reference
For server-side administration only. Never expose the adminKey to clients.
import { SmartHiveAdminClient } from '@smarthivelabs-devs/storage-sdk';
const admin = new SmartHiveAdminClient({
baseUrl: 'https://storage.yourapp.com',
adminKey: process.env.SMARTHIVE_ADMIN_KEY!,
});| Method | Description |
|--------|-------------|
| createApp(input) | Create a new app and its initial credential |
| listApps(filters?) | List all apps |
| getApp(appId) | Get app with buckets and credentials |
| createCredential(appId, input?) | Issue a new API key for an app |
| listCredentials(appId) | List credentials for an app |
| revokeCredential(appId, credentialId) | Revoke a credential |
| createBucket(appId, input) | Create a bucket (triggers folder provisioning on Drive) |
| listBuckets(appId) | List buckets with rules and mappings |
| updateBucket(bucketId, input) | Update bucket config |
| createProviderRule(bucketId, input) | Add a provider routing rule to a bucket |
| createProvider(input) | Register a new storage provider |
| listProviders() | List all providers |
| updateProvider(providerId, input) | Enable/disable or reconfigure a provider |
| testProvider(providerId) | Run a connectivity test against a provider |
| getProviderHealth() | Health check all enabled providers |
| listEvents(filters?) | Query the audit event log |
| getUsage(filters?) | Get storage usage metrics |
| createFileTransfer(input) | Start a bulk file migration job |
| listFileTransfers(filters?) | List migration jobs |
| pauseFileTransfer(jobId) | Pause a running migration |
| resumeFileTransfer(jobId) | Resume a paused migration |
| cancelFileTransfer(jobId) | Cancel a migration |
| runRetention() | Trigger a manual retention sweep |
Error Classes
import { StorageApiError, StorageConfigError, NotImplementedError } from '@smarthivelabs-devs/storage-sdk';
try {
await client.upload({ bucket: 'photos', file: blob });
} catch (err) {
if (err instanceof StorageApiError) {
// err.status — HTTP status code
// err.message — server error message
// err.payload — raw response body
} else if (err instanceof StorageConfigError) {
// invalid client config (missing baseUrl, appCode, apiKey)
}
}Requests aborted via signal throw a standard AbortError (err.name === 'AbortError').
Cancellation
const controller = new AbortController();
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10_000);
try {
const result = await client.upload({ bucket: 'videos', file }, { signal: controller.signal });
} catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
console.log('Upload cancelled');
}
}