@uplift-io/uplift 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/utils.ts","../src/builder.ts","../src/client.ts"],"sourcesContent":["export {\r\n any,\r\n audio,\r\n csv,\r\n custom,\r\n image,\r\n json,\r\n pdf,\r\n text,\r\n UploadBuilder,\r\n video,\r\n type AudioExtension,\r\n type DimensionRule,\r\n type DurationRule,\r\n type ImageExtension,\r\n type PageRule,\r\n type TextEncoding,\r\n type TextExtension,\r\n type VideoExtension\r\n} from \"./builder\";\r\nexport { createUploadClient } from \"./client\";\r\n// export { createMemoryStorage } from \"./storage/memory\";\r\nexport {\r\n UploadError,\r\n type ClientInput,\r\n type ClientOutput,\r\n type DoneContext,\r\n type KeyContext,\r\n type Middleware,\r\n type SizeValue,\r\n type StandardSchema,\r\n type StorageAdapter,\r\n type StoragePutInput,\r\n type UploadedFile,\r\n type UploadErrorCode,\r\n type UploadInputFile,\r\n type UploadClient,\r\n type UpliftApp\r\n} from \"./types\";\r\n\r\nimport type { Middleware, StorageAdapter, UpliftApp, UploadRoutes } from \"./types\";\r\n\r\nexport function uplift<TRoutes extends UploadRoutes>(config: {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown>;\r\n onUploadComplete?: UpliftApp<TRoutes>[\"onUploadComplete\"];\r\n}): UpliftApp<TRoutes> {\r\n return config;\r\n}\r\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\r\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\r\n\r\nexport type UploadInputFile = {\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string;\r\n file?: File;\r\n};\r\n\r\nexport type UploadedFile = {\r\n url: string;\r\n key: string;\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string | undefined;\r\n provider: string;\r\n};\r\n\r\nexport type UploadErrorCode =\r\n | \"FILE_TOO_LARGE\"\r\n | \"FILE_TOO_SMALL\"\r\n | \"INVALID_TYPE\"\r\n | \"AUTH_FAILED\"\r\n | \"VALIDATION_FAILED\"\r\n | \"UPLOAD_FAILED\"\r\n | \"UNKNOWN\";\r\n\r\nexport class UploadError extends Error {\r\n readonly code: UploadErrorCode;\r\n\r\n constructor(code: UploadErrorCode, message: string) {\r\n super(message);\r\n this.name = \"UploadError\";\r\n this.code = code;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n message: this.message,\r\n code: this.code\r\n };\r\n }\r\n}\r\n\r\nexport type StandardSchema<T = unknown> = {\r\n parse(input: unknown): T;\r\n};\r\n\r\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n};\r\n\r\nexport type DoneContext<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false\r\n> = TMultiple extends true\r\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\r\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\r\n\r\nexport type StoragePutInput = {\r\n key: string;\r\n file: UploadInputFile;\r\n body: File;\r\n};\r\n\r\nexport type StorageAdapter = {\r\n provider: string;\r\n put(input: StoragePutInput): Promise<UploadedFile>;\r\n};\r\n\r\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\r\n\r\nexport type UploadKind =\r\n | \"any\"\r\n | \"image\"\r\n | \"pdf\"\r\n | \"video\"\r\n | \"audio\"\r\n | \"text\"\r\n | \"json\"\r\n | \"csv\"\r\n | \"custom\";\r\n\r\nexport type UploadRouteDefinition = {\r\n kind: UploadKind;\r\n maxBytes?: number;\r\n minBytes?: number;\r\n multiple: boolean;\r\n multipleLimit?: number;\r\n auth?: Middleware<unknown>;\r\n overrideAuth: boolean;\r\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\r\n validate?: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n extensions?: string[];\r\n mimeTypes?: string[];\r\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\r\n requireSquare?: boolean;\r\n aspectRatio?: `${number}:${number}`;\r\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\r\n schema?: StandardSchema;\r\n headers?: string[];\r\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\r\n pageRule?: { min?: number; max?: number };\r\n encrypted?: boolean;\r\n durationRule?: { min?: DurationValue; max?: DurationValue };\r\n};\r\n\r\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\r\n\r\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown> | undefined;\r\n onUploadComplete?: ((ctx: {\r\n route: keyof TRoutes & string;\r\n result: UploadedFile | UploadedFile[];\r\n user: unknown;\r\n }) => void | Promise<void>) | undefined;\r\n};\r\n\r\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\r\n ? TMultiple extends true\r\n ? true\r\n : false\r\n : false;\r\n\r\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\r\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true ? UploadedFile[] : UploadedFile;\r\n\r\nexport type UploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: (\r\n input: ClientInput<TApp[\"routes\"][TRouteName]>\r\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n};\r\n","import { UploadError, type SizeValue, type UploadInputFile } from \"./types\";\r\n\r\nconst sizeUnits = {\r\n b: 1,\r\n kb: 1024,\r\n mb: 1024 * 1024,\r\n gb: 1024 * 1024 * 1024\r\n} as const;\r\n\r\nexport function parseSize(value: SizeValue): number {\r\n const match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)$/.exec(value);\r\n if (!match) throw new UploadError(\"VALIDATION_FAILED\", `Invalid size value: ${value}`);\r\n const amount = Number(match[1]);\r\n const unit = match[2] as keyof typeof sizeUnits;\r\n return Math.floor(amount * sizeUnits[unit]);\r\n}\r\n\r\nexport function extensionFor(name: string): string | undefined {\r\n const index = name.lastIndexOf(\".\");\r\n if (index < 0 || index === name.length - 1) return undefined;\r\n return name.slice(index + 1).toLowerCase();\r\n}\r\n\r\nexport function toInputFile(file: File): UploadInputFile {\r\n const extension = extensionFor(file.name);\r\n const input: UploadInputFile = {\r\n name: file.name,\r\n type: file.type,\r\n size: file.size,\r\n file\r\n };\r\n if (extension) input.extension = extension;\r\n return input;\r\n}\r\n\r\nexport function defaultKey(file: UploadInputFile): string {\r\n const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;\r\n return `${random}/${file.name}`;\r\n}\r\n\r\nexport async function readJsonFile(file: File): Promise<unknown> {\r\n try {\r\n return JSON.parse(await file.text());\r\n } catch {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Invalid JSON file.\");\r\n }\r\n}\r\n","import {\r\n type DoneContext,\r\n type DurationValue,\r\n type KeyContext,\r\n type Middleware,\r\n type SizeValue,\r\n type StandardSchema,\r\n type UploadInputFile,\r\n type UploadKind,\r\n type UploadRouteDefinition\r\n} from \"./types\";\r\nimport { parseSize } from \"./utils\";\r\n\r\nexport type ImageExtension = \"png\" | \"jpg\" | \"jpeg\" | \"webp\" | \"gif\" | \"avif\";\r\nexport type VideoExtension = \"mp4\" | \"webm\" | \"mov\" | \"avi\" | \"mkv\";\r\nexport type AudioExtension = \"mp3\" | \"wav\" | \"ogg\" | \"m4a\" | \"aac\" | \"flac\";\r\nexport type TextExtension = \"txt\" | \"md\" | \"log\";\r\nexport type TextEncoding = \"utf-8\" | \"utf-16\" | \"ascii\";\r\n\r\nexport type DimensionRule = {\r\n minWidth?: number;\r\n minHeight?: number;\r\n maxWidth?: number;\r\n maxHeight?: number;\r\n};\r\n\r\nexport type PageRule = {\r\n min?: number;\r\n max?: number;\r\n};\r\n\r\nexport type DurationRule = {\r\n min?: DurationValue;\r\n max?: DurationValue;\r\n};\r\n\r\nexport class UploadBuilder<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false,\r\n TKind extends UploadKind = UploadKind\r\n> {\r\n readonly __auth?: TAuth;\r\n readonly __meta?: TMeta;\r\n readonly __multiple?: TMultiple;\r\n readonly __kind?: TKind;\r\n readonly _def: UploadRouteDefinition;\r\n\r\n constructor(kind: TKind, mimeTypes: string[] = [], extensions: string[] = []) {\r\n this._def = {\r\n kind,\r\n multiple: false,\r\n overrideAuth: false\r\n };\r\n if (mimeTypes.length > 0) this._def.mimeTypes = mimeTypes;\r\n if (extensions.length > 0) this._def.extensions = extensions;\r\n }\r\n\r\n max(size: SizeValue): this {\r\n this._def.maxBytes = parseSize(size);\r\n return this;\r\n }\r\n\r\n min(size: SizeValue): this {\r\n this._def.minBytes = parseSize(size);\r\n return this;\r\n }\r\n\r\n multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind> {\r\n this._def.multiple = true;\r\n if (count !== undefined) this._def.multipleLimit = count;\r\n return this as unknown as UploadBuilder<TAuth, TMeta, true, TKind>;\r\n }\r\n\r\n auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind> {\r\n this._def.auth = handler as Middleware<unknown>;\r\n this._def.overrideAuth = false;\r\n return this as unknown as UploadBuilder<TUser, TMeta, TMultiple, TKind>;\r\n }\r\n\r\n overrideAuth(): this {\r\n this._def.overrideAuth = true;\r\n delete this._def.auth;\r\n return this;\r\n }\r\n\r\n key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this {\r\n this._def.key = handler as (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n return this;\r\n }\r\n\r\n meta<TNextMeta>(\r\n handler: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n }) => TNextMeta | Promise<TNextMeta>\r\n ): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind> {\r\n this._def.meta = handler as (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n }) => unknown | Promise<unknown>;\r\n return this as unknown as UploadBuilder<TAuth, TNextMeta, TMultiple, TKind>;\r\n }\r\n\r\n validate(\r\n handler: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n }) => true | string | Promise<true | string>\r\n ): this {\r\n this._def.validate = handler as (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n return this;\r\n }\r\n\r\n done(handler: (ctx: DoneContext<TAuth, TMeta, TMultiple>) => void | Promise<void>): this {\r\n this._def.done = handler as (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n return this;\r\n }\r\n\r\n types(types: string[]): this {\r\n this._def.extensions = types.map((type) => type.toLowerCase());\r\n return this;\r\n }\r\n\r\n dimensions(rule: DimensionRule): this {\r\n this._def.dimensionRule = rule;\r\n return this;\r\n }\r\n\r\n square(): this {\r\n this._def.requireSquare = true;\r\n return this;\r\n }\r\n\r\n aspectRatio(value: `${number}:${number}`): this {\r\n this._def.aspectRatio = value;\r\n return this;\r\n }\r\n\r\n encoding(value: TextEncoding): this {\r\n this._def.encoding = value;\r\n return this;\r\n }\r\n\r\n schema<TSchema extends StandardSchema>(schema: TSchema): this {\r\n this._def.schema = schema;\r\n return this;\r\n }\r\n\r\n headers(headers: string[]): this {\r\n this._def.headers = headers;\r\n return this;\r\n }\r\n\r\n delimiter(value: \",\" | \";\" | \"\\t\" | \"|\"): this {\r\n this._def.delimiter = value;\r\n return this;\r\n }\r\n\r\n pages(rule: PageRule): this {\r\n this._def.pageRule = rule;\r\n return this;\r\n }\r\n\r\n encrypted(value: boolean): this {\r\n this._def.encrypted = value;\r\n return this;\r\n }\r\n\r\n duration(rule: DurationRule): this {\r\n this._def.durationRule = rule;\r\n return this;\r\n }\r\n}\r\n\r\nexport function any(): UploadBuilder<unknown, unknown, false, \"any\"> {\r\n return new UploadBuilder(\"any\");\r\n}\r\n\r\nexport function image(): UploadBuilder<unknown, unknown, false, \"image\"> {\r\n return new UploadBuilder(\"image\", [\"image/\"], [\"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"avif\"]);\r\n}\r\n\r\nexport function pdf(): UploadBuilder<unknown, unknown, false, \"pdf\"> {\r\n return new UploadBuilder(\"pdf\", [\"application/pdf\"], [\"pdf\"]);\r\n}\r\n\r\nexport function video(): UploadBuilder<unknown, unknown, false, \"video\"> {\r\n return new UploadBuilder(\"video\", [\"video/\"], [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"]);\r\n}\r\n\r\nexport function audio(): UploadBuilder<unknown, unknown, false, \"audio\"> {\r\n return new UploadBuilder(\"audio\", [\"audio/\"], [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"aac\", \"flac\"]);\r\n}\r\n\r\nexport function text(): UploadBuilder<unknown, unknown, false, \"text\"> {\r\n return new UploadBuilder(\"text\", [\"text/\"], [\"txt\", \"md\", \"log\"]);\r\n}\r\n\r\nexport function json(): UploadBuilder<unknown, unknown, false, \"json\"> {\r\n return new UploadBuilder(\"json\", [\"application/json\"], [\"json\"]);\r\n}\r\n\r\nexport function csv(): UploadBuilder<unknown, unknown, false, \"csv\"> {\r\n return new UploadBuilder(\"csv\", [\"text/csv\"], [\"csv\"]);\r\n}\r\n\r\nexport function custom(type: string | string[]): UploadBuilder<unknown, unknown, false, \"custom\"> {\r\n const mimeTypes = Array.isArray(type) ? type : [type];\r\n return new UploadBuilder(\"custom\", mimeTypes);\r\n}\r\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\r\n\r\nexport type UploadProgressHandler = (progress: number) => void;\r\n\r\nexport function createUploadClient<TApp extends UpliftApp>(\r\n baseUrl: string,\r\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\r\n): UploadClient<TApp> {\r\n const fetcher = options.fetch ?? fetch;\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n return async (input: File | File[] | FileList) => {\r\n const files = input instanceof File ? [input] : Array.from(input);\r\n const form = new FormData();\r\n const field = files.length === 1 ? \"file\" : \"files\";\r\n for (const file of files) form.append(field, file);\r\n\r\n const url = routeUrl(baseUrl, property);\r\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\r\n return uploadWithXhr(url, form, property, options.onProgress);\r\n }\r\n\r\n options.onProgress?.(property, 0);\r\n const response = await fetcher(url, { method: \"POST\", body: form });\r\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\r\n if (!response.ok) {\r\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\r\n }\r\n options.onProgress?.(property, 100);\r\n return body.result;\r\n };\r\n }\r\n }) as UploadClient<TApp>;\r\n}\r\n\r\nfunction routeUrl(baseUrl: string, route: string): string {\r\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\r\n url.searchParams.set(\"route\", route);\r\n const value = url.toString();\r\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\r\n}\r\n\r\nfunction uploadWithXhr(\r\n url: string,\r\n form: FormData,\r\n route: string,\r\n onProgress?: (route: string, progress: number) => void\r\n): Promise<unknown> {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.upload.onprogress = (event) => {\r\n if (!event.lengthComputable) return;\r\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\r\n };\r\n xhr.onload = () => {\r\n const body = JSON.parse(xhr.responseText || \"{}\") as {\r\n result?: unknown;\r\n error?: { code: string; message: string };\r\n };\r\n if (xhr.status < 200 || xhr.status >= 300) {\r\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\r\n return;\r\n }\r\n onProgress?.(route, 100);\r\n resolve(body.result);\r\n };\r\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\r\n onProgress?.(route, 0);\r\n xhr.send(form);\r\n });\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/utils.ts","../src/builder.ts","../src/client.ts"],"sourcesContent":["export {\n any,\n audio,\n csv,\n custom,\n image,\n json,\n pdf,\n text,\n UploadBuilder,\n video,\n type AudioExtension,\n type DimensionRule,\n type DurationRule,\n type ImageExtension,\n type PageRule,\n type TextEncoding,\n type TextExtension,\n type VideoExtension\n} from \"./builder\";\nexport { createUploadClient } from \"./client\";\n// export { createMemoryStorage } from \"./storage/memory\";\nexport {\n UploadError,\n type ClientUploadedFile,\n type ClientInput,\n type ClientOutput,\n type CompatibleOutput,\n type CompatibleTransform,\n type DoneContext,\n type KeyContext,\n type Middleware,\n type OutputContext,\n type OutputNames,\n type PreparedUploadFile,\n type SizeValue,\n type StandardSchema,\n type StorageAdapter,\n type StoragePutInput,\n type TransformContext,\n type UploadedFile,\n type UploadOutput,\n type UploadErrorCode,\n type UploadInputFile,\n type UploadTransform,\n type UploadTransformFunction,\n type UploadClient,\n type UpliftApp\n} from \"./types\";\n\nimport type { Middleware, StorageAdapter, UpliftApp, UploadRoutes } from \"./types\";\n\nexport function uplift<TRoutes extends UploadRoutes>(config: {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown>;\n onUploadComplete?: UpliftApp<TRoutes>[\"onUploadComplete\"];\n}): UpliftApp<TRoutes> {\n return config;\n}\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\n\nexport type UploadInputFile = {\n name: string;\n type: string;\n size: number;\n extension?: string;\n file?: File;\n};\n\nexport type UploadedFile = {\n url: string;\n key: string;\n name: string;\n type: string;\n size: number;\n extension?: string | undefined;\n provider: string;\n outputs?: Record<string, UploadedFile> | undefined;\n};\n\nexport type ClientUploadedFile<TOutputNames extends string = never> = UploadedFile & (\n [TOutputNames] extends [never]\n ? object\n : {\n output<TName extends TOutputNames>(name: TName): UploadedFile;\n }\n);\n\nexport type UploadErrorCode =\n | \"FILE_TOO_LARGE\"\n | \"FILE_TOO_SMALL\"\n | \"INVALID_TYPE\"\n | \"AUTH_FAILED\"\n | \"VALIDATION_FAILED\"\n | \"UPLOAD_FAILED\"\n | \"UNKNOWN\";\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message: string) {\n super(message);\n this.name = \"UploadError\";\n this.code = code;\n }\n\n toJSON() {\n return {\n message: this.message,\n code: this.code\n };\n }\n}\n\nexport type StandardSchema<T = unknown> = {\n parse(input: unknown): T;\n};\n\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n};\n\nexport type DoneContext<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false\n> = TMultiple extends true\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\n\nexport type StoragePutInput = {\n key: string;\n file: UploadInputFile;\n body: File;\n};\n\nexport type StorageAdapter = {\n provider: string;\n put(input: StoragePutInput): Promise<UploadedFile>;\n delete?(key: string): Promise<void>;\n};\n\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\n\nexport type UploadKind =\n | \"any\"\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"json\"\n | \"csv\"\n | \"custom\";\n\nexport type PreparedUploadFile = {\n file: UploadInputFile;\n body: File;\n};\n\nexport type TransformContext = PreparedUploadFile;\n\nexport type UploadTransform<TKind extends UploadKind = UploadKind> = {\n readonly __kind?: TKind | undefined;\n transform(ctx: TransformContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type UploadTransformFunction<TKind extends UploadKind = UploadKind> = ((\n ctx: TransformContext\n) => File | PreparedUploadFile | Promise<File | PreparedUploadFile>) & {\n readonly __kind?: TKind | undefined;\n};\n\nexport type CompatibleTransform<TKind extends UploadKind> = TKind extends \"any\" | \"custom\"\n ? UploadTransform<UploadKind> | UploadTransformFunction<UploadKind>\n : UploadTransform<TKind> | UploadTransform<\"any\"> | UploadTransformFunction<TKind> | UploadTransformFunction<\"any\">;\n\nexport type OutputContext = PreparedUploadFile & {\n primary: UploadedFile;\n};\n\nexport type UploadOutput<TKind extends UploadKind = UploadKind, TName extends string = string> = {\n readonly __kind?: TKind | undefined;\n name: TName;\n produce(ctx: OutputContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type CompatibleOutput<TKind extends UploadKind, TName extends string = string> = TKind extends \"any\" | \"custom\"\n ? UploadOutput<UploadKind, TName>\n : UploadOutput<TKind, TName> | UploadOutput<\"any\", TName>;\n\nexport type UploadRouteDefinition = {\n kind: UploadKind;\n maxBytes?: number;\n minBytes?: number;\n multiple: boolean;\n multipleLimit?: number;\n auth?: Middleware<unknown>;\n overrideAuth: boolean;\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\n validate?: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n extensions?: string[];\n mimeTypes?: string[];\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\n requireSquare?: boolean;\n aspectRatio?: `${number}:${number}`;\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\n schema?: StandardSchema;\n headers?: string[];\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\n pageRule?: { min?: number; max?: number };\n encrypted?: boolean;\n durationRule?: { min?: DurationValue; max?: DurationValue };\n transforms?: Array<UploadTransform | UploadTransformFunction>;\n outputs?: Array<UploadOutput>;\n};\n\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\n\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown> | undefined;\n onUploadComplete?: ((ctx: {\n route: keyof TRoutes & string;\n result: UploadedFile | UploadedFile[];\n user: unknown;\n }) => void | Promise<void>) | undefined;\n};\n\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\n ? TMultiple extends true\n ? true\n : false\n : false;\n\nexport type OutputNames<TRoute> = TRoute extends { __outputs?: infer TOutputNames }\n ? TOutputNames extends string\n ? TOutputNames\n : never\n : never;\n\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true\n ? Array<ClientUploadedFile<OutputNames<TRoute>>>\n : ClientUploadedFile<OutputNames<TRoute>>;\n\nexport type UploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: (\n input: ClientInput<TApp[\"routes\"][TRouteName]>\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n};\n","import { UploadError, type SizeValue, type UploadInputFile } from \"./types\";\n\nconst sizeUnits = {\n b: 1,\n kb: 1024,\n mb: 1024 * 1024,\n gb: 1024 * 1024 * 1024\n} as const;\n\nexport function parseSize(value: SizeValue): number {\n const match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)$/.exec(value);\n if (!match) throw new UploadError(\"VALIDATION_FAILED\", `Invalid size value: ${value}`);\n const amount = Number(match[1]);\n const unit = match[2] as keyof typeof sizeUnits;\n return Math.floor(amount * sizeUnits[unit]);\n}\n\nexport function extensionFor(name: string): string | undefined {\n const index = name.lastIndexOf(\".\");\n if (index < 0 || index === name.length - 1) return undefined;\n return name.slice(index + 1).toLowerCase();\n}\n\nexport function extensionForType(type: string): string | undefined {\n const normalized = type.toLowerCase().split(\";\")[0]?.trim();\n if (!normalized) return undefined;\n const known: Record<string, string> = {\n \"image/avif\": \"avif\",\n \"image/gif\": \"gif\",\n \"image/jpeg\": \"jpg\",\n \"image/png\": \"png\",\n \"image/webp\": \"webp\",\n \"video/mp4\": \"mp4\",\n \"video/quicktime\": \"mov\",\n \"video/webm\": \"webm\",\n \"audio/aac\": \"aac\",\n \"audio/flac\": \"flac\",\n \"audio/mpeg\": \"mp3\",\n \"audio/mp4\": \"m4a\",\n \"audio/ogg\": \"ogg\",\n \"audio/wav\": \"wav\",\n \"application/json\": \"json\",\n \"application/pdf\": \"pdf\",\n \"text/csv\": \"csv\",\n \"text/plain\": \"txt\"\n };\n return known[normalized];\n}\n\nexport function toInputFile(file: File): UploadInputFile {\n const extension = extensionForType(file.type) ?? extensionFor(file.name);\n const input: UploadInputFile = {\n name: file.name,\n type: file.type,\n size: file.size,\n file\n };\n if (extension) input.extension = extension;\n return input;\n}\n\nexport function defaultKey(file: UploadInputFile): string {\n const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;\n return `${random}/${file.name}`;\n}\n\nexport async function readJsonFile(file: File): Promise<unknown> {\n try {\n return JSON.parse(await file.text());\n } catch {\n throw new UploadError(\"VALIDATION_FAILED\", \"Invalid JSON file.\");\n }\n}\n","import {\n type DoneContext,\n type DurationValue,\n type KeyContext,\n type Middleware,\n type CompatibleOutput,\n type CompatibleTransform,\n type SizeValue,\n type StandardSchema,\n type UploadInputFile,\n type UploadKind,\n type UploadOutput,\n type UploadRouteDefinition\n} from \"./types\";\nimport { parseSize } from \"./utils\";\n\nexport type ImageExtension = \"png\" | \"jpg\" | \"jpeg\" | \"webp\" | \"gif\" | \"avif\";\nexport type VideoExtension = \"mp4\" | \"webm\" | \"mov\" | \"avi\" | \"mkv\";\nexport type AudioExtension = \"mp3\" | \"wav\" | \"ogg\" | \"m4a\" | \"aac\" | \"flac\";\nexport type TextExtension = \"txt\" | \"md\" | \"log\";\nexport type TextEncoding = \"utf-8\" | \"utf-16\" | \"ascii\";\n\nexport type DimensionRule = {\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n};\n\nexport type PageRule = {\n min?: number;\n max?: number;\n};\n\nexport type DurationRule = {\n min?: DurationValue;\n max?: DurationValue;\n};\n\nexport class UploadBuilder<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false,\n TKind extends UploadKind = UploadKind,\n TOutputNames extends string = never\n> {\n readonly __auth?: TAuth;\n readonly __meta?: TMeta;\n readonly __multiple?: TMultiple;\n readonly __kind?: TKind;\n readonly __outputs?: TOutputNames;\n readonly _def: UploadRouteDefinition;\n\n constructor(kind: TKind, mimeTypes: string[] = [], extensions: string[] = []) {\n this._def = {\n kind,\n multiple: false,\n overrideAuth: false\n };\n if (mimeTypes.length > 0) this._def.mimeTypes = mimeTypes;\n if (extensions.length > 0) this._def.extensions = extensions;\n }\n\n max(size: SizeValue): this {\n this._def.maxBytes = parseSize(size);\n return this;\n }\n\n min(size: SizeValue): this {\n this._def.minBytes = parseSize(size);\n return this;\n }\n\n multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames> {\n this._def.multiple = true;\n if (count !== undefined) this._def.multipleLimit = count;\n return this as unknown as UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames>;\n }\n\n auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames> {\n this._def.auth = handler as Middleware<unknown>;\n this._def.overrideAuth = false;\n return this as unknown as UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames>;\n }\n\n overrideAuth(): this {\n this._def.overrideAuth = true;\n delete this._def.auth;\n return this;\n }\n\n key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this {\n this._def.key = handler as (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n return this;\n }\n\n meta<TNextMeta>(\n handler: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n }) => TNextMeta | Promise<TNextMeta>\n ): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames> {\n this._def.meta = handler as (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n }) => unknown | Promise<unknown>;\n return this as unknown as UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames>;\n }\n\n validate(\n handler: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n }) => true | string | Promise<true | string>\n ): this {\n this._def.validate = handler as (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n return this;\n }\n\n done(handler: (ctx: DoneContext<TAuth, TMeta, TMultiple>) => void | Promise<void>): this {\n this._def.done = handler as (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n return this;\n }\n\n types(types: string[]): this {\n this._def.extensions = types.map((type) => type.toLowerCase());\n return this;\n }\n\n dimensions(rule: DimensionRule): this {\n this._def.dimensionRule = rule;\n return this;\n }\n\n square(): this {\n this._def.requireSquare = true;\n return this;\n }\n\n aspectRatio(value: `${number}:${number}`): this {\n this._def.aspectRatio = value;\n return this;\n }\n\n encoding(value: TextEncoding): this {\n this._def.encoding = value;\n return this;\n }\n\n schema<TSchema extends StandardSchema>(schema: TSchema): this {\n this._def.schema = schema;\n return this;\n }\n\n headers(headers: string[]): this {\n this._def.headers = headers;\n return this;\n }\n\n delimiter(value: \",\" | \";\" | \"\\t\" | \"|\"): this {\n this._def.delimiter = value;\n return this;\n }\n\n pages(rule: PageRule): this {\n this._def.pageRule = rule;\n return this;\n }\n\n encrypted(value: boolean): this {\n this._def.encrypted = value;\n return this;\n }\n\n duration(rule: DurationRule): this {\n this._def.durationRule = rule;\n return this;\n }\n\n transform(...transforms: Array<CompatibleTransform<TKind>>): this {\n this._def.transforms = [...(this._def.transforms ?? []), ...transforms];\n return this;\n }\n\n outputs<const TName extends string>(\n ...outputs: Array<CompatibleOutput<TKind, TName>>\n ): UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName> {\n this._def.outputs = [...(this._def.outputs ?? []), ...(outputs as UploadOutput[])];\n return this as unknown as UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName>;\n }\n}\n\nexport function any(): UploadBuilder<unknown, unknown, false, \"any\"> {\n return new UploadBuilder(\"any\");\n}\n\nexport function image(): UploadBuilder<unknown, unknown, false, \"image\"> {\n return new UploadBuilder(\"image\", [\"image/\"], [\"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"avif\"]);\n}\n\nexport function pdf(): UploadBuilder<unknown, unknown, false, \"pdf\"> {\n return new UploadBuilder(\"pdf\", [\"application/pdf\"], [\"pdf\"]);\n}\n\nexport function video(): UploadBuilder<unknown, unknown, false, \"video\"> {\n return new UploadBuilder(\"video\", [\"video/\"], [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"]);\n}\n\nexport function audio(): UploadBuilder<unknown, unknown, false, \"audio\"> {\n return new UploadBuilder(\"audio\", [\"audio/\"], [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"aac\", \"flac\"]);\n}\n\nexport function text(): UploadBuilder<unknown, unknown, false, \"text\"> {\n return new UploadBuilder(\"text\", [\"text/\"], [\"txt\", \"md\", \"log\"]);\n}\n\nexport function json(): UploadBuilder<unknown, unknown, false, \"json\"> {\n return new UploadBuilder(\"json\", [\"application/json\"], [\"json\"]);\n}\n\nexport function csv(): UploadBuilder<unknown, unknown, false, \"csv\"> {\n return new UploadBuilder(\"csv\", [\"text/csv\"], [\"csv\"]);\n}\n\nexport function custom(type: string | string[]): UploadBuilder<unknown, unknown, false, \"custom\"> {\n const mimeTypes = Array.isArray(type) ? type : [type];\n return new UploadBuilder(\"custom\", mimeTypes);\n}\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\n\nexport type UploadProgressHandler = (progress: number) => void;\n\nexport function createUploadClient<TApp extends UpliftApp>(\n baseUrl: string,\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\n): UploadClient<TApp> {\n const fetcher = options.fetch ?? fetch;\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n return async (input: File | File[] | FileList) => {\n const files = input instanceof File ? [input] : Array.from(input);\n const form = new FormData();\n const field = files.length === 1 ? \"file\" : \"files\";\n for (const file of files) form.append(field, file);\n\n const url = routeUrl(baseUrl, property);\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\n return uploadWithXhr(url, form, property, options.onProgress);\n }\n\n options.onProgress?.(property, 0);\n const response = await fetcher(url, { method: \"POST\", body: form });\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\n if (!response.ok) {\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\n }\n options.onProgress?.(property, 100);\n return attachOutputGetters(body.result);\n };\n }\n }) as UploadClient<TApp>;\n}\n\nfunction routeUrl(baseUrl: string, route: string): string {\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\n url.searchParams.set(\"route\", route);\n const value = url.toString();\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\n}\n\nfunction uploadWithXhr(\n url: string,\n form: FormData,\n route: string,\n onProgress?: (route: string, progress: number) => void\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable) return;\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\n };\n xhr.onload = () => {\n const body = JSON.parse(xhr.responseText || \"{}\") as {\n result?: unknown;\n error?: { code: string; message: string };\n };\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\n return;\n }\n onProgress?.(route, 100);\n resolve(attachOutputGetters(body.result));\n };\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\n onProgress?.(route, 0);\n xhr.send(form);\n });\n}\n\nfunction attachOutputGetters(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));\n if (!isUploadedFileLike(result)) return result;\n if (!Object.prototype.hasOwnProperty.call(result, \"output\")) {\n Object.defineProperty(result, \"output\", {\n enumerable: false,\n value(name: string) {\n const output = result.outputs?.[name];\n if (!output) throw new UploadError(\"VALIDATION_FAILED\", `Unknown output: ${name}`);\n return attachOutputGetters(output);\n }\n });\n }\n if (result.outputs) {\n for (const output of Object.values(result.outputs)) attachOutputGetters(output);\n }\n return result;\n}\n\nfunction isUploadedFileLike(value: unknown): value is {\n outputs?: Record<string, unknown>;\n output?: (name: string) => unknown;\n} {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DurationValue, U as UploadKind, a as UploadRouteDefinition, S as SizeValue, M as Middleware, K as KeyContext, b as UploadInputFile, c as DoneContext, d as StandardSchema, e as UploadRoutes, f as StorageAdapter, g as UpliftApp } from './types-BdcszAj8.cjs';
2
- export { C as ClientInput, h as ClientOutput, i as StoragePutInput, j as UploadClient, k as UploadError, l as UploadErrorCode, m as UploadedFile } from './types-BdcszAj8.cjs';
1
+ import { D as DurationValue, U as UploadKind, a as UploadRouteDefinition, S as SizeValue, M as Middleware, K as KeyContext, b as UploadInputFile, c as DoneContext, d as StandardSchema, C as CompatibleTransform, e as CompatibleOutput, f as UploadRoutes, g as StorageAdapter, h as UpliftApp } from './types-Cw4fuNs_.cjs';
2
+ export { i as ClientInput, j as ClientOutput, k as ClientUploadedFile, O as OutputContext, l as OutputNames, P as PreparedUploadFile, m as StoragePutInput, T as TransformContext, n as UploadClient, o as UploadError, p as UploadErrorCode, q as UploadOutput, r as UploadTransform, s as UploadTransformFunction, t as UploadedFile } from './types-Cw4fuNs_.cjs';
3
3
  export { createUploadClient } from './client.cjs';
