UNPKG

uploadthing

Version:

Learn more: [docs.uploadthing.com](https://docs.uploadthing.com)

414 lines (408 loc) 20.2 kB
const require_chunk = require('../dist/chunk-CUT6urMc.cjs'); const require_package = require('../dist/package-CKF1Vr61.cjs'); const require_deprecations = require('../dist/deprecations-DPGpmqha.cjs'); require('../dist/shared-schemas-CG9VaBtT.cjs'); const require_upload_builder = require('../dist/upload-builder-RrqQb3by.cjs'); const effect_Array = require_chunk.__toESM(require("effect/Array")); const __uploadthing_shared = require_chunk.__toESM(require("@uploadthing/shared")); const effect_Function = require_chunk.__toESM(require("effect/Function")); const effect_Predicate = require_chunk.__toESM(require("effect/Predicate")); const effect_Effect = require_chunk.__toESM(require("effect/Effect")); const __effect_platform_HttpClient = require_chunk.__toESM(require("@effect/platform/HttpClient")); const __effect_platform_HttpClientRequest = require_chunk.__toESM(require("@effect/platform/HttpClientRequest")); const __effect_platform_HttpClientResponse = require_chunk.__toESM(require("@effect/platform/HttpClientResponse")); const effect_Redacted = require_chunk.__toESM(require("effect/Redacted")); const effect_Schema = require_chunk.__toESM(require("effect/Schema")); const effect_Cause = require_chunk.__toESM(require("effect/Cause")); const __uploadthing_mime_types = require_chunk.__toESM(require("@uploadthing/mime-types")); //#region src/sdk/ut-file.ts /** * Extension of the Blob class that simplifies setting the `name` and `customId` properties, * similar to the built-in File class from Node > 20. */ var UTFile = class extends Blob { name; lastModified; customId; constructor(parts, name, options) { const optionsWithDefaults = { ...options, type: options?.type ?? ((0, __uploadthing_mime_types.lookup)(name) || "application/octet-stream"), lastModified: options?.lastModified ?? Date.now() }; super(parts, optionsWithDefaults); this.name = name; this.customId = optionsWithDefaults.customId; this.lastModified = optionsWithDefaults.lastModified; } }; //#endregion //#region src/_internal/upload-server.ts const uploadWithoutProgress = (file, presigned) => effect_Effect.gen(function* () { const formData = new FormData(); formData.append("file", file); const httpClient = (yield* __effect_platform_HttpClient.HttpClient).pipe(__effect_platform_HttpClient.filterStatusOk); const json = yield* __effect_platform_HttpClientRequest.put(presigned.url).pipe(__effect_platform_HttpClientRequest.bodyFormData(formData), __effect_platform_HttpClientRequest.setHeader("Range", "bytes=0-"), __effect_platform_HttpClientRequest.setHeader("x-uploadthing-version", require_package.version), httpClient.execute, effect_Effect.tapError(require_upload_builder.logHttpClientError("Failed to upload file")), effect_Effect.mapError((e) => new __uploadthing_shared.UploadThingError({ code: "UPLOAD_FAILED", message: "Failed to upload file", cause: e })), effect_Effect.andThen((_) => _.json), effect_Effect.andThen(effect_Function.unsafeCoerce), effect_Effect.scoped); yield* effect_Effect.logDebug(`File ${file.name} uploaded successfully`).pipe(effect_Effect.annotateLogs("json", json)); return { ...json, get url() { require_deprecations.logDeprecationWarning("`file.url` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead."); return json.url; }, get appUrl() { require_deprecations.logDeprecationWarning("`file.appUrl` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead."); return json.appUrl; } }; }); //#endregion //#region src/sdk/utils.ts function guardServerOnly() { if (typeof window !== "undefined") throw new __uploadthing_shared.UploadThingError({ code: "INTERNAL_SERVER_ERROR", message: "The `utapi` can only be used on the server." }); } const downloadFile = (_url) => effect_Effect.gen(function* () { let url = effect_Predicate.isRecord(_url) ? _url.url : _url; if (typeof url === "string") { if (url.startsWith("data:")) return yield* effect_Effect.fail({ code: "BAD_REQUEST", message: "Please use uploadFiles() for data URLs. uploadFilesFromUrl() is intended for use with remote URLs only.", data: void 0 }); } url = new URL(url); const { name = url.pathname.split("/").pop() ?? "unknown-filename", customId = void 0 } = effect_Predicate.isRecord(_url) ? _url : {}; const httpClient = (yield* __effect_platform_HttpClient.HttpClient).pipe(__effect_platform_HttpClient.filterStatusOk); const arrayBuffer = yield* __effect_platform_HttpClientRequest.get(url).pipe(__effect_platform_HttpClientRequest.modify({ headers: {} }), httpClient.execute, effect_Effect.flatMap((_) => _.arrayBuffer), effect_Effect.mapError((cause) => { return { code: "BAD_REQUEST", message: `Failed to download requested file: ${cause.message}`, data: cause.toJSON() }; }), effect_Effect.scoped); return new UTFile([arrayBuffer], name, { customId, lastModified: Date.now() }); }).pipe(effect_Effect.withLogSpan("downloadFile")); const generatePresignedUrl = (file, cd, acl) => effect_Effect.gen(function* () { const { apiKey, appId } = yield* require_upload_builder.UTToken; const baseUrl = yield* require_upload_builder.IngestUrl(void 0); const key = yield* (0, __uploadthing_shared.generateKey)(file, appId); const url = yield* (0, __uploadthing_shared.generateSignedURL)(`${baseUrl}/${key}`, apiKey, { data: { "x-ut-identifier": appId, "x-ut-file-name": file.name, "x-ut-file-size": file.size, "x-ut-file-type": file.type, "x-ut-custom-id": file.customId, "x-ut-content-disposition": cd, "x-ut-acl": acl } }); return { url, key }; }).pipe(effect_Effect.withLogSpan("generatePresignedUrl")); const uploadFile = (file, opts) => effect_Effect.gen(function* () { const presigned = yield* generatePresignedUrl(file, opts.contentDisposition ?? "inline", opts.acl).pipe(effect_Effect.catchTag("UploadThingError", (e) => effect_Effect.fail(__uploadthing_shared.UploadThingError.toObject(e))), effect_Effect.catchTag("ConfigError", () => effect_Effect.fail({ code: "INVALID_SERVER_CONFIG", message: "Failed to generate presigned URL" }))); const response = yield* uploadWithoutProgress(file, presigned).pipe(effect_Effect.catchTag("UploadThingError", (e) => effect_Effect.fail(__uploadthing_shared.UploadThingError.toObject(e))), effect_Effect.catchTag("ResponseError", (e) => effect_Effect.fail({ code: "UPLOAD_FAILED", message: "Failed to upload file", data: e.toJSON() }))); return { key: presigned.key, url: response.url, appUrl: response.appUrl, ufsUrl: response.ufsUrl, lastModified: file.lastModified ?? Date.now(), name: file.name, size: file.size, type: file.type, customId: file.customId ?? null, fileHash: response.fileHash }; }).pipe(effect_Effect.withLogSpan("uploadFile")); //#endregion //#region src/sdk/index.ts var UTApi = class { fetch; defaultKeyType; runtime; opts; constructor(options) { guardServerOnly(); this.opts = options ?? {}; this.fetch = this.opts.fetch ?? globalThis.fetch; this.defaultKeyType = this.opts.defaultKeyType ?? "fileKey"; this.runtime = require_upload_builder.makeRuntime(this.fetch, this.opts); } requestUploadThing = (pathname, body, responseSchema) => effect_Effect.gen(this, function* () { const { apiKey } = yield* require_upload_builder.UTToken; const baseUrl = yield* require_upload_builder.ApiUrl; const httpClient = (yield* __effect_platform_HttpClient.HttpClient).pipe(__effect_platform_HttpClient.filterStatusOk); return yield* __effect_platform_HttpClientRequest.post(pathname).pipe(__effect_platform_HttpClientRequest.prependUrl(baseUrl), __effect_platform_HttpClientRequest.bodyUnsafeJson(body), __effect_platform_HttpClientRequest.setHeaders({ "x-uploadthing-version": require_package.version, "x-uploadthing-be-adapter": "server-sdk", "x-uploadthing-api-key": effect_Redacted.value(apiKey) }), httpClient.execute, effect_Effect.tapBoth({ onSuccess: require_upload_builder.logHttpClientResponse("UploadThing API Response"), onFailure: require_upload_builder.logHttpClientError("Failed to request UploadThing API") }), effect_Effect.flatMap(__effect_platform_HttpClientResponse.schemaBodyJson(responseSchema)), effect_Effect.scoped); }).pipe(effect_Effect.catchTag("ConfigError", (e) => new __uploadthing_shared.UploadThingError({ code: "INVALID_SERVER_CONFIG", message: "There was an error with the server configuration. More info can be found on this error's `cause` property", cause: e })), effect_Effect.withLogSpan("utapi.#requestUploadThing")); executeAsync = async (program, signal) => { const exit = await program.pipe(effect_Effect.withLogSpan("utapi.#executeAsync"), (e) => this.runtime.runPromiseExit(e, signal ? { signal } : void 0)); if (exit._tag === "Failure") throw effect_Cause.squash(exit.cause); return exit.value; }; uploadFiles(files, opts) { guardServerOnly(); const concurrency = opts?.concurrency ?? 1; if (concurrency < 1 || concurrency > 25) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "concurrency must be a positive integer between 1 and 25" }); const program = effect_Effect.forEach(effect_Array.ensure(files), (file) => uploadFile(file, opts ?? {}).pipe(effect_Effect.match({ onSuccess: (data) => ({ data, error: null }), onFailure: (error) => ({ data: null, error }) })), { concurrency }).pipe(effect_Effect.map((ups) => Array.isArray(files) ? ups : ups[0]), effect_Effect.tap((res) => effect_Effect.logDebug("Finished uploading").pipe(effect_Effect.annotateLogs("uploadResult", res))), effect_Effect.withLogSpan("uploadFiles")); return this.executeAsync(program, opts?.signal); } uploadFilesFromUrl(urls, opts) { guardServerOnly(); const concurrency = opts?.concurrency ?? 1; if (concurrency < 1 || concurrency > 25) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "concurrency must be a positive integer between 1 and 25" }); const program = effect_Effect.forEach(effect_Array.ensure(urls), (url) => downloadFile(url).pipe(effect_Effect.flatMap((file) => uploadFile(file, opts ?? {})), effect_Effect.match({ onSuccess: (data) => ({ data, error: null }), onFailure: (error) => ({ data: null, error }) })), { concurrency }).pipe(effect_Effect.map((ups) => Array.isArray(urls) ? ups : ups[0]), effect_Effect.tap((res) => effect_Effect.logDebug("Finished uploading").pipe(effect_Effect.annotateLogs("uploadResult", res))), effect_Effect.withLogSpan("uploadFiles")).pipe(effect_Effect.withLogSpan("uploadFilesFromUrl")); return this.executeAsync(program, opts?.signal); } /** * Request to delete files from UploadThing storage. * @param {string | string[]} fileKeys * * @example * await deleteFiles("2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg"); * * @example * await deleteFiles(["2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg","1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg"]) * * @example * await deleteFiles("myCustomIdentifier", { keyType: "customId" }) */ deleteFiles = async (keys, opts) => { guardServerOnly(); const { keyType = this.defaultKeyType } = opts ?? {}; class DeleteFileResponse extends effect_Schema.Class("DeleteFileResponse")({ success: effect_Schema.Boolean, deletedCount: effect_Schema.Number }) {} return await this.executeAsync(this.requestUploadThing("/v6/deleteFiles", keyType === "fileKey" ? { fileKeys: effect_Array.ensure(keys) } : { customIds: effect_Array.ensure(keys) }, DeleteFileResponse).pipe(effect_Effect.withLogSpan("deleteFiles"))); }; /** * Request file URLs from UploadThing storage. * @param {string | string[]} fileKeys * * @example * const data = await getFileUrls("2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg"); * console.log(data); // [{key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg", url: "https://uploadthing.com/f/2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg"}] * * @example * const data = await getFileUrls(["2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg","1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg"]) * console.log(data) // [{key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg", url: "https://uploadthing.com/f/2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg" },{key: "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg", url: "https://uploadthing.com/f/1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg"}] * * @deprecated - See https://docs.uploadthing.com/working-with-files#accessing-files for info how to access files */ getFileUrls = async (keys, opts) => { guardServerOnly(); const { keyType = this.defaultKeyType } = opts ?? {}; class GetFileUrlResponse extends effect_Schema.Class("GetFileUrlResponse")({ data: effect_Schema.Array(effect_Schema.Struct({ key: effect_Schema.String, url: effect_Schema.String })) }) {} return await this.executeAsync(this.requestUploadThing("/v6/getFileUrl", keyType === "fileKey" ? { fileKeys: effect_Array.ensure(keys) } : { customIds: effect_Array.ensure(keys) }, GetFileUrlResponse).pipe(effect_Effect.withLogSpan("getFileUrls"))); }; /** * Request file list from UploadThing storage. * @param {object} opts * @param {number} opts.limit The maximum number of files to return * @param {number} opts.offset The number of files to skip * * @example * const data = await listFiles({ limit: 1 }); * console.log(data); // { key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg", id: "2e0fdb64-9957-4262-8e45-f372ba903ac8" } */ listFiles = async (opts) => { guardServerOnly(); class ListFileResponse extends effect_Schema.Class("ListFileResponse")({ hasMore: effect_Schema.Boolean, files: effect_Schema.Array(effect_Schema.Struct({ id: effect_Schema.String, customId: effect_Schema.NullOr(effect_Schema.String), key: effect_Schema.String, name: effect_Schema.String, size: effect_Schema.Number, status: effect_Schema.Literal("Deletion Pending", "Failed", "Uploaded", "Uploading"), uploadedAt: effect_Schema.Number })) }) {} return await this.executeAsync(this.requestUploadThing("/v6/listFiles", { ...opts }, ListFileResponse).pipe(effect_Effect.withLogSpan("listFiles"))); }; renameFiles = async (updates) => { guardServerOnly(); class RenameFileResponse extends effect_Schema.Class("RenameFileResponse")({ success: effect_Schema.Boolean }) {} return await this.executeAsync(this.requestUploadThing("/v6/renameFiles", { updates: effect_Array.ensure(updates) }, RenameFileResponse).pipe(effect_Effect.withLogSpan("renameFiles"))); }; getUsageInfo = async () => { guardServerOnly(); class GetUsageInfoResponse extends effect_Schema.Class("GetUsageInfoResponse")({ totalBytes: effect_Schema.Number, appTotalBytes: effect_Schema.Number, filesUploaded: effect_Schema.Number, limitBytes: effect_Schema.Number }) {} return await this.executeAsync(this.requestUploadThing("/v6/getUsageInfo", {}, GetUsageInfoResponse).pipe(effect_Effect.withLogSpan("getUsageInfo"))); }; /** * Generate a presigned url for a private file * Unlike {@link getSignedURL}, this method does not make a fetch request to the UploadThing API * and is the recommended way to generate a presigned url for a private file. **/ generateSignedURL = async (key, opts) => { guardServerOnly(); const expiresIn = (0, __uploadthing_shared.parseTimeToSeconds)(opts?.expiresIn ?? "5 minutes"); if (opts?.expiresIn && isNaN(expiresIn)) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "expiresIn must be a valid time string, for example '1d', '2 days', or a number of seconds." }); if (expiresIn > 86400 * 7) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "expiresIn must be less than 7 days (604800 seconds)." }); const program = effect_Effect.gen(function* () { const { apiKey, appId } = yield* require_upload_builder.UTToken; const appIdLocation = yield* require_upload_builder.UfsAppIdLocation; const ufsHost = yield* require_upload_builder.UfsHost; const proto = ufsHost.includes("local") ? "http" : "https"; const urlBase = appIdLocation === "subdomain" ? `${proto}://${appId}.${ufsHost}/f/${key}` : `${proto}://${ufsHost}/a/${appId}/${key}`; const ufsUrl = yield* (0, __uploadthing_shared.generateSignedURL)(urlBase, apiKey, { ttlInSeconds: expiresIn }); return { ufsUrl }; }); return await this.executeAsync(program.pipe(effect_Effect.catchTag("ConfigError", (e) => new __uploadthing_shared.UploadThingError({ code: "INVALID_SERVER_CONFIG", message: "There was an error with the server configuration. More info can be found on this error's `cause` property", cause: e })), effect_Effect.withLogSpan("generateSignedURL"))); }; /** * Request a presigned url for a private file(s) * @remarks This method is no longer recommended as it makes a fetch * request to the UploadThing API which incurs redundant latency. It * will be deprecated in UploadThing v8 and removed in UploadThing v9. * * @see {@link generateSignedURL} for a more efficient way to generate a presigned url **/ getSignedURL = async (key, opts) => { guardServerOnly(); const expiresIn = opts?.expiresIn ? (0, __uploadthing_shared.parseTimeToSeconds)(opts.expiresIn) : void 0; const { keyType = this.defaultKeyType } = opts ?? {}; if (opts?.expiresIn && isNaN(expiresIn)) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "expiresIn must be a valid time string, for example '1d', '2 days', or a number of seconds." }); if (expiresIn && expiresIn > 86400 * 7) throw new __uploadthing_shared.UploadThingError({ code: "BAD_REQUEST", message: "expiresIn must be less than 7 days (604800 seconds)." }); class GetSignedUrlResponse extends effect_Schema.Class("GetSignedUrlResponse")({ url: effect_Schema.String, ufsUrl: effect_Schema.String }) {} return await this.executeAsync(this.requestUploadThing("/v6/requestFileAccess", keyType === "fileKey" ? { fileKey: key, expiresIn } : { customId: key, expiresIn }, GetSignedUrlResponse).pipe(effect_Effect.withLogSpan("getSignedURL"))); }; /** * Update the ACL of a file or set of files. * * @example * // Make a single file public * await utapi.updateACL("2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg", "public-read"); * * // Make multiple files private * await utapi.updateACL( * [ * "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg", * "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg", * ], * "private", * ); */ updateACL = async (keys, acl, opts) => { guardServerOnly(); const { keyType = this.defaultKeyType } = opts ?? {}; const updates = effect_Array.ensure(keys).map((key) => { return keyType === "fileKey" ? { fileKey: key, acl } : { customId: key, acl }; }); const responseSchema = effect_Schema.Struct({ success: effect_Schema.Boolean }); return await this.executeAsync(this.requestUploadThing("/v6/updateACL", { updates }, responseSchema).pipe(effect_Effect.withLogSpan("updateACL"))); }; }; //#endregion //#region src/server.ts const createUploadthing = (opts) => require_upload_builder.createBuilder(opts); const createRouteHandler = (opts) => { return require_upload_builder.makeAdapterHandler((ev) => effect_Effect.succeed({ req: "request" in ev ? ev.request : ev }), (ev) => effect_Effect.succeed("request" in ev ? ev.request : ev), opts, "server"); }; const extractRouterConfig$1 = (router) => effect_Effect.runSync(require_upload_builder.extractRouterConfig(router)); //#endregion exports.UTApi = UTApi; exports.UTFile = UTFile; exports.UTFiles = require_upload_builder.UTFiles; exports.UploadThingError = __uploadthing_shared.UploadThingError; exports.createBuilder = require_upload_builder.createBuilder; exports.createRouteHandler = createRouteHandler; exports.createUploadthing = createUploadthing; exports.experimental_UTRegion = require_upload_builder.UTRegion; exports.extractRouterConfig = extractRouterConfig$1; exports.makeAdapterHandler = require_upload_builder.makeAdapterHandler;