| title | IStorageService Contract |
|---|---|
| description | Reference for the Storage Service contract — file upload, download, deletion, metadata, and signed URL generation |
The Storage Service provides a unified interface for file management — uploading, downloading, and organizing files across different storage backends (local filesystem and S3-compatible object storage).
**Source:** `packages/spec/src/contracts/storage-service.ts` **Service name:** `'file-storage'` — resolve via `kernel.getService('file-storage')`.export interface IStorageService {
// Core Operations
upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void>;
download(key: string): Promise<Buffer>;
delete(key: string): Promise<void>;
exists(key: string): Promise<boolean>;
getInfo(key: string): Promise<StorageFileInfo>;
// Listing (optional — adapter may not implement)
list?(prefix: string): Promise<StorageFileInfo[]>;
// Signed URL (optional)
getSignedUrl?(key: string, expiresIn: number, options?: PresignedDownloadOptions): Promise<string>;
// Presigned browser-direct upload / download (optional)
getPresignedUpload?(
key: string,
expiresIn: number,
options?: StorageUploadOptions,
): Promise<PresignedUploadDescriptor>;
getPresignedDownload?(
key: string,
expiresIn: number,
options?: PresignedDownloadOptions,
): Promise<PresignedDownloadDescriptor>;
// Chunked / multipart upload (optional)
initiateChunkedUpload?(key: string, options?: StorageUploadOptions): Promise<string>;
uploadChunk?(uploadId: string, partNumber: number, data: Buffer): Promise<string>;
completeChunkedUpload?(
uploadId: string,
parts: Array<{ partNumber: number; eTag: string }>,
): Promise<string>;
abortChunkedUpload?(uploadId: string): Promise<void>;
}Uploads a file to storage. The key is passed as the first argument and the
content as the second; the call resolves to void. Use getInfo() afterwards
to read back stored metadata.
await storageService.upload(
'attachments/tasks/tsk_01HQ4A7B/document.pdf',
fileBuffer,
{
contentType: 'application/pdf',
acl: 'private',
metadata: {
objectType: 'task',
recordId: 'tsk_01HQ4A7B9D3F5G8J2K4L',
uploadedBy: 'usr_01HQ3V5K8N2M4P6R7T9W',
},
},
);
const info = await storageService.getInfo(
'attachments/tasks/tsk_01HQ4A7B/document.pdf',
);
console.log(info.key); // "attachments/tasks/tsk_01HQ4A7B/document.pdf"
console.log(info.size); // 245760Downloads a file and returns its content as a Buffer. Use getInfo() for
content type and size.
const content = await storageService.download(
'attachments/tasks/tsk_01HQ4A7B/document.pdf'
);
console.log(content.length); // 245760 (Buffer)Permanently removes a file from storage.
await storageService.delete('attachments/tasks/tsk_01HQ4A7B/document.pdf');Checks if a file exists at the given path without downloading it.
const fileExists = await storageService.exists(
'attachments/tasks/tsk_01HQ4A7B/document.pdf'
);
// true or falseRetrieves file metadata without downloading the content.
const info = await storageService.getInfo(
'attachments/tasks/tsk_01HQ4A7B/document.pdf'
);
console.log(info.key); // "attachments/tasks/tsk_01HQ4A7B/document.pdf"
console.log(info.size); // 245760
console.log(info.contentType); // "application/pdf"
console.log(info.lastModified); // Date
console.log(info.metadata); // { objectType: 'task', ... }Configuration options for upload behavior.
export interface StorageUploadOptions {
/** MIME content type */
contentType?: string;
/** Custom metadata key-value pairs */
metadata?: Record<string, string>;
/** Access control level */
acl?: 'private' | 'public-read';
}| Option | Type | Default | Description |
|---|---|---|---|
contentType |
string |
— | MIME content type stored with the object |
metadata |
Record<string, string> |
— | Custom metadata key-value pairs |
acl |
'private' | 'public-read' |
— | Access control level |
Lists files under a key prefix. This method is optional — verify the active
adapter implements it before calling. It takes a single prefix argument
(no pagination options) and returns StorageFileInfo[].
// List all attachments for a task
const files = await storageService.list?.(
'attachments/tasks/tsk_01HQ4A7B/'
) ?? [];
for (const file of files) {
console.log(`${file.key} (${file.size} bytes)`);
}Generates a temporary signed URL for private file access. Optional — verify the
active adapter implements it. Takes the key and an expiresIn value (seconds)
as positional arguments.
const url = await storageService.getSignedUrl?.(
'attachments/tasks/tsk_01HQ4A7B/document.pdf',
3600, // 1 hour
);
// "https://storage.example.com/...?signature=abc&expires=..."For browser-direct uploads, optional adapters expose presigned descriptors.
getPresignedUpload(key, expiresIn, options?) returns a PresignedUploadDescriptor
the client SDK posts the bytes to; getPresignedDownload(key, expiresIn) returns
a PresignedDownloadDescriptor.
const upload = await storageService.getPresignedUpload?.(
'attachments/tasks/tsk_01HQ4A7B/document.pdf',
3600,
{ contentType: 'application/pdf' },
);
// { uploadUrl, method: 'PUT', headers?, expiresIn, downloadUrl? }export interface PresignedUploadDescriptor {
uploadUrl: string;
method: 'PUT' | 'POST';
headers?: Record<string, string>;
expiresIn: number;
downloadUrl?: string;
}
export interface PresignedDownloadDescriptor {
downloadUrl: string;
expiresIn: number;
}S3-compatible adapters support multipart (chunked) uploads for large files. All
chunked methods are optional. ETags from uploadChunk must be collected and
passed to completeChunkedUpload in order.
const uploadId = await storageService.initiateChunkedUpload?.(
'attachments/tasks/tsk_01HQ4A7B/large-video.mp4',
{ contentType: 'video/mp4' },
);
const eTag1 = await storageService.uploadChunk?.(uploadId, 1, chunk1);
const eTag2 = await storageService.uploadChunk?.(uploadId, 2, chunk2);
const key = await storageService.completeChunkedUpload?.(uploadId, [
{ partNumber: 1, eTag: eTag1 },
{ partNumber: 2, eTag: eTag2 },
]);
// On failure, release uploaded parts:
// await storageService.abortChunkedUpload?.(uploadId);The return type for file metadata (getInfo, list).
export interface StorageFileInfo {
key: string;
size: number;
contentType?: string;
lastModified: Date;
metadata?: Record<string, string>;
}