4
4
 
5
5
  type ImageExtension = "png" | "jpg" | "jpeg" | "webp" | "gif" | "avif";
@@ -21,24 +21,25 @@ type DurationRule = {
21
21
  min?: DurationValue;
22
22
  max?: DurationValue;
23
23
  };
24
- declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends boolean = false, TKind extends UploadKind = UploadKind> {
24
+ declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends boolean = false, TKind extends UploadKind = UploadKind, TOutputNames extends string = never> {
25
25
  readonly __auth?: TAuth;
26
26
  readonly __meta?: TMeta;
27
27
  readonly __multiple?: TMultiple;
28
28
  readonly __kind?: TKind;
29
+ readonly __outputs?: TOutputNames;
29
30
  readonly _def: UploadRouteDefinition;
30
31
  constructor(kind: TKind, mimeTypes?: string[], extensions?: string[]);
31
32
  max(size: SizeValue): this;
32
33
  min(size: SizeValue): this;
33
- multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind>;
34
- auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind>;
34
+ multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames>;
35
+ auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames>;
35
36
  overrideAuth(): this;
36
37
  key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this;
37
38
  meta<TNextMeta>(handler: (ctx: {
38
39
  req: Request;
39
40
  file: UploadInputFile;
40
41
  user: TAuth;
41
- }) => TNextMeta | Promise<TNextMeta>): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind>;
42
+ }) => TNextMeta | Promise<TNextMeta>): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames>;
42
43
  validate(handler: (ctx: {
43
44
  req: Request;
44
45
  file: UploadInputFile;
@@ -57,6 +58,8 @@ declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends
57
58
  pages(rule: PageRule): this;
58
59
  encrypted(value: boolean): this;
59
60
  duration(rule: DurationRule): this;
61
+ transform(...transforms: Array<CompatibleTransform<TKind>>): this;
62
+ outputs<const TName extends string>(...outputs: Array<CompatibleOutput<TKind, TName>>): UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName>;
60
63
  }
61
64
  declare function any(): UploadBuilder<unknown, unknown, false, "any">;
62
65
  declare function image(): UploadBuilder<unknown, unknown, false, "image">;
@@ -75,4 +78,4 @@ declare function uplift<TRoutes extends UploadRoutes>(config: {
75
78
  onUploadComplete?: UpliftApp<TRoutes>["onUploadComplete"];
76
79
  }): UpliftApp<TRoutes>;
77
80
 
78
- export { type AudioExtension, type DimensionRule, DoneContext, type DurationRule, type ImageExtension, KeyContext, Middleware, type PageRule, SizeValue, StandardSchema, StorageAdapter, type TextEncoding, type TextExtension, UpliftApp, UploadBuilder, UploadInputFile, type VideoExtension, any, audio, csv, custom, image, json, pdf, text, uplift, video };
81
+ export { type AudioExtension, CompatibleOutput, CompatibleTransform, type DimensionRule, DoneContext, type DurationRule, type ImageExtension, KeyContext, Middleware, type PageRule, SizeValue, StandardSchema, StorageAdapter, type TextEncoding, type TextExtension, UpliftApp, UploadBuilder, UploadInputFile, type VideoExtension, any, audio, csv, custom, image, json, pdf, text, uplift, video };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DurationValue, U as UploadKind, a as UploadRouteDefinition, S as SizeValue, M as Middleware, K as KeyContext, b as UploadInputFile, c as DoneContext, d as StandardSchema, e as UploadRoutes, f as StorageAdapter, g as UpliftApp } from './types-BdcszAj8.js';
2
- export { C as ClientInput, h as ClientOutput, i as StoragePutInput, j as UploadClient, k as UploadError, l as UploadErrorCode, m as UploadedFile } from './types-BdcszAj8.js';
1
+ import { D as DurationValue, U as UploadKind, a as UploadRouteDefinition, S as SizeValue, M as Middleware, K as KeyContext, b as UploadInputFile, c as DoneContext, d as StandardSchema, C as CompatibleTransform, e as CompatibleOutput, f as UploadRoutes, g as StorageAdapter, h as UpliftApp } from './types-Cw4fuNs_.js';
2
+ export { i as ClientInput, j as ClientOutput, k as ClientUploadedFile, O as OutputContext, l as OutputNames, P as PreparedUploadFile, m as StoragePutInput, T as TransformContext, n as UploadClient, o as UploadError, p as UploadErrorCode, q as UploadOutput, r as UploadTransform, s as UploadTransformFunction, t as UploadedFile } from './types-Cw4fuNs_.js';
3
3
  export { createUploadClient } from './client.js';
4
4
 
5
5
  type ImageExtension = "png" | "jpg" | "jpeg" | "webp" | "gif" | "avif";
@@ -21,24 +21,25 @@ type DurationRule = {
21
21
  min?: DurationValue;
22
22
  max?: DurationValue;
23
23
  };
24
- declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends boolean = false, TKind extends UploadKind = UploadKind> {
24
+ declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends boolean = false, TKind extends UploadKind = UploadKind, TOutputNames extends string = never> {
25
25
  readonly __auth?: TAuth;
26
26
  readonly __meta?: TMeta;
27
27
  readonly __multiple?: TMultiple;
28
28
  readonly __kind?: TKind;
29
+ readonly __outputs?: TOutputNames;
29
30
  readonly _def: UploadRouteDefinition;
30
31
  constructor(kind: TKind, mimeTypes?: string[], extensions?: string[]);
31
32
  max(size: SizeValue): this;
32
33
  min(size: SizeValue): this;
33
- multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind>;
34
- auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind>;
34
+ multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames>;
35
+ auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames>;
35
36
  overrideAuth(): this;
36
37
  key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this;
37
38
  meta<TNextMeta>(handler: (ctx: {
38
39
  req: Request;
39
40
  file: UploadInputFile;
40
41
  user: TAuth;
41
- }) => TNextMeta | Promise<TNextMeta>): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind>;
42
+ }) => TNextMeta | Promise<TNextMeta>): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames>;
42
43
  validate(handler: (ctx: {
43
44
  req: Request;
44
45
  file: UploadInputFile;
@@ -57,6 +58,8 @@ declare class UploadBuilder<TAuth = unknown, TMeta = unknown, TMultiple extends
57
58
  pages(rule: PageRule): this;
58
59
  encrypted(value: boolean): this;
59
60
  duration(rule: DurationRule): this;
61
+ transform(...transforms: Array<CompatibleTransform<TKind>>): this;
62
+ outputs<const TName extends string>(...outputs: Array<CompatibleOutput<TKind, TName>>): UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName>;
60
63
  }
61
64
  declare function any(): UploadBuilder<unknown, unknown, false, "any">;
62
65
  declare function image(): UploadBuilder<unknown, unknown, false, "image">;
@@ -75,4 +78,4 @@ declare function uplift<TRoutes extends UploadRoutes>(config: {
75
78
  onUploadComplete?: UpliftApp<TRoutes>["onUploadComplete"];
76
79
  }): UpliftApp<TRoutes>;
77
80
 
78
- export { type AudioExtension, type DimensionRule, DoneContext, type DurationRule, type ImageExtension, KeyContext, Middleware, type PageRule, SizeValue, StandardSchema, StorageAdapter, type TextEncoding, type TextExtension, UpliftApp, UploadBuilder, UploadInputFile, type VideoExtension, any, audio, csv, custom, image, json, pdf, text, uplift, video };
81
+ export { type AudioExtension, CompatibleOutput, CompatibleTransform, type DimensionRule, DoneContext, type DurationRule, type ImageExtension, KeyContext, Middleware, type PageRule, SizeValue, StandardSchema, StorageAdapter, type TextEncoding, type TextExtension, UpliftApp, UploadBuilder, UploadInputFile, type VideoExtension, any, audio, csv, custom, image, json, pdf, text, uplift, video };
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ var UploadBuilder = class {
35
35
  __meta;
36
36
  __multiple;
37
37
  __kind;
38
+ __outputs;
38
39
  _def;
39
40
  constructor(kind, mimeTypes = [], extensions = []) {
40
41
  this._def = {
@@ -128,6 +129,14 @@ var UploadBuilder = class {
128
129
  this._def.durationRule = rule;
129
130
  return this;
130
131
  }
132
+ transform(...transforms) {
133
+ this._def.transforms = [...this._def.transforms ?? [], ...transforms];
134
+ return this;
135
+ }
136
+ outputs(...outputs) {
137
+ this._def.outputs = [...this._def.outputs ?? [], ...outputs];
138
+ return this;
139
+ }
131
140
  };
132
141
  function any() {
133
142
  return new UploadBuilder("any");
@@ -180,7 +189,7 @@ function createUploadClient(baseUrl, options = {}) {
180
189
  throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
181
190
  }
182
191
  options.onProgress?.(property, 100);
183
- return body.result;
192
+ return attachOutputGetters(body.result);
184
193
  };
185
194
  }
186
195
  });
@@ -206,13 +215,34 @@ function uploadWithXhr(url, form, route, onProgress) {
206
215
  return;
207
216
  }
208
217
  onProgress?.(route, 100);
209
- resolve(body.result);
218
+ resolve(attachOutputGetters(body.result));
210
219
  };
