Skip to content

Latest commit

 

History

History
284 lines (218 loc) · 7.55 KB

File metadata and controls

284 lines (218 loc) · 7.55 KB
title IStorageService Contract
description Reference for the Storage Service contract — file upload, download, deletion, metadata, and signed URL generation

IStorageService Contract

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')`.

Interface Definition

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>;
}
Only `upload`, `download`, `delete`, `exists`, and `getInfo` are required. Listing, signed/presigned URLs, and chunked upload are optional — call them only after checking the method exists on the active adapter (the local and S3 adapters implement them).

Core Operations

upload

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);  // 245760

download

Downloads 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)

delete

Permanently removes a file from storage.

await storageService.delete('attachments/tasks/tsk_01HQ4A7B/document.pdf');

exists

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 false

getInfo

Retrieves 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', ... }

StorageUploadOptions

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
**File Path Convention:** Use the pattern `{category}/{object}/{record_id}/{filename}` for organized storage. Example: `attachments/task/tsk_01HQ4A7B/report.pdf`.

Listing Files

list

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)`);
}

URL Generation

getSignedUrl

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=..."

getPresignedUpload / getPresignedDownload

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? }

Descriptor Types

export interface PresignedUploadDescriptor {
  uploadUrl: string;
  method: 'PUT' | 'POST';
  headers?: Record<string, string>;
  expiresIn: number;
  downloadUrl?: string;
}

export interface PresignedDownloadDescriptor {
  downloadUrl: string;
  expiresIn: number;
}

Chunked Upload

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);

StorageFileInfo

The return type for file metadata (getInfo, list).

export interface StorageFileInfo {
  key: string;
  size: number;
  contentType?: string;
  lastModified: Date;
  metadata?: Record<string, string>;
}