@stoar/sdk
Version:
JavaScript/TypeScript SDK for STOAR - Decentralized file storage on Arweave
811 lines (799 loc) • 22.6 kB
TypeScript
import Arweave from 'arweave/web';
interface StoarConfig {
arweave?: Arweave;
gateway?: string;
wallet?: string | ArrayBuffer | object;
appName?: string;
appVersion?: string;
}
interface UploadOptions {
tags?: Record<string, string>;
contentType?: string;
encrypt?: boolean;
progress?: (progress: number) => void;
batch?: boolean | string;
}
interface UploadResult {
id: string;
url: string;
size: number;
contentType: string;
tags: Record<string, string>;
timestamp: number;
status?: 'completed' | 'pending';
batchId?: string;
}
interface FileMetadata {
name: string;
size: number;
contentType: string;
lastModified?: number;
}
interface BatchUploadOptions {
tags?: Record<string, string>;
bundleTags?: Record<string, string>;
progress?: (progress: {
completed: number;
total: number;
current?: string;
}) => void;
concurrent?: number;
}
interface BatchUploadResult {
bundleId?: string;
bundleUrl?: string;
files: Array<{
name: string;
id: string;
url: string;
size: number;
index: number;
}>;
totalSize: number;
totalCost: number;
}
interface S3CompatibleConfig {
bucket: string;
region?: string;
endpoint?: string;
accessKeyId?: string;
secretAccessKey?: string;
}
interface S3PutObjectParams {
Key: string;
Body: Buffer | Uint8Array | string;
ContentType?: string;
Metadata?: Record<string, string>;
ACL?: string;
}
interface S3GetObjectParams {
Key: string;
}
interface S3ListObjectsParams {
Prefix?: string;
MaxKeys?: number;
Marker?: string;
}
interface QueryOptions {
limit?: number;
after?: string;
tags?: Record<string, string>;
owner?: string;
minBlock?: number;
maxBlock?: number;
}
interface QueryResult {
id: string;
owner: string;
tags: Record<string, string>;
block: {
height: number;
timestamp: number;
};
fee: string;
quantity: string;
}
interface WalletInterface {
address: string;
sign(data: Uint8Array): Promise<Uint8Array>;
encrypt?(data: Uint8Array, publicKey: string): Promise<Uint8Array>;
decrypt?(data: Uint8Array): Promise<Uint8Array>;
}
declare class StoarError extends Error {
code: string;
details?: unknown;
constructor(message: string, code: string, details?: unknown);
}
declare class UploadError extends StoarError {
constructor(message: string, details?: unknown);
}
declare class BundleError extends StoarError {
constructor(message: string, details?: unknown);
}
declare class WalletError extends StoarError {
constructor(message: string, details?: unknown);
}
declare class InsufficientBalanceError extends StoarError {
required: string;
available: string;
constructor(required: string, available: string, details?: unknown);
}
interface BatchOptions {
maxFiles?: number;
maxBytes?: number;
timeout?: number;
autoCommit?: boolean;
tags?: Record<string, string>;
bundleTags?: Record<string, string>;
}
interface BatchCommitResult {
batchId: string;
bundleId: string;
bundleUrl: string;
fileCount: number;
totalSize: number;
totalCost: number;
files: Array<{
name: string;
id: string;
url: string;
size: number;
index: number;
}>;
}
interface BatchStatus {
batchId: string;
fileCount: number;
totalSize: number;
status: 'open' | 'processing' | 'completed' | 'failed';
createdAt: number;
error?: string;
}
declare class StoarClient$1 {
private arweave;
private wallet?;
private config;
private batchQueues;
private batchStatuses;
private defaultBatchId?;
constructor(config?: StoarConfig);
/**
* Initialize the client with a wallet
*/
init(walletSource?: string | ArrayBuffer | object): Promise<void>;
/**
* Upload a single file to Arweave
*/
uploadFile(data: Buffer | Uint8Array | string, metadata: FileMetadata, options?: UploadOptions): Promise<UploadResult>;
/**
* Upload multiple files as an AR bundle
*/
uploadBatch(files: Array<{
data: Buffer | Uint8Array;
metadata: FileMetadata;
}>, options?: BatchUploadOptions): Promise<BatchUploadResult>;
/**
* Query transactions using GraphQL
*/
query(options?: QueryOptions): Promise<QueryResult[]>;
/**
* Get file data by transaction ID
*/
getFile(transactionId: string): Promise<Uint8Array>;
/**
* Get wallet address
*/
getAddress(): string;
/**
* Get wallet balance in AR
*/
getBalance(): Promise<string>;
/**
* Create a new batch for bundling files
*/
createBatch(options?: BatchOptions): string;
/**
* Add a file to a specific batch
*/
addToBatch(batchId: string, data: Buffer | Uint8Array | string, metadata: FileMetadata, options?: UploadOptions): Promise<string>;
/**
* Commit a batch and upload as bundle
*/
commitBatch(batchId: string): Promise<BatchCommitResult>;
/**
* Get batch status
*/
getBatchStatus(batchId: string): BatchStatus | null;
/**
* Enable auto-batching for all uploads
*/
enableBatching(options?: BatchOptions): void;
/**
* Disable auto-batching and commit any pending files
*/
disableBatching(): Promise<BatchCommitResult | void>;
/**
* Check if wallet has sufficient balance for upload
* @throws InsufficientBalanceError if balance is 0 or negative
*/
private checkBalance;
/**
* Helper method to extract tags from transaction
*/
private extractTags;
/**
* Helper method to build tag query for ArQL
*/
private buildTagQuery;
}
/**
* Arweave manifest structure according to spec
*/
interface ArweaveManifest {
manifest: 'arweave/paths';
version: '0.2.0';
index: {
path: string;
id: string;
};
paths: Record<string, {
id: string;
}>;
fallback?: {
id: string;
};
}
/**
* Transaction record stored locally
*/
interface TransactionRecord {
txId: string;
timestamp: number;
type: 'file' | 'manifest' | 'metadata' | 'versions' | 'index';
bucketName?: string;
key?: string;
size: number;
contentType?: string;
tags?: Record<string, string>;
data?: any;
}
/**
* Bucket state tracked locally
*/
interface BucketState {
bucketName: string;
currentManifestId: string;
manifestVersion: number;
previousManifests: string[];
files: Record<string, {
txId: string;
version: number;
size: number;
uploadedAt: number;
}>;
metadata: {
owner: string;
createdAt: number;
visibility: 'public' | 'private';
};
}
/**
* Local storage configuration
*/
interface LocalStorageConfig {
baseDir?: string;
}
/**
* Manages local storage for development and testing
* Tracks all transactions and allows state reconstruction
*/
declare class LocalStorageManager {
private baseDir;
private transactionsDir;
private bucketsDir;
private manifestsDir;
constructor(config?: LocalStorageConfig);
/**
* Ensure all required directories exist
*/
private ensureDirectories;
/**
* Record a transaction
*/
recordTransaction(record: TransactionRecord): void;
/**
* Get a transaction record
*/
getTransaction(txId: string): TransactionRecord | null;
/**
* Record a manifest
*/
recordManifest(manifestId: string, manifest: ArweaveManifest, bucketName: string): void;
/**
* Get a manifest
*/
getManifest(manifestId: string): {
manifest: ArweaveManifest;
bucketName: string;
} | null;
/**
* Update bucket state based on transaction
*/
private updateBucketState;
/**
* Get bucket state
*/
getBucketState(bucketName: string): BucketState | null;
/**
* List all buckets
*/
listBuckets(): string[];
/**
* Get manifest history for a bucket
*/
getManifestHistory(bucketName: string): {
version: number;
manifestId: string;
timestamp: number;
}[];
/**
* Get file icon based on extension/type
*/
private getFileIcon;
/**
* Export bucket state as HTML for visualization
*/
exportBucketAsHtml(bucketName: string): string;
/**
* Update bucket with manifest changes
*/
updateBucketFromManifest(bucketName: string, manifestId: string, updates: Array<{
operation: 'add' | 'remove';
key: string;
txId?: string;
size?: number;
}>, owner?: string): void;
/**
* Clear all local storage
*/
clear(): void;
}
/**
* S3-compatible API layer for STOAR
* Provides familiar S3-like methods for interacting with Arweave storage
*/
declare class StoarS3Client {
private stoarClient;
private config;
private localStorage;
private manifestManager;
private versionManager;
private bucketRegistry;
private costCalculator;
constructor(stoarClient: StoarClient$1, config: S3CompatibleConfig, localStorageConfig?: LocalStorageConfig);
/**
* Put an object (equivalent to S3 putObject)
* Now with version support
*/
putObject(params: S3PutObjectParams): Promise<{
ETag: string;
Location: string;
Bucket: string;
Key: string;
VersionId: string;
}>;
/**
* Get an object (equivalent to S3 getObject)
* Now supports version retrieval
*/
getObject(params: S3GetObjectParams & {
VersionId?: string;
}): Promise<{
Body: Uint8Array;
ContentType?: string;
ContentLength: number;
ETag: string;
LastModified: Date;
VersionId: string;
Metadata: Record<string, string>;
}>;
/**
* List objects (equivalent to S3 listObjects)
*/
listObjects(params?: S3ListObjectsParams): Promise<{
IsTruncated: boolean;
Contents: Array<{
Key: string;
LastModified: Date;
ETag: string;
Size: number;
StorageClass: string;
}>;
Name: string;
Prefix?: string;
MaxKeys: number;
Marker?: string;
}>;
/**
* Delete an object (equivalent to S3 deleteObject)
* Note: In Arweave, data is permanent and cannot be deleted
*/
deleteObject(params: S3GetObjectParams): Promise<never>;
/**
* Check if an object exists (equivalent to S3 headObject)
*/
headObject(params: S3GetObjectParams): Promise<{
ContentType?: string;
ContentLength: number;
ETag: string;
LastModified: Date;
Metadata: Record<string, string>;
}>;
/**
* Copy an object (equivalent to S3 copyObject)
*/
copyObject(params: {
CopySource: string;
Key: string;
Metadata?: Record<string, string>;
MetadataDirective?: 'COPY' | 'REPLACE';
}): Promise<{
ETag: string;
LastModified: Date;
}>;
/**
* Helper method to get data size
*/
private getDataSize;
/**
* Helper method to convert S3 metadata to Arweave tags
*/
private convertMetadataToTags;
/**
* Helper method to extract S3 metadata from Arweave tags
*/
private extractS3Metadata;
/**
* Create a new bucket with manifest support
*/
createBucket(bucketName: string): Promise<{
manifestId: string;
bucketUrl: string;
}>;
/**
* Update a bucket manifest with new files
*/
updateBucketManifest(params: {
bucketName: string;
manifestId: string;
updates: Array<{
operation: 'add' | 'remove';
key: string;
txId?: string;
size?: number;
}>;
}): Promise<{
manifestId: string;
bucketUrl: string;
version: number;
}>;
/**
* Get bucket info from local storage
*/
getBucketInfo(bucketName: string): {
state: ReturnType<LocalStorageManager['getBucketState']>;
manifestHistory: ReturnType<LocalStorageManager['getManifestHistory']>;
};
/**
* List all buckets from local storage
*/
listLocalBuckets(): string[];
/**
* Export bucket as HTML
*/
exportBucketHtml(bucketName: string): string;
/**
* List all versions of objects in a bucket
*/
listObjectVersions(params: {
Bucket: string;
Prefix?: string;
KeyMarker?: string;
VersionIdMarker?: string;
MaxKeys?: number;
}): Promise<{
IsTruncated: boolean;
KeyMarker?: string;
VersionIdMarker?: string;
Versions: Array<{
Key: string;
VersionId: string;
IsLatest: boolean;
LastModified: Date;
ETag: string;
Size: number;
Owner: string;
}>;
}>;
/**
* Get bucket history by traversing manifest chain
*/
getBucketHistory(bucket: string, limit?: number): Promise<{
History: Array<{
ManifestId: string;
ManifestVersion: number;
Timestamp: Date;
Operation: string;
ChangedKey?: string;
Actor: string;
PreviousManifest?: string;
}>;
}>;
/**
* Estimate costs for operations
*/
estimateCost(operations: Array<{
type: 'putObject' | 'createBucket' | 'updateManifest';
size?: number;
}>): Promise<{
breakdown: Array<{
operation: string;
costAR: string;
costUSD: string;
}>;
totalAR: string;
totalUSD: string;
warning?: string;
}>;
/**
* Format bytes to human readable format
*/
private formatBytes;
/**
* List all buckets (S3-compatible)
* Discovers buckets from the blockchain
*/
listBuckets(): Promise<{
Buckets: Array<{
Name: string;
CreationDate: Date;
}>;
Owner: {
ID: string;
DisplayName: string;
};
}>;
/**
* Remove an object from manifest (soft delete)
* The file data still exists on Arweave but won't appear in bucket listings
*/
removeFromManifest(bucket: string, key: string): Promise<{
ManifestVersion: number;
ManifestId: string;
RemovedKey: string;
Note: string;
}>;
/**
* Get file icon based on extension/type
*/
private getFileIcon;
/**
* Generate index.html for a bucket
*/
private generateIndexHtml;
}
declare abstract class BaseBrowserWallet<TArweave> implements WalletInterface {
protected arweave: TArweave;
address: string;
protected constructor(arweave: TArweave);
protected connect(): Promise<void>;
sign(data: Uint8Array): Promise<Uint8Array>;
getPublicKey(): Promise<string>;
encrypt(data: Uint8Array, publicKey: string): Promise<Uint8Array>;
decrypt(data: Uint8Array): Promise<Uint8Array>;
}
declare abstract class BaseJSONWallet<TArweave> implements WalletInterface {
protected arweave: TArweave;
readonly jwk: any;
address: string;
protected constructor(arweave: TArweave, jwk: any);
protected abstract jwkToAddress(jwk: any): Promise<string>;
protected abstract cryptoSign(jwk: any, data: Uint8Array): Promise<Uint8Array>;
protected abstract cryptoEncrypt(data: Uint8Array, publicKey: string): Promise<Uint8Array>;
protected abstract cryptoDecrypt(jwk: any, data: Uint8Array): Promise<Uint8Array>;
sign(data: Uint8Array): Promise<Uint8Array>;
getPublicKey(): Promise<string>;
encrypt(data: Uint8Array, publicKey: string): Promise<Uint8Array>;
decrypt(data: Uint8Array): Promise<Uint8Array>;
}
declare global {
interface Window {
arweaveWallet?: {
connect(permissions: string[]): Promise<void>;
disconnect(): Promise<void>;
getActiveAddress(): Promise<string>;
getActivePublicKey(): Promise<string>;
getAllAddresses(): Promise<string[]>;
signature(data: Uint8Array, algorithm: any): Promise<ArrayBuffer>;
encrypt(data: Uint8Array, publicKey: string): Promise<ArrayBuffer>;
decrypt(data: Uint8Array): Promise<ArrayBuffer>;
sign(transaction: any): Promise<any>;
};
}
}
declare class BrowserWallet extends BaseBrowserWallet<Arweave> implements WalletInterface {
static create(arweave: Arweave): Promise<BrowserWallet>;
}
declare class JSONWallet extends BaseJSONWallet<Arweave> implements WalletInterface {
static create(arweave: Arweave, jwkOrString: any | string): Promise<JSONWallet>;
static fromJWK(arweave: Arweave, jwk: any): Promise<JSONWallet>;
static fromJSONString(arweave: Arweave, jsonString: string): Promise<JSONWallet>;
protected jwkToAddress(jwk: any): Promise<string>;
protected cryptoSign(jwk: any, data: Uint8Array): Promise<Uint8Array>;
protected cryptoEncrypt(data: Uint8Array, publicKey: string): Promise<Uint8Array>;
protected cryptoDecrypt(jwk: any, data: Uint8Array): Promise<Uint8Array>;
}
/**
* Get MIME type for a file based on its name or extension
* @param fileName - The file name or path
* @returns The MIME type or 'application/octet-stream' as default
*/
declare function getMimeType(fileName: string): string;
/**
* Get file extension for a MIME type
* @param mimeType - The MIME type
* @returns The file extension (without dot) or false if not found
*/
declare function getExtension(mimeType: string): string | false;
/**
* Get content type with charset for text-based MIME types
* @param fileName - The file name or path
* @returns The content type with charset if applicable
*/
declare function getContentType(fileName: string): string;
/**
* Check if a file is likely to be text-based
* @param fileName - The file name or path
* @returns True if the file is likely text-based
*/
declare function isTextFile(fileName: string): boolean;
/**
* Check if a file is an image
* @param fileName - The file name or path
* @returns True if the file is an image
*/
declare function isImageFile(fileName: string): boolean;
/**
* Check if a file is a video
* @param fileName - The file name or path
* @returns True if the file is a video
*/
declare function isVideoFile(fileName: string): boolean;
/**
* Check if a file is audio
* @param fileName - The file name or path
* @returns True if the file is audio
*/
declare function isAudioFile(fileName: string): boolean;
/**
* Get a human-readable file type category
* @param fileName - The file name or path
* @returns A category string like 'image', 'video', 'audio', 'text', 'document', or 'other'
*/
declare function getFileCategory(fileName: string): string;
/**
* GraphQL utilities for querying Arweave
*/
interface GraphQLEdge {
cursor: string;
node: {
id: string;
owner: {
address: string;
};
fee: {
winston: string;
};
quantity: {
winston: string;
};
tags: Array<{
name: string;
value: string;
}>;
block?: {
height: number;
timestamp: number;
};
};
}
interface GraphQLResponse {
data: {
transactions: {
edges: GraphQLEdge[];
pageInfo: {
hasNextPage: boolean;
};
};
};
}
interface GraphQLQueryOptions {
owners?: string[];
tags?: Array<{
name: string;
values: string[];
}>;
first?: number;
after?: string;
block?: {
min?: number;
max?: number;
};
}
/**
* Build a GraphQL query for transactions
*/
declare function buildTransactionQuery(options?: GraphQLQueryOptions): string;
/**
* Execute a GraphQL query
*/
declare function executeGraphQLQuery(query: string, gateway?: string): Promise<GraphQLResponse>;
/**
* Convert tags from array format to object format
*/
declare function tagsArrayToObject(tags: Array<{
name: string;
value: string;
}>): Record<string, string>;
declare class StoarClient {
private arweave;
private wallet?;
private config;
constructor(config?: StoarConfig);
/**
* Initialize the client with a wallet
*/
init(walletSource?: string | ArrayBuffer | object): Promise<void>;
/**
* Upload a single file to Arweave
*/
uploadFile(data: Buffer | Uint8Array | string, metadata: FileMetadata, options?: UploadOptions): Promise<UploadResult>;
/**
* Upload multiple files as an AR bundle
*/
uploadBatch(files: Array<{
data: Buffer | Uint8Array;
metadata: FileMetadata;
}>, options?: BatchUploadOptions): Promise<BatchUploadResult>;
/**
* Query transactions
*/
query(options?: QueryOptions): Promise<QueryResult[]>;
/**
* Get file data by transaction ID
*/
getFile(transactionId: string): Promise<Uint8Array>;
/**
* Get wallet address
*/
getAddress(): string;
/**
* Get wallet balance in AR
*/
getBalance(): Promise<string>;
/**
* Check if wallet has sufficient balance for upload
* @throws InsufficientBalanceError if balance is 0 or negative
*/
private checkBalance;
/**
* Helper method to extract tags from transaction
*/
private extractTags;
/**
* Helper method to build tag query for ArQL
*/
private buildTagQuery;
}
export { BrowserWallet, BundleError, InsufficientBalanceError, JSONWallet, StoarClient$1 as StoarClient, StoarClient as StoarClientNode, StoarError, StoarS3Client, UploadError, WalletError, buildTransactionQuery, StoarClient$1 as default, executeGraphQLQuery, getContentType, getExtension, getFileCategory, getMimeType, isAudioFile, isImageFile, isTextFile, isVideoFile, tagsArrayToObject };
export type { BatchCommitResult, BatchOptions, BatchStatus, BatchUploadOptions, BatchUploadResult, FileMetadata, GraphQLEdge, GraphQLQueryOptions, GraphQLResponse, QueryOptions, QueryResult, S3CompatibleConfig, S3GetObjectParams, S3ListObjectsParams, S3PutObjectParams, StoarConfig, UploadOptions, UploadResult, WalletInterface };