211
220
  xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
212
221
  onProgress?.(route, 0);
213
222
  xhr.send(form);
214
223
  });
215
224
  }
225
+ function attachOutputGetters(result) {
226
+ if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
227
+ if (!isUploadedFileLike(result)) return result;
228
+ if (!Object.prototype.hasOwnProperty.call(result, "output")) {
229
+ Object.defineProperty(result, "output", {
230
+ enumerable: false,
231
+ value(name) {
232
+ const output = result.outputs?.[name];
233
+ if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
234
+ return attachOutputGetters(output);
235
+ }
236
+ });
237
+ }
238
+ if (result.outputs) {
239
+ for (const output of Object.values(result.outputs)) attachOutputGetters(output);
240
+ }
241
+ return result;
242
+ }
243
+ function isUploadedFileLike(value) {
244
+ return typeof value === "object" && value !== null;
245
+ }
216
246
 
217
247
  // src/index.ts
218
248
  function uplift(config) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/utils.ts","../src/builder.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\r\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\r\n\r\nexport type UploadInputFile = {\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string;\r\n file?: File;\r\n};\r\n\r\nexport type UploadedFile = {\r\n url: string;\r\n key: string;\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string | undefined;\r\n provider: string;\r\n};\r\n\r\nexport type UploadErrorCode =\r\n | \"FILE_TOO_LARGE\"\r\n | \"FILE_TOO_SMALL\"\r\n | \"INVALID_TYPE\"\r\n | \"AUTH_FAILED\"\r\n | \"VALIDATION_FAILED\"\r\n | \"UPLOAD_FAILED\"\r\n | \"UNKNOWN\";\r\n\r\nexport class UploadError extends Error {\r\n readonly code: UploadErrorCode;\r\n\r\n constructor(code: UploadErrorCode, message: string) {\r\n super(message);\r\n this.name = \"UploadError\";\r\n this.code = code;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n message: this.message,\r\n code: this.code\r\n };\r\n }\r\n}\r\n\r\nexport type StandardSchema<T = unknown> = {\r\n parse(input: unknown): T;\r\n};\r\n\r\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n};\r\n\r\nexport type DoneContext<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false\r\n> = TMultiple extends true\r\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\r\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\r\n\r\nexport type StoragePutInput = {\r\n key: string;\r\n file: UploadInputFile;\r\n body: File;\r\n};\r\n\r\nexport type StorageAdapter = {\r\n provider: string;\r\n put(input: StoragePutInput): Promise<UploadedFile>;\r\n};\r\n\r\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\r\n\r\nexport type UploadKind =\r\n | \"any\"\r\n | \"image\"\r\n | \"pdf\"\r\n | \"video\"\r\n | \"audio\"\r\n | \"text\"\r\n | \"json\"\r\n | \"csv\"\r\n | \"custom\";\r\n\r\nexport type UploadRouteDefinition = {\r\n kind: UploadKind;\r\n maxBytes?: number;\r\n minBytes?: number;\r\n multiple: boolean;\r\n multipleLimit?: number;\r\n auth?: Middleware<unknown>;\r\n overrideAuth: boolean;\r\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\r\n validate?: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n extensions?: string[];\r\n mimeTypes?: string[];\r\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\r\n requireSquare?: boolean;\r\n aspectRatio?: `${number}:${number}`;\r\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\r\n schema?: StandardSchema;\r\n headers?: string[];\r\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\r\n pageRule?: { min?: number; max?: number };\r\n encrypted?: boolean;\r\n durationRule?: { min?: DurationValue; max?: DurationValue };\r\n};\r\n\r\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\r\n\r\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown> | undefined;\r\n onUploadComplete?: ((ctx: {\r\n route: keyof TRoutes & string;\r\n result: UploadedFile | UploadedFile[];\r\n user: unknown;\r\n }) => void | Promise<void>) | undefined;\r\n};\r\n\r\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\r\n ? TMultiple extends true\r\n ? true\r\n : false\r\n : false;\r\n\r\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\r\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true ? UploadedFile[] : UploadedFile;\r\n\r\nexport type UploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: (\r\n input: ClientInput<TApp[\"routes\"][TRouteName]>\r\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n};\r\n","import { UploadError, type SizeValue, type UploadInputFile } from \"./types\";\r\n\r\nconst sizeUnits = {\r\n b: 1,\r\n kb: 1024,\r\n mb: 1024 * 1024,\r\n gb: 1024 * 1024 * 1024\r\n} as const;\r\n\r\nexport function parseSize(value: SizeValue): number {\r\n const match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)$/.exec(value);\r\n if (!match) throw new UploadError(\"VALIDATION_FAILED\", `Invalid size value: ${value}`);\r\n const amount = Number(match[1]);\r\n const unit = match[2] as keyof typeof sizeUnits;\r\n return Math.floor(amount * sizeUnits[unit]);\r\n}\r\n\r\nexport function extensionFor(name: string): string | undefined {\r\n const index = name.lastIndexOf(\".\");\r\n if (index < 0 || index === name.length - 1) return undefined;\r\n return name.slice(index + 1).toLowerCase();\r\n}\r\n\r\nexport function toInputFile(file: File): UploadInputFile {\r\n const extension = extensionFor(file.name);\r\n const input: UploadInputFile = {\r\n name: file.name,\r\n type: file.type,\r\n size: file.size,\r\n file\r\n };\r\n if (extension) input.extension = extension;\r\n return input;\r\n}\r\n\r\nexport function defaultKey(file: UploadInputFile): string {\r\n const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;\r\n return `${random}/${file.name}`;\r\n}\r\n\r\nexport async function readJsonFile(file: File): Promise<unknown> {\r\n try {\r\n return JSON.parse(await file.text());\r\n } catch {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Invalid JSON file.\");\r\n }\r\n}\r\n","import {\r\n type DoneContext,\r\n type DurationValue,\r\n type KeyContext,\r\n type Middleware,\r\n type SizeValue,\r\n type StandardSchema,\r\n type UploadInputFile,\r\n type UploadKind,\r\n type UploadRouteDefinition\r\n} from \"./types\";\r\nimport { parseSize } from \"./utils\";\r\n\r\nexport type ImageExtension = \"png\" | \"jpg\" | \"jpeg\" | \"webp\" | \"gif\" | \"avif\";\r\nexport type VideoExtension = \"mp4\" | \"webm\" | \"mov\" | \"avi\" | \"mkv\";\r\nexport type AudioExtension = \"mp3\" | \"wav\" | \"ogg\" | \"m4a\" | \"aac\" | \"flac\";\r\nexport type TextExtension = \"txt\" | \"md\" | \"log\";\r\nexport type TextEncoding = \"utf-8\" | \"utf-16\" | \"ascii\";\r\n\r\nexport type DimensionRule = {\r\n minWidth?: number;\r\n minHeight?: number;\r\n maxWidth?: number;\r\n maxHeight?: number;\r\n};\r\n\r\nexport type PageRule = {\r\n min?: number;\r\n max?: number;\r\n};\r\n\r\nexport type DurationRule = {\r\n min?: DurationValue;\r\n max?: DurationValue;\r\n};\r\n\r\nexport class UploadBuilder<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false,\r\n TKind extends UploadKind = UploadKind\r\n> {\r\n readonly __auth?: TAuth;\r\n readonly __meta?: TMeta;\r\n readonly __multiple?: TMultiple;\r\n readonly __kind?: TKind;\r\n readonly _def: UploadRouteDefinition;\r\n\r\n constructor(kind: TKind, mimeTypes: string[] = [], extensions: string[] = []) {\r\n this._def = {\r\n kind,\r\n multiple: false,\r\n overrideAuth: false\r\n };\r\n if (mimeTypes.length > 0) this._def.mimeTypes = mimeTypes;\r\n if (extensions.length > 0) this._def.extensions = extensions;\r\n }\r\n\r\n max(size: SizeValue): this {\r\n this._def.maxBytes = parseSize(size);\r\n return this;\r\n }\r\n\r\n min(size: SizeValue): this {\r\n this._def.minBytes = parseSize(size);\r\n return this;\r\n }\r\n\r\n multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind> {\r\n this._def.multiple = true;\r\n if (count !== undefined) this._def.multipleLimit = count;\r\n return this as unknown as UploadBuilder<TAuth, TMeta, true, TKind>;\r\n }\r\n\r\n auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind> {\r\n this._def.auth = handler as Middleware<unknown>;\r\n this._def.overrideAuth = false;\r\n return this as unknown as UploadBuilder<TUser, TMeta, TMultiple, TKind>;\r\n }\r\n\r\n overrideAuth(): this {\r\n this._def.overrideAuth = true;\r\n delete this._def.auth;\r\n return this;\r\n }\r\n\r\n key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this {\r\n this._def.key = handler as (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n return this;\r\n }\r\n\r\n meta<TNextMeta>(\r\n handler: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n }) => TNextMeta | Promise<TNextMeta>\r\n ): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind> {\r\n this._def.meta = handler as (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n }) => unknown | Promise<unknown>;\r\n return this as unknown as UploadBuilder<TAuth, TNextMeta, TMultiple, TKind>;\r\n }\r\n\r\n validate(\r\n handler: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n }) => true | string | Promise<true | string>\r\n ): this {\r\n this._def.validate = handler as (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n return this;\r\n }\r\n\r\n done(handler: (ctx: DoneContext<TAuth, TMeta, TMultiple>) => void | Promise<void>): this {\r\n this._def.done = handler as (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n return this;\r\n }\r\n\r\n types(types: string[]): this {\r\n this._def.extensions = types.map((type) => type.toLowerCase());\r\n return this;\r\n }\r\n\r\n dimensions(rule: DimensionRule): this {\r\n this._def.dimensionRule = rule;\r\n return this;\r\n }\r\n\r\n square(): this {\r\n this._def.requireSquare = true;\r\n return this;\r\n }\r\n\r\n aspectRatio(value: `${number}:${number}`): this {\r\n this._def.aspectRatio = value;\r\n return this;\r\n }\r\n\r\n encoding(value: TextEncoding): this {\r\n this._def.encoding = value;\r\n return this;\r\n }\r\n\r\n schema<TSchema extends StandardSchema>(schema: TSchema): this {\r\n this._def.schema = schema;\r\n return this;\r\n }\r\n\r\n headers(headers: string[]): this {\r\n this._def.headers = headers;\r\n return this;\r\n }\r\n\r\n delimiter(value: \",\" | \";\" | \"\\t\" | \"|\"): this {\r\n this._def.delimiter = value;\r\n return this;\r\n }\r\n\r\n pages(rule: PageRule): this {\r\n this._def.pageRule = rule;\r\n return this;\r\n }\r\n\r\n encrypted(value: boolean): this {\r\n this._def.encrypted = value;\r\n return this;\r\n }\r\n\r\n duration(rule: DurationRule): this {\r\n this._def.durationRule = rule;\r\n return this;\r\n }\r\n}\r\n\r\nexport function any(): UploadBuilder<unknown, unknown, false, \"any\"> {\r\n return new UploadBuilder(\"any\");\r\n}\r\n\r\nexport function image(): UploadBuilder<unknown, unknown, false, \"image\"> {\r\n return new UploadBuilder(\"image\", [\"image/\"], [\"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"avif\"]);\r\n}\r\n\r\nexport function pdf(): UploadBuilder<unknown, unknown, false, \"pdf\"> {\r\n return new UploadBuilder(\"pdf\", [\"application/pdf\"], [\"pdf\"]);\r\n}\r\n\r\nexport function video(): UploadBuilder<unknown, unknown, false, \"video\"> {\r\n return new UploadBuilder(\"video\", [\"video/\"], [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"]);\r\n}\r\n\r\nexport function audio(): UploadBuilder<unknown, unknown, false, \"audio\"> {\r\n return new UploadBuilder(\"audio\", [\"audio/\"], [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"aac\", \"flac\"]);\r\n}\r\n\r\nexport function text(): UploadBuilder<unknown, unknown, false, \"text\"> {\r\n return new UploadBuilder(\"text\", [\"text/\"], [\"txt\", \"md\", \"log\"]);\r\n}\r\n\r\nexport function json(): UploadBuilder<unknown, unknown, false, \"json\"> {\r\n return new UploadBuilder(\"json\", [\"application/json\"], [\"json\"]);\r\n}\r\n\r\nexport function csv(): UploadBuilder<unknown, unknown, false, \"csv\"> {\r\n return new UploadBuilder(\"csv\", [\"text/csv\"], [\"csv\"]);\r\n}\r\n\r\nexport function custom(type: string | string[]): UploadBuilder<unknown, unknown, false, \"custom\"> {\r\n const mimeTypes = Array.isArray(type) ? type : [type];\r\n return new UploadBuilder(\"custom\", mimeTypes);\r\n}\r\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\r\n\r\nexport type UploadProgressHandler = (progress: number) => void;\r\n\r\nexport function createUploadClient<TApp extends UpliftApp>(\r\n baseUrl: string,\r\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\r\n): UploadClient<TApp> {\r\n const fetcher = options.fetch ?? fetch;\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n return async (input: File | File[] | FileList) => {\r\n const files = input instanceof File ? [input] : Array.from(input);\r\n const form = new FormData();\r\n const field = files.length === 1 ? \"file\" : \"files\";\r\n for (const file of files) form.append(field, file);\r\n\r\n const url = routeUrl(baseUrl, property);\r\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\r\n return uploadWithXhr(url, form, property, options.onProgress);\r\n }\r\n\r\n options.onProgress?.(property, 0);\r\n const response = await fetcher(url, { method: \"POST\", body: form });\r\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\r\n if (!response.ok) {\r\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\r\n }\r\n options.onProgress?.(property, 100);\r\n return body.result;\r\n };\r\n }\r\n }) as UploadClient<TApp>;\r\n}\r\n\r\nfunction routeUrl(baseUrl: string, route: string): string {\r\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\r\n url.searchParams.set(\"route\", route);\r\n const value = url.toString();\r\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\r\n}\r\n\r\nfunction uploadWithXhr(\r\n url: string,\r\n form: FormData,\r\n route: string,\r\n onProgress?: (route: string, progress: number) => void\r\n): Promise<unknown> {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.upload.onprogress = (event) => {\r\n if (!event.lengthComputable) return;\r\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\r\n };\r\n xhr.onload = () => {\r\n const body = JSON.parse(xhr.responseText || \"{}\") as {\r\n result?: unknown;\r\n error?: { code: string; message: string };\r\n };\r\n if (xhr.status < 200 || xhr.status >= 300) {\r\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\r\n return;\r\n }\r\n onProgress?.(route, 100);\r\n resolve(body.result);\r\n };\r\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\r\n onProgress?.(route, 0);\r\n xhr.send(form);\r\n });\r\n}\r\n","export {\r\n any,\r\n audio,\r\n csv,\r\n custom,\r\n image,\r\n json,\r\n pdf,\r\n text,\r\n UploadBuilder,\r\n video,\r\n type AudioExtension,\r\n type DimensionRule,\r\n type DurationRule,\r\n type ImageExtension,\r\n type PageRule,\r\n type TextEncoding,\r\n type TextExtension,\r\n type VideoExtension\r\n} from \"./builder\";\r\nexport { createUploadClient } from \"./client\";\r\n// export { createMemoryStorage } from \"./storage/memory\";\r\nexport {\r\n UploadError,\r\n type ClientInput,\r\n type ClientOutput,\r\n type DoneContext,\r\n type KeyContext,\r\n type Middleware,\r\n type SizeValue,\r\n type StandardSchema,\r\n type StorageAdapter,\r\n type StoragePutInput,\r\n type UploadedFile,\r\n type UploadErrorCode,\r\n type UploadInputFile,\r\n type UploadClient,\r\n type UpliftApp\r\n} from \"./types\";\r\n\r\nimport type { Middleware, StorageAdapter, UpliftApp, UploadRoutes } from \"./types\";\r\n\r\nexport function uplift<TRoutes extends UploadRoutes>(config: {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown>;\r\n onUploadComplete?: UpliftApp<TRoutes>[\"onUploadComplete\"];\r\n}): UpliftApp<TRoutes> {\r\n return config;\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/utils.ts","../src/builder.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\n\nexport type UploadInputFile = {\n name: string;\n type: string;\n size: number;\n extension?: string;\n file?: File;\n};\n\nexport type UploadedFile = {\n url: string;\n key: string;\n name: string;\n type: string;\n size: number;\n extension?: string | undefined;\n provider: string;\n outputs?: Record<string, UploadedFile> | undefined;\n};\n\nexport type ClientUploadedFile<TOutputNames extends string = never> = UploadedFile & (\n [TOutputNames] extends [never]\n ? object\n : {\n output<TName extends TOutputNames>(name: TName): UploadedFile;\n }\n);\n\nexport type UploadErrorCode =\n | \"FILE_TOO_LARGE\"\n | \"FILE_TOO_SMALL\"\n | \"INVALID_TYPE\"\n | \"AUTH_FAILED\"\n | \"VALIDATION_FAILED\"\n | \"UPLOAD_FAILED\"\n | \"UNKNOWN\";\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message: string) {\n super(message);\n this.name = \"UploadError\";\n this.code = code;\n }\n\n toJSON() {\n return {\n message: this.message,\n code: this.code\n };\n }\n}\n\nexport type StandardSchema<T = unknown> = {\n parse(input: unknown): T;\n};\n\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n};\n\nexport type DoneContext<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false\n> = TMultiple extends true\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\n\nexport type StoragePutInput = {\n key: string;\n file: UploadInputFile;\n body: File;\n};\n\nexport type StorageAdapter = {\n provider: string;\n put(input: StoragePutInput): Promise<UploadedFile>;\n delete?(key: string): Promise<void>;\n};\n\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\n\nexport type UploadKind =\n | \"any\"\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"json\"\n | \"csv\"\n | \"custom\";\n\nexport type PreparedUploadFile = {\n file: UploadInputFile;\n body: File;\n};\n\nexport type TransformContext = PreparedUploadFile;\n\nexport type UploadTransform<TKind extends UploadKind = UploadKind> = {\n readonly __kind?: TKind | undefined;\n transform(ctx: TransformContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type UploadTransformFunction<TKind extends UploadKind = UploadKind> = ((\n ctx: TransformContext\n) => File | PreparedUploadFile | Promise<File | PreparedUploadFile>) & {\n readonly __kind?: TKind | undefined;\n};\n\nexport type CompatibleTransform<TKind extends UploadKind> = TKind extends \"any\" | \"custom\"\n ? UploadTransform<UploadKind> | UploadTransformFunction<UploadKind>\n : UploadTransform<TKind> | UploadTransform<\"any\"> | UploadTransformFunction<TKind> | UploadTransformFunction<\"any\">;\n\nexport type OutputContext = PreparedUploadFile & {\n primary: UploadedFile;\n};\n\nexport type UploadOutput<TKind extends UploadKind = UploadKind, TName extends string = string> = {\n readonly __kind?: TKind | undefined;\n name: TName;\n produce(ctx: OutputContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type CompatibleOutput<TKind extends UploadKind, TName extends string = string> = TKind extends \"any\" | \"custom\"\n ? UploadOutput<UploadKind, TName>\n : UploadOutput<TKind, TName> | UploadOutput<\"any\", TName>;\n\nexport type UploadRouteDefinition = {\n kind: UploadKind;\n maxBytes?: number;\n minBytes?: number;\n multiple: boolean;\n multipleLimit?: number;\n auth?: Middleware<unknown>;\n overrideAuth: boolean;\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\n validate?: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n extensions?: string[];\n mimeTypes?: string[];\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\n requireSquare?: boolean;\n aspectRatio?: `${number}:${number}`;\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\n schema?: StandardSchema;\n headers?: string[];\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\n pageRule?: { min?: number; max?: number };\n encrypted?: boolean;\n durationRule?: { min?: DurationValue; max?: DurationValue };\n transforms?: Array<UploadTransform | UploadTransformFunction>;\n outputs?: Array<UploadOutput>;\n};\n\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\n\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown> | undefined;\n onUploadComplete?: ((ctx: {\n route: keyof TRoutes & string;\n result: UploadedFile | UploadedFile[];\n user: unknown;\n }) => void | Promise<void>) | undefined;\n};\n\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\n ? TMultiple extends true\n ? true\n : false\n : false;\n\nexport type OutputNames<TRoute> = TRoute extends { __outputs?: infer TOutputNames }\n ? TOutputNames extends string\n ? TOutputNames\n : never\n : never;\n\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true\n ? Array<ClientUploadedFile<OutputNames<TRoute>>>\n : ClientUploadedFile<OutputNames<TRoute>>;\n\nexport type UploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: (\n input: ClientInput<TApp[\"routes\"][TRouteName]>\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n};\n","import { UploadError, type SizeValue, type UploadInputFile } from \"./types\";\n\nconst sizeUnits = {\n b: 1,\n kb: 1024,\n mb: 1024 * 1024,\n gb: 1024 * 1024 * 1024\n} as const;\n\nexport function parseSize(value: SizeValue): number {\n const match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)$/.exec(value);\n if (!match) throw new UploadError(\"VALIDATION_FAILED\", `Invalid size value: ${value}`);\n const amount = Number(match[1]);\n const unit = match[2] as keyof typeof sizeUnits;\n return Math.floor(amount * sizeUnits[unit]);\n}\n\nexport function extensionFor(name: string): string | undefined {\n const index = name.lastIndexOf(\".\");\n if (index < 0 || index === name.length - 1) return undefined;\n return name.slice(index + 1).toLowerCase();\n}\n\nexport function extensionForType(type: string): string | undefined {\n const normalized = type.toLowerCase().split(\";\")[0]?.trim();\n if (!normalized) return undefined;\n const known: Record<string, string> = {\n \"image/avif\": \"avif\",\n \"image/gif\": \"gif\",\n \"image/jpeg\": \"jpg\",\n \"image/png\": \"png\",\n \"image/webp\": \"webp\",\n \"video/mp4\": \"mp4\",\n \"video/quicktime\": \"mov\",\n \"video/webm\": \"webm\",\n \"audio/aac\": \"aac\",\n \"audio/flac\": \"flac\",\n \"audio/mpeg\": \"mp3\",\n \"audio/mp4\": \"m4a\",\n \"audio/ogg\": \"ogg\",\n \"audio/wav\": \"wav\",\n \"application/json\": \"json\",\n \"application/pdf\": \"pdf\",\n \"text/csv\": \"csv\",\n \"text/plain\": \"txt\"\n };\n return known[normalized];\n}\n\nexport function toInputFile(file: File): UploadInputFile {\n const extension = extensionForType(file.type) ?? extensionFor(file.name);\n const input: UploadInputFile = {\n name: file.name,\n type: file.type,\n size: file.size,\n file\n };\n if (extension) input.extension = extension;\n return input;\n}\n\nexport function defaultKey(file: UploadInputFile): string {\n const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;\n return `${random}/${file.name}`;\n}\n\nexport async function readJsonFile(file: File): Promise<unknown> {\n try {\n return JSON.parse(await file.text());\n } catch {\n throw new UploadError(\"VALIDATION_FAILED\", \"Invalid JSON file.\");\n }\n}\n","import {\n type DoneContext,\n type DurationValue,\n type KeyContext,\n type Middleware,\n type CompatibleOutput,\n type CompatibleTransform,\n type SizeValue,\n type StandardSchema,\n type UploadInputFile,\n type UploadKind,\n type UploadOutput,\n type UploadRouteDefinition\n} from \"./types\";\nimport { parseSize } from \"./utils\";\n\nexport type ImageExtension = \"png\" | \"jpg\" | \"jpeg\" | \"webp\" | \"gif\" | \"avif\";\nexport type VideoExtension = \"mp4\" | \"webm\" | \"mov\" | \"avi\" | \"mkv\";\nexport type AudioExtension = \"mp3\" | \"wav\" | \"ogg\" | \"m4a\" | \"aac\" | \"flac\";\nexport type TextExtension = \"txt\" | \"md\" | \"log\";\nexport type TextEncoding = \"utf-8\" | \"utf-16\" | \"ascii\";\n\nexport type DimensionRule = {\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n};\n\nexport type PageRule = {\n min?: number;\n max?: number;\n};\n\nexport type DurationRule = {\n min?: DurationValue;\n max?: DurationValue;\n};\n\nexport class UploadBuilder<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false,\n TKind extends UploadKind = UploadKind,\n TOutputNames extends string = never\n> {\n readonly __auth?: TAuth;\n readonly __meta?: TMeta;\n readonly __multiple?: TMultiple;\n readonly __kind?: TKind;\n readonly __outputs?: TOutputNames;\n readonly _def: UploadRouteDefinition;\n\n constructor(kind: TKind, mimeTypes: string[] = [], extensions: string[] = []) {\n this._def = {\n kind,\n multiple: false,\n overrideAuth: false\n };\n if (mimeTypes.length > 0) this._def.mimeTypes = mimeTypes;\n if (extensions.length > 0) this._def.extensions = extensions;\n }\n\n max(size: SizeValue): this {\n this._def.maxBytes = parseSize(size);\n return this;\n }\n\n min(size: SizeValue): this {\n this._def.minBytes = parseSize(size);\n return this;\n }\n\n multiple(count?: number): UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames> {\n this._def.multiple = true;\n if (count !== undefined) this._def.multipleLimit = count;\n return this as unknown as UploadBuilder<TAuth, TMeta, true, TKind, TOutputNames>;\n }\n\n auth<TUser>(handler: Middleware<TUser>): UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames> {\n this._def.auth = handler as Middleware<unknown>;\n this._def.overrideAuth = false;\n return this as unknown as UploadBuilder<TUser, TMeta, TMultiple, TKind, TOutputNames>;\n }\n\n overrideAuth(): this {\n this._def.overrideAuth = true;\n delete this._def.auth;\n return this;\n }\n\n key(handler: (ctx: KeyContext<TAuth, TMeta>) => string | Promise<string>): this {\n this._def.key = handler as (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n return this;\n }\n\n meta<TNextMeta>(\n handler: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n }) => TNextMeta | Promise<TNextMeta>\n ): UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames> {\n this._def.meta = handler as (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n }) => unknown | Promise<unknown>;\n return this as unknown as UploadBuilder<TAuth, TNextMeta, TMultiple, TKind, TOutputNames>;\n }\n\n validate(\n handler: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n }) => true | string | Promise<true | string>\n ): this {\n this._def.validate = handler as (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n return this;\n }\n\n done(handler: (ctx: DoneContext<TAuth, TMeta, TMultiple>) => void | Promise<void>): this {\n this._def.done = handler as (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n return this;\n }\n\n types(types: string[]): this {\n this._def.extensions = types.map((type) => type.toLowerCase());\n return this;\n }\n\n dimensions(rule: DimensionRule): this {\n this._def.dimensionRule = rule;\n return this;\n }\n\n square(): this {\n this._def.requireSquare = true;\n return this;\n }\n\n aspectRatio(value: `${number}:${number}`): this {\n this._def.aspectRatio = value;\n return this;\n }\n\n encoding(value: TextEncoding): this {\n this._def.encoding = value;\n return this;\n }\n\n schema<TSchema extends StandardSchema>(schema: TSchema): this {\n this._def.schema = schema;\n return this;\n }\n\n headers(headers: string[]): this {\n this._def.headers = headers;\n return this;\n }\n\n delimiter(value: \",\" | \";\" | \"\\t\" | \"|\"): this {\n this._def.delimiter = value;\n return this;\n }\n\n pages(rule: PageRule): this {\n this._def.pageRule = rule;\n return this;\n }\n\n encrypted(value: boolean): this {\n this._def.encrypted = value;\n return this;\n }\n\n duration(rule: DurationRule): this {\n this._def.durationRule = rule;\n return this;\n }\n\n transform(...transforms: Array<CompatibleTransform<TKind>>): this {\n this._def.transforms = [...(this._def.transforms ?? []), ...transforms];\n return this;\n }\n\n outputs<const TName extends string>(\n ...outputs: Array<CompatibleOutput<TKind, TName>>\n ): UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName> {\n this._def.outputs = [...(this._def.outputs ?? []), ...(outputs as UploadOutput[])];\n return this as unknown as UploadBuilder<TAuth, TMeta, TMultiple, TKind, TOutputNames | TName>;\n }\n}\n\nexport function any(): UploadBuilder<unknown, unknown, false, \"any\"> {\n return new UploadBuilder(\"any\");\n}\n\nexport function image(): UploadBuilder<unknown, unknown, false, \"image\"> {\n return new UploadBuilder(\"image\", [\"image/\"], [\"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"avif\"]);\n}\n\nexport function pdf(): UploadBuilder<unknown, unknown, false, \"pdf\"> {\n return new UploadBuilder(\"pdf\", [\"application/pdf\"], [\"pdf\"]);\n}\n\nexport function video(): UploadBuilder<unknown, unknown, false, \"video\"> {\n return new UploadBuilder(\"video\", [\"video/\"], [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"]);\n}\n\nexport function audio(): UploadBuilder<unknown, unknown, false, \"audio\"> {\n return new UploadBuilder(\"audio\", [\"audio/\"], [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"aac\", \"flac\"]);\n}\n\nexport function text(): UploadBuilder<unknown, unknown, false, \"text\"> {\n return new UploadBuilder(\"text\", [\"text/\"], [\"txt\", \"md\", \"log\"]);\n}\n\nexport function json(): UploadBuilder<unknown, unknown, false, \"json\"> {\n return new UploadBuilder(\"json\", [\"application/json\"], [\"json\"]);\n}\n\nexport function csv(): UploadBuilder<unknown, unknown, false, \"csv\"> {\n return new UploadBuilder(\"csv\", [\"text/csv\"], [\"csv\"]);\n}\n\nexport function custom(type: string | string[]): UploadBuilder<unknown, unknown, false, \"custom\"> {\n const mimeTypes = Array.isArray(type) ? type : [type];\n return new UploadBuilder(\"custom\", mimeTypes);\n}\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\n\nexport type UploadProgressHandler = (progress: number) => void;\n\nexport function createUploadClient<TApp extends UpliftApp>(\n baseUrl: string,\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\n): UploadClient<TApp> {\n const fetcher = options.fetch ?? fetch;\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n return async (input: File | File[] | FileList) => {\n const files = input instanceof File ? [input] : Array.from(input);\n const form = new FormData();\n const field = files.length === 1 ? \"file\" : \"files\";\n for (const file of files) form.append(field, file);\n\n const url = routeUrl(baseUrl, property);\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\n return uploadWithXhr(url, form, property, options.onProgress);\n }\n\n options.onProgress?.(property, 0);\n const response = await fetcher(url, { method: \"POST\", body: form });\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\n if (!response.ok) {\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\n }\n options.onProgress?.(property, 100);\n return attachOutputGetters(body.result);\n };\n }\n }) as UploadClient<TApp>;\n}\n\nfunction routeUrl(baseUrl: string, route: string): string {\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\n url.searchParams.set(\"route\", route);\n const value = url.toString();\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\n}\n\nfunction uploadWithXhr(\n url: string,\n form: FormData,\n route: string,\n onProgress?: (route: string, progress: number) => void\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable) return;\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\n };\n xhr.onload = () => {\n const body = JSON.parse(xhr.responseText || \"{}\") as {\n result?: unknown;\n error?: { code: string; message: string };\n };\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\n return;\n }\n onProgress?.(route, 100);\n resolve(attachOutputGetters(body.result));\n };\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\n onProgress?.(route, 0);\n xhr.send(form);\n });\n}\n\nfunction attachOutputGetters(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));\n if (!isUploadedFileLike(result)) return result;\n if (!Object.prototype.hasOwnProperty.call(result, \"output\")) {\n Object.defineProperty(result, \"output\", {\n enumerable: false,\n value(name: string) {\n const output = result.outputs?.[name];\n if (!output) throw new UploadError(\"VALIDATION_FAILED\", `Unknown output: ${name}`);\n return attachOutputGetters(output);\n }\n });\n }\n if (result.outputs) {\n for (const output of Object.values(result.outputs)) attachOutputGetters(output);\n }\n return result;\n}\n\nfunction isUploadedFileLike(value: unknown): value is {\n outputs?: Record<string, unknown>;\n output?: (name: string) => unknown;\n} {\n return typeof value === \"object\" && value !== null;\n}\n","export {\n any,\n audio,\n csv,\n custom,\n image,\n json,\n pdf,\n text,\n UploadBuilder,\n video,\n type AudioExtension,\n type DimensionRule,\n type DurationRule,\n type ImageExtension,\n type PageRule,\n type TextEncoding,\n type TextExtension,\n type VideoExtension\n} from \"./builder\";\nexport { createUploadClient } from \"./client\";\n// export { createMemoryStorage } from \"./storage/memory\";\nexport {\n UploadError,\n type ClientUploadedFile,\n type ClientInput,\n type ClientOutput,\n type CompatibleOutput,\n type CompatibleTransform,\n type DoneContext,\n type KeyContext,\n type Middleware,\n type OutputContext,\n type OutputNames,\n type PreparedUploadFile,\n type SizeValue,\n type StandardSchema,\n type StorageAdapter,\n type StoragePutInput,\n type TransformContext,\n type UploadedFile,\n type UploadOutput,\n type UploadErrorCode,\n type UploadInputFile,\n type UploadTransform,\n type UploadTransformFunction,\n type UploadClient,\n type UpliftApp\n} from \"./types\";\n\nimport type { Middleware, StorageAdapter, UpliftApp, UploadRoutes } from \"./types\";\n\nexport function uplift<TRoutes extends UploadRoutes>(config: {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown>;\n onUploadComplete?: UpliftApp<TRoutes>[\"onUploadComplete\"];\n}): UpliftApp<TRoutes> {\n return config;\n}\n"],"mappings":"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","names":[]}
