pushduck
Version:
The fastest way to add file uploads to any web application. Enterprise security, edge-ready. Works with 16+ frameworks and 5+ storage providers. No heavy AWS SDK required.
873 lines (866 loc) • 29.3 kB
text/typescript
import { AwsClient } from "aws4fetch";
import { NextRequest } from "next/server";
//#region src/core/providers/providers.d.ts
/**
* Cloud Storage Providers System
*
* This provides a clean way to configure different cloud storage providers
* with environment-based configuration and type-safe initialization.
*
* Supported Providers:
* - AWS S3, Cloudflare R2, DigitalOcean Spaces, MinIO
*
* Future Provider Support:
* - Enterprise: Azure Blob, IBM Cloud, Oracle OCI
* - Cost-Optimized: Wasabi, Backblaze B2, Storj DCS
* - Specialized: Telnyx, Tigris, Cloudian HyperStore
*/
interface BaseProviderConfig {
provider: string;
region?: string;
bucket: string;
acl?: string;
customDomain?: string;
forcePathStyle?: boolean;
}
interface AWSProviderConfig extends BaseProviderConfig {
provider: "aws";
accessKeyId: string;
secretAccessKey: string;
region: string;
sessionToken?: string;
}
interface CloudflareR2Config extends BaseProviderConfig {
provider: "cloudflare-r2";
accountId: string;
accessKeyId: string;
secretAccessKey: string;
region?: "auto";
endpoint?: string;
}
interface DigitalOceanSpacesConfig extends BaseProviderConfig {
provider: "digitalocean-spaces";
accessKeyId: string;
secretAccessKey: string;
region: string;
endpoint?: string;
}
interface MinIOConfig extends BaseProviderConfig {
provider: "minio";
endpoint: string;
accessKeyId: string;
secretAccessKey: string;
useSSL?: boolean;
port?: number;
}
interface AzureBlobConfig extends BaseProviderConfig {
provider: "azure-blob";
accountName: string;
accessKeyId: string;
secretAccessKey: string;
endpoint?: string;
}
interface IBMCloudConfig extends BaseProviderConfig {
provider: "ibm-cloud";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
serviceInstanceId?: string;
}
interface OracleOCIConfig extends BaseProviderConfig {
provider: "oracle-oci";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
namespace?: string;
}
interface WasabiConfig extends BaseProviderConfig {
provider: "wasabi";
accessKeyId: string;
secretAccessKey: string;
endpoint?: string;
}
interface BackblazeB2Config extends BaseProviderConfig {
provider: "backblaze-b2";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
}
interface StorjDCSConfig extends BaseProviderConfig {
provider: "storj-dcs";
accessKeyId: string;
secretAccessKey: string;
endpoint?: string;
}
interface TelnyxStorageConfig extends BaseProviderConfig {
provider: "telnyx-storage";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
}
interface TigrisDataConfig extends BaseProviderConfig {
provider: "tigris-data";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
region?: "auto";
}
interface CloudianHyperStoreConfig extends BaseProviderConfig {
provider: "cloudian-hyperstore";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
}
interface GoogleCloudStorageConfig extends BaseProviderConfig {
provider: "gcs";
projectId: string;
keyFilename?: string;
credentials?: object;
}
interface S3CompatibleConfig extends BaseProviderConfig {
provider: "s3-compatible";
accessKeyId: string;
secretAccessKey: string;
endpoint: string;
}
type ProviderConfig = AWSProviderConfig | CloudflareR2Config | DigitalOceanSpacesConfig | MinIOConfig | AzureBlobConfig | IBMCloudConfig | OracleOCIConfig | WasabiConfig | BackblazeB2Config | StorjDCSConfig | TelnyxStorageConfig | TigrisDataConfig | CloudianHyperStoreConfig | GoogleCloudStorageConfig | S3CompatibleConfig;
declare const PROVIDER_SPECS: {
readonly aws: {
readonly provider: "aws";
readonly configKeys: {
readonly region: readonly ["AWS_REGION", "S3_REGION"];
readonly bucket: readonly ["AWS_S3_BUCKET", "S3_BUCKET", "S3_BUCKET_NAME"];
readonly accessKeyId: readonly ["AWS_ACCESS_KEY_ID", "S3_ACCESS_KEY_ID"];
readonly secretAccessKey: readonly ["AWS_SECRET_ACCESS_KEY", "S3_SECRET_ACCESS_KEY"];
readonly sessionToken: readonly ["AWS_SESSION_TOKEN"];
readonly acl: readonly ["S3_ACL"];
readonly customDomain: readonly ["S3_CUSTOM_DOMAIN"];
};
readonly defaults: {
readonly region: "us-east-1";
readonly acl: "private";
};
};
readonly cloudflareR2: {
readonly provider: "cloudflare-r2";
readonly configKeys: {
readonly accountId: readonly ["CLOUDFLARE_ACCOUNT_ID", "R2_ACCOUNT_ID"];
readonly bucket: readonly ["CLOUDFLARE_R2_BUCKET", "R2_BUCKET"];
readonly accessKeyId: readonly ["CLOUDFLARE_R2_ACCESS_KEY_ID", "R2_ACCESS_KEY_ID"];
readonly secretAccessKey: readonly ["CLOUDFLARE_R2_SECRET_ACCESS_KEY", "R2_SECRET_ACCESS_KEY"];
readonly endpoint: readonly ["CLOUDFLARE_R2_ENDPOINT", "R2_ENDPOINT"];
readonly customDomain: readonly ["R2_CUSTOM_DOMAIN"];
readonly acl: readonly [];
};
readonly defaults: {
readonly region: "auto";
readonly acl: "private";
};
readonly customLogic: (config: any, computed: any) => {
endpoint: any;
};
};
readonly digitalOceanSpaces: {
readonly provider: "digitalocean-spaces";
readonly configKeys: {
readonly region: readonly ["DO_SPACES_REGION", "DIGITALOCEAN_SPACES_REGION"];
readonly bucket: readonly ["DO_SPACES_BUCKET", "DIGITALOCEAN_SPACES_BUCKET"];
readonly accessKeyId: readonly ["DO_SPACES_ACCESS_KEY_ID", "DIGITALOCEAN_SPACES_ACCESS_KEY_ID"];
readonly secretAccessKey: readonly ["DO_SPACES_SECRET_ACCESS_KEY", "DIGITALOCEAN_SPACES_SECRET_ACCESS_KEY"];
readonly endpoint: readonly ["DO_SPACES_ENDPOINT", "DIGITALOCEAN_SPACES_ENDPOINT"];
readonly customDomain: readonly ["DO_SPACES_CUSTOM_DOMAIN"];
readonly acl: readonly [];
};
readonly defaults: {
readonly region: "nyc3";
readonly acl: "private";
};
readonly customLogic: (config: any, computed: any) => {
endpoint: any;
};
};
readonly minio: {
readonly provider: "minio";
readonly configKeys: {
readonly endpoint: readonly ["MINIO_ENDPOINT"];
readonly bucket: readonly ["MINIO_BUCKET"];
readonly accessKeyId: readonly ["MINIO_ACCESS_KEY_ID", "MINIO_ACCESS_KEY"];
readonly secretAccessKey: readonly ["MINIO_SECRET_ACCESS_KEY", "MINIO_SECRET_KEY"];
readonly region: readonly ["MINIO_REGION"];
readonly customDomain: readonly ["MINIO_CUSTOM_DOMAIN"];
readonly acl: readonly [];
};
readonly defaults: {
readonly endpoint: "localhost:9000";
readonly region: "us-east-1";
readonly acl: "private";
};
readonly customLogic: (config: any, computed: any) => {
useSSL: any;
port: number | undefined;
};
};
readonly gcs: {
readonly provider: "gcs";
readonly configKeys: {
readonly projectId: readonly ["GOOGLE_CLOUD_PROJECT_ID", "GCS_PROJECT_ID"];
readonly bucket: readonly ["GCS_BUCKET", "GOOGLE_CLOUD_STORAGE_BUCKET"];
readonly keyFilename: readonly ["GOOGLE_APPLICATION_CREDENTIALS", "GCS_KEY_FILE"];
readonly region: readonly ["GCS_REGION"];
readonly customDomain: readonly ["GCS_CUSTOM_DOMAIN"];
readonly acl: readonly [];
};
readonly defaults: {
readonly region: "us-central1";
readonly acl: "private";
};
readonly customLogic: (config: any) => {
credentials: any;
};
};
readonly s3Compatible: {
readonly provider: "s3-compatible";
readonly configKeys: {
readonly endpoint: readonly ["S3_ENDPOINT", "S3_COMPATIBLE_ENDPOINT"];
readonly bucket: readonly ["S3_BUCKET", "S3_BUCKET_NAME"];
readonly accessKeyId: readonly ["S3_ACCESS_KEY_ID", "ACCESS_KEY_ID"];
readonly secretAccessKey: readonly ["S3_SECRET_ACCESS_KEY", "SECRET_ACCESS_KEY"];
readonly region: readonly ["S3_REGION", "REGION"];
readonly customDomain: readonly ["S3_CUSTOM_DOMAIN"];
readonly acl: readonly ["S3_ACL"];
};
readonly defaults: {
readonly region: "us-east-1";
readonly acl: "private";
readonly forcePathStyle: true;
};
};
};
type ProviderSpecsType = typeof PROVIDER_SPECS;
type ProviderType = keyof ProviderSpecsType;
/**
* Maps each provider type to its corresponding configuration interface
* This enables type-safe provider configuration in createUploadConfig().provider()
*/
type ProviderConfigMap = {
aws: Partial<Omit<AWSProviderConfig, "provider">>;
cloudflareR2: Partial<Omit<CloudflareR2Config, "provider">>;
digitalOceanSpaces: Partial<Omit<DigitalOceanSpacesConfig, "provider">>;
minio: Partial<Omit<MinIOConfig, "provider">>;
gcs: Partial<Omit<GoogleCloudStorageConfig, "provider">>;
s3Compatible: Partial<Omit<S3CompatibleConfig, "provider">>;
};
/**
* Type-safe provider configuration function
* Usage: createProvider("aws", { bucket: "my-bucket", region: "us-west-2" })
*/
declare function createProvider<T extends ProviderType>(type: T, config?: ProviderConfigMap[T]): ProviderConfig;
declare function validateProviderConfig(config: ProviderConfig): {
valid: boolean;
errors: string[];
};
declare function getProviderEndpoint(config: ProviderConfig): string;
//#endregion
//#region src/core/schema.d.ts
interface S3FileConstraints {
maxSize?: string | number;
minSize?: string | number;
allowedTypes?: string[];
allowedExtensions?: string[];
required?: boolean;
}
interface S3ArrayConstraints {
min?: number;
max?: number;
length?: number;
}
interface S3ValidationContext {
file: File;
fieldName: string;
allFiles?: Record<string, File | File[]>;
}
interface S3ValidationResult {
success: boolean;
error?: {
code: string;
message: string;
path: string[];
};
data?: any;
}
interface S3TransformContext<T = any> {
file: File;
metadata?: Record<string, any>;
originalData: T;
}
declare abstract class S3Schema<TInput = any, TOutput = TInput> {
protected _constraints: Record<string, any>;
protected _transforms: Array<(ctx: S3TransformContext<TInput>) => Promise<any> | any>;
protected _validators: Array<(ctx: S3ValidationContext) => S3ValidationResult | Promise<S3ValidationResult>>;
protected _optional: boolean;
abstract _type: string;
abstract _parse(input: unknown): S3ValidationResult | Promise<S3ValidationResult>;
validate(input: unknown, context?: Partial<S3ValidationContext>): Promise<S3ValidationResult>;
optional(): S3Schema<TInput, TOutput | undefined>;
transform<TNewOutput>(transformer: (ctx: S3TransformContext<TOutput>) => Promise<TNewOutput> | TNewOutput): S3Schema<TInput, TNewOutput>;
refine(validator: (ctx: S3ValidationContext) => boolean | Promise<boolean>, message: string): this;
protected abstract _clone(): this;
}
declare class S3FileSchema extends S3Schema<File, File> {
protected constraints: S3FileConstraints;
_type: "file";
constructor(constraints?: S3FileConstraints);
_parse(input: unknown): S3ValidationResult;
max(size: string | number): S3FileSchema;
min(size: string | number): S3FileSchema;
types(allowedTypes: string[]): S3FileSchema;
extensions(allowedExtensions: string[]): S3FileSchema;
array(constraints?: S3ArrayConstraints): S3ArraySchema<this>;
protected _clone(): this;
middleware<TMetadata>(middleware: (ctx: {
req: any;
file: {
name: string;
size: number;
type: string;
};
metadata: any;
}) => Promise<TMetadata> | TMetadata): S3Route<this, TMetadata>;
onUploadStart(hook: (ctx: {
file: {
name: string;
size: number;
type: string;
};
metadata: any;
}) => Promise<void> | void): S3Route<this, any>;
onUploadComplete(hook: (ctx: {
file: {
name: string;
size: number;
type: string;
};
metadata: any;
url?: string;
key?: string;
}) => Promise<void> | void): S3Route<this, any>;
onUploadError(hook: (ctx: {
file: {
name: string;
size: number;
type: string;
};
metadata: any;
error: Error;
}) => Promise<void> | void): S3Route<this, any>;
private _parseSize;
private _formatSize;
}
declare class S3ImageSchema extends S3FileSchema {
constructor(constraints?: S3FileConstraints);
formats(formats: string[]): S3ImageSchema;
max(size: string | number): S3ImageSchema;
min(size: string | number): S3ImageSchema;
types(allowedTypes: string[]): S3ImageSchema;
extensions(allowedExtensions: string[]): S3ImageSchema;
array(constraints?: S3ArrayConstraints): S3ArraySchema<this>;
protected _clone(): this;
}
declare class S3ArraySchema<T extends S3Schema> extends S3Schema<File[], File[]> {
private elementSchema;
private arrayConstraints;
_type: "array";
constructor(elementSchema: T, arrayConstraints?: S3ArrayConstraints);
_parse(input: unknown): Promise<S3ValidationResult>;
min(count: number): S3ArraySchema<T>;
max(count: number): S3ArraySchema<T>;
length(count: number): S3ArraySchema<T>;
protected _clone(): this;
}
declare class S3ObjectSchema<T extends Record<string, S3Schema>> extends S3Schema<{ [K in keyof T]: T[K] extends S3Schema<any, infer U> ? U : never }, { [K in keyof T]: T[K] extends S3Schema<any, infer U> ? U : never }> {
private shape;
_type: "object";
constructor(shape: T);
_parse(input: unknown): Promise<S3ValidationResult>;
protected _clone(): this;
}
type InferS3Input<T extends S3Schema> = T extends S3Schema<infer I, any> ? I : never;
type InferS3Output<T extends S3Schema> = T extends S3Schema<any, infer O> ? O : never;
//#endregion
//#region src/core/storage/client.d.ts
/**
* Creates and caches an AWS client instance using aws4fetch
*/
declare function createS3Client(uploadConfig?: UploadConfig): AwsClient;
/**
* Resets the AWS client instance (useful for testing)
*/
declare function resetS3Client(): void;
interface PresignedUrlOptions {
key: string;
contentType?: string;
contentLength?: number;
expiresIn?: number;
metadata?: Record<string, string>;
}
interface PresignedUrlResult {
url: string;
key: string;
fields?: Record<string, string>;
}
/**
* Generates a presigned URL for downloading/viewing a file from S3
*/
interface FileKeyOptions {
originalName: string;
userId?: string;
prefix?: string;
preserveExtension?: boolean;
addTimestamp?: boolean;
addRandomId?: boolean;
}
/**
* Generates a unique file key for S3 storage
*/
interface UploadProgress {
loaded: number;
total: number;
percentage: number;
key: string;
progress?: number;
uploadSpeed?: number;
eta?: number;
}
type ProgressCallback = (progress: UploadProgress) => void;
/**
* Uploads a file directly to S3 with progress tracking
* Note: This is for server-side uploads. Client-side uploads use presigned URLs.
*/
interface ListFilesOptions {
prefix?: string;
maxFiles?: number;
includeMetadata?: boolean;
sortBy?: "key" | "size" | "modified";
sortOrder?: "asc" | "desc";
}
interface PaginatedListOptions extends ListFilesOptions {
pageSize?: number;
continuationToken?: string;
}
interface FileInfo {
key: string;
url: string;
size: number;
contentType: string;
lastModified: Date;
etag: string;
metadata?: Record<string, string>;
}
interface ListFilesResult {
files: FileInfo[];
continuationToken?: string;
isTruncated: boolean;
totalCount?: number;
}
/**
* Lists files in the bucket with optional filtering and pagination
*/
interface FileInfoResult {
key: string;
info: FileInfo | null;
error?: string;
}
interface FileValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
info: FileInfo;
}
interface ValidationRules {
maxSize?: number;
minSize?: number;
allowedTypes?: string[];
requiredExtensions?: string[];
customValidators?: ((info: FileInfo) => boolean | string)[];
}
/**
* Gets detailed information about a file
*/
interface DeleteFilesResult {
deleted: string[];
errors: DeleteError[];
}
interface DeleteError {
key: string;
code: string;
message: string;
}
interface DeleteByPrefixResult {
filesFound: number;
deleted: string[];
errors: DeleteError[];
dryRun: boolean;
}
//#endregion
//#region src/core/storage/storage-api.d.ts
declare class StorageInstance {
private readonly config;
constructor(config: UploadConfig);
/**
* Get the current configuration (read-only)
*/
getConfig(): Readonly<UploadConfig>;
/**
* Get provider information
*/
getProviderInfo(): {
provider: "aws" | "cloudflare-r2" | "digitalocean-spaces" | "minio" | "azure-blob" | "ibm-cloud" | "oracle-oci" | "wasabi" | "backblaze-b2" | "storj-dcs" | "telnyx-storage" | "tigris-data" | "cloudian-hyperstore" | "gcs" | "s3-compatible";
bucket: string;
region: string | undefined;
};
list: {
files: (options?: ListFilesOptions) => Promise<FileInfo[]>;
paginated: (options?: PaginatedListOptions) => Promise<ListFilesResult>;
byExtension: (extension: string, prefix?: string) => Promise<FileInfo[]>;
bySize: (minSize?: number, maxSize?: number, prefix?: string) => Promise<FileInfo[]>;
byDate: (fromDate?: Date, toDate?: Date, prefix?: string) => Promise<FileInfo[]>;
directories: (prefix?: string) => Promise<string[]>;
paginatedGenerator: (options?: PaginatedListOptions) => AsyncGenerator<FileInfo[], any, any>;
};
metadata: {
getInfo: (key: string) => Promise<FileInfo>;
getBatch: (keys: string[]) => Promise<FileInfoResult[]>;
getSize: (key: string) => Promise<number>;
getContentType: (key: string) => Promise<string>;
getLastModified: (key: string) => Promise<Date>;
getCustom: (key: string) => Promise<Record<string, string>>;
setCustom: (key: string, metadata: Record<string, string>) => Promise<void>;
};
download: {
presignedUrl: (key: string, expiresIn?: number) => Promise<string>;
url: (key: string) => string;
};
upload: {
file: (file: File | Buffer, key: string, options?: any) => Promise<string>;
presignedUrl: (options: PresignedUrlOptions) => Promise<PresignedUrlResult>;
presignedBatch: (requests: PresignedUrlOptions[]) => Promise<PresignedUrlResult[]>;
generateKey: (options: FileKeyOptions) => string;
};
validation: {
exists: (key: string) => Promise<boolean>;
existsWithInfo: (key: string) => Promise<FileInfo | null>;
validateFile: (key: string, rules: ValidationRules) => Promise<FileValidationResult>;
validateFiles: (keys: string[], rules: ValidationRules) => Promise<FileValidationResult[]>;
connection: () => Promise<{
success: boolean;
error?: string;
}>;
};
delete: {
file: (key: string) => Promise<void>;
files: (keys: string[]) => Promise<DeleteFilesResult>;
byPrefix: (prefix: string, options?: {
dryRun?: boolean;
maxFiles?: number;
}) => Promise<DeleteByPrefixResult>;
};
}
/**
* Create a new storage instance with the given configuration
*/
declare function createStorage(config: UploadConfig): StorageInstance;
//#endregion
//#region src/core/config/upload-config.d.ts
declare function createS3Instance(config: UploadConfig): {
readonly file: (constraints?: S3FileConstraints) => S3FileSchema;
readonly image: (constraints?: S3FileConstraints) => S3ImageSchema;
readonly object: <T extends Record<string, any>>(shape: T) => S3ObjectSchema<T>;
readonly createRouter: <TRoutes extends Record<string, any>>(routes: TRoutes) => S3Router<{ [K in keyof TRoutes]: TRoutes[K] extends S3Route<any, any> ? TRoutes[K] : S3Route<any, any> }>;
};
interface UploadConfig {
provider: ProviderConfig;
debug?: boolean;
enableMetrics?: boolean;
defaults?: {
maxFileSize?: string | number;
allowedFileTypes?: string[];
acl?: string;
metadata?: Record<string, any>;
};
paths?: {
prefix?: string;
generateKey?: (file: {
name: string;
type: string;
}, metadata: any) => string;
};
security?: {
requireAuth?: boolean;
allowedOrigins?: string[];
rateLimiting?: {
maxUploads?: number;
windowMs?: number;
};
};
hooks?: {
onUploadStart?: (ctx: {
file: any;
metadata: any;
}) => Promise<void> | void;
onUploadComplete?: (ctx: {
file: any;
url: string;
metadata: any;
}) => Promise<void> | void;
onUploadError?: (ctx: {
file: any;
error: Error;
metadata: any;
}) => Promise<void> | void;
};
}
declare class UploadConfigBuilder {
private config;
/**
* Set the storage provider with type-safe configuration
*/
provider(providerConfig: ProviderConfig): UploadConfigBuilder;
provider<T extends ProviderType>(type: T, config: ProviderConfigMap[T]): UploadConfigBuilder;
/**
* Set default file constraints
*/
defaults(defaults: UploadConfig["defaults"]): UploadConfigBuilder;
/**
* Configure file paths and key generation
*/
paths(paths: UploadConfig["paths"]): UploadConfigBuilder;
/**
* Configure security settings
*/
security(security: UploadConfig["security"]): UploadConfigBuilder;
/**
* Add lifecycle hooks
*/
hooks(hooks: UploadConfig["hooks"]): UploadConfigBuilder;
/**
* Enable debug mode
*/
debug(enabled?: boolean): UploadConfigBuilder;
/**
* Enable metrics collection
*/
metrics(enabled?: boolean): UploadConfigBuilder;
/**
* Build the final configuration and return configured instances
*/
build(): UploadInitResult;
}
interface UploadInitResult {
config: UploadConfig;
storage: StorageInstance;
s3: ReturnType<typeof createS3Instance>;
}
/**
* Create a new upload configuration builder
* This is the recommended way to configure pushduck
*/
declare function createUploadConfig(): UploadConfigBuilder;
//#endregion
//#region src/core/router/router-v2.d.ts
interface S3RouteContext {
req: NextRequest;
metadata?: Record<string, any>;
}
interface S3FileMetadata$1 {
name: string;
size: number;
type: string;
}
interface S3MiddlewareContext extends S3RouteContext {
file: S3FileMetadata$1;
}
interface S3LifecycleContext<T = any> {
file: S3FileMetadata$1;
metadata: T;
url?: string;
key?: string;
}
type S3Middleware<TInput = any, TOutput = any> = (ctx: S3MiddlewareContext & {
metadata: TInput;
}) => Promise<TOutput> | TOutput;
type S3LifecycleHook<T = any> = (ctx: S3LifecycleContext<T>) => Promise<void> | void;
interface PathContext<TMetadata = any> {
file: {
name: string;
type: string;
};
metadata: TMetadata;
globalConfig: {
prefix?: string;
generateKey?: (file: {
name: string;
type: string;
}, metadata: any) => string;
};
routeName: string;
}
interface S3RoutePathConfig<TMetadata = any> {
prefix?: string;
generateKey?: (ctx: PathContext<TMetadata>) => string;
suffix?: string;
}
declare class S3Route<TSchema extends S3Schema = S3Schema, TMetadata = any> {
private schema;
private config;
constructor(schema: TSchema, config?: S3RouteConfig<TMetadata>);
middleware<TNewMetadata>(middleware: S3Middleware<TMetadata, TNewMetadata>): S3Route<TSchema, TNewMetadata>;
paths(paths: S3RoutePathConfig<TMetadata>): this;
onUploadStart(hook: S3LifecycleHook<TMetadata>): this;
onUploadProgress(hook: (ctx: S3LifecycleContext<TMetadata> & {
progress: number;
}) => Promise<void> | void): this;
onUploadComplete(hook: S3LifecycleHook<TMetadata>): this;
onUploadError(hook: (ctx: S3LifecycleContext<TMetadata> & {
error: Error;
}) => Promise<void> | void): this;
_getConfig(): S3RouteConfig<TMetadata> & {
schema: TSchema;
};
}
interface S3RouteConfig<TMetadata = any> {
middleware?: S3Middleware<any, any>[];
paths?: S3RoutePathConfig<TMetadata>;
onUploadStart?: S3LifecycleHook<TMetadata>;
onUploadProgress?: (ctx: S3LifecycleContext<TMetadata> & {
progress: number;
}) => Promise<void> | void;
onUploadComplete?: S3LifecycleHook<TMetadata>;
onUploadError?: (ctx: S3LifecycleContext<TMetadata> & {
error: Error;
}) => Promise<void> | void;
}
type S3RouterDefinition = Record<string, S3Route<any, any>>;
declare class S3Router<TRoutes extends S3RouterDefinition> {
private config;
private routes;
constructor(routes: TRoutes, config: UploadConfig);
getRoute<K extends keyof TRoutes>(routeName: K): TRoutes[K] | undefined;
getRouteNames(): (keyof TRoutes)[];
get handlers(): {
GET: (request: Request) => Promise<Response>;
POST: (request: Request) => Promise<Response>;
};
generatePresignedUrls<K extends keyof TRoutes>(routeName: K, req: NextRequest, files: S3FileMetadata$1[]): Promise<PresignedUrlResponse[]>;
handleUploadComplete<K extends keyof TRoutes>(routeName: K, req: NextRequest, completions: UploadCompletion[]): Promise<CompletionResponse[]>;
}
interface PresignedUrlResponse {
success: boolean;
file: S3FileMetadata$1;
presignedUrl?: string;
key?: string;
metadata?: any;
error?: string;
}
interface UploadCompletion {
key: string;
file: S3FileMetadata$1;
metadata?: any;
}
interface CompletionResponse {
success: boolean;
key: string;
url?: string;
presignedUrl?: string;
file?: S3FileMetadata$1;
error?: string;
}
/**
* ✅ Config-aware router factory
* Creates router with explicit config dependency
*/
declare function createS3RouterWithConfig<TRoutes extends S3RouterDefinition>(routes: TRoutes, config: UploadConfig): S3Router<TRoutes>;
type InferRouterRoutes<T> = T extends S3Router<infer TRoutes> ? TRoutes : never;
type InferRouteInput<T> = T extends S3Route<infer TSchema, any> ? InferS3Input<TSchema> : never;
type InferRouteOutput<T> = T extends S3Route<infer TSchema, any> ? InferS3Output<TSchema> : never;
type InferRouteMetadata<T> = T extends S3Route<any, infer TMetadata> ? TMetadata : never;
type GetRoute<TRouter, TRouteName> = TRouter extends S3Router<infer TRoutes> ? TRouteName extends keyof TRoutes ? TRoutes[TRouteName] : never : never;
//#endregion
//#region src/types/index.d.ts
/**
* Centralized Type Definitions for pushduck
*
* This file consolidates all type definitions to prevent circular dependencies
* and provide a single source of truth for types used across the library.
*/
interface S3UploadedFile {
id: string;
name: string;
size: number;
type: string;
status: "pending" | "uploading" | "success" | "error";
progress: number;
url?: string;
key?: string;
presignedUrl?: string;
error?: string;
file?: File;
uploadStartTime?: number;
uploadSpeed?: number;
eta?: number;
}
interface S3FileMetadata {
name: string;
size: number;
type: string;
}
interface UploadRouteConfig {
endpoint?: string;
onStart?: (files: S3FileMetadata[]) => void | Promise<void>;
onSuccess?: (results: S3UploadedFile[]) => void | Promise<void>;
onError?: (error: Error) => void;
onProgress?: (progress: number) => void;
}
type S3RouteUploadConfig = UploadRouteConfig;
type RouteUploadOptions = UploadRouteConfig;
interface S3RouteUploadResult {
files: S3UploadedFile[];
uploadFiles: (files: File[]) => Promise<void>;
reset: () => void;
isUploading: boolean;
errors: string[];
progress?: number;
uploadSpeed?: number;
eta?: number;
}
interface ClientConfig {
endpoint: string;
fetcher?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
defaultOptions?: RouteUploadOptions;
}
interface TypedUploadedFile<TOutput = any> extends S3UploadedFile {
metadata?: TOutput;
constraints?: {
maxSize?: string;
formats?: readonly string[];
dimensions?: {
width?: number;
height?: number;
};
};
routeName?: string;
}
interface TypedRouteHook<TRouter = any, TRouteName extends string = string> {
files: TypedUploadedFile[];
uploadFiles: (files: File[], metadata?: any) => Promise<any[]>;
reset: () => void;
isUploading: boolean;
errors: string[];
routeName: TRouteName;
progress?: number;
uploadSpeed?: number;
eta?: number;
}
type RouterRouteNames<T> = T extends S3Router<infer TRoutes> ? keyof TRoutes : never;
type InferClientRouter<T> = T extends S3Router<infer TRoutes> ? { readonly [K in keyof TRoutes]: (options?: RouteUploadOptions) => TypedRouteHook<T, K extends string ? K : string> } : never;
//#endregion
export { AWSProviderConfig, BaseProviderConfig, ClientConfig, CloudflareR2Config, DeleteByPrefixResult, DeleteError, DeleteFilesResult, DigitalOceanSpacesConfig, FileInfo, FileInfoResult, FileKeyOptions, FileValidationResult, GetRoute, GoogleCloudStorageConfig, InferClientRouter, InferRouteInput, InferRouteMetadata, InferRouteOutput, InferRouterRoutes, InferS3Input, InferS3Output, ListFilesOptions, ListFilesResult, MinIOConfig, PaginatedListOptions, PresignedUrlOptions, PresignedUrlResult, ProgressCallback, ProviderConfig, ProviderType, RouterRouteNames, S3ArraySchema, S3FileConstraints, S3FileMetadata, S3FileSchema, S3ImageSchema, S3LifecycleContext, S3LifecycleHook, S3Middleware, S3MiddlewareContext, S3ObjectSchema, S3Route, S3RouteContext, S3RouteUploadConfig, S3RouteUploadResult, S3Router, S3RouterDefinition, S3Schema, S3UploadedFile, StorageInstance, TypedRouteHook, TypedUploadedFile, UploadConfig, UploadConfigBuilder, UploadInitResult, UploadProgress, UploadRouteConfig, ValidationRules, createProvider, createS3Client, createS3RouterWithConfig, createStorage, createUploadConfig, getProviderEndpoint, resetS3Client, validateProviderConfig };