@file-goblin/upload-goblin
Version:
Alchemy-powered file uploader for Cloudflare Workers with R2 and S3 support
193 lines (184 loc) • 6.97 kB
text/typescript
import { R2Bucket } from '@cloudflare/workers-types';
import * as alchemy_cloudflare from 'alchemy/cloudflare';
import { R2Bucket as R2Bucket$1, FetchWorkerProps, Worker } from 'alchemy/cloudflare';
import { Secret } from 'alchemy';
type Error = {
code: number;
message: string;
};
type Ok<T> = {
readonly tag: "ok";
readonly value: T;
readonly ok: true;
};
type Err<E> = {
readonly tag: "err";
readonly error: E;
readonly ok: false;
};
type Result<T, E> = Ok<T> | Err<E>;
type AsyncResult<T, E> = Promise<Result<T, E>>;
type SyncOrAsyncResult<T, E> = Result<T, E> | AsyncResult<T, E>;
declare function safeAsync<T>(promise: Promise<T>): AsyncResult<T, Error>;
declare function ok<E = Error>(): Result<void, E>;
declare function ok<T, E = Error>(value: T): Result<T, E>;
declare const err: <T, E = Error>(error: E) => Result<T, E>;
type FileContents = ReadableStream | ArrayBuffer | Blob | string;
type DefaultContext = Record<string, any>;
type BlobStorage<TContext = DefaultContext> = {
putBytes: (id: string, content: FileContents, metadata?: Record<string, any>, context?: TContext) => AsyncResult<void, Error>;
getBytes: (id: string, context?: TContext) => AsyncResult<{
data: ReadableStream;
metadata: Record<string, any>;
}, Error>;
deleteBytes: (id: string, context?: TContext) => AsyncResult<void, Error>;
};
type UploadRequest<TContext = DefaultContext> = {
req: Request;
context: TContext;
};
type UploadComplete<TContext = DefaultContext> = {
req: Request;
fileId: string;
metadata: Record<string, any>;
context: TContext;
};
type RequestValidationRequest<TContext = DefaultContext> = {
req: Request;
fileId: string;
context: TContext;
};
type DownloadValidationRequest<TContext = DefaultContext> = {
req: Request;
fileId: string;
context: TContext;
};
type DownloadValidation<TContext = DefaultContext> = ((request: DownloadValidationRequest<TContext>) => Promise<{
valid: boolean;
error?: string;
}> | {
valid: boolean;
error?: string;
}) | string | undefined;
interface UploaderConfig<TContext extends Record<string, any> = DefaultContext> {
storage: BlobStorage<TContext>;
validateUploadRequest: (request: RequestValidationRequest<TContext>) => SyncOrAsyncResult<void, Error>;
downloadValidation?: DownloadValidation<TContext>;
contextFn?: (req: Request) => Promise<TContext> | TContext;
preUpload?: (request: UploadRequest<TContext>) => SyncOrAsyncResult<void, Error>;
postUpload?: (request: UploadComplete<TContext>) => SyncOrAsyncResult<void, Error>;
}
/**
* Creates a file uploader with signature validation and post-upload hooks.
*
* @param config Configuration for the uploader including storage and validation
* @returns Object with upload handler function
*/
declare function createUploader<TContext extends Record<string, any> = Record<string, any>>(config: UploaderConfig<TContext>): {
/**
* Handles file upload with PUT method, signature validation, and post-upload hook
*
* @param req The incoming request
* @returns Response indicating success or failure
*/
handleUpload: (req: Request) => Promise<Response>;
/**
* Handles file download with optional validation
*
* @param req The incoming request
* @returns Response with file content and metadata headers
*/
handleDownload: (req: Request) => Promise<Response>;
};
/**
* Implementation of BlobStorage using Cloudflare R2 for object storage.
* This provides methods for storing, retrieving, and deleting binary data.
*/
declare const R2BlobStorage: <TContext>(bucket: R2Bucket) => BlobStorage<TContext>;
type S3Config = {
region: string;
endpoint: string;
credentials: {
accessKeyId: string;
secretAccessKey: string;
};
};
/**
* Creates an S3-compatible BlobStorage implementation using aws4fetch
* Works well with Web ReadableStream in browser/edge environments
*
* @param config S3 configuration including region, endpoint, and credentials
* @returns A BlobStorage implementation using S3
*/
declare function S3BlobStorage<TContext = Record<string, any>>(config: S3Config): BlobStorage<TContext>;
/**
* Validates an upload signature and enforces signed constraints
*/
declare function validateSignature(token: string, secretKey: string, request: Request): {
valid: boolean;
payload?: any;
error?: string;
};
/**
* Generates a signed upload token
*/
declare function generateUploadSignature(payload: {
fileId: string;
expiresAt: number;
[key: string]: any;
}, secretKey: string): string;
interface GoblinUploaderResourceConfig {
name: string;
uploadValidation: Secret | ((request: RequestValidationRequest) => Promise<{
valid: boolean;
error?: string;
}> | {
valid: boolean;
error?: string;
});
storage: {
type: "r2";
bucket: R2Bucket$1;
} | {
type: "s3";
credentials: {
accessKeyId: Secret;
secretAccessKey: Secret;
};
endpoint: string;
region: string;
} | {
type: "custom";
storage: BlobStorage;
};
downloadValidation?: Secret | ((request: {
req: Request;
fileId: string;
context: any;
}) => Promise<{
valid: boolean;
error?: string;
}> | {
valid: boolean;
error?: string;
}) | undefined;
preUpload?: UploaderConfig["preUpload"];
postUpload?: UploaderConfig["postUpload"];
contextFn?: UploaderConfig["contextFn"];
workerConfig?: Pick<FetchWorkerProps, "url" | "env" | "observability" | "accountId" | "apiKey" | "apiToken">;
}
declare function GoblinUploader(name: string, config: GoblinUploaderResourceConfig): Promise<{
worker: Promise<Worker<alchemy_cloudflare.Bindings, Rpc.WorkerEntrypointBranded>> & {
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
connect(address: SocketAddress | string, options?: SocketOptions): Socket;
queue(queueName: string, messages: ServiceBindingQueueMessage[]): Promise<FetcherQueueResult>;
scheduled(options?: FetcherScheduledOptions): Promise<FetcherScheduledResult>;
};
bucket: any;
storage: BlobStorage;
uploader: {
handleUpload: (req: Request) => Promise<Response>;
handleDownload: (req: Request) => Promise<Response>;
};
}>;
export { type AsyncResult, type BlobStorage, type DefaultContext, type DownloadValidation, type DownloadValidationRequest, type Err, type Error, type FileContents, GoblinUploader, type GoblinUploaderResourceConfig, type Ok, R2BlobStorage, type RequestValidationRequest, type Result, S3BlobStorage, type S3Config, type SyncOrAsyncResult, type UploadComplete, type UploadRequest, type UploaderConfig, createUploader, err, generateUploadSignature, ok, safeAsync, validateSignature };