package/dist/react.cjs CHANGED
@@ -63,7 +63,7 @@ function createUploadClient(baseUrl, options = {}) {
63
63
  throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
64
64
  }
65
65
  options.onProgress?.(property, 100);
66
- return body.result;
66
+ return attachOutputGetters(body.result);
67
67
  };
68
68
  }
69
69
  });
@@ -89,13 +89,34 @@ function uploadWithXhr(url, form, route, onProgress) {
89
89
  return;
90
90
  }
91
91
  onProgress?.(route, 100);
92
- resolve(body.result);
92
+ resolve(attachOutputGetters(body.result));
93
93
  };
94
94
  xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
95
95
  onProgress?.(route, 0);
96
96
  xhr.send(form);
97
97
  });
98
98
  }
99
+ function attachOutputGetters(result) {
100
+ if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
101
+ if (!isUploadedFileLike(result)) return result;
102
+ if (!Object.prototype.hasOwnProperty.call(result, "output")) {
103
+ Object.defineProperty(result, "output", {
104
+ enumerable: false,
105
+ value(name) {
106
+ const output = result.outputs?.[name];
107
+ if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
108
+ return attachOutputGetters(output);
109
+ }
110
+ });
111
+ }
112
+ if (result.outputs) {
113
+ for (const output of Object.values(result.outputs)) attachOutputGetters(output);
114
+ }
115
+ return result;
116
+ }
117
+ function isUploadedFileLike(value) {
118
+ return typeof value === "object" && value !== null;
119
+ }
99
120
 
100
121
  // src/react.ts
101
122
  function useUploads(baseUrl) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts","../src/types.ts","../src/client.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\r\nimport { createUploadClient } from \"./client\";\r\nimport type { ClientInput, ClientOutput, UpliftApp, UploadError } from \"./types\";\r\n\r\ntype RouteState<TData> = {\r\n progress: number;\r\n isUploading: boolean;\r\n error: UploadError | null;\r\n data: TData | null;\r\n};\r\n\r\ntype ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp[\"routes\"] & string> =\r\n ((input: ClientInput<TApp[\"routes\"][TRouteName]>) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>) &\r\n RouteState<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n\r\nexport type ReactUploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: ReactUploadMethod<TApp, TRouteName>;\r\n};\r\n\r\nexport function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp> {\r\n const [states, setStates] = useState<Record<string, RouteState<unknown>>>({});\r\n\r\n return useMemo(() => {\r\n const client = createUploadClient<TApp>(baseUrl, {\r\n onProgress(route, progress) {\r\n setStates((current) => ({\r\n ...current,\r\n [route]: { ...(current[route] ?? emptyState()), progress }\r\n }));\r\n }\r\n });\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n const state = states[property] ?? emptyState();\r\n const method = async (input: never) => {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: true, error: null }\r\n }));\r\n try {\r\n const upload = (client as Record<string, (value: never) => Promise<unknown>>)[property];\r\n if (!upload) throw new Error(`Unknown upload route: ${property}`);\r\n const data = await upload(input);\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { progress: 100, isUploading: false, error: null, data }\r\n }));\r\n return data;\r\n } catch (error) {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: false, error: error as UploadError }\r\n }));\r\n throw error;\r\n }\r\n };\r\n\r\n return Object.assign(method, state);\r\n }\r\n }) as ReactUploadClient<TApp>;\r\n }, [baseUrl, states]);\r\n}\r\n\r\nfunction emptyState(): RouteState<unknown> {\r\n return {\r\n progress: 0,\r\n isUploading: false,\r\n error: null,\r\n data: null\r\n };\r\n}\r\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\r\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\r\n\r\nexport type UploadInputFile = {\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string;\r\n file?: File;\r\n};\r\n\r\nexport type UploadedFile = {\r\n url: string;\r\n key: string;\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string | undefined;\r\n provider: string;\r\n};\r\n\r\nexport type UploadErrorCode =\r\n | \"FILE_TOO_LARGE\"\r\n | \"FILE_TOO_SMALL\"\r\n | \"INVALID_TYPE\"\r\n | \"AUTH_FAILED\"\r\n | \"VALIDATION_FAILED\"\r\n | \"UPLOAD_FAILED\"\r\n | \"UNKNOWN\";\r\n\r\nexport class UploadError extends Error {\r\n readonly code: UploadErrorCode;\r\n\r\n constructor(code: UploadErrorCode, message: string) {\r\n super(message);\r\n this.name = \"UploadError\";\r\n this.code = code;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n message: this.message,\r\n code: this.code\r\n };\r\n }\r\n}\r\n\r\nexport type StandardSchema<T = unknown> = {\r\n parse(input: unknown): T;\r\n};\r\n\r\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n};\r\n\r\nexport type DoneContext<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false\r\n> = TMultiple extends true\r\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\r\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\r\n\r\nexport type StoragePutInput = {\r\n key: string;\r\n file: UploadInputFile;\r\n body: File;\r\n};\r\n\r\nexport type StorageAdapter = {\r\n provider: string;\r\n put(input: StoragePutInput): Promise<UploadedFile>;\r\n};\r\n\r\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\r\n\r\nexport type UploadKind =\r\n | \"any\"\r\n | \"image\"\r\n | \"pdf\"\r\n | \"video\"\r\n | \"audio\"\r\n | \"text\"\r\n | \"json\"\r\n | \"csv\"\r\n | \"custom\";\r\n\r\nexport type UploadRouteDefinition = {\r\n kind: UploadKind;\r\n maxBytes?: number;\r\n minBytes?: number;\r\n multiple: boolean;\r\n multipleLimit?: number;\r\n auth?: Middleware<unknown>;\r\n overrideAuth: boolean;\r\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\r\n validate?: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n extensions?: string[];\r\n mimeTypes?: string[];\r\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\r\n requireSquare?: boolean;\r\n aspectRatio?: `${number}:${number}`;\r\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\r\n schema?: StandardSchema;\r\n headers?: string[];\r\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\r\n pageRule?: { min?: number; max?: number };\r\n encrypted?: boolean;\r\n durationRule?: { min?: DurationValue; max?: DurationValue };\r\n};\r\n\r\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\r\n\r\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown> | undefined;\r\n onUploadComplete?: ((ctx: {\r\n route: keyof TRoutes & string;\r\n result: UploadedFile | UploadedFile[];\r\n user: unknown;\r\n }) => void | Promise<void>) | undefined;\r\n};\r\n\r\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\r\n ? TMultiple extends true\r\n ? true\r\n : false\r\n : false;\r\n\r\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\r\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true ? UploadedFile[] : UploadedFile;\r\n\r\nexport type UploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: (\r\n input: ClientInput<TApp[\"routes\"][TRouteName]>\r\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n};\r\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\r\n\r\nexport type UploadProgressHandler = (progress: number) => void;\r\n\r\nexport function createUploadClient<TApp extends UpliftApp>(\r\n baseUrl: string,\r\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\r\n): UploadClient<TApp> {\r\n const fetcher = options.fetch ?? fetch;\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n return async (input: File | File[] | FileList) => {\r\n const files = input instanceof File ? [input] : Array.from(input);\r\n const form = new FormData();\r\n const field = files.length === 1 ? \"file\" : \"files\";\r\n for (const file of files) form.append(field, file);\r\n\r\n const url = routeUrl(baseUrl, property);\r\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\r\n return uploadWithXhr(url, form, property, options.onProgress);\r\n }\r\n\r\n options.onProgress?.(property, 0);\r\n const response = await fetcher(url, { method: \"POST\", body: form });\r\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\r\n if (!response.ok) {\r\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\r\n }\r\n options.onProgress?.(property, 100);\r\n return body.result;\r\n };\r\n }\r\n }) as UploadClient<TApp>;\r\n}\r\n\r\nfunction routeUrl(baseUrl: string, route: string): string {\r\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\r\n url.searchParams.set(\"route\", route);\r\n const value = url.toString();\r\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\r\n}\r\n\r\nfunction uploadWithXhr(\r\n url: string,\r\n form: FormData,\r\n route: string,\r\n onProgress?: (route: string, progress: number) => void\r\n): Promise<unknown> {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.upload.onprogress = (event) => {\r\n if (!event.lengthComputable) return;\r\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\r\n };\r\n xhr.onload = () => {\r\n const body = JSON.parse(xhr.responseText || \"{}\") as {\r\n result?: unknown;\r\n error?: { code: string; message: string };\r\n };\r\n if (xhr.status < 200 || xhr.status >= 300) {\r\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\r\n return;\r\n }\r\n onProgress?.(route, 100);\r\n resolve(body.result);\r\n };\r\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\r\n onProgress?.(route, 0);\r\n xhr.send(form);\r\n });\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.ts","../src/types.ts","../src/client.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { createUploadClient } from \"./client\";\nimport type { ClientInput, ClientOutput, UpliftApp, UploadError } from \"./types\";\n\ntype RouteState<TData> = {\n progress: number;\n isUploading: boolean;\n error: UploadError | null;\n data: TData | null;\n};\n\ntype ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp[\"routes\"] & string> =\n ((input: ClientInput<TApp[\"routes\"][TRouteName]>) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>) &\n RouteState<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n\nexport type ReactUploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: ReactUploadMethod<TApp, TRouteName>;\n};\n\nexport function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp> {\n const [states, setStates] = useState<Record<string, RouteState<unknown>>>({});\n\n return useMemo(() => {\n const client = createUploadClient<TApp>(baseUrl, {\n onProgress(route, progress) {\n setStates((current) => ({\n ...current,\n [route]: { ...(current[route] ?? emptyState()), progress }\n }));\n }\n });\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n const state = states[property] ?? emptyState();\n const method = async (input: never) => {\n setStates((current) => ({\n ...current,\n [property]: { ...(current[property] ?? emptyState()), isUploading: true, error: null }\n }));\n try {\n const upload = (client as Record<string, (value: never) => Promise<unknown>>)[property];\n if (!upload) throw new Error(`Unknown upload route: ${property}`);\n const data = await upload(input);\n setStates((current) => ({\n ...current,\n [property]: { progress: 100, isUploading: false, error: null, data }\n }));\n return data;\n } catch (error) {\n setStates((current) => ({\n ...current,\n [property]: { ...(current[property] ?? emptyState()), isUploading: false, error: error as UploadError }\n }));\n throw error;\n }\n };\n\n return Object.assign(method, state);\n }\n }) as ReactUploadClient<TApp>;\n }, [baseUrl, states]);\n}\n\nfunction emptyState(): RouteState<unknown> {\n return {\n progress: 0,\n isUploading: false,\n error: null,\n data: null\n };\n}\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\n\nexport type UploadInputFile = {\n name: string;\n type: string;\n size: number;\n extension?: string;\n file?: File;\n};\n\nexport type UploadedFile = {\n url: string;\n key: string;\n name: string;\n type: string;\n size: number;\n extension?: string | undefined;\n provider: string;\n outputs?: Record<string, UploadedFile> | undefined;\n};\n\nexport type ClientUploadedFile<TOutputNames extends string = never> = UploadedFile & (\n [TOutputNames] extends [never]\n ? object\n : {\n output<TName extends TOutputNames>(name: TName): UploadedFile;\n }\n);\n\nexport type UploadErrorCode =\n | \"FILE_TOO_LARGE\"\n | \"FILE_TOO_SMALL\"\n | \"INVALID_TYPE\"\n | \"AUTH_FAILED\"\n | \"VALIDATION_FAILED\"\n | \"UPLOAD_FAILED\"\n | \"UNKNOWN\";\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message: string) {\n super(message);\n this.name = \"UploadError\";\n this.code = code;\n }\n\n toJSON() {\n return {\n message: this.message,\n code: this.code\n };\n }\n}\n\nexport type StandardSchema<T = unknown> = {\n parse(input: unknown): T;\n};\n\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n};\n\nexport type DoneContext<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false\n> = TMultiple extends true\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\n\nexport type StoragePutInput = {\n key: string;\n file: UploadInputFile;\n body: File;\n};\n\nexport type StorageAdapter = {\n provider: string;\n put(input: StoragePutInput): Promise<UploadedFile>;\n delete?(key: string): Promise<void>;\n};\n\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\n\nexport type UploadKind =\n | \"any\"\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"json\"\n | \"csv\"\n | \"custom\";\n\nexport type PreparedUploadFile = {\n file: UploadInputFile;\n body: File;\n};\n\nexport type TransformContext = PreparedUploadFile;\n\nexport type UploadTransform<TKind extends UploadKind = UploadKind> = {\n readonly __kind?: TKind | undefined;\n transform(ctx: TransformContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type UploadTransformFunction<TKind extends UploadKind = UploadKind> = ((\n ctx: TransformContext\n) => File | PreparedUploadFile | Promise<File | PreparedUploadFile>) & {\n readonly __kind?: TKind | undefined;\n};\n\nexport type CompatibleTransform<TKind extends UploadKind> = TKind extends \"any\" | \"custom\"\n ? UploadTransform<UploadKind> | UploadTransformFunction<UploadKind>\n : UploadTransform<TKind> | UploadTransform<\"any\"> | UploadTransformFunction<TKind> | UploadTransformFunction<\"any\">;\n\nexport type OutputContext = PreparedUploadFile & {\n primary: UploadedFile;\n};\n\nexport type UploadOutput<TKind extends UploadKind = UploadKind, TName extends string = string> = {\n readonly __kind?: TKind | undefined;\n name: TName;\n produce(ctx: OutputContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type CompatibleOutput<TKind extends UploadKind, TName extends string = string> = TKind extends \"any\" | \"custom\"\n ? UploadOutput<UploadKind, TName>\n : UploadOutput<TKind, TName> | UploadOutput<\"any\", TName>;\n\nexport type UploadRouteDefinition = {\n kind: UploadKind;\n maxBytes?: number;\n minBytes?: number;\n multiple: boolean;\n multipleLimit?: number;\n auth?: Middleware<unknown>;\n overrideAuth: boolean;\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\n validate?: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n extensions?: string[];\n mimeTypes?: string[];\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\n requireSquare?: boolean;\n aspectRatio?: `${number}:${number}`;\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\n schema?: StandardSchema;\n headers?: string[];\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\n pageRule?: { min?: number; max?: number };\n encrypted?: boolean;\n durationRule?: { min?: DurationValue; max?: DurationValue };\n transforms?: Array<UploadTransform | UploadTransformFunction>;\n outputs?: Array<UploadOutput>;\n};\n\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\n\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown> | undefined;\n onUploadComplete?: ((ctx: {\n route: keyof TRoutes & string;\n result: UploadedFile | UploadedFile[];\n user: unknown;\n }) => void | Promise<void>) | undefined;\n};\n\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\n ? TMultiple extends true\n ? true\n : false\n : false;\n\nexport type OutputNames<TRoute> = TRoute extends { __outputs?: infer TOutputNames }\n ? TOutputNames extends string\n ? TOutputNames\n : never\n : never;\n\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true\n ? Array<ClientUploadedFile<OutputNames<TRoute>>>\n : ClientUploadedFile<OutputNames<TRoute>>;\n\nexport type UploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: (\n input: ClientInput<TApp[\"routes\"][TRouteName]>\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n};\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\n\nexport type UploadProgressHandler = (progress: number) => void;\n\nexport function createUploadClient<TApp extends UpliftApp>(\n baseUrl: string,\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\n): UploadClient<TApp> {\n const fetcher = options.fetch ?? fetch;\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n return async (input: File | File[] | FileList) => {\n const files = input instanceof File ? [input] : Array.from(input);\n const form = new FormData();\n const field = files.length === 1 ? \"file\" : \"files\";\n for (const file of files) form.append(field, file);\n\n const url = routeUrl(baseUrl, property);\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\n return uploadWithXhr(url, form, property, options.onProgress);\n }\n\n options.onProgress?.(property, 0);\n const response = await fetcher(url, { method: \"POST\", body: form });\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\n if (!response.ok) {\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\n }\n options.onProgress?.(property, 100);\n return attachOutputGetters(body.result);\n };\n }\n }) as UploadClient<TApp>;\n}\n\nfunction routeUrl(baseUrl: string, route: string): string {\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\n url.searchParams.set(\"route\", route);\n const value = url.toString();\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\n}\n\nfunction uploadWithXhr(\n url: string,\n form: FormData,\n route: string,\n onProgress?: (route: string, progress: number) => void\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable) return;\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\n };\n xhr.onload = () => {\n const body = JSON.parse(xhr.responseText || \"{}\") as {\n result?: unknown;\n error?: { code: string; message: string };\n };\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\n return;\n }\n onProgress?.(route, 100);\n resolve(attachOutputGetters(body.result));\n };\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\n onProgress?.(route, 0);\n xhr.send(form);\n });\n}\n\nfunction attachOutputGetters(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));\n if (!isUploadedFileLike(result)) return result;\n if (!Object.prototype.hasOwnProperty.call(result, \"output\")) {\n Object.defineProperty(result, \"output\", {\n enumerable: false,\n value(name: string) {\n const output = result.outputs?.[name];\n if (!output) throw new UploadError(\"VALIDATION_FAILED\", `Unknown output: ${name}`);\n return attachOutputGetters(output);\n }\n });\n }\n if (result.outputs) {\n for (const output of Object.values(result.outputs)) attachOutputGetters(output);\n }\n return result;\n}\n\nfunction isUploadedFileLike(value: unknown): value is {\n outputs?: Record<string, unknown>;\n output?: (name: string) => unknown;\n} {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":"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","names":[]}
package/dist/react.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as UpliftApp, C as ClientInput, h as ClientOutput, k as UploadError } from './types-BdcszAj8.cjs';
1
+ import { h as UpliftApp, i as ClientInput, j as ClientOutput, o as UploadError } from './types-Cw4fuNs_.cjs';
2
2
 
3
3
  type RouteState<TData> = {
4
4
  progress: number;
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as UpliftApp, C as ClientInput, h as ClientOutput, k as UploadError } from './types-BdcszAj8.js';
1
+ import { h as UpliftApp, i as ClientInput, j as ClientOutput, o as UploadError } from './types-Cw4fuNs_.js';
2
2
 
3
3
  type RouteState<TData> = {
4
4
  progress: number;
package/dist/react.js CHANGED
@@ -39,7 +39,7 @@ function createUploadClient(baseUrl, options = {}) {
39
39
  throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
40
40
  }
41
41
  options.onProgress?.(property, 100);
42
- return body.result;
42
+ return attachOutputGetters(body.result);
43
43
  };
44
44
  }
45
45
  });
@@ -65,13 +65,34 @@ function uploadWithXhr(url, form, route, onProgress) {
65
65
  return;
66
66
  }
67
67
  onProgress?.(route, 100);
68
- resolve(body.result);
68
+ resolve(attachOutputGetters(body.result));
69
69
  };
70
70
  xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
71
71
  onProgress?.(route, 0);
72
72
  xhr.send(form);
73
73
  });
74
74
  }
75
+ function attachOutputGetters(result) {
76
+ if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
77
+ if (!isUploadedFileLike(result)) return result;
78
+ if (!Object.prototype.hasOwnProperty.call(result, "output")) {
79
+ Object.defineProperty(result, "output", {
80
+ enumerable: false,
81
+ value(name) {
82
+ const output = result.outputs?.[name];
83
+ if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
84
+ return attachOutputGetters(output);
85
+ }
86
+ });
87
+ }
88
+ if (result.outputs) {
89
+ for (const output of Object.values(result.outputs)) attachOutputGetters(output);
90
+ }
91
+ return result;
92
+ }
93
+ function isUploadedFileLike(value) {
94
+ return typeof value === "object" && value !== null;
95
+ }
75
96
 
76
97
  // src/react.ts
77
98
  function useUploads(baseUrl) {